frostfs-sdk-go/pool/tree/circuitbreaker.go
Alex Vanin 06ef257ddc
Some checks failed
DCO / DCO (pull_request) Successful in 29s
Code generation / Generate proto (pull_request) Successful in 30s
Tests and linters / Lint (pull_request) Failing after 39s
Tests and linters / Tests (pull_request) Successful in 44s
[#339] pool/tree: Do not lock mutex on circuit break function
Circuit break function may take some time to execute so it should
not be executed when lock is enabled.

Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2025-03-04 14:41:01 +03:00

87 lines
1.6 KiB
Go

package tree
import (
"errors"
"sync"
"time"
)
type (
circuitBreaker struct {
breakDuration time.Duration
threshold int
mu sync.RWMutex
state map[uint64]state
}
state struct {
counter int
breakTimestamp time.Time
}
)
var ErrCBClosed = errors.New("circuit breaker is closed")
func newCircuitBreaker(breakDuration time.Duration, threshold int) *circuitBreaker {
return &circuitBreaker{
breakDuration: breakDuration,
threshold: threshold,
state: make(map[uint64]state),
}
}
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()
s := cb.state[id]
s.counter++
if s.counter >= cb.threshold {
s.counter = cb.threshold
if s.breakTimestamp.Before(doTime) {
s.breakTimestamp = doTime
}
}
cb.state[id] = s
}
func (c *circuitBreaker) Do(id uint64, f func() error) error {
breakTime, ok := c.breakTime(id)
if ok && time.Since(breakTime) < c.breakDuration {
return ErrCBClosed
}
// 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()
err := f()
if err == nil {
c.openBreak(id)
} else {
c.incError(id, doTime)
}
return err
}