circuit_breakage 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -0
- data/lib/circuit_breakage/breaker.rb +10 -4
- data/lib/circuit_breakage/version.rb +1 -1
- data/spec/breaker_spec.rb +82 -0
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cba4ea6a897024a9a64f381298b155806e47ade7
|
4
|
+
data.tar.gz: bda065d0f7132b76891004752add0cc948bcfc6e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04e698f1eff6b98ad18a4ad6359c96db40d7756a3bec6c04c1e44f78aecf5f05781cd1f506631139535c1ee9f8c47585a915750e8d44e3dcafa79eb0cf4196c7
|
7
|
+
data.tar.gz: 9d5cfd0d6bf4e8b3ba7ef1f349e28f27ab92b9951c3dde89a94bc30e7ff0b5bb43bc3eabcdd79600a3f60a445c1a6112d5748a9098cf30e036a210f9f626fadd
|
data/README.md
CHANGED
@@ -18,10 +18,16 @@ proc = ->(*args) do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
breaker = CircuitBreakage::Breaker.new(proc)
|
21
|
+
|
22
|
+
# These options are required.
|
21
23
|
breaker.failure_threshold = 3 # only 3 failures before tripping circuit
|
22
24
|
breaker.duration = 10 # 10 seconds before retry
|
23
25
|
breaker.timeout = 0.5 # 500 milliseconds allowed before auto-fail
|
24
26
|
|
27
|
+
# These options are, uh, optional.
|
28
|
+
breaker.only_trip_on = [ExpensiveFailureException]
|
29
|
+
breaker.never_trip_on = [CheapUnimportantFailureException]
|
30
|
+
|
25
31
|
begin
|
26
32
|
breaker.call(*some_args) # args are passed through to the proc
|
27
33
|
rescue CircuitBreakage::CircuitOpen
|
@@ -10,16 +10,21 @@ module CircuitBreakage
|
|
10
10
|
class Breaker
|
11
11
|
attr_accessor :failure_count, :last_failed, :state, :block
|
12
12
|
attr_accessor :failure_threshold, :duration, :timeout, :last_exception
|
13
|
+
attr_accessor :only_trip_on, :never_trip_on
|
13
14
|
|
14
|
-
DEFAULT_FAILURE_THRESHOLD = 5
|
15
|
-
DEFAULT_DURATION = 300
|
16
|
-
DEFAULT_TIMEOUT = 10
|
15
|
+
DEFAULT_FAILURE_THRESHOLD = 5 # Number of failures required to trip circuit
|
16
|
+
DEFAULT_DURATION = 300 # Number of seconds the circuit stays tripped
|
17
|
+
DEFAULT_TIMEOUT = 10 # Number of seconds before the call times out
|
18
|
+
DEFAULT_ONLY_TRIP_ON = [Exception] # Exceptions that trigger the breaker
|
19
|
+
DEFAULT_NEVER_TRIP_ON = [] # Exceptions that won't trigger the breaker
|
17
20
|
|
18
21
|
def initialize(block=nil)
|
19
22
|
self.block = block
|
20
23
|
self.failure_threshold = DEFAULT_FAILURE_THRESHOLD
|
21
24
|
self.duration = DEFAULT_DURATION
|
22
25
|
self.timeout = DEFAULT_TIMEOUT
|
26
|
+
self.only_trip_on = DEFAULT_ONLY_TRIP_ON
|
27
|
+
self.never_trip_on = DEFAULT_NEVER_TRIP_ON
|
23
28
|
self.failure_count ||= 0
|
24
29
|
self.last_failed ||= 0
|
25
30
|
self.state ||= 'closed'
|
@@ -55,7 +60,8 @@ module CircuitBreakage
|
|
55
60
|
handle_success
|
56
61
|
|
57
62
|
return ret_value
|
58
|
-
rescue
|
63
|
+
rescue *self.only_trip_on => e
|
64
|
+
raise if never_trip_on.any? { |t| e.instance_of?(t) }
|
59
65
|
handle_failure(e)
|
60
66
|
end
|
61
67
|
|
data/spec/breaker_spec.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
module CircuitBreakage
|
2
|
+
class VerySpecificException < StandardError
|
3
|
+
end
|
4
|
+
|
2
5
|
describe Breaker do
|
3
6
|
let(:breaker) { Breaker.new(block) }
|
4
7
|
let(:block) { ->(x) { return x } }
|
@@ -78,6 +81,85 @@ module CircuitBreakage
|
|
78
81
|
expect { breaker.call(arg) }.to raise_exception(CircuitBreakage::CircuitTimeout)
|
79
82
|
end
|
80
83
|
end
|
84
|
+
|
85
|
+
context 'with specific exceptions defined' do
|
86
|
+
before do
|
87
|
+
breaker.only_trip_on = [VerySpecificException]
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'and the call fails with one of the specific exceptions' do
|
91
|
+
let(:block) { ->(_) { raise VerySpecificException } }
|
92
|
+
|
93
|
+
it { is_expected.to change { breaker.failure_count }.by(1) }
|
94
|
+
it { is_expected.to change { breaker.last_failed } }
|
95
|
+
it { is_expected.to change { breaker.last_exception }.from(nil) }
|
96
|
+
|
97
|
+
it 'raises the exception that caused the failure' do
|
98
|
+
expect { breaker.call(arg) }.to raise_exception(VerySpecificException)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'and the call fails with a different exception (including timeouts)' do
|
103
|
+
let(:block) { ->(_) { raise Timeout::Error } }
|
104
|
+
|
105
|
+
it { is_expected.not_to change { breaker.failure_count } }
|
106
|
+
it { is_expected.not_to change { breaker.last_failed } }
|
107
|
+
it { is_expected.not_to change { breaker.last_exception } }
|
108
|
+
|
109
|
+
it 'raises the exception that caused the failure' do
|
110
|
+
expect { breaker.call(arg) }.to raise_exception(Timeout::Error)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'with specific exceptions excluded' do
|
116
|
+
before do
|
117
|
+
breaker.never_trip_on = [VerySpecificException]
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'and the call fails with one of the specific exceptions' do
|
121
|
+
let(:block) { ->(_) { raise VerySpecificException } }
|
122
|
+
|
123
|
+
it { is_expected.not_to change { breaker.failure_count } }
|
124
|
+
it { is_expected.not_to change { breaker.last_failed } }
|
125
|
+
it { is_expected.not_to change { breaker.last_exception } }
|
126
|
+
|
127
|
+
it 'raises the exception that caused the failure' do
|
128
|
+
expect { breaker.call(arg) }.to raise_exception(VerySpecificException)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'and the call fails with a different exception' do
|
133
|
+
let(:block) { ->(_) { raise RuntimeError } }
|
134
|
+
|
135
|
+
it { is_expected.to change { breaker.failure_count }.by(1) }
|
136
|
+
it { is_expected.to change { breaker.last_failed } }
|
137
|
+
it { is_expected.to change { breaker.last_exception }.from(nil) }
|
138
|
+
|
139
|
+
it 'raises the exception that caused the failure' do
|
140
|
+
expect { breaker.call(arg) }.to raise_exception(RuntimeError)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'with overlapping whitelisted and blacklisted exceptions' do
|
146
|
+
before do
|
147
|
+
breaker.only_trip_on = [StandardError]
|
148
|
+
breaker.never_trip_on = [VerySpecificException] # inherits from StandardError
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'and the call fails with an overlapped exception' do
|
152
|
+
let(:block) { ->(_) { raise VerySpecificException } }
|
153
|
+
|
154
|
+
it { is_expected.not_to change { breaker.failure_count } }
|
155
|
+
it { is_expected.not_to change { breaker.last_failed } }
|
156
|
+
it { is_expected.not_to change { breaker.last_exception } }
|
157
|
+
|
158
|
+
it 'raises the exception that caused the failure' do
|
159
|
+
expect { breaker.call(arg) }.to raise_exception(VerySpecificException)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
81
163
|
end
|
82
164
|
|
83
165
|
context 'when the circuit is open' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: circuit_breakage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Hyland
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
108
|
version: '0'
|
109
109
|
requirements: []
|
110
110
|
rubyforge_project:
|
111
|
-
rubygems_version: 2.
|
111
|
+
rubygems_version: 2.4.5
|
112
112
|
signing_key:
|
113
113
|
specification_version: 4
|
114
114
|
summary: Provides a simple circuit breaker pattern.
|
@@ -116,4 +116,3 @@ test_files:
|
|
116
116
|
- spec/breaker_spec.rb
|
117
117
|
- spec/redis_backed_breaker_spec.rb
|
118
118
|
- spec/spec_helper.rb
|
119
|
-
has_rdoc:
|