fix(leonardo): cookie 写回改 CAS,并补上额度对账后的轮换写回
Leonardo 只认加密的 session_data 缓存,轮换值一旦没存住账号就会被判死。写回原先是无条件 update:长任务(生成、慢额度查询)手里的旧 cookie 完成时会盖掉期间 keepalive 存好的新值。改为 SwapValue(WHERE value = 旧值 才写),旧值无法覆盖新值;reconcileLeonardoCredits 原先完全没写回 FetchCreditsBalance 带回的轮换值,补上。
This commit is contained in:
@@ -87,6 +87,23 @@ func (r *TokenRepository) Update(ctx context.Context, pool, id string, patch map
|
|||||||
return r.Get(ctx, pool, id)
|
return r.Get(ctx, pool, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SwapValue replaces an account's credential only while the stored one is still
|
||||||
|
// the value the caller started from. A rotating cookie is minted from whatever
|
||||||
|
// was in the row, so a goroutine that has been holding an older copy (a long
|
||||||
|
// render, a slow quota probe) must NOT be allowed to write it back over a newer
|
||||||
|
// rotation — that older copy no longer authenticates, and the account then looks
|
||||||
|
// dead. Reports whether the row was updated.
|
||||||
|
func (r *TokenRepository) SwapValue(ctx context.Context, pool, id, from, to string) (bool, error) {
|
||||||
|
res := r.db.WithContext(ctx).
|
||||||
|
Model(&model.TokenAccount{}).
|
||||||
|
Where("pool = ? AND id = ? AND value = ?", pool, id, from).
|
||||||
|
Updates(map[string]any{"value": to, "updated_at": time.Now()})
|
||||||
|
if res.Error != nil {
|
||||||
|
return false, res.Error
|
||||||
|
}
|
||||||
|
return res.RowsAffected > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ReserveQuota atomically pre-deducts `amount` from an account's cached image
|
// ReserveQuota atomically pre-deducts `amount` from an account's cached image
|
||||||
// token balance under a row lock, so concurrent picks of the same near-empty
|
// token balance under a row lock, so concurrent picks of the same near-empty
|
||||||
// account can never over-commit it. Returns:
|
// account can never over-commit it. Returns:
|
||||||
|
|||||||
@@ -195,10 +195,10 @@ func (s *TokenService) RefreshLeonardoSessions(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
continue // leave the stamp alone so the next tick retries
|
continue // leave the stamp alone so the next tick retries
|
||||||
}
|
}
|
||||||
fields := map[string]any{}
|
|
||||||
if fresh, ok := s.leonardo.RotatedCookie(a.Value); ok && strings.TrimSpace(fresh) != "" {
|
if fresh, ok := s.leonardo.RotatedCookie(a.Value); ok && strings.TrimSpace(fresh) != "" {
|
||||||
fields["value"] = fresh
|
_, _ = s.tokens.SwapValue(callCtx, "leonardo", a.ID, a.Value, fresh)
|
||||||
}
|
}
|
||||||
|
fields := map[string]any{}
|
||||||
meta := cloneJSONMap(a.Meta)
|
meta := cloneJSONMap(a.Meta)
|
||||||
meta[leonardoKeptAtKey] = int(time.Now().Unix())
|
meta[leonardoKeptAtKey] = int(time.Now().Unix())
|
||||||
fields["meta"] = meta
|
fields["meta"] = meta
|
||||||
@@ -459,7 +459,7 @@ func (s *TokenService) persistLeonardoCookie(ctx context.Context, tokenID, cooki
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if fresh, ok := s.leonardo.RotatedCookie(cookie); ok && strings.TrimSpace(fresh) != "" {
|
if fresh, ok := s.leonardo.RotatedCookie(cookie); ok && strings.TrimSpace(fresh) != "" {
|
||||||
_, _ = s.tokens.Update(ctx, "leonardo", tokenID, map[string]any{"value": fresh})
|
_, _ = s.tokens.SwapValue(ctx, "leonardo", tokenID, cookie, fresh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3046,7 +3046,7 @@ func (s *V1Service) leonardoPersistCookie(ctx context.Context, tokenID, cookie s
|
|||||||
if !ok || strings.TrimSpace(fresh) == "" {
|
if !ok || strings.TrimSpace(fresh) == "" {
|
||||||
return cookie
|
return cookie
|
||||||
}
|
}
|
||||||
_, _ = s.tokens.Update(ctx, "leonardo", tokenID, map[string]any{"value": fresh})
|
_, _ = s.tokens.SwapValue(ctx, "leonardo", tokenID, cookie, fresh)
|
||||||
return fresh
|
return fresh
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3058,6 +3058,7 @@ func (s *V1Service) reconcileLeonardoCredits(ctx context.Context, tokenID, cooki
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := s.leonardo.FetchCreditsBalance(ctx, cookie)
|
data, err := s.leonardo.FetchCreditsBalance(ctx, cookie)
|
||||||
|
s.leonardoPersistCookie(ctx, tokenID, cookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user