Documentation
¶
Index ¶
- Variables
- func Execute[T any](ctx context.Context, policy Policy, fn func(context.Context) (T, error)) (T, error)
- func RetryAll(err error, _ int) bool
- func RetryNone(_ error, _ int) bool
- func WithFallback[T any](ctx context.Context, policy Policy, fn func(context.Context) (T, error), ...) (T, error)
- type AtomicCounter
- type BackoffStrategy
- func ConstantBackoff(d time.Duration) BackoffStrategy
- func ExponentialBackoff(base time.Duration, multiplier float64, max time.Duration) BackoffStrategy
- func ExponentialBackoffWithJitter(base time.Duration, multiplier float64, max time.Duration, ...) BackoffStrategy
- func LinearBackoff(initial, step time.Duration, max time.Duration) BackoffStrategy
- type Bulkhead
- type BulkheadConfig
- type CBConfig
- type CircuitBreaker
- type Clock
- type CountWindow
- type FailureWindow
- type NoopObserver
- type Observer
- type Pipeline
- type Policy
- type Retry
- type RetryConfig
- type RetryPredicate
- type SlidingWindow
- type State
- type Timeout
- type TimeoutConfig
Constants ¶
This section is empty.
Variables ¶
var ErrBulkheadFull = &policyError{msg: "bulkhead is full"}
ErrBulkheadFull is returned when no execution slot is available.
var ErrCircuitOpen = &policyError{msg: "circuit breaker is open"}
ErrCircuitOpen is returned when a call is rejected because the circuit is open.
var ErrMaxRetriesExceeded = &policyError{msg: "max retries exceeded", wrapped: nil}
ErrMaxRetriesExceeded is returned when all retry attempts are exhausted.
var ErrTimeout = &policyError{msg: "execution timed out"}
ErrTimeout is returned when an execution exceeds its deadline.
Functions ¶
func Execute ¶
func Execute[T any](ctx context.Context, policy Policy, fn func(context.Context) (T, error)) (T, error)
Execute runs fn through policy, returning a typed result. policy can be a *CircuitBreaker, *Retry, *Bulkhead, *Timeout, or *Pipeline.
Example:
result, err := resilix.Execute(ctx, pipeline, func(ctx context.Context) (MyResult, error) {
return client.Call(ctx)
})
func WithFallback ¶
func WithFallback[T any]( ctx context.Context, policy Policy, fn func(context.Context) (T, error), fallback func(context.Context, error) (T, error), ) (T, error)
WithFallback runs fn through policy. If the policy returns an error, fallback is called with that error to produce a default value. This is useful for graceful degradation.
Types ¶
type AtomicCounter ¶
type AtomicCounter struct {
// contains filtered or unexported fields
}
AtomicCounter is the simplest possible failure counter: just a running total, reset on success. Use when you want "N consecutive failures" semantics rather than a rate.
func (*AtomicCounter) ConsecutiveFailures ¶
func (c *AtomicCounter) ConsecutiveFailures() int64
ConsecutiveFailures returns the current consecutive failure count.
func (*AtomicCounter) FailureRate ¶
func (c *AtomicCounter) FailureRate() float64
func (*AtomicCounter) Record ¶
func (c *AtomicCounter) Record(success bool)
func (*AtomicCounter) Reset ¶
func (c *AtomicCounter) Reset()
func (*AtomicCounter) Total ¶
func (c *AtomicCounter) Total() int64
type BackoffStrategy ¶
BackoffStrategy returns the wait duration before attempt number `attempt` (1-indexed).
func ConstantBackoff ¶
func ConstantBackoff(d time.Duration) BackoffStrategy
ConstantBackoff waits the same duration before every retry.
func ExponentialBackoff ¶
ExponentialBackoff implements base * multiplier^(attempt-1), capped at max. Pass max=0 to disable the cap.
func ExponentialBackoffWithJitter ¶
func ExponentialBackoffWithJitter(base time.Duration, multiplier float64, max time.Duration, jitterFraction float64) BackoffStrategy
ExponentialBackoffWithJitter adds ±jitterFraction random jitter to ExponentialBackoff to avoid thundering herd on retry storms. jitterFraction should be in (0, 1]; 0.2 means ±20%.
func LinearBackoff ¶
func LinearBackoff(initial, step time.Duration, max time.Duration) BackoffStrategy
LinearBackoff increases wait by `step` on each attempt.
type Bulkhead ¶
type Bulkhead struct {
// contains filtered or unexported fields
}
Bulkhead limits the number of concurrent executions to prevent a slow downstream from monopolising the caller's goroutine pool.
It is backed by a buffered channel used as a semaphore — zero allocations per call once the channel is created.
func NewBulkhead ¶
func NewBulkhead(cfg BulkheadConfig) *Bulkhead
NewBulkhead creates a Bulkhead with the given config.
func (*Bulkhead) ExecuteBulkhead ¶
ExecuteBulkhead runs fn if a slot is available; otherwise it either waits (if WaitTimeout > 0) or returns ErrBulkheadFull immediately.
type BulkheadConfig ¶
type BulkheadConfig struct {
// Name identifies this policy in metrics.
Name string
// MaxConcurrent is the maximum number of calls that may execute simultaneously.
// Default: 10.
MaxConcurrent int
// WaitTimeout is how long a caller will wait for a slot before receiving
// ErrBulkheadFull. Use 0 for fail-fast (never queue).
// Default: 0 (fail fast).
WaitTimeout time.Duration
// Observer receives rejected and success events.
Observer Observer
// Clock is used for timeout tracking. Override in tests.
Clock Clock
}
BulkheadConfig configures a Bulkhead.
type CBConfig ¶
type CBConfig struct {
// Name identifies this breaker in metrics and logs.
Name string
// FailureThreshold is the failure rate [0,1] at which the circuit trips.
// Default: 0.5 (50% failure rate trips the breaker).
FailureThreshold float64
// MinRequests is the minimum number of calls in the window before the
// failure rate is evaluated. Prevents tripping on a single early failure.
// Default: 5.
MinRequests int64
// OpenTimeout is how long the circuit stays open before moving to half-open.
// Default: 30s.
OpenTimeout time.Duration
// HalfOpenMax is how many probe calls are allowed in the half-open state.
// Default: 1.
HalfOpenMax int64
// Window is the failure tracking strategy. Defaults to CountWindow(10).
Window FailureWindow
// Observer receives state change and execution events.
Observer Observer
// Clock is used for time. Defaults to the real clock.
// Override in tests with testutil.NewFakeClock().
Clock Clock
}
CBConfig configures a CircuitBreaker.
type CircuitBreaker ¶
type CircuitBreaker struct {
// contains filtered or unexported fields
}
CircuitBreaker implements the classic three-state circuit breaker pattern.
Use Execute or ExecuteCB to run calls through it. CircuitBreaker is safe for concurrent use.
func NewCircuitBreaker ¶
func NewCircuitBreaker(cfg CBConfig) *CircuitBreaker
NewCircuitBreaker creates a new CircuitBreaker with the given config.
func (*CircuitBreaker) ExecuteCB ¶
ExecuteCB runs fn through the circuit breaker, returning ErrCircuitOpen if the circuit is open. Use the generic Execute[T] function for type-safe results.
func (*CircuitBreaker) Reset ¶
func (cb *CircuitBreaker) Reset()
Reset forces the circuit breaker back to the closed state and clears the window.
func (*CircuitBreaker) State ¶
func (cb *CircuitBreaker) State() State
State returns the current circuit state. Safe to call concurrently.
type CountWindow ¶
type CountWindow struct {
// contains filtered or unexported fields
}
CountWindow uses a simple rolling count of the last N calls. It is the lightest option and suitable when call rate is stable.
func NewCountWindow ¶
func NewCountWindow(size int) *CountWindow
NewCountWindow returns a window that tracks the last `size` calls.
func (*CountWindow) FailureRate ¶
func (w *CountWindow) FailureRate() float64
func (*CountWindow) Record ¶
func (w *CountWindow) Record(success bool)
func (*CountWindow) Reset ¶
func (w *CountWindow) Reset()
func (*CountWindow) Total ¶
func (w *CountWindow) Total() int64
type FailureWindow ¶
type FailureWindow interface {
// Record registers one result. success=true on a successful call.
Record(success bool)
// FailureRate returns a value in [0, 1].
FailureRate() float64
// Reset clears all accumulated data.
Reset()
// Total returns the total number of calls recorded.
Total() int64
}
FailureWindow tracks the recent failure rate used by the circuit breaker. Swap in a different implementation for sliding-window or count-based semantics.
type NoopObserver ¶
type NoopObserver struct{}
NoopObserver is a zero-allocation observer that discards all events. It is the default when no observer is configured.
func (NoopObserver) OnRejected ¶
func (NoopObserver) OnRejected(_ string)
func (NoopObserver) OnStateChange ¶
func (NoopObserver) OnStateChange(_ string, _, _ State)
type Observer ¶
type Observer interface {
// OnStateChange is called when a circuit breaker transitions between states.
OnStateChange(policy string, from, to State)
// OnSuccess is called after a successful execution.
OnSuccess(policy string, latency time.Duration)
// OnFailure is called after a failed execution (counted against the threshold).
OnFailure(policy string, err error, latency time.Duration)
// OnRejected is called when a call is rejected without being attempted.
// This happens when the circuit is open, the bulkhead is full, etc.
OnRejected(policy string)
// OnRetry is called before each retry attempt (attempt starts at 1).
OnRetry(policy string, attempt int, err error)
}
Observer receives events from any resilience primitive. Implement this to wire in OpenTelemetry, Prometheus, structured logs, etc.
type Pipeline ¶
type Pipeline struct {
// contains filtered or unexported fields
}
Pipeline chains multiple policies. Policies are applied in order: the first policy in the slice is the outermost wrapper.
Recommended ordering (outermost → innermost):
Timeout → Retry → CircuitBreaker → Bulkhead
This means: the timeout governs the entire operation including retries; the circuit breaker is checked before each attempt; the bulkhead gates each individual call.
func DefaultPipeline ¶
DefaultPipeline builds a sensible pipeline for a single downstream with sane defaults. Good for getting started quickly.
Timeout(5s) → Retry(3 attempts, exp backoff) → CircuitBreaker(50% rate, 30s)
func NewPipeline ¶
NewPipeline creates a pipeline from a mix of policy types. Accepted types: *CircuitBreaker, *Retry, *Bulkhead, *Timeout. Panics on unknown types.
type Policy ¶
type Policy interface {
// Name returns a human-readable identifier used in metrics and logs.
Name() string
}
Policy is the common interface for all resilience primitives. Policies are composable; a Pipeline implements Policy itself.
type Retry ¶
type Retry struct {
// contains filtered or unexported fields
}
Retry is a policy that re-executes a call on failure according to a configurable backoff and predicate.
func NewRetry ¶
func NewRetry(cfg RetryConfig) *Retry
NewRetry creates a Retry policy with the given config.
func (*Retry) ExecuteRetry ¶
ExecuteRetry runs fn with retry semantics. Returns the last error if all attempts are exhausted, wrapped in ErrMaxRetriesExceeded.
type RetryConfig ¶
type RetryConfig struct {
// Name identifies this policy in metrics.
Name string
// MaxAttempts is the total number of attempts (including the first).
// A value of 3 means 1 initial call + 2 retries.
// Default: 3.
MaxAttempts int
// Backoff controls how long to wait between attempts.
// Default: ExponentialBackoff(100ms, 2.0, 10s).
Backoff BackoffStrategy
// RetryIf is called after each failure to decide whether to retry.
// Default: RetryAll (retry any error).
RetryIf RetryPredicate
// Observer receives retry and failure events.
Observer Observer
// Clock is used for sleeping. Override in tests.
Clock Clock
}
RetryConfig configures a Retry policy.
type RetryPredicate ¶
RetryPredicate decides whether a given error on a given attempt is retryable.
func RetryIf ¶
func RetryIf(preds ...RetryPredicate) RetryPredicate
RetryIf composes multiple predicates with OR semantics.
func RetryOn ¶
func RetryOn(target error) RetryPredicate
RetryOn returns a predicate that retries only when the error matches target (using errors.Is semantics).
type SlidingWindow ¶
type SlidingWindow struct {
// contains filtered or unexported fields
}
SlidingWindow tracks calls within a rolling time range divided into buckets. It is more accurate than CountWindow for high-variance traffic.
func NewSlidingWindow ¶
func NewSlidingWindow(window time.Duration, numBuckets int) *SlidingWindow
NewSlidingWindow creates a window of the given duration split into `buckets` slots. Example: NewSlidingWindow(60*time.Second, 6) → 6×10s buckets, 60s total.
func (*SlidingWindow) FailureRate ¶
func (w *SlidingWindow) FailureRate() float64
func (*SlidingWindow) Record ¶
func (w *SlidingWindow) Record(success bool)
func (*SlidingWindow) Reset ¶
func (w *SlidingWindow) Reset()
func (*SlidingWindow) SetClock ¶
func (w *SlidingWindow) SetClock(clock Clock)
SetClock allows injecting a fake clock in tests to control time for sliding window boundaries.
func (*SlidingWindow) Total ¶
func (w *SlidingWindow) Total() int64
type Timeout ¶
type Timeout struct {
// contains filtered or unexported fields
}
Timeout wraps a function with a deadline. If fn does not complete within the configured duration, the context passed to fn is cancelled and ErrTimeout is returned to the caller.
Importantly, Timeout waits for fn to return even after the deadline passes. This prevents goroutine leaks when fn does not respect context cancellation, though it will add latency in that case. Well-behaved fns should honour ctx.
func (*Timeout) ExecuteTimeout ¶
ExecuteTimeout runs fn with a context that expires after the configured duration.
type TimeoutConfig ¶
type TimeoutConfig struct {
// Name identifies this policy in metrics.
Name string
// Duration is the maximum time a call may take.
// If the call exceeds this, it receives a cancelled context and
// the caller receives ErrTimeout.
Duration time.Duration
// Observer receives success and failure events.
Observer Observer
// Clock is used for tracking. Override in tests.
Clock Clock
}
TimeoutConfig configures a Timeout policy.