diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index 028b70c1..df44c855 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{}) @@ -1878,11 +1881,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()