retry

package module
v5.0.0-rc2 Latest Latest
Warning

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

Go to latest
Published: Mar 1, 2020 License: MIT Imports: 2 Imported by: 4

README

♻️ 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.

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.Do
var response *http.Response

action := func() 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.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() 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/v5@latest

🤲 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

Documentation

Overview

Package retry provides the most advanced interruptible mechanism to perform actions repetitively until successful.

Example
package main

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

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,
			),
		),
		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)
	}
	fmt.Println("success communication")
}

func SendRequest() error {
	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 strategy.Breaker,
	action func() error,
	strategies ...func(strategy.Breaker, uint, error) bool,
) error

Do takes an 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 DoAsync - if an action doesn't support the interrupt mechanism - if a strategy doesn't support the interrupt mechanism

var (
	realTime = 100 * time.Millisecond
	needTime = 5 * time.Millisecond
)
{
	badAction := func() 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() error { return nil }, badStrategy))
	if time.Since(now) < realTime {
		fmt.Println("unexpected waiting time")
	}
	cancel()
}

fmt.Println("done")
Output:

done

func DoAsync

func DoAsync(
	breaker strategy.Breaker,
	action func() error,
	strategies ...func(strategy.Breaker, uint, error) bool,
) error

DoAsync takes an action and performs it, repetitively, until successful.

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 DoAsync - if an action doesn't support the interrupt mechanism - if a strategy doesn't support the interrupt mechanism

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

	Silent(retry.DoAsync(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.DoAsync(breaker, func() 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() error

Action defines a callable function that package retry can handle.

type How

type How []func(strategy.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