breaker_machines 0.5.0 → 0.6.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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/ext/breaker_machines_native/Cargo.toml +8 -0
  3. data/ext/breaker_machines_native/core/Cargo.toml +18 -0
  4. data/ext/breaker_machines_native/core/examples/basic.rs +61 -0
  5. data/ext/breaker_machines_native/core/src/builder.rs +232 -0
  6. data/ext/breaker_machines_native/core/src/bulkhead.rs +223 -0
  7. data/ext/breaker_machines_native/core/src/callbacks.rs +58 -0
  8. data/ext/breaker_machines_native/core/src/circuit.rs +1156 -0
  9. data/ext/breaker_machines_native/core/src/classifier.rs +177 -0
  10. data/ext/breaker_machines_native/core/src/errors.rs +47 -0
  11. data/ext/breaker_machines_native/core/src/lib.rs +62 -0
  12. data/ext/breaker_machines_native/core/src/storage.rs +377 -0
  13. data/ext/breaker_machines_native/extconf.rb +40 -0
  14. data/ext/breaker_machines_native/ffi/Cargo.toml +16 -0
  15. data/ext/breaker_machines_native/ffi/src/lib.rs +218 -0
  16. data/ext/breaker_machines_native/target/debug/build/clang-sys-d961dfabd5f43fba/out/common.rs +355 -0
  17. data/ext/breaker_machines_native/target/debug/build/clang-sys-d961dfabd5f43fba/out/dynamic.rs +276 -0
  18. data/ext/breaker_machines_native/target/debug/build/clang-sys-d961dfabd5f43fba/out/macros.rs +49 -0
  19. data/ext/breaker_machines_native/target/debug/build/rb-sys-2bb7281aac8faec8/out/bindings-0.9.117-mri-arm64-darwin24-3.4.7.rs +8936 -0
  20. data/ext/breaker_machines_native/target/debug/build/rb-sys-54cb99ea6aeab8bc/out/bindings-0.9.117-mri-arm64-darwin24-3.4.7.rs +8936 -0
  21. data/ext/breaker_machines_native/target/debug/build/rb-sys-9e64a270c6421e93/out/bindings-0.9.117-mri-arm64-darwin24-3.4.7.rs +8936 -0
  22. data/ext/breaker_machines_native/target/debug/build/rb-sys-e627030114d3fc19/out/bindings-0.9.117-mri-arm64-darwin24-3.4.7.rs +8936 -0
  23. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/Cargo.toml +48 -0
  24. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/examples/basic.rs +61 -0
  25. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/builder.rs +154 -0
  26. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/callbacks.rs +55 -0
  27. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/circuit.rs +607 -0
  28. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/errors.rs +38 -0
  29. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/lib.rs +58 -0
  30. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/storage.rs +377 -0
  31. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/Cargo.toml +48 -0
  32. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/examples/basic.rs +61 -0
  33. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/builder.rs +173 -0
  34. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/callbacks.rs +55 -0
  35. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/circuit.rs +855 -0
  36. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/errors.rs +38 -0
  37. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/lib.rs +58 -0
  38. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/storage.rs +377 -0
  39. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/Cargo.toml +48 -0
  40. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/examples/basic.rs +61 -0
  41. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/builder.rs +154 -0
  42. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/callbacks.rs +55 -0
  43. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/circuit.rs +607 -0
  44. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/errors.rs +38 -0
  45. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/lib.rs +58 -0
  46. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/storage.rs +377 -0
  47. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/Cargo.toml +48 -0
  48. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/examples/basic.rs +61 -0
  49. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/builder.rs +232 -0
  50. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/bulkhead.rs +223 -0
  51. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/callbacks.rs +58 -0
  52. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/circuit.rs +1156 -0
  53. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/classifier.rs +177 -0
  54. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/errors.rs +47 -0
  55. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/lib.rs +62 -0
  56. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/storage.rs +377 -0
  57. data/ext/breaker_machines_native/target/release/build/clang-sys-ef8ad8b846ac8b75/out/common.rs +355 -0
  58. data/ext/breaker_machines_native/target/release/build/clang-sys-ef8ad8b846ac8b75/out/dynamic.rs +276 -0
  59. data/ext/breaker_machines_native/target/release/build/clang-sys-ef8ad8b846ac8b75/out/macros.rs +49 -0
  60. data/ext/breaker_machines_native/target/release/build/rb-sys-064bf9961dd17810/out/bindings-0.9.117-mri-arm64-darwin24-3.4.7.rs +8936 -0
  61. data/lib/breaker_machines/circuit/async_state_management.rb +1 -1
  62. data/lib/breaker_machines/circuit/base.rb +2 -1
  63. data/lib/breaker_machines/circuit/coordinated_state_management.rb +3 -3
  64. data/lib/breaker_machines/circuit/native.rb +127 -0
  65. data/lib/breaker_machines/circuit/state_callbacks.rb +17 -5
  66. data/lib/breaker_machines/circuit/state_management.rb +1 -1
  67. data/lib/breaker_machines/native_extension.rb +36 -0
  68. data/lib/breaker_machines/native_speedup.rb +6 -0
  69. data/lib/breaker_machines/storage/bucket_memory.rb +4 -1
  70. data/lib/breaker_machines/storage/memory.rb +4 -1
  71. data/lib/breaker_machines/storage/native.rb +90 -0
  72. data/lib/breaker_machines/version.rb +1 -1
  73. data/lib/breaker_machines.rb +98 -11
  74. data/lib/breaker_machines_native/breaker_machines_native.bundle +0 -0
  75. data/sig/breaker_machines.rbs +20 -8
  76. metadata +99 -6
@@ -41,7 +41,7 @@ module BreakerMachines
41
41
  end
42
42
 
43
43
  before_transition on: :hard_reset do |circuit|
44
- circuit.storage.clear(circuit.name) if circuit.storage
44
+ circuit.storage&.clear(circuit.name)
45
45
  circuit.half_open_attempts.value = 0
46
46
  circuit.half_open_successes.value = 0
47
47
  end
@@ -16,7 +16,8 @@ module BreakerMachines
16
16
  include Circuit::Callbacks
17
17
  include Circuit::StateCallbacks
18
18
 
19
- attr_reader :name, :config, :opened_at, :storage, :metrics, :semaphore
19
+ # name/config/opened_at readers are defined in Circuit::Configuration
20
+ attr_reader :storage, :metrics, :semaphore
20
21
  attr_reader :half_open_attempts, :half_open_successes, :mutex
21
22
  attr_reader :last_failure_at, :last_error
22
23
  end
@@ -17,12 +17,12 @@ module BreakerMachines
17
17
 
18
18
  event :attempt_recovery do
19
19
  transition open: :half_open,
20
- if: ->(circuit) { circuit.recovery_allowed? }
20
+ if: lambda(&:recovery_allowed?)
21
21
  end
22
22
 
23
23
  event :reset do
24
24
  transition %i[open half_open] => :closed,
25
- if: ->(circuit) { circuit.reset_allowed? }
25
+ if: lambda(&:reset_allowed?)
26
26
  transition closed: :closed
27
27
  end
28
28
 
@@ -39,7 +39,7 @@ module BreakerMachines
39
39
  end
40
40
 
41
41
  before_transition on: :hard_reset do |circuit|
42
- circuit.storage.clear(circuit.name) if circuit.storage
42
+ circuit.storage&.clear(circuit.name)
43
43
  circuit.half_open_attempts.value = 0
44
44
  circuit.half_open_successes.value = 0
45
45
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BreakerMachines
4
+ class Circuit
5
+ # Native circuit breaker implementation using Rust FFI
6
+ #
7
+ # This provides a high-performance circuit breaker with state machine logic
8
+ # implemented in Rust. It's fully compatible with the Ruby circuit API but
9
+ # significantly faster for high-throughput scenarios.
10
+ #
11
+ # @example Basic usage
12
+ # circuit = BreakerMachines::Circuit::Native.new('api_calls',
13
+ # failure_threshold: 5,
14
+ # failure_window_secs: 60.0
15
+ # )
16
+ #
17
+ # circuit.call { api.fetch_data }
18
+ class Native
19
+ # @return [String] Circuit name
20
+ attr_reader :name
21
+
22
+ # @return [Hash] Circuit configuration
23
+ attr_reader :config
24
+
25
+ # Create a new native circuit breaker
26
+ #
27
+ # @param name [String] Circuit name
28
+ # @param options [Hash] Configuration options
29
+ # @option options [Integer] :failure_threshold Number of failures to open circuit (default: 5)
30
+ # @option options [Float] :failure_window_secs Time window for counting failures (default: 60.0)
31
+ # @option options [Float] :half_open_timeout_secs Timeout before attempting reset (default: 30.0)
32
+ # @option options [Integer] :success_threshold Successes needed to close from half-open (default: 2)
33
+ # @option options [Boolean] :auto_register Register with global registry (default: true)
34
+ def initialize(name, options = {})
35
+ unless BreakerMachines.native_available?
36
+ raise BreakerMachines::ConfigurationError,
37
+ 'Native extension not available. Install with native support or use Circuit::Ruby'
38
+ end
39
+
40
+ @name = name
41
+ @config = default_config.merge(options)
42
+
43
+ # Create the native circuit breaker
44
+ @native_circuit = BreakerMachinesNative::Circuit.new(
45
+ name,
46
+ {
47
+ failure_threshold: @config[:failure_threshold],
48
+ failure_window_secs: @config[:failure_window_secs],
49
+ half_open_timeout_secs: @config[:half_open_timeout_secs],
50
+ success_threshold: @config[:success_threshold]
51
+ }
52
+ )
53
+
54
+ # Register with global registry unless disabled
55
+ BreakerMachines::Registry.instance.register(self) unless @config[:auto_register] == false
56
+ end
57
+
58
+ # Execute a block with circuit breaker protection
59
+ #
60
+ # @yield Block to execute
61
+ # @return Result of the block
62
+ # @raise [CircuitOpenError] if circuit is open
63
+ def call
64
+ raise CircuitOpenError, "Circuit '#{@name}' is open" if open?
65
+
66
+ start_time = BreakerMachines.monotonic_time
67
+ begin
68
+ result = yield
69
+ duration = BreakerMachines.monotonic_time - start_time
70
+ @native_circuit.record_success(duration)
71
+ result
72
+ rescue StandardError => _e
73
+ duration = BreakerMachines.monotonic_time - start_time
74
+ @native_circuit.record_failure(duration)
75
+ raise
76
+ end
77
+ end
78
+
79
+ # Check if circuit is open
80
+ # @return [Boolean]
81
+ def open?
82
+ @native_circuit.is_open
83
+ end
84
+
85
+ # Check if circuit is closed
86
+ # @return [Boolean]
87
+ def closed?
88
+ @native_circuit.is_closed
89
+ end
90
+
91
+ # Get current state name
92
+ # @return [String] 'open' or 'closed'
93
+ def state
94
+ @native_circuit.state_name
95
+ end
96
+
97
+ # Reset the circuit (clear all events)
98
+ def reset!
99
+ @native_circuit.reset
100
+ end
101
+
102
+ # Get circuit status for inspection
103
+ # @return [Hash] Status information
104
+ def status
105
+ {
106
+ name: @name,
107
+ state: state,
108
+ open: open?,
109
+ closed: closed?,
110
+ config: @config
111
+ }
112
+ end
113
+
114
+ private
115
+
116
+ def default_config
117
+ {
118
+ failure_threshold: 5,
119
+ failure_window_secs: 60.0,
120
+ half_open_timeout_secs: 30.0,
121
+ success_threshold: 2,
122
+ auto_register: true
123
+ }
124
+ end
125
+ end
126
+ end
127
+ end
@@ -47,14 +47,26 @@ module BreakerMachines
47
47
  def reset_timeout_elapsed?
48
48
  return false unless @opened_at.value
49
49
 
50
- # Add jitter to prevent thundering herd
51
- jitter_factor = @config[:reset_timeout_jitter] || 0.25
52
- # Calculate random jitter between -jitter_factor and +jitter_factor
53
- jitter_multiplier = 1.0 + (((rand * 2) - 1) * jitter_factor)
54
- timeout_with_jitter = @config[:reset_timeout] * jitter_multiplier
50
+ # Add jitter to prevent thundering herd using ChronoMachines
51
+ # This matches the Rust implementation which uses chrono-machines for jitter
52
+ timeout_with_jitter = if (jitter_factor = @config[:reset_timeout_jitter]) && jitter_factor.positive?
53
+ calculate_timeout_with_jitter(@config[:reset_timeout], jitter_factor)
54
+ else
55
+ @config[:reset_timeout]
56
+ end
55
57
 
56
58
  BreakerMachines.monotonic_time - @opened_at.value >= timeout_with_jitter
57
59
  end
60
+
61
+ # Calculate timeout with jitter using ChronoMachines algorithm
62
+ # Matches the Rust implementation: timeout * (1 - jitter + rand * jitter)
63
+ def calculate_timeout_with_jitter(base_timeout, jitter_factor)
64
+ # Use full jitter strategy from ChronoMachines
65
+ # Formula: base * (1 - jitter + rand * jitter)
66
+ # This gives values in range [base * (1-jitter), base]
67
+ normalized_jitter = [jitter_factor.to_f, 1.0].min
68
+ base_timeout * (1.0 - normalized_jitter + (rand * normalized_jitter))
69
+ end
58
70
  end
59
71
  end
60
72
  end
@@ -36,7 +36,7 @@ module BreakerMachines
36
36
  end
37
37
 
38
38
  before_transition on: :hard_reset do |circuit|
39
- circuit.storage.clear(circuit.name) if circuit.storage
39
+ circuit.storage&.clear(circuit.name)
40
40
  circuit.half_open_attempts.value = 0
41
41
  circuit.half_open_successes.value = 0
42
42
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BreakerMachines
4
+ # Handles loading and status of the optional native extension
5
+ module NativeExtension
6
+ class << self
7
+ # Load the native extension and set availability flag
8
+ # Can be called multiple times - subsequent calls are memoized
9
+ def load!
10
+ return @loaded if defined?(@loaded)
11
+
12
+ @loaded = true
13
+ require 'breaker_machines_native/breaker_machines_native'
14
+ BreakerMachines.instance_variable_set(:@native_available, true)
15
+ BreakerMachines.log(:info, 'Native extension loaded successfully')
16
+ true
17
+ rescue LoadError => e
18
+ @loaded = false
19
+ BreakerMachines.instance_variable_set(:@native_available, false)
20
+
21
+ # Only log if it's not JRuby (expected failure) and logging is enabled
22
+ if RUBY_ENGINE != 'jruby'
23
+ BreakerMachines.log(:warn, "Native extension not available: #{e.message}")
24
+ BreakerMachines.log(:warn, 'Using pure Ruby backend (slower but functional)')
25
+ end
26
+
27
+ false
28
+ end
29
+
30
+ # Check if load was attempted
31
+ def loaded?
32
+ defined?(@loaded) && @loaded
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'native_extension'
4
+
5
+ # Load the native extension if available
6
+ BreakerMachines::NativeExtension.load!
@@ -21,6 +21,8 @@ module BreakerMachines
21
21
  @event_logs = Concurrent::Map.new
22
22
  @bucket_count = options[:bucket_count] || 300 # Default 5 minutes
23
23
  @max_events = options[:max_events] || 100
24
+ # Store creation time as anchor for relative timestamps (like Rust implementation)
25
+ @start_time = BreakerMachines.monotonic_time
24
26
  end
25
27
 
26
28
  def get_status(circuit_name)
@@ -160,7 +162,8 @@ module BreakerMachines
160
162
  end
161
163
 
162
164
  def monotonic_time
163
- BreakerMachines.monotonic_time
165
+ # Return time relative to storage creation (matches Rust implementation)
166
+ BreakerMachines.monotonic_time - @start_time
164
167
  end
165
168
 
166
169
  def with_timeout(_timeout_ms)
@@ -17,6 +17,8 @@ module BreakerMachines
17
17
  @events = Concurrent::Map.new
18
18
  @event_logs = Concurrent::Map.new
19
19
  @max_events = options[:max_events] || 100
20
+ # Store creation time as anchor for relative timestamps (like Rust implementation)
21
+ @start_time = BreakerMachines.monotonic_time
20
22
  end
21
23
 
22
24
  def get_status(circuit_name)
@@ -130,7 +132,8 @@ module BreakerMachines
130
132
  end
131
133
 
132
134
  def monotonic_time
133
- BreakerMachines.monotonic_time
135
+ # Return time relative to storage creation (matches Rust implementation)
136
+ BreakerMachines.monotonic_time - @start_time
134
137
  end
135
138
  end
136
139
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BreakerMachines
4
+ module Storage
5
+ # Native extension storage backend for high-performance event tracking
6
+ #
7
+ # This backend provides identical functionality to Memory storage but with
8
+ # significantly better performance for sliding window calculations. It's
9
+ # particularly beneficial for high-throughput applications where circuit
10
+ # breaker state checks happen on every request.
11
+ #
12
+ # Performance: ~63x faster than Memory storage for sliding window calculations
13
+ #
14
+ # Usage:
15
+ # BreakerMachines.configure do |config|
16
+ # config.default_storage = :native
17
+ # end
18
+ #
19
+ # Fallback: If the native extension isn't available (e.g., on JRuby or if
20
+ # Rust wasn't installed during gem installation), this will raise LoadError.
21
+ # Use Storage::Memory as a fallback in such cases.
22
+ class Native < Base
23
+ def initialize(**options)
24
+ super
25
+ unless defined?(BreakerMachinesNative::Storage)
26
+ raise LoadError, 'Native extension not available. Use Storage::Memory instead.'
27
+ end
28
+
29
+ @native = BreakerMachinesNative::Storage.new
30
+ end
31
+
32
+ def get_status(_circuit_name)
33
+ # Status is still managed by Ruby layer
34
+ # This storage backend only handles event tracking
35
+ nil
36
+ end
37
+
38
+ def set_status(circuit_name, status, opened_at = nil)
39
+ # Status management delegated to Ruby layer
40
+ # This backend focuses on high-performance event counting
41
+ end
42
+
43
+ def record_success(circuit_name, duration)
44
+ @native.record_success(circuit_name.to_s, duration.to_f)
45
+ end
46
+
47
+ def record_failure(circuit_name, duration)
48
+ @native.record_failure(circuit_name.to_s, duration.to_f)
49
+ end
50
+
51
+ def success_count(circuit_name, window_seconds)
52
+ @native.success_count(circuit_name.to_s, window_seconds.to_f)
53
+ end
54
+
55
+ def failure_count(circuit_name, window_seconds)
56
+ @native.failure_count(circuit_name.to_s, window_seconds.to_f)
57
+ end
58
+
59
+ def clear(circuit_name)
60
+ @native.clear(circuit_name.to_s)
61
+ end
62
+
63
+ def clear_all
64
+ @native.clear_all
65
+ end
66
+
67
+ def record_event_with_details(circuit_name, type, duration, error: nil, new_state: nil)
68
+ # Basic event recording (native extension handles type and duration)
69
+ case type
70
+ when :success
71
+ record_success(circuit_name, duration)
72
+ when :failure
73
+ record_failure(circuit_name, duration)
74
+ end
75
+
76
+ # NOTE: Error and state details not tracked in native backend
77
+ # This is intentional for performance - use Memory backend if you need full event details
78
+ end
79
+
80
+ def event_log(circuit_name, limit)
81
+ @native.event_log(circuit_name.to_s, limit)
82
+ end
83
+
84
+ def with_timeout(_timeout_ms)
85
+ # Native operations should be instant
86
+ yield
87
+ end
88
+ end
89
+ end
90
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BreakerMachines
4
- VERSION = '0.5.0'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -15,32 +15,94 @@ loader.ignore("#{__dir__}/breaker_machines/console.rb")
15
15
  loader.ignore("#{__dir__}/breaker_machines/async_support.rb")
16
16
  loader.ignore("#{__dir__}/breaker_machines/hedged_async_support.rb")
17
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")
18
20
  loader.setup
19
21
 
20
22
  # BreakerMachines provides a thread-safe implementation of the Circuit Breaker pattern
21
23
  # for Ruby applications, helping to prevent cascading failures in distributed systems.
22
24
  module BreakerMachines
25
+ # Global configuration class for BreakerMachines
26
+ class Configuration
27
+ attr_accessor :default_storage,
28
+ :default_timeout,
29
+ :default_reset_timeout,
30
+ :default_failure_threshold,
31
+ :log_events,
32
+ :fiber_safe
33
+
34
+ def initialize
35
+ @default_storage = :bucket_memory
36
+ @default_timeout = nil
37
+ @default_reset_timeout = 60.seconds
38
+ @default_failure_threshold = 5
39
+ @log_events = true
40
+ @fiber_safe = false
41
+ end
42
+ end
43
+
23
44
  class << self
24
45
  def loader
25
46
  loader
26
47
  end
27
- end
28
-
29
- # Global configuration
30
- include ActiveSupport::Configurable
31
48
 
32
- config_accessor :default_storage, default: :bucket_memory
33
- config_accessor :default_timeout, default: nil
34
- config_accessor :default_reset_timeout, default: 60.seconds
35
- config_accessor :default_failure_threshold, default: 5
36
- config_accessor :log_events, default: true
37
- config_accessor :fiber_safe, default: false
49
+ def config
50
+ @config ||= Configuration.new
51
+ end
38
52
 
39
- class << self
40
53
  def configure
41
54
  yield config
42
55
  end
43
56
 
57
+ # Delegate config attributes to config object for backward compatibility
58
+ def default_storage
59
+ config.default_storage
60
+ end
61
+
62
+ def default_storage=(value)
63
+ config.default_storage = value
64
+ end
65
+
66
+ def default_timeout
67
+ config.default_timeout
68
+ end
69
+
70
+ def default_timeout=(value)
71
+ config.default_timeout = value
72
+ end
73
+
74
+ def default_reset_timeout
75
+ config.default_reset_timeout
76
+ end
77
+
78
+ def default_reset_timeout=(value)
79
+ config.default_reset_timeout = value
80
+ end
81
+
82
+ def default_failure_threshold
83
+ config.default_failure_threshold
84
+ end
85
+
86
+ def default_failure_threshold=(value)
87
+ config.default_failure_threshold = value
88
+ end
89
+
90
+ def log_events
91
+ config.log_events
92
+ end
93
+
94
+ def log_events=(value)
95
+ config.log_events = value
96
+ end
97
+
98
+ def fiber_safe
99
+ config.fiber_safe
100
+ end
101
+
102
+ def fiber_safe=(value)
103
+ config.fiber_safe = value
104
+ end
105
+
44
106
  def setup_notifications
45
107
  return unless config.log_events
46
108
 
@@ -65,12 +127,27 @@ module BreakerMachines
65
127
 
66
128
  attr_writer :logger
67
129
 
130
+ # Centralized logging helper
131
+ # @param level [Symbol] log level (:debug, :info, :warn, :error)
132
+ # @param message [String] message to log
133
+ def log(level, message)
134
+ return unless config.log_events && logger
135
+
136
+ logger.public_send(level, "[BreakerMachines] #{message}")
137
+ end
138
+
68
139
  def instrument(event, payload = {})
69
140
  return unless config.log_events
70
141
 
71
142
  ActiveSupport::Notifications.instrument("breaker_machines.#{event}", payload)
72
143
  end
73
144
 
145
+ # Check if native extension is available
146
+ # @return [Boolean] true if native extension loaded successfully
147
+ def native_available?
148
+ @native_available || false
149
+ end
150
+
74
151
  # Launch the interactive console
75
152
  def console
76
153
  require_relative 'breaker_machines/console'
@@ -111,3 +188,13 @@ module BreakerMachines
111
188
  # Set up notifications on first use
112
189
  setup_notifications if config.log_events
113
190
  end
191
+
192
+ # Load optional native speedup after core is loaded
193
+ # Opt-in with BREAKER_MACHINES_NATIVE=1 to enable native extensions
194
+ if ENV['BREAKER_MACHINES_NATIVE'] == '1'
195
+ begin
196
+ require_relative 'breaker_machines/native_speedup'
197
+ rescue LoadError
198
+ # Native gem not available, skip native support
199
+ end
200
+ end
@@ -1,14 +1,27 @@
1
1
  module BreakerMachines
2
2
  VERSION: String
3
3
 
4
+ class Configuration
5
+ attr_accessor default_storage: (:memory | :bucket_memory | :null | untyped)
6
+ attr_accessor default_timeout: Integer?
7
+ attr_accessor default_reset_timeout: Integer
8
+ attr_accessor default_failure_threshold: Integer
9
+ attr_accessor log_events: bool
10
+ attr_accessor fiber_safe: bool
11
+
12
+ def initialize: () -> void
13
+ end
14
+
4
15
  # Global configuration
5
- extend ActiveSupport::Configurable
16
+ self.@config: Configuration?
17
+ def self.config: () -> Configuration
18
+
19
+ # Class methods
20
+ def self.configure: () { (Configuration config) -> void } -> void
6
21
 
7
- # Configuration accessors
8
- self.@config: untyped
9
- def self.config: () -> ActiveSupport::Configurable::Configuration
22
+ # Delegated config accessors for backward compatibility
10
23
  def self.default_storage: () -> (:memory | :bucket_memory | :null | untyped)
11
- def self.default_storage=: (:memory | :bucket_memory | :null | untyped value) -> void
24
+ def self.default_storage=: ((:memory | :bucket_memory | :null | untyped) value) -> void
12
25
  def self.default_timeout: () -> Integer?
13
26
  def self.default_timeout=: (Integer? value) -> void
14
27
  def self.default_reset_timeout: () -> Integer
@@ -17,9 +30,8 @@ module BreakerMachines
17
30
  def self.default_failure_threshold=: (Integer value) -> void
18
31
  def self.log_events: () -> bool
19
32
  def self.log_events=: (bool value) -> void
20
-
21
- # Class methods
22
- def self.configure: () { (untyped config) -> void } -> void
33
+ def self.fiber_safe: () -> bool
34
+ def self.fiber_safe=: (bool value) -> void
23
35
  def self.setup_notifications: () -> void
24
36
  def self.logger: () -> ActiveSupport::Logger?
25
37
  def self.logger=: (ActiveSupport::Logger? logger) -> void