ccm,ocm: add exponential backoff with cap for poll retry

Replace flat 1-minute poll retry interval with exponential backoff
(1m → 2m → 4m → 5m cap). Suppress error logs after reaching the cap.
This commit is contained in:
世界
2026-03-14 18:26:51 +08:00
parent 4d907bc49d
commit d1e5426bc8
4 changed files with 68 additions and 10 deletions
+15 -2
View File
@@ -529,7 +529,9 @@ func (c *externalCredential) pollUsage(ctx context.Context) {
return request, nil
})
if err != nil {
c.logger.Error("poll usage for ", c.tag, ": ", err)
if !c.isPollBackoffAtCap() {
c.logger.Error("poll usage for ", c.tag, ": ", err)
}
c.incrementPollFailures()
return
}
@@ -609,7 +611,18 @@ func (c *externalCredential) pollBackoff(baseInterval time.Duration) time.Durati
if failures <= 0 {
return baseInterval
}
return failedPollRetryInterval
backoff := failedPollRetryInterval * time.Duration(1<<(failures-1))
if backoff > httpRetryMaxBackoff {
return httpRetryMaxBackoff
}
return backoff
}
func (c *externalCredential) isPollBackoffAtCap() bool {
c.stateMutex.RLock()
defer c.stateMutex.RUnlock()
failures := c.state.consecutivePollFailures
return failures > 0 && failedPollRetryInterval*time.Duration(1<<(failures-1)) >= httpRetryMaxBackoff
}
func (c *externalCredential) incrementPollFailures() {
+19 -3
View File
@@ -29,6 +29,7 @@ import (
const (
defaultPollInterval = 60 * time.Minute
failedPollRetryInterval = time.Minute
httpRetryMaxBackoff = 5 * time.Minute
)
const (
@@ -583,7 +584,18 @@ func (c *defaultCredential) pollBackoff(baseInterval time.Duration) time.Duratio
if failures <= 0 {
return baseInterval
}
return failedPollRetryInterval
backoff := failedPollRetryInterval * time.Duration(1<<(failures-1))
if backoff > httpRetryMaxBackoff {
return httpRetryMaxBackoff
}
return backoff
}
func (c *defaultCredential) isPollBackoffAtCap() bool {
c.stateMutex.RLock()
defer c.stateMutex.RUnlock()
failures := c.state.consecutivePollFailures
return failures > 0 && failedPollRetryInterval*time.Duration(1<<(failures-1)) >= httpRetryMaxBackoff
}
func (c *defaultCredential) earliestReset() time.Time {
@@ -616,7 +628,9 @@ func (c *defaultCredential) pollUsage(ctx context.Context) {
accessToken, err := c.getAccessToken()
if err != nil {
c.logger.Error("poll usage for ", c.tag, ": get token: ", err)
if !c.isPollBackoffAtCap() {
c.logger.Error("poll usage for ", c.tag, ": get token: ", err)
}
c.incrementPollFailures()
return
}
@@ -638,7 +652,9 @@ func (c *defaultCredential) pollUsage(ctx context.Context) {
return request, nil
})
if err != nil {
c.logger.Error("poll usage for ", c.tag, ": ", err)
if !c.isPollBackoffAtCap() {
c.logger.Error("poll usage for ", c.tag, ": ", err)
}
c.incrementPollFailures()
return
}
+15 -2
View File
@@ -568,7 +568,9 @@ func (c *externalCredential) pollUsage(ctx context.Context) {
return request, nil
})
if err != nil {
c.logger.Error("poll usage for ", c.tag, ": ", err)
if !c.isPollBackoffAtCap() {
c.logger.Error("poll usage for ", c.tag, ": ", err)
}
c.incrementPollFailures()
return
}
@@ -648,7 +650,18 @@ func (c *externalCredential) pollBackoff(baseInterval time.Duration) time.Durati
if failures <= 0 {
return baseInterval
}
return failedPollRetryInterval
backoff := failedPollRetryInterval * time.Duration(1<<(failures-1))
if backoff > httpRetryMaxBackoff {
return httpRetryMaxBackoff
}
return backoff
}
func (c *externalCredential) isPollBackoffAtCap() bool {
c.stateMutex.RLock()
defer c.stateMutex.RUnlock()
failures := c.state.consecutivePollFailures
return failures > 0 && failedPollRetryInterval*time.Duration(1<<(failures-1)) >= httpRetryMaxBackoff
}
func (c *externalCredential) incrementPollFailures() {
+19 -3
View File
@@ -29,6 +29,7 @@ import (
const (
defaultPollInterval = 60 * time.Minute
failedPollRetryInterval = time.Minute
httpRetryMaxBackoff = 5 * time.Minute
)
const (
@@ -597,7 +598,18 @@ func (c *defaultCredential) pollBackoff(baseInterval time.Duration) time.Duratio
if failures <= 0 {
return baseInterval
}
return failedPollRetryInterval
backoff := failedPollRetryInterval * time.Duration(1<<(failures-1))
if backoff > httpRetryMaxBackoff {
return httpRetryMaxBackoff
}
return backoff
}
func (c *defaultCredential) isPollBackoffAtCap() bool {
c.stateMutex.RLock()
defer c.stateMutex.RUnlock()
failures := c.state.consecutivePollFailures
return failures > 0 && failedPollRetryInterval*time.Duration(1<<(failures-1)) >= httpRetryMaxBackoff
}
func (c *defaultCredential) earliestReset() time.Time {
@@ -633,7 +645,9 @@ func (c *defaultCredential) pollUsage(ctx context.Context) {
accessToken, err := c.getAccessToken()
if err != nil {
c.logger.Error("poll usage for ", c.tag, ": get token: ", err)
if !c.isPollBackoffAtCap() {
c.logger.Error("poll usage for ", c.tag, ": get token: ", err)
}
c.incrementPollFailures()
return
}
@@ -663,7 +677,9 @@ func (c *defaultCredential) pollUsage(ctx context.Context) {
return request, nil
})
if err != nil {
c.logger.Error("poll usage for ", c.tag, ": ", err)
if !c.isPollBackoffAtCap() {
c.logger.Error("poll usage for ", c.tag, ": ", err)
}
c.incrementPollFailures()
return
}