circuit_breaker-ruby 0.1.2 → 0.1.3

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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- N2RmNmY0NDYzZWUwZjBjODdhMTU1MjQzNDJiYjg4NGNmMGUzOWE2Yw==
5
- data.tar.gz: !binary |-
6
- MDA3MGE3MzM5YTgxYjJjZGIzMjdlNTRjOTQyNThlYmNlZGVhOThlMg==
2
+ SHA256:
3
+ metadata.gz: bdadf98a4b61b6a675d57f0dfea304f540d1a773d01fa606e12362691864a8a0
4
+ data.tar.gz: 7631a64f7462b834b955a2ba7229044e71a0bbb31d74a9a126cb9f918cdc4d73
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- YjEzNzJhODgzNjNjOTY4ZDBiOGUxNTM2ODUxYjJlNzI3YzNmNjY4NzQ3YTEw
10
- ZDRmYzYxMmZmNzUxNjA4NDZiOWFlYzI2M2U4MDJlYmRjMTJlYjZiYzY0ZjA1
11
- MWZmODY5MTI3ZTExMGZiZGVmYWIzZTQxZTUxODJmNjFmMzhlYmI=
12
- data.tar.gz: !binary |-
13
- ZTJhOGZjYTQ0ZGYxNzA1MTNkYzI3NTgzYzRjZmM2ZTI2NDdkYWFhZmZjZWFm
14
- ZjgwNThkZjUwOTdiOGRlODc2ZDVmOTI3N2RhNDk0NWQ5NzUyZDk0MWU0NjQ0
15
- NzQ4NDQxZDU5MGRkODVjYmFhNTkyODVjMWY3YjE0MDNlZjc5YjY=
6
+ metadata.gz: 28c3f17a5116eb33859a215fcfc2f5e6b077bb0b55d7a10554c52c9ab8407f320ad2fadf0a5203a63355bb1d4b5d473918fd130b7bec6ab8907c691f77aca560
7
+ data.tar.gz: 46ef9b8b30c57d7591d9f195e40572527acdb2e9dec5d3140eafe44ad92893c88395260fa9d00b87ae527398b6de2a8b4caa9ae1703b728e49241f8fc89eb0f5
@@ -0,0 +1 @@
1
+ ruby 2.4
@@ -0,0 +1,9 @@
1
+ # Changelog for v0.1.x
2
+
3
+ ## v0.1.3 (2019-03-12)
4
+
5
+ ### Bug fixes
6
+
7
+ * Total count is not getting incremented incase of exception is raised
8
+ * total_count reset to 0 when state is in half_open
9
+ * reached_failure_threshold? returns only true once it is set to true
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
- # A sample Gemfile
2
1
  source 'https://rubygems.org'
3
2
 
4
3
  gemspec
@@ -7,4 +6,5 @@ gem 'rake'
7
6
 
8
7
  group :test do
9
8
  gem 'rspec', '>= 3.1'
9
+ gem 'timecop', '~> 0.8.1'
10
10
  end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- circuit_breaker-ruby (0.1.1)
4
+ circuit_breaker-ruby (0.1.3)
5
5
  rake (>= 10)
6
6
  rspec (>= 3)
7
7
 
@@ -23,6 +23,7 @@ GEM
23
23
  diff-lcs (>= 1.2.0, < 2.0)
24
24
  rspec-support (~> 3.5.0)
25
25
  rspec-support (3.5.0)
26
+ timecop (0.8.1)
26
27
 
27
28
  PLATFORMS
28
29
  ruby
@@ -31,6 +32,7 @@ DEPENDENCIES
31
32
  circuit_breaker-ruby!
32
33
  rake
33
34
  rspec (>= 3.1)
35
+ timecop (~> 0.8.1)
34
36
 
35
37
  BUNDLED WITH
36
- 1.11.2
38
+ 1.16.1
@@ -1,7 +1,19 @@
1
- Copyright (c) 2016-2018 Vasu Adari
1
+ Copyright (c) 2016-2020 Scripbox.
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sub-license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
4
9
 
5
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
6
12
 
7
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md CHANGED
@@ -1,38 +1,39 @@
1
1
  ## Circuit Breaker
2
2
  [![Gem Version](https://img.shields.io/gem/v/circuit_breaker-ruby.svg?style=flat)](http://rubygems.org/gems/circuit_breaker-ruby)
3
+ [![Gem Downloads](https://img.shields.io/gem/dt/circuit_breaker-ruby.svg?style=flat)](http://rubygems.org/gems/circuit_breaker-ruby)
3
4
  [![Build Status](https://travis-ci.org/scripbox/circuit_breaker-ruby.svg?style=flat&branch=master)](https://travis-ci.org/scripbox/circuit_breaker-ruby)
4
5
 
5
6
  A circuit breaker which terminates a connection or block of code from executing when it reaches the failure threshold and a percentage. Also it gets reset when a connection succeeds. It also keeps monitoring if connections are working again by checking if it has attained a time to retry.
6
7
 
7
8
  ## Installation
8
9
 
9
- ```
10
+ ```ruby
10
11
  gem install circuit_breaker-ruby
11
12
  ```
12
13
 
13
14
  ## Usage
14
15
 
15
- ```
16
+ ```ruby
16
17
  circuit_breaker = CircuitBreaker::Shield.new(
17
18
  invocation_timeout: 1,
18
19
  failure_threshold: 2,
19
- failure_threshold_percentage, 0.2,
20
+ failure_threshold_percentage: 0.2,
20
21
  retry_timeout: 10
21
22
  )
22
23
  circuit_breaker.protect { sleep(10) }
23
24
  ```
24
25
 
25
- ### Running the specs
26
+ ## Running the specs
26
27
 
27
- ```
28
+ ```ruby
28
29
  bundle exec rspec spec
29
30
  ```
30
31
 
31
- ### Configuration
32
+ ## Configuration
32
33
 
33
34
  Add the following configuration to config/initializers/circuit_breaker.rb. These are the default values.
34
35
 
35
- ```
36
+ ```ruby
36
37
  CircuitBreaker.configure do |cb|
37
38
  cb.failure_threshold = 10
38
39
  cb.failure_threshold_percentage = 0.5
@@ -41,10 +42,16 @@ Add the following configuration to config/initializers/circuit_breaker.rb. These
41
42
  end
42
43
  ```
43
44
 
44
- ### Contributing
45
+ ## Contributing
45
46
 
46
47
  If you have any issues with circuit_breaker-ruby,
47
48
  or feature requests,
48
49
  please [add an issue](https://github.com/scripbox/circuit_breaker-ruby/issues) on GitHub
49
50
  or fork the project and send a pull request.
50
51
  Please include passing specs with all pull requests.
52
+
53
+ ## Copyright and License
54
+
55
+ Copyright (c) 2019, Scripbox.
56
+
57
+ circuit_breaker-ruby source code is licensed under the [MIT License](MIT-LICENSE).
@@ -5,6 +5,7 @@ require 'timeout'
5
5
 
6
6
  module CircuitBreaker
7
7
  class Open < StandardError; end
8
+ class TimeoutError < StandardError; end
8
9
 
9
10
  class << self
10
11
  def config
@@ -20,5 +20,11 @@ module CircuitBreaker
20
20
  self.invocation_timeout = INVOCATION_TIMEOUT
21
21
  self.retry_timeout = RETRY_TIMEOUT
22
22
  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
23
29
  end
24
30
  end
@@ -6,16 +6,21 @@ module CircuitBreaker
6
6
  HALF_OPEN = :half_open
7
7
  end
8
8
 
9
- attr_reader :invocation_timeout, :retry_timeout, :failure_threshold, :failure_threshold_percentage,
10
- :total_count, :failure_count
9
+ attr_reader :invocation_timeout,
10
+ :retry_timeout,
11
+ :failure_threshold,
12
+ :failure_threshold_percentage,
13
+ :total_count,
14
+ :failure_count
11
15
 
12
16
  def initialize(**options)
13
17
  @failure_count = 0
14
18
  @total_count = 0
15
- @failure_threshold = options[:failure_threshold] || config.failure_threshold
16
- @failure_threshold_percentage = options[:failure_threshold_percentage] || config.failure_threshold_percentage
17
- @invocation_timeout = options[:invocation_timeout] || config.invocation_timeout
18
- @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)
23
+ @callback = options[:callback]
19
24
  end
20
25
 
21
26
  def config
@@ -23,9 +28,7 @@ module CircuitBreaker
23
28
  end
24
29
 
25
30
  def update_config!(options)
26
- (CircuitBreaker::Config::UPDATABLE & options.keys).each do |variable|
27
- instance_variable_set("@#{variable}", options[variable])
28
- end
31
+ CircuitBreaker::Config.update(self, options)
29
32
  end
30
33
 
31
34
  def protect(options = {}, &block)
@@ -33,7 +36,7 @@ module CircuitBreaker
33
36
 
34
37
  case prev_state = state
35
38
  when States::CLOSED, States::HALF_OPEN
36
- connect(&block).tap { update_total_count(prev_state) }
39
+ connect(&block)
37
40
  when States::OPEN
38
41
  raise CircuitBreaker::Open
39
42
  end
@@ -42,10 +45,9 @@ module CircuitBreaker
42
45
  private
43
46
 
44
47
  def state
45
- case
46
- when reached_failure_threshold? && reached_retry_timeout?
48
+ if reached_failure_threshold? && reached_failure_threshold_percentage? && reached_retry_timeout?
47
49
  States::HALF_OPEN
48
- when reached_failure_threshold?
50
+ elsif reached_failure_threshold? && reached_failure_threshold_percentage?
49
51
  States::OPEN
50
52
  else
51
53
  States::CLOSED
@@ -53,9 +55,11 @@ module CircuitBreaker
53
55
  end
54
56
 
55
57
  def reached_failure_threshold?
56
- (failure_count >= failure_threshold) &&
57
- (total_count != 0 &&
58
- (failure_count.to_f / total_count.to_f) >= failure_threshold_percentage)
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)
59
63
  end
60
64
 
61
65
  def reached_retry_timeout?
@@ -64,34 +68,42 @@ module CircuitBreaker
64
68
 
65
69
  def reset
66
70
  @failure_count = 0
71
+ @total_count = 0
67
72
  @state = States::CLOSED
68
73
  end
69
74
 
70
75
  def connect(&block)
71
76
  begin
72
77
  result = nil
73
- Timeout::timeout(invocation_timeout) do
78
+ ::Timeout::timeout(invocation_timeout) do
79
+ start_time = Time.now
74
80
  result = block.call
81
+ duration = Time.now - start_time
82
+ invoke_callback(result, duration: duration)
75
83
  reset
76
84
  end
77
- rescue Timeout::Error => e
85
+ rescue ::Timeout::Error => e
78
86
  record_failure
87
+ invoke_callback
88
+ raise CircuitBreaker::TimeoutError
89
+ ensure
90
+ increment_total_count
79
91
  end
80
92
 
81
93
  result
82
94
  end
83
95
 
84
- def update_total_count(state)
85
- if state == States::HALF_OPEN
86
- @total_count = 0
87
- else
88
- @total_count += 1
89
- end
96
+ def increment_total_count
97
+ @total_count += 1
90
98
  end
91
99
 
92
100
  def record_failure
93
101
  @last_failure_time = Time.now
94
102
  @failure_count += 1
95
103
  end
104
+
105
+ def invoke_callback(result = nil, options = {})
106
+ @callback.respond_to?(:call) && @callback.call(result, options)
107
+ end
96
108
  end
97
109
  end
@@ -1,3 +1,3 @@
1
1
  module CircuitBreaker
2
- VERSION = '0.1.2'
2
+ VERSION = '0.1.3'
3
3
  end
@@ -1,58 +1,202 @@
1
1
  require 'spec_helper'
2
2
  require 'circuit_breaker-ruby'
3
-
4
- CircuitBreaker.configure do |cb|
5
- cb.invocation_timeout = 1
6
- cb.retry_timeout = 1
7
- cb.failure_threshold = 1
8
- end
3
+ require 'timecop'
9
4
 
10
5
  describe CircuitBreaker::Shield do
11
- let(:circuit_breaker_shield) { CircuitBreaker::Shield.new }
12
-
13
- it 'goes to closed state' do
14
- circuit_breaker_shield.protect { sleep(0.1) }
6
+ context 'with no failures' do
7
+ it 'goes to closed state' do
8
+ circuit_breaker_shield = CircuitBreaker::Shield.new(
9
+ invocation_timeout: 1,
10
+ retry_timeout: 1,
11
+ failure_threshold: 1
12
+ )
15
13
 
16
- expect(circuit_breaker_shield.send :state).to be(CircuitBreaker::Shield::States::CLOSED)
14
+ circuit_breaker_shield.protect { sleep(0.1) } # succeed once
15
+ expect(circuit_breaker_shield.total_count).to eql(1)
16
+ expect(circuit_breaker_shield.failure_count).to eql(0)
17
+ expect(circuit_breaker_shield.send(:state)).to be(CircuitBreaker::Shield::States::CLOSED)
18
+ end
17
19
  end
18
20
 
19
- it 'goes to open state' do
20
- no_of_tries = circuit_breaker_shield.failure_threshold * 2
21
- no_of_failures = no_of_tries * circuit_breaker_shield.config.failure_threshold_percentage
22
- no_of_success = no_of_tries - no_of_failures
23
- no_of_success.to_i.times { circuit_breaker_shield.protect { sleep(0.1) } }
24
- no_of_failures.to_i.times { circuit_breaker_shield.protect { sleep(2) } }
21
+ context 'with failures less than threshold' do
22
+ it 'goes to closed state' do
23
+ retry_timeout = 1
25
24
 
26
- expect(circuit_breaker_shield.send :state).to be(CircuitBreaker::Shield::States::OPEN)
27
- end
25
+ circuit_breaker_shield = CircuitBreaker::Shield.new(
26
+ invocation_timeout: 1,
27
+ retry_timeout: retry_timeout,
28
+ failure_threshold: 2
29
+ )
28
30
 
29
- it 'goes to half open state' do
30
- no_of_tries = circuit_breaker_shield.failure_threshold * 2
31
- no_of_failures = no_of_tries * circuit_breaker_shield.config.failure_threshold_percentage
32
- no_of_success = no_of_tries - no_of_failures
33
- no_of_success.to_i.times { circuit_breaker_shield.protect { sleep(0.1) } }
34
- no_of_failures.to_i.times { circuit_breaker_shield.protect { sleep(2) } }
35
- sleep(1)
31
+ circuit_breaker_shield.protect { sleep(0.1) } # succeed once
32
+ expect(circuit_breaker_shield.total_count).to eql(1)
33
+ expect(circuit_breaker_shield.failure_count).to eql(0)
36
34
 
37
- expect(circuit_breaker_shield.send :state).to be(CircuitBreaker::Shield::States::HALF_OPEN)
35
+ expect {circuit_breaker_shield.protect { sleep(1.1) }}.to raise_error(CircuitBreaker::TimeoutError) # fail once
36
+ expect(circuit_breaker_shield.total_count).to eql(2)
37
+ expect(circuit_breaker_shield.failure_count).to eql(1)
38
+ expect(circuit_breaker_shield.send(:state)).to be(CircuitBreaker::Shield::States::CLOSED)
39
+ end
38
40
  end
39
41
 
40
- it 'raises CircuitBreaker::Shield::Open' do
41
- no_of_tries = circuit_breaker_shield.failure_threshold * 2
42
- no_of_failures = no_of_tries * circuit_breaker_shield.config.failure_threshold_percentage
43
- no_of_success = no_of_tries - no_of_failures
44
- no_of_success.to_i.times { circuit_breaker_shield.protect { sleep(0.1) } }
42
+ context 'with failures more than threshold' do
43
+ context 'within retry_timeout' do
44
+ it 'goes to open state' do
45
+ circuit_breaker_shield = CircuitBreaker::Shield.new(
46
+ invocation_timeout: 1,
47
+ retry_timeout: 1,
48
+ failure_threshold: 1
49
+ )
50
+
51
+ circuit_breaker_shield.protect { sleep(0.1) } # succeed once
52
+ expect(circuit_breaker_shield.total_count).to eql(1)
53
+ expect(circuit_breaker_shield.failure_count).to eql(0)
54
+
55
+ expect { circuit_breaker_shield.protect { sleep(1.1) } }.to raise_error(CircuitBreaker::TimeoutError) # fail once
56
+ expect(circuit_breaker_shield.total_count).to eql(2)
57
+ expect(circuit_breaker_shield.failure_count).to eql(1)
58
+
59
+ expect { circuit_breaker_shield.protect { sleep(1.1) } }.to raise_error(CircuitBreaker::Open) # fail twice
60
+ expect(circuit_breaker_shield.total_count).to eql(2)
61
+ expect(circuit_breaker_shield.failure_count).to eql(1)
62
+
63
+ expect(circuit_breaker_shield.send(:state)).to be(CircuitBreaker::Shield::States::OPEN)
64
+ end
65
+ end
66
+
67
+ context 'after retry_timeout' do
68
+ it 'goes to half open state' do
69
+ retry_timeout = 1
70
+
71
+ circuit_breaker_shield = CircuitBreaker::Shield.new(
72
+ invocation_timeout: 1,
73
+ retry_timeout: retry_timeout,
74
+ failure_threshold: 1
75
+ )
76
+
77
+ circuit_breaker_shield.protect { sleep(0.1) } # succeed once
78
+ expect(circuit_breaker_shield.total_count).to eql(1)
79
+ expect(circuit_breaker_shield.failure_count).to eql(0)
80
+
81
+ expect {circuit_breaker_shield.protect { sleep(1.1) }}.to raise_error(CircuitBreaker::TimeoutError) # fail once
82
+ expect(circuit_breaker_shield.total_count).to eql(2)
83
+ expect(circuit_breaker_shield.failure_count).to eql(1)
84
+
85
+
86
+ Timecop.freeze(Time.now + retry_timeout) do
87
+ expect(circuit_breaker_shield.send(:state)).to be(CircuitBreaker::Shield::States::HALF_OPEN)
88
+ end
89
+ end
90
+ end
91
+
92
+ context 'when circuit is in half-open state' do
93
+ it 'goes to open state' do
94
+ retry_timeout = 1
95
+
96
+ circuit_breaker_shield = CircuitBreaker::Shield.new(
97
+ invocation_timeout: 1,
98
+ retry_timeout: retry_timeout,
99
+ failure_threshold: 1
100
+ )
101
+
102
+ circuit_breaker_shield.protect { sleep(0.1) } # succeed once
103
+ expect(circuit_breaker_shield.total_count).to eql(1)
104
+ expect(circuit_breaker_shield.failure_count).to eql(0)
45
105
 
46
- expect { (no_of_failures.to_i + 1).times { circuit_breaker_shield.protect { sleep(2) } } }.to(
47
- raise_error(CircuitBreaker::Open)
48
- )
106
+ expect {circuit_breaker_shield.protect { sleep(1.1) }}.to raise_error(CircuitBreaker::TimeoutError) # fail once
107
+ expect(circuit_breaker_shield.total_count).to eql(2)
108
+ expect(circuit_breaker_shield.failure_count).to eql(1)
109
+
110
+ Timecop.freeze(Time.now + retry_timeout) do
111
+ expect(circuit_breaker_shield.send(:state)).to be(CircuitBreaker::Shield::States::HALF_OPEN)
112
+
113
+ expect {circuit_breaker_shield.protect { sleep(1.1) }}.to raise_error(CircuitBreaker::TimeoutError)
114
+ expect(circuit_breaker_shield.total_count).to eql(3)
115
+ expect(circuit_breaker_shield.failure_count).to eql(2)
116
+
117
+ expect(circuit_breaker_shield.send(:state)).to be(CircuitBreaker::Shield::States::OPEN)
118
+ end
119
+ end
120
+ 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
49
159
  end
50
160
 
51
161
  context '#protect' do
52
162
  it 'update invocation_timeout in config' do
163
+ circuit_breaker_shield = CircuitBreaker::Shield.new(
164
+ invocation_timeout: 1,
165
+ retry_timeout: 1,
166
+ failure_threshold: 1
167
+ )
168
+
53
169
  circuit_breaker_shield.protect(invocation_timeout: 20) {}
54
170
 
55
171
  expect(circuit_breaker_shield.invocation_timeout).to be(20)
56
172
  end
173
+
174
+ it 'invokes callback on success' do
175
+ callback = proc { 'Success' }
176
+ circuit_breaker_shield = CircuitBreaker::Shield.new(
177
+ invocation_timeout: 1,
178
+ retry_timeout: 1,
179
+ failure_threshold: 1,
180
+ callback: callback
181
+ )
182
+
183
+ expect(callback).to receive(:call).and_return('Success')
184
+
185
+ circuit_breaker_shield.protect { { response: 'Dummy response' } }
186
+ end
187
+
188
+ context 'with timeout error' do
189
+ it 're-raises timeout error' do
190
+ circuit_breaker_shield = CircuitBreaker::Shield.new(
191
+ invocation_timeout: 1,
192
+ retry_timeout: 1,
193
+ failure_threshold: 1
194
+ )
195
+
196
+ invocation_block = proc{ raise Timeout::Error }
197
+
198
+ expect {circuit_breaker_shield.protect(&invocation_block)}.to raise_error(CircuitBreaker::TimeoutError)
199
+ end
200
+ end
57
201
  end
58
202
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: circuit_breaker-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scripbox
@@ -14,28 +14,28 @@ dependencies:
14
14
  name: rspec
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '3'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ! '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '10'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10'
41
41
  description: Self-resetting breaker retries the protected call after a suitable interval,
@@ -45,9 +45,11 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
- - .gitignore
49
- - .ruby-version
50
- - .travis.yml
48
+ - ".gitignore"
49
+ - ".ruby-version"
50
+ - ".tool-versions"
51
+ - ".travis.yml"
52
+ - CHANGELOG.md
51
53
  - Gemfile
52
54
  - Gemfile.lock
53
55
  - MIT-LICENSE
@@ -70,20 +72,19 @@ require_paths:
70
72
  - lib
71
73
  required_ruby_version: !ruby/object:Gem::Requirement
72
74
  requirements:
73
- - - ! '>='
75
+ - - ">="
74
76
  - !ruby/object:Gem::Version
75
77
  version: 2.1.8
76
78
  required_rubygems_version: !ruby/object:Gem::Requirement
77
79
  requirements:
78
- - - ! '>='
80
+ - - ">="
79
81
  - !ruby/object:Gem::Version
80
82
  version: '0'
81
83
  requirements: []
82
- rubyforge_project:
83
- rubygems_version: 2.4.5
84
+ rubygems_version: 3.0.3
84
85
  signing_key:
85
86
  specification_version: 4
86
87
  summary: Circuit breaker for ruby
87
88
  test_files:
88
- - spec/circuit_breaker/shield_spec.rb
89
89
  - spec/spec_helper.rb
90
+ - spec/circuit_breaker/shield_spec.rb