Patterns: concurrent Go · 3 MIN READ
Circuit breaker: stop calls while a dependency recovers
Separate retry policy from failure isolation and a controlled recovery probe.
Separate retry policy from failure isolation and a controlled recovery probe. A circuit breaker rejects calls after repeated failures, waits for a recovery interval, then permits a probe. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.
Concurrent Go pattern · A MAP OF THE IDEA
- Closed state observes counted failures
- Open state rejects calls for an interval
- Half-open probe succeeds or reopens the circuit
Useful when
- A failing dependency consumes caller capacity
- Recovery probes should be controlled
Watch out
- Concurrent state and probe admission need protection
- Failure classification and metrics affect usefulness
Implementation & operation cost: High
Fit depends on the task
Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.
The design decision
A circuit breaker rejects calls after repeated failures, waits for a recovery interval, then permits a probe. Closed, open and half-open describe the lifecycle. Unlike a retry, it reduces attempted work during a continuing outage; it does not prove the dependency is healthy.
A complete Go example
Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.
package main
import (
"errors"
"fmt"
"time"
)
var ErrOpen = errors.New("circuit open")
type Breaker struct {
failures int
until time.Time
}
func (b *Breaker) Call(now time.Time, op func() error) error {
if now.Before(b.until) {
return ErrOpen
}
err := op()
if err == nil {
b.failures = 0
b.until = time.Time{}
return nil
}
b.failures++
if b.failures >= 2 {
b.until = now.Add(time.Second)
}
return err
}
func main() {
b := &Breaker{}
now := time.Unix(0, 0)
calls := 0
fail := func() error { calls++; return errors.New("offline") }
_ = b.Call(now, fail)
_ = b.Call(now, fail)
fmt.Println(b.Call(now, fail), calls)
fmt.Println(b.Call(now.Add(time.Second), func() error { return nil }))
}
// Output:
// circuit open 2
// <nil>
After two failures, a third call is rejected without invoking fail. Advancing the injected clock permits a successful recovery probe and resets the count. This is a single-owner teaching breaker; sequential execution permits one probe at a time implicitly.
Tradeoffs and the simpler alternative
A concurrent implementation must lock state and admit only a bounded number of half-open probes. Classify which failures count, expose metrics, and avoid treating authorization errors as an outage. Prefer a proven library when failure windows and concurrent recovery become complex.
TAKE THIS WITH YOU
Rejects calls during an outage and probes for recovery. A concurrent implementation must lock state and admit only a bounded number of half-open probes. Classify which failures count, expose metrics, and avoid treating authorization errors as an outage. Prefer a proven library when failure windows and concurrent recovery become complex.
Check your understanding
Which statement best describes Circuit breaker: stop calls while a dependency recovers?
Go to the source
Progress stays in this browser. No account needed.