Documentation
¶
Overview ¶
Package circuit provides a small circuit-breaker primitive for wrapping calls to external dependencies (mail, storage, plugin bridges, third-party APIs) in a deterministic failure-isolation pattern. The design follows the standard closed / open / half-open state machine: the breaker passes calls through while closed, fails fast while open, and admits a small number of probe calls while half-open to test whether the dependency has recovered.
pkg/app automatically wires circuit breakers for mail.Sender.Send and remote storage.Store operations (Put, Get, Delete, Exists, List, Copy, SignedURL). Operators only need to set circuit_breaker.enabled=false (or tune thresholds via mail_circuit_breaker.* / storage.circuit_breaker.* in nucleus.yml) to opt out or adjust behavior. Additional external calls — plugin bridges, third-party APIs — can be wrapped manually using New.
The package is intentionally minimal — no event bus, no metrics surface, no per-call timeout. The one observability affordance is Config.OnStateChange (a per-transition callback) plus the Opens() counter; callers compose richer instrumentation with whatever they already use (pkg/observe for logging, the /metrics MeterProvider for counters, etc.).
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrOpen = errors.New("circuit breaker is open")
ErrOpen is returned by Do when the breaker is open (and the cooldown has not yet elapsed) or when the half-open probe budget is exhausted.
Functions ¶
This section is empty.
Types ¶
type Breaker ¶
type Breaker struct {
// contains filtered or unexported fields
}
Breaker is a circuit breaker. The zero value is not usable — use New.
func New ¶
New constructs a Breaker from a Config. Zero/negative numeric fields fall back to safe defaults documented on Config.
func (*Breaker) Do ¶
Do executes fn while respecting the breaker state.
When the breaker is closed, fn runs and its error (or success) drives the state machine. When the breaker is open and the cooldown has not elapsed, ErrOpen is returned without invoking fn. When the cooldown has elapsed, the breaker transitions to half-open and admits up to HalfOpenMaxConcurrent calls; a success resets to closed, any failure (or exceeded budget) returns to open with a fresh cooldown.
The context is forwarded to fn and is not inspected by the breaker itself — context cancellation is the caller's responsibility within fn.
type Config ¶
type Config struct {
// FailureThreshold is the number of consecutive failures in the
// closed state required to trip the breaker open. Must be >= 1;
// non-positive values default to 1.
FailureThreshold int
// Cooldown is the duration the breaker stays open before admitting
// half-open probes. Non-positive values default to 30 seconds.
Cooldown time.Duration
// HalfOpenMaxConcurrent is the maximum number of in-flight probe
// calls allowed in the half-open state. Non-positive values default
// to 1 (single-shot probe).
HalfOpenMaxConcurrent int
// Now is a time source override used by tests. Production callers
// should leave it nil; the breaker uses time.Now() by default.
Now func() time.Time
// OnStateChange, when non-nil, is invoked after every state
// transition (closed→open, open→half-open, half-open→open,
// half-open→closed) with the states involved. It is called outside
// the breaker's internal lock — reading State() or Opens() from the
// callback is safe — but synchronously on the calling goroutine, so
// keep it cheap (a log line, a counter increment). Before this hook
// the auto-wired mail and storage breakers opened and closed in
// complete silence (NF-9): no log, no metric, no event.
OnStateChange func(from, to State)
}
Config configures a Breaker. Zero values fall back to conservative defaults: a single failure trips the breaker, cooldown is 30s, and the half-open probe budget is 1.
type State ¶
type State int
State enumerates the circuit-breaker states.
const ( // StateClosed is the normal pass-through state. StateClosed State = iota // StateOpen short-circuits every call with ErrOpen until the // cooldown elapses. StateOpen // StateHalfOpen lets a bounded number of probe calls through. // A success transitions back to closed; any failure re-opens. StateHalfOpen )