diff --git a/backend/internal/service/upstream_billing_probe.go b/backend/internal/service/upstream_billing_probe.go index adf3d25277..b34900f4f5 100644 --- a/backend/internal/service/upstream_billing_probe.go +++ b/backend/internal/service/upstream_billing_probe.go @@ -1105,6 +1105,12 @@ func (s *UpstreamBillingProbeService) currentTime() time.Time { } func nextProbeDelay(intervalMinutes int, retryAfterDuration time.Duration) time.Duration { + if retryAfterDuration < 0 { + retryAfterDuration = 0 + } + if retryAfterDuration > upstreamBillingProbeMaxDelay { + retryAfterDuration = upstreamBillingProbeMaxDelay + } interval := time.Duration(intervalMinutes) * time.Minute if interval < upstreamBillingProbeMinIntervalMinutes*time.Minute { interval = upstreamBillingProbeMinIntervalMinutes * time.Minute @@ -1120,8 +1126,8 @@ func nextProbeDelay(intervalMinutes int, retryAfterDuration time.Duration) time. interval += time.Duration(rand.Int64N(int64(jitterRange)*2+1)) - jitterRange } if retryAfterDuration > interval { - // Retry-After is an explicit upstream instruction; do not shorten it - // with the local maximum delay. + // Retry-After is an explicit upstream instruction; honor it over the + // local backoff interval, clamped to the same 24h ceiling. return retryAfterDuration } if interval > upstreamBillingProbeMaxDelay { @@ -1133,8 +1139,7 @@ func nextProbeDelay(intervalMinutes int, retryAfterDuration time.Duration) time. // unsupportedProbeDelay 拉长 unsupported 账号的重探间隔,让无效候选自然退出 // 热队列,不再和真正接入 sub2api 的中转账号抢每周期的探测名额。 // 仍按 upstreamBillingProbeMaxDelay 封顶,保证上游后来接入 sub2api 时最迟一天 -// 内会被重新发现;base 本身已达上限(例如 Retry-After 明确要求更久)时原样返回, -// 不缩短上游指令。 +// 内会被重新发现;base 本身亦受 24h 封顶保护。 func unsupportedProbeDelay(intervalMinutes int, retryAfterDuration time.Duration) time.Duration { base := nextProbeDelay(intervalMinutes, retryAfterDuration) if base >= upstreamBillingProbeMaxDelay { diff --git a/backend/internal/service/upstream_billing_probe_test.go b/backend/internal/service/upstream_billing_probe_test.go index 9a5f774f4f..68df096efe 100644 --- a/backend/internal/service/upstream_billing_probe_test.go +++ b/backend/internal/service/upstream_billing_probe_test.go @@ -767,13 +767,13 @@ func TestUpstreamBillingProbeFailurePreservesLastSuccessAndRetryAfter(t *testing require.Equal(t, initialRate, *account.RateMultiplier) } -func TestUpstreamBillingProbeRetryAfterIsNotShortened(t *testing.T) { +func TestUpstreamBillingProbeRetryAfterIsCappedAtMaxDelay(t *testing.T) { delay := nextProbeDelay(30, 48*time.Hour) - require.Equal(t, 48*time.Hour, delay) + require.Equal(t, upstreamBillingProbeMaxDelay, delay) } // unsupported 的重探间隔明显长于普通失败,但始终有上界:上游后来接入 sub2api -// 时最迟一天内会被重新发现,且不会缩短上游 Retry-After 指令。 +// 时最迟一天内会被重新发现,且上游超长 Retry-After 亦受 24h 封顶保护。 func TestUpstreamBillingProbeUnsupportedDelayIsStretchedAndBounded(t *testing.T) { // 默认 30 分钟 interval:普通失败 24~36 分钟,unsupported 为其 8 倍。 stretched := unsupportedProbeDelay(30, 0) @@ -785,8 +785,8 @@ func TestUpstreamBillingProbeUnsupportedDelayIsStretchedAndBounded(t *testing.T) require.LessOrEqual(t, unsupportedProbeDelay(upstreamBillingProbeMaxIntervalMinutes, 0), upstreamBillingProbeMaxDelay) require.Positive(t, unsupportedProbeDelay(upstreamBillingProbeMinIntervalMinutes, 0)) - // Retry-After 更长时原样保留,不被封顶缩短;更短时至少不早于该指令。 - require.Equal(t, 48*time.Hour, unsupportedProbeDelay(30, 48*time.Hour)) + // Retry-After 更长时受封顶保护,不超过 upstreamBillingProbeMaxDelay;更短时至少不早于该指令。 + require.Equal(t, upstreamBillingProbeMaxDelay, unsupportedProbeDelay(30, 48*time.Hour)) require.GreaterOrEqual(t, unsupportedProbeDelay(30, time.Hour), time.Hour) }