circuitbox 2.0.0.pre3 → 2.0.0.pre5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 91bcf88d24630e489540fd2c149ce6b468ecdd7e
4
- data.tar.gz: 8576e094971fb74bb390b2a1508fd6c330644915
2
+ SHA256:
3
+ metadata.gz: 541c5e674d62681f13021254d4304c4903e2812915c59b74fcb419468f608c9d
4
+ data.tar.gz: 97fdbeedc5ac6aa8aa249ca92a1202175e8bbd3f777c4c5211583130aecf5b0d
5
5
  SHA512:
6
- metadata.gz: 7e2246d5b01dddaa5fb1e3637f5f66a1bb1a9ffcbf20d8bec5d28d787328f748b7dc1818a72662ede92aab94eae32f1608eacc42697cd0fbe72f5ef16f7c42c9
7
- data.tar.gz: 9b2022385d22e3e6fc31a1be8028fa047a49fb59f09779c77bdae53712ab8cda482513f6dd7909a1ee3b04ca4a6fdcb1d9a110cc57febb1b5e712b19f284b4a0
6
+ metadata.gz: 2466098850251fab23dd6be74a292150ba8eda1e27c3ef1fefcc79aab36dedc170daf4292ae3e9d20ea49468bb487855ce4d3bd77aa7ef1ec8b3ba18bd52d1c6
7
+ data.tar.gz: a62d7c7a283ce4adcec0c73b69db7de8ca982f493369a7ac6534bfc9641de2bc909b8294bb634913b64c1f454861488595e0f103672fa6cc448c7f6beaa8c170
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Circuitbox
2
2
 
3
- [![Build Status](https://travis-ci.org/yammer/circuitbox.svg?branch=master)](https://travis-ci.org/yammer/circuitbox) [![Gem Version](https://badge.fury.io/rb/circuitbox.svg)](https://badge.fury.io/rb/circuitbox)
3
+ ![Tests](https://github.com/yammer/circuitbox/workflows/Tests/badge.svg) [![Gem Version](https://badge.fury.io/rb/circuitbox.svg)](https://badge.fury.io/rb/circuitbox)
4
4
 
5
5
  Circuitbox is a Ruby circuit breaker gem. It protects your application from failures of its service dependencies. It wraps calls to external services and monitors for failures in one minute intervals. Once more than 10 requests have been made with a 50% failure rate, Circuitbox stops sending requests to that failing service for one minute. This helps your application gracefully degrade.
6
6
  Resources about the circuit breaker pattern:
@@ -25,7 +25,7 @@ class ExampleServiceClient
25
25
  end
26
26
 
27
27
  def http_get
28
- circuit.run(circuitbox_exceptions: false) do
28
+ circuit.run(exception: false) do
29
29
  Zephyr.new("http://example.com").get(200, 1000, "/api/messages")
30
30
  end
31
31
  end
@@ -43,7 +43,7 @@ Using the `run` method will throw an exception when the circuit is open or the u
43
43
  ```
44
44
 
45
45
  ## Global Configuration
46
- Circuitbox has defaults for circuit_store, notifier, timer, and logger.
46
+ Circuitbox has defaults for circuit_store and notifier.
47
47
  This can be configured through ```Circuitbox.configure```.
48
48
  The circuit cache used by ```Circuitbox.circuit``` will be cleared after running ```Circuitbox.configure```.
49
49
  This means when accessing the circuit through ```Circuitbox.circuit``` any custom configuration options should always be given.
@@ -55,8 +55,6 @@ will need to be recreated to pick up the new defaults.
55
55
  Circuitbox.configure do |config|
56
56
  config.default_circuit_store = Circuitbox::MemoryStore.new
57
57
  config.default_notifier = Circuitbox::Notifier::Null.new
58
- config.default_timer = Circuitbox::Timer::Simple.new
59
- config.default_logger = Rails.logger
60
58
  end
61
59
  ```
62
60
 
@@ -82,21 +80,11 @@ class ExampleServiceClient
82
80
  # the store you want to use to save the circuit state so it can be
83
81
  # tracked, this needs to be Moneta compatible, and support increment
84
82
  # this overrides what is set in the global configuration
85
- cache: Circuitbox::MemoryStore.new,
83
+ circuit_store: Circuitbox::MemoryStore.new,
86
84
 
87
85
  # exceeding this rate will open the circuit (checked on failures)
88
86
  error_threshold: 50,
89
87
 
90
- # Logger to use
91
- # This overrides what is set in the global configuration
92
- logger: Logger.new(STDOUT),
93
-
94
- # Customized Timer object
95
- # Use NullTimer if you don't want to time circuit execution
96
- # Use MonotonicTimer to avoid bad time metrics on system time resync
97
- # This overrides what is set in the global configuration
98
- execution_timer: SimpleTimer,
99
-
100
88
  # Customized notifier
101
89
  # overrides the default
102
90
  # this overrides what is set in the global configuration
@@ -115,7 +103,7 @@ Circuitbox.circuit(:yammer, {
115
103
  })
116
104
  ```
117
105
 
118
- ## Circuit Store (:cache)
106
+ ## Circuit Store
119
107
 
120
108
  Holds all the relevant data to trip the circuit if a given number of requests
121
109
  fail in a specified period of time. Circuitbox also supports
@@ -132,60 +120,59 @@ some pre-requisits need to be satisfied first:
132
120
 
133
121
  ## Notifications
134
122
 
135
- circuitbox use ActiveSupport Notifications.
123
+ Circuitbox has two built in notifiers, null and active support.
124
+ The active support notifier is used if `ActiveSupport::Notifications` is defined when circuitbox is loaded.
125
+ If `ActiveSupport::Notifications` is not defined the null notifier is used.
126
+ The null notifier does not send notifications anywhere.
127
+
128
+ The default notifier can be changed to use a specific built in notifier or a custom notifier when [configuring circuitbox](#global-configuration).
136
129
 
130
+ ### ActiveSupport
137
131
  Usage example:
138
132
 
139
- **Log on circuit open/close:**
133
+ **Circuit open/close:**
140
134
 
141
135
  ```ruby
142
- class CircuitOpenException < StandardError ; end
143
-
144
- ActiveSupport::Notifications.subscribe('circuit_open') do |name, start, finish, id, payload|
136
+ ActiveSupport::Notifications.subscribe('open.circuitbox') do |_name, _start, _finish, _id, payload|
145
137
  circuit_name = payload[:circuit]
146
138
  Rails.logger.warn("Open circuit for: #{circuit_name}")
147
139
  end
148
- ActiveSupport::Notifications.subscribe('circuit_close') do |name, start, finish, id, payload|
140
+ ActiveSupport::Notifications.subscribe('close.circuitbox') do |_name, _start, _finish, _id, payload|
149
141
  circuit_name = payload[:circuit]
150
142
  Rails.logger.info("Close circuit for: #{circuit_name}")
151
143
  end
152
144
  ```
153
145
 
154
- **generate metrics:**
146
+ **Circuit run:**
155
147
 
156
148
  ```ruby
157
- $statsd = Statsd.new 'localhost', 9125
158
-
159
- ActiveSupport::Notifications.subscribe('circuit_gauge') do |name, start, finish, id, payload|
160
- circuit_name = payload[:circuit]
161
- gauge = payload[:gauge]
162
- value = payload[:value]
163
- metrics_key = "circuitbox.circuit.#{circuit_name}.#{gauge}"
164
-
165
- $statsd.gauge(metrics_key, value)
149
+ ActiveSupport::Notifications.subscribe('run.circuitbox') do |*args|
150
+ event = ActiveSupport::Notifications::Event.new(*args)
151
+ circuit_name = event.payload[:circuit_name]
152
+
153
+ Rails.logger.info("Circuit: #{circuit_name} Runtime: #{event.duration}")
166
154
  end
167
155
  ```
168
156
 
169
- `payload[:gauge]` can be:
170
-
171
- - `execution_time` # execution time will only be notified when circuit is closed and block is successfully executed.
172
-
173
- **warnings:**
174
- in case of misconfiguration, circuitbox will fire a circuitbox_warning
157
+ **Circuit Warnings:**
158
+ In case of misconfiguration, circuitbox will fire a `warning.circuitbox`
175
159
  notification.
176
160
 
177
161
  ```ruby
178
- ActiveSupport::Notifications.subscribe('circuit_warning') do |name, start, finish, id, payload|
162
+ ActiveSupport::Notifications.subscribe('warning.circuitbox') do |_name, _start, _finish, _id, payload|
179
163
  circuit_name = payload[:circuit]
180
164
  warning = payload[:message]
181
- Rails.logger.warning("#{circuit_name} - #{warning}")
165
+ Rails.logger.warning("Circuit warning for: #{circuit_name} Message: #{warning}")
182
166
  end
183
167
 
184
168
  ```
185
169
 
186
170
  ## Faraday
187
171
 
188
- Circuitbox ships with [Faraday HTTP client](https://github.com/lostisland/faraday) middleware.
172
+ Circuitbox ships with a [Faraday HTTP client](https://github.com/lostisland/faraday) middleware.
173
+ The versions of faraday the middleware has been tested against is `>= 0.17` through `~> 2.0`.
174
+ The middleware does not support parallel requests through a connections `in_parallel` method.
175
+
189
176
 
190
177
  ```ruby
191
178
  require 'faraday'
@@ -241,71 +228,6 @@ c.use Circuitbox::FaradayMiddleware, circuit_breaker_options: {}
241
228
  c.use Circuitbox::FaradayMiddleware, open_circuit: lambda { |response| response.status >= 500 }
242
229
  ```
243
230
 
244
- ## CHANGELOG
245
- ### v1.1.0
246
- - ruby 2.2 support [#58](https://github.com/yammer/circuitbox/pull/58)
247
- - configurable logger [#58](https://github.com/yammer/circuitbox/pull/58)
248
-
249
- ### v1.0.3
250
- - fix timeout issue for default configuration, as default `:Memory` adapter does
251
- not natively support expires, we need to actually load it on demand.
252
- - fix memoization of `circuit_breaker_options` not actually doing memoization in
253
- `excon` and `faraday` middleware.
254
-
255
- ### v1.0.2
256
- - Fix timeout issue [#51](https://github.com/yammer/circuitbox/issues/51)
257
- [sebastian-juliu](https://github.com/sebastian-julius)
258
-
259
- ### v1.0.1
260
- - Fix Rails integration, as version 1.0.0 removed the rails tasks integration, but missed
261
- removing the related railtie.
262
-
263
- ### v1.0.0
264
- - support for cross process circuitbreakers by swapping the circuitbreaker store for a
265
- `Moneta` supported key value store.
266
- - Change `FaradayMiddleware` default behaviour to not open on `4xx` errors but just on `5xx`
267
- server errors and connection errors
268
- - Remove stat store, which was largely unused
269
-
270
- ### v0.11.0
271
- - fix URI require missing (https://github.com/yammer/circuitbox/pull/42 @gottfrois)
272
- - configurable circuitbox store backend via Moneta supporting multi process circuits
273
-
274
- ### v0.10.4
275
- - Issue #39, keep the original backtrace for the wrapped exception around when
276
- re-raising a Circuitbox::Error
277
-
278
- ### v0.10.3
279
- - Circuitbox::ServiceFailureError wraps the original exception that was raised.
280
- The behaviour for to_s wasn't exposing this information and was returning the
281
- name of class "Circuitbox::ServiceFailureError". Change the behaviour for to_s
282
- to indicate this exception is a wrapper around the original exception.
283
- [sherrry](https://github.com/sherrry)
284
-
285
- ### v0.10.2
286
- - Faraday middleware passes two arguments to the `default_value` callback, not
287
- just one. First argument is still the error response from Faraday if there is
288
- one. Second argument is the exception that caused the call to fail if it
289
- failed before Faraday returned a response. Old behaviour is preserved if you
290
- pass a lambda that takes just one argument, but this is deprecated and will be
291
- removed in the next version of Circuitbox.
292
- [dwaller](https://github.com/dwaller)
293
-
294
- ### v0.10.1
295
- - [Documentation fix](https://github.com/yammer/circuitbox/pull/29) [chiefcll](https://github.com/chiefcll)
296
- - [Faraday middleware fix](https://github.com/yammer/circuitbox/pull/30) [chiefcll](https://github.com/chiefcll)
297
-
298
- ### v0.10
299
- - configuration option for faraday middleware for what should be considered to open the circuit [enrico-scalavio](https://github.com/enrico-scalavino)
300
- - fix for issue 16, support of in_parallel requests in faraday middleware which were opening the circuit.
301
- - deprecate the __run_option__ `:storage_key`
302
-
303
- ### v0.9
304
- - add `run!` method to raise exception on circuit open and service
305
-
306
- ### v0.8
307
- - Everything prior to keeping the change log
308
-
309
231
  ## Installation
310
232
 
311
233
  Add this line to your application's Gemfile:
@@ -1,36 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'circuit_breaker/logger_messages'
3
+ require_relative 'time_helper/monotonic'
4
+ require_relative 'time_helper/real'
4
5
 
5
6
  class Circuitbox
6
7
  class CircuitBreaker
7
- include LoggerMessages
8
-
9
8
  attr_reader :service, :circuit_options, :exceptions,
10
- :logger, :circuit_store, :notifier, :time_class, :execution_timer
9
+ :circuit_store, :notifier, :time_class
11
10
 
12
11
  DEFAULTS = {
13
- sleep_window: 90,
12
+ sleep_window: 90,
14
13
  volume_threshold: 5,
15
- error_threshold: 50,
16
- time_window: 60
14
+ error_threshold: 50,
15
+ time_window: 60
17
16
  }.freeze
18
17
 
18
+ # Initialize a CircuitBreaker
19
19
  #
20
- # Configuration options
21
- #
22
- # `sleep_window` - seconds to sleep the circuit
23
- # `volume_threshold` - number of requests before error rate calculation occurs
24
- # `error_threshold` - percentage of failed requests needed to trip circuit
25
- # `exceptions` - exceptions that count as failures
26
- # `time_window` - interval of time used to calculate error_rate (in seconds) - default is 60s
27
- # `logger` - Logger to use - defaults to Rails.logger if defined, otherwise STDOUT
20
+ # @param service [String, Symbol] Name of the circuit for notifications and metrics store
21
+ # @param options [Hash] Options to create the circuit with
22
+ # @option options [Integer] :time_window (60) Interval of time, in seconds, used to calculate the error_rate
23
+ # @option options [Integer, Proc] :sleep_window (90) Seconds for the circuit to stay open when tripped
24
+ # @option options [Integer, Proc] :volume_threshold (5) Number of requests before error rate is first calculated
25
+ # @option options [Integer, Proc] :error_threshold (50) Percentage of failed requests needed to trip the circuit
26
+ # @option options [Array] :exceptions The exceptions that should be monitored and counted as failures
27
+ # @option options [Circuitbox::MemoryStore, Moneta] :circuit_store (Circuitbox.default_circuit_store) Class to store circuit open/close statistics
28
+ # @option options [Object] :notifier (Circuitbox.default_notifier) Class notifications are sent to
28
29
  #
30
+ # @raise [ArgumentError] If the exceptions option is not an Array
29
31
  def initialize(service, options = {})
30
32
  @service = service.to_s
31
33
  @circuit_options = DEFAULTS.merge(options)
32
- @circuit_store = options.fetch(:cache) { Circuitbox.default_circuit_store }
33
- @execution_timer = options.fetch(:execution_timer) { Circuitbox.default_timer }
34
+ @circuit_store = options.fetch(:circuit_store) { Circuitbox.default_circuit_store }
34
35
  @notifier = options.fetch(:notifier) { Circuitbox.default_notifier }
35
36
 
36
37
  if @circuit_options[:timeout_seconds]
@@ -38,83 +39,126 @@ class Circuitbox
38
39
  'Check the upgrade guide at https://github.com/yammer/circuitbox')
39
40
  end
40
41
 
42
+ if @circuit_options[:cache]
43
+ warn('cache was changed to circuit_store in circuitbox 2.0. '\
44
+ 'Check the upgrade guide at https://github.com/yammer/circuitbox')
45
+ end
46
+
41
47
  @exceptions = options.fetch(:exceptions)
42
- raise ArgumentError, 'exceptions need to be an array' unless @exceptions.is_a?(Array)
48
+ raise ArgumentError.new('exceptions must be an array') unless @exceptions.is_a?(Array)
49
+
50
+ @time_class = options.fetch(:time_class) { default_time_klass }
43
51
 
44
- @logger = options.fetch(:logger) { Circuitbox.default_logger }
45
- @time_class = options.fetch(:time_class) { Time }
46
52
  @state_change_mutex = Mutex.new
53
+ @open_storage_key = "circuits:#{@service}:open"
54
+ @half_open_storage_key = "circuits:#{@service}:half_open"
47
55
  check_sleep_window
48
56
  end
49
57
 
50
58
  def option_value(name)
51
- value = circuit_options[name]
59
+ value = @circuit_options[name]
52
60
  value.is_a?(Proc) ? value.call : value
53
61
  end
54
62
 
55
- def run(circuitbox_exceptions: true)
63
+ # Run the circuit with the given block.
64
+ # If the circuit is closed or half_open the block will run.
65
+ # If the circuit is open the block will not be run.
66
+ #
67
+ # @param exception [Boolean] If exceptions should be raised when the circuit is open
68
+ # or when a watched exception is raised from the block
69
+ # @yield Block to run if circuit is not open
70
+ #
71
+ # @raise [Circuitbox::OpenCircuitError] If the circuit is open and exception is true
72
+ # @raise [Circuitbox::ServiceFailureError] If a tracked exception is raised from the block and exception is true
73
+ #
74
+ # @return [Object] The result from the block
75
+ # @return [Nil] If the circuit is open and exception is false
76
+ # In cases where an exception that circuitbox is watching is raised from either a notifier
77
+ # or from a custom circuit store nil can be returned even though the block ran successfully
78
+ def run(exception: true, &block)
56
79
  if open?
57
80
  skipped!
58
- raise Circuitbox::OpenCircuitError.new(service) if circuitbox_exceptions
81
+ raise Circuitbox::OpenCircuitError.new(@service) if exception
59
82
  else
60
- logger.debug(circuit_running_message)
61
-
62
83
  begin
63
- response = execution_timer.time(service, notifier, 'execution_time') do
64
- yield
65
- end
84
+ response = @notifier.notify_run(@service, &block)
66
85
 
67
86
  success!
68
- rescue *exceptions => exception
87
+ rescue *@exceptions => e
69
88
  # Other stores could raise an exception that circuitbox is asked to watch.
70
- # setting to nil keeps the same behavior as the previous defination of run.
89
+ # setting to nil keeps the same behavior as the previous definition of run.
71
90
  response = nil
72
91
  failure!
73
- raise Circuitbox::ServiceFailureError.new(service, exception) if circuitbox_exceptions
92
+ raise Circuitbox::ServiceFailureError.new(@service, e) if exception
74
93
  end
75
94
  end
76
95
 
77
96
  response
78
97
  end
79
98
 
99
+ # Check if the circuit is open
100
+ #
101
+ # @return [Boolean] True if circuit is open, False if closed
80
102
  def open?
81
- circuit_store.key?(open_storage_key)
103
+ @circuit_store.key?(@open_storage_key)
82
104
  end
83
105
 
106
+ # Calculates the current error rate of the circuit
107
+ #
108
+ # @return [Float] Error Rate
84
109
  def error_rate(failures = failure_count, success = success_count)
85
110
  all_count = failures + success
86
- return 0.0 unless all_count > 0
111
+ return 0.0 unless all_count.positive?
112
+
87
113
  (failures / all_count.to_f) * 100
88
114
  end
89
115
 
116
+ # Number of Failures the circuit has encountered in the current time window
117
+ #
118
+ # @return [Integer] Number of failures
90
119
  def failure_count
91
- circuit_store.load(stat_storage_key('failure'), raw: true).to_i
120
+ @circuit_store.load(stat_storage_key('failure'), raw: true).to_i
92
121
  end
93
122
 
123
+ # Number of successes the circuit has encountered in the current time window
124
+ #
125
+ # @return [Integer] Number of successes
94
126
  def success_count
95
- circuit_store.load(stat_storage_key('success'), raw: true).to_i
127
+ @circuit_store.load(stat_storage_key('success'), raw: true).to_i
96
128
  end
97
129
 
130
+ # If the circuit is open the key indicating that the circuit is open
131
+ # On the next call to run the circuit would run as if it were in the half open state
132
+ #
133
+ # This does not reset any of the circuit success/failure state so future failures
134
+ # in the same time window may cause the circuit to open sooner
98
135
  def try_close_next_time
99
- circuit_store.delete(open_storage_key)
136
+ @circuit_store.delete(@open_storage_key)
100
137
  end
101
138
 
102
- private
139
+ private
103
140
 
104
141
  def should_open?
105
- failures = failure_count
106
- successes = success_count
107
- rate = error_rate(failures, successes)
108
-
109
- passed_volume_threshold?(failures, successes) && passed_rate_threshold?(rate)
142
+ aligned_time = align_time_to_window
143
+
144
+ failures, successes = @circuit_store.values_at(stat_storage_key('failure', aligned_time),
145
+ stat_storage_key('success', aligned_time),
146
+ raw: true)
147
+ # Calling to_i is only needed for moneta stores which can return a string representation of an integer.
148
+ # While readability could increase by adding .map(&:to_i) to the end of the values_at call it's also slightly
149
+ # less performant when we only have two values to convert.
150
+ failures = failures.to_i
151
+ successes = successes.to_i
152
+
153
+ passed_volume_threshold?(failures, successes) && passed_rate_threshold?(failures, successes)
110
154
  end
111
155
 
112
156
  def passed_volume_threshold?(failures, successes)
113
157
  failures + successes >= option_value(:volume_threshold)
114
158
  end
115
159
 
116
- def passed_rate_threshold?(rate)
117
- rate >= option_value(:error_threshold)
160
+ def passed_rate_threshold?(failures, successes)
161
+ error_rate(failures, successes) >= option_value(:error_threshold)
118
162
  end
119
163
 
120
164
  def half_open_failure
@@ -124,7 +168,7 @@ class Circuitbox
124
168
  trip
125
169
  end
126
170
 
127
- # Running event and logger outside of the synchronize block to allow other threads
171
+ # Running event outside of the synchronize block to allow other threads
128
172
  # that may be waiting to become unblocked
129
173
  notify_opened
130
174
  end
@@ -136,19 +180,18 @@ class Circuitbox
136
180
  trip
137
181
  end
138
182
 
139
- # Running event and logger outside of the synchronize block to allow other threads
183
+ # Running event outside of the synchronize block to allow other threads
140
184
  # that may be waiting to become unblocked
141
185
  notify_opened
142
186
  end
143
187
 
144
188
  def notify_opened
145
189
  notify_event('open')
146
- logger.debug(circuit_opened_message)
147
190
  end
148
191
 
149
192
  def trip
150
- circuit_store.store(open_storage_key, true, expires: option_value(:sleep_window))
151
- circuit_store.store(half_open_storage_key, true)
193
+ @circuit_store.store(@open_storage_key, true, expires: option_value(:sleep_window))
194
+ @circuit_store.store(@half_open_storage_key, true)
152
195
  end
153
196
 
154
197
  def close!
@@ -156,29 +199,26 @@ class Circuitbox
156
199
  # If the circuit is not open, the half_open key will be deleted from the store
157
200
  # if half_open exists the deleted value is returned and allows us to continue
158
201
  # if half_open doesn't exist nil is returned, causing us to return early
159
- return unless !open? && circuit_store.delete(half_open_storage_key)
202
+ return unless !open? && @circuit_store.delete(@half_open_storage_key)
160
203
  end
161
204
 
162
205
  # Running event outside of the synchronize block to allow other threads
163
206
  # that may be waiting to become unblocked
164
207
  notify_event('close')
165
- logger.debug(circuit_closed_message)
166
208
  end
167
209
 
168
210
  def half_open?
169
- circuit_store.key?(half_open_storage_key)
211
+ @circuit_store.key?(@half_open_storage_key)
170
212
  end
171
213
 
172
214
  def success!
173
215
  increment_and_notify_event('success')
174
- logger.debug(circuit_success_message)
175
216
 
176
217
  close! if half_open?
177
218
  end
178
219
 
179
220
  def failure!
180
221
  increment_and_notify_event('failure')
181
- logger.debug(circuit_failure_message)
182
222
 
183
223
  if half_open?
184
224
  half_open_failure
@@ -189,48 +229,48 @@ class Circuitbox
189
229
 
190
230
  def skipped!
191
231
  notify_event('skipped')
192
- logger.debug(circuit_skipped_message)
193
232
  end
194
233
 
195
234
  # Send event notification to notifier
196
235
  def notify_event(event)
197
- notifier.notify(service, event)
236
+ @notifier.notify(@service, event)
198
237
  end
199
238
 
200
239
  # Increment stat store and send notification
201
240
  def increment_and_notify_event(event)
202
- circuit_store.increment(stat_storage_key(event), 1, expires: (option_value(:time_window) * 2))
241
+ time_window = option_value(:time_window)
242
+ aligned_time = align_time_to_window(time_window)
243
+ @circuit_store.increment(stat_storage_key(event, aligned_time), 1, expires: time_window)
203
244
  notify_event(event)
204
245
  end
205
246
 
206
- def check_sleep_window
207
- sleep_window = option_value(:sleep_window)
208
- time_window = option_value(:time_window)
209
- if sleep_window < time_window
210
- warning_message = "sleep_window: #{sleep_window} is shorter than time_window: #{time_window}, "\
211
- "the error_rate would not be reset after a sleep."
212
- notifier.notify_warning(service, warning_message)
213
- warn("Circuit: #{service}, Warning: #{warning_message}")
214
- end
215
- end
216
-
217
- def stat_storage_key(event)
218
- "circuits:#{service}:stats:#{align_time_to_window}:#{event}"
247
+ def stat_storage_key(event, aligned_time = align_time_to_window)
248
+ "circuits:#{@service}:stats:#{aligned_time}:#{event}"
219
249
  end
220
250
 
221
251
  # return time representation in seconds
222
- def align_time_to_window
223
- time = time_class.now.to_i
224
- time_window = option_value(:time_window)
225
- time - (time % time_window) # remove rest of integer division
252
+ def align_time_to_window(window = option_value(:time_window))
253
+ time = @time_class.current_second
254
+ time - (time % window) # remove rest of integer division
226
255
  end
227
256
 
228
- def open_storage_key
229
- @open_storage_key ||= "circuits:#{service}:open"
257
+ def check_sleep_window
258
+ sleep_window = option_value(:sleep_window)
259
+ time_window = option_value(:time_window)
260
+ return unless sleep_window < time_window
261
+
262
+ warning_message = "sleep_window: #{sleep_window} is shorter than time_window: #{time_window}, "\
263
+ "the error_rate would not be reset after a sleep."
264
+ @notifier.notify_warning(@service, warning_message)
265
+ warn("Circuit: #{@service}, Warning: #{warning_message}")
230
266
  end
231
267
 
232
- def half_open_storage_key
233
- @half_open_storage_key ||= "circuits:#{service}:half_open"
268
+ def default_time_klass
269
+ if @circuit_store.is_a?(Circuitbox::MemoryStore)
270
+ Circuitbox::TimeHelper::Monotonic
271
+ else
272
+ Circuitbox::TimeHelper::Real
273
+ end
234
274
  end
235
275
  end
236
276
  end
@@ -1,16 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'memory_store'
2
- require_relative 'timer/monotonic'
3
- require_relative 'timer/null'
4
- require_relative 'timer/simple'
5
4
  require_relative 'notifier/active_support'
6
5
  require_relative 'notifier/null'
7
6
 
8
7
  class Circuitbox
9
8
  module Configuration
10
9
  attr_writer :default_circuit_store,
11
- :default_notifier,
12
- :default_timer,
13
- :default_logger
10
+ :default_notifier
11
+
12
+ def self.extended(base)
13
+ base.instance_eval do
14
+ @cached_circuits_mutex = Mutex.new
15
+ @cached_circuits = {}
16
+
17
+ # preload circuit_store because it has no other dependencies
18
+ default_circuit_store
19
+ end
20
+ end
14
21
 
15
22
  def configure
16
23
  yield self
@@ -30,26 +37,16 @@ class Circuitbox
30
37
  end
31
38
  end
32
39
 
33
- def default_timer
34
- @default_timer ||= Timer::Simple.new
35
- end
36
-
37
- def default_logger
38
- @default_logger ||= if defined?(Rails)
39
- Rails.logger
40
- else
41
- Logger.new(STDOUT)
42
- end
43
- end
44
-
45
- private
40
+ private
46
41
 
47
- def cached_circuits
48
- @cached_circuits ||= {}
42
+ def find_or_create_circuit_breaker(service_name, options)
43
+ @cached_circuits_mutex.synchronize do
44
+ @cached_circuits[service_name] ||= CircuitBreaker.new(service_name, options)
45
+ end
49
46
  end
50
47
 
51
48
  def clear_cached_circuits!
52
- @cached_circuits = {}
49
+ @cached_circuits_mutex.synchronize { @cached_circuits = {} }
53
50
  end
54
51
  end
55
52
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Circuitbox
2
4
  class Error < StandardError; end
3
5
  end
@@ -1,9 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Circuitbox
2
4
  class OpenCircuitError < Circuitbox::Error
3
5
  attr_reader :service
4
6
 
5
7
  def initialize(service)
8
+ super()
6
9
  @service = service
7
10
  end
11
+
12
+ def to_s
13
+ "#{self.class}: Service #{service.inspect} has an open circuit"
14
+ end
8
15
  end
9
16
  end
@@ -1,17 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Circuitbox
2
4
  class ServiceFailureError < Circuitbox::Error
3
5
  attr_reader :service, :original
4
6
 
5
7
  def initialize(service, exception)
8
+ super()
6
9
  @service = service
7
10
  @original = exception
8
- # we copy over the original exceptions backtrace if there is one
11
+ # We copy over the original exceptions backtrace if there is one
9
12
  backtrace = exception.backtrace
10
13
  set_backtrace(backtrace) unless backtrace.empty?
11
14
  end
12
15
 
13
16
  def to_s
14
- "#{self.class.name} wrapped: #{original}"
17
+ "#{self.class}: Service #{service.inspect} was unavailable (original: #{original})"
15
18
  end
16
19
  end
17
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'excon'
2
4
  require 'circuitbox'
3
5
 
@@ -8,7 +10,7 @@ class Circuitbox
8
10
  DEFAULT_EXCEPTIONS = [
9
11
  Excon::Errors::Timeout,
10
12
  RequestFailed
11
- ]
13
+ ].freeze
12
14
 
13
15
  class NullResponse < Excon::Response
14
16
  def initialize(response, exception)
@@ -26,7 +28,7 @@ class Circuitbox
26
28
 
27
29
  def initialize(stack, opts = {})
28
30
  @stack = stack
29
- default_options = { open_circuit: lambda { |response| response[:status] >= 400 } }
31
+ default_options = { open_circuit: ->(response) { response[:status] >= 400 } }
30
32
  @opts = default_options.merge(opts)
31
33
  super(stack)
32
34
  end
@@ -35,8 +37,8 @@ class Circuitbox
35
37
  circuit(datum).run do
36
38
  raise RequestFailed
37
39
  end
38
- rescue Circuitbox::Error => exception
39
- circuit_open_value(datum, datum[:response], exception)
40
+ rescue Circuitbox::Error => e
41
+ circuit_open_value(datum, datum[:response], e)
40
42
  end
41
43
 
42
44
  def request_call(datum)
@@ -50,8 +52,8 @@ class Circuitbox
50
52
  raise RequestFailed if open_circuit?(datum[:response])
51
53
  end
52
54
  @stack.response_call(datum)
53
- rescue Circuitbox::Error => exception
54
- circuit_open_value(datum, datum[:response], exception)
55
+ rescue Circuitbox::Error => e
56
+ circuit_open_value(datum, datum[:response], e)
55
57
  end
56
58
 
57
59
  def identifier
@@ -93,9 +95,9 @@ class Circuitbox
93
95
  def default_value
94
96
  @default_value ||= begin
95
97
  default = opts.fetch(:default_value) do
96
- lambda { |response, exception| NullResponse.new(response, exception) }
98
+ ->(response, exception) { NullResponse.new(response, exception) }
97
99
  end
98
- default.respond_to?(:call) ? default : lambda { |*| default }
100
+ default.respond_to?(:call) ? default : ->(*) { default }
99
101
  end
100
102
  end
101
103
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'faraday'
2
4
  require 'circuitbox'
3
5
 
@@ -7,6 +9,7 @@ class Circuitbox
7
9
 
8
10
  class NullResponse < Faraday::Response
9
11
  attr_reader :original_response, :original_exception
12
+
10
13
  def initialize(response = nil, exception = nil)
11
14
  @original_response = response
12
15
  @original_exception = exception
@@ -18,7 +21,7 @@ class Circuitbox
18
21
  open_circuit: lambda do |response|
19
22
  # response.status:
20
23
  # nil -> connection could not be established, or failed very hard
21
- # 5xx -> non recoverable server error, oposed to 4xx which are client errors
24
+ # 5xx -> non recoverable server error, opposed to 4xx which are client errors
22
25
  response.status.nil? || (response.status >= 500 && response.status <= 599)
23
26
  end,
24
27
  default_value: ->(service_response, exception) { NullResponse.new(service_response, exception) },
@@ -31,10 +34,7 @@ class Circuitbox
31
34
  }.freeze
32
35
 
33
36
  DEFAULT_EXCEPTIONS = [
34
- # Faraday before 0.9.0 didn't have Faraday::TimeoutError so we default to Faraday::Error::TimeoutError
35
- # Faraday >= 0.9.0 defines Faraday::TimeoutError and this can be used for all versions up to 1.0.0 that
36
- # also define and raise Faraday::Error::TimeoutError as Faraday::TimeoutError is an ancestor
37
- defined?(Faraday::TimeoutError) ? Faraday::TimeoutError : Faraday::Error::TimeoutError,
37
+ Faraday::TimeoutError,
38
38
  RequestFailed
39
39
  ].freeze
40
40
 
@@ -42,8 +42,6 @@ class Circuitbox
42
42
  exceptions: DEFAULT_EXCEPTIONS
43
43
  }.freeze
44
44
 
45
- attr_reader :opts
46
-
47
45
  def initialize(app, opts = {})
48
46
  @app = app
49
47
  @opts = DEFAULT_OPTIONS.merge(opts)
@@ -60,19 +58,19 @@ class Circuitbox
60
58
  raise RequestFailed if open_circuit?(service_response)
61
59
  end
62
60
  end
63
- rescue Circuitbox::Error => ex
64
- circuit_open_value(request_env, service_response, ex)
61
+ rescue Circuitbox::Error => e
62
+ circuit_open_value(request_env, service_response, e)
65
63
  end
66
64
 
67
65
  private
68
66
 
69
67
  def call_default_value(response, exception)
70
- default_value = opts[:default_value]
68
+ default_value = @opts[:default_value]
71
69
  default_value.respond_to?(:call) ? default_value.call(response, exception) : default_value
72
70
  end
73
71
 
74
72
  def open_circuit?(response)
75
- opts[:open_circuit].call(response)
73
+ @opts[:open_circuit].call(response)
76
74
  end
77
75
 
78
76
  def circuit_open_value(env, service_response, exception)
@@ -80,12 +78,12 @@ class Circuitbox
80
78
  end
81
79
 
82
80
  def circuit(env)
83
- identifier = opts[:identifier]
81
+ identifier = @opts[:identifier]
84
82
  id = identifier.respond_to?(:call) ? identifier.call(env) : identifier
85
83
 
86
- Circuitbox.circuit(id, opts[:circuit_breaker_options])
84
+ Circuitbox.circuit(id, @opts[:circuit_breaker_options])
87
85
  end
88
86
  end
89
87
  end
90
88
 
91
- Faraday::Middleware.register_middleware circuitbox: Circuitbox::FaradayMiddleware
89
+ Faraday::Middleware.register_middleware(circuitbox: Circuitbox::FaradayMiddleware)
@@ -1,9 +1,11 @@
1
- require_relative 'monotonic_time'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../time_helper/monotonic'
2
4
 
3
5
  class Circuitbox
4
6
  class MemoryStore
5
7
  class Container
6
- include MonotonicTime
8
+ include TimeHelper::Monotonic
7
9
 
8
10
  attr_accessor :value
9
11
 
@@ -13,11 +15,11 @@ class Circuitbox
13
15
  end
14
16
 
15
17
  def expired?
16
- @expires_after > 0 && @expires_after < current_second
18
+ @expires_after.positive? && @expires_after < current_second
17
19
  end
18
20
 
19
21
  def expired_at?(clock_second)
20
- @expires_after > 0 && @expires_after < clock_second
22
+ @expires_after.positive? && @expires_after < clock_second
21
23
  end
22
24
 
23
25
  def expires_after(seconds = 0)
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'memory_store/monotonic_time'
3
+ require_relative 'time_helper/monotonic'
4
4
  require_relative 'memory_store/container'
5
5
 
6
6
  class Circuitbox
7
7
  class MemoryStore
8
- include MonotonicTime
8
+ include TimeHelper::Monotonic
9
9
 
10
10
  def initialize(compaction_frequency: 60)
11
11
  @store = {}
@@ -27,7 +27,7 @@ class Circuitbox
27
27
  @mutex.synchronize do
28
28
  existing_container = fetch_container(key)
29
29
 
30
- # reusing the existing container is a small optmization
30
+ # reusing the existing container is a small optimization
31
31
  # to reduce the amount of objects created
32
32
  if existing_container
33
33
  existing_container.expires_after(seconds_to_expire)
@@ -40,7 +40,14 @@ class Circuitbox
40
40
  end
41
41
 
42
42
  def load(key, _opts = {})
43
- @mutex.synchronize { fetch_value(key) }
43
+ @mutex.synchronize { fetch_container(key)&.value }
44
+ end
45
+
46
+ def values_at(*keys, **_opts)
47
+ @mutex.synchronize do
48
+ current_time = current_second
49
+ keys.map! { |key| fetch_container(key, current_time)&.value }
50
+ end
44
51
  end
45
52
 
46
53
  def key?(key)
@@ -51,11 +58,9 @@ class Circuitbox
51
58
  @mutex.synchronize { @store.delete(key) }
52
59
  end
53
60
 
54
- private
55
-
56
- def fetch_container(key)
57
- current_time = current_second
61
+ private
58
62
 
63
+ def fetch_container(key, current_time = current_second)
59
64
  compact(current_time) if @compact_after < current_time
60
65
 
61
66
  container = @store[key]
@@ -70,12 +75,6 @@ class Circuitbox
70
75
  end
71
76
  end
72
77
 
73
- def fetch_value(key)
74
- container = fetch_container(key)
75
- return unless container
76
- container.value
77
- end
78
-
79
78
  def compact(current_time)
80
79
  @store.delete_if { |_, value| value.expired_at?(current_time) }
81
80
  @compact_after = current_time + @compaction_frequency
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Circuitbox
4
- class Notifier
4
+ module Notifier
5
5
  class ActiveSupport
6
6
  def notify(circuit_name, event)
7
- ::ActiveSupport::Notifications.instrument("circuit_#{event}", circuit: circuit_name)
7
+ ::ActiveSupport::Notifications.instrument("#{event}.circuitbox", circuit: circuit_name)
8
8
  end
9
9
 
10
10
  def notify_warning(circuit_name, message)
11
- ::ActiveSupport::Notifications.instrument('circuit_warning', circuit: circuit_name, message: message)
11
+ ::ActiveSupport::Notifications.instrument('warning.circuitbox', circuit: circuit_name, message: message)
12
12
  end
13
13
 
14
- def metric_gauge(circuit_name, gauge, value)
15
- ::ActiveSupport::Notifications.instrument('circuit_gauge', circuit: circuit_name, gauge: gauge, value: value)
14
+ def notify_run(circuit_name, &block)
15
+ ::ActiveSupport::Notifications.instrument('run.circuitbox', circuit: circuit_name, &block)
16
16
  end
17
17
  end
18
18
  end
@@ -1,11 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Circuitbox
2
- class Notifier
4
+ module Notifier
3
5
  class Null
4
- def notify(_, _); end
6
+ def notify(_circuit_name, _event); end
5
7
 
6
- def notify_warning(_, _); end
8
+ def notify_warning(_circuit_name, _message); end
7
9
 
8
- def metric_gauge(_, _, _); end
10
+ def notify_run(_circuit_name)
11
+ yield
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Circuitbox
2
- class MemoryStore
3
- module MonotonicTime
4
+ module TimeHelper
5
+ module Monotonic
4
6
  module_function
5
7
 
6
8
  def current_second
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Circuitbox
4
+ module TimeHelper
5
+ module Real
6
+ module_function
7
+
8
+ def current_second
9
+ ::Time.now.to_i
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Circuitbox
2
- VERSION = '2.0.0.pre3'.freeze
4
+ VERSION = '2.0.0.pre5'
3
5
  end
data/lib/circuitbox.rb CHANGED
@@ -1,5 +1,4 @@
1
- require 'uri'
2
- require 'logger'
1
+ # frozen_string_literal: true
3
2
 
4
3
  require_relative 'circuitbox/version'
5
4
  require_relative 'circuitbox/circuit_breaker'
@@ -9,15 +8,15 @@ require_relative 'circuitbox/errors/service_failure_error'
9
8
  require_relative 'circuitbox/configuration'
10
9
 
11
10
  class Circuitbox
12
- class << self
13
- include Configuration
11
+ extend Configuration
14
12
 
15
- def circuit(service_name, options)
16
- circuit = (cached_circuits[service_name] ||= CircuitBreaker.new(service_name, options))
13
+ class << self
14
+ def circuit(service_name, options, &block)
15
+ circuit = find_or_create_circuit_breaker(service_name, options)
17
16
 
18
- return circuit unless block_given?
17
+ return circuit unless block
19
18
 
20
- circuit.run(circuitbox_exceptions: false) { yield }
19
+ circuit.run(exception: false, &block)
21
20
  end
22
21
  end
23
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: circuitbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre3
4
+ version: 2.0.0.pre5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fahim Ferdous
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-05-28 00:00:00.000000000 Z
12
+ date: 2023-04-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - ">"
19
19
  - !ruby/object:Gem::Version
20
- version: '1.16'
20
+ version: '2.0'
21
21
  type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">"
26
26
  - !ruby/object:Gem::Version
27
- version: '1.16'
27
+ version: '2.0'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: excon
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -45,20 +45,14 @@ dependencies:
45
45
  requirements:
46
46
  - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: '0.15'
49
- - - "<"
50
- - !ruby/object:Gem::Version
51
- version: '2.0'
48
+ version: '0.17'
52
49
  type: :development
53
50
  prerelease: false
54
51
  version_requirements: !ruby/object:Gem::Requirement
55
52
  requirements:
56
53
  - - ">="
57
54
  - !ruby/object:Gem::Version
58
- version: '0.15'
59
- - - "<"
60
- - !ruby/object:Gem::Version
61
- version: '2.0'
55
+ version: '0.17'
62
56
  - !ruby/object:Gem::Dependency
63
57
  name: gimme
64
58
  requirement: !ruby/object:Gem::Requirement
@@ -79,14 +73,14 @@ dependencies:
79
73
  requirements:
80
74
  - - "~>"
81
75
  - !ruby/object:Gem::Version
82
- version: '5.11'
76
+ version: '5.14'
83
77
  type: :development
84
78
  prerelease: false
85
79
  version_requirements: !ruby/object:Gem::Requirement
86
80
  requirements:
87
81
  - - "~>"
88
82
  - !ruby/object:Gem::Version
89
- version: '5.11'
83
+ version: '5.14'
90
84
  - !ruby/object:Gem::Dependency
91
85
  name: minitest-excludes
92
86
  requirement: !ruby/object:Gem::Requirement
@@ -107,14 +101,28 @@ dependencies:
107
101
  requirements:
108
102
  - - "~>"
109
103
  - !ruby/object:Gem::Version
110
- version: '1.7'
104
+ version: '1.12'
111
105
  type: :development
112
106
  prerelease: false
113
107
  version_requirements: !ruby/object:Gem::Requirement
114
108
  requirements:
115
109
  - - "~>"
116
110
  - !ruby/object:Gem::Version
117
- version: '1.7'
111
+ version: '1.12'
112
+ - !ruby/object:Gem::Dependency
113
+ name: moneta
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '1.0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '1.0'
118
126
  - !ruby/object:Gem::Dependency
119
127
  name: rack
120
128
  requirement: !ruby/object:Gem::Requirement
@@ -163,28 +171,42 @@ dependencies:
163
171
  requirements:
164
172
  - - "~>"
165
173
  - !ruby/object:Gem::Version
166
- version: '1.3'
174
+ version: '1.4'
167
175
  type: :development
168
176
  prerelease: false
169
177
  version_requirements: !ruby/object:Gem::Requirement
170
178
  requirements:
171
179
  - - "~>"
172
180
  - !ruby/object:Gem::Version
173
- version: '1.3'
181
+ version: '1.4'
174
182
  - !ruby/object:Gem::Dependency
175
- name: moneta
183
+ name: webrick
176
184
  requirement: !ruby/object:Gem::Requirement
177
185
  requirements:
178
186
  - - "~>"
179
187
  - !ruby/object:Gem::Version
180
- version: '1.0'
188
+ version: '1.7'
181
189
  type: :development
182
190
  prerelease: false
183
191
  version_requirements: !ruby/object:Gem::Requirement
184
192
  requirements:
185
193
  - - "~>"
186
194
  - !ruby/object:Gem::Version
187
- version: '1.0'
195
+ version: '1.7'
196
+ - !ruby/object:Gem::Dependency
197
+ name: yard
198
+ requirement: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - "~>"
201
+ - !ruby/object:Gem::Version
202
+ version: 0.9.26
203
+ type: :development
204
+ prerelease: false
205
+ version_requirements: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - "~>"
208
+ - !ruby/object:Gem::Version
209
+ version: 0.9.26
188
210
  description:
189
211
  email:
190
212
  - fahimfmf@gmail.com
@@ -196,7 +218,6 @@ files:
196
218
  - README.md
197
219
  - lib/circuitbox.rb
198
220
  - lib/circuitbox/circuit_breaker.rb
199
- - lib/circuitbox/circuit_breaker/logger_messages.rb
200
221
  - lib/circuitbox/configuration.rb
201
222
  - lib/circuitbox/errors/error.rb
202
223
  - lib/circuitbox/errors/open_circuit_error.rb
@@ -205,17 +226,19 @@ files:
205
226
  - lib/circuitbox/faraday_middleware.rb
206
227
  - lib/circuitbox/memory_store.rb
207
228
  - lib/circuitbox/memory_store/container.rb
208
- - lib/circuitbox/memory_store/monotonic_time.rb
209
229
  - lib/circuitbox/notifier/active_support.rb
210
230
  - lib/circuitbox/notifier/null.rb
211
- - lib/circuitbox/timer/monotonic.rb
212
- - lib/circuitbox/timer/null.rb
213
- - lib/circuitbox/timer/simple.rb
231
+ - lib/circuitbox/time_helper/monotonic.rb
232
+ - lib/circuitbox/time_helper/real.rb
214
233
  - lib/circuitbox/version.rb
215
234
  homepage: https://github.com/yammer/circuitbox
216
235
  licenses:
217
236
  - Apache-2.0
218
- metadata: {}
237
+ metadata:
238
+ bug_tracker_uri: https://github.com/yammer/circuitbox/issues
239
+ changelog_uri: https://github.com/yammer/circuitbox/blob/main/CHANGELOG.md
240
+ source_code_uri: https://github.com/yammer/circuitbox
241
+ rubygems_mfa_required: 'true'
219
242
  post_install_message:
220
243
  rdoc_options: []
221
244
  require_paths:
@@ -224,15 +247,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
224
247
  requirements:
225
248
  - - ">="
226
249
  - !ruby/object:Gem::Version
227
- version: '0'
250
+ version: 2.6.0
228
251
  required_rubygems_version: !ruby/object:Gem::Requirement
229
252
  requirements:
230
253
  - - ">"
231
254
  - !ruby/object:Gem::Version
232
255
  version: 1.3.1
233
256
  requirements: []
234
- rubyforge_project:
235
- rubygems_version: 2.5.2.3
257
+ rubygems_version: 3.1.6
236
258
  signing_key:
237
259
  specification_version: 4
238
260
  summary: A robust circuit breaker that manages failing external services.
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Circuitbox
4
- class CircuitBreaker
5
- module LoggerMessages
6
- def circuit_skipped_message
7
- @circuit_skipped_message ||= "[CIRCUIT] #{service}: skipped"
8
- end
9
-
10
- def circuit_running_message
11
- @circuit_running_message ||= "[CIRCUIT] #{service}: running"
12
- end
13
-
14
- def circuit_success_message
15
- @circuit_success_message ||= "[CIRCUIT] #{service}: success"
16
- end
17
-
18
- def circuit_failure_message
19
- @circuit_failure_message ||= "[CIRCUIT] #{service}: failure"
20
- end
21
-
22
- def circuit_opened_message
23
- @circuit_opened_message ||= "[CIRCUIT] #{service}: opened"
24
- end
25
-
26
- def circuit_closed_message
27
- @circuit_closed_message ||= "[CIRCUIT] #{service}: closed"
28
- end
29
- end
30
- end
31
- end
@@ -1,17 +0,0 @@
1
- class Circuitbox
2
- class Timer
3
- class Monotonic
4
- def initialize(time_unit = :millisecond)
5
- @time_unit = time_unit
6
- end
7
-
8
- def time(service, notifier, metric_name)
9
- before = Process.clock_gettime(Process::CLOCK_MONOTONIC, @time_unit)
10
- result = yield
11
- total_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, @time_unit) - before
12
- notifier.metric_gauge(service, metric_name, total_time)
13
- result
14
- end
15
- end
16
- end
17
- end
@@ -1,9 +0,0 @@
1
- class Circuitbox
2
- class Timer
3
- class Null
4
- def time(_service, _notifier, _metric_name)
5
- yield
6
- end
7
- end
8
- end
9
- end
@@ -1,13 +0,0 @@
1
- class Circuitbox
2
- class Timer
3
- class Simple
4
- def time(service, notifier, metric_name)
5
- before = Time.now.to_f
6
- result = yield
7
- total_time = Time.now.to_f - before
8
- notifier.metric_gauge(service, metric_name, total_time)
9
- result
10
- end
11
- end
12
- end
13
- end