mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 21:10:51 +08:00
feat: enhance quota management with backoff levels and cooldown logic
This commit is contained in:
@@ -40,6 +40,8 @@ const (
|
|||||||
refreshCheckInterval = 5 * time.Second
|
refreshCheckInterval = 5 * time.Second
|
||||||
refreshPendingBackoff = time.Minute
|
refreshPendingBackoff = time.Minute
|
||||||
refreshFailureBackoff = 5 * time.Minute
|
refreshFailureBackoff = 5 * time.Minute
|
||||||
|
quotaBackoffBase = time.Second
|
||||||
|
quotaBackoffMax = 30 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
// Result captures execution outcome used to adjust auth state.
|
// Result captures execution outcome used to adjust auth state.
|
||||||
@@ -532,9 +534,15 @@ func (m *Manager) MarkResult(ctx context.Context, result Result) {
|
|||||||
suspendReason = "payment_required"
|
suspendReason = "payment_required"
|
||||||
shouldSuspendModel = true
|
shouldSuspendModel = true
|
||||||
case 429:
|
case 429:
|
||||||
next := now.Add(30 * time.Minute)
|
cooldown, nextLevel := nextQuotaCooldown(state.Quota.BackoffLevel)
|
||||||
|
next := now.Add(cooldown)
|
||||||
state.NextRetryAfter = next
|
state.NextRetryAfter = next
|
||||||
state.Quota = QuotaState{Exceeded: true, Reason: "quota", NextRecoverAt: next}
|
state.Quota = QuotaState{
|
||||||
|
Exceeded: true,
|
||||||
|
Reason: "quota",
|
||||||
|
NextRecoverAt: next,
|
||||||
|
BackoffLevel: nextLevel,
|
||||||
|
}
|
||||||
suspendReason = "quota"
|
suspendReason = "quota"
|
||||||
shouldSuspendModel = true
|
shouldSuspendModel = true
|
||||||
setModelQuota = true
|
setModelQuota = true
|
||||||
@@ -608,6 +616,7 @@ func updateAggregatedAvailability(auth *Auth, now time.Time) {
|
|||||||
earliestRetry := time.Time{}
|
earliestRetry := time.Time{}
|
||||||
quotaExceeded := false
|
quotaExceeded := false
|
||||||
quotaRecover := time.Time{}
|
quotaRecover := time.Time{}
|
||||||
|
maxBackoffLevel := 0
|
||||||
for _, state := range auth.ModelStates {
|
for _, state := range auth.ModelStates {
|
||||||
if state == nil {
|
if state == nil {
|
||||||
continue
|
continue
|
||||||
@@ -636,6 +645,9 @@ func updateAggregatedAvailability(auth *Auth, now time.Time) {
|
|||||||
if quotaRecover.IsZero() || (!state.Quota.NextRecoverAt.IsZero() && state.Quota.NextRecoverAt.Before(quotaRecover)) {
|
if quotaRecover.IsZero() || (!state.Quota.NextRecoverAt.IsZero() && state.Quota.NextRecoverAt.Before(quotaRecover)) {
|
||||||
quotaRecover = state.Quota.NextRecoverAt
|
quotaRecover = state.Quota.NextRecoverAt
|
||||||
}
|
}
|
||||||
|
if state.Quota.BackoffLevel > maxBackoffLevel {
|
||||||
|
maxBackoffLevel = state.Quota.BackoffLevel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auth.Unavailable = allUnavailable
|
auth.Unavailable = allUnavailable
|
||||||
@@ -648,10 +660,12 @@ func updateAggregatedAvailability(auth *Auth, now time.Time) {
|
|||||||
auth.Quota.Exceeded = true
|
auth.Quota.Exceeded = true
|
||||||
auth.Quota.Reason = "quota"
|
auth.Quota.Reason = "quota"
|
||||||
auth.Quota.NextRecoverAt = quotaRecover
|
auth.Quota.NextRecoverAt = quotaRecover
|
||||||
|
auth.Quota.BackoffLevel = maxBackoffLevel
|
||||||
} else {
|
} else {
|
||||||
auth.Quota.Exceeded = false
|
auth.Quota.Exceeded = false
|
||||||
auth.Quota.Reason = ""
|
auth.Quota.Reason = ""
|
||||||
auth.Quota.NextRecoverAt = time.Time{}
|
auth.Quota.NextRecoverAt = time.Time{}
|
||||||
|
auth.Quota.BackoffLevel = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,6 +699,7 @@ func clearAuthStateOnSuccess(auth *Auth, now time.Time) {
|
|||||||
auth.Quota.Exceeded = false
|
auth.Quota.Exceeded = false
|
||||||
auth.Quota.Reason = ""
|
auth.Quota.Reason = ""
|
||||||
auth.Quota.NextRecoverAt = time.Time{}
|
auth.Quota.NextRecoverAt = time.Time{}
|
||||||
|
auth.Quota.BackoffLevel = 0
|
||||||
auth.LastError = nil
|
auth.LastError = nil
|
||||||
auth.NextRetryAfter = time.Time{}
|
auth.NextRetryAfter = time.Time{}
|
||||||
auth.UpdatedAt = now
|
auth.UpdatedAt = now
|
||||||
@@ -734,7 +749,9 @@ func applyAuthFailureState(auth *Auth, resultErr *Error, now time.Time) {
|
|||||||
auth.StatusMessage = "quota exhausted"
|
auth.StatusMessage = "quota exhausted"
|
||||||
auth.Quota.Exceeded = true
|
auth.Quota.Exceeded = true
|
||||||
auth.Quota.Reason = "quota"
|
auth.Quota.Reason = "quota"
|
||||||
auth.Quota.NextRecoverAt = now.Add(30 * time.Minute)
|
cooldown, nextLevel := nextQuotaCooldown(auth.Quota.BackoffLevel)
|
||||||
|
auth.Quota.NextRecoverAt = now.Add(cooldown)
|
||||||
|
auth.Quota.BackoffLevel = nextLevel
|
||||||
auth.NextRetryAfter = auth.Quota.NextRecoverAt
|
auth.NextRetryAfter = auth.Quota.NextRecoverAt
|
||||||
case 408, 500, 502, 503, 504:
|
case 408, 500, 502, 503, 504:
|
||||||
auth.StatusMessage = "transient upstream error"
|
auth.StatusMessage = "transient upstream error"
|
||||||
@@ -746,6 +763,21 @@ func applyAuthFailureState(auth *Auth, resultErr *Error, now time.Time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nextQuotaCooldown returns the next cooldown duration and updated backoff level for repeated quota errors.
|
||||||
|
func nextQuotaCooldown(prevLevel int) (time.Duration, int) {
|
||||||
|
if prevLevel < 0 {
|
||||||
|
prevLevel = 0
|
||||||
|
}
|
||||||
|
cooldown := quotaBackoffBase * time.Duration(1<<prevLevel)
|
||||||
|
if cooldown < quotaBackoffBase {
|
||||||
|
cooldown = quotaBackoffBase
|
||||||
|
}
|
||||||
|
if cooldown >= quotaBackoffMax {
|
||||||
|
return quotaBackoffMax, prevLevel
|
||||||
|
}
|
||||||
|
return cooldown, prevLevel + 1
|
||||||
|
}
|
||||||
|
|
||||||
// List returns all auth entries currently known by the manager.
|
// List returns all auth entries currently known by the manager.
|
||||||
func (m *Manager) List() []*Auth {
|
func (m *Manager) List() []*Auth {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ type QuotaState struct {
|
|||||||
Reason string `json:"reason,omitempty"`
|
Reason string `json:"reason,omitempty"`
|
||||||
// NextRecoverAt is when the credential may become available again.
|
// NextRecoverAt is when the credential may become available again.
|
||||||
NextRecoverAt time.Time `json:"next_recover_at"`
|
NextRecoverAt time.Time `json:"next_recover_at"`
|
||||||
|
// BackoffLevel stores the progressive cooldown exponent used for rate limits.
|
||||||
|
BackoffLevel int `json:"backoff_level,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModelState captures the execution state for a specific model under an auth entry.
|
// ModelState captures the execution state for a specific model under an auth entry.
|
||||||
|
|||||||
Reference in New Issue
Block a user