244 lines
6.4 KiB
Markdown
244 lines
6.4 KiB
Markdown
> # ♻️ retry
|
|
>
|
|
> The most advanced interruptible mechanism to perform actions repetitively until successful.
|
|
|
|
[![Build][icon_build]][page_build]
|
|
[![Quality][icon_quality]][page_quality]
|
|
[![Documentation][icon_docs]][page_docs]
|
|
[![Coverage][icon_coverage]][page_coverage]
|
|
[![Awesome][icon_awesome]][page_awesome]
|
|
|
|
## 💡 Idea
|
|
|
|
The package based on [github.com/Rican7/retry](https://github.com/Rican7/retry) but fully reworked
|
|
and focused on integration with the 🚧 [breaker][] package.
|
|
|
|
Full description of the idea is available [here][design].
|
|
|
|
## 🏆 Motivation
|
|
|
|
I developed distributed systems at [Lazada](https://github.com/lazada), and later at [Avito](https://tech.avito.ru),
|
|
which communicate with each other through a network, and I need a package to make these communications more reliable.
|
|
|
|
## 🤼♂️ How to
|
|
|
|
### retry.Retry
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
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](https://semver.org) for versioning, and it is not
|
|
[BC](https://en.wikipedia.org/wiki/Backward_compatibility)-safe through major releases.
|
|
You can use [go modules](https://github.com/golang/go/wiki/Modules) or
|
|
[dep](https://golang.github.io/dep/) to manage its version.
|
|
|
|
The **[master][legacy]** is a feature frozen branch for versions **3.3.x** and no longer maintained.
|
|
|
|
```bash
|
|
$ dep ensure -add github.com/kamilsk/retry@3.3.3
|
|
```
|
|
|
|
The **[v3][]** branch is a continuation of the **[master][legacy]** branch for versions **v3.4.x**
|
|
to better integration with [go modules](https://github.com/golang/go/wiki/Modules).
|
|
|
|
```bash
|
|
$ go get -u github.com/kamilsk/retry/v3@v3.4.4
|
|
```
|
|
|
|
The **[v4][]** branch is an actual development branch.
|
|
|
|
```bash
|
|
$ 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.
|
|
|
|
```bash
|
|
$ retry -timeout 10m -backoff lin:500ms -- /bin/sh -c 'echo "trying..."; exit $((1 + RANDOM % 10 > 5))'
|
|
```
|
|
|
|
[![asciicast][cli.preview]][cli.demo]
|
|
|
|
See more details [here][cli].
|
|
|
|
---
|
|
|
|
made with ❤️ for everyone
|
|
|
|
[icon_awesome]: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg
|
|
[icon_build]: https://travis-ci.org/kamilsk/retry.svg?branch=v4
|
|
[icon_coverage]: https://api.codeclimate.com/v1/badges/ed88afbc0754e49e9d2d/test_coverage
|
|
[icon_docs]: https://godoc.org/github.com/kamilsk/retry?status.svg
|
|
[icon_quality]: https://goreportcard.com/badge/github.com/kamilsk/retry
|
|
|
|
[page_awesome]: https://github.com/avelino/awesome-go#utilities
|
|
[page_build]: https://travis-ci.org/kamilsk/retry
|
|
[page_coverage]: https://codeclimate.com/github/kamilsk/retry/test_coverage
|
|
[page_docs]: https://godoc.org/github.com/kamilsk/retry
|
|
[page_quality]: https://goreportcard.com/report/github.com/kamilsk/retry
|
|
|
|
[breaker]: https://github.com/kamilsk/breaker
|
|
[cli]: https://github.com/kamilsk/retry.cli
|
|
[cli.demo]: https://asciinema.org/a/150367
|
|
[cli.preview]: https://asciinema.org/a/150367.png
|
|
[design]: https://www.notion.so/octolab/retry-cab5722faae445d197e44fbe0225cc98?r=0b753cbf767346f5a6fd51194829a2f3
|
|
[egg]: https://github.com/kamilsk/egg
|
|
[promo]: https://github.com/kamilsk/retry
|
|
|
|
[legacy]: https://github.com/kamilsk/retry/tree/master
|
|
[v3]: https://github.com/kamilsk/retry/tree/v3
|
|
[v4]: https://github.com/kamilsk/retry/projects/4
|
|
|
|
[tmp.docs]: https://nicedoc.io/kamilsk/retry?theme=dark
|
|
[tmp.history]: https://github.githistory.xyz/kamilsk/retry/blob/v4/README.md
|