Write distributed systems that are resilient and self-heal. Remote calls can fail or hang indefinietly without a response. Supervision will help to isolate failure and keep individual components from bringing down the whole system. The basic idea is to wrap dangerous method call inside protected
supervise
helper that will monitor for failure and handle it according to the specified rules to prevent it from cascading.
Add this line to your application's Gemfile:
gem 'supervision'
And then execute:
$ bundle
Or install it yourself as:
$ gem install supervision
Supervision instance takes the following configuration options:
:max_failure
- maximum failure count allowed before Supervision raisesCircuitBreakerOpenError
. By default5 failures
are allowed.:call_timeout
- duration time for a method before it is assumed to have failed. By default10 milliseconds
.:reset_timeout
- duration before a method is allowed to attempt a call. Subsequent calls will fail fast if failure is detected. By default100 milliseconds
Next to instantiate the Supervision in order to protect a call to external/remote service that has potential to fail do:
@supervision = Supervision.new { |arg| remote_api_call(arg) }
or alternatively use supervise
helper
@supervision = Supervision.supervise { |arg| remote_api_call(arg) }
Once the call is wrapped you can execute it by sending call
messsage with arguments like so:
@supervision.call({user: 'Piotr'})
You can register more than one Supervision by using internal register system. Simply register name under which you want the circuit to be available by calling supervise_as
helper:
Supervision.supervise_as(:danger) { remote_api_call }
In order to retrieve registered circuit you can use hash syntax:
Supervision[:danger] # => returns registered circuit
The name under which method is registerd will be available as a method call. Consequently, to execute registered circuit do:
Supervision.danger(:foo, :bar) # => will call underlying method and pass :foo, :barr
Supervision can also act as a mixin and expose supervise
and supervise_as
accordingly. Use supervise_as
if you want to be able to register supervised calls inside Supervision system. Otherwise, use supervise
helper to create anonymous supervised call.
class Api
include Supervision
def remote_call
...
end
supervise_as :danger { remote_call } # => register supervision as :danger
def fetch(repository)
danger(repository)
rescue Supervision::CircuitBreakerOpenError
nil
end
end
@api = Api.new
@api.fetch('github_api')
You can listen for failure
and success
by attaching on_failure
, on_success
listeners respectively:
@supervision.on_failure { notify_me }
def notify_me
puts("The circuit breaker is now open")
end
If you want to configure Supervision, you can either pass options directly
@supervision = Supervison.new max_failures: 2, call_timeout: 10.milli, reset_timeout: 0.1.sec do
remote_api_call
end
or use configure
helper
@supervision.configure do
max_failures 5
call_timeout 10.sec
reset_timeout 1.min
end
All the numeric types are extended with time related helpers to allow for more fluid parameters when creating Supervision
call_timeout: 10.milliseconds
call_timeout: 10.millis
call_timeout: 1.millisecond
call_timeout: 1.milli
call_timeout: 1.second
call_timeout: 1.sec
call_timeout: 10.secs
call_timeout: 10.seconds
call_timeout: 1.minute
call_timeout: 1.min
call_timeout: 10.minutes
call_timeout: 10.mins
call_timeout: 1.hour
call_timeout: 10.hours
- Fork it ( https://github.com/piotrmurach/supervision/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Copyright (c) 2014-2016 Piotr Murach. See LICENSE for further details.