Documentation
¶
Index ¶
Constants ¶
const ( // UnlimitedRetries disables the retry limit — the task retries forever // (until a permanent error or context cancellation). // Use with caution: set this explicitly to opt in. UnlimitedRetries = -1 // DefaultMaxRetries is used when MaxRetries is 0 (zero-value). DefaultMaxRetries = 3 )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BackoffConfig ¶ added in v0.5.0
type BackoffConfig struct {
// Initial is the delay before the first retry (attempt 0).
// Must be > 0. Validated at Task construction time.
Initial time.Duration
// Max caps the computed delay. 0 means no cap (use with caution —
// combined with UnlimitedRetries, delay grows until overflow).
Max time.Duration
// Multiplier scales the delay on each consecutive attempt.
// Must be > 0. Validated at Task construction time.
// 1.0 = constant delay, 2.0 = classic exponential, 1.5 = gentle growth.
Multiplier float64
}
BackoffConfig controls exponential backoff between retry attempts.
Formula: delay = Initial * Multiplier^attempt
Example with Initial=1s, Multiplier=2.0, Max=30s:
attempt 0: 1s * 2^0 = 1s attempt 1: 1s * 2^1 = 2s attempt 2: 1s * 2^2 = 4s attempt 3: 1s * 2^3 = 8s ... attempt 5: 1s * 2^5 = 32s → capped at 30s
func Backoff ¶ added in v0.14.0
func Backoff(initial, maxCap time.Duration) BackoffConfig
Backoff is a convenience constructor for BackoffConfig with sensible defaults. Multiplier defaults to 2.0 (classic exponential backoff).
Example:
longrun.Backoff(2*time.Second, 2*time.Minute)
// → BackoffConfig{Initial: 2s, Max: 2m, Multiplier: 2.0}
func DefaultBackoff ¶ added in v0.5.0
func DefaultBackoff() BackoffConfig
DefaultBackoff returns a sensible default backoff configuration.
Configured as Initial=1s, Max=30s, Multiplier=2.0
Perfect for 5 retries
func (*BackoffConfig) Duration ¶ added in v0.6.0
func (b *BackoffConfig) Duration(attempt int) time.Duration
Duration returns the backoff duration for the given 0-based attempt index.
When Max > 0, the result is capped at Max. When Max is 0 (no cap) and the computed duration overflows (e.g. after thousands of attempts with UnlimitedRetries), Duration clamps to math.MaxInt64.
type Baseline ¶ added in v0.14.0
type Baseline struct {
// Node policy for transport-level errors (TCP, DNS, TLS, timeout).
// Aggressive retry — network will recover.
Node Policy
// Service policy for service-pressure errors (rate limit, 5xx).
// Gentle retry — don't overload the remote service.
Service Policy
// Degraded policy for unknown/unclassified errors.
// nil → unknown errors are permanent (crash). Use for preflights.
// non-nil → retry with loud ERROR logging. Use for workers.
Degraded *Policy
// Classify is the application-level classifier.
// Called after built-in transport classification.
// nil = no application classification, only transport + degraded.
Classify ClassifierFunc
}
Baseline is a set of policies that Runner silently applies to every task. Tasks don't know about baseline — it's configured once on Runner.
Classification pipeline in handleFailure:
[1] Built-in transport classify (net.OpError, timeout → Node)
[2] User classifier via Classify (apierr interfaces → Service)
[3] Not classified → Unknown ->
Unknown + Degraded != nil → retry with Degraded policy (LOUD log)
Unknown + Degraded == nil → permanent error
type ClassifierFunc ¶ added in v0.14.0
type ClassifierFunc func(err error) *ErrorClass
ClassifierFunc inspects an error and returns its classification. Return nil if the error is not recognized — the next classification step will handle it.
ClassifierFunc must be safe to call concurrently from multiple goroutines.
type ErrorCategory ¶ added in v0.14.0
type ErrorCategory int
ErrorCategory classifies an error for baseline policy selection.
const ( // CategoryUnknown means the error was not recognized by any classifier. // If Baseline.Degraded is set — retry with degraded policy. // If Baseline.Degraded is nil — permanent error. CategoryUnknown ErrorCategory = iota // CategoryNode indicates a transport-level failure (TCP, DNS, TLS, timeout). // The request never reached the server or the connection was interrupted. // Retry aggressively — the network will recover. CategoryNode // CategoryService indicates the remote service is under pressure // (rate limit, 5xx, maintenance). Retry gently — don't kick them // while they're down. CategoryService )
type ErrorClass ¶ added in v0.14.0
type ErrorClass struct {
// Category determines which baseline policy to use.
Category ErrorCategory
// WaitDuration, when > 0, overrides the backoff calculation.
// The task sleeps exactly this duration instead of using
// policy.Backoff.Duration(attempt).
// Typical source: Retry-After header on HTTP 429.
WaitDuration time.Duration
}
ErrorClass is the result of error classification. Returned by ClassifierFunc to tell handleFailure which category the error belongs to and optionally how long to wait before retrying.
func ClassifyTransport ¶ added in v0.14.0
func ClassifyTransport(err error) *ErrorClass
ClassifyTransport checks whether err is a transport-level failure. Returns CategoryNode for network and timeout errors, nil otherwise.
This is a built-in classifier that depends only on stdlib. It runs before any user-provided ClassifierFunc in the handleFailure pipeline.
Exported for testability and for use in custom ClassifierFunc implementations that want to extend (not replace) the built-in transport classification.
Classification rules:
- url.Error with Timeout() → Node (check before net.OpError because url.Error often wraps it)
- context.DeadlineExceeded → Node
- net.OpError → Node
- net.DNSError → Node
- io.EOF, io.ErrUnexpectedEOF → Node (connection dropped mid-response)
type Matcher ¶ added in v0.6.0
type Matcher struct {
// contains filtered or unexported fields
}
Matcher checks whether an error matches a given pattern.
Two forms are supported:
- error value (sentinel): matched via errors.Is
- *T where T implements error: matched via errors.As
Examples:
NewMatcher(ErrTimeout) // sentinel → errors.Is NewMatcher((*net.OpError)(nil)) // pointer-to-type → errors.As
func NewMatcher ¶ added in v0.6.0
NewMatcher compiles an error pattern into a Matcher.
The errVal argument must be one of:
- an error value (for errors.Is matching)
- a pointer to an error type, i.e. *T where T implements error (for errors.As matching)
Panics if errVal is nil or an unsupported type.
type Option ¶ added in v0.6.0
type Option func(*taskConfig)
Option configures a Task. Use With* functions to create options.
func WithDelay ¶ added in v0.6.0
WithDelay delays the first execution by the given duration. For interval tasks: first tick fires after delay, then every interval. For one-shot tasks: execution starts after delay. Delay is independent of interval.
func WithLogger ¶ added in v0.6.0
WithLogger sets a custom logger for the task. Defaults to slog.Default().
func WithShutdown ¶ added in v0.6.0
func WithShutdown(fn ShutdownFunc) Option
WithShutdown registers a graceful shutdown hook for the task. The hook is called by Runner after all task goroutines have stopped.
func WithTimeout ¶ added in v0.6.0
WithTimeout sets a per-invocation timeout for the work function. Each call to work gets its own context with this deadline.
type Policy ¶ added in v0.14.0
type Policy struct {
// Retries limits consecutive retry attempts.
// 0 (zero-value) → unlimited retries (baseline default).
// >0 → exact retry count.
Retries int
// Backoff controls exponential backoff between retries.
Backoff BackoffConfig
}
Policy defines retry behavior for a single error category.
type RuleTracker ¶ added in v0.6.0
type RuleTracker struct {
// contains filtered or unexported fields
}
RuleTracker tracks retry attempts for a single TransientRule.
Each rule has its own independent budget. The tracker is created internally by Task from TransientRule.MaxRetries.
func NewRuleTracker ¶ added in v0.6.0
func NewRuleTracker(maxRetries int) *RuleTracker
NewRuleTracker creates a tracker with the given max retries.
MaxRetries semantics:
0 (zero-value) → DefaultMaxRetries (3). -1 (UnlimitedRetries) → no limit. >0 → exact limit.
func (*RuleTracker) Attempt ¶ added in v0.6.0
func (rt *RuleTracker) Attempt() int
Attempt returns the current attempt count.
func (*RuleTracker) Max ¶ added in v0.6.0
func (rt *RuleTracker) Max() int
Max returns the resolved max retries.
func (*RuleTracker) OnFailure ¶ added in v0.6.0
func (rt *RuleTracker) OnFailure() (int, bool)
OnFailure records a failure and returns the 0-based attempt index and whether the caller is allowed to retry.
Example with max=3:
1st call: attempt=0, ok=true 2nd call: attempt=1, ok=true 3rd call: attempt=2, ok=true 4th call: attempt=3, ok=false (budget exhausted)
func (*RuleTracker) Reset ¶ added in v0.6.0
func (rt *RuleTracker) Reset()
Reset sets the attempt counter back to zero (e.g. after healthy progress).
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
Runner orchestrates N tasks. When any task returns a permanent error the runner cancels all remaining tasks and performs graceful shutdown.
Runner does NOT handle OS signals — pass a cancellable context (e.g. via signal.NotifyContext).
func NewRunner ¶
func NewRunner(opts RunnerOptions) *Runner
NewRunner creates a Runner with the given options.
func (*Runner) Add ¶
Add registers a task for concurrent execution. If Runner has a Baseline configured, it is passed to the task.
func (*Runner) Wait ¶
Wait starts all tasks concurrently and blocks until they all finish. When any task returns an error, all other tasks are cancelled via ctx. After all goroutines finish, shutdown hooks are called in LIFO order (reverse of Add). The ctx passed in controls the lifetime — the runner does NOT listen for OS signals; use signal.NotifyContext in the caller.
type RunnerOptions ¶ added in v0.5.0
type RunnerOptions struct {
ShutdownTimeout time.Duration // default 30s
Logger *slog.Logger // nil = slog.Default()
// Baseline is a set of policies silently applied to every task.
// When set, Runner passes it to each Task at Add time.
// Zero value means no baseline — tasks rely solely on their own TransientRules.
Baseline Baseline
}
RunnerOptions configures a Runner.
type ShutdownFunc ¶ added in v0.5.0
ShutdownFunc is called during graceful shutdown.
type Task ¶ added in v0.5.0
type Task struct {
// contains filtered or unexported fields
}
Task is a self-contained unit of work with interval, retry and backoff support. It can be used standalone (via Wait) or managed by a Runner.
Task is NOT safe for concurrent use — call Wait from a single goroutine. Runner handles this automatically (one goroutine per task).
func NewIntervalTask ¶ added in v0.6.0
func NewIntervalTask(name string, interval time.Duration, work WorkFunc, rules []TransientRule, opts ...Option) *Task
NewIntervalTask creates a task that runs on a ticker loop. If rules is nil — any error kills the task. If rules is provided — transient errors are retried per their configuration, permanent errors (no matching rule) kill the task.
Each TransientRule binds an error to its own retry budget and backoff curve. TransientRule.MaxRetries limits consecutive failures for that rule. When a tick completes successfully, all rule trackers reset — so intermittent failures separated by successful ticks never accumulate toward MaxRetries.
Panics if work is nil or interval <= 0. Panics if any rule has nil Err, unsupported Err type, or Backoff.Initial <= 0.
func NewOneShotTask ¶ added in v0.6.0
func NewOneShotTask(name string, work WorkFunc, rules []TransientRule, opts ...Option) *Task
NewOneShotTask creates a task that executes once. If rules is nil — no retries, any error is fatal. If rules is provided — transient errors are retried per their configuration.
Each TransientRule binds an error to its own retry budget and backoff curve. TransientRule.MaxRetries limits consecutive failures for that rule — the budget is never reset mid-execution for one-shot tasks.
Panics if work is nil. Panics if any rule has nil Err, unsupported Err type, or Backoff.Initial <= 0.
type TransientRule ¶ added in v0.6.0
type TransientRule struct {
// Err is the error to match.
// Must be an error value (for errors.Is) or a pointer to an error type (for errors.As).
// Passing nil or an unsupported type panics at construction time.
// Examples:
//
// {Err: ErrTimeout} // sentinel → errors.Is
// {Err: (*net.OpError)(nil)} // pointer-to-type → errors.As
Err error
// MaxRetries limits consecutive retry attempts for this rule.
// 0 (zero-value) → DefaultMaxRetries (3) — safe default.
// -1 (UnlimitedRetries) → no limit — explicit opt-in.
// >0 → exact retry count.
MaxRetries int
Backoff BackoffConfig
}
TransientRule binds an error to its retry settings. Different errors can have different retry budgets and backoff curves.
The Err field accepts two forms:
- error value (sentinel): matched via errors.Is
- *T where T implements error: matched via errors.As
Examples:
{Err: ErrTimeout} // sentinel → errors.Is
{Err: (*net.OpError)(nil)} // pointer-to-type → errors.As
func TransientGroup ¶ added in v0.7.0
func TransientGroup(maxRetries int, backoff BackoffConfig, errs ...error) []TransientRule
TransientGroup creates N rules with identical MaxRetries and BackoffConfig. Each rule gets its own independent retry budget — failures of one error do not count toward the budget of another.
Each error in errs must be a valid Err value (sentinel or typed nil pointer). See TransientRule.Err for details.
Example:
longrun.TransientGroup(longrun.UnlimitedRetries, longrun.DefaultBackoff(),
(*net.OpError)(nil),
ErrFetchIssues,
ErrStoreIssues,
)