2025-03-03 17:06:04 +03:00
|
|
|
package tree
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
circuitBreaker struct {
|
|
|
|
breakDuration time.Duration
|
|
|
|
threshold int
|
|
|
|
|
2025-03-04 14:08:08 +03:00
|
|
|
mu sync.RWMutex
|
2025-03-04 12:17:20 +03:00
|
|
|
state map[uint64]state
|
2025-03-03 17:06:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
state struct {
|
|
|
|
counter int
|
|
|
|
breakTimestamp time.Time
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
var ErrCBClosed = errors.New("circuit breaker is closed")
|
|
|
|
|
2025-03-04 12:20:24 +03:00
|
|
|
func newCircuitBreaker(breakDuration time.Duration, threshold int) *circuitBreaker {
|
2025-03-03 17:06:04 +03:00
|
|
|
return &circuitBreaker{
|
|
|
|
breakDuration: breakDuration,
|
|
|
|
threshold: threshold,
|
2025-03-04 12:17:20 +03:00
|
|
|
state: make(map[uint64]state),
|
2025-03-03 17:06:04 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-04 14:08:08 +03:00
|
|
|
func (cb *circuitBreaker) breakTime(id uint64) (time.Time, bool) {
|
|
|
|
cb.mu.RLock()
|
|
|
|
defer cb.mu.RUnlock()
|
|
|
|
|
|
|
|
if s, ok := cb.state[id]; ok {
|
|
|
|
return s.breakTimestamp, true
|
|
|
|
}
|
|
|
|
|
|
|
|
return time.Time{}, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cb *circuitBreaker) openBreak(id uint64) {
|
|
|
|
cb.mu.Lock()
|
|
|
|
defer cb.mu.Unlock()
|
|
|
|
delete(cb.state, id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cb *circuitBreaker) incError(id uint64, doTime time.Time) {
|
|
|
|
cb.mu.Lock()
|
|
|
|
defer cb.mu.Unlock()
|
2025-03-03 17:06:04 +03:00
|
|
|
|
2025-03-04 14:08:08 +03:00
|
|
|
s := cb.state[id]
|
|
|
|
|
|
|
|
s.counter++
|
|
|
|
if s.counter >= cb.threshold {
|
|
|
|
s.counter = cb.threshold
|
|
|
|
if s.breakTimestamp.Before(doTime) {
|
|
|
|
s.breakTimestamp = doTime
|
|
|
|
}
|
2025-03-03 17:06:04 +03:00
|
|
|
}
|
|
|
|
|
2025-03-04 14:08:08 +03:00
|
|
|
cb.state[id] = s
|
|
|
|
}
|
2025-03-03 17:06:04 +03:00
|
|
|
|
2025-03-04 14:08:08 +03:00
|
|
|
func (c *circuitBreaker) Do(id uint64, f func() error) error {
|
|
|
|
breakTime, ok := c.breakTime(id)
|
|
|
|
if ok && time.Since(breakTime) < c.breakDuration {
|
2025-03-03 17:06:04 +03:00
|
|
|
return ErrCBClosed
|
|
|
|
}
|
|
|
|
|
2025-03-04 14:08:08 +03:00
|
|
|
// Use this timestamp to update circuit breaker in case of an error.
|
|
|
|
// f() may be blocked for unpredictable duration, so concurrent calls
|
|
|
|
// may update time in 'incError' endlessly and circuit will never be open
|
|
|
|
doTime := time.Now()
|
|
|
|
|
2025-03-04 12:17:20 +03:00
|
|
|
err := f()
|
2025-03-03 17:06:04 +03:00
|
|
|
if err == nil {
|
2025-03-04 14:08:08 +03:00
|
|
|
c.openBreak(id)
|
|
|
|
} else {
|
|
|
|
c.incError(id, doTime)
|
2025-03-03 17:06:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|