From 0aaf177640c7ca0e935932ec4a151c7cea1fe744 Mon Sep 17 00:00:00 2001 From: lyd123qw2008 <326643467@qq.com> Date: Mon, 23 Feb 2026 22:28:41 +0800 Subject: [PATCH] fix(auth): limit auto-refresh concurrency to prevent refresh storms --- sdk/cliproxy/auth/conductor.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index cd447e68..e1db2ee6 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -60,6 +60,7 @@ type RefreshEvaluator interface { const ( refreshCheckInterval = 5 * time.Second + refreshMaxConcurrency = 16 refreshPendingBackoff = time.Minute refreshFailureBackoff = 5 * time.Minute quotaBackoffBase = time.Second @@ -155,7 +156,8 @@ type Manager struct { rtProvider RoundTripperProvider // Auto refresh state - refreshCancel context.CancelFunc + refreshCancel context.CancelFunc + refreshSemaphore chan struct{} } // NewManager constructs a manager with optional custom selector and hook. @@ -173,6 +175,7 @@ func NewManager(store Store, selector Selector, hook Hook) *Manager { hook: hook, auths: make(map[string]*Auth), providerOffsets: make(map[string]int), + refreshSemaphore: make(chan struct{}, refreshMaxConcurrency), } // atomic.Value requires non-nil initial value. manager.runtimeConfig.Store(&internalconfig.Config{}) @@ -1880,11 +1883,25 @@ func (m *Manager) checkRefreshes(ctx context.Context) { if !m.markRefreshPending(a.ID, now) { continue } - go m.refreshAuth(ctx, a.ID) + go m.refreshAuthWithLimit(ctx, a.ID) } } } +func (m *Manager) refreshAuthWithLimit(ctx context.Context, id string) { + if m.refreshSemaphore == nil { + m.refreshAuth(ctx, id) + return + } + select { + case m.refreshSemaphore <- struct{}{}: + defer func() { <-m.refreshSemaphore }() + case <-ctx.Done(): + return + } + m.refreshAuth(ctx, id) +} + func (m *Manager) snapshotAuths() []*Auth { m.mu.RLock() defer m.mu.RUnlock()