Files
David Gageot 4fd44ed62d modelerrors: move backoff and sleep utilities to pkg/backoff
SleepWithContext and CalculateBackoff are general-purpose utilities not
specific to model errors. Move them to a new pkg/backoff package for
clarity and reusability. MaxRetryAfterWait moves there too.

Deprecated wrapper variables are left in modelerrors to maintain
backward compatibility. The runtime/fallback package now imports
pkg/backoff directly.

Assisted-By: docker-agent
2026-03-19 19:31:49 +01:00

61 lines
1.4 KiB
Go

// Package backoff provides exponential backoff calculation and
// context-aware sleep utilities.
package backoff
import (
"context"
"math/rand/v2"
"time"
)
// Configuration constants for exponential backoff.
const (
baseDelay = 200 * time.Millisecond
maxDelay = 2 * time.Second
factor = 2.0
jitter = 0.1
// MaxRetryAfterWait caps how long we'll honor a Retry-After header to prevent
// a misbehaving server from blocking the agent for an unreasonable amount of time.
MaxRetryAfterWait = 60 * time.Second
)
// Calculate returns the backoff duration for a given attempt (0-indexed).
// Uses exponential backoff with jitter.
func Calculate(attempt int) time.Duration {
if attempt < 0 {
attempt = 0
}
// Calculate exponential delay
delay := float64(baseDelay)
for range attempt {
delay *= factor
}
// Cap at max delay
if delay > float64(maxDelay) {
delay = float64(maxDelay)
}
// Add jitter (±10%)
j := delay * jitter * (2*rand.Float64() - 1)
delay += j
return time.Duration(delay)
}
// SleepWithContext sleeps for the specified duration, returning early if context is cancelled.
// Returns true if the sleep completed, false if it was interrupted by context cancellation.
func SleepWithContext(ctx context.Context, d time.Duration) bool {
timer := time.NewTimer(d)
defer timer.Stop()
select {
case <-timer.C:
return true
case <-ctx.Done():
return false
}
}