backoff

package
v0.34.1 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package backoff is the retry arithmetic of plan 6.6 and 6.8 item 7 (U-16): an exponential schedule with jitter for the watcher's restart of its `message watch` child (1 s doubling to a 30 s cap, and a fixed 5 min once that pace has failed ten times — card 34) and for adapter errors (1 s doubling to a 5 min cap), and the retryability predicate — only `rate_limited` and `unavailable` (and a crash or signal, which adapterkit.Spawn already maps to `unavailable`) are ever retried; `invalid_input`, `unauthorized`, `loop_detected` and every other code are terminal. Nothing here sleeps or keeps time: a Schedule hands back durations and the caller (P3-5's watcher, the socket poster's retry loop) waits on them, which is what keeps the schedule testable under testing/synctest and its sequence exact for a seeded source.

Index

Constants

View Source
const (
	// WatchRestartMin and WatchRestartMax bound the restart of the
	// `message watch` child (6.6: "exponential backoff 1 s..30 s with
	// jitter").
	WatchRestartMin = time.Second
	WatchRestartMax = 30 * time.Second
	// WatchSlowRetry is the pace the watcher falls back to once the restarts
	// above have failed often enough to trip its give-up rule on a failure a
	// restart can fix (card 34): it tries again at least this often for as
	// long as the session lives, instead of exiting. With the schedule's
	// jitter a delay is between half of it and all of it.
	WatchSlowRetry = 5 * time.Minute
	// AdapterErrorMin and AdapterErrorMax bound retries of a failed
	// adapter call (6.8 item 7: "exponential with jitter, capped at 5 min").
	AdapterErrorMin = time.Second
	AdapterErrorMax = 5 * time.Minute
)

The schedules the plan names, and the slow retry of card 34.

View Source
const Factor = 2

Factor is the growth per attempt.

Variables

This section is empty.

Functions

func Retryable

func Retryable(code protocol.Code) bool

Retryable reports whether a failed adapter call with this code may be retried (4.3, 4.6, U-16): only rate_limited and unavailable. Every other code — invalid_input, unauthorized, loop_detected, and the rest — is terminal, and so is a code this harness does not recognise (4.6 maps an unknown code to internal). The switch is exhaustive over protocol.Code so a code added to the taxonomy must be classified here on purpose.

func RetryableError

func RetryableError(err error) bool

RetryableError is Retryable over an error: a *protocol.Error anywhere in the chain is classified by its code (adapterkit.Spawn maps a timeout, a signal death and a missing executable to unavailable, so those retry), and any other error — including nil — is not retried, because an error the harness cannot classify is a bug, not a transient.

func RetryableExit

func RetryableExit(exit int) bool

RetryableExit reports whether a `message watch` child that ended with this exit status should be restarted (6.6: "restart ... on exit codes 8 and 9 or a crash; stop on 4, 5, 10, 11"). An exit status that is a 4.6 code's is classified as that code (so 8 and 9 restart; 1, 2, 3, 4, 5, 6, 7, 10, 11 and 12 stop); 0 is a clean end and is not a failure to retry; anything else — a signal death (-1 from exec.ExitError.ExitCode(), or 128+n from a shell), 126/127, or a status no code owns — is a crash and restarts. The caller's give-up rule (10 consecutive failures inside 5 minutes) bounds every fast loop this can start: past it the watcher exits, or — for a code Retryable accepts — drops to WatchSlowRetry.

Types

type Schedule

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

A Schedule yields the delay before each successive attempt. Delay n is drawn uniformly from [base/2, base] where base = min·Factor^n capped at max ("equal jitter"): it never drops below half the minimum, never exceeds the cap, and doubles in expectation until the cap. Reset returns to attempt 0 after a success. A Schedule is not safe for concurrent use; each retry loop owns one.

func AdapterError

func AdapterError(rng *rand.Rand) *Schedule

AdapterError is the 6.8 schedule for retrying a failed adapter call.

func New

func New(minDelay, maxDelay time.Duration, rng *rand.Rand) *Schedule

New builds a schedule from minDelay doubling to maxDelay. rng is the jitter source; tests pass rand.New(rand.NewPCG(seed, seed)) for an exact sequence, and nil means a fresh ChaCha8 source seeded from crypto/rand (jitter needs no secrecy, but an unseeded source would make every watcher on a machine retry in lockstep). minDelay must be positive and maxDelay at least minDelay; New clamps rather than panics, because a retry loop is the wrong place to crash.

func WatchRestart

func WatchRestart(rng *rand.Rand) *Schedule

WatchRestart is the 6.6 schedule for restarting the watch child.

func WatchSlow added in v0.15.0

func WatchSlow(rng *rand.Rand) *Schedule

WatchSlow is the slow retry of the watch child (card 34): a fixed base, so only the jitter varies.

func (*Schedule) Attempt

func (s *Schedule) Attempt() int

Attempt is how many delays Next has handed out since New or Reset.

func (*Schedule) Base

func (s *Schedule) Base() time.Duration

Base returns the un-jittered delay of the NEXT attempt: min·Factor^n capped at max. Exposed so a caller can log "retrying in about N s" without consuming the draw.

func (*Schedule) Max

func (s *Schedule) Max() time.Duration

Max is the cap.

func (*Schedule) Min

func (s *Schedule) Min() time.Duration

Min and Max report the bounds the schedule was built with.

func (*Schedule) Next

func (s *Schedule) Next() time.Duration

Next returns the delay before the next attempt and advances the schedule. The first call after New or Reset is attempt 0: a delay in [min/2, min].

func (*Schedule) Reset

func (s *Schedule) Reset()

Reset returns the schedule to attempt 0, after a success.

Jump to

Keyboard shortcuts

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