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/goretry"
)
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 ΒΆ
IsPermanent reports whether err (or any error in its chain) was wrapped with Permanent.
func Permanent ΒΆ
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/goretry"
)
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 ΒΆ
Backoff is a function that, given an attempt number (1-indexed), returns the duration to wait before the next retry.
func Constant ΒΆ
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 ΒΆ
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 ΒΆ
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 ΒΆ
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/goretry"
)
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 ΒΆ
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 ΒΆ
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 ΒΆ
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 ΒΆ
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/goretry"
)
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 ΒΆ
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 ΒΆ
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/goretry"
)
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