breaker_machines 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fae87263df3eaaba47c2a78df338740ae9ed8599e13a42867110270787f9dc8a
4
- data.tar.gz: 03bb57d8e463c3bd92ed2d7938788563b3df6d94329f87cf176b6934c218a963
3
+ metadata.gz: 5137f3692641c76411b86e725df03e901220a09d18a9bc1ba64fc1834d93b811
4
+ data.tar.gz: f5f0c05289c82dfe6aa1fe23322a531fbfd9faa1b8e9f29575eb5c801aacf712
5
5
  SHA512:
6
- metadata.gz: 67a9a1adad11d9d83fba339ccf3fe24317dde8e1da919b5fa7ffd2cf0509908daeb20a6efac0519e30604433617d493bdfa358e9b3de41140a0f440432c626df
7
- data.tar.gz: 907986bddf430ea0daa163d2c8d51e7c77e3a61031927117529bcdc5586bec664f45c9b9c293cb614ca0c96ee2f539e810fdebef9ea55a04d2d3a6c163d54d25
6
+ metadata.gz: 1150d77d3823b038277fb4bd7ee2c28bb39ea0307fefe227f2346c220ceb961f89349bfadc0d8b7bbcea01d7c0bd319ad96177c2de0eb24b6df82f57ee8001f6
7
+ data.tar.gz: 3673de4be4d9784ea7135da34e0d02aae91c4b2f8a0c04bc3e058cb01e7f250feb9e6526c4f638d555cc7a3e35a4d3ffb75ffcd770db867442e234ecf43f58a7
@@ -1,40 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Skip native extension compilation on JRuby
4
- if RUBY_ENGINE == 'jruby'
5
- puts 'Skipping native extension compilation on JRuby'
6
- puts 'BreakerMachines will use pure Ruby backend'
7
- makefile_content = "all:\n\t@echo 'Skipping native extension on JRuby'\n" \
8
- "install:\n\t@echo 'Skipping native extension on JRuby'\n"
9
- File.write('Makefile', makefile_content)
10
- exit 0
11
- end
12
-
13
- # Check if Cargo is available
14
- def cargo_available?
15
- system('cargo --version > /dev/null 2>&1')
16
- end
17
-
18
- unless cargo_available?
19
- warn 'WARNING: Cargo (Rust toolchain) not found!'
20
- warn 'BreakerMachines will fall back to pure Ruby backend.'
21
- warn 'To enable native performance, install Rust from https://rustup.rs'
22
-
23
- # Create a dummy Makefile that does nothing
24
- makefile_content = "all:\n\t@echo 'Skipping native extension (Cargo not found)'\n" \
25
- "install:\n\t@echo 'Skipping native extension (Cargo not found)'\n"
26
- File.write('Makefile', makefile_content)
27
- exit 0
28
- end
29
-
30
- # Use rb_sys to compile the Rust extension
31
- require 'mkmf'
32
- require 'rb_sys/mkmf'
33
-
34
- create_rust_makefile('breaker_machines_native/breaker_machines_native') do |r|
35
- # Set the path to the FFI crate (relative to current directory)
36
- r.ext_dir = 'ffi'
37
-
38
- # Profile configuration
39
- r.profile = ENV.fetch('RB_SYS_CARGO_PROFILE', :release).to_sym
40
- end
3
+ require_relative 'ffi/extconf'
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Skip native extension compilation on JRuby
4
+ if RUBY_ENGINE == 'jruby'
5
+ puts 'Skipping native extension compilation on JRuby'
6
+ puts 'BreakerMachines will use pure Ruby backend'
7
+ makefile_content = "all:\n\t@echo 'Skipping native extension on JRuby'\n" \
8
+ "install:\n\t@echo 'Skipping native extension on JRuby'\n"
9
+ File.write('Makefile', makefile_content)
10
+ exit 0
11
+ end
12
+
13
+ # Check if Cargo is available
14
+ def cargo_available?
15
+ system('cargo --version > /dev/null 2>&1')
16
+ end
17
+
18
+ def create_noop_makefile(message)
19
+ warn message
20
+ warn 'BreakerMachines will fall back to pure Ruby backend.'
21
+ File.write('Makefile', <<~MAKE)
22
+ all:
23
+ @echo '#{message}'
24
+ install:
25
+ @echo '#{message}'
26
+ MAKE
27
+ exit 0
28
+ end
29
+
30
+ unless cargo_available?
31
+ create_noop_makefile('Skipping native extension (Cargo not found)')
32
+ end
33
+
34
+ # Use rb_sys to compile the Rust extension
35
+ require 'mkmf'
36
+
37
+ # Wrap entire compilation process in error handling to ensure gem install never fails
38
+ begin
39
+ require 'rb_sys/mkmf'
40
+
41
+ require 'pathname'
42
+
43
+ create_rust_makefile('breaker_machines_native/breaker_machines_native') do |r|
44
+ ffi_dir = Pathname(__dir__)
45
+ r.ext_dir = begin
46
+ ffi_dir.relative_path_from(Pathname(Dir.pwd)).to_s
47
+ rescue ArgumentError
48
+ ffi_dir.expand_path.to_s
49
+ end
50
+ # Profile configuration
51
+ r.profile = ENV.fetch('RB_SYS_CARGO_PROFILE', :release).to_sym
52
+ end
53
+
54
+ makefile_path = File.join(Dir.pwd, 'Makefile')
55
+ if File.exist?(makefile_path)
56
+ manifest_path = File.expand_path(__dir__)
57
+ contents = File.read(makefile_path)
58
+ contents.gsub!(%r{^RB_SYS_CARGO_MANIFEST_DIR \?=.*$}, "RB_SYS_CARGO_MANIFEST_DIR ?= #{manifest_path}")
59
+ File.write(makefile_path, contents)
60
+ end
61
+ rescue LoadError => e
62
+ # rb_sys not available
63
+ create_noop_makefile("Skipping native extension (rb_sys gem not available: #{e.message})")
64
+ rescue StandardError => e
65
+ # Any other compilation setup failure (Rust compilation errors, Makefile generation, etc.)
66
+ create_noop_makefile("Skipping native extension (compilation setup failed: #{e.message})")
67
+ end
@@ -9,6 +9,14 @@ module BreakerMachines
9
9
  def load!
10
10
  return @loaded if defined?(@loaded)
11
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
+
12
20
  @loaded = true
13
21
  require 'breaker_machines_native/breaker_machines_native'
14
22
  BreakerMachines.instance_variable_set(:@native_available, true)
@@ -3,4 +3,8 @@
3
3
  require_relative 'native_extension'
4
4
 
5
5
  # Load the native extension if available
6
- BreakerMachines::NativeExtension.load!
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
@@ -2,31 +2,34 @@
2
2
 
3
3
  module BreakerMachines
4
4
  module Storage
5
- # Native extension storage backend for high-performance event tracking
5
+ # Native extension storage backend with graceful fallback to pure Ruby
6
6
  #
7
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.
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.
11
12
  #
12
- # Performance: ~63x faster than Memory storage for sliding window calculations
13
+ # Performance: ~63x faster than Memory storage when native extension is available
13
14
  #
14
15
  # Usage:
15
16
  # BreakerMachines.configure do |config|
16
17
  # config.default_storage = :native
17
18
  # end
18
19
  #
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.
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
22
  class Native < Base
23
23
  def initialize(**options)
24
24
  super
25
- unless defined?(BreakerMachinesNative::Storage)
26
- raise LoadError, 'Native extension not available. Use Storage::Memory instead.'
27
- end
25
+ # Native extension is guaranteed to be available when this class is loaded
26
+ @backend = BreakerMachinesNative::Storage.new
27
+ end
28
28
 
29
- @native = BreakerMachinesNative::Storage.new
29
+ # Check if using native backend
30
+ # @return [Boolean] always true - this class only exists when native is available
31
+ def native?
32
+ true
30
33
  end
31
34
 
32
35
  def get_status(_circuit_name)
@@ -41,27 +44,27 @@ module BreakerMachines
41
44
  end
42
45
 
43
46
  def record_success(circuit_name, duration)
44
- @native.record_success(circuit_name.to_s, duration.to_f)
47
+ @backend.record_success(circuit_name.to_s, duration)
45
48
  end
46
49
 
47
50
  def record_failure(circuit_name, duration)
48
- @native.record_failure(circuit_name.to_s, duration.to_f)
51
+ @backend.record_failure(circuit_name.to_s, duration)
49
52
  end
50
53
 
51
54
  def success_count(circuit_name, window_seconds)
52
- @native.success_count(circuit_name.to_s, window_seconds.to_f)
55
+ @backend.success_count(circuit_name.to_s, window_seconds)
53
56
  end
54
57
 
55
58
  def failure_count(circuit_name, window_seconds)
56
- @native.failure_count(circuit_name.to_s, window_seconds.to_f)
59
+ @backend.failure_count(circuit_name.to_s, window_seconds)
57
60
  end
58
61
 
59
62
  def clear(circuit_name)
60
- @native.clear(circuit_name.to_s)
63
+ @backend.clear(circuit_name.to_s)
61
64
  end
62
65
 
63
66
  def clear_all
64
- @native.clear_all
67
+ @backend.clear_all
65
68
  end
66
69
 
67
70
  def record_event_with_details(circuit_name, type, duration, error: nil, new_state: nil)
@@ -78,7 +81,7 @@ module BreakerMachines
78
81
  end
79
82
 
80
83
  def event_log(circuit_name, limit)
81
- @native.event_log(circuit_name.to_s, limit)
84
+ @backend.event_log(circuit_name.to_s, limit)
82
85
  end
83
86
 
84
87
  def with_timeout(_timeout_ms)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BreakerMachines
4
- VERSION = '0.6.0'
4
+ VERSION = '0.7.0'
5
5
  end
@@ -17,6 +17,7 @@ loader.ignore("#{__dir__}/breaker_machines/hedged_async_support.rb")
17
17
  loader.ignore("#{__dir__}/breaker_machines/circuit/async_state_management.rb")
18
18
  loader.ignore("#{__dir__}/breaker_machines/native_speedup.rb")
19
19
  loader.ignore("#{__dir__}/breaker_machines/native_extension.rb")
20
+ loader.ignore("#{__dir__}/breaker_machines/storage/native.rb")
20
21
  loader.setup
21
22
 
22
23
  # BreakerMachines provides a thread-safe implementation of the Circuit Breaker pattern
@@ -190,11 +191,10 @@ module BreakerMachines
190
191
  end
191
192
 
192
193
  # 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
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
200
  end
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.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -79,6 +79,20 @@ dependencies:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '2.7'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rb_sys
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.9'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.9'
82
96
  - !ruby/object:Gem::Dependency
83
97
  name: minitest
84
98
  requirement: !ruby/object:Gem::Requirement
@@ -108,19 +122,19 @@ dependencies:
108
122
  - !ruby/object:Gem::Version
109
123
  version: '13.0'
110
124
  - !ruby/object:Gem::Dependency
111
- name: rb_sys
125
+ name: rake-compiler
112
126
  requirement: !ruby/object:Gem::Requirement
113
127
  requirements:
114
128
  - - "~>"
115
129
  - !ruby/object:Gem::Version
116
- version: '0.9'
130
+ version: '1.3'
117
131
  type: :development
118
132
  prerelease: false
119
133
  version_requirements: !ruby/object:Gem::Requirement
120
134
  requirements:
121
135
  - - "~>"
122
136
  - !ruby/object:Gem::Version
123
- version: '0.9'
137
+ version: '1.3'
124
138
  description: |
125
139
  BreakerMachines is a production-ready circuit breaker implementation for Ruby that prevents
126
140
  cascade failures in distributed systems. Built on the battle-tested state_machines gem, it
@@ -149,6 +163,7 @@ files:
149
163
  - ext/breaker_machines_native/core/src/storage.rs
150
164
  - ext/breaker_machines_native/extconf.rb
151
165
  - ext/breaker_machines_native/ffi/Cargo.toml
166
+ - ext/breaker_machines_native/ffi/extconf.rb
152
167
  - ext/breaker_machines_native/ffi/src/lib.rs
153
168
  - ext/breaker_machines_native/target/debug/build/clang-sys-d961dfabd5f43fba/out/common.rs
154
169
  - ext/breaker_machines_native/target/debug/build/clang-sys-d961dfabd5f43fba/out/dynamic.rs
@@ -235,7 +250,6 @@ files:
235
250
  - lib/breaker_machines/storage/null.rb
236
251
  - lib/breaker_machines/types.rb
237
252
  - lib/breaker_machines/version.rb
238
- - lib/breaker_machines_native/breaker_machines_native.bundle
239
253
  - sig/README.md
240
254
  - sig/all.rbs
241
255
  - sig/breaker_machines.rbs
@@ -258,6 +272,8 @@ metadata:
258
272
  bug_tracker_uri: https://github.com/seuros/breaker_machines/issues
259
273
  documentation_uri: https://github.com/seuros/breaker_machines#readme
260
274
  rubygems_mfa_required: 'true'
275
+ cargo_crate_name: breaker_machines_native
276
+ cargo_manifest_path: ext/breaker_machines_native/ffi/Cargo.toml
261
277
  rdoc_options: []
262
278
  require_paths:
263
279
  - lib
@@ -265,7 +281,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
265
281
  requirements:
266
282
  - - ">="
267
283
  - !ruby/object:Gem::Version
268
- version: 3.2.0
284
+ version: 3.3.0
269
285
  required_rubygems_version: !ruby/object:Gem::Requirement
270
286
  requirements:
271
287
  - - ">="