kretry

package
v0.0.0-...-7d6b634 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package kretry provides retry-with-backoff for flaky operations, with jitter and cancellation built in.

Two things worth knowing that aren't separate API surface because they're already covered by what's here:

  • A per-attempt timeout, distinct from the overall ctx passed to Do, is just context.WithTimeout(ctx, ...) inside f — f already gets ctx and can derive its own bound from it.
  • Retrying at more than one layer of a call chain (client, load balancer, gateway all retrying the same request) multiplies load on a struggling downstream service instead of backing off it — retry at one layer, or share a retry budget across them.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Do

func Do(ctx context.Context, b Backoff, f RetryFunc) error

Do calls f, retrying it with b's delays for as long as f returns an error wrapped with RetryableError. It returns nil on the first success; the most recent error once f returns a non-retryable error or b stops retrying; or ctx's error if ctx is canceled before an attempt or during a retry wait (waiting never blocks past ctx's cancellation). If f panics, the panic is not recovered — it propagates to Do's caller, same as kevent.Bus.Publish: a panic means something in f is actually broken, not a condition to retry past.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/Trxncoo/kinetic/pkg/kretry"
)

func main() {
	backoff := kretry.NewExponential(time.Millisecond).
		WithMaxRetries(5).
		WithCappedDuration(10 * time.Millisecond).
		WithFullJitter()

	attempts := 0
	err := kretry.Do(context.Background(), backoff, func(ctx context.Context) error {
		attempts++
		if attempts < 3 {
			return kretry.RetryableError(fmt.Errorf("attempt %d failed", attempts))
		}
		fmt.Println("succeeded on attempt", attempts)
		return nil
	})
	if err != nil {
		fmt.Println("failed:", err)
	}

}
Output:
succeeded on attempt 3

func DoValue

func DoValue[T any](ctx context.Context, b Backoff, f RetryFuncValue[T]) (T, error)

DoValue is Do for an operation that produces a value on success.

func RetryableError

func RetryableError(err error) error

RetryableError marks err as worth retrying. Do and DoValue treat any error NOT wrapped this way as permanent and stop immediately — opt-in to retry, not opt-out, so a caller can't accidentally retry a non-idempotent or genuinely permanent failure just because they forgot to special-case it. errors.Is and errors.As still reach the original err through the wrapper.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/Trxncoo/kinetic/pkg/kretry"
)

func main() {
	backoff := kretry.NewConstant(time.Millisecond).WithMaxRetries(3)

	attempts := 0
	err := kretry.Do(context.Background(), backoff, func(ctx context.Context) error {
		attempts++
		// A 4xx-shaped error: not wrapped with RetryableError, so Do
		// stops immediately instead of retrying it.
		return errors.New("400 bad request")
	})

	fmt.Println("attempts:", attempts)
	fmt.Println("error:", err)

}
Output:
attempts: 1
error: 400 bad request

Types

type Backoff

type Backoff struct {
	// contains filtered or unexported fields
}

Backoff computes the delay before each retry attempt. It's a concrete struct, not an interface — unlike kevent.Bus or kcache.Cache, there's no plausible second "backend" to swap in here, just algorithm composition, so a closure-wrapped struct with chainable With* methods gets simpler call sites for free.

A Backoff is stateful: each instance (and each link in a chain) closes over its own attempt count. Build one fresh per Do/DoValue call — it's not safe to share or reuse across concurrent retry loops.

func NewConstant

func NewConstant(delay time.Duration) Backoff

NewConstant returns a Backoff with a fixed delay that never stops on its own — pair it with WithMaxRetries if you want a bound.

func NewExponential

func NewExponential(base time.Duration) Backoff

NewExponential returns a Backoff that doubles the delay on every call: base, 2*base, 4*base, and so on. Like NewConstant, it never stops on its own — pair it with WithCappedDuration too, not just WithMaxRetries: time.Duration is an int64 count of nanoseconds, and doubling indefinitely will eventually overflow it (~63 doublings from 1ms) if nothing caps the delay first. WithFullJitter guards against the resulting non-positive delay rather than panicking, but backoff that's silently stopped backing off is still a real degradation, not a safe substitute for capping.

func (Backoff) Next

func (b Backoff) Next() (delay time.Duration, stop bool)

Next returns the delay before the next attempt, and whether to stop retrying.

func (Backoff) WithCappedDuration

func (b Backoff) WithCappedDuration(limit time.Duration) Backoff

WithCappedDuration clamps every delay from b to at most limit.

func (Backoff) WithFullJitter

func (b Backoff) WithFullJitter() Backoff

WithFullJitter replaces every delay from b with a random duration in [0, delay) — AWS's "Full Jitter" formula, which their own simulations found gives the lowest server load and lowest completion time among the common jitter strategies. Put this last in a chain, after any WithCappedDuration, so the cap bounds what gets jittered.

func (Backoff) WithMaxRetries

func (b Backoff) WithMaxRetries(n int) Backoff

WithMaxRetries allows at most n retries through b, not counting the first attempt — paired with Do, WithMaxRetries(2) means up to 2 retries after the first attempt fails, 3 total calls to the retried function.

type RetryFunc

type RetryFunc func(ctx context.Context) error

RetryFunc is an operation Do retries on failure.

type RetryFuncValue

type RetryFuncValue[T any] func(ctx context.Context) (T, error)

RetryFuncValue is an operation DoValue retries on failure, producing a value on success.

Jump to

Keyboard shortcuts

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