breaker_machines 0.5.0 → 0.7.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 +3 -0
  14. data/ext/breaker_machines_native/ffi/Cargo.toml +16 -0
  15. data/ext/breaker_machines_native/ffi/extconf.rb +67 -0
  16. data/ext/breaker_machines_native/ffi/src/lib.rs +218 -0
  17. data/ext/breaker_machines_native/target/debug/build/clang-sys-d961dfabd5f43fba/out/common.rs +355 -0
  18. data/ext/breaker_machines_native/target/debug/build/clang-sys-d961dfabd5f43fba/out/dynamic.rs +276 -0
  19. data/ext/breaker_machines_native/target/debug/build/clang-sys-d961dfabd5f43fba/out/macros.rs +49 -0
  20. 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
  21. 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
  22. 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
  23. 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
  24. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/Cargo.toml +48 -0
  25. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/examples/basic.rs +61 -0
  26. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/builder.rs +154 -0
  27. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/callbacks.rs +55 -0
  28. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/circuit.rs +607 -0
  29. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/errors.rs +38 -0
  30. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/lib.rs +58 -0
  31. data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/storage.rs +377 -0
  32. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/Cargo.toml +48 -0
  33. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/examples/basic.rs +61 -0
  34. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/builder.rs +173 -0
  35. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/callbacks.rs +55 -0
  36. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/circuit.rs +855 -0
  37. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/errors.rs +38 -0
  38. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/lib.rs +58 -0
  39. data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/storage.rs +377 -0
  40. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/Cargo.toml +48 -0
  41. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/examples/basic.rs +61 -0
  42. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/builder.rs +154 -0
  43. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/callbacks.rs +55 -0
  44. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/circuit.rs +607 -0
  45. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/errors.rs +38 -0
  46. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/lib.rs +58 -0
  47. data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/storage.rs +377 -0
  48. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/Cargo.toml +48 -0
  49. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/examples/basic.rs +61 -0
  50. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/builder.rs +232 -0
  51. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/bulkhead.rs +223 -0
  52. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/callbacks.rs +58 -0
  53. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/circuit.rs +1156 -0
  54. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/classifier.rs +177 -0
  55. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/errors.rs +47 -0
  56. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/lib.rs +62 -0
  57. data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/storage.rs +377 -0
  58. data/ext/breaker_machines_native/target/release/build/clang-sys-ef8ad8b846ac8b75/out/common.rs +355 -0
  59. data/ext/breaker_machines_native/target/release/build/clang-sys-ef8ad8b846ac8b75/out/dynamic.rs +276 -0
  60. data/ext/breaker_machines_native/target/release/build/clang-sys-ef8ad8b846ac8b75/out/macros.rs +49 -0
  61. 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
  62. data/lib/breaker_machines/circuit/async_state_management.rb +1 -1
  63. data/lib/breaker_machines/circuit/base.rb +2 -1
  64. data/lib/breaker_machines/circuit/coordinated_state_management.rb +3 -3
  65. data/lib/breaker_machines/circuit/native.rb +127 -0
  66. data/lib/breaker_machines/circuit/state_callbacks.rb +17 -5
  67. data/lib/breaker_machines/circuit/state_management.rb +1 -1
  68. data/lib/breaker_machines/native_extension.rb +44 -0
  69. data/lib/breaker_machines/native_speedup.rb +10 -0
  70. data/lib/breaker_machines/storage/bucket_memory.rb +4 -1
  71. data/lib/breaker_machines/storage/memory.rb +4 -1
  72. data/lib/breaker_machines/storage/native.rb +93 -0
  73. data/lib/breaker_machines/version.rb +1 -1
  74. data/lib/breaker_machines.rb +98 -11
  75. data/sig/breaker_machines.rbs +20 -8
  76. metadata +116 -7
@@ -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,44 @@
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
+ # Respect explicit ENV flag to disable native (useful for testing)
13
+ if ENV['BREAKER_MACHINES_NATIVE'] == '0'
14
+ @loaded = false
15
+ BreakerMachines.instance_variable_set(:@native_available, false)
16
+ BreakerMachines.log(:info, 'Native extension disabled via ENV')
17
+ return false
18
+ end
19
+
20
+ @loaded = true
21
+ require 'breaker_machines_native/breaker_machines_native'
22
+ BreakerMachines.instance_variable_set(:@native_available, true)
23
+ BreakerMachines.log(:info, 'Native extension loaded successfully')
24
+ true
25
+ rescue LoadError => e
26
+ @loaded = false
27
+ BreakerMachines.instance_variable_set(:@native_available, false)
28
+
29
+ # Only log if it's not JRuby (expected failure) and logging is enabled
30
+ if RUBY_ENGINE != 'jruby'
31
+ BreakerMachines.log(:warn, "Native extension not available: #{e.message}")
32
+ BreakerMachines.log(:warn, 'Using pure Ruby backend (slower but functional)')
33
+ end
34
+
35
+ false
36
+ end
37
+
38
+ # Check if load was attempted
39
+ def loaded?
40
+ defined?(@loaded) && @loaded
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'native_extension'
4
+
5
+ # Load the native extension if available
6
+ if BreakerMachines::NativeExtension.load!
7
+ # Only load Storage::Native if native extension loaded successfully
8
+ # This prevents referencing BreakerMachinesNative::Storage when not available
9
+ require_relative 'storage/native'
10
+ end
@@ -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,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BreakerMachines
4
+ module Storage
5
+ # Native extension storage backend with graceful fallback to pure Ruby
6
+ #
7
+ # This backend provides identical functionality to Memory storage but with
8
+ # significantly better performance for sliding window calculations when the
9
+ # native extension is available. If the native extension isn't available
10
+ # (e.g., on JRuby or if Rust wasn't installed), it automatically falls back
11
+ # to the pure Ruby Memory storage backend.
12
+ #
13
+ # Performance: ~63x faster than Memory storage when native extension is available
14
+ #
15
+ # Usage:
16
+ # BreakerMachines.configure do |config|
17
+ # config.default_storage = :native
18
+ # end
19
+ #
20
+ # FFI Hybrid Pattern: This class is only loaded when native extension is available
21
+ # Fallback happens at load time (native_speedup.rb), not at runtime
22
+ class Native < Base
23
+ def initialize(**options)
24
+ super
25
+ # Native extension is guaranteed to be available when this class is loaded
26
+ @backend = BreakerMachinesNative::Storage.new
27
+ end
28
+
29
+ # Check if using native backend
30
+ # @return [Boolean] always true - this class only exists when native is available
31
+ def native?
32
+ true
33
+ end
34
+
35
+ def get_status(_circuit_name)
36
+ # Status is still managed by Ruby layer
37
+ # This storage backend only handles event tracking
38
+ nil
39
+ end
40
+
41
+ def set_status(circuit_name, status, opened_at = nil)
42
+ # Status management delegated to Ruby layer
43
+ # This backend focuses on high-performance event counting
44
+ end
45
+
46
+ def record_success(circuit_name, duration)
47
+ @backend.record_success(circuit_name.to_s, duration)
48
+ end
49
+
50
+ def record_failure(circuit_name, duration)
51
+ @backend.record_failure(circuit_name.to_s, duration)
52
+ end
53
+
54
+ def success_count(circuit_name, window_seconds)
55
+ @backend.success_count(circuit_name.to_s, window_seconds)
56
+ end
57
+
58
+ def failure_count(circuit_name, window_seconds)
59
+ @backend.failure_count(circuit_name.to_s, window_seconds)
60
+ end
61
+
62
+ def clear(circuit_name)
63
+ @backend.clear(circuit_name.to_s)
64
+ end
65
+
66
+ def clear_all
67
+ @backend.clear_all
68
+ end
69
+
70
+ def record_event_with_details(circuit_name, type, duration, error: nil, new_state: nil)
71
+ # Basic event recording (native extension handles type and duration)
72
+ case type
73
+ when :success
74
+ record_success(circuit_name, duration)
75
+ when :failure
76
+ record_failure(circuit_name, duration)
77
+ end
78
+
79
+ # NOTE: Error and state details not tracked in native backend
80
+ # This is intentional for performance - use Memory backend if you need full event details
81
+ end
82
+
83
+ def event_log(circuit_name, limit)
84
+ @backend.event_log(circuit_name.to_s, limit)
85
+ end
86
+
87
+ def with_timeout(_timeout_ms)
88
+ # Native operations should be instant
89
+ yield
90
+ end
91
+ end
92
+ end
93
+ 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.7.0'
5
5
  end
@@ -15,32 +15,95 @@ 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")
20
+ loader.ignore("#{__dir__}/breaker_machines/storage/native.rb")
18
21
  loader.setup
19
22
 
20
23
  # BreakerMachines provides a thread-safe implementation of the Circuit Breaker pattern
21
24
  # for Ruby applications, helping to prevent cascading failures in distributed systems.
22
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
+
23
45
  class << self
24
46
  def loader
25
47
  loader
26
48
  end
27
- end
28
-
29
- # Global configuration
30
- include ActiveSupport::Configurable
31
49
 
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
50
+ def config
51
+ @config ||= Configuration.new
52
+ end
38
53
 
39
- class << self
40
54
  def configure
41
55
  yield config
42
56
  end
43
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
+
44
107
  def setup_notifications
45
108
  return unless config.log_events
46
109
 
@@ -65,12 +128,27 @@ module BreakerMachines
65
128
 
66
129
  attr_writer :logger
67
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
+
68
140
  def instrument(event, payload = {})
69
141
  return unless config.log_events
70
142
 
71
143
  ActiveSupport::Notifications.instrument("breaker_machines.#{event}", payload)
72
144
  end
73
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
+
74
152
  # Launch the interactive console
75
153
  def console
76
154
  require_relative 'breaker_machines/console'
@@ -111,3 +189,12 @@ module BreakerMachines
111
189
  # Set up notifications on first use
112
190
  setup_notifications if config.log_events
113
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
@@ -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