Class: AllMyCircuits::Breaker
- Inherits:
-
Object
- Object
- AllMyCircuits::Breaker
- Includes:
- Logging
- Defined in:
- lib/all_my_circuits/breaker.rb
Instance Attribute Summary collapse
-
#name ⇒ Object
readonly
Returns the value of attribute name.
Class Method Summary collapse
-
.net_errors ⇒ Object
Public: exceptions typically thrown when using Net::HTTP.
Instance Method Summary collapse
-
#initialize(name:, watch_errors: Breaker.net_errors, sleep_seconds:, strategy:, notifier: Notifiers::NullNotifier.new, clock: Clock) ⇒ Breaker
constructor
Public: Initializes circuit breaker instance.
-
#run ⇒ Object
Public: executes supplied block of code and monitors failures.
Methods included from Logging
Constructor Details
#initialize(name:, watch_errors: Breaker.net_errors, sleep_seconds:, strategy:, notifier: Notifiers::NullNotifier.new, clock: Clock) ⇒ Breaker
Public: Initializes circuit breaker instance.
Options
name - name of the call wrapped into circuit breaker (e.g. "That Unstable Service").
watch_errors - exceptions to count as failures. Other exceptions will simply get re-raised
(default: AllMyCircuits::Breaker.net_errors).
sleep_seconds - number of seconds the circuit stays open before attempting to close.
strategy - an AllMyCircuits::Strategies::AbstractStrategy-compliant object that controls
when the circuit should be tripped open.
Built-in strategies:
AllMyCircuits::Strategies::PercentageOverWindowStrategy,
AllMyCircuits::Strategies::NumberOverWindowStrategy.
notifier - (optional) AllMyCircuits::Notifiers::AbstractNotifier-compliant object that
is called whenever circuit breaker state (open, closed) changes.
Built-in notifiers:
AllMyCircuits::Notifiers::NullNotifier.
Examples
AllMyCircuits::Breaker.new(
name: "My Unstable Service",
sleep_seconds: 5,
strategy: AllMyCircuits::Strategies::PercentageOverWindowStrategy.new(
requests_window: 20, # number of requests in the window to calculate failure rate for
failure_rate_percent_threshold: 25 # how many failures can occur within the window, in percent,
) # before the circuit opens
)
AllMyCircuits::Breaker.new(
name: "Another Unstable Service",
sleep_seconds: 5,
strategy: AllMyCircuits::Strategies::NumberOverWindowStrategy.new(
requests_window: 20,
failures_threshold: 25 # how many failures can occur within the window before the circuit opens
)
)
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/all_my_circuits/breaker.rb', line 59 def initialize(name:, watch_errors: Breaker.net_errors, sleep_seconds:, strategy:, notifier: Notifiers::NullNotifier.new, clock: Clock) @name = String(name).dup.freeze @watch_errors = Array(watch_errors).dup @sleep_seconds = Integer(sleep_seconds) @strategy = strategy @notifier = notifier @state_lock = Mutex.new @request_number = Concurrent::AtomicReference.new(0) @last_open_or_probed = nil @opened_at_request_number = 0 @clock = clock end |
Instance Attribute Details
#name ⇒ Object (readonly)
Returns the value of attribute name.
9 10 11 |
# File 'lib/all_my_circuits/breaker.rb', line 9 def name @name end |
Class Method Details
.net_errors ⇒ Object
Public: exceptions typically thrown when using Net::HTTP
13 14 15 16 17 18 19 |
# File 'lib/all_my_circuits/breaker.rb', line 13 def self.net_errors require "timeout" require "net/http" [Timeout::Error, Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::HTTPFatalError, Net::HTTPServerError] end |
Instance Method Details
#run ⇒ Object
Public: executes supplied block of code and monitors failures. Once the number of failures reaches a certain threshold, the block is bypassed for a certain period of time.
Consider the following examples of calls through circuit breaker (let it be 1 call per second, and let the circuit breaker be configured as in the example below):
Legend
S - successful request
F - failed request
O - skipped request (circuit open)
| - open circuit interval end
1) S S F F S S F F S F O O O O O|S S S S S
Here among the first 10 requests (window), 5 failures occur (50%), the circuit is tripped open
for 5 seconds, and a few requests are skipped. Then, after 5 seconds, a request is issued to
see whether everything is back to normal, and the circuit is then closed again.
2) S S F F S S F F S F O O O O O|F O O O O O|S S S S S
Same situation, 10 requests, 5 failed, circuit is tripped open for 5 seconds. Then we
check that service is back to normal, and it is not. The circuit is open again.
After another 5 seconds we check again and close the circuit.
Returns nothing. Raises AllMyCircuit::BreakerOpen with the name of the service when the circuit is open. Raises whatever has been raised by the supplied block.
This call is thread-safe sans the supplied block.
Examples
@cb = AllMyCircuits::Breaker.new(
name: "that bad service",
sleep_seconds: 5,
strategy: AllMyCircuits::Strategies::PercentageOverWindowStrategy.new(
requests_window: 10,
failure_rate_percent_threshold: 50
)
)
@client = MyBadServiceClient.new(timeout: 2)
begin
@cb.run do
@client.make_expensive_unreliable_http_call
end
rescue AllMyCircuits::BreakerOpen => e
[]
rescue MyBadServiceClient::Error => e
MyLog << "an error has occured in call to my bad service"
[]
end
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/all_my_circuits/breaker.rb', line 137 def run unless allow_request? debug "declining request, circuit is open", name raise BreakerOpen, @name end current_request_number = generate_request_number begin result = yield success(current_request_number) result rescue *@watch_errors error(current_request_number) raise end end |