supervision 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +22 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +22 -0
- data/README.md +154 -0
- data/Rakefile +8 -0
- data/lib/supervision/atomic.rb +41 -0
- data/lib/supervision/circuit_breaker.rb +89 -0
- data/lib/supervision/circuit_control.rb +182 -0
- data/lib/supervision/circuit_monitor.rb +13 -0
- data/lib/supervision/circuit_system.rb +16 -0
- data/lib/supervision/configuration.rb +73 -0
- data/lib/supervision/factory.rb +7 -0
- data/lib/supervision/registry.rb +69 -0
- data/lib/supervision/time_dsl.rb +36 -0
- data/lib/supervision/version.rb +5 -0
- data/lib/supervision.rb +67 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/unit/atomic_spec.rb +30 -0
- data/spec/unit/circuit_breaker_spec.rb +102 -0
- data/spec/unit/circuit_control_spec.rb +72 -0
- data/spec/unit/circuit_monitor_spec.rb +6 -0
- data/spec/unit/configuration_spec.rb +14 -0
- data/spec/unit/initialize_spec.rb +55 -0
- data/spec/unit/registry_spec.rb +30 -0
- data/spec/unit/time_dsl_spec.rb +31 -0
- data/supervision.gemspec +23 -0
- data/tasks/console.rake +10 -0
- data/tasks/coverage.rake +11 -0
- data/tasks/spec.rake +29 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 11acf39bc8b8bc7f6c2ab1ac8f58696aa305c7f5
|
4
|
+
data.tar.gz: 697bf64670a3fe4fed371cc6f2ed7b8c700b1ae6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6e81cdae2a3f09db0bc05511d1291e2291b54150bdd8ac7d4580e01a4010769fb05b428cd02e874f8c2766f97239ca2e941c71057e10f38f8f424b5bf25e1366
|
7
|
+
data.tar.gz: 04449ffd0306979981482efaf67818a37e4a2bda9cc4173933f532402c7f8c9b3ac8242b62dd3ff09444d696f2aec38e0a1c5b509509c16a10c9c5e70afc4669
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
*.sw[a-z]
|
23
|
+
mkmf.log
|
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
supervision
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0
|
data/.travis.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
language: ruby
|
2
|
+
bundler_args: --without yard guard benchmarks
|
3
|
+
script: "bundle exec rake ci"
|
4
|
+
rvm:
|
5
|
+
- 1.9.3
|
6
|
+
- 2.0.0
|
7
|
+
- 2.1.0
|
8
|
+
- ruby-head
|
9
|
+
matrix:
|
10
|
+
include:
|
11
|
+
- rvm: jruby-19mode
|
12
|
+
- rvm: jruby-20mode
|
13
|
+
- rvm: jruby-21mode
|
14
|
+
- rvm: jruby-head
|
15
|
+
- rvm: rbx
|
16
|
+
allow_failures:
|
17
|
+
- rvm: ruby-head
|
18
|
+
- rvm: jruby-head
|
19
|
+
- rvm: rbx
|
20
|
+
fast_finish: true
|
21
|
+
branches:
|
22
|
+
only: master
|
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem 'rake', '~> 10.2.2'
|
7
|
+
gem 'rspec', '~> 2.14.1'
|
8
|
+
gem 'yard', '~> 0.8.7'
|
9
|
+
end
|
10
|
+
|
11
|
+
group :metrics do
|
12
|
+
gem 'coveralls', '~> 0.7.0'
|
13
|
+
gem 'simplecov', '~> 0.8.2'
|
14
|
+
gem 'yardstick', '~> 0.9.9'
|
15
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Piotr Murach
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# Supervision
|
2
|
+
[][gem]
|
3
|
+
[][travis]
|
4
|
+
[][codeclimate]
|
5
|
+
|
6
|
+
[gem]: http://badge.fury.io/rb/supervision
|
7
|
+
[travis]: http://travis-ci.org/peter-murach/supervision
|
8
|
+
[codeclimate]: https://codeclimate.com/github/peter-murach/supervision
|
9
|
+
|
10
|
+
Write distributed systems that are resilient and self-heal. Remote calls can fail or hang indefinietly without a response.
|
11
|
+
**Supervision** will help to isolate failure and keep individual components from bringing down the whole system.
|
12
|
+
The basic idea is to wrap dangerous method call inside protected `supervise` helper that will monitor for failure and
|
13
|
+
handle it according to the specified rules to prevent it from cascading.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'supervision'
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install supervision
|
28
|
+
|
29
|
+
## 1 Usage
|
30
|
+
|
31
|
+
**Supervision** instance takes the following configuration options:
|
32
|
+
|
33
|
+
* `:max_failure` - maximum failure count allowed before **Supervision** raises `CircuitBreakerOpenError`. By default `5 failures` are allowed.
|
34
|
+
* `:call_timeout` - duration time for a method before it is assumed to have failed. By default `10 milliseconds`.
|
35
|
+
* `:reset_timeout` - duration before a method is allowed to attempt a call. Subsequent calls will fail fast if failure is detected. By default `100 milliseconds`
|
36
|
+
|
37
|
+
Next to instantiate the **Supervision** in order to protect a call to external/remote service that has potential to fail do:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
@supervision = Supervision.new { |arg| remote_api_call(arg) }
|
41
|
+
```
|
42
|
+
|
43
|
+
or alternatively use `supervise` helper
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
@supervision = Supervision.supervise { |arg| remote_api_call(arg) }
|
47
|
+
```
|
48
|
+
|
49
|
+
Once the call is wrapped you can execute it by sending `call` messsage with arguments like so:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
@supervision.call({user: 'Piotr'})
|
53
|
+
```
|
54
|
+
|
55
|
+
Finally, you can also register **Supervision** instance by name
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
Supervision.supervise_as(:danger) { remote_api_call }
|
59
|
+
```
|
60
|
+
|
61
|
+
The name under which method is registerd will be available as a method call
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
Supervision.danger.call
|
65
|
+
```
|
66
|
+
|
67
|
+
## 2 Mixin
|
68
|
+
|
69
|
+
**Supervision** can also act as a mixin and expose `supervise` and `supervise_as` accordingly.
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class Api
|
73
|
+
include Supervision
|
74
|
+
|
75
|
+
def remote_call
|
76
|
+
...
|
77
|
+
end
|
78
|
+
supervise :danger { remote_call }
|
79
|
+
|
80
|
+
def fetch(repository)
|
81
|
+
danger.call(repository)
|
82
|
+
rescue Supervision::CircuitBreakerOpenError
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
@api = Api.new
|
88
|
+
@api.fetch('github_api')
|
89
|
+
```
|
90
|
+
|
91
|
+
## 3 Callbacks
|
92
|
+
|
93
|
+
You can listen for `failure` and `success` by attaching `on_failure`, `on_success` listeners respectively:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
@supervision.on_failure { notify_me }
|
97
|
+
|
98
|
+
def notify_me
|
99
|
+
puts("The circuit breaker is now open")
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
## 4 Configuration
|
104
|
+
|
105
|
+
If you want to configure **Supervision**, you can either pass options directly
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
@supervision = Supervison.new max_failures: 2, call_timeout: 10.milli, reset_timeout: 0.1.sec do
|
109
|
+
remote_api_call
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
or use `configure` helper
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
@supervision.configure do
|
117
|
+
max_failures 5
|
118
|
+
call_timeout 10.sec
|
119
|
+
reset_timeout 1.min
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
## 5 Time
|
124
|
+
|
125
|
+
All the numeric types are extended with time related helpers to allow for more fluid parameters when creating **Supervision**
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
call_timeout: 10.milliseconds
|
129
|
+
call_timeout: 10.millis
|
130
|
+
call_timeout: 1.millisecond
|
131
|
+
call_timeout: 1.milli
|
132
|
+
call_timeout: 1.second
|
133
|
+
call_timeout: 1.sec
|
134
|
+
call_timeout: 10.secs
|
135
|
+
call_timeout: 10.seconds
|
136
|
+
call_timeout: 1.minute
|
137
|
+
call_timeout: 1.min
|
138
|
+
call_timeout: 10.minutes
|
139
|
+
call_timeout: 10.mins
|
140
|
+
call_timeout: 1.hour
|
141
|
+
call_timeout: 10.hours
|
142
|
+
```
|
143
|
+
|
144
|
+
## Contributing
|
145
|
+
|
146
|
+
1. Fork it ( https://github.com/[my-github-username]/supervision/fork )
|
147
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
148
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
149
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
150
|
+
5. Create a new Pull Request
|
151
|
+
|
152
|
+
## Copyright
|
153
|
+
|
154
|
+
Copyright (c) 2014 Piotr Murach. See LICENSE for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Supervision
|
4
|
+
# A class responsible for creating threadsafe value objects
|
5
|
+
class Atomic
|
6
|
+
|
7
|
+
# Initialize an Atomic instance
|
8
|
+
#
|
9
|
+
# @param [Numeric] value
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def initialize(value = nil)
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@value = value
|
15
|
+
end
|
16
|
+
|
17
|
+
# Retrieve value
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def get
|
21
|
+
@mutex.synchronize { @value }
|
22
|
+
end
|
23
|
+
alias_method :value, :get
|
24
|
+
|
25
|
+
# Set value
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
def set(new_value)
|
29
|
+
@mutex.synchronize { @value = new_value}
|
30
|
+
end
|
31
|
+
alias_method :value=, :set
|
32
|
+
|
33
|
+
# Update value
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def update
|
37
|
+
set(new_value = yield(get)) if block_given?
|
38
|
+
new_value
|
39
|
+
end
|
40
|
+
end # Atomic
|
41
|
+
end # Supervision
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Supervision
|
4
|
+
# A class responsible for protecting remote calls
|
5
|
+
class CircuitBreaker
|
6
|
+
include Timeout
|
7
|
+
|
8
|
+
attr_reader :control
|
9
|
+
|
10
|
+
def initialize(options = {}, &block)
|
11
|
+
if block.nil?
|
12
|
+
raise ArgumentError, 'CircuitBreaker.new requires a block'
|
13
|
+
end
|
14
|
+
@control = CircuitControl.new(options)
|
15
|
+
@circuit = Atomic.new(block)
|
16
|
+
@mutex = Mutex.new
|
17
|
+
@before_hook = -> {}
|
18
|
+
@success_hook = -> {}
|
19
|
+
@failure_hook = -> {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Configure circuit instance parameters
|
23
|
+
#
|
24
|
+
# @yield [Configuration]
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
def configure(&block)
|
28
|
+
if block.arity.zero?
|
29
|
+
control.config.instance_eval(&block)
|
30
|
+
else
|
31
|
+
yield control.config
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Executes the dangerous call
|
36
|
+
#
|
37
|
+
# # TODO: this should distribute calls so we don't wait
|
38
|
+
# in sync call for timeout
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
def call(*args)
|
42
|
+
@before_hook.call
|
43
|
+
begin
|
44
|
+
result = dispatch(*args)
|
45
|
+
@success_hook.call
|
46
|
+
result
|
47
|
+
rescue Exception => error
|
48
|
+
control.handle(error)
|
49
|
+
@failure_hook.call
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def force_open
|
54
|
+
end
|
55
|
+
|
56
|
+
def force_close
|
57
|
+
end
|
58
|
+
|
59
|
+
def before(&block)
|
60
|
+
@before_hook = block
|
61
|
+
end
|
62
|
+
|
63
|
+
# Callback executed on successful call
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def on_success(&block)
|
67
|
+
@success_hook = block
|
68
|
+
end
|
69
|
+
alias_method :on_closed, :on_success
|
70
|
+
|
71
|
+
def on_failure(&block)
|
72
|
+
@failure_hook = block
|
73
|
+
end
|
74
|
+
alias_method :on_open, :on_failure
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Dispatch message to the current circuit
|
79
|
+
#
|
80
|
+
# @api private
|
81
|
+
def dispatch(*args)
|
82
|
+
result = timeout(control.call_timeout) do
|
83
|
+
@circuit.value.call(*args)
|
84
|
+
end
|
85
|
+
control.record_success
|
86
|
+
result
|
87
|
+
end
|
88
|
+
end # CircuitBreaker
|
89
|
+
end # Supervision
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Supervision
|
4
|
+
# A class responsible for controling state of the circuit
|
5
|
+
class CircuitControl
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :@config, :max_failures, :call_timeout, :failure_count
|
9
|
+
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
MAX_THREAD_LIFETIME = 5
|
13
|
+
|
14
|
+
# Create a circuit control
|
15
|
+
#
|
16
|
+
# @param [Hash] options
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def initialize(options = {})
|
20
|
+
@config = Configuration.new(options)
|
21
|
+
@failure_count = Atomic.new(0)
|
22
|
+
@last_failure_time = Atomic.new
|
23
|
+
@lock = Mutex.new
|
24
|
+
fsm
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates internal finite state machine to
|
28
|
+
# transitions through three states :closed,
|
29
|
+
# :open and :half_open.
|
30
|
+
#
|
31
|
+
# @return [FiniteMachine]
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
def fsm
|
35
|
+
context = self
|
36
|
+
@fsm ||= FiniteMachine.define do
|
37
|
+
initial :closed
|
38
|
+
|
39
|
+
target context
|
40
|
+
|
41
|
+
events {
|
42
|
+
event :trip, [:closed, :half_open] => :open
|
43
|
+
event :attempt_reset, :open => :half_open
|
44
|
+
event :reset, :half_open => :closed
|
45
|
+
}
|
46
|
+
|
47
|
+
callbacks {
|
48
|
+
on_enter :closed do |event|
|
49
|
+
reset_failure
|
50
|
+
end
|
51
|
+
|
52
|
+
on_enter :open do |event|
|
53
|
+
measure_timeout
|
54
|
+
fail_fast!
|
55
|
+
end
|
56
|
+
|
57
|
+
on_enter :half_open do |event|
|
58
|
+
end
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def_delegators :@fsm, :trip, :attempt_reset, :reset, :current
|
64
|
+
|
65
|
+
# Total failure count for current circuit
|
66
|
+
#
|
67
|
+
# @return [Integer]
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def failure_count
|
71
|
+
@failure_count.value
|
72
|
+
end
|
73
|
+
|
74
|
+
# Last time failure occured
|
75
|
+
#
|
76
|
+
# @return [Time]
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
def last_failure_time
|
80
|
+
@last_failure_time.value
|
81
|
+
end
|
82
|
+
|
83
|
+
# Fail fast on any call
|
84
|
+
#
|
85
|
+
# @raise [CircuitBreakerOpenError]
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
def fail_fast!
|
89
|
+
# monitor.record_open
|
90
|
+
raise CircuitBreakerOpenError
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Boolean]
|
94
|
+
#
|
95
|
+
# @api private
|
96
|
+
def failure_count_exceeded?
|
97
|
+
failure_count > @config.max_failures
|
98
|
+
end
|
99
|
+
|
100
|
+
# @return [Boolean]
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
def tripped?
|
104
|
+
fsm.open? && timeout_exceeded?
|
105
|
+
end
|
106
|
+
|
107
|
+
# Check if remaining duration until reset has been exceeded
|
108
|
+
#
|
109
|
+
# @return [Boolean]
|
110
|
+
# whether or not the breaker will attempt a reset by transitioning
|
111
|
+
# to :half_open state
|
112
|
+
#
|
113
|
+
# @api private
|
114
|
+
def timeout_exceeded?
|
115
|
+
return false unless last_failure_time
|
116
|
+
timeout = Time.now - last_failure_time
|
117
|
+
timeout > @config.reset_timeout
|
118
|
+
end
|
119
|
+
|
120
|
+
# Handler exception
|
121
|
+
#
|
122
|
+
# @api public
|
123
|
+
def handle(error = nil)
|
124
|
+
fail_fast! if fsm.open?
|
125
|
+
record_failure
|
126
|
+
trip if failure_count_exceeded? || fsm.half_open?
|
127
|
+
end
|
128
|
+
|
129
|
+
def record_success
|
130
|
+
reset if fsm.half_open?
|
131
|
+
reset_failure
|
132
|
+
end
|
133
|
+
|
134
|
+
# Records failure count
|
135
|
+
#
|
136
|
+
# @api public
|
137
|
+
def record_failure
|
138
|
+
if fsm.closed? || fsm.half_open?
|
139
|
+
@failure_count.update { |v| v + 1 }
|
140
|
+
@last_failure_time.value = Time.now
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Resets failure count
|
145
|
+
#
|
146
|
+
# @api public
|
147
|
+
def reset_failure
|
148
|
+
@failure_count.value = 0
|
149
|
+
@last_failure_time.value = nil
|
150
|
+
end
|
151
|
+
|
152
|
+
# Measure remaining timeout
|
153
|
+
#
|
154
|
+
# @api private
|
155
|
+
def measure_timeout
|
156
|
+
Thread.new do
|
157
|
+
Thread.current.abort_on_exception = true
|
158
|
+
thread = Thread.current
|
159
|
+
thread[:created_at] = Time.now
|
160
|
+
@lock.synchronize do
|
161
|
+
loop do
|
162
|
+
if tripped?
|
163
|
+
attempt_reset
|
164
|
+
break
|
165
|
+
elsif Time.now - thread[:created_at] > max_thread_lifetime
|
166
|
+
thread.kill
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Estimate maximum duration the scheduling thread should live
|
174
|
+
#
|
175
|
+
# @return [Time]
|
176
|
+
#
|
177
|
+
# @api private
|
178
|
+
def max_thread_lifetime
|
179
|
+
@config.reset_timeout + 100.milli
|
180
|
+
end
|
181
|
+
end # CircuitControl
|
182
|
+
end # Supervision
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Supervision
|
4
|
+
# A class responsible for registering circuits
|
5
|
+
class CircuitSystem
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators '@registry', :[], :get, :[]=, :set,
|
9
|
+
:register, :delete, :unregister
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@registry = Registry.new
|
13
|
+
end
|
14
|
+
|
15
|
+
end # CircuitSystem
|
16
|
+
end # Supervision
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Supervision
|
4
|
+
# A class responsbile for the circuit configuration options
|
5
|
+
class Configuration
|
6
|
+
DEFAULT_MAX_FAILURES = 5
|
7
|
+
|
8
|
+
DEFAULT_CALL_TIMEOUT = 10.milli
|
9
|
+
|
10
|
+
DEFAULT_RESET_TIMEOUT = 100.milli
|
11
|
+
|
12
|
+
# Create a Configuration options
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
def initialize(options = {})
|
16
|
+
verify_options!(options)
|
17
|
+
@max_failures = Atomic.new(options.fetch(:max_failures,
|
18
|
+
DEFAULT_MAX_FAILURES))
|
19
|
+
@call_timeout = Atomic.new(options.fetch(:call_timeout,
|
20
|
+
DEFAULT_CALL_TIMEOUT))
|
21
|
+
@reset_timeout = Atomic.new(options.fetch(:reset_timeout,
|
22
|
+
DEFAULT_RESET_TIMEOUT))
|
23
|
+
end
|
24
|
+
|
25
|
+
def max_failures=(value)
|
26
|
+
@max_failures.set(value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def max_failures(number = nil)
|
30
|
+
return @max_failures.value unless number
|
31
|
+
|
32
|
+
self.max_failures = number
|
33
|
+
end
|
34
|
+
|
35
|
+
def call_timeout=(value)
|
36
|
+
@call_timeout.set(value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def call_timeout(time = nil)
|
40
|
+
return @call_timeout.value unless time
|
41
|
+
|
42
|
+
self.call_timeout = time
|
43
|
+
end
|
44
|
+
|
45
|
+
def reset_timeout=(value)
|
46
|
+
@reset_timeout.set(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def reset_timeout(time = nil)
|
50
|
+
return @reset_timeout.value unless time
|
51
|
+
|
52
|
+
self.reset_timeout = time
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def known_options
|
58
|
+
[:max_failures, :call_timeout, :reset_timeout]
|
59
|
+
end
|
60
|
+
|
61
|
+
def verify_options!(options)
|
62
|
+
options.keys.each do |key|
|
63
|
+
raise_unknown_config_option(key) unless known_options.include?(key)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# TODO: replace with custom error
|
68
|
+
def raise_unknown_config_option(option)
|
69
|
+
raise ArgumentError, "`#{option}` isn`t recognized as valid parameter." \
|
70
|
+
" Please use one of `#{known_options.join(', ')}`"
|
71
|
+
end
|
72
|
+
end # Configuration
|
73
|
+
end # Supervision
|