cenkalti/backoff

repository·v7·Indexed 26 days ago

https://github.com/cenkalti/backoff

A Go port of Google's exponential backoff algorithm from the Java HTTP Client Library. It provides mechanisms to retry operations with increasing delays to handle transient failures, featuring a flexible BackOff interface, configurable ExponentialBackOff, and a Retry function with options for max tries, max elapsed time, and permanent error signaling.

Tokens
3.4K
Snippets
8
Records
22
Agent score
87%

What's inside cenkalti/backoff

  1. Bound retry duration with context and WithMaxElapsedTime

    v7

    There are two ways to limit the total time spent retrying:

    1. Context Deadline: Use context.WithTimeout. This is reactive and can interrupt the wait between attempts or abort an in-flight operation if the operation respects the context. It reports context.DeadlineExceeded.
    2. WithMaxElapsedTime: This bounds only the retry scheduling. It is checked between attempts and never interrupts a running operation. It reports backoff.ErrMaxElapsedTime.

    Note: WithMaxElapsedTime defaults to 15 minutes. To rely solely on the context and disable the default 15-minute limit, pass backoff.WithMaxElapsedTime(0).

  2. Use backoff.Retry for exponential retries

    v7

    Wrap your operation in backoff.Retry(ctx, operation) to execute it with exponential backoff. The operation should return a value and an error. To stop retrying immediately on specific errors (like client-side errors), wrap the error with backoff.Permanent(err).

    Available options to configure the retry behavior include:

    • WithBackOff
    • WithMaxTries
    • WithMaxElapsedTime
    • WithNotify
    result, err := backoff.Retry(ctx, func() (string, error) {
    	resp, err := http.Get("https://www.example.com")
    	if err != nil {
    		return "", err // transient: Retry will try again
    	}
    	defer resp.Body.Close()
    
    	switch {
    	case resp.StatusCode >= 500:
    		return "", fmt.Errorf("server error: %s", resp.Status) // retried
    	case resp.StatusCode >= 400:
    		// client errors won't fix themselves, so stop retrying.
    		return "", backoff.Permanent(fmt.Errorf("client error: %s", resp.Status))
    	}
    	return "ok", nil
    }, backoff.WithMaxTries(5))
  3. Handle retry errors and inspect causes

    v7

    When Retry fails, it returns a *RetryError. You can use errors.Is to check the reason why retrying stopped or backoff.AsRetryError(err) to access the LastErr (the error from the final attempt).

    Common error causes to check for:

    • backoff.ErrPermanent: The operation returned an error wrapped in backoff.Permanent().
    • context.Canceled or context.DeadlineExceeded: The provided context was cancelled or expired.
    • backoff.ErrMaxElapsedTime: The time limit set by WithMaxElapsedTime was reached.
    • backoff.ErrExhausted: The maximum number of tries (WithMaxTries) was reached or the backoff policy signaled to stop.
    result, err := backoff.Retry(ctx, operation)
    switch {
    case errors.Is(err, backoff.ErrPermanent):
    	// the operation returned a Permanent error
    case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
    	// the caller's context was cancelled or its deadline expired
    case errors.Is(err, backoff.ErrMaxElapsedTime):
    	// the WithMaxElapsedTime budget was exhausted
    case errors.Is(err, backoff.ErrExhausted):
    	// WithMaxTries was reached or the backoff policy returned Stop
    }
    
    // The last operation error is always available, whatever the cause:
    if re := backoff.AsRetryError(err); re != nil {
    	log.Printf("gave up after last error: %v", re.LastErr)
    }
  4. Use the BackOff interface for retry policies

    v7

    The BackOff interface defines the contract for any retry policy in this package. It allows you to control the delay between retries and determine when to stop retrying.

    To implement or use a BackOff policy, you interact with two methods:

    • NextBackOff(): Returns the time.Duration to wait before the next attempt. If it returns backoff.Stop, the caller should cease all retry attempts.
    • Reset(): Resets the policy to its initial state.

    Note that backoff.Stop is a constant representing a duration of -1 used to signal that no more retries should be made.

    // Example of using the NextBackOff interface
    duration := b.NextBackOff()
    if duration == backoff.Stop {
        // Do not retry operation.
    } else {
        // Sleep for duration and retry operation.
    }
  5. Signal a specific wait duration with RetryAfter()

    v7

    To tell the retry loop to wait for a specific duration before the next attempt, return an error created by backoff.RetryAfter(duration, cause).

    When this error is returned, backoff.Retry will wait for the specified duration and then reset the backoff policy (restarting the backoff sequence). The cause error is preserved and will be available via Unwrap() or as RetryError.LastErr if retrying eventually stops.

  6. Configure Retry with RetryOption

    v7

    You can customize the behavior of Retry using functional options.

    Available options:

    • WithBackOff(b BackOff): Sets the backoff strategy. Defaults to NewExponentialBackOff. Note that BackOff is stateful and not thread-safe; provide a unique instance per Retry call.
    • WithNotify(n Notify): Sets an optional function called after a failed attempt that will be retried. It receives the error and the duration to wait before the next attempt.
    • WithMaxTries(n uint): Limits the total number of attempts (e.g., WithMaxTries(1) runs the operation exactly once). A value of 0 means no limit. If reached, returns ErrExhausted.
    • WithMaxElapsedTime(d time.Duration): Limits the total wall-clock time for all retries. The limit is checked between attempts. If reached, returns ErrMaxElapsedTime. Pass 0 to disable this limit. The default is DefaultMaxElapsedTime (15 minutes).
    func WithBackOff(b BackOff) RetryOption
    func WithNotify(n Notify) RetryOption
    func WithMaxTries(n uint) RetryOption
    func WithMaxElapsedTime(d time.Duration) RetryOption
  7. Configure ExponentialBackOff parameters

    v7

    You can customize the backoff behavior by setting the following fields on an ExponentialBackOff struct:

    • InitialInterval (time.Duration): The starting interval for the first retry.
    • RandomizationFactor (float64): A factor used to jitter the interval. The actual interval will be within [1 - RandomizationFactor, 1 + RandomizationFactor] of the current interval.
    • Multiplier (float64): The factor by which the interval is multiplied after each attempt.
    • MaxInterval (time.Duration): The maximum cap for the currentInterval (not the randomized interval).
  8. Use Retry to execute operations with backoff

    v7

    The Retry[T] function executes an Operation[T] until it succeeds, returns a permanent error, or reaches a configured limit (max tries, max elapsed time, or backoff exhaustion).

    Key behaviors:

    • It ensures the operation is executed at least once.
    • On success, it returns the result and nil error.
    • On failure, it returns the last result and a *RetryError. The Cause field of the RetryError indicates why retrying stopped (e.g., ErrPermanent, ErrExhausted, ErrMaxElapsedTime, or a context cancellation).
    • The ctx parameter bounds the retry loop. To abort an in-flight attempt, you must capture the ctx inside your Operation.
    • To bound only the total time spent retrying without interrupting in-flight attempts, use WithMaxElapsedTime instead.
    func Retry[T any](ctx context.Context, operation Operation[T], opts ...RetryOption) (T, error)
  9. Initialize ExponentialBackOff with default values

    v7

    Use NewExponentialBackOff() to create a new instance of ExponentialBackOff using the library's default configuration:

    • InitialInterval: 500ms
    • RandomizationFactor: 0.5
    • Multiplier: 1.5
    • MaxInterval: 60s

    Note: The implementation is not thread-safe.