retry

package
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 26, 2026 License: MIT Imports: 14 Imported by: 0

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

View Source
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

func IsIdempotent(method string) bool

IsIdempotent reports whether a request method can be retried by default. Per RFC 7231 these are GET, HEAD, OPTIONS, PUT, DELETE.

func IsRetryable5xx

func IsRetryable5xx(code int) bool

IsRetryable5xx reports whether a 5xx code is transient (worth retrying). 501 Not Implemented and 505 HTTP Version Not Supported are permanent.

func IsTransientNetErr

func IsTransientNetErr(err error) bool

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

func WithBackoff(minDelay, maxDelay time.Duration) Option

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

func WithLogger(l *slog.Logger) Option

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

func WithMaxAttempts(n int) Option

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

func WithRetryOn(predicate func(*http.Request, *http.Response, error) bool) Option

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.

type Transport

type Transport struct {
	// contains filtered or unexported fields
}

Transport is a chainable http.RoundTripper that retries transient failures from the inner transport. Safe for concurrent use.

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL