github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/f/breaker.go (about) 1 package f 2 3 // Implements the Circuit Breaker pattern. 4 // See https://msdn.microsoft.com/en-us/library/dn589784.aspx. 5 6 import ( 7 "errors" 8 "fmt" 9 "sync" 10 "time" 11 ) 12 13 // BreakerState is a type that represents a state of CircuitBreaker. 14 type BreakerState int 15 16 // These constants are states of CircuitBreaker. 17 const ( 18 BreakerStateClosed BreakerState = iota 19 BreakerStateHalfOpen 20 BreakerStateOpen 21 ) 22 23 var ( 24 // ErrTooManyRequests is returned when the CB state is half open and the requests count is over the cb maxRequests 25 ErrTooManyRequests = errors.New("too many requests") 26 // ErrBreakerOpenState is returned when the CB state is open 27 ErrBreakerOpenState = errors.New("circuit breaker is open") 28 ) 29 30 // String implements stringer interface. 31 func (s BreakerState) String() string { 32 switch s { 33 case BreakerStateClosed: 34 return "closed" 35 case BreakerStateHalfOpen: 36 return "half-open" 37 case BreakerStateOpen: 38 return "open" 39 default: 40 return fmt.Sprintf("unknown state: %d", s) 41 } 42 } 43 44 // BreakerCounts holds the numbers of requests and their successes/failures. 45 // CircuitBreaker clears the internal BreakerCounts either 46 // on the change of the state or at the closed-state intervals. 47 // BreakerCounts ignores the results of the requests sent before clearing. 48 type BreakerCounts struct { 49 Requests uint32 50 TotalSuccesses uint32 51 TotalFailures uint32 52 ConsecutiveSuccesses uint32 53 ConsecutiveFailures uint32 54 } 55 56 func (c *BreakerCounts) onRequest() { 57 c.Requests++ 58 } 59 60 func (c *BreakerCounts) onSuccess() { 61 c.TotalSuccesses++ 62 c.ConsecutiveSuccesses++ 63 c.ConsecutiveFailures = 0 64 } 65 66 func (c *BreakerCounts) onFailure() { 67 c.TotalFailures++ 68 c.ConsecutiveFailures++ 69 c.ConsecutiveSuccesses = 0 70 } 71 72 func (c *BreakerCounts) clear() { 73 c.Requests = 0 74 c.TotalSuccesses = 0 75 c.TotalFailures = 0 76 c.ConsecutiveSuccesses = 0 77 c.ConsecutiveFailures = 0 78 } 79 80 // BreakerSettings configures CircuitBreaker: 81 // 82 // Name is the name of the CircuitBreaker. 83 // 84 // MaxRequests is the maximum number of requests allowed to pass through 85 // when the CircuitBreaker is half-open. 86 // If MaxRequests is 0, the CircuitBreaker allows only 1 request. 87 // 88 // Interval is the cyclic period of the closed state 89 // for the CircuitBreaker to clear the internal BreakerCounts. 90 // If Interval is less than or equal to 0, the CircuitBreaker doesn't clear internal BreakerCounts during the closed state. 91 // 92 // Timeout is the period of the open state, 93 // after which the state of the CircuitBreaker becomes half-open. 94 // If Timeout is less than or equal to 0, the timeout value of the CircuitBreaker is set to 60 seconds. 95 // 96 // ReadyToTrip is called with a copy of BreakerCounts whenever a request fails in the closed state. 97 // If ReadyToTrip returns true, the CircuitBreaker will be placed into the open state. 98 // If ReadyToTrip is nil, default ReadyToTrip is used. 99 // Default ReadyToTrip returns true when the number of consecutive failures is more than 5. 100 // 101 // OnStateChange is called whenever the state of the CircuitBreaker changes. 102 // 103 // IsSuccessful is called with the error returned from the request, if not nil. 104 // If IsSuccessful returns false, the error is considered a failure, and is counted towards tripping the circuit breaker. 105 // If IsSuccessful returns true, the error will be returned to the caller without tripping the circuit breaker. 106 // If IsSuccessful is nil, default IsSuccessful is used, which returns false for all non-nil errors. 107 type BreakerSettings struct { 108 Name string 109 MaxRequests uint32 110 Interval time.Duration 111 Timeout time.Duration 112 ReadyToTrip func(counts BreakerCounts) bool 113 OnStateChange func(name string, from BreakerState, to BreakerState) 114 IsSuccessful func(err error) bool 115 } 116 117 // CircuitBreaker is a state machine to prevent sending requests that are likely to fail. 118 type CircuitBreaker struct { 119 name string 120 maxRequests uint32 121 interval time.Duration 122 timeout time.Duration 123 readyToTrip func(counts BreakerCounts) bool 124 isSuccessful func(err error) bool 125 onStateChange func(name string, from BreakerState, to BreakerState) 126 127 mutex sync.Mutex 128 state BreakerState 129 generation uint64 130 counts BreakerCounts 131 expiry time.Time 132 } 133 134 // TwoStepCircuitBreaker is like CircuitBreaker but instead of surrounding a function 135 // with the breaker functionality, it only checks whether a request can proceed and 136 // expects the caller to report the outcome in a separate step using a callback. 137 type TwoStepCircuitBreaker struct { 138 cb *CircuitBreaker 139 } 140 141 // NewCircuitBreaker returns a new CircuitBreaker configured with the given BreakerSettings. 142 func NewCircuitBreaker(st BreakerSettings) *CircuitBreaker { 143 cb := new(CircuitBreaker) 144 145 cb.name = st.Name 146 cb.onStateChange = st.OnStateChange 147 148 if st.MaxRequests == 0 { 149 cb.maxRequests = 1 150 } else { 151 cb.maxRequests = st.MaxRequests 152 } 153 154 if st.Interval <= 0 { 155 cb.interval = defaultBreakerInterval 156 } else { 157 cb.interval = st.Interval 158 } 159 160 if st.Timeout <= 0 { 161 cb.timeout = defaultBreakerTimeout 162 } else { 163 cb.timeout = st.Timeout 164 } 165 166 if st.ReadyToTrip == nil { 167 cb.readyToTrip = defaultReadyToTrip 168 } else { 169 cb.readyToTrip = st.ReadyToTrip 170 } 171 172 if st.IsSuccessful == nil { 173 cb.isSuccessful = defaultIsSuccessful 174 } else { 175 cb.isSuccessful = st.IsSuccessful 176 } 177 178 cb.toNewGeneration(time.Now()) 179 180 return cb 181 } 182 183 // NewTwoStepCircuitBreaker returns a new TwoStepCircuitBreaker configured with the given BreakerSettings. 184 func NewTwoStepCircuitBreaker(st BreakerSettings) *TwoStepCircuitBreaker { 185 return &TwoStepCircuitBreaker{ 186 cb: NewCircuitBreaker(st), 187 } 188 } 189 190 const defaultBreakerInterval = time.Duration(0) * time.Second 191 const defaultBreakerTimeout = time.Duration(60) * time.Second 192 193 func defaultReadyToTrip(counts BreakerCounts) bool { 194 return counts.ConsecutiveFailures > 5 195 } 196 197 func defaultIsSuccessful(err error) bool { 198 return err == nil 199 } 200 201 // Name returns the name of the CircuitBreaker. 202 func (cb *CircuitBreaker) Name() string { 203 return cb.name 204 } 205 206 // State returns the current state of the CircuitBreaker. 207 func (cb *CircuitBreaker) State() BreakerState { 208 cb.mutex.Lock() 209 defer cb.mutex.Unlock() 210 211 now := time.Now() 212 state, _ := cb.currentState(now) 213 return state 214 } 215 216 // Execute runs the given request if the CircuitBreaker accepts it. 217 // Execute returns an error instantly if the CircuitBreaker rejects the request. 218 // Otherwise, Execute returns the result of the request. 219 // If a panic occurs in the request, the CircuitBreaker handles it as an error 220 // and causes the same panic again. 221 func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) { 222 generation, err := cb.beforeRequest() 223 if err != nil { 224 return nil, err 225 } 226 227 defer func() { 228 e := recover() 229 if e != nil { 230 cb.afterRequest(generation, false) 231 panic(e) 232 } 233 }() 234 235 result, err := req() 236 cb.afterRequest(generation, cb.isSuccessful(err)) 237 return result, err 238 } 239 240 // Name returns the name of the TwoStepCircuitBreaker. 241 func (cb *TwoStepCircuitBreaker) Name() string { 242 return cb.cb.Name() 243 } 244 245 // State returns the current state of the TwoStepCircuitBreaker. 246 func (cb *TwoStepCircuitBreaker) State() BreakerState { 247 return cb.cb.State() 248 } 249 250 // Allow checks if a new request can proceed. It returns a callback that should be used to 251 // register the success or failure in a separate step. If the circuit breaker doesn't allow 252 // requests, it returns an error. 253 func (cb *TwoStepCircuitBreaker) Allow() (done func(success bool), err error) { 254 generation, err1 := cb.cb.beforeRequest() 255 if err1 != nil { 256 return nil, err1 257 } 258 259 return func(success bool) { 260 cb.cb.afterRequest(generation, success) 261 }, nil 262 } 263 264 func (cb *CircuitBreaker) beforeRequest() (uint64, error) { 265 cb.mutex.Lock() 266 defer cb.mutex.Unlock() 267 268 now := time.Now() 269 state, generation := cb.currentState(now) 270 271 if state == BreakerStateOpen { 272 return generation, ErrBreakerOpenState 273 } else if state == BreakerStateHalfOpen && cb.counts.Requests >= cb.maxRequests { 274 return generation, ErrTooManyRequests 275 } 276 277 cb.counts.onRequest() 278 return generation, nil 279 } 280 281 func (cb *CircuitBreaker) afterRequest(before uint64, success bool) { 282 cb.mutex.Lock() 283 defer cb.mutex.Unlock() 284 285 now := time.Now() 286 state, generation := cb.currentState(now) 287 if generation != before { 288 return 289 } 290 291 if success { 292 cb.onSuccess(state, now) 293 } else { 294 cb.onFailure(state, now) 295 } 296 } 297 298 func (cb *CircuitBreaker) onSuccess(state BreakerState, now time.Time) { 299 switch state { 300 case BreakerStateClosed: 301 cb.counts.onSuccess() 302 case BreakerStateHalfOpen: 303 cb.counts.onSuccess() 304 if cb.counts.ConsecutiveSuccesses >= cb.maxRequests { 305 cb.setState(BreakerStateClosed, now) 306 } 307 } 308 } 309 310 func (cb *CircuitBreaker) onFailure(state BreakerState, now time.Time) { 311 switch state { 312 case BreakerStateClosed: 313 cb.counts.onFailure() 314 if cb.readyToTrip(cb.counts) { 315 cb.setState(BreakerStateOpen, now) 316 } 317 case BreakerStateHalfOpen: 318 cb.setState(BreakerStateOpen, now) 319 } 320 } 321 322 func (cb *CircuitBreaker) currentState(now time.Time) (BreakerState, uint64) { 323 switch cb.state { 324 case BreakerStateClosed: 325 if !cb.expiry.IsZero() && cb.expiry.Before(now) { 326 cb.toNewGeneration(now) 327 } 328 case BreakerStateOpen: 329 if cb.expiry.Before(now) { 330 cb.setState(BreakerStateHalfOpen, now) 331 } 332 } 333 return cb.state, cb.generation 334 } 335 336 func (cb *CircuitBreaker) setState(state BreakerState, now time.Time) { 337 if cb.state == state { 338 return 339 } 340 341 prev := cb.state 342 cb.state = state 343 344 cb.toNewGeneration(now) 345 346 if cb.onStateChange != nil { 347 cb.onStateChange(cb.name, prev, state) 348 } 349 } 350 351 func (cb *CircuitBreaker) toNewGeneration(now time.Time) { 352 cb.generation++ 353 cb.counts.clear() 354 355 var zero time.Time 356 switch cb.state { 357 case BreakerStateClosed: 358 if cb.interval == 0 { 359 cb.expiry = zero 360 } else { 361 cb.expiry = now.Add(cb.interval) 362 } 363 case BreakerStateOpen: 364 cb.expiry = now.Add(cb.timeout) 365 default: // BreakerStateHalfOpen 366 cb.expiry = zero 367 } 368 }