♻️ retry
The most advanced interruptible mechanism to perform actions repetitively until successful.
💡 Idea
The package based on github.com/Rican7/retry but fully reworked and focused on integration with the 🚧 breaker package.
Full description of the idea is available here.
🏆 Motivation
I developed distributed systems at Lazada, and later at Avito, which communicate with each other through a network, and I need a package to make these communications more reliable.
🤼♂️ How to
retry.Retry
var response *http.Response
action := func(uint) error {
var err error
response, err = http.Get("https://github.com/kamilsk/retry")
return err
}
if err := retry.Retry(breaker.BreakByTimeout(time.Minute), action, strategy.Limit(3)); err != nil {
if err == retry.Interrupted {
// timeout exceeded
}
// handle error
}
// work with response
retry.Try
var response *http.Response
action := func(uint) error {
var err error
response, err = http.Get("https://github.com/kamilsk/retry")
return err
}
// you can combine multiple Breakers into one
interrupter := breaker.MultiplexTwo(
breaker.BreakByTimeout(time.Minute),
breaker.BreakBySignal(os.Interrupt),
)
defer interrupter.Close()
if err := retry.Try(interrupter, action, strategy.Limit(3)); err != nil {
if err == retry.Interrupted {
// timeout exceeded
}
// handle error
}
// work with response
or use Context
ctx, cancel := context.WithTimeout(request.Context(), time.Minute)
defer cancel()
if err := retry.Try(ctx, action, strategy.Limit(3)); err != nil {
if err == retry.Interrupted {
// timeout exceeded
}
// handle error
}
// work with response
retry.TryContext
var response *http.Response
action := func(ctx context.Context, _ uint) error {
req, err := http.NewRequest(http.MethodGet, "https://github.com/kamilsk/retry", nil)
if err != nil {
return err
}
req = req.WithContext(ctx)
response, err = http.DefaultClient.Do(req)
return err
}
// you can combine Context and Breaker together
interrupter, ctx := breaker.WithContext(request.Context())
defer interrupter.Close()
if err := retry.TryContext(ctx, action, strategy.Limit(3)); err != nil {
if err == retry.Interrupted {
// timeout exceeded
}
// handle error
}
// work with response
Complex example
import (
"context"
"fmt"
"log"
"math/rand"
"net"
"time"
"github.com/kamilsk/retry/v4"
"github.com/kamilsk/retry/v4/backoff"
"github.com/kamilsk/retry/v4/jitter"
"github.com/kamilsk/retry/v4/strategy"
)
func main() {
what := func(uint) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("unexpected panic: %v", r)
}
}()
return SendRequest()
}
how := retry.How{
strategy.Limit(5),
strategy.BackoffWithJitter(
backoff.Fibonacci(10*time.Millisecond),
jitter.NormalDistribution(
rand.New(rand.NewSource(time.Now().UnixNano())),
0.25,
),
),
func(attempt uint, err error) bool {
if network, is := err.(net.Error); is {
return network.Temporary()
}
return attempt == 0 || err != nil
},
}
ctx, _ := context.WithTimeout(context.Background(), time.Second)
if err := retry.Try(ctx, what, how...); err != nil {
log.Fatal(err)
}
}
func SendRequest() error {
// communicate with some service
}
🧩 Integration
The library uses SemVer for versioning, and it is not BC-safe through major releases. You can use go modules or dep to manage its version.
The master is a feature frozen branch for versions 3.3.x and no longer maintained.
$ dep ensure -add github.com/kamilsk/retry@3.3.3
The v3 branch is a continuation of the master branch for versions v3.4.x to better integration with go modules.
$ go get -u github.com/kamilsk/retry/v3@v3.4.4
The v4 branch is an actual development branch.
$ go get -u github.com/kamilsk/retry # inside GOPATH and for old Go versions
$ go get -u github.com/kamilsk/retry/v4 # inside Go module, works well since Go 1.11
$ dep ensure -add github.com/kamilsk/retry@v4.0.0
🤲 Outcomes
Console tool for command execution with retries
This example shows how to repeat console command until successful.
$ retry -timeout 10m -backoff lin:500ms -- /bin/sh -c 'echo "trying..."; exit $((1 + RANDOM % 10 > 5))'
See more details here.
made with ❤️ for everyone