circuit

package
v1.27.0 Latest Latest
Warning

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

Go to latest
Published: Sep 11, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

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

View Source
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

func New(cfg Config) *Breaker

New constructs a Breaker from a Config. Zero/negative numeric fields fall back to safe defaults documented on Config.

func (*Breaker) Do

func (b *Breaker) Do(ctx context.Context, fn func(context.Context) error) error

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.

func (*Breaker) Opens

func (b *Breaker) Opens() uint64

Opens returns the cumulative number of times the breaker has tripped open since construction (closed→open and half-open→open both count). It is the counter a log line or a metric gauge reads alongside Config.OnStateChange.

func (*Breaker) State

func (b *Breaker) State() State

State returns the current breaker state. Useful for instrumentation and tests; production code typically does not need to inspect this — Do enforces the state machine internally.

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
)

func (State) String

func (s State) String() string

String returns a human-readable state label suitable for logging.

Jump to

Keyboard shortcuts

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