breaker_machines 0.9.2-arm64-darwin
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +184 -0
- data/ext/breaker_machines_native/extconf.rb +3 -0
- data/lib/breaker_machines/async_circuit.rb +47 -0
- data/lib/breaker_machines/async_support.rb +104 -0
- data/lib/breaker_machines/cascading_circuit.rb +177 -0
- data/lib/breaker_machines/circuit/async_state_management.rb +71 -0
- data/lib/breaker_machines/circuit/base.rb +59 -0
- data/lib/breaker_machines/circuit/callbacks.rb +135 -0
- data/lib/breaker_machines/circuit/configuration.rb +67 -0
- data/lib/breaker_machines/circuit/coordinated_state_management.rb +117 -0
- data/lib/breaker_machines/circuit/execution.rb +231 -0
- data/lib/breaker_machines/circuit/hedged_execution.rb +115 -0
- data/lib/breaker_machines/circuit/introspection.rb +93 -0
- data/lib/breaker_machines/circuit/native.rb +127 -0
- data/lib/breaker_machines/circuit/state_callbacks.rb +72 -0
- data/lib/breaker_machines/circuit/state_management.rb +59 -0
- data/lib/breaker_machines/circuit.rb +8 -0
- data/lib/breaker_machines/circuit_group.rb +153 -0
- data/lib/breaker_machines/console.rb +345 -0
- data/lib/breaker_machines/coordinated_circuit.rb +10 -0
- 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 +283 -0
- data/lib/breaker_machines/errors.rb +71 -0
- data/lib/breaker_machines/hedged_async_support.rb +88 -0
- data/lib/breaker_machines/native_extension.rb +81 -0
- data/lib/breaker_machines/native_speedup.rb +10 -0
- data/lib/breaker_machines/registry.rb +243 -0
- data/lib/breaker_machines/storage/backend_state.rb +69 -0
- data/lib/breaker_machines/storage/base.rb +52 -0
- data/lib/breaker_machines/storage/bucket_memory.rb +176 -0
- data/lib/breaker_machines/storage/cache.rb +169 -0
- data/lib/breaker_machines/storage/fallback_chain.rb +294 -0
- data/lib/breaker_machines/storage/memory.rb +140 -0
- data/lib/breaker_machines/storage/native.rb +93 -0
- data/lib/breaker_machines/storage/null.rb +54 -0
- data/lib/breaker_machines/storage.rb +8 -0
- data/lib/breaker_machines/types.rb +41 -0
- data/lib/breaker_machines/version.rb +5 -0
- data/lib/breaker_machines.rb +200 -0
- data/lib/breaker_machines_native/breaker_machines_native.bundle +0 -0
- data/sig/README.md +74 -0
- data/sig/all.rbs +25 -0
- data/sig/breaker_machines/circuit.rbs +154 -0
- data/sig/breaker_machines/console.rbs +32 -0
- data/sig/breaker_machines/dsl.rbs +50 -0
- data/sig/breaker_machines/errors.rbs +24 -0
- data/sig/breaker_machines/interfaces.rbs +46 -0
- data/sig/breaker_machines/registry.rbs +30 -0
- data/sig/breaker_machines/storage.rbs +65 -0
- data/sig/breaker_machines/types.rbs +97 -0
- data/sig/breaker_machines.rbs +42 -0
- data/sig/manifest.yaml +5 -0
- metadata +227 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'zeitwerk'
|
|
4
|
+
require 'active_support'
|
|
5
|
+
require 'active_support/core_ext'
|
|
6
|
+
require 'state_machines'
|
|
7
|
+
require_relative 'breaker_machines/errors'
|
|
8
|
+
require_relative 'breaker_machines/types'
|
|
9
|
+
|
|
10
|
+
loader = Zeitwerk::Loader.for_gem
|
|
11
|
+
loader.inflector.inflect('dsl' => 'DSL')
|
|
12
|
+
loader.ignore("#{__dir__}/breaker_machines/errors.rb")
|
|
13
|
+
loader.ignore("#{__dir__}/breaker_machines/types.rb")
|
|
14
|
+
loader.ignore("#{__dir__}/breaker_machines/console.rb")
|
|
15
|
+
loader.ignore("#{__dir__}/breaker_machines/async_support.rb")
|
|
16
|
+
loader.ignore("#{__dir__}/breaker_machines/hedged_async_support.rb")
|
|
17
|
+
loader.ignore("#{__dir__}/breaker_machines/circuit/async_state_management.rb")
|
|
18
|
+
loader.ignore("#{__dir__}/breaker_machines/native_speedup.rb")
|
|
19
|
+
loader.ignore("#{__dir__}/breaker_machines/native_extension.rb")
|
|
20
|
+
loader.ignore("#{__dir__}/breaker_machines/storage/native.rb")
|
|
21
|
+
loader.setup
|
|
22
|
+
|
|
23
|
+
# BreakerMachines provides a thread-safe implementation of the Circuit Breaker pattern
|
|
24
|
+
# for Ruby applications, helping to prevent cascading failures in distributed systems.
|
|
25
|
+
module BreakerMachines
|
|
26
|
+
# Global configuration class for BreakerMachines
|
|
27
|
+
class Configuration
|
|
28
|
+
attr_accessor :default_storage,
|
|
29
|
+
:default_timeout,
|
|
30
|
+
:default_reset_timeout,
|
|
31
|
+
:default_failure_threshold,
|
|
32
|
+
:log_events,
|
|
33
|
+
:fiber_safe
|
|
34
|
+
|
|
35
|
+
def initialize
|
|
36
|
+
@default_storage = :bucket_memory
|
|
37
|
+
@default_timeout = nil
|
|
38
|
+
@default_reset_timeout = 60.seconds
|
|
39
|
+
@default_failure_threshold = 5
|
|
40
|
+
@log_events = true
|
|
41
|
+
@fiber_safe = false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
def loader
|
|
47
|
+
loader
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def config
|
|
51
|
+
@config ||= Configuration.new
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def configure
|
|
55
|
+
yield config
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Delegate config attributes to config object for backward compatibility
|
|
59
|
+
def default_storage
|
|
60
|
+
config.default_storage
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def default_storage=(value)
|
|
64
|
+
config.default_storage = value
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def default_timeout
|
|
68
|
+
config.default_timeout
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def default_timeout=(value)
|
|
72
|
+
config.default_timeout = value
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def default_reset_timeout
|
|
76
|
+
config.default_reset_timeout
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def default_reset_timeout=(value)
|
|
80
|
+
config.default_reset_timeout = value
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def default_failure_threshold
|
|
84
|
+
config.default_failure_threshold
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def default_failure_threshold=(value)
|
|
88
|
+
config.default_failure_threshold = value
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def log_events
|
|
92
|
+
config.log_events
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def log_events=(value)
|
|
96
|
+
config.log_events = value
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def fiber_safe
|
|
100
|
+
config.fiber_safe
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def fiber_safe=(value)
|
|
104
|
+
config.fiber_safe = value
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def setup_notifications
|
|
108
|
+
return unless config.log_events
|
|
109
|
+
|
|
110
|
+
ActiveSupport::Notifications.subscribe(/^breaker_machines\./) do |name, _start, _finish, _id, payload|
|
|
111
|
+
event_type = name.split('.').last
|
|
112
|
+
circuit_name = payload[:circuit]
|
|
113
|
+
|
|
114
|
+
case event_type
|
|
115
|
+
when 'opened'
|
|
116
|
+
logger&.warn "[BreakerMachines] Circuit '#{circuit_name}' opened"
|
|
117
|
+
when 'closed'
|
|
118
|
+
logger&.info "[BreakerMachines] Circuit '#{circuit_name}' closed"
|
|
119
|
+
when 'half_opened'
|
|
120
|
+
logger&.info "[BreakerMachines] Circuit '#{circuit_name}' half-opened"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def logger
|
|
126
|
+
@logger ||= ActiveSupport::Logger.new($stdout)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
attr_writer :logger
|
|
130
|
+
|
|
131
|
+
# Centralized logging helper
|
|
132
|
+
# @param level [Symbol] log level (:debug, :info, :warn, :error)
|
|
133
|
+
# @param message [String] message to log
|
|
134
|
+
def log(level, message)
|
|
135
|
+
return unless config.log_events && logger
|
|
136
|
+
|
|
137
|
+
logger.public_send(level, "[BreakerMachines] #{message}")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def instrument(event, payload = {})
|
|
141
|
+
return unless config.log_events
|
|
142
|
+
|
|
143
|
+
ActiveSupport::Notifications.instrument("breaker_machines.#{event}", payload)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Check if native extension is available
|
|
147
|
+
# @return [Boolean] true if native extension loaded successfully
|
|
148
|
+
def native_available?
|
|
149
|
+
@native_available || false
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Launch the interactive console
|
|
153
|
+
def console
|
|
154
|
+
require_relative 'breaker_machines/console'
|
|
155
|
+
Console.start
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Get the global registry
|
|
159
|
+
def registry
|
|
160
|
+
Registry.instance
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Register a circuit with the global registry
|
|
164
|
+
def register(circuit)
|
|
165
|
+
registry.register(circuit)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Reset the registry and configurations (useful for testing)
|
|
169
|
+
def reset!
|
|
170
|
+
registry.clear
|
|
171
|
+
config.default_storage = :bucket_memory
|
|
172
|
+
config.default_timeout = nil
|
|
173
|
+
config.default_reset_timeout = 60.seconds
|
|
174
|
+
config.default_failure_threshold = 5
|
|
175
|
+
config.log_events = true
|
|
176
|
+
config.fiber_safe = false
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Returns the current monotonic time in seconds.
|
|
180
|
+
# Monotonic time is guaranteed to always increase and is not affected
|
|
181
|
+
# by system clock adjustments, making it ideal for measuring durations.
|
|
182
|
+
#
|
|
183
|
+
# @return [Float] current monotonic time in seconds
|
|
184
|
+
def monotonic_time
|
|
185
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Set up notifications on first use
|
|
190
|
+
setup_notifications if config.log_events
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Load optional native speedup after core is loaded
|
|
194
|
+
# Automatically loads if available, gracefully falls back to pure Ruby if not
|
|
195
|
+
begin
|
|
196
|
+
require_relative 'breaker_machines/native_speedup'
|
|
197
|
+
rescue LoadError
|
|
198
|
+
# Native extension not available, using pure Ruby backend
|
|
199
|
+
# This is expected on JRuby or when Cargo is not available
|
|
200
|
+
end
|
|
Binary file
|
data/sig/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# BreakerMachines RBS Type Signatures
|
|
2
|
+
|
|
3
|
+
This directory contains RBS (Ruby type signatures) for the BreakerMachines gem.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- `breaker_machines.rbs` - Main module and configuration
|
|
8
|
+
- `breaker_machines/` - Type signatures for all classes and modules
|
|
9
|
+
- `circuit.rbs` - Circuit class and its modules
|
|
10
|
+
- `errors.rbs` - Error classes
|
|
11
|
+
- `storage.rbs` - Storage backends
|
|
12
|
+
- `dsl.rbs` - DSL module for including in classes
|
|
13
|
+
- `registry.rbs` - Global circuit registry
|
|
14
|
+
- `console.rbs` - Interactive console
|
|
15
|
+
- `types.rbs` - Common type aliases
|
|
16
|
+
- `interfaces.rbs` - Interface definitions
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
To use these type signatures in your project:
|
|
21
|
+
|
|
22
|
+
1. Add to your `Steepfile`:
|
|
23
|
+
```ruby
|
|
24
|
+
target :app do
|
|
25
|
+
signature "sig"
|
|
26
|
+
check "lib"
|
|
27
|
+
|
|
28
|
+
library "breaker_machines"
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
2. Or with RBS directly:
|
|
33
|
+
```bash
|
|
34
|
+
rbs validate
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Type Checking Examples
|
|
38
|
+
|
|
39
|
+
### Basic Circuit Usage
|
|
40
|
+
```ruby
|
|
41
|
+
circuit = BreakerMachines::Circuit.new("api",
|
|
42
|
+
failure_threshold: 5,
|
|
43
|
+
reset_timeout: 30
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
result = circuit.call { api.fetch_data }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### DSL Usage
|
|
50
|
+
```ruby
|
|
51
|
+
class MyService
|
|
52
|
+
include BreakerMachines::DSL
|
|
53
|
+
|
|
54
|
+
circuit :database do
|
|
55
|
+
threshold failures: 10, within: 60
|
|
56
|
+
reset_after 120
|
|
57
|
+
fallback { [] }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Key Types
|
|
63
|
+
|
|
64
|
+
- `circuit_state` - `:open | :closed | :half_open`
|
|
65
|
+
- `storage_backend` - `:memory | :bucket_memory | :null | :redis`
|
|
66
|
+
- `circuit_options` - Configuration hash for circuits
|
|
67
|
+
- `event_record` - Structure for logged events
|
|
68
|
+
|
|
69
|
+
## Interfaces
|
|
70
|
+
|
|
71
|
+
The type signatures define several interfaces:
|
|
72
|
+
- `_StorageBackend` - For custom storage implementations
|
|
73
|
+
- `_MetricsRecorder` - For custom metrics recording
|
|
74
|
+
- `_CircuitLike` - For circuit-compatible objects
|
data/sig/all.rbs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Main entry point for BreakerMachines RBS types
|
|
2
|
+
#
|
|
3
|
+
# This file provides a complete type definition for the BreakerMachines gem,
|
|
4
|
+
# a Ruby implementation of the Circuit Breaker pattern.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# class MyService
|
|
8
|
+
# include BreakerMachines::DSL
|
|
9
|
+
#
|
|
10
|
+
# circuit :api_call do
|
|
11
|
+
# threshold failures: 5, within: 60
|
|
12
|
+
# reset_after 30
|
|
13
|
+
# fallback { |error| { error: "Service unavailable" } }
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# def fetch_data
|
|
17
|
+
# circuit(:api_call).call do
|
|
18
|
+
# # Your API call here
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
|
|
23
|
+
# Import all type definitions
|
|
24
|
+
use BreakerMachines::*
|
|
25
|
+
use BreakerMachines::Storage::*
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
module BreakerMachines
|
|
2
|
+
class Circuit
|
|
3
|
+
include StateManagement
|
|
4
|
+
include Configuration
|
|
5
|
+
include Execution
|
|
6
|
+
include Introspection
|
|
7
|
+
include Callbacks
|
|
8
|
+
|
|
9
|
+
# Instance variables from Configuration module
|
|
10
|
+
@name: String
|
|
11
|
+
@config: Hash[Symbol, untyped]
|
|
12
|
+
@storage: _StorageBackend?
|
|
13
|
+
@metrics: _MetricsRecorder?
|
|
14
|
+
@opened_at: Concurrent::AtomicReference[Float?]
|
|
15
|
+
@half_open_attempts: Concurrent::AtomicFixnum
|
|
16
|
+
@half_open_successes: Concurrent::AtomicFixnum
|
|
17
|
+
@mutex: Concurrent::ReentrantReadWriteLock
|
|
18
|
+
@last_failure_at: Concurrent::AtomicReference[Float?]
|
|
19
|
+
@last_error: Concurrent::AtomicReference[StandardError?]
|
|
20
|
+
|
|
21
|
+
# State machine status (from StateManagement)
|
|
22
|
+
attr_reader status: (:open | :closed | :half_open)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module StateManagement
|
|
26
|
+
interface _StateManagementState
|
|
27
|
+
def status: () -> (:open | :closed | :half_open)
|
|
28
|
+
def status=: (:open | :closed | :half_open value) -> void
|
|
29
|
+
def status_name: () -> (:open | :closed | :half_open)
|
|
30
|
+
def open?: () -> bool
|
|
31
|
+
def closed?: () -> bool
|
|
32
|
+
def half_open?: () -> bool
|
|
33
|
+
def trip: () -> bool
|
|
34
|
+
def attempt_recovery: () -> bool
|
|
35
|
+
def reset: () -> bool
|
|
36
|
+
def force_open: () -> bool
|
|
37
|
+
def force_close: () -> bool
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.included: (singleton(Circuit) base) -> void
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def on_circuit_open: () -> void
|
|
45
|
+
def on_circuit_close: () -> void
|
|
46
|
+
def on_circuit_half_open: () -> void
|
|
47
|
+
def restore_status_from_storage: () -> void
|
|
48
|
+
def reset_timeout_elapsed?: () -> bool
|
|
49
|
+
def monotonic_time: () -> Float
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module Configuration
|
|
53
|
+
extend ActiveSupport::Concern
|
|
54
|
+
|
|
55
|
+
interface _ConfigurationMethods
|
|
56
|
+
def name: () -> String
|
|
57
|
+
def config: () -> Hash[Symbol, untyped]
|
|
58
|
+
def opened_at: () -> Concurrent::AtomicReference[Float?]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
type storage_type = Storage::Base | Storage::Memory | Storage::BucketMemory | Storage::Null
|
|
62
|
+
type metrics_type = untyped
|
|
63
|
+
type callback_type = Proc | nil
|
|
64
|
+
type fallback_type = Proc | Array[Proc | untyped] | untyped
|
|
65
|
+
|
|
66
|
+
type circuit_config = {
|
|
67
|
+
failure_threshold: Integer,
|
|
68
|
+
failure_window: Integer,
|
|
69
|
+
success_threshold: Integer,
|
|
70
|
+
timeout: Integer?,
|
|
71
|
+
reset_timeout: Integer,
|
|
72
|
+
reset_timeout_jitter: Float,
|
|
73
|
+
half_open_calls: Integer,
|
|
74
|
+
storage: storage_type?,
|
|
75
|
+
metrics: metrics_type?,
|
|
76
|
+
fallback: fallback_type?,
|
|
77
|
+
on_open: callback_type,
|
|
78
|
+
on_close: callback_type,
|
|
79
|
+
on_half_open: callback_type,
|
|
80
|
+
on_reject: callback_type,
|
|
81
|
+
exceptions: Array[Class],
|
|
82
|
+
owner: untyped
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
def initialize: (String name, ?circuit_config options) -> void
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def default_config: () -> circuit_config
|
|
90
|
+
def create_default_storage: () -> storage_type
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
module Execution
|
|
94
|
+
def call: [T] () { () -> T } -> untyped
|
|
95
|
+
def wrap: [T] () { () -> T } -> untyped
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
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
|
|
103
|
+
def reject_call: () -> untyped
|
|
104
|
+
def handle_success: () -> void
|
|
105
|
+
def handle_failure: () -> void
|
|
106
|
+
def failure_threshold_exceeded?: () -> bool
|
|
107
|
+
def success_threshold_reached?: () -> bool
|
|
108
|
+
def record_success: (Float duration) -> void
|
|
109
|
+
def record_failure: (Float duration, ?StandardError? error) -> void
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
module Introspection
|
|
113
|
+
type stats_hash = {
|
|
114
|
+
state: (:open | :closed | :half_open),
|
|
115
|
+
failure_count: Integer,
|
|
116
|
+
success_count: Integer,
|
|
117
|
+
last_failure_at: Float?,
|
|
118
|
+
opened_at: Float?,
|
|
119
|
+
half_open_attempts: Integer,
|
|
120
|
+
half_open_successes: Integer
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type error_info = {
|
|
124
|
+
class: String,
|
|
125
|
+
message: String,
|
|
126
|
+
occurred_at: Float
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
type circuit_hash = {
|
|
130
|
+
name: String,
|
|
131
|
+
state: (:open | :closed | :half_open),
|
|
132
|
+
stats: stats_hash,
|
|
133
|
+
config: Hash[Symbol, untyped],
|
|
134
|
+
event_log: Array[untyped],
|
|
135
|
+
last_error: error_info?
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
def stats: () -> stats_hash
|
|
139
|
+
def configuration: () -> Hash[Symbol, untyped]
|
|
140
|
+
def event_log: (?limit: Integer) -> Array[untyped]?
|
|
141
|
+
def last_error: () -> StandardError?
|
|
142
|
+
def to_h: () -> circuit_hash
|
|
143
|
+
def summary: () -> String
|
|
144
|
+
def last_error_info: () -> error_info?
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
module Callbacks
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def invoke_callback: (Symbol callback_name) -> void
|
|
151
|
+
def invoke_fallback: (StandardError error) -> untyped
|
|
152
|
+
def invoke_single_fallback: (Proc | untyped fallback, StandardError error) -> untyped
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module BreakerMachines
|
|
2
|
+
class Console
|
|
3
|
+
@running: bool
|
|
4
|
+
|
|
5
|
+
def self.start: () -> void
|
|
6
|
+
|
|
7
|
+
def initialize: () -> void
|
|
8
|
+
def run: () -> void
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def print_header: () -> void
|
|
13
|
+
def print_help: () -> void
|
|
14
|
+
def print_prompt: () -> void
|
|
15
|
+
def process_command: (String command) -> void
|
|
16
|
+
|
|
17
|
+
# Command methods
|
|
18
|
+
def list_circuits: () -> void
|
|
19
|
+
def show_stats: () -> void
|
|
20
|
+
def show_circuit: (String? name) -> void
|
|
21
|
+
def show_events: (String? name, Integer limit) -> void
|
|
22
|
+
def reset_circuit: (String? name) -> void
|
|
23
|
+
def force_open_circuit: (String? name) -> void
|
|
24
|
+
def force_close_circuit: (String? name) -> void
|
|
25
|
+
def generate_report: () -> void
|
|
26
|
+
def cleanup_registry: () -> void
|
|
27
|
+
|
|
28
|
+
# Utility methods
|
|
29
|
+
def colorize_state: (Symbol state) -> String
|
|
30
|
+
def colorize_event_type: (Symbol type) -> String
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module BreakerMachines
|
|
2
|
+
module DSL
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
@instance_registries: Concurrent::Map[Class, Concurrent::Array[WeakRef[untyped]]]
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
@circuits: Hash[Symbol, Hash[Symbol, untyped]]
|
|
9
|
+
|
|
10
|
+
def instance_registry: () -> Concurrent::Array[WeakRef[untyped]]
|
|
11
|
+
def cleanup_instance_registry: () -> void
|
|
12
|
+
def circuit: (Symbol name) ?{ (CircuitBuilder) -> void } -> Hash[Symbol, untyped]
|
|
13
|
+
def circuits: () -> Hash[Symbol, Hash[Symbol, untyped]]
|
|
14
|
+
def circuit_definitions: () -> Hash[Symbol, Hash[Symbol, untyped]]
|
|
15
|
+
def reset_all_circuits: () -> void
|
|
16
|
+
def circuit_stats: () -> Hash[Symbol, { total: Integer, by_state: Hash[Symbol, Integer] }]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def circuit: (Symbol name) -> Circuit
|
|
20
|
+
def circuit_instances: () -> Hash[Symbol, Circuit]
|
|
21
|
+
def circuits_summary: () -> Hash[Symbol, String]
|
|
22
|
+
def circuits_report: () -> Hash[Symbol, untyped]
|
|
23
|
+
def reset_all_circuits: () -> void
|
|
24
|
+
|
|
25
|
+
class CircuitBuilder
|
|
26
|
+
attr_reader config: Hash[Symbol, untyped]
|
|
27
|
+
|
|
28
|
+
def initialize: () -> void
|
|
29
|
+
|
|
30
|
+
# Configuration methods
|
|
31
|
+
def threshold: (?failures: Integer?, ?within: Integer, ?successes: Integer?) -> void
|
|
32
|
+
def reset_after: (Integer duration, ?jitter: Float?) -> void
|
|
33
|
+
def timeout: (Integer duration) -> void
|
|
34
|
+
def half_open_requests: (Integer count) -> void
|
|
35
|
+
def storage: ((:memory | :bucket_memory | :redis | Class | Storage::Base) backend, **untyped) -> void
|
|
36
|
+
def metrics: (?untyped recorder) ?{ () -> void } -> void
|
|
37
|
+
def fallback: (?untyped value) ?{ (StandardError) -> untyped } -> void
|
|
38
|
+
def on_open: () { () -> void } -> void
|
|
39
|
+
def on_close: () { () -> void } -> void
|
|
40
|
+
def on_half_open: () { () -> void } -> void
|
|
41
|
+
def on_reject: () { () -> void } -> void
|
|
42
|
+
def notify: (Symbol service, ?String? url, ?events: Array[Symbol], **untyped options) -> void
|
|
43
|
+
def handle: (*Class exceptions) -> void
|
|
44
|
+
|
|
45
|
+
# Advanced features
|
|
46
|
+
def backends: (Array[untyped] list) -> void
|
|
47
|
+
def parallel_calls: (Integer count, ?timeout: Integer?) -> void
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module BreakerMachines
|
|
2
|
+
class Error < StandardError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class CircuitOpenError < Error
|
|
6
|
+
attr_reader circuit_name: String
|
|
7
|
+
attr_reader opened_at: Float?
|
|
8
|
+
|
|
9
|
+
def initialize: (String circuit_name, ?Float? opened_at) -> void
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class CircuitTimeoutError < Error
|
|
13
|
+
attr_reader circuit_name: String
|
|
14
|
+
attr_reader timeout: Numeric
|
|
15
|
+
|
|
16
|
+
def initialize: (String circuit_name, Numeric timeout) -> void
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class ConfigurationError < Error
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class StorageError < Error
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module BreakerMachines
|
|
2
|
+
# Interface for objects that can record metrics
|
|
3
|
+
interface _MetricsRecorder
|
|
4
|
+
def record_success: (String circuit_name, Float duration) -> void
|
|
5
|
+
def record_failure: (String circuit_name, Float duration) -> void
|
|
6
|
+
def record_rejection: (String circuit_name) -> void
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Interface for storage backends
|
|
10
|
+
interface _StorageBackend
|
|
11
|
+
def get_status: (String circuit_name) -> status_record?
|
|
12
|
+
def set_status: (String circuit_name, circuit_state status, ?Float? opened_at) -> void
|
|
13
|
+
def record_success: (String circuit_name, Float duration) -> void
|
|
14
|
+
def record_failure: (String circuit_name, Float duration) -> void
|
|
15
|
+
def success_count: (String circuit_name, Integer window_seconds) -> Integer
|
|
16
|
+
def failure_count: (String circuit_name, Integer window_seconds) -> Integer
|
|
17
|
+
def clear: (String circuit_name) -> void
|
|
18
|
+
def clear_all: () -> void
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Interface for advanced storage backends with event logging
|
|
22
|
+
interface _AdvancedStorageBackend
|
|
23
|
+
def record_success: (String circuit_name, ?Float? duration) -> void
|
|
24
|
+
def record_failure: (String circuit_name, ?Float? duration) -> void
|
|
25
|
+
def success_count: (String circuit_name, ?Integer? window) -> Integer
|
|
26
|
+
def failure_count: (String circuit_name, ?Integer? window) -> Integer
|
|
27
|
+
def get_status: (String circuit_name) -> Hash[Symbol, untyped]?
|
|
28
|
+
def set_status: (String circuit_name, circuit_state status, ?Float? opened_at) -> void
|
|
29
|
+
def clear: (String circuit_name) -> void
|
|
30
|
+
def record_event_with_details: (String circuit_name, event_type event_type, Float duration, ?Hash[Symbol, untyped] details) -> void
|
|
31
|
+
def event_log: (String circuit_name, ?Integer limit) -> Array[event_record]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Interface for circuit-like objects
|
|
35
|
+
interface _CircuitLike
|
|
36
|
+
def name: () -> String
|
|
37
|
+
def call: () { () -> untyped } -> untyped
|
|
38
|
+
def wrap: () { () -> untyped } -> untyped
|
|
39
|
+
def status_name: () -> circuit_state
|
|
40
|
+
def open?: () -> bool
|
|
41
|
+
def closed?: () -> bool
|
|
42
|
+
def half_open?: () -> bool
|
|
43
|
+
def reset: () -> bool
|
|
44
|
+
def stats: () -> Hash[Symbol, untyped]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module BreakerMachines
|
|
2
|
+
class Registry
|
|
3
|
+
include Singleton
|
|
4
|
+
|
|
5
|
+
@circuits: Concurrent::Map[Integer, WeakRef[Circuit]]
|
|
6
|
+
@mutex: Mutex
|
|
7
|
+
@registration_count: Integer
|
|
8
|
+
@cleanup_interval: Integer
|
|
9
|
+
|
|
10
|
+
def initialize: () -> void
|
|
11
|
+
|
|
12
|
+
# Circuit management
|
|
13
|
+
def register: (Circuit circuit) -> void
|
|
14
|
+
def unregister: (Circuit circuit) -> void
|
|
15
|
+
def all_circuits: () -> Array[Circuit]
|
|
16
|
+
def find_by_name: (Symbol name) -> Array[Circuit]
|
|
17
|
+
|
|
18
|
+
# Reporting
|
|
19
|
+
def stats_summary: () -> { total: Integer, by_state: Hash[Symbol, Integer], by_name: Hash[Symbol, Integer] }
|
|
20
|
+
def detailed_report: () -> Array[Hash[Symbol, untyped]]
|
|
21
|
+
|
|
22
|
+
# Maintenance
|
|
23
|
+
def clear: () -> void
|
|
24
|
+
def cleanup_dead_references: () -> void
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def cleanup_dead_references_unsafe: () -> void
|
|
29
|
+
end
|
|
30
|
+
end
|