♻️ retry

The most advanced interruptible mechanism to perform actions repetitively until successful.

Build Documentation Quality Template Coverage Awesome

💡 Idea

The package based on Rican7/retry but fully reworked and focused on integration with the 🚧 breaker and the built-in context packages.

A 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

Important: retry/v5 compatible with breaker version v1.2+ and above.

retry.Do

var response *http.Response

action := func(ctx context.Context) error {
    var err error
    req := http.NewRequestWithContext(
        ctx,
        http.MethodGet, "https://github.com/kamilsk/retry",
        nil,
    )
    response, err = http.DefaultClient.Do(req)
    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.Do(interrupter, action, strategy.Limit(3)); err != nil {
    if err == breaker.Interrupted {
        // operation was interrupted
    }
    // handle error
}
// work with response

or use Context

ctx, cancel := context.WithTimeout(request.Context(), time.Minute)
defer cancel()

if err := retry.Do(ctx, action, strategy.Limit(3)); err != nil {
    if err == context.Canceled || err == context.DeadlineExceeded {
        // operation was interrupted
    }
    // handle error
}
// work with response

Complex example

import (
    "context"
    "database/sql"
    "fmt"
    "log"
    "math/rand"
    "net"
    "time"

    "github.com/kamilsk/retry/v5"
    "github.com/kamilsk/retry/v5/backoff"
    "github.com/kamilsk/retry/v5/jitter"
    "github.com/kamilsk/retry/v5/strategy"
)

func main() {
    what := 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,
            ),
        ),
        strategy.CheckError(
            strategy.NetworkError(strategy.Skip),
            DatabaseError(),
        ),
    }

    breaker, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()

    if err := retry.Do(breaker, what, how...); err != nil {
        log.Fatal(err)
    }
}

func SendRequest(ctx context.Context) error {
    // communicate with some service
}

func DatabaseError() func(error) bool {
    blacklist := []error{sql.ErrNoRows, sql.ErrConnDone, sql.ErrTxDone}
    return func(err error) bool {
        for _, preset := range blacklist {
            if err == preset {
                return false
            }
        }
        return true
    }
}

🧩 Integration

The library uses SemVer for versioning, and it is not BC-safe through major releases. You can use go modules to manage its version.

$ go get github.com/kamilsk/retry/[email protected]

🤲 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))'

asciicast

See more details here.


made with ❤️ for everyone

Retry

♻️ The most advanced interruptible mechanism to perform actions repetitively until successful.

Retry Info

⭐ Stars 254
🔗 Source Code github.com
🕒 Last Update a year ago
🕒 Created 5 years ago
🐞 Open Issues 4
➗ Star-Issue Ratio 64
😎 Author kamilsk