Skip to content

Commit 6eac363

Browse files
authored
Merge pull request #8 from tarunsai/master
Remove caching for reached_failure_threshold and add spec for the same
2 parents 9c024c9 + 629bf25 commit 6eac363

File tree

3 files changed

+60
-18
lines changed

3 files changed

+60
-18
lines changed

lib/circuit_breaker-ruby/config.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,11 @@ def initialize
2020
self.invocation_timeout = INVOCATION_TIMEOUT
2121
self.retry_timeout = RETRY_TIMEOUT
2222
end
23+
24+
def self.update(klass, options)
25+
(UPDATABLE & options.keys).each do |variable|
26+
klass.instance_variable_set("@#{variable}", options[variable])
27+
end
28+
end
2329
end
2430
end

lib/circuit_breaker-ruby/shield.rb

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ module States
1616
def initialize(**options)
1717
@failure_count = 0
1818
@total_count = 0
19-
@failure_threshold = options[:failure_threshold] || config.failure_threshold
20-
@failure_threshold_percentage = options[:failure_threshold_percentage] || config.failure_threshold_percentage
21-
@invocation_timeout = options[:invocation_timeout] || config.invocation_timeout
22-
@retry_timeout = options[:retry_timeout] || config.retry_timeout
19+
@failure_threshold = options.fetch(:failure_threshold, config.failure_threshold)
20+
@failure_threshold_percentage = options.fetch(:failure_threshold_percentage, config.failure_threshold_percentage)
21+
@invocation_timeout = options.fetch(:invocation_timeout, config.invocation_timeout)
22+
@retry_timeout = options.fetch(:retry_timeout, config.retry_timeout)
2323
@callback = options[:callback]
2424
end
2525

@@ -28,17 +28,15 @@ def config
2828
end
2929

3030
def update_config!(options)
31-
(CircuitBreaker::Config::UPDATABLE & options.keys).each do |variable|
32-
instance_variable_set("@#{variable}", options[variable])
33-
end
31+
CircuitBreaker::Config.update(self, options)
3432
end
3533

3634
def protect(options = {}, &block)
3735
update_config!(options)
3836

3937
case prev_state = state
4038
when States::CLOSED, States::HALF_OPEN
41-
connect(prev_state, &block)
39+
connect(&block)
4240
when States::OPEN
4341
raise CircuitBreaker::Open
4442
end
@@ -47,21 +45,21 @@ def protect(options = {}, &block)
4745
private
4846

4947
def state
50-
if reached_failure_threshold? && reached_retry_timeout?
48+
if reached_failure_threshold? && reached_failure_threshold_percentage? && reached_retry_timeout?
5149
States::HALF_OPEN
52-
elsif reached_failure_threshold?
50+
elsif reached_failure_threshold? && reached_failure_threshold_percentage?
5351
States::OPEN
5452
else
5553
States::CLOSED
5654
end
5755
end
5856

5957
def reached_failure_threshold?
60-
@_reached_failure_threshold ||= begin
61-
(failure_count >= failure_threshold) &&
62-
(total_count != 0 &&
63-
(failure_count.to_f / total_count.to_f) >= failure_threshold_percentage)
64-
end
58+
(failure_count >= failure_threshold)
59+
end
60+
61+
def reached_failure_threshold_percentage?
62+
(total_count.nonzero? && (failure_count.to_f / total_count.to_f) >= failure_threshold_percentage)
6563
end
6664

6765
def reached_retry_timeout?
@@ -74,7 +72,7 @@ def reset
7472
@state = States::CLOSED
7573
end
7674

77-
def connect(prev_state, &block)
75+
def connect(&block)
7876
begin
7977
result = nil
8078
::Timeout::timeout(invocation_timeout) do
@@ -89,13 +87,13 @@ def connect(prev_state, &block)
8987
invoke_callback
9088
raise CircuitBreaker::TimeoutError
9189
ensure
92-
increment_total_count(prev_state)
90+
increment_total_count
9391
end
9492

9593
result
9694
end
9795

98-
def increment_total_count(state)
96+
def increment_total_count
9997
@total_count += 1
10098
end
10199

spec/circuit_breaker/shield_spec.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,44 @@
118118
end
119119
end
120120
end
121+
122+
context 'when circuit is in open state and goes to closed state' do
123+
it 'remains in closed state until it reaches failure_threshold' do
124+
retry_timeout = 3
125+
126+
circuit_breaker_shield = CircuitBreaker::Shield.new(
127+
invocation_timeout: 1,
128+
retry_timeout: retry_timeout,
129+
failure_threshold: 2
130+
)
131+
132+
circuit_breaker_shield.protect { sleep(0.1) } # succeed once
133+
expect(circuit_breaker_shield.total_count).to eql(1)
134+
expect(circuit_breaker_shield.failure_count).to eql(0)
135+
136+
expect {circuit_breaker_shield.protect { sleep(1.1) }}.to raise_error(CircuitBreaker::TimeoutError) # fail once
137+
expect(circuit_breaker_shield.total_count).to eql(2)
138+
expect(circuit_breaker_shield.failure_count).to eql(1)
139+
expect(circuit_breaker_shield.send(:state)).to be(CircuitBreaker::Shield::States::CLOSED)
140+
141+
expect {circuit_breaker_shield.protect { sleep(1.1) }}.to raise_error(CircuitBreaker::TimeoutError) # fail twice
142+
expect(circuit_breaker_shield.total_count).to eql(3)
143+
expect(circuit_breaker_shield.failure_count).to eql(2)
144+
expect(circuit_breaker_shield.send(:state)).to be(CircuitBreaker::Shield::States::OPEN)
145+
146+
Timecop.freeze(Time.now + retry_timeout) do
147+
circuit_breaker_shield.protect { sleep(0.1) } # succeed once
148+
expect(circuit_breaker_shield.total_count).to eql(1)
149+
expect(circuit_breaker_shield.failure_count).to eql(0)
150+
expect(circuit_breaker_shield.send(:state)).to be(CircuitBreaker::Shield::States::CLOSED)
151+
152+
expect {circuit_breaker_shield.protect { sleep(1.1) }}.to raise_error(CircuitBreaker::TimeoutError) # fail once
153+
expect(circuit_breaker_shield.total_count).to eql(2)
154+
expect(circuit_breaker_shield.failure_count).to eql(1)
155+
expect(circuit_breaker_shield.send(:state)).to be(CircuitBreaker::Shield::States::CLOSED)
156+
end
157+
end
158+
end
121159
end
122160

123161
context '#protect' do

0 commit comments

Comments
 (0)