retry

package module
v5.0.0-rc8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 22, 2021 License: MIT Imports: 2 Imported by: 2

README

♻️ retry Awesome Go

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

Build Documentation Quality Template Coverage Mirror

💡 Idea

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

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

action := func(ctx context.Context) (err error) {
	req := req.Clone(ctx)
	resp, err = http.DefaultClient.Do(req)
	return err
}

how := []retry.How{
	strategy.Limit(5),
	strategy.BackoffWithJitter(
		backoff.Fibonacci(10*time.Millisecond),
		jitter.NormalDistribution(
			rand.New(rand.NewSource(time.Now().UnixNano())),
			0.25,
		),
	),
}

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

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

rewriting...

🧩 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/v5@latest

🤲 Outcomes

Console tool to execute commands until successful

...

See more details here.

made with ❤️ for everyone

Documentation

Overview

Package retry provides the most advanced interruptible mechanism to perform actions repetitively until successful. The retry based on https://github.com/Rican7/retry but fully reworked and focused on integration with the https://github.com/kamilsk/breaker and the built-in https://pkg.go.dev/context package.

Example
package main

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

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

var generator = rand.New(rand.NewSource(0))

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,
			),
		),

		// experimental
		exp.CheckError(
			exp.NetworkError(exp.Skip),
			DatabaseError(),
		),
	}

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

	if err := retry.Do(breaker, what, how...); err != nil {
		panic(err)
	}
	fmt.Println("success communication")
}

func SendRequest(ctx context.Context) error {
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
	}
	if generator.Intn(5) > 3 {
		return &net.DNSError{Name: "unknown host", IsTemporary: true}
	}
	return nil
}

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
	}
}
Output:

success communication

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Do

func Do(
	breaker Breaker,
	action func(context.Context) error,
	strategies ...func(Breaker, uint, error) bool,
) error

Do takes the action and performs it, repetitively, until successful.

Optionally, strategies may be passed that assess whether or not an attempt should be made.

Example (BadCases)

The example shows the difference between Do and Go.

var (
	realTime = 100 * time.Millisecond
	needTime = 5 * time.Millisecond
)
{
	badAction := func(context.Context) error {
		time.Sleep(realTime)
		return nil
	}
	now := time.Now()
	breaker, cancel := context.WithTimeout(context.Background(), needTime)

	silent(retry.Do(breaker, badAction))
	if time.Since(now) < realTime {
		fmt.Println("unexpected waiting time")
	}
	cancel()
}
{
	badStrategy := func(strategy.Breaker, uint, error) bool {
		time.Sleep(realTime)
		return true
	}
	now := time.Now()
	breaker, cancel := context.WithTimeout(context.Background(), needTime)

	silent(retry.Do(breaker, func(context.Context) error { return nil }, badStrategy))
	if time.Since(now) < realTime {
		fmt.Println("unexpected waiting time")
	}
	cancel()
}

fmt.Println("done")
Output:

done

func Go

func Go(
	breaker Breaker,
	action func(context.Context) error,
	strategies ...func(Breaker, uint, error) bool,
) error

Go takes the action and performs it, repetitively, until successful. It differs from the Do method in that it performs the action in a goroutine.

Optionally, strategies may be passed that assess whether or not an attempt should be made.

Example (Guarantees)

The example shows the difference between Do and Go.

var (
	sleepTime  = 100 * time.Millisecond
	needTime   = 5 * time.Millisecond
	inaccuracy = time.Millisecond
)
{
	badAction := func(context.Context) error {
		time.Sleep(sleepTime)
		return nil
	}
	now := time.Now()
	breaker, cancel := context.WithTimeout(context.Background(), needTime)

	silent(retry.Go(breaker, badAction))
	if time.Since(now)-needTime > time.Millisecond+inaccuracy {
		fmt.Println("unexpected waiting time")
	}
	cancel()
}
{
	badStrategy := func(strategy.Breaker, uint, error) bool {
		time.Sleep(sleepTime)
		return true
	}
	now := time.Now()
	breaker, cancel := context.WithTimeout(context.Background(), needTime)

	silent(retry.Go(breaker, func(context.Context) error { return nil }, badStrategy))
	if time.Since(now)-needTime > time.Millisecond+inaccuracy {
		fmt.Println("unexpected waiting time")
	}
	cancel()
}

fmt.Println("done")
Output:

done

Types

type Action

type Action = func(context.Context) error

Action defines a callable function that package retry can handle.

type Breaker

type Breaker = interface {
	// Done returns a channel that's closed when a cancellation signal occurred.
	Done() <-chan struct{}
	// If Done is not yet closed, Err returns nil.
	// If Done is closed, Err returns a non-nil error.
	// After Err returns a non-nil error, successive calls to Err return the same error.
	Err() error
}

A Breaker carries a cancellation signal to interrupt an action execution.

It is a subset of the built-in context and github.com/kamilsk/breaker interfaces.

type Error

type Error string

Error defines a string-based error without a different root cause.

func (Error) Error

func (err Error) Error() string

Error returns a string representation of an error.

func (Error) Unwrap

func (err Error) Unwrap() error

Unwrap always returns nil means that an error doesn't have other root cause.

type How

type How = []func(Breaker, uint, error) bool

How is an alias for batch of Strategies.

how := retry.How{
	strategy.Limit(3),
}

Directories

Path Synopsis
Package backoff provides stateless methods of calculating durations based on a number of attempts made.
Package backoff provides stateless methods of calculating durations based on a number of attempts made.
Package jitter provides methods of transforming durations.
Package jitter provides methods of transforming durations.
Package strategy provides a way to define how retry is performed.
Package strategy provides a way to define how retry is performed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL