Documentation
¶
Overview ¶
Package retry executes fallible operations with bounded, context-aware retry behavior and provides a retrying http.RoundTripper.
An attempt is one invocation of an operation. A retry is an additional attempt after a failed attempt. WithMaxAttempts(3) therefore permits one initial attempt and at most two retries.
Do defaults to three attempts, exponential full-jitter backoff starting at 100 milliseconds, and a five-second cap on each delay. Its generic policy retries non-context errors because the package cannot infer an application's permanent-error taxonomy. Applications should normally provide a Policy. The caller's context owns the overall deadline; the package does not add per-attempt deadlines.
Transport retries only replay-eligible methods and bodies. By default it considers GET, HEAD, OPTIONS, and TRACE replay-eligible, as well as requests with a non-empty Idempotency-Key header. The header is only a client-side signal: it is safe only when the server actually enforces idempotency. Request bodies require http.Request.GetBody for replay. Retried response bodies are bounded-drained and closed before another attempt.
Retry cannot guarantee exactly-once execution. A timeout may mean that the remote operation completed but its response was lost.
Index ¶
- Variables
- func Do[T any](ctx context.Context, fn func(context.Context) (T, error), opts ...Option) (T, error)
- type AttemptEvent
- type Backoff
- type ExhaustedError
- type ExhaustedEvent
- type HTTPPolicy
- type Hooks
- type Option
- func WithBackoff(backoff Backoff) Option
- func WithHTTPPolicy(policy HTTPPolicy) Option
- func WithHooks(hooks Hooks) Option
- func WithIdempotencyKeyHeader(header string) Option
- func WithMaxAttempts(attempts int) Option
- func WithMaxDelay(delay time.Duration) Option
- func WithRetryPolicy(policy Policy) Option
- func WithRetryableMethods(methods ...string) Option
- type Policy
- type RetryEvent
- type Transport
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrExhausted identifies an operation that consumed every configured attempt. ErrExhausted = errors.New("retry attempts exhausted") // ErrInvalidConfiguration identifies invalid retry options or arguments. ErrInvalidConfiguration = errors.New("invalid retry configuration") )
var ( // ErrBodyNotReplayable identifies a retry prevented because a consumed // request body could not be recreated safely. ErrBodyNotReplayable = errors.New("request body cannot be replayed") )
Functions ¶
func Do ¶
Do invokes fn until it succeeds, returns a non-retryable error, exhausts the configured attempts, or observes ctx cancellation. It returns a non-retryable operation error unchanged. Exhaustion returns an ExhaustedError that matches ErrExhausted and unwraps to the last operation error.
fn is never started after ctx is done, and a backoff wait stops promptly when ctx is cancelled. Do does not add per-attempt deadlines; fn receives the caller's context. Hooks run synchronously in the calling goroutine.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"github.com/Dzhamal265/microkit/retry"
)
func main() {
attempts := 0
value, err := retry.Do(context.Background(), func(context.Context) (string, error) {
attempts++
if attempts == 1 {
return "", errors.New("temporarily unavailable")
}
return "ready", nil
},
retry.WithMaxAttempts(3),
retry.WithBackoff(retry.Constant(0)),
)
fmt.Println(value, attempts, err)
}
Output: ready 2 <nil>
Types ¶
type AttemptEvent ¶
type AttemptEvent struct {
// Attempt is the one-based operation invocation number.
Attempt int
}
AttemptEvent describes an operation invocation about to start.
type Backoff ¶
Backoff computes the delay before a retry. retryNumber starts at 1 for the first retry after the initial attempt. A Backoff shared by callers must be safe for concurrent use.
func Constant ¶
Constant returns a Backoff that always waits delay. Negative delays are treated as zero by Do and Transport.
func Exponential ¶
Exponential returns an uncapped exponential Backoff. The first retry waits initial, and later delays double until time.Duration's maximum value. Use WithMaxDelay to impose the effective cap.
func ExponentialJitter ¶
ExponentialJitter returns a full-jitter exponential Backoff. Each delay is selected uniformly from [0, exponential delay]. WithMaxDelay applies after jitter. The returned Backoff is safe for concurrent use.
type ExhaustedError ¶
type ExhaustedError struct {
// Attempts is the total number of completed invocations.
Attempts int
// Err is the final operation or transport error.
Err error
}
ExhaustedError reports the last error from an operation that used every configured attempt.
func (*ExhaustedError) Is ¶
func (e *ExhaustedError) Is(target error) bool
Is makes ExhaustedError match ErrExhausted while preserving matching against its underlying error through Unwrap.
func (*ExhaustedError) Unwrap ¶
func (e *ExhaustedError) Unwrap() error
Unwrap returns the final operation error.
type ExhaustedEvent ¶
type ExhaustedEvent struct {
// Attempts is the total number of completed invocations.
Attempts int
// Err is the final operation or transport error, if one occurred.
Err error
// HTTPStatus is the final retryable response status, or zero otherwise.
HTTPStatus int
}
ExhaustedEvent describes an operation that used all configured attempts. Err is nil when an HTTP response, rather than a transport error, caused the final retryable outcome.
type HTTPPolicy ¶
HTTPPolicy reports whether a response or transport error is transient. resp may be non-nil when err is nil. It does not decide whether the request method or body is safe to replay; Transport applies those checks separately. A policy shared by callers must be safe for concurrent use.
type Hooks ¶
type Hooks struct {
// OnAttempt runs immediately before an operation invocation.
OnAttempt func(AttemptEvent)
// OnRetry runs after a failure is classified and before the backoff wait.
OnRetry func(RetryEvent)
// OnExhausted runs when a retryable outcome consumes the final attempt.
OnExhausted func(ExhaustedEvent)
}
Hooks contains synchronous observability callbacks. Callbacks may run from concurrent goroutines when an option is shared, must return promptly, and must not mutate event values. Panics are not recovered.
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option configures Do and Transport. Options are immutable and may be reused.
func WithBackoff ¶
WithBackoff sets the delay strategy used between attempts.
func WithHTTPPolicy ¶
func WithHTTPPolicy(policy HTTPPolicy) Option
WithHTTPPolicy replaces the default HTTP transient-failure policy. The default retries network timeouts, EOF-before-response errors, and status codes 408, 429, 502, 503, and 504.
func WithHooks ¶
WithHooks installs synchronous observability callbacks. Microkit does not log or emit metrics by itself.
func WithIdempotencyKeyHeader ¶
WithIdempotencyKeyHeader sets the request header that makes otherwise unsafe methods eligible for retries. The default is Idempotency-Key. An empty value disables this mechanism. Header presence is only a client-side replay signal; it cannot guarantee correct server-side idempotency.
func WithMaxAttempts ¶
WithMaxAttempts sets the total number of operation invocations, including the initial attempt. Values below one are invalid.
func WithMaxDelay ¶
WithMaxDelay caps every backoff and HTTP Retry-After delay. Zero disables waiting without disabling retries. Negative values are invalid.
func WithRetryPolicy ¶
WithRetryPolicy sets the error policy for generic operations. By default, Do retries all errors except context cancellation and deadline errors.
func WithRetryableMethods ¶
WithRetryableMethods replaces the methods that are intrinsically eligible for HTTP retries. The default set is GET, HEAD, OPTIONS, and TRACE. Any method with a non-empty configured idempotency-key header is also eligible, but the server must actually enforce idempotency for such a retry to be safe.
type Policy ¶
Policy reports whether an operation error is eligible for retry. A Policy shared by callers must be safe for concurrent use. Context cancellation always stops Do regardless of the policy result.
type RetryEvent ¶
type RetryEvent struct {
// Attempt is the unsuccessful one-based attempt number.
Attempt int
// NextAttempt is the one-based attempt number that will follow the delay.
NextAttempt int
// Delay is the context-aware wait scheduled before NextAttempt.
Delay time.Duration
// Err is the operation or transport error, if one occurred.
Err error
// HTTPStatus is the retryable response status, or zero for generic and
// transport-error retries.
HTTPStatus int
}
RetryEvent describes a retry scheduled after an unsuccessful attempt. HTTPStatus is zero for generic operations and transport errors.
type Transport ¶
type Transport struct {
// contains filtered or unexported fields
}
Transport is a bounded retrying http.RoundTripper. It is safe for concurrent use when its underlying RoundTripper, policies, backoff, and hooks are safe for concurrent use. Its configuration is immutable after construction.
func NewTransport ¶
func NewTransport(base http.RoundTripper, opts ...Option) *Transport
NewTransport wraps base with bounded HTTP retries. A nil base uses http.DefaultTransport. The returned transport uses the package defaults described by Transport and may be shared by concurrent clients.
NewTransport cannot return an error without making normal http.Client setup cumbersome. Invalid options are therefore retained and reported by RoundTrip as an error matching ErrInvalidConfiguration.
Example ¶
package main
import (
"net/http"
"time"
"github.com/Dzhamal265/microkit/retry"
)
func main() {
transport := retry.NewTransport(
http.DefaultTransport,
retry.WithMaxAttempts(3),
)
client := &http.Client{
Transport: transport,
Timeout: 5 * time.Second,
}
_ = client
}
Output:
func (*Transport) RoundTrip ¶
RoundTrip implements http.RoundTripper. It returns the last HTTP response when retryable statuses exhaust the attempt budget, preserving normal net/http status handling. Exhausted transport errors return ExhaustedError.
A retryable response is not retried when its body cannot be recreated; that response is returned to the caller. A retryable transport error with a non-replayable body is joined with ErrBodyNotReplayable. Before retrying a response, RoundTrip drains at most 4 KiB and closes its body. It honors Retry-After on 429 and 503 responses, subject to the configured maximum delay.