nxretry

package module
v0.0.0-...-b0019d7 Latest Latest
Warning

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

Go to latest
Published: Apr 8, 2026 License: MIT Imports: 4 Imported by: 1

README

nxretry

Godoc Reference Pipeline Status

Context-aware retrier with an optional and customizable backoff.

Installation

go get github.com/matthewpi/nxretry

Usage

r := nxretry.New(
	nxretry.MaxAttempts(3),
	nxretry.Exponential{
		Factor: 2,
		Min:    1 * time.Second,
		Max:    5 * time.Second,
	},
)

// Run code with the ability to retry, optionally using the provided context.
for ctx := range r.Next(context.Background()) {
	_ = ctx

	// Do something.
	//
	// `break` on success (or if you don't want to retry anymore) and `continue` on failure.
}

See example_test.go or the Godoc reference for more examples

Licensing

All code in this repository is licensed under the MIT license.

This package includes ZERO external dependencies, including any golang.org/x packages.

Documentation

Overview

Package nxretry implements a context-aware retrier with an optional and customizable backoff.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Backoff

type Backoff interface {
	// TODO: this interface cannot be publicly implemented, making external
	// Backoff implementations impossible.
	Option

	// Delay returns the [time.Duration] to wait before running the given attempt.
	Delay(attempt uint) time.Duration
}

Backoff represents an abstract backoff implementation.

This is used to abstract the duration to wait between attempts, to allow for different algorithms/implementations.

type ContextFactory

type ContextFactory func(context.Context) (context.Context, context.CancelFunc)

ContextFactory is a function that takes a context and returns a modified context, optionally with an associated cancel function. It is required to return a non-nil context.Context, but the context.ContextFunc may be `nil` or an empty function `func() { }`.

type Exponential

type Exponential struct {
	// Factor is the factor at which Min will increase after each failed attempt.
	Factor float64
	// Min is the initial backoff time to wait after the first failed attempt.
	Min time.Duration
	// Max is the maximum time to wait before retrying.
	Max time.Duration
}

Exponential is the implementation of an exponential Backoff.

func (Exponential) Delay

func (e Exponential) Delay(attempt uint) time.Duration

Delay returns the time.Duration to wait before running the given attempt.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option for a Retry.

type OptionFunc

type OptionFunc func(o *options)

OptionFunc type is an adapter to allow the use of ordinary functions as an Option. If f is a function with the appropriate signature, `OptionFunc(f)` is an Option that calls f.

func MaxAttempts

func MaxAttempts(maxAttempts uint) OptionFunc

MaxAttempts sets the maximum number of attempts that can occur. If set to 0 the maximum number of attempts will be unlimited.

func WithContextFactory

func WithContextFactory(f ContextFactory) OptionFunc

WithContextFactory sets the ContextFactory used by a Retry.

The context factory is invoked before each attempt and the returned context.Context is passed through by the [Retry.Next] iterator. If a non-nil context.CancelFunc is provided by the context factory, it will be called after the current iteration completes.

func WithTimer

func WithTimer(t Timer) OptionFunc

WithTimer overrides the Timer used by a Retry. This should only ever be used for testing.

type Retry

type Retry interface {
	// Reset resets the [Retry] instance back to its original state.
	//
	// [Retry.Attempt], [Retry.Delay] and [Retry.Override], will be all be reset
	// to `0`.
	Reset()

	// Attempt returns the number of the current attempt.
	Attempt() uint

	// Delay returns the [time.Duration] to wait for the next attempt.
	//
	// The delay will be calculated using the [Backoff] configured on the [Retry]
	// instance. If [Retry.Override] has been used, its value will be returned
	// instead, until it has been consumed by an iteration of [Retry.Next].
	//
	// This function is useful for logging when the next attempt will occur.
	Delay() time.Duration

	// Next increments the attempt, then waits for the duration of the attempt.
	// Once the duration has passed, Next returns true. Next will return false if
	// the attempt will exceed the MaxAttempts limit or if the given context has
	// been canceled.
	//
	// This function was designed to be used as follows:
	//
	// 	r := New()
	// 	for ctx := range r.Next(ctx) {
	// 		// Do work, `continue` on soft-failure, `break` on success or non-retryable error.
	// 	}
	Next(ctx context.Context) iter.Seq[context.Context]

	// Override overrides the delay for the [Retry.Next] iteration.
	//
	// If a value of `0` is given, no override will be performed on the next
	// iteration.
	//
	// Calling [Retry.Override] multiple times before an iteration if safe, but
	// only the value from the last call will be used.
	//
	// The value of [Retry.Attempt] will still be incremented and count towards
	// the maximum attempt limit, however [Retry.Delay] will return a delay
	// as-if the overridden attempt never occurred. This is to allow for
	// temporary overrides without exceeding the maximum attempt limit or
	// changing the [Retry.Delay] for subsequent non-overridden attempts.
	//
	// [Retry.Delay] will temporarily return the value of `d`, until the
	// next iteration where the override will be reset. After the override
	// has been reset, [Retry.Delay] will return a delay as-if the overridden
	// attempt never occurred. This is to allow for temporary overrides without
	// exceeding the maximum attempt limit or affecting the delay for subsequent
	// non-overridden attempts.
	//
	// Subsequent iterations of [Retry.Next] will continue to use the [Backoff]
	// configured on the [Retry] instance, unless [Retry.Override] is called
	// again before the next iteration.
	Override(d time.Duration)
}

Retry represents a retrier implementation.

func New

func New(opts ...Option) Retry

New creates a new retrier.

Example
package main

import (
	"context"
	"time"

	"github.com/matthewpi/nxretry"
)

func main() {
	r := nxretry.New(
		nxretry.MaxAttempts(3),
		nxretry.Exponential{
			Factor: 2,
			Min:    1 * time.Second,
			Max:    5 * time.Second,
		},
	)

	// Run code with the ability to retry, optionally using the provided context.
	for ctx := range r.Next(context.Background()) {
		_ = ctx

		// Do something.
		//
		// `break` on success (or if you don't want to retry anymore) and `continue` on failure.
	}
}
Example (ContextFactory)
package main

import (
	"context"
	"time"

	"github.com/matthewpi/nxretry"
)

func main() {
	r := nxretry.New(
		nxretry.MaxAttempts(3),
		nxretry.Exponential{
			Factor: 2,
			Min:    1 * time.Second,
			Max:    5 * time.Second,
		},
		// For each attempt create a context with a dedicated timeout to prevent
		// any individual attempt from being attempted for too long.
		nxretry.WithContextFactory(func(ctx context.Context) (context.Context, context.CancelFunc) {
			return context.WithTimeout(ctx, 5*time.Second)
		}),
	)

	// Run code with the ability to retry, optionally using the provided context.
	for ctx := range r.Next(context.Background()) {
		_ = ctx // Context with timeout from the context factory.

		// Do something.
		//
		// `break` on success (or if you don't want to retry anymore) and `continue` on failure.
	}
}
Example (NoContext)
package main

import (
	"context"
	"time"

	"github.com/matthewpi/nxretry"
)

func main() {
	r := nxretry.New(
		nxretry.MaxAttempts(3),
		nxretry.Exponential{
			Factor: 2,
			Min:    1 * time.Second,
			Max:    5 * time.Second,
		},
	)

	// Run code with the ability to retry.
	for range r.Next(context.Background()) {
		// Do something.
		//
		// `break` on success (or if you don't want to retry anymore) and `continue` on failure.
	}
}

type Timer

type Timer interface {
	// C returns the channel where the current [time.Time] will be sent when the
	// timer fires. Calling [Timer.C] before [Timer.Start] will return nil, if
	// you ever find yourself nil-checking the result of this function, you are
	// likely using this interface incorrectly.
	C() <-chan time.Time

	// Start starts a timer using the specified duration. If Start is being
	// called for the first time it will create a new underlying timer,
	// otherwise it will reset the existing timer to a new duration.
	//
	// If you are re-using the same timer by calling Start multiple times,
	// ensure that the channel returned by [Timer.C] was drained or that
	// [Timer.Stop] was called properly according to its documentation.
	//
	// Essentially always ensure that the channel was drained if a value was
	// ever sent.
	Start(time.Duration)

	// Stop prevents the Timer from firing.
	//
	// It returns true if the call stops the timer, false if the timer has
	// already expired or been stopped.
	//
	// Stop does not close the channel, to prevent a read from the channel
	// succeeding incorrectly.
	//
	// To ensure the channel is empty after a call to Stop, check the
	// return value and drain the channel.
	// For example, assuming the program has not received from t.C already:
	//
	//	if !t.Stop() {
	//		<-t.C()
	//	}
	//
	// This cannot be done concurrent to other receives from the Timer's
	// channel or other calls to the Timer's Stop method.
	Stop() bool
}

Timer is used as an abstraction to swap out the timer implementation used by Backoff. Most users will not need to implement this interface, it is used for mocking during tests.

func NewRealTimer

func NewRealTimer() Timer

NewRealTimer returns a new real timer.

Jump to

Keyboard shortcuts

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