retry

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jan 20, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package retry implements a generic and customizable retry mechanism.

Took some inspiration from: - https://github.com/eapache/go-resiliency/tree/main/retrier

Example (RetryCustomClassifier)
package main

import (
	"errors"
	"fmt"
	"log/slog"
	"os"
	"time"

	"github.com/Pix4D/go-kit/retry"
)

// Used in [ExampleRetry_CustomClassifier].
var ErrBananaUnavailable = errors.New("banana service unavailable")

// Embedded in [BananaResponseError].
type BananaResponse struct {
	Amount int
}

// Used in [ExampleRetry_CustomClassifier].
type BananaResponseError struct {
	Response *BananaResponse
}

func (eb BananaResponseError) Error() string {
	return "look at my fields, there is more information there"
}

func main() {
	rtr := retry.Retry{
		UpTo:         30 * time.Second,
		FirstDelay:   2 * time.Second,
		BackoffLimit: 1 * time.Minute,
		Log: slog.New(slog.NewTextHandler(os.Stdout,
			&slog.HandlerOptions{ReplaceAttr: removeTime})),
		SleepFn: func(d time.Duration) {}, // Only for the test!
	}

	attempt := 0
	workFn := func() error {
		attempt++
		if attempt == 3 {
			// Error wrapping is optional; we do it to show that it works also.
			return fmt.Errorf("workFn: %w",
				BananaResponseError{Response: &BananaResponse{Amount: 42}})
		}
		if attempt < 5 {
			return ErrBananaUnavailable
		}
		// On 5th attempt we finally succeed.
		return nil
	}

	classifierFn := func(err error) retry.Action {
		var bananaResponseErr BananaResponseError
		if errors.As(err, &bananaResponseErr) {
			response := bananaResponseErr.Response
			if response.Amount == 42 {
				return retry.SoftFail
			}
			return retry.HardFail
		}
		if errors.Is(err, ErrBananaUnavailable) {
			return retry.SoftFail
		}
		if err != nil {
			return retry.HardFail
		}
		return retry.Success
	}

	err := rtr.Do(retry.ExponentialBackoff, classifierFn, workFn)
	if err != nil {
		// Handle error...
		fmt.Println("error:", err)
	}

}

// removeTime removes time-dependent attributes from log/slog records, making
// the output of testable examples [1] deterministic.
// [1]: https://go.dev/blog/examples
func removeTime(groups []string, a slog.Attr) slog.Attr {
	if a.Key == slog.TimeKey {
		return slog.Attr{}
	}

	return a
}
Output:

level=INFO msg=waiting system=retry attempt=1 delay=2s totalDelay=2s
level=INFO msg=waiting system=retry attempt=2 delay=4s totalDelay=6s
level=INFO msg=waiting system=retry attempt=3 delay=8s totalDelay=14s
level=INFO msg=waiting system=retry attempt=4 delay=16s totalDelay=30s
level=INFO msg=success system=retry attempt=5 totalDelay=30s

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConstantBackoff

func ConstantBackoff(first bool, previous, limit time.Duration, err error) time.Duration

func ExponentialBackoff

func ExponentialBackoff(first bool, previous, limit time.Duration, err error) time.Duration

Types

type Action

type Action int

Action is returned by a ClassifierFunc to indicate to Retry how to proceed.

const (
	// Success informs Retry that the attempt has been a success.
	Success Action = iota
	// HardFail informs Retry that the attempt has been a hard failure and
	// thus should abort retrying.
	HardFail
	// SoftFail informs Retry that the attempt has been a soft failure and
	// thus should keep retrying.
	SoftFail
)

type BackoffFunc

type BackoffFunc func(first bool, previous, limit time.Duration, err error) time.Duration

BackoffFunc returns the next backoff duration; called by Retry.Do. You can use one of the ready-made functions ConstantBackoff, ExponentialBackoff or write your own. Parameter err allows to optionally inspect the error that caused the retry and return a custom delay; this can be used in special cases such as when rate-limited with a fixed window; for an example see github.com/Pix4D/go-kit/github.Backoff.

type ClassifierFunc

type ClassifierFunc func(err error) Action

ClassifierFunc decides whether to proceed or not; called by Retry.Do. Parameter err allows to inspect the error; for an example see github.com/Pix4D/go-kit/github.Classifier

type Retry

type Retry struct {
	UpTo         time.Duration // Total maximum duration of the retries.
	FirstDelay   time.Duration // Duration of the first backoff.
	BackoffLimit time.Duration // Upper bound duration of a backoff.
	Log          *slog.Logger
	SleepFn      func(d time.Duration) // Optional; used only to override in tests.
}

Retry is the controller of the retry mechanism. See the examples in file retry_example_test.go.

Example
package main

import (
	"fmt"
	"log/slog"
	"os"
	"time"

	"github.com/Pix4D/go-kit/retry"
)

func main() {
	rtr := retry.Retry{
		UpTo:         5 * time.Second,
		FirstDelay:   1 * time.Second,
		BackoffLimit: 1 * time.Second,
		Log: slog.New(slog.NewTextHandler(os.Stdout,
			&slog.HandlerOptions{ReplaceAttr: removeTime})),
	}

	workFn := func() error {
		// Do work...
		// If something fails, as usual, return error.

		// Everything went well.
		return nil
	}
	classifierFn := func(err error) retry.Action {
		if err != nil {
			return retry.SoftFail
		}
		return retry.Success
	}

	err := rtr.Do(retry.ConstantBackoff, classifierFn, workFn)
	if err != nil {
		// Handle error...
		fmt.Println("error:", err)
	}

}

// removeTime removes time-dependent attributes from log/slog records, making
// the output of testable examples [1] deterministic.
// [1]: https://go.dev/blog/examples
func removeTime(groups []string, a slog.Attr) slog.Attr {
	if a.Key == slog.TimeKey {
		return slog.Attr{}
	}

	return a
}
Output:

level=INFO msg=success system=retry attempt=1 totalDelay=0s

func (Retry) Do

func (rtr Retry) Do(
	backoffFn BackoffFunc,
	classifierFn ClassifierFunc,
	workFn WorkFunc,
) error

Do is the loop of Retry. See the examples in file retry_example_test.go.

type WorkFunc

type WorkFunc func() error

WorkFunc does the unit of work that might fail and need to be retried; called by Retry.Do.

Jump to

Keyboard shortcuts

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