Documentation
ΒΆ
Overview ΒΆ
Package retry provides a simple and composable retry mechanism for Go.
Package retry provides a minimal, composable retry mechanism for Go. All features are additive and the API is backward compatible.
Index ΒΆ
Examples ΒΆ
Constants ΒΆ
This section is empty.
Variables ΒΆ
This section is empty.
Functions ΒΆ
func DoValue ΒΆ
DoValue is a generic helper that executes fn using the provided Retry configuration and returns both the result and any error. This avoids capturing result variables outside the closure when retrying operations that produce a value.
Example:
user, err := retry.DoValue(ctx, retry.New().Attempts(3), func() (*User, error) {
return db.FindUser(id)
})
Example ΒΆ
ExampleDoValue demonstrates returning a typed result from the retry loop.
package main
import (
"context"
"errors"
"fmt"
"log"
"time"
retry "github.com/chmenegatti/go-retry"
)
func main() {
ctx := context.Background()
attempts := 0
result, err := retry.DoValue(ctx,
retry.New().Attempts(3).Backoff(retry.Constant(time.Millisecond)),
func() (string, error) {
attempts++
if attempts < 3 {
return "", errors.New("not ready yet")
}
return "ok", nil
},
)
if err != nil {
log.Printf("failed: %v", err)
return
}
fmt.Println(result)
}
Output: ok
func IsPermanent ΒΆ added in v1.1.0
IsPermanent reports whether err (or any error in its chain) was wrapped with Permanent.
func Permanent ΒΆ added in v1.1.0
Permanent wraps err so that the retry loop stops immediately without further attempts. The original error is preserved and can be retrieved with errors.Unwrap or errors.Is/As.
Example:
Do(ctx, func() error {
resp, err := http.Get(url)
if err != nil {
return err // transient β will retry
}
if resp.StatusCode == 401 {
return retry.Permanent(ErrUnauthorized) // fatal β stop immediately
}
return nil
})
Example ΒΆ
ExamplePermanent shows how to instantly stop retrying from inside fn.
package main
import (
"context"
"errors"
"fmt"
retry "github.com/chmenegatti/go-retry"
)
func main() {
ctx := context.Background()
var ErrBadRequest = errors.New("400 bad request")
err := retry.New().Attempts(5).Do(ctx, func() error {
// Wrapping with Permanent signals "don't retry this".
return retry.Permanent(ErrBadRequest)
})
// Unwrap to get the original error.
fmt.Println(errors.Is(err, ErrBadRequest))
}
Output: true
Types ΒΆ
type Backoff ΒΆ added in v1.0.1
Backoff is a function that, given an attempt number (1-indexed), returns the duration to wait before the next retry.
func Constant ΒΆ added in v1.0.1
Constant returns a Backoff strategy that always waits the same duration between attempts, regardless of the attempt number.
Example:
r := retry.New().Backoff(retry.Constant(2 * time.Second))
func Exponential ΒΆ added in v1.0.1
Exponential returns a Backoff strategy where the wait duration doubles with each attempt: delay = base * 2^(attempt-1).
Example:
r := retry.New().Backoff(retry.Exponential(100 * time.Millisecond)) // attempt 1 β 100ms, attempt 2 β 200ms, attempt 3 β 400ms
func ExponentialJitter ΒΆ added in v1.0.1
ExponentialJitter returns a Backoff strategy similar to Exponential, but adds a random jitter in the range [d/2, d] to avoid the thundering herd problem when many clients retry simultaneously.
Example:
r := retry.New().Backoff(retry.ExponentialJitter(100 * time.Millisecond))
type Retry ΒΆ added in v1.0.1
type Retry struct {
// contains filtered or unexported fields
}
Retry holds the configuration for a retry operation. Use New() to construct a Retry and chain builder methods before calling Do.
func New ΒΆ
func New() *Retry
New creates a new Retry with sensible defaults: 3 total attempts with a 100ms constant backoff.
Example ΒΆ
ExampleNew demonstrates a basic retry with the default configuration.
package main
import (
"context"
"errors"
"log"
retry "github.com/chmenegatti/go-retry"
)
func main() {
ctx := context.Background()
err := retry.New().
Attempts(3).
Do(ctx, func() error {
// Simulate a transient failure.
return errors.New("service unavailable")
})
if err != nil {
log.Printf("all attempts failed: %v", err)
}
}
Output:
func (*Retry) Attempts ΒΆ added in v1.0.1
Attempts sets the total number of times fn will be called (including the first attempt). Values less than 1 are treated as 1.
Example:
retry.New().Attempts(5).Do(ctx, fn)
func (*Retry) Backoff ΒΆ added in v1.0.1
Backoff sets the strategy used to compute the wait duration between attempts. Use the built-in strategies (Constant, Linear, Exponential, ExponentialJitter) or provide a custom func(attempt int) time.Duration.
Example:
retry.New().Backoff(retry.ExponentialJitter(200 * time.Millisecond))
func (*Retry) Do ΒΆ added in v1.0.1
Do executes fn up to the configured number of Attempts. Between each failed attempt, Do sleeps for the duration returned by the Backoff strategy (capped by MaxDelay if set), or until ctx is cancelled β whichever comes first.
Do returns nil on the first successful execution of fn. Do returns the last error from fn if all attempts fail. Do returns ctx.Err() if the context is cancelled while waiting. Do returns the error immediately (without further retries) if:
- the error was wrapped with Permanent, or
- a RetryIf predicate returns false for that error.
func (*Retry) MaxDelay ΒΆ added in v1.1.0
MaxDelay caps the delay returned by the backoff strategy. Any computed delay exceeding MaxDelay will be clamped to MaxDelay. This is especially useful with exponential backoffs to prevent excessively long waits.
Example:
retry.New().
Backoff(retry.Exponential(100*time.Millisecond)).
MaxDelay(5 * time.Second)
Example ΒΆ
ExampleRetry_MaxDelay shows capping exponential backoff at a fixed ceiling.
package main
import (
"context"
"errors"
"time"
retry "github.com/chmenegatti/go-retry"
)
func main() {
ctx := context.Background()
retry.New().
Attempts(10).
Backoff(retry.Exponential(100*time.Millisecond)).
MaxDelay(2*time.Second).
Do(ctx, func() error {
return errors.New("fail")
})
}
Output:
func (*Retry) OnRetry ΒΆ added in v1.0.1
OnRetry registers a callback invoked before each sleep between attempts. The callback receives the current attempt number (1-indexed) and the error from the last execution. It is NOT called after the final failing attempt.
Example:
retry.New().OnRetry(func(attempt int, err error) {
log.Printf("attempt %d failed: %v", attempt, err)
})
func (*Retry) RetryIf ΒΆ added in v1.1.0
RetryIf sets a predicate that controls whether a given error should trigger a retry. If the predicate returns false, Do returns the error immediately without further attempts. If unset, all errors are retried up to Attempts.
Note: errors wrapped with Permanent always stop the loop, regardless of RetryIf.
Example:
retry.New().RetryIf(func(err error) bool {
return errors.Is(err, io.ErrUnexpectedEOF)
})
Example ΒΆ
ExampleRetry_RetryIf shows how to skip retrying for known fatal errors.
package main
import (
"context"
"errors"
"fmt"
retry "github.com/chmenegatti/go-retry"
)
func main() {
ctx := context.Background()
errFatal := errors.New("fatal: invalid credentials")
err := retry.New().
Attempts(5).
RetryIf(func(err error) bool {
// Only retry transient errors, not authentication failures.
return !errors.Is(err, errFatal)
}).
Do(ctx, func() error {
return errFatal // stopped after 1 attempt
})
fmt.Println(err)
}
Output: fatal: invalid credentials