circuitbox 2.0.0.pre4 → 2.0.0.pre5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|