Documentation
¶
Overview ¶
Package hedge executes explicitly replay-safe operations with bounded, delayed concurrent attempts. It does not infer idempotency or clone request state; an AttemptFactory must create independently owned mutable state for every attempt.
Index ¶
- Constants
- Variables
- type Attempt
- type AttemptFactory
- type AttemptFactoryFunc
- type AttemptInfo
- type AttemptResult
- type Budget
- type CanceledError
- type Classification
- type Classifier
- type ClassifyFunc
- type CleanupError
- type Clock
- type Config
- type DeadlineError
- type DelayFunc
- type DelayInput
- type DisposeFunc
- type Disposer
- type ExecutionError
- type FactoryFailureMode
- type Failure
- type Observation
- type Observer
- type Outcome
- type OutstandingBudget
- type Permit
- type Policy
- type RealClock
- type Reason
- type Report
- type Timer
Examples ¶
Constants ¶
const MaxBudgetCapacity uint = 1_000_000
MaxBudgetCapacity prevents a nominally finite budget from acting as an operationally unbounded admission policy.
const MaxHedges uint = 64
MaxHedges bounds policy allocation, retained failure metadata, and fan-out.
const MaxResourceLength = 128
MaxResourceLength bounds endpoint-safe resource identity used by budgets and observations.
Variables ¶
var ErrInvalidBudget = errors.New("hedge: invalid budget")
ErrInvalidBudget identifies a zero or otherwise unusable shared-work bound.
var ErrInvalidPolicy = errors.New("hedge: invalid policy")
ErrInvalidPolicy identifies implicit, contradictory, or unbounded policy configuration.
Functions ¶
This section is empty.
Types ¶
type Attempt ¶
Attempt performs one independently owned execution. Cancellation is cooperative; implementations must return promptly when ctx is canceled.
type AttemptFactory ¶
type AttemptFactory[T any] interface { NewAttempt(context.Context, AttemptInfo) (Attempt[T], string, error) }
AttemptFactory creates independently owned mutable state for every attempt. Construction must honor the context, return promptly, and avoid starting the external operation. Endpoint must be a bounded, credential-free identity suitable for labels.
type AttemptFactoryFunc ¶
type AttemptFactoryFunc[T any] func(AttemptInfo) (Attempt[T], string, error)
AttemptFactoryFunc adapts a non-blocking factory function. Use a concrete AttemptFactory when construction itself needs the logical context.
func (AttemptFactoryFunc[T]) NewAttempt ¶
func (function AttemptFactoryFunc[T]) NewAttempt(_ context.Context, info AttemptInfo) (Attempt[T], string, error)
NewAttempt invokes the adapted function.
type AttemptInfo ¶
AttemptInfo identifies an original or additional execution. Ordinal zero is always the original; positive ordinals are hedges.
type AttemptResult ¶
AttemptResult is passed to a Classifier. Value and Err are never included in observations or error strings.
type Budget ¶
Budget bounds shared additional work. Implementations may account globally, per resource, or over a documented recent-work window. Capacity must be immutable, finite, and consistent with non-blocking, panic-free TryAcquire.
type CanceledError ¶
type CanceledError struct {
// contains filtered or unexported fields
}
CanceledError distinguishes caller cancellation from downstream failure.
func (*CanceledError) Error ¶
func (err *CanceledError) Error() string
func (*CanceledError) Unwrap ¶
func (err *CanceledError) Unwrap() error
type Classification ¶
type Classification uint8
Classification is the explicit policy decision for one completed attempt.
const ( // ClassificationSuccess selects the attempt as the winner. ClassificationSuccess Classification = iota + 1 // ClassificationFailure retains the failure and permits active or scheduled // attempts to continue. ClassificationFailure // ClassificationCanceled records a non-downstream cancellation and permits // other attempts to continue. ClassificationCanceled // ClassificationTerminal stops the logical operation without a winner. ClassificationTerminal )
type Classifier ¶
type Classifier[T any] interface { Classify(context.Context, AttemptResult[T]) (Classification, error) }
Classifier decides whether one result wins, fails, was canceled, or terminally stops the logical operation. It must be safe for concurrent use.
type ClassifyFunc ¶
type ClassifyFunc[T any] func(context.Context, AttemptResult[T]) (Classification, error)
ClassifyFunc adapts a function to Classifier.
func (ClassifyFunc[T]) Classify ¶
func (function ClassifyFunc[T]) Classify(ctx context.Context, result AttemptResult[T]) (Classification, error)
Classify invokes the adapted function.
type CleanupError ¶
type CleanupError struct{ Failures uint }
CleanupError reports how many result disposals failed.
func (*CleanupError) Error ¶
func (err *CleanupError) Error() string
type Clock ¶
type Clock interface {
Now() time.Time
NewTimer(time.Duration) Timer
WithTimeout(context.Context, time.Duration) (context.Context, context.CancelFunc)
}
Clock supplies monotonic policy time, timers, and deadline contexts so scheduling can be tested deterministically. Implementations must be safe for concurrent use and must not panic.
type Config ¶
type Config[T any] struct { MaxHedges uint ReplaySafe bool Delay time.Duration Schedule []time.Duration DynamicDelay DelayFunc TotalTimeout time.Duration AttemptTimeout time.Duration CleanupTimeout time.Duration Clock Clock Budget Budget Classifier Classifier[T] Disposer Disposer[T] Observer Observer Resource string FactoryFailureMode FactoryFailureMode // UseResilienceBudget consumes the shared work-amplification scope attached // to the execution context instead of the compatibility Budget. UseResilienceBudget bool }
Config defines every safety decision and dependency for a Policy. Exactly one of Delay, Schedule, or DynamicDelay must be configured.
type DeadlineError ¶
type DeadlineError struct{}
DeadlineError distinguishes the total policy deadline from downstream failure.
func (*DeadlineError) Error ¶
func (*DeadlineError) Error() string
func (*DeadlineError) Unwrap ¶
func (*DeadlineError) Unwrap() error
type DelayFunc ¶
type DelayFunc func(DelayInput) (time.Duration, error)
DelayFunc returns the delay after the preceding attempt launch.
type DelayInput ¶
DelayInput gives a dynamic delay policy bounded metadata. Dynamic functions must not retain hidden unbounded latency history.
type DisposeFunc ¶
DisposeFunc adapts a function to Disposer.
type Disposer ¶
Disposer releases resources owned by every non-winning result. It must be concurrency-safe and honor its cleanup context.
type ExecutionError ¶
type ExecutionError struct {
// contains filtered or unexported fields
}
ExecutionError reports deterministic all-attempt failure. Its message never contains downstream errors; Unwrap exposes only the documented selected cause, which is the lowest ordinal failure.
func (*ExecutionError) Error ¶
func (err *ExecutionError) Error() string
func (*ExecutionError) Unwrap ¶
func (err *ExecutionError) Unwrap() error
type FactoryFailureMode ¶
type FactoryFailureMode uint8
FactoryFailureMode defines what happens when a hedge attempt cannot be constructed. Original-attempt factory failure always stops execution.
const ( // FactoryFailureStop stops the logical operation and cancels active attempts. FactoryFailureStop FactoryFailureMode = iota + 1 // FactoryFailureContinue records the failed hedge and permits later work. FactoryFailureContinue )
type Failure ¶
type Failure struct {
Ordinal uint
Hedge bool
Delay time.Duration
Duration time.Duration
Endpoint string
Classification Classification
}
Failure retains bounded attempt metadata without retaining or joining raw error messages.
type Observation ¶
type Observation struct {
Outcome Outcome
Ordinal uint
Delay time.Duration
Duration time.Duration
Resource string
Endpoint string
// Classification is set for OutcomeAttemptCompleted and zero otherwise.
Classification Classification
Winner bool
Loser bool
}
Observation contains bounded metadata and deliberately excludes results, request data, URLs, and raw errors.
type Observer ¶
type Observer interface {
TryObserve(Observation) bool
}
Observer receives bounded lifecycle metadata through a non-blocking method. TryObserve must return immediately and must not call back into an execution.
type Outcome ¶
type Outcome uint8
Outcome identifies one bounded lifecycle event. It is safe to use as a metric label after mapping unknown values to a bounded fallback.
const ( // OutcomeNoHedgeNeeded records an original winner before any hedge started. OutcomeNoHedgeNeeded Outcome = iota + 1 // OutcomeHedgeStarted records one admitted additional attempt. OutcomeHedgeStarted // OutcomeBudgetDenied records rejected additional work. OutcomeBudgetDenied // OutcomeWinnerSelected records a winner after additional work started. OutcomeWinnerSelected // OutcomeAllAttemptsFailed records deterministic failed-result selection. OutcomeAllAttemptsFailed // OutcomeCallerCanceled records caller cancellation. OutcomeCallerCanceled // OutcomeTotalDeadline records total timeout expiry. OutcomeTotalDeadline // OutcomeAttemptCompleted records one result published to the coordinator. OutcomeAttemptCompleted // OutcomeCleanupFailed records one disposer failure. OutcomeCleanupFailed )
type OutstandingBudget ¶
type OutstandingBudget struct {
// contains filtered or unexported fields
}
OutstandingBudget bounds concurrent additional attempts across every policy and execution sharing the instance. Use one instance per resource when resources require independent limits; doing so avoids an unbounded hidden resource-key registry.
func NewOutstandingBudget ¶
func NewOutstandingBudget(limit uint) (*OutstandingBudget, error)
NewOutstandingBudget constructs a finite shared concurrency budget.
func (*OutstandingBudget) Capacity ¶
func (budget *OutstandingBudget) Capacity() uint
Capacity returns the immutable maximum number of outstanding hedges.
func (*OutstandingBudget) Outstanding ¶
func (budget *OutstandingBudget) Outstanding() uint64
Outstanding returns the number of currently admitted additional attempts.
func (*OutstandingBudget) TryAcquire ¶
func (budget *OutstandingBudget) TryAcquire(_ string) (Permit, bool)
TryAcquire reserves one additional attempt without blocking. A policy holds the permit until the completed result is consumed or reclaimed. Resource is accepted for the Budget contract but this implementation deliberately has a single bounded scope.
type Permit ¶
type Permit interface{ Release() }
Permit accounts for one additional attempt. Release must be idempotent, concurrency-safe, non-blocking, and must not panic.
type Policy ¶
type Policy[T any] struct { // contains filtered or unexported fields }
Policy is immutable and safe for concurrent execution. Its Budget, Classifier, Disposer, Clock, and Observer dependencies must also be safe for concurrent use.
type RealClock ¶
type RealClock struct{}
RealClock uses the standard library's monotonic clock and timers.
type Reason ¶
type Reason uint8
Reason identifies the terminal logical outcome.
const ( // ReasonNoHedgeNeeded means the original won before additional work started. ReasonNoHedgeNeeded Reason = iota + 1 // ReasonWinnerSelected means one of multiple started attempts won. ReasonWinnerSelected // ReasonAllAttemptsFailed means every admitted attempt completed unsuccessfully. ReasonAllAttemptsFailed // ReasonCallerCanceled means the caller canceled the logical operation. ReasonCallerCanceled // ReasonTotalDeadline means the configured total timeout elapsed. ReasonTotalDeadline // ReasonTerminalFailure means classification stopped the logical operation. ReasonTerminalFailure // ReasonFactoryFailure means attempt construction stopped the operation. ReasonFactoryFailure // ReasonDelayFailure means dynamic delay selection failed validation. ReasonDelayFailure // ReasonBudgetFailure means shared budget setup or admission failed locally. ReasonBudgetFailure )
type Report ¶
type Report struct {
Reason Reason
AttemptsStarted uint
HedgesStarted uint
BudgetDenied uint
WinnerOrdinal uint
SelectedOrdinal uint
Failures []Failure
// contains filtered or unexported fields
}
Report describes one logical execution. Wait lets callers and graceful shutdown code wait for cooperative loser cleanup without delaying winner delivery.
func Do ¶
func Do[T any](ctx context.Context, policy *Policy[T], factory AttemptFactory[T]) (T, Report, error)
Do executes one original attempt and at most MaxHedges delayed concurrent attempts. The factory is required to create independently owned mutable state; Do never clones or interprets application requests.
Example ¶
package main
import (
"context"
"fmt"
"time"
"github.com/faustbrian/go-hedge"
)
func main() {
budget, _ := hedge.NewOutstandingBudget(1)
policy, _ := hedge.NewPolicy(hedge.Config[string]{
MaxHedges: 1,
ReplaySafe: true,
Delay: time.Hour,
TotalTimeout: time.Second,
CleanupTimeout: time.Second,
Clock: hedge.RealClock{},
Budget: budget,
Classifier: hedge.ClassifyFunc[string](func(_ context.Context, result hedge.AttemptResult[string]) (hedge.Classification, error) {
if result.Err == nil {
return hedge.ClassificationSuccess, nil
}
return hedge.ClassificationFailure, nil
}),
Disposer: hedge.DisposeFunc[string](func(context.Context, string) error { return nil }),
Resource: "profile-read",
FactoryFailureMode: hedge.FactoryFailureStop,
})
value, report, err := hedge.Do(context.Background(), policy,
hedge.AttemptFactoryFunc[string](func(hedge.AttemptInfo) (hedge.Attempt[string], string, error) {
return func(context.Context) (string, error) { return "profile", nil }, "pod-a", nil
}))
fmt.Println(value, report.Reason == hedge.ReasonNoHedgeNeeded, err)
}
Output: profile true <nil>