Documentation
¶
Overview ¶
Package breakers provides named, dynamically-controllable on/off switches for individual functional surfaces of the rendezvous process. Operators flip a switch by writing a JSON file (<store-dir>/breakers.json); a file watcher reloads the state every 2 s and call sites consult the breaker before serving the affected request type.
Three states per breaker:
closed — normal operation (default). Allow.
half_open — log a warning, but allow. Use for canary testing
or to observe call frequency without disrupting
traffic.
open — deny. The serving site returns an appropriate
error to the caller (typically a 503-equivalent
with the operator-supplied reason).
Names are flat strings, conventionally dotted paths reflecting the component and action being gated (e.g. "registry.register", "beacon.punch", "snapshot.write"). The Manager is permissive: it accepts any name on the read path; a missing breaker is treated as "closed" (allow). Unknown names in the JSON file are kept but silently unused — write-time tolerance lets operators stage a name before its call site is wired.
Index ¶
- func Watch(m *Manager, path string, interval time.Duration, stop <-chan struct{})
- type Breaker
- type Manager
- func (m *Manager) Allow(name string) (bool, State)
- func (m *Manager) Delete(name string)
- func (m *Manager) Reason(name string) string
- func (m *Manager) Replace(breakers map[string]*Breaker)
- func (m *Manager) Set(name string, state State, reason string)
- func (m *Manager) SetClock(fn func() time.Time)
- func (m *Manager) Size() int
- func (m *Manager) Snapshot() []Breaker
- func (m *Manager) StatsSnapshot() []Stats
- type State
- type Stats
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Watch ¶
Watch polls path for mtime+size changes every interval and atomically reloads breakers into the Manager when the file changes.
Operational contract — pinned by tests; identical in spirit to the rate-limit whitelist watcher:
- Never blocks startup, the registry, the beacon, or any request path. Runs in its own goroutine; the only state it mutates is the Manager, via Replace.
- Fail-open: missing file is fine (no breakers installed; every name returns Allow=closed via Manager's default-allow). Parse, stat, or apply errors are logged at WARN and leave whatever map is currently installed in place.
- Idempotent: same file content (unchanged mtime + size) is a no-op.
- File removal reverts the Manager to an empty map on the next tick (logged INFO).
File format — JSON object, name → {state, reason?}:
{
"registry.register": {"state": "closed"},
"beacon.punch": {"state": "open", "reason": "maintenance"},
"dashboard.public_stats": {"state": "half_open","reason": "soaking pre-lockdown"}
}
state must be one of "closed", "half_open", "open" (case-insensitive, with the same aliases ParseState accepts). reason is operator-facing free text surfaced in error messages when a breaker is open.
Types ¶
type Breaker ¶
type Breaker struct {
Name string `json:"name"`
State State `json:"-"` // serialised via custom marshalling
StateString string `json:"state"`
Reason string `json:"reason,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
Breaker is a snapshot of one named switch's state.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager holds the live breaker map. Safe for concurrent use.
func New ¶
func New() *Manager
New returns an empty Manager. No breakers are pre-registered; Allow on any unknown name returns (true, Closed) — the default- allow semantic ensures a missing JSON file or a misspelled name can never cut off service.
func (*Manager) Allow ¶
Allow consults the named breaker and returns:
(true, Closed) — normal operation (true, HalfOpen) — caller should log a warning, then proceed (false, Open) — caller must NOT proceed; return an error
An unknown name is treated as Closed (default-allow) so a request path is never blocked by a typo. The state is returned alongside the bool so the caller can log appropriately without a second lookup.
func (*Manager) Delete ¶ added in v0.2.5
Delete removes a single named breaker. No-op when the name is not registered. Counters are preserved so post-incident review still shows traffic that flowed through the path. UpdatedAt of the file watcher will revert any change on the next reload, so callers that want durability should persist their intent to breakers.json too.
func (*Manager) Reason ¶
Reason returns the operator-supplied reason for the named breaker's current state, or "" if no breaker is registered. Use in error messages surfaced to callers when Allow returns false.
func (*Manager) Replace ¶
Replace atomically swaps the full breaker map. Used by the file watcher to apply a fresh JSON snapshot in one shot.
func (*Manager) Set ¶
Set installs or replaces one breaker. Audit-logged via the Manager's updated-at timestamp; callers who want a separate event sink should wrap this method.
func (*Manager) Snapshot ¶
Snapshot returns a copy of every registered breaker. Safe to mutate. Order is insertion-stable only within a single call; callers needing a sorted view should sort the slice themselves.
func (*Manager) StatsSnapshot ¶ added in v0.2.5
StatsSnapshot returns one Stats entry per known name — union of registered breakers and seen-counters. Sorted by name for stable output. A name with counters but no registered breaker reports state="closed" since that's its effective behaviour (the default).
type State ¶
type State int
State enumerates the three configurable states a breaker can be in.
const ( // Closed means normal operation: Allow returns (true, Closed). Closed State = iota // HalfOpen means warn-but-allow: Allow returns (true, HalfOpen). // Call sites should log a warning so operators can observe. HalfOpen // Open means deny: Allow returns (false, Open). Call sites should // surface the breaker's reason in any error returned to the caller. Open )
func ParseState ¶
ParseState parses a wire-format state string. Accepts the canonical "closed" / "half_open" / "open" plus a few common aliases ("half-open", "halfopen") so the JSON file is forgiving. Unknown values return an error so a typo doesn't silently become Closed.
type Stats ¶ added in v0.2.5
type Stats struct {
Name string `json:"name"`
State string `json:"state"`
Reason string `json:"reason,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
AllowedTotal int64 `json:"allowed_total"`
DeniedTotal int64 `json:"denied_total"`
LastDeniedAt *time.Time `json:"last_denied_at,omitempty"`
}
Stats is the per-breaker live snapshot returned via /api/breakers. Allowed + Denied are monotonic since process start (so a Prom/Grafana scraper can rate() them). LastDeniedAt is nil when Denied == 0.