package monitoring import ( "sync" "time" ) type breakerState int const ( breakerClosed breakerState = iota breakerOpen breakerHalfOpen ) type circuitBreaker struct { mu sync.Mutex state breakerState failureCount int openedAt time.Time lastAttempt time.Time retryInterval time.Duration maxDelay time.Duration openThreshold int halfOpenWindow time.Duration stateSince time.Time lastTransition time.Time } func newCircuitBreaker(openThreshold int, retryInterval, maxDelay, halfOpenWindow time.Duration) *circuitBreaker { if openThreshold <= 0 { openThreshold = 3 } if retryInterval <= 0 { retryInterval = 5 * time.Second } if maxDelay <= 0 { maxDelay = 5 * time.Minute } if halfOpenWindow <= 0 { halfOpenWindow = 30 * time.Second } now := time.Now() return &circuitBreaker{ state: breakerClosed, retryInterval: retryInterval, maxDelay: maxDelay, openThreshold: openThreshold, halfOpenWindow: halfOpenWindow, stateSince: now, lastTransition: now, } } func (b *circuitBreaker) allow(now time.Time) bool { b.mu.Lock() defer b.mu.Unlock() switch b.state { case breakerClosed: return true case breakerOpen: if now.Sub(b.openedAt) >= b.retryInterval { b.state = breakerHalfOpen b.lastAttempt = now b.stateSince = now b.lastTransition = now return true } return false case breakerHalfOpen: if now.Sub(b.lastAttempt) >= b.halfOpenWindow { b.lastAttempt = now return true } return false default: return true } } func (b *circuitBreaker) recordSuccess() { b.mu.Lock() defer b.mu.Unlock() if b.state != breakerClosed { now := time.Now() b.state = breakerClosed b.failureCount = 0 b.stateSince = now b.lastTransition = now } } func (b *circuitBreaker) recordFailure(now time.Time) { b.mu.Lock() defer b.mu.Unlock() b.failureCount++ b.lastAttempt = now switch b.state { case breakerHalfOpen: b.trip(now) case breakerClosed: if b.failureCount >= b.openThreshold { b.trip(now) } } } func (b *circuitBreaker) trip(now time.Time) { b.state = breakerOpen delay := b.retryInterval << uint(b.failureCount) if delay > b.maxDelay { delay = b.maxDelay } b.retryInterval = delay b.openedAt = now b.stateSince = now b.lastTransition = now } // BreakerSnapshot represents the current state of a circuit breaker. type BreakerSnapshot struct { Instance string `json:"instance"` Type string `json:"type"` State string `json:"state"` Failures int `json:"failures"` RetryAt time.Time `json:"retryAt,omitempty"` } // State returns a snapshot of the circuit breaker state for API exposure. func (b *circuitBreaker) State() (state string, failures int, retryAt time.Time) { state, failures, retryAt, _, _ = b.stateDetails() return } func (b *circuitBreaker) stateDetails() (state string, failures int, retryAt time.Time, since time.Time, lastTransition time.Time) { b.mu.Lock() defer b.mu.Unlock() switch b.state { case breakerClosed: state = "closed" case breakerOpen: state = "open" retryAt = b.openedAt.Add(b.retryInterval) case breakerHalfOpen: state = "half_open" retryAt = b.lastAttempt.Add(b.halfOpenWindow) default: state = "unknown" } return state, b.failureCount, retryAt, b.stateSince, b.lastTransition }