breaker_machines 0.13.2 → 0.18.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dab65770123fb6626a9112608ddeb784b1f623c6dd61ba59bd4f2094f8c73bb4
4
- data.tar.gz: 572686fc3febc21058ea20c300667d09b466d427c0bcab98af4230e5e14c7ee3
3
+ metadata.gz: b13af2a00df3d691dde7793676b7106280bfc9badf16b83e74d672c9640a7a87
4
+ data.tar.gz: 213c6d184a208b56e8233c077d3a4d53675631dcba235f047c7aa41aad5c4f22
5
5
  SHA512:
6
- metadata.gz: ba432f7aeda81bd88dd93ea8d1c0cb1c843cbc964bc3a31c6baf1359b4fb0a2c4873cd0e5d19ed0543ef5f3ba728f7e2de78a022f395abec3164465f8d562169
7
- data.tar.gz: 43f4c0da0445a1c7e91a9a1ba24e8892d4ba10cf1891ab75c44704834a1026b20e23fa5830c23029b3dee4a04ca9bba51c2319f069b0b92bbf7abd910a445ad1
6
+ metadata.gz: 4cd735ec384cd14ba9528fabe2d0e478b71ea9eaf807e11ab823f171b2876587dc48fbce228b1a225a92d2aae6c4234fa5a802ef8f174612bc49444daba35acb
7
+ data.tar.gz: 9ef430d6cf3d0b7f4a0d56283c1904d2dbcf1119d7d675dba4043ccaae622521b2247d63892a83a9da2d4cd1610f04effba6f388b35e0e5cd2fd6a3390fe8529
@@ -11,11 +11,11 @@ module BreakerMachines
11
11
  include Circuit::AsyncStateManagement
12
12
 
13
13
  # Additional async-specific methods
14
- def call_async(&block)
14
+ def call_async(&)
15
15
  require 'async' unless defined?(::Async)
16
16
 
17
17
  Async do
18
- call(&block)
18
+ call(&)
19
19
  end
20
20
  end
21
21
 
@@ -18,32 +18,36 @@ module BreakerMachines
18
18
  end
19
19
 
20
20
  # Execute a call with async support (fiber-safe mode)
21
- def execute_call_async(&)
21
+ def execute_call_async(admission, &)
22
+ completed = false
22
23
  start_time = BreakerMachines.monotonic_time
23
24
 
24
25
  begin
25
- # Execute with hedged requests if enabled
26
- result = if @config[:hedged_requests] || @config[:backends]
27
- execute_hedged(&)
28
- else
29
- execute_with_async_timeout(@config[:timeout], &)
30
- end
26
+ result = execute_async_operation(&)
31
27
 
32
- record_success(BreakerMachines.monotonic_time - start_time)
33
- handle_success
28
+ complete_call_success(admission, start_time)
29
+ completed = true
34
30
  result
35
31
  rescue StandardError => e
36
32
  # Re-raise if it's not an async timeout or configured exception
37
33
  raise unless e.is_a?(async_timeout_error_class) || @config[:exceptions].any? { |klass| e.is_a?(klass) }
38
34
 
39
- record_failure(BreakerMachines.monotonic_time - start_time, e)
40
- handle_failure
35
+ complete_call_failure(admission, start_time, e)
36
+ completed = true
41
37
  raise unless @config[:fallback]
42
38
 
43
39
  invoke_fallback_with_async(e)
40
+ ensure
41
+ release_abandoned_admission(admission) unless completed
44
42
  end
45
43
  end
46
44
 
45
+ def execute_async_operation(&)
46
+ return execute_hedged(&) if @config[:hedged_requests] || @config[:backends]
47
+
48
+ execute_with_async_timeout(@config[:timeout], &)
49
+ end
50
+
47
51
  # Execute a block with optional timeout using modern Async API
48
52
  def execute_with_async_timeout(timeout, &)
49
53
  if timeout
@@ -60,14 +64,7 @@ module BreakerMachines
60
64
  when BreakerMachines::DSL::ParallelFallbackWrapper
61
65
  invoke_parallel_fallbacks(@config[:fallback].fallbacks, error)
62
66
  when Proc
63
- result = if @config[:owner]
64
- @config[:owner].instance_exec(error, &@config[:fallback])
65
- else
66
- @config[:fallback].call(error)
67
- end
68
-
69
- # If the fallback returns an Async::Task, wait for it
70
- result.is_a?(::Async::Task) ? result.wait : result
67
+ invoke_proc_fallback_async(@config[:fallback], error)
71
68
  when Array
72
69
  # Try each fallback in order until one succeeds
73
70
  last_error = error
@@ -85,6 +82,16 @@ module BreakerMachines
85
82
 
86
83
  private
87
84
 
85
+ def invoke_proc_fallback_async(fallback, error)
86
+ result = if @config[:owner]
87
+ @config[:owner].instance_exec(error, &fallback)
88
+ else
89
+ fallback.call(error)
90
+ end
91
+
92
+ result.is_a?(::Async::Task) ? result.wait : result
93
+ end
94
+
88
95
  def invoke_single_fallback_async(fallback, error)
89
96
  case fallback
90
97
  when Proc
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BreakerMachines
4
+ class Circuit
5
+ # Admission reserves calls under short locks and tracks their state generation.
6
+ module Admission
7
+ extend ActiveSupport::Concern
8
+
9
+ Call = Data.define(:state, :state_epoch) do
10
+ def closed?
11
+ state == :closed
12
+ end
13
+
14
+ def half_open?
15
+ state == :half_open
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def attempt_recovery_if_ready
22
+ return unless open? && reset_timeout_elapsed?
23
+
24
+ @mutex.with_write_lock do
25
+ attempt_recovery if open? && reset_timeout_elapsed?
26
+ end
27
+ end
28
+
29
+ def admit_call
30
+ @mutex.with_read_lock do
31
+ case status_name
32
+ when :closed
33
+ Call.new(state: :closed, state_epoch: @state_epoch.value)
34
+ when :half_open
35
+ admit_half_open_call
36
+ end
37
+ end
38
+ end
39
+
40
+ def admit_half_open_call
41
+ state_epoch = @state_epoch.value
42
+ new_attempts = @half_open_attempts.increment
43
+
44
+ return Call.new(state: :half_open, state_epoch:) if new_attempts <= @config[:half_open_calls]
45
+
46
+ # This caller lost the race for a probe slot.
47
+ @half_open_attempts.decrement
48
+ nil
49
+ end
50
+
51
+ def admission_current?(admission)
52
+ admission.state_epoch == @state_epoch.value
53
+ end
54
+
55
+ def handle_success(admission)
56
+ return unless admission.half_open?
57
+
58
+ @mutex.with_write_lock do
59
+ return unless admission_current?(admission) && half_open?
60
+
61
+ successful_attempts = @half_open_successes.increment
62
+ next unless successful_attempts >= @config[:half_open_calls] || success_threshold_reached?
63
+
64
+ @half_open_attempts.value = 0
65
+ @half_open_successes.value = 0
66
+ reset
67
+ end
68
+ end
69
+
70
+ def handle_failure(admission)
71
+ @mutex.with_write_lock do
72
+ return unless admission_current?(admission)
73
+
74
+ if admission.closed? && closed? && failure_threshold_exceeded?
75
+ trip
76
+ elsif admission.half_open? && half_open?
77
+ @half_open_attempts.value = 0
78
+ @half_open_successes.value = 0
79
+ trip
80
+ end
81
+ end
82
+ end
83
+
84
+ def release_abandoned_admission(admission)
85
+ return unless admission.half_open?
86
+
87
+ @mutex.with_write_lock do
88
+ next unless admission_current?(admission) && half_open?
89
+ next unless @half_open_attempts.value.positive?
90
+
91
+ @half_open_attempts.decrement
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -10,6 +10,7 @@ module BreakerMachines
10
10
 
11
11
  included do
12
12
  include Circuit::Configuration
13
+ include Circuit::Admission
13
14
  include Circuit::Execution
14
15
  include Circuit::HedgedExecution
15
16
  include Circuit::Introspection
@@ -29,24 +30,29 @@ module BreakerMachines
29
30
  # Use global default storage if not specified
30
31
  @storage = @config[:storage] || create_default_storage
31
32
  @metrics = @config[:metrics]
33
+ initialize_runtime_state
34
+
35
+ restore_status_from_storage if @storage
36
+
37
+ # Register with global registry unless auto_register is disabled
38
+ BreakerMachines::Registry.instance.register(self) unless @config[:auto_register] == false
39
+ end
40
+
41
+ private
42
+
43
+ def initialize_runtime_state
32
44
  @opened_at = Concurrent::AtomicReference.new(nil)
33
45
  @half_open_attempts = Concurrent::AtomicFixnum.new(0)
34
46
  @half_open_successes = Concurrent::AtomicFixnum.new(0)
47
+ @state_epoch = Concurrent::AtomicFixnum.new(0)
35
48
  @mutex = Concurrent::ReentrantReadWriteLock.new
36
49
  @last_failure_at = Concurrent::AtomicReference.new(nil)
37
50
  @last_error = Concurrent::AtomicReference.new(nil)
38
51
 
39
52
  # Initialize semaphore for bulkheading if max_concurrent is set
40
53
  @semaphore = (Concurrent::Semaphore.new(@config[:max_concurrent]) if @config[:max_concurrent])
41
-
42
- restore_status_from_storage if @storage
43
-
44
- # Register with global registry unless auto_register is disabled
45
- BreakerMachines::Registry.instance.register(self) unless @config[:auto_register] == false
46
54
  end
47
55
 
48
- private
49
-
50
56
  def restore_status_from_storage
51
57
  stored_status = @storage.get_status(@name)
52
58
  return unless stored_status
@@ -31,100 +31,92 @@ module BreakerMachines
31
31
 
32
32
  private
33
33
 
34
- def execute_with_state_check(&block)
35
- # Check if we need to transition from open to half-open first
36
- if open? && reset_timeout_elapsed?
37
- @mutex.with_write_lock do
38
- attempt_recovery if open? # Double-check after acquiring lock
39
- end
40
- end
34
+ def execute_with_state_check(&)
35
+ attempt_recovery_if_ready
41
36
 
42
37
  # Apply bulkheading first, outside of any locks
38
+ acquired = false
43
39
  if @semaphore
44
40
  acquired = @semaphore.try_acquire
45
- unless acquired
46
- # Reject immediately if we can't acquire semaphore
47
- return reject_call_bulkhead
48
- end
41
+ return reject_call_bulkhead unless acquired
49
42
  end
50
43
 
51
44
  begin
52
- @mutex.with_read_lock do
53
- case status_name
54
- when :open
55
- reject_call
56
- when :half_open
57
- handle_half_open_status(&block)
58
- when :closed
59
- handle_closed_status(&block)
60
- end
45
+ admission = admit_call
46
+ unless admission
47
+ # An open-circuit fallback is user code and may yield. It must not
48
+ # retain a bulkhead permit or circuit lock while it runs.
49
+ @semaphore&.release if acquired
50
+ acquired = false
51
+ return reject_call
61
52
  end
53
+
54
+ execute_call(admission, &)
62
55
  ensure
63
56
  @semaphore&.release if @semaphore && acquired
64
57
  end
65
58
  end
66
59
 
67
- def handle_half_open_status(&)
68
- # Atomically increment and get the new value
69
- new_attempts = @half_open_attempts.increment
70
-
71
- if new_attempts <= @config[:half_open_calls]
72
- execute_call(&)
73
- else
74
- # This thread lost the race, decrement back and reject
75
- @half_open_attempts.decrement
76
- reject_call
77
- end
78
- end
79
-
80
- def handle_closed_status(&)
81
- execute_call(&)
82
- end
83
-
84
- def execute_call(&block)
60
+ def execute_call(admission, &)
85
61
  # Use async version if fiber_safe is enabled
86
62
  if @config[:fiber_safe]
87
63
  # Ensure async is loaded and included
88
64
  Execution.load_async_support unless respond_to?(:execute_call_async)
89
- return execute_call_async(&block)
65
+ return execute_call_async(admission, &)
90
66
  end
91
67
 
68
+ execute_call_sync(admission, &)
69
+ end
70
+
71
+ def execute_call_sync(admission, &)
72
+ completed = false
92
73
  start_time = BreakerMachines.monotonic_time
93
74
 
94
75
  begin
95
- # IMPORTANT: We do NOT implement forceful timeouts as they are inherently unsafe
96
- # The timeout configuration is provided for documentation/intent purposes
97
- # Users should implement timeouts in their own code using safe mechanisms
98
- # (e.g., HTTP client timeouts, database statement timeouts, etc.)
99
- # Log a warning if timeout is configured
100
- if @config[:timeout] && BreakerMachines.logger && BreakerMachines.config.log_events
101
- BreakerMachines.logger.warn(
102
- "[BreakerMachines] Circuit '#{@name}' has timeout configured but " \
103
- 'forceful timeouts are not implemented for safety. ' \
104
- 'Please use timeout mechanisms provided by your libraries ' \
105
- '(e.g., Net::HTTP read_timeout, ActiveRecord statement_timeout).'
106
- )
107
- end
108
-
109
- # Execute with hedged requests if enabled
110
- result = if @config[:hedged_requests] || @config[:backends]
111
- execute_hedged(&block)
112
- else
113
- block.call
114
- end
115
-
116
- record_success(BreakerMachines.monotonic_time - start_time)
117
- handle_success
76
+ result = execute_sync_operation(&)
77
+ complete_call_success(admission, start_time)
78
+ completed = true
118
79
  result
119
80
  rescue *@config[:exceptions] => e
120
- record_failure(BreakerMachines.monotonic_time - start_time, e)
121
- handle_failure
81
+ complete_call_failure(admission, start_time, e)
82
+ completed = true
122
83
  raise unless @config[:fallback]
123
84
 
124
85
  invoke_fallback(e)
86
+ ensure
87
+ release_abandoned_admission(admission) unless completed
125
88
  end
126
89
  end
127
90
 
91
+ def execute_sync_operation(&)
92
+ warn_about_sync_timeout
93
+ return execute_hedged(&) if @config[:hedged_requests] || @config[:backends]
94
+
95
+ yield
96
+ end
97
+
98
+ def warn_about_sync_timeout
99
+ return unless @config[:timeout] && BreakerMachines.logger && BreakerMachines.config.log_events
100
+
101
+ # Forceful Ruby timeouts can interrupt code while it holds resources.
102
+ BreakerMachines.logger.warn(
103
+ "[BreakerMachines] Circuit '#{@name}' has timeout configured but " \
104
+ 'forceful timeouts are not implemented for safety. ' \
105
+ 'Please use timeout mechanisms provided by your libraries ' \
106
+ '(e.g., Net::HTTP read_timeout, ActiveRecord statement_timeout).'
107
+ )
108
+ end
109
+
110
+ def complete_call_success(admission, start_time)
111
+ record_success(BreakerMachines.monotonic_time - start_time)
112
+ handle_success(admission)
113
+ end
114
+
115
+ def complete_call_failure(admission, start_time, error)
116
+ record_failure(BreakerMachines.monotonic_time - start_time, error)
117
+ handle_failure(admission)
118
+ end
119
+
128
120
  def reject_call
129
121
  @metrics&.record_rejection(@name)
130
122
  invoke_callback(:on_reject)
@@ -144,43 +136,6 @@ module BreakerMachines
144
136
  invoke_fallback(error)
145
137
  end
146
138
 
147
- def handle_success
148
- return unless half_open?
149
-
150
- @mutex.with_write_lock do
151
- if half_open?
152
- # Check if all allowed half-open calls have succeeded
153
- # This ensures the circuit can close even if success_threshold > half_open_calls
154
- successful_attempts = @half_open_successes.increment
155
-
156
- # Fast-close logic: Circuit closes if EITHER:
157
- # 1. All allowed half-open calls succeeded (conservative approach)
158
- # 2. Success threshold is reached (aggressive approach for quick recovery)
159
- # This allows flexible configuration - set success_threshold=1 for fast recovery
160
- # or success_threshold=half_open_calls for cautious recovery
161
- if successful_attempts >= @config[:half_open_calls] || success_threshold_reached?
162
- @half_open_attempts.value = 0
163
- @half_open_successes.value = 0
164
- reset
165
- end
166
- end
167
- end
168
- end
169
-
170
- def handle_failure
171
- return unless closed? || half_open?
172
-
173
- @mutex.with_write_lock do
174
- if closed? && failure_threshold_exceeded?
175
- trip
176
- elsif half_open?
177
- @half_open_attempts.value = 0
178
- @half_open_successes.value = 0
179
- trip
180
- end
181
- end
182
- end
183
-
184
139
  def failure_threshold_exceeded?
185
140
  if @config[:use_rate_threshold]
186
141
  # Rate-based threshold
@@ -9,6 +9,7 @@ module BreakerMachines
9
9
  private
10
10
 
11
11
  def on_circuit_open
12
+ @state_epoch.increment
12
13
  @opened_at.value = BreakerMachines.monotonic_time
13
14
  @storage&.set_status(@name, :open, @opened_at.value)
14
15
  if @storage.respond_to?(:record_event_with_details)
@@ -20,6 +21,7 @@ module BreakerMachines
20
21
  end
21
22
 
22
23
  def on_circuit_close
24
+ @state_epoch.increment
23
25
  @opened_at.value = nil
24
26
  @last_error.value = nil
25
27
  @last_failure_at.value = nil
@@ -33,6 +35,7 @@ module BreakerMachines
33
35
  end
34
36
 
35
37
  def on_circuit_half_open
38
+ @state_epoch.increment
36
39
  @half_open_attempts.value = 0
37
40
  @half_open_successes.value = 0
38
41
  @storage&.set_status(@name, :half_open)
@@ -160,7 +160,7 @@ module BreakerMachines
160
160
  @config[:exceptions] = exceptions
161
161
  end
162
162
 
163
- def fiber_safe(enabled = true) # rubocop:disable Style/OptionalBooleanParameter
163
+ def fiber_safe(enabled = true)
164
164
  @config[:fiber_safe] = enabled
165
165
  end
166
166
 
@@ -39,12 +39,12 @@ module BreakerMachines
39
39
  end
40
40
  end
41
41
 
42
- def circuit(name, &block)
42
+ def circuit(name, &)
43
43
  @circuits ||= {}
44
44
 
45
45
  if block_given?
46
46
  builder = DSL::CircuitBuilder.new
47
- builder.instance_eval(&block)
47
+ builder.instance_eval(&)
48
48
  @circuits[name] = builder.config
49
49
  end
50
50
 
@@ -52,12 +52,12 @@ module BreakerMachines
52
52
  end
53
53
 
54
54
  # Define a cascading circuit breaker that can trip dependent circuits
55
- def cascade_circuit(name, &block)
55
+ def cascade_circuit(name, &)
56
56
  @circuits ||= {}
57
57
 
58
58
  if block_given?
59
59
  builder = DSL::CascadingCircuitBuilder.new
60
- builder.instance_eval(&block)
60
+ builder.instance_eval(&)
61
61
  @circuits[name] = builder.config.merge(circuit_type: :cascading)
62
62
  end
63
63
 
@@ -81,12 +81,12 @@ module BreakerMachines
81
81
  end
82
82
 
83
83
  # Define reusable circuit templates
84
- def circuit_template(name, &block)
84
+ def circuit_template(name, &)
85
85
  @circuit_templates ||= {}
86
86
 
87
87
  if block_given?
88
88
  builder = DSL::CircuitBuilder.new
89
- builder.instance_eval(&block)
89
+ builder.instance_eval(&)
90
90
  @circuit_templates[name] = builder.config
91
91
  end
92
92
 
@@ -60,7 +60,7 @@ module BreakerMachines
60
60
  end
61
61
 
62
62
  # Force open a circuit by name
63
- def force_open(name) # rubocop:disable Naming/PredicateMethod
63
+ def force_open(name)
64
64
  circuits = find_by_name(name)
65
65
  return false if circuits.empty?
66
66
 
@@ -69,7 +69,7 @@ module BreakerMachines
69
69
  end
70
70
 
71
71
  # Force close a circuit by name
72
- def force_close(name) # rubocop:disable Naming/PredicateMethod
72
+ def force_close(name)
73
73
  circuits = find_by_name(name)
74
74
  return false if circuits.empty?
75
75
 
@@ -78,7 +78,7 @@ module BreakerMachines
78
78
  end
79
79
 
80
80
  # Reset a circuit by name
81
- def reset(name) # rubocop:disable Naming/PredicateMethod
81
+ def reset(name)
82
82
  circuits = find_by_name(name)
83
83
  return false if circuits.empty?
84
84
 
@@ -80,7 +80,7 @@ module BreakerMachines
80
80
 
81
81
  private
82
82
 
83
- def execute_with_fallback(method, *args, **kwargs)
83
+ def execute_with_fallback(method, *, **kwargs)
84
84
  chain_started_at = BreakerMachines.monotonic_time
85
85
  attempted_backends = []
86
86
 
@@ -100,9 +100,9 @@ module BreakerMachines
100
100
 
101
101
  result = backend.with_timeout(config[:timeout]) do
102
102
  if kwargs.any?
103
- backend.send(method, *args, **kwargs)
103
+ backend.send(method, *, **kwargs)
104
104
  else
105
- backend.send(method, *args)
105
+ backend.send(method, *)
106
106
  end
107
107
  end
108
108
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BreakerMachines
4
- VERSION = '0.13.2'
4
+ VERSION = '0.18.0'
5
5
  end
@@ -2,6 +2,7 @@ module BreakerMachines
2
2
  class Circuit
3
3
  include StateManagement
4
4
  include Configuration
5
+ include Admission
5
6
  include Execution
6
7
  include Introspection
7
8
  include Callbacks
@@ -14,6 +15,7 @@ module BreakerMachines
14
15
  @opened_at: Concurrent::AtomicReference[Float?]
15
16
  @half_open_attempts: Concurrent::AtomicFixnum
16
17
  @half_open_successes: Concurrent::AtomicFixnum
18
+ @state_epoch: Concurrent::AtomicFixnum
17
19
  @mutex: Concurrent::ReentrantReadWriteLock
18
20
  @last_failure_at: Concurrent::AtomicReference[Float?]
19
21
  @last_error: Concurrent::AtomicReference[StandardError?]
@@ -22,6 +24,27 @@ module BreakerMachines
22
24
  attr_reader status: (:open | :closed | :half_open)
23
25
  end
24
26
 
27
+ module Admission
28
+ class Call
29
+ attr_reader state: (:closed | :half_open)
30
+ attr_reader state_epoch: Integer
31
+
32
+ def self.new: (state: (:closed | :half_open), state_epoch: Integer) -> Call
33
+ def closed?: () -> bool
34
+ def half_open?: () -> bool
35
+ end
36
+
37
+ private
38
+
39
+ def attempt_recovery_if_ready: () -> void
40
+ def admit_call: () -> Call?
41
+ def admit_half_open_call: () -> Call?
42
+ def admission_current?: (Call admission) -> bool
43
+ def handle_success: (Call admission) -> void
44
+ def handle_failure: (Call admission) -> void
45
+ def release_abandoned_admission: (Call admission) -> void
46
+ end
47
+
25
48
  module StateManagement
26
49
  interface _StateManagementState
27
50
  def status: () -> (:open | :closed | :half_open)
@@ -96,13 +119,15 @@ module BreakerMachines
96
119
 
97
120
  private
98
121
 
99
- def handle_open_status: () { () -> untyped } -> untyped
100
- def handle_half_open_status: () { () -> untyped } -> untyped
101
- def handle_closed_status: () { () -> untyped } -> untyped
102
- def execute_call: () { () -> untyped } -> untyped
122
+ def execute_with_state_check: [T] () { () -> T } -> untyped
123
+ def execute_call: [T] (Admission::Call admission) { () -> T } -> untyped
124
+ def execute_call_sync: [T] (Admission::Call admission) { () -> T } -> untyped
125
+ def execute_sync_operation: [T] () { () -> T } -> T
126
+ def warn_about_sync_timeout: () -> void
127
+ def complete_call_success: (Admission::Call admission, Float start_time) -> void
128
+ def complete_call_failure: (Admission::Call admission, Float start_time, Exception error) -> void
103
129
  def reject_call: () -> untyped
104
- def handle_success: () -> void
105
- def handle_failure: () -> void
130
+ def reject_call_bulkhead: () -> untyped
106
131
  def failure_threshold_exceeded?: () -> bool
107
132
  def success_threshold_reached?: () -> bool
108
133
  def record_success: (Float duration) -> void
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: breaker_machines
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.2
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '0.2'
32
+ version: '0.9'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '0.2'
39
+ version: '0.9'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: concurrent-ruby
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -55,16 +55,16 @@ dependencies:
55
55
  name: state_machines
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - ">="
58
+ - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: 0.200.0
60
+ version: '0.202'
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - ">="
65
+ - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: 0.200.0
67
+ version: '0.202'
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: zeitwerk
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -154,6 +154,7 @@ files:
154
154
  - lib/breaker_machines/async_support.rb
155
155
  - lib/breaker_machines/cascading_circuit.rb
156
156
  - lib/breaker_machines/circuit.rb
157
+ - lib/breaker_machines/circuit/admission.rb
157
158
  - lib/breaker_machines/circuit/async_state_management.rb
158
159
  - lib/breaker_machines/circuit/base.rb
159
160
  - lib/breaker_machines/circuit/callbacks.rb
@@ -221,14 +222,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
221
222
  requirements:
222
223
  - - ">="
223
224
  - !ruby/object:Gem::Version
224
- version: 3.3.0
225
+ version: 4.0.0
225
226
  required_rubygems_version: !ruby/object:Gem::Requirement
226
227
  requirements:
227
228
  - - ">="
228
229
  - !ruby/object:Gem::Version
229
230
  version: '0'
230
231
  requirements: []
231
- rubygems_version: 3.6.9
232
+ rubygems_version: 4.0.20
232
233
  specification_version: 4
233
234
  summary: Circuit breaker implementation for Ruby with a clean DSL and state_machines
234
235
  under the hood