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) overrides the jitter; if it exceeds the operator's maxDelay the call returns ErrRetryAfterExceedsMax with the prior response (which the caller must Close).
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 ( ErrInvalidMaxAttempts = errors.New("retry: maxAttempts must be in [1, 100]") ErrInvalidBackoff = errors.New("retry: backoff requires 1ms <= minDelay <= maxDelay <= 1h") ErrBodyNotRewindable = errors.New("retry: cannot retry request with non-nil Body and nil GetBody") ErrRetryAfterExceedsMax = errors.New("retry: server Retry-After exceeds operator max delay") )
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.
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.