Circuit Breaker with Three States
Protect a flaky dependency with closed, open, and half-open states, a failure threshold, and a cooldown before probing again.
Code
package main
import (
"errors"
"fmt"
"sync"
"time"
)
type State int
const (
StateClosed State = iota
StateOpen
StateHalfOpen
)
func (s State) String() string {
switch s {
case StateClosed:
return "closed"
case StateOpen:
return "open"
case StateHalfOpen:
return "half-open"
default:
return "unknown"
}
}
var ErrOpen = errors.New("circuit breaker is open")
type Breaker struct {
mu sync.Mutex
threshold int
cooldown time.Duration
now func() time.Time
state State
failures int
openedAt time.Time
}
func NewBreaker(threshold int, cooldown time.Duration) (*Breaker, error) {
if threshold < 1 {
return nil, fmt.Errorf("threshold must be positive")
}
if cooldown <= 0 {
return nil, fmt.Errorf("cooldown must be positive")
}
return &Breaker{threshold: threshold, cooldown: cooldown, now: time.Now}, nil
}
func (b *Breaker) Allow() error {
b.mu.Lock()
defer b.mu.Unlock()
if b.state == StateOpen {
if b.now().Sub(b.openedAt) < b.cooldown {
return ErrOpen
}
b.state = StateHalfOpen
}
return nil
}
func (b *Breaker) record(err error) {
b.mu.Lock()
defer b.mu.Unlock()
if err == nil {
b.state = StateClosed
b.failures = 0
return
}
b.failures++
if b.state == StateHalfOpen || b.failures >= b.threshold {
b.state = StateOpen
b.openedAt = b.now()
}
}
func (b *Breaker) Execute(operation func() error) error {
if err := b.Allow(); err != nil {
return err
}
err := operation()
b.record(err)
return err
}
func (b *Breaker) CurrentState() State {
b.mu.Lock()
defer b.mu.Unlock()
return b.state
}
type fakeClock struct {
current time.Time
}
func (c *fakeClock) Now() time.Time { return c.current }
func (c *fakeClock) Advance(step time.Duration) { c.current = c.current.Add(step) }
func main() {
clock := &fakeClock{current: time.Date(2026, 10, 21, 12, 0, 0, 0, time.UTC)}
breaker, err := NewBreaker(2, 100*time.Millisecond)
if err != nil {
fmt.Println(err)
return
}
breaker.now = clock.Now
failing := func() error { return errors.New("upstream unavailable") }
for call := 1; call <= 3; call++ {
fmt.Printf(
"call %d: err=%v state=%s\n",
call,
breaker.Execute(failing),
breaker.CurrentState(),
)
}
clock.Advance(150 * time.Millisecond)
fmt.Println("after cooldown state:", breaker.CurrentState())
probeErr := breaker.Execute(func() error { return nil })
fmt.Printf("probe err=%v state=%s\n", probeErr, breaker.CurrentState())
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.