github.com/msales/pkg/v3@v3.24.0/breaker/breaker.go (about) 1 package breaker 2 3 import ( 4 "errors" 5 "sync" 6 "time" 7 ) 8 9 // State represents a Breakers state. 10 type State int8 11 12 // State constants for the Breaker. 13 const ( 14 StateClosed State = iota 15 StateHalfOpen 16 StateOpen 17 ) 18 19 var ( 20 // ErrOpenState is the error returned when the Breaker is open. 21 ErrOpenState = errors.New("breaker: circuit breaker is open") 22 23 // ErrTooManyRequests is the error returned when too many test requests are 24 // made when the Breaker is half-open. 25 ErrTooManyRequests = errors.New("breaker: too many requests") 26 ) 27 28 // Counter holds the number number of requests, successes and failures of a breaker. 29 // 30 // Counter is reset from time to time, and totals should not be used as a full totals. 31 type Counter struct { 32 // Requests is the total number of requests made. 33 Requests uint64 34 // Successes is the total number of successes returned. 35 Successes uint64 36 // Failures is the total number of failures returned. 37 Failures uint64 38 // ConsecutiveSuccesses is the number of consecutive successes returned. 39 ConsecutiveSuccesses uint64 40 // ConsecutiveFailures is the number of consecutive failures returned. 41 ConsecutiveFailures uint64 42 } 43 44 func (c *Counter) reset() { 45 c.Requests = 0 46 c.Successes = 0 47 c.Failures = 0 48 c.ConsecutiveSuccesses = 0 49 c.ConsecutiveFailures = 0 50 } 51 52 func (c *Counter) request() { 53 c.Requests++ 54 } 55 56 func (c *Counter) success() { 57 c.Successes++ 58 c.ConsecutiveSuccesses++ 59 c.ConsecutiveFailures = 0 60 } 61 62 func (c *Counter) failure() { 63 c.Failures++ 64 c.ConsecutiveFailures++ 65 c.ConsecutiveSuccesses = 0 66 } 67 68 // Fuse represents a Breaker fuse used to trip the breaker. 69 type Fuse interface { 70 // Trip determines if the Breaker should be tripped. 71 Trip(Counter) bool 72 } 73 74 // FuseFunc is an adapter allowing to use a function as a Fuse. 75 type FuseFunc func(Counter) bool 76 77 // Trip determines if the Breaker should be tripped. 78 func (f FuseFunc) Trip(c Counter) bool { 79 return f(c) 80 } 81 82 // ThresholdFuse trips the Breaker when the total number of failures exceeds the given count. 83 func ThresholdFuse(count uint64) FuseFunc { 84 return FuseFunc(func(c Counter) bool { 85 return c.Failures > count 86 }) 87 } 88 89 // ConsecutiveFuse trips the Breaker when the consecutive number of failures exceeds the given count. 90 func ConsecutiveFuse(count uint64) FuseFunc { 91 return FuseFunc(func(c Counter) bool { 92 return c.ConsecutiveFailures > count 93 }) 94 } 95 96 // RateFuse trips the Breaker when the percentage of failures exceeds the given rate. 97 // 98 // rate should be between 0 and 100. 99 func RateFuse(rate uint64) FuseFunc { 100 if rate >= 100 { 101 panic(errors.New("breaker: rate should be less than 100 percent")) 102 } 103 104 return FuseFunc(func(c Counter) bool { 105 return c.Failures/c.Requests*100 > rate 106 }) 107 } 108 109 // OptFunc represents a configuration function for Breaker. 110 type OptFunc func(b *Breaker) 111 112 // WithSleep sets the time the Breaker stays open for. 113 func WithSleep(d time.Duration) OptFunc { 114 return OptFunc(func(b *Breaker) { 115 b.sleep = d 116 }) 117 } 118 119 // WithTestRequests sets the number of test requests allowed when the Breaker is half-open. 120 func WithTestRequests(c uint64) OptFunc { 121 return OptFunc(func(b *Breaker) { 122 b.testRequests = c 123 }) 124 } 125 126 // Breaker is a circuit breaker. 127 type Breaker struct { 128 fuse Fuse 129 sleep time.Duration 130 testRequests uint64 131 132 mu sync.Mutex 133 state State 134 counter Counter 135 openUntil time.Time 136 } 137 138 // NewBreaker creates a new Breaker. 139 func NewBreaker(f Fuse, opts ...OptFunc) *Breaker { 140 b := &Breaker{ 141 fuse: f, 142 sleep: 10 * time.Second, 143 testRequests: 1, 144 } 145 146 for _, opt := range opts { 147 opt(b) 148 } 149 150 return b 151 } 152 153 // Run runs the given request if the Breaker allows it. 154 // 155 // Run returns an error immediately if the Breaker rejects the request, 156 // otherwise it returns the result of the request. 157 func (b *Breaker) Run(req func() error) error { 158 if err := b.canRun(); err != nil { 159 return err 160 } 161 162 defer func() { 163 if r := recover(); r != nil { 164 b.handleResult(false) 165 panic(r) 166 } 167 }() 168 169 err := req() 170 171 b.handleResult(err == nil) 172 173 return err 174 } 175 176 func (b *Breaker) canRun() error { 177 b.mu.Lock() 178 defer b.mu.Unlock() 179 180 state := b.getState() 181 182 if state == StateOpen { 183 return ErrOpenState 184 } else if state == StateHalfOpen && b.counter.Requests >= b.testRequests { 185 return ErrTooManyRequests 186 } 187 188 b.counter.request() 189 190 return nil 191 } 192 193 func (b *Breaker) handleResult(success bool) { 194 b.mu.Lock() 195 defer b.mu.Unlock() 196 197 state := b.getState() 198 199 if !success { 200 b.handleFailure(state) 201 return 202 } 203 204 b.handleSuccess(state) 205 } 206 207 func (b *Breaker) handleSuccess(state State) { 208 switch state { 209 case StateClosed: 210 b.counter.success() 211 212 case StateHalfOpen: 213 b.counter.success() 214 if state == StateHalfOpen && b.counter.ConsecutiveSuccesses >= b.testRequests { 215 b.setState(StateClosed) 216 } 217 } 218 } 219 220 func (b *Breaker) handleFailure(state State) { 221 switch state { 222 case StateClosed: 223 b.counter.failure() 224 if b.fuse.Trip(b.counter) { 225 b.setState(StateOpen) 226 } 227 228 case StateHalfOpen: 229 b.setState(StateOpen) 230 } 231 } 232 233 // State returns the state of the Breaker. 234 func (b *Breaker) State() State { 235 b.mu.Lock() 236 defer b.mu.Unlock() 237 238 return b.getState() 239 } 240 241 func (b *Breaker) getState() State { 242 now := time.Now() 243 244 switch b.state { 245 case StateClosed: 246 if !b.openUntil.IsZero() && b.openUntil.Before(now) { 247 b.openUntil = time.Time{} 248 } 249 250 case StateOpen: 251 if b.openUntil.Before(now) { 252 b.setState(StateHalfOpen) 253 } 254 } 255 return b.state 256 } 257 258 func (b *Breaker) setState(state State) { 259 b.state = state 260 261 if b.state == StateOpen { 262 b.openUntil = time.Now().Add(b.sleep) 263 } 264 265 b.counter.reset() 266 }