Files
Agent-System/internal/jevdecide/circuit.go
T
weichen 45f21e84c7 feat(jevdecide): product shadow client with offline eval gates
Add typed jevdecide HTTP client (circuit, metrics) and coordinator
DecideAssist wiring behind JEV_DECIDE_MODE (default off). Offline eval
and synthetic soak evidence gate realtime/profile before assist rollout.
2026-09-23 17:40:40 +00:00

91 lines
1.8 KiB
Go

package jevdecide
import (
"sync"
"time"
)
const (
defaultFailThreshold = 5
defaultOpenFor = 60 * time.Second
)
// CircuitBreaker opens after consecutive failures and half-opens after a cool-down.
type CircuitBreaker struct {
mu sync.Mutex
failures int
failThreshold int
openUntil time.Time
openFor time.Duration
now func() time.Time
}
// NewCircuitBreaker builds a breaker with the given thresholds.
func NewCircuitBreaker(failThreshold int, openFor time.Duration) *CircuitBreaker {
if failThreshold <= 0 {
failThreshold = defaultFailThreshold
}
if openFor <= 0 {
openFor = defaultOpenFor
}
return &CircuitBreaker{
failThreshold: failThreshold,
openFor: openFor,
now: time.Now,
}
}
// Allow reports whether a call may proceed.
func (c *CircuitBreaker) Allow() bool {
if c == nil {
return true
}
c.mu.Lock()
defer c.mu.Unlock()
if c.openUntil.IsZero() {
return true
}
if c.now().After(c.openUntil) {
// half-open: allow one probe
c.openUntil = time.Time{}
c.failures = c.failThreshold - 1
return true
}
return false
}
// RecordSuccess resets the breaker.
func (c *CircuitBreaker) RecordSuccess() {
if c == nil {
return
}
c.mu.Lock()
defer c.mu.Unlock()
c.failures = 0
c.openUntil = time.Time{}
}
// RecordFailure increments consecutive failures and may open the circuit.
func (c *CircuitBreaker) RecordFailure() {
if c == nil {
return
}
c.mu.Lock()
defer c.mu.Unlock()
c.failures++
if c.failures >= c.failThreshold {
c.openUntil = c.now().Add(c.openFor)
c.failures = 0
}
}
// Open reports whether the circuit is currently open (for tests/metrics).
func (c *CircuitBreaker) Open() bool {
if c == nil {
return false
}
c.mu.Lock()
defer c.mu.Unlock()
return !c.openUntil.IsZero() && c.now().Before(c.openUntil)
}