1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
|
# frozen_string_literal: true
module HTTPX
module Plugins::CircuitBreaker
#
# A circuit is assigned to a given absoolute url or origin.
#
# It sets +max_attempts+, the number of attempts the circuit allows, before it is opened.
# It sets +reset_attempts_in+, the time a circuit stays open at most, before it resets.
# It sets +break_in+, the time that must elapse before an open circuit can transit to the half-open state.
# It sets +circuit_breaker_half_open_drip_rate+, the rate of requests a circuit allows to be performed when in an half-open state.
#
class Circuit
def initialize(max_attempts, reset_attempts_in, break_in, circuit_breaker_half_open_drip_rate)
@max_attempts = max_attempts
@reset_attempts_in = reset_attempts_in
@break_in = break_in
@circuit_breaker_half_open_drip_rate = circuit_breaker_half_open_drip_rate
@attempts = 0
total_real_attempts = @max_attempts * @circuit_breaker_half_open_drip_rate
@drip_factor = (@max_attempts / total_real_attempts).round
@state = :closed
end
def respond
try_close
case @state
when :closed
nil
when :half_open
@attempts += 1
# do real requests while drip rate valid
if (@real_attempts % @drip_factor).zero?
@real_attempts += 1
return
end
@response
when :open
@response
end
end
def try_open(response)
case @state
when :closed
now = Utils.now
if @attempts.positive?
# reset if error happened long ago
@attempts = 0 if now - @attempted_at > @reset_attempts_in
else
@attempted_at = now
end
@attempts += 1
return unless @attempts >= @max_attempts
@state = :open
@opened_at = now
@response = response
when :half_open
# open immediately
@state = :open
@attempted_at = @opened_at = Utils.now
@response = response
end
end
def try_close
case @state
when :closed
nil
when :half_open
# do not close circuit unless attempts exhausted
return unless @attempts >= @max_attempts
# reset!
@attempts = 0
@opened_at = @attempted_at = @response = nil
@state = :closed
when :open
if Utils.elapsed_time(@opened_at) > @break_in
@state = :half_open
@attempts = 0
@real_attempts = 0
end
end
end
end
end
end
|