retry: github.com/kamilsk/retry Index | Examples | Files | Directories

package retry

import "github.com/kamilsk/retry"

Package retry provides functional mechanism based on channels to perform actions repetitively until successful.

This package is an extended version of https://godoc.org/github.com/Rican7/retry. Copyright © 2016 Trevor N. Suarez (Rican7)

Code:

_ = retry.Retry(nil, func(attempt uint) error {
    return nil // Do something that may or may not cause an error
})

This example shows how to use retry to restore database connection by `database/sql/driver.Pinger`.

Code:

// +build go1.8

package main

import (
    "context"
    "database/sql"
    "database/sql/driver"
    "errors"
    "fmt"
    "time"

    "github.com/kamilsk/retry"
    "github.com/kamilsk/retry/strategy"
)

type drv struct {
    conn *conn
}

func (d *drv) Open(name string) (driver.Conn, error) {
    return d.conn, nil
}

type conn struct {
    counter int
    ping    chan error
}

func (c *conn) Prepare(string) (driver.Stmt, error) { return nil, nil }

func (c *conn) Close() error { return nil }

func (c *conn) Begin() (driver.Tx, error) { return nil, nil }

func (c *conn) Ping(context.Context) error {
    c.counter++
    return <-c.ping
}

// This example shows how to use retry to restore database connection by `database/sql/driver.Pinger`.
func main() {
    const total = 10

    d := &drv{conn: &conn{ping: make(chan error, total)}}
    for i := 0; i < cap(d.conn.ping); i++ {
        d.conn.ping <- nil
    }
    sql.Register("stub", d)

    MustOpen := func() *sql.DB {
        db, err := sql.Open("stub", "stub://test")
        if err != nil {
            panic(err)
        }
        return db
    }

    shutdown := make(chan struct{})
    go func(db *sql.DB, ctx context.Context, shutdown chan<- struct{}, frequency time.Duration,
        strategies ...strategy.Strategy) {

        defer func() {
            if r := recover(); r != nil {
                shutdown <- struct{}{}
            }
        }()

        ping := func(uint) error {
            return db.Ping()
        }

        for {
            if err := retry.Retry(ctx.Done(), ping, strategies...); err != nil {
                panic(err)
            }
            time.Sleep(frequency)
        }
    }(MustOpen(), context.Background(), shutdown, time.Millisecond, strategy.Limit(1))

    d.conn.ping <- errors.New("done")
    <-shutdown

    fmt.Printf("number of ping calls: %d", d.conn.counter)
}

Code:

const logFilePath = "/var/log/myapp.log"

var logFile *os.File

err := retry.Retry(nil, func(attempt uint) error {
    var err error

    logFile, err = os.Open(logFilePath)

    return err
})

if nil != err {
    log.Fatalf("Unable to open file %q with error %q", logFilePath, err)
}

_ = logFile.Chdir() // Do something with the file

This example shows how to extend standard http.Client with retry under the hood.

Code:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "sync/atomic"
    "time"

    "github.com/kamilsk/retry"
    "github.com/kamilsk/retry/strategy"
)

type client struct {
    base       *http.Client
    strategies []strategy.Strategy
}

func New(timeout time.Duration, strategies ...strategy.Strategy) *client {
    return &client{
        base:       &http.Client{Timeout: timeout},
        strategies: strategies,
    }
}

func (c *client) Get(deadline <-chan struct{}, url string) (*http.Response, error) {
    var response *http.Response
    err := retry.Retry(deadline, func(uint) error {
        resp, err := c.base.Get(url)
        if err != nil {
            return err
        }
        response = resp
        return nil
    }, c.strategies...)
    return response, err
}

// This example shows how to extend standard http.Client with retry under the hood.
func main() {
    var attempts int32 = 2
    ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
        if atomic.CompareAndSwapInt32(&attempts, 0, -1) {
            rw.Write([]byte("success"))
            return
        }
        atomic.AddInt32(&attempts, -1)
        time.Sleep(100 * time.Millisecond)
    }))
    defer ts.Close()

    cl := New(10*time.Millisecond, strategy.Limit(uint(attempts)+1))
    resp, err := cl.Get(retry.WithTimeout(time.Second), ts.URL)

    fmt.Printf("response: %s, error: %+v \n", func() string {
        b, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            return err.Error()
        }
        return string(b)
    }(), err)
}

Code:

var response *http.Response
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
    rw.WriteHeader(http.StatusOK)
}))

action := func(attempt uint) error {
    var err error

    response, err = http.Get(ts.URL)

    if nil == err && nil != response && response.StatusCode > 200 {
        err = fmt.Errorf("failed to fetch (attempt #%d) with status code: %d", attempt, response.StatusCode)
    }

    return err
}

err := retry.Retry(nil, action, strategy.Limit(5), strategy.Backoff(backoff.Fibonacci(10*time.Millisecond)))
if nil != err {
    log.Fatalf("Failed to fetch repository with error %q", err)
}

This example shows how to use context and retry together.

Code:

const expected = 3

attempts := expected
communication := make(chan error)
go func() {
    for {
        <-communication
        if attempts == 0 {
            close(communication)
            return
        }
        attempts--
        communication <- errors.New("service unavailable")
    }
}()

action := func(uint) error {
    communication <- nil   // ping
    return <-communication // pong
}
ctx := retry.WithContext(context.Background(), retry.WithTimeout(time.Second))
if err := retry.Retry(ctx.Done(), action, strategy.Delay(time.Millisecond)); err != nil {
    panic(err)
}

fmt.Printf("attempts: %d, expected: %d \n", expected-attempts, expected)

Output:

attempts: 3, expected: 3

Code:

action := func(attempt uint) error {
    return errors.New("something happened")
}

seed := time.Now().UnixNano()
random := rand.New(rand.NewSource(seed))

_ = retry.Retry(
    nil,
    action,
    strategy.Limit(5),
    strategy.BackoffWithJitter(
        backoff.BinaryExponential(10*time.Millisecond),
        jitter.Deviation(random, 0.5),
    ),
)

Index

Examples

Package Files

channel.go context.go retry.go

func IsRecovered Uses

func IsRecovered(err error) (interface{}, bool)

IsRecovered checks that the error is related to unhandled Action's panic and returns an original cause of panic.

func IsTimeout Uses

func IsTimeout(err error) bool

IsTimeout checks that the error is related to the incident deadline on Retry call.

func Multiplex Uses

func Multiplex(channels ...<-chan struct{}) <-chan struct{}

Multiplex combines multiple empty struct channels into one. TODO can be leaky, https://github.com/kamilsk/retry/issues/109

func Retry Uses

func Retry(deadline <-chan struct{}, action Action, strategies ...strategy.Strategy) error

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

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

func WithContext Uses

func WithContext(parent context.Context, deadline <-chan struct{}) context.Context

WithContext returns Context with cancellation based on empty struct channel.

func WithDeadline Uses

func WithDeadline(deadline time.Time) <-chan struct{}

WithDeadline returns empty struct channel above on `time.Timer` channel. TODO can be leaky, https://github.com/kamilsk/retry/issues/109

func WithSignal Uses

func WithSignal(s os.Signal) <-chan struct{}

WithSignal returns empty struct channel above on `os.Signal` channel. TODO can be leaky, https://github.com/kamilsk/retry/issues/109

func WithTimeout Uses

func WithTimeout(timeout time.Duration) <-chan struct{}

WithTimeout returns empty struct channel above on `time.Timer` channel. TODO can be leaky, https://github.com/kamilsk/retry/issues/109

type Action Uses

type Action func(attempt uint) error

Action defines a callable function that package retry can handle.

Directories

PathSynopsis
backoffPackage backoff provides stateless methods of calculating durations based on a number of attempts made.
classifierPackage classifier provides a way to classify an occurred error.
jitterPackage jitter provides methods of transforming durations.
strategyPackage strategy provides a way to change the way that retry is performed.

Package retry imports 8 packages (graph) and is imported by 1 packages. Updated 2018-10-17. Refresh now. Tools for package owners.