♻️¯\_ʕ◔ϖ◔ʔ_/¯
retry is a simple retrier for golang with exponential backoff and context support.
It exists mainly because I found the other libraries either too heavy in implementation or not to my liking.
retry is simple and opinionated; it re-runs your code with a particular ("full jitter") exponential backoff implementation, it supports context, and it lets you bail early on non-retryable errors. It does not implement constant backoff or alternative jitter schemes.
Retrier objects are intended to be re-used, which means you define them once and then run functions with them whenever you want, as many times as you want. This is safe for concurrent use.
// create a new retrier that will try a maximum of five times, with
// an initial delay of 100 ms and a maximum delay of 1 second
retrier := retry.NewRetrier(5, 100 * time.Millisecond, time.Second)
err := retrier.Run(func() error {
resp, err := http.Get("http://golang.org")
switch {
case err != nil:
// request error - return it
return err
case resp.StatusCode == 0 || resp.StatusCode >= 500:
// retryable StatusCode - return it
return fmt.Errorf("Retryable HTTP status: %s", http.StatusText(resp.StatusCode))
case resp.StatusCode != 200:
// non-retryable error - stop now
return retry.Stop(fmt.Errorf("Non-retryable HTTP status: %s", http.StatusText(resp.StatusCode)))
}
return nil
})
if err != nil {
// handle error
}
// create a new retrier that will try a maximum of five times, with
// an initial delay of 100 ms and a maximum delay of 1 second
retrier := retry.NewRetrier(5, 100 * time.Millisecond, time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
err := retrier.RunContext(ctx, func(ctx context.Context) error {
req, _ := http.NewRequest("GET", "http://golang.org/notfastenough", nil)
req = req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("OMG AWFUL CODE %d", resp.StatusCode)
// or decide not to retry
}
return nil
})
if err != nil {
// handle error
}
Some of the other libs I considered:
- jpillora/backoff
- A little more bare bones than I wanted and no builtin concurrency safety. No context support.
- cenkalti/backoff
- A good library, but has some issues with context deadlines/timeouts. Can't "share" backup strategies / not thread-safe.
- gopkg.in/retry.v1
- Iterator-based and a little awkward for me to use personally. I preferred to abstract the loop away.
- eapache/go-resiliency/retrier
- Of the alternatives, I like this one the most, but I found the slice of
time.Duration
odd - No context support
- Classifier pattern is not a bad idea, but it really comes down to "do I want to retry or stop?" and I thought it would be more flexible to simply allow the user to implement this logic how they saw fit. I could be open to changing my mind, if the solution is right. PRs welcome ;)
- Of the alternatives, I like this one the most, but I found the slice of
If you're doing HTTP work there are some good alternatives out there that add a layer on top of the standard libraries as well as providing special logic to help you automatically determine whether or not to retry a request.
- hashicorp/go-retryablehttp
- A very good library, but it requires conversion of all
io.Reader
s toio.ReadSeeker
s, and one of my use-cases didn't allow for unconstrained cacheing ofPOST
bodies.
- A very good library, but it requires conversion of all
- facebookgo/httpcontrol
- A great fully-featured transport. Only retries
GET
s, though :(
- A great fully-featured transport. Only retries
- sethgrid/pester
- Another good client, but had more options than I needed and also caches request bodies transparently.
See: