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 +5 -5
- data/README.md +29 -107
- data/lib/circuitbox/circuit_breaker.rb +118 -78
- data/lib/circuitbox/configuration.rb +19 -22
- data/lib/circuitbox/errors/error.rb +2 -0
- data/lib/circuitbox/errors/open_circuit_error.rb +7 -0
- data/lib/circuitbox/errors/service_failure_error.rb +5 -2
- data/lib/circuitbox/excon_middleware.rb +10 -8
- data/lib/circuitbox/faraday_middleware.rb +12 -14
- data/lib/circuitbox/memory_store/container.rb +6 -4
- data/lib/circuitbox/memory_store.rb +13 -14
- data/lib/circuitbox/notifier/active_support.rb +5 -5
- data/lib/circuitbox/notifier/null.rb +8 -4
- data/lib/circuitbox/{memory_store/monotonic_time.rb → time_helper/monotonic.rb} +4 -2
- data/lib/circuitbox/time_helper/real.rb +13 -0
- data/lib/circuitbox/version.rb +3 -1
- data/lib/circuitbox.rb +7 -8
- metadata +52 -30
- data/lib/circuitbox/circuit_breaker/logger_messages.rb +0 -31
- data/lib/circuitbox/timer/monotonic.rb +0 -17
- data/lib/circuitbox/timer/null.rb +0 -9
- data/lib/circuitbox/timer/simple.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 541c5e674d62681f13021254d4304c4903e2812915c59b74fcb419468f608c9d
|
4
|
+
data.tar.gz: 97fdbeedc5ac6aa8aa249ca92a1202175e8bbd3f777c4c5211583130aecf5b0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2466098850251fab23dd6be74a292150ba8eda1e27c3ef1fefcc79aab36dedc170daf4292ae3e9d20ea49468bb487855ce4d3bd77aa7ef1ec8b3ba18bd52d1c6
|
7
|
+
data.tar.gz: a62d7c7a283ce4adcec0c73b69db7de8ca982f493369a7ac6534bfc9641de2bc909b8294bb634913b64c1f454861488595e0f103672fa6cc448c7f6beaa8c170
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Circuitbox
|
2
2
|
|
3
|
-
|
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(
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
**
|
133
|
+
**Circuit open/close:**
|
140
134
|
|
141
135
|
```ruby
|
142
|
-
|
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('
|
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
|
-
**
|
146
|
+
**Circuit run:**
|
155
147
|
|
156
148
|
```ruby
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
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('
|
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}
|
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 '
|
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
|
-
:
|
9
|
+
:circuit_store, :notifier, :time_class
|
11
10
|
|
12
11
|
DEFAULTS = {
|
13
|
-
sleep_window:
|
12
|
+
sleep_window: 90,
|
14
13
|
volume_threshold: 5,
|
15
|
-
error_threshold:
|
16
|
-
time_window:
|
14
|
+
error_threshold: 50,
|
15
|
+
time_window: 60
|
17
16
|
}.freeze
|
18
17
|
|
18
|
+
# Initialize a CircuitBreaker
|
19
19
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
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(:
|
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
|
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
|
-
|
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
|
81
|
+
raise Circuitbox::OpenCircuitError.new(@service) if exception
|
59
82
|
else
|
60
|
-
logger.debug(circuit_running_message)
|
61
|
-
|
62
83
|
begin
|
63
|
-
response =
|
64
|
-
yield
|
65
|
-
end
|
84
|
+
response = @notifier.notify_run(@service, &block)
|
66
85
|
|
67
86
|
success!
|
68
|
-
rescue
|
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
|
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,
|
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
|
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
|
-
|
139
|
+
private
|
103
140
|
|
104
141
|
def should_open?
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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?(
|
117
|
-
|
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
|
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
|
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
|
-
|
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
|
207
|
-
|
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.
|
224
|
-
|
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
|
229
|
-
|
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
|
233
|
-
@
|
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
|
-
|
13
|
-
|
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
|
-
|
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
|
48
|
-
@
|
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,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
|
-
#
|
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.
|
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:
|
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 =>
|
39
|
-
circuit_open_value(datum, datum[:response],
|
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 =>
|
54
|
-
circuit_open_value(datum, datum[:response],
|
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
|
-
|
98
|
+
->(response, exception) { NullResponse.new(response, exception) }
|
97
99
|
end
|
98
|
-
default.respond_to?(:call) ? 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,
|
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
|
-
|
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 =>
|
64
|
-
circuit_open_value(request_env, service_response,
|
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
|
89
|
+
Faraday::Middleware.register_middleware(circuitbox: Circuitbox::FaradayMiddleware)
|
@@ -1,9 +1,11 @@
|
|
1
|
-
|
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
|
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
|
18
|
+
@expires_after.positive? && @expires_after < current_second
|
17
19
|
end
|
18
20
|
|
19
21
|
def expired_at?(clock_second)
|
20
|
-
@expires_after
|
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 '
|
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
|
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
|
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 {
|
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
|
-
|
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
|
-
|
4
|
+
module Notifier
|
5
5
|
class ActiveSupport
|
6
6
|
def notify(circuit_name, event)
|
7
|
-
::ActiveSupport::Notifications.instrument("
|
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('
|
11
|
+
::ActiveSupport::Notifications.instrument('warning.circuitbox', circuit: circuit_name, message: message)
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
::ActiveSupport::Notifications.instrument('
|
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
|
-
|
4
|
+
module Notifier
|
3
5
|
class Null
|
4
|
-
def notify(
|
6
|
+
def notify(_circuit_name, _event); end
|
5
7
|
|
6
|
-
def notify_warning(
|
8
|
+
def notify_warning(_circuit_name, _message); end
|
7
9
|
|
8
|
-
def
|
10
|
+
def notify_run(_circuit_name)
|
11
|
+
yield
|
12
|
+
end
|
9
13
|
end
|
10
14
|
end
|
11
15
|
end
|
data/lib/circuitbox/version.rb
CHANGED
data/lib/circuitbox.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
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
|
-
|
13
|
-
include Configuration
|
11
|
+
extend Configuration
|
14
12
|
|
15
|
-
|
16
|
-
|
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
|
17
|
+
return circuit unless block
|
19
18
|
|
20
|
-
circuit.run(
|
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.
|
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:
|
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: '
|
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: '
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
181
|
+
version: '1.4'
|
174
182
|
- !ruby/object:Gem::Dependency
|
175
|
-
name:
|
183
|
+
name: webrick
|
176
184
|
requirement: !ruby/object:Gem::Requirement
|
177
185
|
requirements:
|
178
186
|
- - "~>"
|
179
187
|
- !ruby/object:Gem::Version
|
180
|
-
version: '1.
|
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.
|
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/
|
212
|
-
- lib/circuitbox/
|
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:
|
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
|
-
|
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,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
|