Coder Social home page Coder Social logo

go-retry's Introduction

Retry

GoDoc

Retry is a Go library for facilitating retry logic and backoff. It's highly extensible with full control over how and when retries occur. You can also write your own custom backoff functions by implementing the Backoff interface.

Features

  • Extensible - Inspired by Go's built-in HTTP package, this Go backoff and retry library is extensible via middleware. You can write custom backoff functions or use a provided filter.

  • Independent - No external dependencies besides the Go standard library, meaning it won't bloat your project.

  • Concurrent - Unless otherwise specified, everything is safe for concurrent use.

  • Context-aware - Use native Go contexts to control cancellation.

Usage

Here is an example use for connecting to a database using Go's database/sql package:

package main

import (
  "context"
  "database/sql"
  "log"
  "time"

  "github.com/sethvargo/go-retry"
)

func main() {
  db, err := sql.Open("mysql", "...")
  if err != nil {
    log.Fatal(err)
  }

  ctx := context.Background()
  if err := retry.Fibonacci(ctx, 1*time.Second, func(ctx context.Context) error {
    if err := db.PingContext(ctx); err != nil {
      // This marks the error as retryable
      return retry.RetryableError(err)
    }
    return nil
  }); err != nil {
    log.Fatal(err)
  }
}

Backoffs

In addition to your own custom algorithms, there are built-in algorithms for backoff in the library.

Constant

A very rudimentary backoff, just returns a constant value. Here is an example:

1s -> 1s -> 1s -> 1s -> 1s -> 1s

Usage:

NewConstant(1 * time.Second)

Exponential

Arguably the most common backoff, the next value is double the previous value. Here is an example:

1s -> 2s -> 4s -> 8s -> 16s -> 32s -> 64s

Usage:

NewExponential(1 * time.Second)

Fibonacci

The Fibonacci backoff uses the Fibonacci sequence to calculate the backoff. The next value is the sum of the current value and the previous value. This means retires happen quickly at first, but then gradually take slower, ideal for network-type issues. Here is an example:

1s -> 1s -> 2s -> 3s -> 5s -> 8s -> 13s

Usage:

NewFibonacci(1 * time.Second)

Modifiers (Middleware)

The built-in backoff algorithms never terminate and have no caps or limits - you control their behavior with middleware. There's built-in middleware, but you can also write custom middleware.

Jitter

To reduce the changes of a thundering herd, add random jitter to the returned value.

b := NewFibonacci(1 * time.Second)

// Return the next value, +/- 500ms
b = WithJitter(500*time.Millisecond, b)

// Return the next value, +/- 5% of the result
b = WithJitterPercent(5, b)

MaxRetries

To terminate a retry, specify the maximum number of retries. Note this is retries, not attempts. Attempts is retries + 1.

b := NewFibonacci(1 * time.Second)

// Stop after 4 retries, when the 5th attempt has failed. In this example, the worst case elapsed
// time would be 1s + 1s + 2s + 3s = 7s.
b = WithMaxRetries(4, b)

CappedDuration

To ensure an individual calculated duration never exceeds a value, use a cap:

b := NewFibonacci(1 * time.Second)

// Ensure the maximum value is 2s. In this example, the sleep values would be
// 1s, 1s, 2s, 2s, 2s, 2s...
b = WithCappedDuration(2 * time.Second, b)

WithMaxDuration

For a best-effort limit on the total execution time, specify a max duration:

b := NewFibonacci(1 * time.Second)

// Ensure the maximum total retry time is 5s.
b = WithMaxDuration(5 * time.Second, b)

Benchmarks

Here are benchmarks against some other popular Go backoff and retry libraries. You can run these benchmarks yourself via the benchmark/ folder. Commas and spacing fixed for clarity.

Benchmark/cenkalti-7      13,052,668     87.3 ns/op
Benchmark/lestrrat-7         902,044    1,355 ns/op
Benchmark/sethvargo-7    203,914,245     5.73 ns/op

Notes and Caveats

  • Randomization uses math/rand seeded with the Unix timestamp instead of crypto/rand.
  • Ordering of addition of multiple modifiers will make a difference. For example; ensure you add CappedDuration before WithMaxDuration, otherwise it may early out too early. Another example is you could add Jitter before or after capping depending on your desired outcome.

go-retry's People

Contributors

ariehschneier avatar chrisfowles avatar deleplace avatar quagmt avatar sethvargo avatar wietsevenema avatar zchee avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-retry's Issues

`NewConstant` panics if the given base is zero

It seems to me that a zero Backoff makes sense, from a user POV who would just want to enjoy a retry facility.

Can we make 0 an accepted value for NewConstant?

Of course it is not difficult for a user to just call NewConstant(time.Nanosecond), but for flexibility I'm not sure we should advertise "zero delay" as a forbidden parameter.

Zero delays may already occur when using the API, as discussed in #8.

WithFullJitter?

Hello! I realize I could add this middleware myself, but I was wondering if you would be interested in adding a WithFullJitter middleware? Full jitter would return a duration between 0 and Next(). According to https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/, full jitter is better than equal jitter (which is essentially what WithJitter and WithJitterPercentage are).

If you think it is a good idea I am happy to create a PR.

Should the caller deal with `*retryableError`

Hello Seth, thanks for the nice facility that I'm trying to use for the first time.

It seems that:

  • the Do signature says it returns an error, which is idiomatic (instead of a more specific and error-prone error type);
  • there is currently no promise about the exact form of the returned error;
  • on success (regardless the number of attempts), Do returns nil;
  • on failure, Do ignores the results of the first failed attempts, and returns the error of the very last failed attempt, wrapped in a *retryableError.

If my understanding (the above bullets) is correct, then it seems that it would be even nicer to return the "root cause" error to the caller of Do, i.e. not wrapped in a *retryableError. Shall I try a PR?

Caveat: maybe I'm not familiar enough with errors.As and interface {Unwrap() error}, which means that it would be, in fact, more idiomatic to keep the *retryableError as a visible trace of the retry device, and let the caller unwrap/test the returned error.

DoWithData?

Hi, me again!

Any thoughts to adding a generic DoWithData[T](ctx context.Context, b Backoff, f RetryFunc[T]) (T, error) {...} function similar to https://pkg.go.dev/github.com/cenkalti/backoff/v4#RetryWithData? Obviously, I could use that, but the API here seems more intuitive to me.

Also happy to try a PR if this is something you are interested in.

Thanks!

Exponential and Fibonacci backoffs easily overflow

If you let exponential or Fibonacci backoffs run for a while you'll easily get into an overflow situation. See https://go.dev/play/p/FUNKkZU0Kyn for an example. The NewExponential documentation says

It's very efficient, but does not check for overflow, so ensure you bound the retry.

but AFAICT you can't prevent overflows with WithCappedDuration since by then the overflow has already happened. How about changing said backoff functions to detect overflows and consistently return the largest time.Duration when the ceiling has been reached? Or am I missing something?

Backwards incompatible changes starting in v0.2.0

Hi, it looks like starting in v0.2.0 there are backwards incompatible changes to exported function signatures, specifically the Backoff constructors have the error return value removed resulting in code changes being required anywhere these are used. I do see this noted in the release notes for v0.2.0. While this isn't necessarily against the guidelines for Golang projects given the major version is v0 it isn't great to have to go through and make updates to projects using this library after the previous API had been stable for about 18 months.

Are there plans to bump this project to v1 and consider the API stable?

Thank you for your time and effort on this library!

Difficulties downloading v0.2.0

The release v0.2.0 clearly mentions:

  • the tag name v0.2.0
  • the commit id e436cb1
  • the breaking change "constructors no longer returns an error" e.g. NewConstant now returns a single value
  • the potentially breaking change "Automatically unwrap"

I confirm that the code at tag v0.2.0 / commit e436cb1 does include the new signature for NewConstant, as viewed in the GitHub web UI.

However, when downloading v0.2.0 with go get (with a recent enough version of go), the downloaded code

  • includes the "Automatically unwrap" change
  • doesn't include the "constructors no longer returns an error"!

Here is a full repro from Cloud Shell:

deleplace@cloudshell:~$ go version
go version go1.17.2 linux/amd64

deleplace@cloudshell:~$ go get github.com/sethvargo/[email protected]                                                                                                                                 
go: downloading github.com/sethvargo/go-retry v0.2.0

deleplace@cloudshell:~$ cat gopath/pkg/mod/github.com/sethvargo/[email protected]/retry.go | grep -A2 "if stop"                                                                                       
                if stop {
                        return rerr.Unwrap()
                }

deleplace@cloudshell:~$ cat gopath/pkg/mod/github.com/sethvargo/[email protected]/backoff_constant.go | grep "func NewConstant"
func NewConstant(t time.Duration) (Backoff, error) {

This is tricky.

I don't know the root cause of the problem. Might be a nasty bug in the go module tooling, or the way we use it. @sethvargo do you remember if you created the tag v0.2.0 first at commit 2ee2801, then deleted it and created it anew at commit e436cb1?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.