circuitbox 2.0.0.pre4 → 2.0.0.pre5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -34
- data/lib/circuitbox/circuit_breaker.rb +110 -68
- data/lib/circuitbox/configuration.rb +16 -15
- data/lib/circuitbox/errors/open_circuit_error.rb +4 -0
- data/lib/circuitbox/errors/service_failure_error.rb +2 -2
- data/lib/circuitbox/faraday_middleware.rb +7 -12
- data/lib/circuitbox/memory_store/container.rb +2 -2
- data/lib/circuitbox/memory_store.rb +12 -14
- data/lib/circuitbox/notifier/active_support.rb +5 -5
- data/lib/circuitbox/notifier/null.rb +6 -4
- data/lib/circuitbox/{memory_store/monotonic_time.rb → time_helper/monotonic.rb} +2 -2
- data/lib/circuitbox/time_helper/real.rb +13 -0
- data/lib/circuitbox/version.rb +1 -1
- data/lib/circuitbox.rb +4 -6
- metadata +31 -65
- data/lib/circuitbox/circuit_breaker/logger_messages.rb +0 -31
- data/lib/circuitbox/timer.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
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
@@ -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,7 +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_logger = Rails.logger
|
59
58
|
end
|
60
59
|
```
|
61
60
|
|
@@ -81,15 +80,11 @@ class ExampleServiceClient
|
|
81
80
|
# the store you want to use to save the circuit state so it can be
|
82
81
|
# tracked, this needs to be Moneta compatible, and support increment
|
83
82
|
# this overrides what is set in the global configuration
|
84
|
-
|
83
|
+
circuit_store: Circuitbox::MemoryStore.new,
|
85
84
|
|
86
85
|
# exceeding this rate will open the circuit (checked on failures)
|
87
86
|
error_threshold: 50,
|
88
87
|
|
89
|
-
# Logger to use
|
90
|
-
# This overrides what is set in the global configuration
|
91
|
-
logger: Logger.new(STDOUT),
|
92
|
-
|
93
88
|
# Customized notifier
|
94
89
|
# overrides the default
|
95
90
|
# this overrides what is set in the global configuration
|
@@ -108,7 +103,7 @@ Circuitbox.circuit(:yammer, {
|
|
108
103
|
})
|
109
104
|
```
|
110
105
|
|
111
|
-
## Circuit Store
|
106
|
+
## Circuit Store
|
112
107
|
|
113
108
|
Holds all the relevant data to trip the circuit if a given number of requests
|
114
109
|
fail in a specified period of time. Circuitbox also supports
|
@@ -125,60 +120,59 @@ some pre-requisits need to be satisfied first:
|
|
125
120
|
|
126
121
|
## Notifications
|
127
122
|
|
128
|
-
|
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).
|
129
129
|
|
130
|
+
### ActiveSupport
|
130
131
|
Usage example:
|
131
132
|
|
132
|
-
**
|
133
|
+
**Circuit open/close:**
|
133
134
|
|
134
135
|
```ruby
|
135
|
-
|
136
|
-
|
137
|
-
ActiveSupport::Notifications.subscribe('circuit_open') do |name, start, finish, id, payload|
|
136
|
+
ActiveSupport::Notifications.subscribe('open.circuitbox') do |_name, _start, _finish, _id, payload|
|
138
137
|
circuit_name = payload[:circuit]
|
139
138
|
Rails.logger.warn("Open circuit for: #{circuit_name}")
|
140
139
|
end
|
141
|
-
ActiveSupport::Notifications.subscribe('
|
140
|
+
ActiveSupport::Notifications.subscribe('close.circuitbox') do |_name, _start, _finish, _id, payload|
|
142
141
|
circuit_name = payload[:circuit]
|
143
142
|
Rails.logger.info("Close circuit for: #{circuit_name}")
|
144
143
|
end
|
145
144
|
```
|
146
145
|
|
147
|
-
**
|
146
|
+
**Circuit run:**
|
148
147
|
|
149
148
|
```ruby
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
value = payload[:value]
|
156
|
-
metrics_key = "circuitbox.circuit.#{circuit_name}.#{gauge}"
|
157
|
-
|
158
|
-
$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}")
|
159
154
|
end
|
160
155
|
```
|
161
156
|
|
162
|
-
|
163
|
-
|
164
|
-
- `runtime` # runtime will only be notified when circuit is closed and block is successfully executed.
|
165
|
-
|
166
|
-
**warnings:**
|
167
|
-
in case of misconfiguration, circuitbox will fire a circuitbox_warning
|
157
|
+
**Circuit Warnings:**
|
158
|
+
In case of misconfiguration, circuitbox will fire a `warning.circuitbox`
|
168
159
|
notification.
|
169
160
|
|
170
161
|
```ruby
|
171
|
-
ActiveSupport::Notifications.subscribe('
|
162
|
+
ActiveSupport::Notifications.subscribe('warning.circuitbox') do |_name, _start, _finish, _id, payload|
|
172
163
|
circuit_name = payload[:circuit]
|
173
164
|
warning = payload[:message]
|
174
|
-
Rails.logger.warning("#{circuit_name}
|
165
|
+
Rails.logger.warning("Circuit warning for: #{circuit_name} Message: #{warning}")
|
175
166
|
end
|
176
167
|
|
177
168
|
```
|
178
169
|
|
179
170
|
## Faraday
|
180
171
|
|
181
|
-
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
|
+
|
182
176
|
|
183
177
|
```ruby
|
184
178
|
require 'faraday'
|
@@ -1,13 +1,12 @@
|
|
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
12
|
sleep_window: 90,
|
@@ -16,20 +15,23 @@ class Circuitbox
|
|
16
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(:
|
34
|
+
@circuit_store = options.fetch(:circuit_store) { Circuitbox.default_circuit_store }
|
33
35
|
@notifier = options.fetch(:notifier) { Circuitbox.default_notifier }
|
34
36
|
|
35
37
|
if @circuit_options[:timeout_seconds]
|
@@ -37,47 +39,73 @@ class Circuitbox
|
|
37
39
|
'Check the upgrade guide at https://github.com/yammer/circuitbox')
|
38
40
|
end
|
39
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
|
+
|
40
47
|
@exceptions = options.fetch(:exceptions)
|
41
|
-
raise ArgumentError.new('exceptions
|
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 }
|
42
51
|
|
43
|
-
@logger = options.fetch(:logger) { Circuitbox.default_logger }
|
44
|
-
@time_class = options.fetch(:time_class) { Time }
|
45
52
|
@state_change_mutex = Mutex.new
|
53
|
+
@open_storage_key = "circuits:#{@service}:open"
|
54
|
+
@half_open_storage_key = "circuits:#{@service}:half_open"
|
46
55
|
check_sleep_window
|
47
56
|
end
|
48
57
|
|
49
58
|
def option_value(name)
|
50
|
-
value = circuit_options[name]
|
59
|
+
value = @circuit_options[name]
|
51
60
|
value.is_a?(Proc) ? value.call : value
|
52
61
|
end
|
53
62
|
|
54
|
-
|
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)
|
55
79
|
if open?
|
56
80
|
skipped!
|
57
|
-
raise Circuitbox::OpenCircuitError.new(service) if
|
81
|
+
raise Circuitbox::OpenCircuitError.new(@service) if exception
|
58
82
|
else
|
59
|
-
logger.debug(circuit_running_message)
|
60
|
-
|
61
83
|
begin
|
62
|
-
response =
|
84
|
+
response = @notifier.notify_run(@service, &block)
|
63
85
|
|
64
86
|
success!
|
65
|
-
rescue
|
87
|
+
rescue *@exceptions => e
|
66
88
|
# Other stores could raise an exception that circuitbox is asked to watch.
|
67
|
-
# setting to nil keeps the same behavior as the previous
|
89
|
+
# setting to nil keeps the same behavior as the previous definition of run.
|
68
90
|
response = nil
|
69
91
|
failure!
|
70
|
-
raise Circuitbox::ServiceFailureError.new(service, e) if
|
92
|
+
raise Circuitbox::ServiceFailureError.new(@service, e) if exception
|
71
93
|
end
|
72
94
|
end
|
73
95
|
|
74
96
|
response
|
75
97
|
end
|
76
98
|
|
99
|
+
# Check if the circuit is open
|
100
|
+
#
|
101
|
+
# @return [Boolean] True if circuit is open, False if closed
|
77
102
|
def open?
|
78
|
-
circuit_store.key?(open_storage_key)
|
103
|
+
@circuit_store.key?(@open_storage_key)
|
79
104
|
end
|
80
105
|
|
106
|
+
# Calculates the current error rate of the circuit
|
107
|
+
#
|
108
|
+
# @return [Float] Error Rate
|
81
109
|
def error_rate(failures = failure_count, success = success_count)
|
82
110
|
all_count = failures + success
|
83
111
|
return 0.0 unless all_count.positive?
|
@@ -85,34 +113,52 @@ class Circuitbox
|
|
85
113
|
(failures / all_count.to_f) * 100
|
86
114
|
end
|
87
115
|
|
116
|
+
# Number of Failures the circuit has encountered in the current time window
|
117
|
+
#
|
118
|
+
# @return [Integer] Number of failures
|
88
119
|
def failure_count
|
89
|
-
circuit_store.load(stat_storage_key('failure'), raw: true).to_i
|
120
|
+
@circuit_store.load(stat_storage_key('failure'), raw: true).to_i
|
90
121
|
end
|
91
122
|
|
123
|
+
# Number of successes the circuit has encountered in the current time window
|
124
|
+
#
|
125
|
+
# @return [Integer] Number of successes
|
92
126
|
def success_count
|
93
|
-
circuit_store.load(stat_storage_key('success'), raw: true).to_i
|
127
|
+
@circuit_store.load(stat_storage_key('success'), raw: true).to_i
|
94
128
|
end
|
95
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
|
96
135
|
def try_close_next_time
|
97
|
-
circuit_store.delete(open_storage_key)
|
136
|
+
@circuit_store.delete(@open_storage_key)
|
98
137
|
end
|
99
138
|
|
100
139
|
private
|
101
140
|
|
102
141
|
def should_open?
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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)
|
108
154
|
end
|
109
155
|
|
110
156
|
def passed_volume_threshold?(failures, successes)
|
111
157
|
failures + successes >= option_value(:volume_threshold)
|
112
158
|
end
|
113
159
|
|
114
|
-
def passed_rate_threshold?(
|
115
|
-
|
160
|
+
def passed_rate_threshold?(failures, successes)
|
161
|
+
error_rate(failures, successes) >= option_value(:error_threshold)
|
116
162
|
end
|
117
163
|
|
118
164
|
def half_open_failure
|
@@ -122,7 +168,7 @@ class Circuitbox
|
|
122
168
|
trip
|
123
169
|
end
|
124
170
|
|
125
|
-
# Running event
|
171
|
+
# Running event outside of the synchronize block to allow other threads
|
126
172
|
# that may be waiting to become unblocked
|
127
173
|
notify_opened
|
128
174
|
end
|
@@ -134,19 +180,18 @@ class Circuitbox
|
|
134
180
|
trip
|
135
181
|
end
|
136
182
|
|
137
|
-
# Running event
|
183
|
+
# Running event outside of the synchronize block to allow other threads
|
138
184
|
# that may be waiting to become unblocked
|
139
185
|
notify_opened
|
140
186
|
end
|
141
187
|
|
142
188
|
def notify_opened
|
143
189
|
notify_event('open')
|
144
|
-
logger.debug(circuit_opened_message)
|
145
190
|
end
|
146
191
|
|
147
192
|
def trip
|
148
|
-
circuit_store.store(open_storage_key, true, expires: option_value(:sleep_window))
|
149
|
-
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)
|
150
195
|
end
|
151
196
|
|
152
197
|
def close!
|
@@ -154,29 +199,26 @@ class Circuitbox
|
|
154
199
|
# If the circuit is not open, the half_open key will be deleted from the store
|
155
200
|
# if half_open exists the deleted value is returned and allows us to continue
|
156
201
|
# if half_open doesn't exist nil is returned, causing us to return early
|
157
|
-
return unless !open? && circuit_store.delete(half_open_storage_key)
|
202
|
+
return unless !open? && @circuit_store.delete(@half_open_storage_key)
|
158
203
|
end
|
159
204
|
|
160
205
|
# Running event outside of the synchronize block to allow other threads
|
161
206
|
# that may be waiting to become unblocked
|
162
207
|
notify_event('close')
|
163
|
-
logger.debug(circuit_closed_message)
|
164
208
|
end
|
165
209
|
|
166
210
|
def half_open?
|
167
|
-
circuit_store.key?(half_open_storage_key)
|
211
|
+
@circuit_store.key?(@half_open_storage_key)
|
168
212
|
end
|
169
213
|
|
170
214
|
def success!
|
171
215
|
increment_and_notify_event('success')
|
172
|
-
logger.debug(circuit_success_message)
|
173
216
|
|
174
217
|
close! if half_open?
|
175
218
|
end
|
176
219
|
|
177
220
|
def failure!
|
178
221
|
increment_and_notify_event('failure')
|
179
|
-
logger.debug(circuit_failure_message)
|
180
222
|
|
181
223
|
if half_open?
|
182
224
|
half_open_failure
|
@@ -187,20 +229,31 @@ class Circuitbox
|
|
187
229
|
|
188
230
|
def skipped!
|
189
231
|
notify_event('skipped')
|
190
|
-
logger.debug(circuit_skipped_message)
|
191
232
|
end
|
192
233
|
|
193
234
|
# Send event notification to notifier
|
194
235
|
def notify_event(event)
|
195
|
-
notifier.notify(service, event)
|
236
|
+
@notifier.notify(@service, event)
|
196
237
|
end
|
197
238
|
|
198
239
|
# Increment stat store and send notification
|
199
240
|
def increment_and_notify_event(event)
|
200
|
-
|
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)
|
201
244
|
notify_event(event)
|
202
245
|
end
|
203
246
|
|
247
|
+
def stat_storage_key(event, aligned_time = align_time_to_window)
|
248
|
+
"circuits:#{@service}:stats:#{aligned_time}:#{event}"
|
249
|
+
end
|
250
|
+
|
251
|
+
# return time representation in seconds
|
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
|
255
|
+
end
|
256
|
+
|
204
257
|
def check_sleep_window
|
205
258
|
sleep_window = option_value(:sleep_window)
|
206
259
|
time_window = option_value(:time_window)
|
@@ -208,27 +261,16 @@ class Circuitbox
|
|
208
261
|
|
209
262
|
warning_message = "sleep_window: #{sleep_window} is shorter than time_window: #{time_window}, "\
|
210
263
|
"the error_rate would not be reset after a sleep."
|
211
|
-
notifier.notify_warning(service, warning_message)
|
212
|
-
warn("Circuit: #{service}, Warning: #{warning_message}")
|
213
|
-
end
|
214
|
-
|
215
|
-
def stat_storage_key(event)
|
216
|
-
"circuits:#{service}:stats:#{align_time_to_window}:#{event}"
|
264
|
+
@notifier.notify_warning(@service, warning_message)
|
265
|
+
warn("Circuit: #{@service}, Warning: #{warning_message}")
|
217
266
|
end
|
218
267
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
def open_storage_key
|
227
|
-
@open_storage_key ||= "circuits:#{service}:open"
|
228
|
-
end
|
229
|
-
|
230
|
-
def half_open_storage_key
|
231
|
-
@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
|
232
274
|
end
|
233
275
|
end
|
234
276
|
end
|
@@ -1,16 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'memory_store'
|
4
|
-
require_relative 'timer'
|
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,22 +37,16 @@ class Circuitbox
|
|
30
37
|
end
|
31
38
|
end
|
32
39
|
|
33
|
-
def default_logger
|
34
|
-
@default_logger ||= if defined?(Rails)
|
35
|
-
Rails.logger
|
36
|
-
else
|
37
|
-
Logger.new($stdout)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
40
|
private
|
42
41
|
|
43
|
-
def
|
44
|
-
@
|
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
|
45
46
|
end
|
46
47
|
|
47
48
|
def clear_cached_circuits!
|
48
|
-
@cached_circuits = {}
|
49
|
+
@cached_circuits_mutex.synchronize { @cached_circuits = {} }
|
49
50
|
end
|
50
51
|
end
|
51
52
|
end
|
@@ -8,13 +8,13 @@ class Circuitbox
|
|
8
8
|
super()
|
9
9
|
@service = service
|
10
10
|
@original = exception
|
11
|
-
#
|
11
|
+
# We copy over the original exceptions backtrace if there is one
|
12
12
|
backtrace = exception.backtrace
|
13
13
|
set_backtrace(backtrace) unless backtrace.empty?
|
14
14
|
end
|
15
15
|
|
16
16
|
def to_s
|
17
|
-
"#{self.class.
|
17
|
+
"#{self.class}: Service #{service.inspect} was unavailable (original: #{original})"
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -21,7 +21,7 @@ class Circuitbox
|
|
21
21
|
open_circuit: lambda do |response|
|
22
22
|
# response.status:
|
23
23
|
# nil -> connection could not be established, or failed very hard
|
24
|
-
# 5xx -> non recoverable server error,
|
24
|
+
# 5xx -> non recoverable server error, opposed to 4xx which are client errors
|
25
25
|
response.status.nil? || (response.status >= 500 && response.status <= 599)
|
26
26
|
end,
|
27
27
|
default_value: ->(service_response, exception) { NullResponse.new(service_response, exception) },
|
@@ -34,10 +34,7 @@ class Circuitbox
|
|
34
34
|
}.freeze
|
35
35
|
|
36
36
|
DEFAULT_EXCEPTIONS = [
|
37
|
-
|
38
|
-
# Faraday >= 0.9.0 defines Faraday::TimeoutError and this can be used for all versions up to 1.0.0 that
|
39
|
-
# also define and raise Faraday::Error::TimeoutError as Faraday::TimeoutError is an ancestor
|
40
|
-
defined?(Faraday::TimeoutError) ? Faraday::TimeoutError : Faraday::Error::TimeoutError,
|
37
|
+
Faraday::TimeoutError,
|
41
38
|
RequestFailed
|
42
39
|
].freeze
|
43
40
|
|
@@ -45,8 +42,6 @@ class Circuitbox
|
|
45
42
|
exceptions: DEFAULT_EXCEPTIONS
|
46
43
|
}.freeze
|
47
44
|
|
48
|
-
attr_reader :opts
|
49
|
-
|
50
45
|
def initialize(app, opts = {})
|
51
46
|
@app = app
|
52
47
|
@opts = DEFAULT_OPTIONS.merge(opts)
|
@@ -70,12 +65,12 @@ class Circuitbox
|
|
70
65
|
private
|
71
66
|
|
72
67
|
def call_default_value(response, exception)
|
73
|
-
default_value = opts[:default_value]
|
68
|
+
default_value = @opts[:default_value]
|
74
69
|
default_value.respond_to?(:call) ? default_value.call(response, exception) : default_value
|
75
70
|
end
|
76
71
|
|
77
72
|
def open_circuit?(response)
|
78
|
-
opts[:open_circuit].call(response)
|
73
|
+
@opts[:open_circuit].call(response)
|
79
74
|
end
|
80
75
|
|
81
76
|
def circuit_open_value(env, service_response, exception)
|
@@ -83,12 +78,12 @@ class Circuitbox
|
|
83
78
|
end
|
84
79
|
|
85
80
|
def circuit(env)
|
86
|
-
identifier = opts[:identifier]
|
81
|
+
identifier = @opts[:identifier]
|
87
82
|
id = identifier.respond_to?(:call) ? identifier.call(env) : identifier
|
88
83
|
|
89
|
-
Circuitbox.circuit(id, opts[:circuit_breaker_options])
|
84
|
+
Circuitbox.circuit(id, @opts[:circuit_breaker_options])
|
90
85
|
end
|
91
86
|
end
|
92
87
|
end
|
93
88
|
|
94
|
-
Faraday::Middleware.register_middleware
|
89
|
+
Faraday::Middleware.register_middleware(circuitbox: Circuitbox::FaradayMiddleware)
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative '../time_helper/monotonic'
|
4
4
|
|
5
5
|
class Circuitbox
|
6
6
|
class MemoryStore
|
7
7
|
class Container
|
8
|
-
include
|
8
|
+
include TimeHelper::Monotonic
|
9
9
|
|
10
10
|
attr_accessor :value
|
11
11
|
|
@@ -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)
|
@@ -53,9 +60,7 @@ class Circuitbox
|
|
53
60
|
|
54
61
|
private
|
55
62
|
|
56
|
-
def fetch_container(key)
|
57
|
-
current_time = current_second
|
58
|
-
|
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,13 +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
|
-
|
77
|
-
container.value
|
78
|
-
end
|
79
|
-
|
80
78
|
def compact(current_time)
|
81
79
|
@store.delete_if { |_, value| value.expired_at?(current_time) }
|
82
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,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Circuitbox
|
4
|
-
|
4
|
+
module Notifier
|
5
5
|
class Null
|
6
|
-
def notify(
|
6
|
+
def notify(_circuit_name, _event); end
|
7
7
|
|
8
|
-
def notify_warning(
|
8
|
+
def notify_warning(_circuit_name, _message); end
|
9
9
|
|
10
|
-
def
|
10
|
+
def notify_run(_circuit_name)
|
11
|
+
yield
|
12
|
+
end
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
data/lib/circuitbox/version.rb
CHANGED
data/lib/circuitbox.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'logger'
|
4
|
-
|
5
3
|
require_relative 'circuitbox/version'
|
6
4
|
require_relative 'circuitbox/circuit_breaker'
|
7
5
|
require_relative 'circuitbox/errors/error'
|
@@ -10,15 +8,15 @@ require_relative 'circuitbox/errors/service_failure_error'
|
|
10
8
|
require_relative 'circuitbox/configuration'
|
11
9
|
|
12
10
|
class Circuitbox
|
13
|
-
|
14
|
-
include Configuration
|
11
|
+
extend Configuration
|
15
12
|
|
13
|
+
class << self
|
16
14
|
def circuit(service_name, options, &block)
|
17
|
-
circuit = (
|
15
|
+
circuit = find_or_create_circuit_breaker(service_name, options)
|
18
16
|
|
19
17
|
return circuit unless block
|
20
18
|
|
21
|
-
circuit.run(
|
19
|
+
circuit.run(exception: false, &block)
|
22
20
|
end
|
23
21
|
end
|
24
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,14 @@ 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'
|
118
112
|
- !ruby/object:Gem::Dependency
|
119
113
|
name: moneta
|
120
114
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,89 +152,61 @@ dependencies:
|
|
158
152
|
- !ruby/object:Gem::Version
|
159
153
|
version: '13.0'
|
160
154
|
- !ruby/object:Gem::Dependency
|
161
|
-
name:
|
162
|
-
requirement: !ruby/object:Gem::Requirement
|
163
|
-
requirements:
|
164
|
-
- - '='
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: 1.8.1
|
167
|
-
type: :development
|
168
|
-
prerelease: false
|
169
|
-
version_requirements: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - '='
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: 1.8.1
|
174
|
-
- !ruby/object:Gem::Dependency
|
175
|
-
name: rubocop-minitest
|
176
|
-
requirement: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - '='
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: 0.10.3
|
181
|
-
type: :development
|
182
|
-
prerelease: false
|
183
|
-
version_requirements: !ruby/object:Gem::Requirement
|
184
|
-
requirements:
|
185
|
-
- - '='
|
186
|
-
- !ruby/object:Gem::Version
|
187
|
-
version: 0.10.3
|
188
|
-
- !ruby/object:Gem::Dependency
|
189
|
-
name: rubocop-performance
|
155
|
+
name: timecop
|
190
156
|
requirement: !ruby/object:Gem::Requirement
|
191
157
|
requirements:
|
192
|
-
- -
|
158
|
+
- - "~>"
|
193
159
|
- !ruby/object:Gem::Version
|
194
|
-
version:
|
160
|
+
version: '0.9'
|
195
161
|
type: :development
|
196
162
|
prerelease: false
|
197
163
|
version_requirements: !ruby/object:Gem::Requirement
|
198
164
|
requirements:
|
199
|
-
- -
|
165
|
+
- - "~>"
|
200
166
|
- !ruby/object:Gem::Version
|
201
|
-
version:
|
167
|
+
version: '0.9'
|
202
168
|
- !ruby/object:Gem::Dependency
|
203
|
-
name:
|
169
|
+
name: typhoeus
|
204
170
|
requirement: !ruby/object:Gem::Requirement
|
205
171
|
requirements:
|
206
|
-
- -
|
172
|
+
- - "~>"
|
207
173
|
- !ruby/object:Gem::Version
|
208
|
-
version:
|
174
|
+
version: '1.4'
|
209
175
|
type: :development
|
210
176
|
prerelease: false
|
211
177
|
version_requirements: !ruby/object:Gem::Requirement
|
212
178
|
requirements:
|
213
|
-
- -
|
179
|
+
- - "~>"
|
214
180
|
- !ruby/object:Gem::Version
|
215
|
-
version:
|
181
|
+
version: '1.4'
|
216
182
|
- !ruby/object:Gem::Dependency
|
217
|
-
name:
|
183
|
+
name: webrick
|
218
184
|
requirement: !ruby/object:Gem::Requirement
|
219
185
|
requirements:
|
220
186
|
- - "~>"
|
221
187
|
- !ruby/object:Gem::Version
|
222
|
-
version: '
|
188
|
+
version: '1.7'
|
223
189
|
type: :development
|
224
190
|
prerelease: false
|
225
191
|
version_requirements: !ruby/object:Gem::Requirement
|
226
192
|
requirements:
|
227
193
|
- - "~>"
|
228
194
|
- !ruby/object:Gem::Version
|
229
|
-
version: '
|
195
|
+
version: '1.7'
|
230
196
|
- !ruby/object:Gem::Dependency
|
231
|
-
name:
|
197
|
+
name: yard
|
232
198
|
requirement: !ruby/object:Gem::Requirement
|
233
199
|
requirements:
|
234
200
|
- - "~>"
|
235
201
|
- !ruby/object:Gem::Version
|
236
|
-
version:
|
202
|
+
version: 0.9.26
|
237
203
|
type: :development
|
238
204
|
prerelease: false
|
239
205
|
version_requirements: !ruby/object:Gem::Requirement
|
240
206
|
requirements:
|
241
207
|
- - "~>"
|
242
208
|
- !ruby/object:Gem::Version
|
243
|
-
version:
|
209
|
+
version: 0.9.26
|
244
210
|
description:
|
245
211
|
email:
|
246
212
|
- fahimfmf@gmail.com
|
@@ -252,7 +218,6 @@ files:
|
|
252
218
|
- README.md
|
253
219
|
- lib/circuitbox.rb
|
254
220
|
- lib/circuitbox/circuit_breaker.rb
|
255
|
-
- lib/circuitbox/circuit_breaker/logger_messages.rb
|
256
221
|
- lib/circuitbox/configuration.rb
|
257
222
|
- lib/circuitbox/errors/error.rb
|
258
223
|
- lib/circuitbox/errors/open_circuit_error.rb
|
@@ -261,10 +226,10 @@ files:
|
|
261
226
|
- lib/circuitbox/faraday_middleware.rb
|
262
227
|
- lib/circuitbox/memory_store.rb
|
263
228
|
- lib/circuitbox/memory_store/container.rb
|
264
|
-
- lib/circuitbox/memory_store/monotonic_time.rb
|
265
229
|
- lib/circuitbox/notifier/active_support.rb
|
266
230
|
- lib/circuitbox/notifier/null.rb
|
267
|
-
- lib/circuitbox/
|
231
|
+
- lib/circuitbox/time_helper/monotonic.rb
|
232
|
+
- lib/circuitbox/time_helper/real.rb
|
268
233
|
- lib/circuitbox/version.rb
|
269
234
|
homepage: https://github.com/yammer/circuitbox
|
270
235
|
licenses:
|
@@ -273,6 +238,7 @@ metadata:
|
|
273
238
|
bug_tracker_uri: https://github.com/yammer/circuitbox/issues
|
274
239
|
changelog_uri: https://github.com/yammer/circuitbox/blob/main/CHANGELOG.md
|
275
240
|
source_code_uri: https://github.com/yammer/circuitbox
|
241
|
+
rubygems_mfa_required: 'true'
|
276
242
|
post_install_message:
|
277
243
|
rdoc_options: []
|
278
244
|
require_paths:
|
@@ -281,14 +247,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
281
247
|
requirements:
|
282
248
|
- - ">="
|
283
249
|
- !ruby/object:Gem::Version
|
284
|
-
version: 2.
|
250
|
+
version: 2.6.0
|
285
251
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
286
252
|
requirements:
|
287
253
|
- - ">"
|
288
254
|
- !ruby/object:Gem::Version
|
289
255
|
version: 1.3.1
|
290
256
|
requirements: []
|
291
|
-
rubygems_version: 3.1.
|
257
|
+
rubygems_version: 3.1.6
|
292
258
|
signing_key:
|
293
259
|
specification_version: 4
|
294
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
|
data/lib/circuitbox/timer.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Circuitbox
|
4
|
-
class Timer
|
5
|
-
class Monotonic
|
6
|
-
class << self
|
7
|
-
def supported?
|
8
|
-
defined?(Process::CLOCK_MONOTONIC)
|
9
|
-
end
|
10
|
-
|
11
|
-
def now
|
12
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
class Default
|
18
|
-
class << self
|
19
|
-
def supported?
|
20
|
-
true
|
21
|
-
end
|
22
|
-
|
23
|
-
def now
|
24
|
-
Time.now.to_f
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
class << self
|
30
|
-
def measure(service, notifier, metric_name)
|
31
|
-
before = now
|
32
|
-
result = yield
|
33
|
-
total_time = now - before
|
34
|
-
notifier.metric_gauge(service, metric_name, total_time)
|
35
|
-
result
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
if Monotonic.supported?
|
41
|
-
def now
|
42
|
-
Monotonic.now
|
43
|
-
end
|
44
|
-
else
|
45
|
-
def now
|
46
|
-
Default.now
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|