breaker_machines 0.2.1 → 0.4.0
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 +75 -58
- data/lib/breaker_machines/async_support.rb +3 -3
- data/lib/breaker_machines/cascading_circuit.rb +175 -0
- data/lib/breaker_machines/circuit/execution.rb +4 -8
- data/lib/breaker_machines/circuit/introspection.rb +35 -20
- data/lib/breaker_machines/circuit/state_management.rb +5 -4
- data/lib/breaker_machines/circuit.rb +0 -1
- data/lib/breaker_machines/console.rb +12 -12
- data/lib/breaker_machines/dsl/cascading_circuit_builder.rb +20 -0
- data/lib/breaker_machines/dsl/circuit_builder.rb +209 -0
- data/lib/breaker_machines/dsl/hedged_builder.rb +21 -0
- data/lib/breaker_machines/dsl/parallel_fallback_wrapper.rb +20 -0
- data/lib/breaker_machines/dsl.rb +26 -236
- data/lib/breaker_machines/errors.rb +10 -0
- data/lib/breaker_machines/registry.rb +3 -3
- data/lib/breaker_machines/storage/backend_state.rb +69 -0
- data/lib/breaker_machines/storage/base.rb +5 -0
- data/lib/breaker_machines/storage/bucket_memory.rb +13 -3
- data/lib/breaker_machines/storage/cache.rb +10 -3
- data/lib/breaker_machines/storage/fallback_chain.rb +294 -0
- data/lib/breaker_machines/storage/memory.rb +13 -3
- data/lib/breaker_machines/storage/null.rb +9 -0
- data/lib/breaker_machines/types.rb +41 -0
- data/lib/breaker_machines/version.rb +1 -1
- data/lib/breaker_machines.rb +12 -0
- data/sig/README.md +3 -3
- metadata +14 -6
@@ -16,10 +16,10 @@ module BreakerMachines
|
|
16
16
|
data = @cache.read(status_key(circuit_name))
|
17
17
|
return nil unless data
|
18
18
|
|
19
|
-
|
19
|
+
BreakerMachines::Status.new(
|
20
20
|
status: data[:status].to_sym,
|
21
21
|
opened_at: data[:opened_at]
|
22
|
-
|
22
|
+
)
|
23
23
|
end
|
24
24
|
|
25
25
|
def set_status(circuit_name, status, opened_at = nil)
|
@@ -94,6 +94,13 @@ module BreakerMachines
|
|
94
94
|
events.last(limit)
|
95
95
|
end
|
96
96
|
|
97
|
+
def with_timeout(_timeout_ms)
|
98
|
+
# Rails cache operations should rely on their own underlying timeouts
|
99
|
+
# Using Ruby's Timeout.timeout is dangerous and can cause deadlocks
|
100
|
+
# For Redis cache stores, configure connect_timeout and read_timeout instead
|
101
|
+
yield
|
102
|
+
end
|
103
|
+
|
97
104
|
private
|
98
105
|
|
99
106
|
def increment_counter(key)
|
@@ -155,7 +162,7 @@ module BreakerMachines
|
|
155
162
|
end
|
156
163
|
|
157
164
|
def monotonic_time
|
158
|
-
|
165
|
+
BreakerMachines.monotonic_time
|
159
166
|
end
|
160
167
|
end
|
161
168
|
end
|
@@ -0,0 +1,294 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BreakerMachines
|
4
|
+
module Storage
|
5
|
+
# Apocalypse-resistant storage backend that tries multiple storage backends in sequence
|
6
|
+
# Falls back to the next storage backend when the current one times out or fails
|
7
|
+
#
|
8
|
+
# NOTE: For DRb (distributed Ruby) environments, only :cache backend with external
|
9
|
+
# cache stores (Redis, Memcached) will work properly. Memory-based backends (:memory,
|
10
|
+
# :bucket_memory) are incompatible with DRb as they don't share state between processes.
|
11
|
+
class FallbackChain < Base
|
12
|
+
attr_reader :storage_configs, :storage_instances, :backend_states
|
13
|
+
|
14
|
+
def initialize(storage_configs, circuit_breaker_threshold: 3, circuit_breaker_timeout: 30, **)
|
15
|
+
super(**)
|
16
|
+
@storage_configs = normalize_storage_configs(storage_configs)
|
17
|
+
@storage_instances = {}
|
18
|
+
@circuit_breaker_threshold = circuit_breaker_threshold
|
19
|
+
@circuit_breaker_timeout = circuit_breaker_timeout
|
20
|
+
@backend_states = @storage_configs.to_h do |config|
|
21
|
+
[config[:backend],
|
22
|
+
BackendState.new(config[:backend], threshold: @circuit_breaker_threshold, timeout: @circuit_breaker_timeout)]
|
23
|
+
end
|
24
|
+
validate_configs!
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_status(circuit_name)
|
28
|
+
execute_with_fallback(:get_status, circuit_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_status(circuit_name, status, opened_at = nil)
|
32
|
+
execute_with_fallback(:set_status, circuit_name, status, opened_at)
|
33
|
+
end
|
34
|
+
|
35
|
+
def record_success(circuit_name, duration)
|
36
|
+
execute_with_fallback(:record_success, circuit_name, duration)
|
37
|
+
end
|
38
|
+
|
39
|
+
def record_failure(circuit_name, duration)
|
40
|
+
execute_with_fallback(:record_failure, circuit_name, duration)
|
41
|
+
end
|
42
|
+
|
43
|
+
def success_count(circuit_name, window_seconds)
|
44
|
+
execute_with_fallback(:success_count, circuit_name, window_seconds)
|
45
|
+
end
|
46
|
+
|
47
|
+
def failure_count(circuit_name, window_seconds)
|
48
|
+
execute_with_fallback(:failure_count, circuit_name, window_seconds)
|
49
|
+
end
|
50
|
+
|
51
|
+
def clear(circuit_name)
|
52
|
+
execute_with_fallback(:clear, circuit_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def clear_all
|
56
|
+
execute_with_fallback(:clear_all)
|
57
|
+
end
|
58
|
+
|
59
|
+
def record_event_with_details(circuit_name, type, duration, error: nil, new_state: nil)
|
60
|
+
execute_with_fallback(:record_event_with_details, circuit_name, type, duration, error: error,
|
61
|
+
new_state: new_state)
|
62
|
+
end
|
63
|
+
|
64
|
+
def event_log(circuit_name, limit)
|
65
|
+
execute_with_fallback(:event_log, circuit_name, limit)
|
66
|
+
end
|
67
|
+
|
68
|
+
def with_timeout(_timeout_ms)
|
69
|
+
# FallbackChain doesn't use timeout directly - each backend handles its own
|
70
|
+
yield
|
71
|
+
end
|
72
|
+
|
73
|
+
def cleanup!
|
74
|
+
storage_instances.each_value do |instance|
|
75
|
+
instance.clear_all if instance.respond_to?(:clear_all)
|
76
|
+
end
|
77
|
+
storage_instances.clear
|
78
|
+
backend_states.each_value(&:reset)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def execute_with_fallback(method, *args, **kwargs)
|
84
|
+
chain_started_at = BreakerMachines.monotonic_time
|
85
|
+
attempted_backends = []
|
86
|
+
|
87
|
+
storage_configs.each_with_index do |config, index|
|
88
|
+
backend_type = config[:backend]
|
89
|
+
attempted_backends << backend_type
|
90
|
+
backend_state = backend_states[backend_type]
|
91
|
+
|
92
|
+
if backend_state.unhealthy_due_to_timeout?
|
93
|
+
emit_backend_skipped_notification(backend_type, method, index)
|
94
|
+
next
|
95
|
+
end
|
96
|
+
|
97
|
+
begin
|
98
|
+
backend = get_backend_instance(backend_type)
|
99
|
+
started_at = BreakerMachines.monotonic_time
|
100
|
+
|
101
|
+
result = backend.with_timeout(config[:timeout]) do
|
102
|
+
if kwargs.any?
|
103
|
+
backend.send(method, *args, **kwargs)
|
104
|
+
else
|
105
|
+
backend.send(method, *args)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
duration_ms = ((BreakerMachines.monotonic_time - started_at) * 1000).round(2)
|
110
|
+
emit_operation_success_notification(backend_type, method, duration_ms, index)
|
111
|
+
reset_backend_failures(backend_type)
|
112
|
+
|
113
|
+
chain_duration_ms = ((BreakerMachines.monotonic_time - chain_started_at) * 1000).round(2)
|
114
|
+
emit_chain_success_notification(method, attempted_backends, backend_type, chain_duration_ms)
|
115
|
+
|
116
|
+
return result
|
117
|
+
rescue BreakerMachines::StorageTimeoutError, BreakerMachines::StorageError, StandardError => e
|
118
|
+
duration_ms = ((BreakerMachines.monotonic_time - started_at) * 1000).round(2)
|
119
|
+
record_backend_failure(backend_type, e, duration_ms)
|
120
|
+
emit_fallback_notification(backend_type, e, duration_ms, index)
|
121
|
+
|
122
|
+
if index == storage_configs.size - 1
|
123
|
+
chain_duration_ms = ((BreakerMachines.monotonic_time - chain_started_at) * 1000).round(2)
|
124
|
+
emit_chain_failure_notification(method, attempted_backends, chain_duration_ms)
|
125
|
+
raise e
|
126
|
+
end
|
127
|
+
|
128
|
+
next
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
chain_duration_ms = ((BreakerMachines.monotonic_time - chain_started_at) * 1000).round(2)
|
133
|
+
emit_chain_failure_notification(method, attempted_backends, chain_duration_ms)
|
134
|
+
raise BreakerMachines::StorageError, 'All storage backends are unhealthy'
|
135
|
+
end
|
136
|
+
|
137
|
+
def get_backend_instance(backend_type)
|
138
|
+
storage_instances[backend_type] ||= create_backend_instance(backend_type)
|
139
|
+
end
|
140
|
+
|
141
|
+
def create_backend_instance(backend_type)
|
142
|
+
case backend_type
|
143
|
+
when :memory
|
144
|
+
Memory.new
|
145
|
+
when :bucket_memory
|
146
|
+
BucketMemory.new
|
147
|
+
when :cache
|
148
|
+
Cache.new
|
149
|
+
when :null
|
150
|
+
Null.new
|
151
|
+
else
|
152
|
+
# Allow custom backend classes
|
153
|
+
raise ConfigurationError, "Unknown storage backend: #{backend_type}" unless backend_type.is_a?(Class)
|
154
|
+
|
155
|
+
backend_type.new
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def record_backend_failure(backend_type, _error, _duration_ms)
|
161
|
+
backend_state = backend_states[backend_type]
|
162
|
+
return unless backend_state
|
163
|
+
|
164
|
+
previous_health = backend_state.health_name
|
165
|
+
backend_state.record_failure
|
166
|
+
new_health = backend_state.health_name
|
167
|
+
|
168
|
+
if new_health != previous_health
|
169
|
+
emit_backend_health_change_notification(backend_type, previous_health, new_health,
|
170
|
+
backend_state.failure_count)
|
171
|
+
end
|
172
|
+
rescue StandardError => e
|
173
|
+
# Don't let failure recording cause the whole chain to hang
|
174
|
+
Rails.logger&.error("FallbackChain: Failed to record backend failure: #{e.message}")
|
175
|
+
end
|
176
|
+
|
177
|
+
def reset_backend_failures(backend_type)
|
178
|
+
backend_state = backend_states[backend_type]
|
179
|
+
return unless backend_state&.unhealthy?
|
180
|
+
|
181
|
+
previous_health = backend_state.health_name
|
182
|
+
backend_state.reset
|
183
|
+
new_health = backend_state.health_name
|
184
|
+
|
185
|
+
return unless new_health != previous_health
|
186
|
+
|
187
|
+
emit_backend_health_change_notification(backend_type, previous_health, new_health, 0)
|
188
|
+
end
|
189
|
+
|
190
|
+
def emit_fallback_notification(backend_type, error, duration_ms, backend_index)
|
191
|
+
ActiveSupport::Notifications.instrument(
|
192
|
+
'storage_fallback.breaker_machines',
|
193
|
+
backend: backend_type,
|
194
|
+
error_class: error.class.name,
|
195
|
+
error_message: error.message,
|
196
|
+
duration_ms: duration_ms,
|
197
|
+
backend_index: backend_index,
|
198
|
+
next_backend: storage_configs[backend_index + 1]&.dig(:backend)
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
202
|
+
def emit_operation_success_notification(backend_type, method, duration_ms, backend_index)
|
203
|
+
ActiveSupport::Notifications.instrument(
|
204
|
+
'storage_operation.breaker_machines',
|
205
|
+
backend: backend_type,
|
206
|
+
operation: method,
|
207
|
+
duration_ms: duration_ms,
|
208
|
+
backend_index: backend_index,
|
209
|
+
success: true
|
210
|
+
)
|
211
|
+
end
|
212
|
+
|
213
|
+
def emit_backend_skipped_notification(backend_type, method, backend_index)
|
214
|
+
backend_state = backend_states[backend_type]
|
215
|
+
ActiveSupport::Notifications.instrument(
|
216
|
+
'storage_backend_skipped.breaker_machines',
|
217
|
+
backend: backend_type,
|
218
|
+
operation: method,
|
219
|
+
backend_index: backend_index,
|
220
|
+
reason: 'unhealthy',
|
221
|
+
unhealthy_until: backend_state&.instance_variable_get(:@unhealthy_until)
|
222
|
+
)
|
223
|
+
end
|
224
|
+
|
225
|
+
def emit_backend_health_change_notification(backend_type, previous_state, new_state, failure_count)
|
226
|
+
backend_state = backend_states[backend_type]
|
227
|
+
ActiveSupport::Notifications.instrument(
|
228
|
+
'storage_backend_health.breaker_machines',
|
229
|
+
backend: backend_type,
|
230
|
+
previous_state: previous_state,
|
231
|
+
new_state: new_state,
|
232
|
+
failure_count: failure_count,
|
233
|
+
threshold: backend_state&.instance_variable_get(:@threshold),
|
234
|
+
recovery_time: new_state == :unhealthy ? backend_state&.instance_variable_get(:@unhealthy_until) : nil
|
235
|
+
)
|
236
|
+
end
|
237
|
+
|
238
|
+
def emit_chain_success_notification(method, attempted_backends, successful_backend, duration_ms)
|
239
|
+
ActiveSupport::Notifications.instrument(
|
240
|
+
'storage_chain_operation.breaker_machines',
|
241
|
+
operation: method,
|
242
|
+
attempted_backends: attempted_backends,
|
243
|
+
successful_backend: successful_backend,
|
244
|
+
duration_ms: duration_ms,
|
245
|
+
success: true,
|
246
|
+
fallback_count: attempted_backends.index(successful_backend)
|
247
|
+
)
|
248
|
+
end
|
249
|
+
|
250
|
+
def emit_chain_failure_notification(method, attempted_backends, duration_ms)
|
251
|
+
ActiveSupport::Notifications.instrument(
|
252
|
+
'storage_chain_operation.breaker_machines',
|
253
|
+
operation: method,
|
254
|
+
attempted_backends: attempted_backends,
|
255
|
+
successful_backend: nil,
|
256
|
+
duration_ms: duration_ms,
|
257
|
+
success: false,
|
258
|
+
fallback_count: attempted_backends.size
|
259
|
+
)
|
260
|
+
end
|
261
|
+
|
262
|
+
def normalize_storage_configs(configs)
|
263
|
+
return configs if configs.is_a?(Array)
|
264
|
+
|
265
|
+
# Convert hash format to array format
|
266
|
+
unless configs.is_a?(Hash)
|
267
|
+
raise ConfigurationError, "Storage configs must be Array or Hash, got: #{configs.class}"
|
268
|
+
end
|
269
|
+
|
270
|
+
configs.map do |_key, value|
|
271
|
+
if value.is_a?(Hash)
|
272
|
+
value
|
273
|
+
else
|
274
|
+
{ backend: value, timeout: 5 }
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def validate_configs!
|
280
|
+
raise ConfigurationError, 'Storage configs cannot be empty' if storage_configs.empty?
|
281
|
+
|
282
|
+
storage_configs.each_with_index do |config, index|
|
283
|
+
unless config.is_a?(Hash) && config[:backend] && config[:timeout]
|
284
|
+
raise ConfigurationError, "Invalid storage config at index #{index}: #{config}"
|
285
|
+
end
|
286
|
+
|
287
|
+
unless config[:timeout].is_a?(Numeric) && config[:timeout].positive?
|
288
|
+
raise ConfigurationError, "Timeout must be a positive number, got: #{config[:timeout]}"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
@@ -6,6 +6,10 @@ require 'concurrent/array'
|
|
6
6
|
module BreakerMachines
|
7
7
|
module Storage
|
8
8
|
# High-performance in-memory storage backend with thread-safe operations
|
9
|
+
#
|
10
|
+
# WARNING: This storage backend is NOT compatible with DRb (distributed Ruby)
|
11
|
+
# environments as memory is not shared between processes. Use Cache backend
|
12
|
+
# with an external cache store (Redis, Memcached) for distributed setups.
|
9
13
|
class Memory < Base
|
10
14
|
def initialize(**options)
|
11
15
|
super
|
@@ -19,10 +23,10 @@ module BreakerMachines
|
|
19
23
|
circuit_data = @circuits[circuit_name]
|
20
24
|
return nil unless circuit_data
|
21
25
|
|
22
|
-
|
26
|
+
BreakerMachines::Status.new(
|
23
27
|
status: circuit_data[:status],
|
24
28
|
opened_at: circuit_data[:opened_at]
|
25
|
-
|
29
|
+
)
|
26
30
|
end
|
27
31
|
|
28
32
|
def set_status(circuit_name, status, opened_at = nil)
|
@@ -87,6 +91,12 @@ module BreakerMachines
|
|
87
91
|
events.last(limit).map(&:dup)
|
88
92
|
end
|
89
93
|
|
94
|
+
def with_timeout(_timeout_ms)
|
95
|
+
# Memory operations should be instant, but we'll still respect the timeout
|
96
|
+
# This is more for consistency and to catch any potential deadlocks
|
97
|
+
yield
|
98
|
+
end
|
99
|
+
|
90
100
|
private
|
91
101
|
|
92
102
|
def record_event(circuit_name, type, duration)
|
@@ -120,7 +130,7 @@ module BreakerMachines
|
|
120
130
|
end
|
121
131
|
|
122
132
|
def monotonic_time
|
123
|
-
|
133
|
+
BreakerMachines.monotonic_time
|
124
134
|
end
|
125
135
|
end
|
126
136
|
end
|
@@ -40,6 +40,15 @@ module BreakerMachines
|
|
40
40
|
def record_event_with_details(_circuit_name, _event_type, _duration, _metadata = {})
|
41
41
|
# No-op
|
42
42
|
end
|
43
|
+
|
44
|
+
def clear_all
|
45
|
+
# No-op
|
46
|
+
end
|
47
|
+
|
48
|
+
def with_timeout(_timeout_ms)
|
49
|
+
# Null storage always succeeds instantly - perfect for fail-open scenarios
|
50
|
+
yield
|
51
|
+
end
|
43
52
|
end
|
44
53
|
end
|
45
54
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BreakerMachines
|
4
|
+
# Represents the status of a circuit from storage
|
5
|
+
# @return [Symbol] status - the circuit status (:open, :closed, :half_open)
|
6
|
+
# @return [Float, nil] opened_at - the monotonic time when the circuit was opened
|
7
|
+
Status = Data.define(:status, :opened_at)
|
8
|
+
|
9
|
+
# Represents statistical information about a circuit
|
10
|
+
Stats = Data.define(
|
11
|
+
:state,
|
12
|
+
:failure_count,
|
13
|
+
:success_count,
|
14
|
+
:last_failure_at,
|
15
|
+
:opened_at,
|
16
|
+
:half_open_attempts,
|
17
|
+
:half_open_successes
|
18
|
+
)
|
19
|
+
|
20
|
+
# Represents information about the last error that occurred
|
21
|
+
# @return [String] error_class - the error class name
|
22
|
+
# @return [String] message - the error message
|
23
|
+
# @return [Float, nil] occurred_at - the monotonic time when the error occurred
|
24
|
+
ErrorInfo = Data.define(:error_class, :message, :occurred_at)
|
25
|
+
|
26
|
+
# Represents cascade information for cascading circuits
|
27
|
+
CascadeInfo = Data.define(
|
28
|
+
:dependent_circuits,
|
29
|
+
:emergency_protocol,
|
30
|
+
:cascade_triggered_at,
|
31
|
+
:dependent_status
|
32
|
+
)
|
33
|
+
|
34
|
+
# Represents an event in the circuit's event log
|
35
|
+
# @return [Symbol] type - the event type (:success, :failure, :state_change)
|
36
|
+
# @return [Float] timestamp - the monotonic timestamp
|
37
|
+
# @return [Float] duration - the duration in milliseconds
|
38
|
+
# @return [String, nil] error - the error message if applicable
|
39
|
+
# @return [Symbol, nil] new_state - the new state if this was a state change
|
40
|
+
Event = Data.define(:type, :timestamp, :duration, :error, :new_state)
|
41
|
+
end
|
data/lib/breaker_machines.rb
CHANGED
@@ -3,11 +3,14 @@
|
|
3
3
|
require 'zeitwerk'
|
4
4
|
require 'active_support'
|
5
5
|
require 'active_support/core_ext'
|
6
|
+
require 'state_machines'
|
6
7
|
require_relative 'breaker_machines/errors'
|
8
|
+
require_relative 'breaker_machines/types'
|
7
9
|
|
8
10
|
loader = Zeitwerk::Loader.for_gem
|
9
11
|
loader.inflector.inflect('dsl' => 'DSL')
|
10
12
|
loader.ignore("#{__dir__}/breaker_machines/errors.rb")
|
13
|
+
loader.ignore("#{__dir__}/breaker_machines/types.rb")
|
11
14
|
loader.ignore("#{__dir__}/breaker_machines/console.rb")
|
12
15
|
loader.ignore("#{__dir__}/breaker_machines/async_support.rb")
|
13
16
|
loader.ignore("#{__dir__}/breaker_machines/hedged_async_support.rb")
|
@@ -77,6 +80,15 @@ module BreakerMachines
|
|
77
80
|
def registry
|
78
81
|
Registry.instance
|
79
82
|
end
|
83
|
+
|
84
|
+
# Returns the current monotonic time in seconds.
|
85
|
+
# Monotonic time is guaranteed to always increase and is not affected
|
86
|
+
# by system clock adjustments, making it ideal for measuring durations.
|
87
|
+
#
|
88
|
+
# @return [Float] current monotonic time in seconds
|
89
|
+
def monotonic_time
|
90
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
91
|
+
end
|
80
92
|
end
|
81
93
|
|
82
94
|
# Set up notifications on first use
|
data/sig/README.md
CHANGED
@@ -24,7 +24,7 @@ To use these type signatures in your project:
|
|
24
24
|
target :app do
|
25
25
|
signature "sig"
|
26
26
|
check "lib"
|
27
|
-
|
27
|
+
|
28
28
|
library "breaker_machines"
|
29
29
|
end
|
30
30
|
```
|
@@ -38,7 +38,7 @@ To use these type signatures in your project:
|
|
38
38
|
|
39
39
|
### Basic Circuit Usage
|
40
40
|
```ruby
|
41
|
-
circuit = BreakerMachines::Circuit.new("api",
|
41
|
+
circuit = BreakerMachines::Circuit.new("api",
|
42
42
|
failure_threshold: 5,
|
43
43
|
reset_timeout: 30
|
44
44
|
)
|
@@ -50,7 +50,7 @@ result = circuit.call { api.fetch_data }
|
|
50
50
|
```ruby
|
51
51
|
class MyService
|
52
52
|
include BreakerMachines::DSL
|
53
|
-
|
53
|
+
|
54
54
|
circuit :database do
|
55
55
|
threshold failures: 10, within: 60
|
56
56
|
reset_after 120
|
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: breaker_machines
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
8
|
-
bindir:
|
8
|
+
bindir: bin
|
9
9
|
cert_chain: []
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
@@ -15,14 +15,14 @@ dependencies:
|
|
15
15
|
requirements:
|
16
16
|
- - ">="
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: '
|
18
|
+
version: '7.2'
|
19
19
|
type: :runtime
|
20
20
|
prerelease: false
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
22
22
|
requirements:
|
23
23
|
- - ">="
|
24
24
|
- !ruby/object:Gem::Version
|
25
|
-
version: '
|
25
|
+
version: '7.2'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: concurrent-ruby
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -43,14 +43,14 @@ dependencies:
|
|
43
43
|
requirements:
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 0.
|
46
|
+
version: 0.50.0
|
47
47
|
type: :runtime
|
48
48
|
prerelease: false
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - ">="
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.
|
53
|
+
version: 0.50.0
|
54
54
|
- !ruby/object:Gem::Dependency
|
55
55
|
name: zeitwerk
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,6 +109,7 @@ files:
|
|
109
109
|
- README.md
|
110
110
|
- lib/breaker_machines.rb
|
111
111
|
- lib/breaker_machines/async_support.rb
|
112
|
+
- lib/breaker_machines/cascading_circuit.rb
|
112
113
|
- lib/breaker_machines/circuit.rb
|
113
114
|
- lib/breaker_machines/circuit/callbacks.rb
|
114
115
|
- lib/breaker_machines/circuit/configuration.rb
|
@@ -117,16 +118,23 @@ files:
|
|
117
118
|
- lib/breaker_machines/circuit/state_management.rb
|
118
119
|
- lib/breaker_machines/console.rb
|
119
120
|
- lib/breaker_machines/dsl.rb
|
121
|
+
- lib/breaker_machines/dsl/cascading_circuit_builder.rb
|
122
|
+
- lib/breaker_machines/dsl/circuit_builder.rb
|
123
|
+
- lib/breaker_machines/dsl/hedged_builder.rb
|
124
|
+
- lib/breaker_machines/dsl/parallel_fallback_wrapper.rb
|
120
125
|
- lib/breaker_machines/errors.rb
|
121
126
|
- lib/breaker_machines/hedged_async_support.rb
|
122
127
|
- lib/breaker_machines/hedged_execution.rb
|
123
128
|
- lib/breaker_machines/registry.rb
|
124
129
|
- lib/breaker_machines/storage.rb
|
130
|
+
- lib/breaker_machines/storage/backend_state.rb
|
125
131
|
- lib/breaker_machines/storage/base.rb
|
126
132
|
- lib/breaker_machines/storage/bucket_memory.rb
|
127
133
|
- lib/breaker_machines/storage/cache.rb
|
134
|
+
- lib/breaker_machines/storage/fallback_chain.rb
|
128
135
|
- lib/breaker_machines/storage/memory.rb
|
129
136
|
- lib/breaker_machines/storage/null.rb
|
137
|
+
- lib/breaker_machines/types.rb
|
130
138
|
- lib/breaker_machines/version.rb
|
131
139
|
- sig/README.md
|
132
140
|
- sig/all.rbs
|