Documentation
¶
Overview ¶
Package retry wraps an http.RoundTripper with retries on transient failures (5xx responses, network errors, transport-level deadline exceeded).
The default predicate retries idempotent methods (GET, HEAD, OPTIONS, PUT, DELETE) on {500, 502, 503, 504} or recognised transient network errors. Known-permanent failures (DNS NXDOMAIN, ECONNREFUSED, x509 validation errors) are explicitly NOT retried even when wrapped in a *net.OpError, so a misconfigured URL or expired cert fails fast instead of burning the retry budget.
429 Too Many Requests is hard-excluded so callers can compose retry with the ratelimit package without the two layers fighting.
Backoff uses decorrelated jitter with caller-supplied min/max bounds. Server-supplied Retry-After (delta-seconds or HTTP-date per RFC 9110 section 10.2.3) overrides the jitter; if it exceeds the operator's maxDelay the call returns (nil, ErrRetryAfterExceedsMax) and the transport drains and closes the prior response itself. When the header is present but unparseable (off-spec date format, RFC 3339 / ISO 8601, garbage), retry falls back to the jitter sleep, the retry_sleep event labels source as "malformed", and a retry_retry_after_unparseable event is emitted at Warn level.
RetryAfter is the parser exposed for sibling packages (e.g. polling) that want to honor server hints without duplicating the parse logic. It returns (0, false) for absent, unparseable, and negative-numeric values; (d, true) otherwise.
Caller-context cancellation is terminal: req.Context().Err() != nil stops retries before any predicate is consulted, including a user-supplied WithRetryOn.
Body-bearing requests must set req.GetBody so the body can be rewound per attempt; a nil GetBody on a retry attempt yields errors.Join(ErrBodyNotRewindable, prior_err).
Throttle interaction: each retry attempt is a real HTTP call from the throttle layer's perspective, so a worst-case failing request can briefly use maxAttempts times the nominal RPS budget.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrBodyNotRewindable = errors.New("retry: cannot retry request with non-nil Body and nil GetBody")
ErrBodyNotRewindable is joined with the prior error when a retry attempt is needed for a body-bearing request with no GetBody.
var ErrInvalidBackoff = errors.New("retry: backoff requires 1ms <= minDelay <= maxDelay <= 1h")
ErrInvalidBackoff is returned when WithBackoff bounds are invalid.
var ErrInvalidMaxAttempts = errors.New("retry: maxAttempts must be in [1, 100]")
ErrInvalidMaxAttempts is returned when WithMaxAttempts is out of range.
var ErrRetryAfterExceedsMax = errors.New("retry: server Retry-After exceeds operator max delay")
ErrRetryAfterExceedsMax is returned when a server's Retry-After header exceeds the operator-configured maxDelay. When this error is returned, resp is nil; the transport drains and closes the prior response before returning, satisfying the http.RoundTripper invariant that a non-nil error implies the caller has nothing to close.
Functions ¶
func IsIdempotent ¶
IsIdempotent reports whether a request method can be retried by default. Per RFC 7231 these are GET, HEAD, OPTIONS, PUT, DELETE.
func IsRetryable5xx ¶
IsRetryable5xx reports whether a 5xx code is transient (worth retrying). 501 Not Implemented and 505 HTTP Version Not Supported are permanent.
func IsTransientNetErr ¶
IsTransientNetErr reports whether a transport-level error is worth retrying. Known-permanent failures (NXDOMAIN, ECONNREFUSED, x509 validation errors) short-circuit to false so we don't waste budget on them. Otherwise *net.OpError, io.EOF/io.ErrUnexpectedEOF, and context.DeadlineExceeded from the inner transport are treated as transient.
func NewTransport ¶
func NewTransport(base http.RoundTripper, opts ...Option) (http.RoundTripper, error)
NewTransport returns a retry middleware wrapping base. When base is nil, http.DefaultTransport is used.
Returns ErrInvalidMaxAttempts or ErrInvalidBackoff at construction when the option combination is out of bounds.
func RetryAfter ¶ added in v1.5.0
RetryAfter parses a response's Retry-After header per RFC 9110 §10.2.3 and reports whether it should be honored as a sleep override. Returns (0, false) for absent, unparseable, OR negative-numeric values; returns the duration plus true otherwise. Used by sibling packages that want to honor server hints without duplicating the parser.
Types ¶
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option configures a Transport.
func WithBackoff ¶
WithBackoff sets the decorrelated-jitter window. Defaults: 200ms / 2s. Constraints: 1ms <= minDelay <= maxDelay <= 1h. The 1ms floor prevents sub-millisecond intervals that would hammer the server; the 1h ceiling keeps prev*3 well within int64 in computeJitter.
func WithLogger ¶
WithLogger supplies the slog.Logger for diagnostic events. Pass nil (or omit the option) to silence the package; a nil logger is replaced with slog.New(slog.DiscardHandler) at construction.
func WithMaxAttempts ¶
WithMaxAttempts sets the total number of attempts including the initial request. n=1 disables retries (single attempt only). n must be in [1, 100]; values outside that range return ErrInvalidMaxAttempts at construction. The 100 ceiling guards against misconfigured env vars (e.g. MAX_RETRIES=10000 instead of 10) blocking goroutines for hours.
func WithRetryOn ¶
WithRetryOn replaces the default retry predicate. The replacement takes ownership of method-safety: the default's idempotency check is bypassed. The 429 hard-exclusion in RoundTrip still applies regardless of this predicate. Caller-context cancellation (req.Context().Err() != nil) always stops retries before the predicate is consulted.
Panics in the predicate are recovered: the call is treated as "do not retry" and a retry_predicate_panic event is logged.