breaker_machines 0.11.0 → 0.12.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: fd34f2d2c0993335422fcb7c9a2495af660ec6c59dde5f57c7f9da0841cb51db
4
- data.tar.gz: fc1ba508ecbaf43dbf451ef81bcacb9b586a0003ca5148d66d80f4d51a53f926
3
+ metadata.gz: 302ad7f4383ff797f0f9bf89c4da1252f27f33ebcfab3cc7c3c114ba32afe5e0
4
+ data.tar.gz: 6fc626f0001941fe023d02417c384673b4df74c3cbbb82c28c8a1e932992ba00
5
5
  SHA512:
6
- metadata.gz: 49eac0d2918c9cee306309f785009fe53ffbd4d4a26641132f2dfe8a069e0cb200a47a3d97a8748ae1bf52d4604969127d3c549db2e31cf6e25d0b95ce9eb65c
7
- data.tar.gz: a08fa2490b8b2a3b1ac6bc44853540763875ce39ce439c860b682256f01ccfbdf5e5e63176f917d9c109a3593b573a32588dddb336c34d0a1023c00bbe4c15c5
6
+ metadata.gz: b84a777ed7f9bb88bca15ec3935193123bcdf47e9642d2a4ffe4c8de35ea9dcd1d335717922739675f144915815e10e2adc638bb6bb747bc2f0ca35e7968211d
7
+ data.tar.gz: 91e832c67256d91f61692fe7f7ca7b006fef2752560b98a95f9ac3d0169a4af5f9fe4d1e8ebb8d794f6d9dbbe72a2f4e7e71b7b3b7d1d4b1c1de115d96d3707d
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BreakerMachines
4
- VERSION = '0.11.0'
4
+ VERSION = '0.12.0'
5
5
  end
data/sig/README.md CHANGED
@@ -71,4 +71,4 @@ end
71
71
  The type signatures define several interfaces:
72
72
  - `_StorageBackend` - For custom storage implementations
73
73
  - `_MetricsRecorder` - For custom metrics recording
74
- - `_CircuitLike` - For circuit-compatible objects
74
+ - `_CircuitLike` - For circuit-compatible objects
data/sig/all.rbs CHANGED
@@ -22,4 +22,4 @@
22
22
 
23
23
  # Import all type definitions
24
24
  use BreakerMachines::*
25
- use BreakerMachines::Storage::*
25
+ use BreakerMachines::Storage::*
@@ -151,4 +151,4 @@ module BreakerMachines
151
151
  def invoke_fallback: (StandardError error) -> untyped
152
152
  def invoke_single_fallback: (Proc | untyped fallback, StandardError error) -> untyped
153
153
  end
154
- end
154
+ end
@@ -29,4 +29,4 @@ module BreakerMachines
29
29
  def colorize_state: (Symbol state) -> String
30
30
  def colorize_event_type: (Symbol type) -> String
31
31
  end
32
- end
32
+ end
@@ -47,4 +47,4 @@ module BreakerMachines
47
47
  def parallel_calls: (Integer count, ?timeout: Integer?) -> void
48
48
  end
49
49
  end
50
- end
50
+ end
@@ -21,4 +21,4 @@ module BreakerMachines
21
21
 
22
22
  class StorageError < Error
23
23
  end
24
- end
24
+ end
@@ -43,4 +43,4 @@ module BreakerMachines
43
43
  def reset: () -> bool
44
44
  def stats: () -> Hash[Symbol, untyped]
45
45
  end
46
- end
46
+ end
@@ -27,4 +27,4 @@ module BreakerMachines
27
27
 
28
28
  def cleanup_dead_references_unsafe: () -> void
29
29
  end
30
- end
30
+ end
@@ -62,4 +62,4 @@ module BreakerMachines
62
62
  def event_log: (String circuit_name, ?Integer limit) -> Array[untyped]
63
63
  end
64
64
  end
65
- end
65
+ end
@@ -94,4 +94,4 @@ module BreakerMachines
94
94
  status: circuit_state,
95
95
  opened_at: Float?
96
96
  }
97
- end
97
+ end
data/sig/manifest.yaml CHANGED
@@ -2,4 +2,4 @@ dependencies:
2
2
  - name: activesupport
3
3
  - name: concurrent-ruby
4
4
  - name: state_machines
5
- - name: zeitwerk
5
+ - name: zeitwerk
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.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -51,20 +51,6 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '1.3'
54
- - !ruby/object:Gem::Dependency
55
- name: rb_sys
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '0.9'
61
- type: :runtime
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '0.9'
68
54
  - !ruby/object:Gem::Dependency
69
55
  name: state_machines
70
56
  requirement: !ruby/object:Gem::Requirement
@@ -107,6 +93,20 @@ dependencies:
107
93
  - - "~>"
108
94
  - !ruby/object:Gem::Version
109
95
  version: '5.16'
96
+ - !ruby/object:Gem::Dependency
97
+ name: rb_sys
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.9'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.9'
110
110
  - !ruby/object:Gem::Dependency
111
111
  name: rake
112
112
  requirement: !ruby/object:Gem::Requirement
@@ -144,26 +144,11 @@ description: |
144
144
  email:
145
145
  - terminale@gmail.com
146
146
  executables: []
147
- extensions:
148
- - ext/breaker_machines_native/extconf.rb
147
+ extensions: []
149
148
  extra_rdoc_files: []
150
149
  files:
151
150
  - LICENSE.txt
152
151
  - README.md
153
- - ext/breaker_machines_native/core/Cargo.toml
154
- - ext/breaker_machines_native/core/examples/basic.rs
155
- - ext/breaker_machines_native/core/src/builder.rs
156
- - ext/breaker_machines_native/core/src/bulkhead.rs
157
- - ext/breaker_machines_native/core/src/callbacks.rs
158
- - ext/breaker_machines_native/core/src/circuit.rs
159
- - ext/breaker_machines_native/core/src/classifier.rs
160
- - ext/breaker_machines_native/core/src/errors.rs
161
- - ext/breaker_machines_native/core/src/lib.rs
162
- - ext/breaker_machines_native/core/src/storage.rs
163
- - ext/breaker_machines_native/extconf.rb
164
- - ext/breaker_machines_native/ffi/Cargo.toml
165
- - ext/breaker_machines_native/ffi/extconf.rb
166
- - ext/breaker_machines_native/ffi/src/lib.rs
167
152
  - lib/breaker_machines.rb
168
153
  - lib/breaker_machines/async_circuit.rb
169
154
  - lib/breaker_machines/async_support.rb
@@ -1,21 +0,0 @@
1
- [package]
2
- name = "breaker-machines"
3
- version = "0.7.6"
4
- edition = "2024"
5
- authors = ["Abdelkader Boudih <oss@seuros.com>"]
6
- license = "MIT"
7
- description = "High-performance circuit breaker with fallback support and rate-based thresholds"
8
- repository = "https://github.com/seuros/breaker_machines"
9
- readme = "README.md"
10
- keywords = ["circuit-breaker", "reliability", "fault-tolerance", "state-machine"]
11
- categories = ["concurrency"]
12
-
13
- [lib]
14
- crate-type = ["lib"]
15
-
16
- [features]
17
- inspect = ["state-machines/inspect"]
18
-
19
- [dependencies]
20
- state-machines = { workspace = true }
21
- chrono-machines = { workspace = true }
@@ -1,61 +0,0 @@
1
- //! Basic circuit breaker usage example
2
-
3
- use breaker_machines::CircuitBreaker;
4
-
5
- fn main() {
6
- println!("=== Circuit Breaker Basic Example ===\n");
7
-
8
- // Create a circuit with builder API
9
- let mut circuit = CircuitBreaker::builder("payment_api")
10
- .failure_threshold(3)
11
- .failure_window_secs(10.0)
12
- .half_open_timeout_secs(5.0)
13
- .success_threshold(2)
14
- .on_open(|name| println!("🔴 Circuit '{}' opened!", name))
15
- .on_close(|name| println!("🟢 Circuit '{}' closed!", name))
16
- .on_half_open(|name| println!("🟡 Circuit '{}' half-open, testing...", name))
17
- .build();
18
-
19
- println!("Initial state: {}\n", circuit.state_name());
20
-
21
- // Simulate successful calls
22
- println!("--- Successful calls ---");
23
- for i in 1..=2 {
24
- match circuit.call(move || Ok::<_, String>(format!("Payment {}", i))) {
25
- Ok(result) => println!("✓ {}", result),
26
- Err(e) => println!("✗ Error: {}", e),
27
- }
28
- }
29
- println!("State: {}\n", circuit.state_name());
30
-
31
- // Simulate failures
32
- println!("--- Triggering failures ---");
33
- for i in 1..=3 {
34
- match circuit.call(move || Err::<String, _>(format!("Payment failed {}", i))) {
35
- Ok(_) => println!("✓ Success"),
36
- Err(e) => println!("✗ {}", e),
37
- }
38
- }
39
- println!("State: {} (circuit opened)\n", circuit.state_name());
40
-
41
- // Try calling while open
42
- println!("--- Attempting call while open ---");
43
- match circuit.call(|| Ok::<_, String>("Should be rejected")) {
44
- Ok(_) => println!("✓ Success"),
45
- Err(e) => println!("✗ {}", e),
46
- }
47
- println!();
48
-
49
- // Reset and demonstrate recovery
50
- println!("--- Resetting circuit ---");
51
- circuit.reset();
52
- println!("State after reset: {}\n", circuit.state_name());
53
-
54
- // Successful calls after reset
55
- println!("--- Calls after reset ---");
56
- match circuit.call(|| Ok::<_, String>("Payment successful")) {
57
- Ok(result) => println!("✓ {}", result),
58
- Err(e) => println!("✗ {}", e),
59
- }
60
- println!("State: {}", circuit.state_name());
61
- }
@@ -1,232 +0,0 @@
1
- //! Builder API for ergonomic circuit breaker configuration
2
-
3
- use crate::{
4
- MemoryStorage, StorageBackend,
5
- bulkhead::BulkheadSemaphore,
6
- callbacks::Callbacks,
7
- circuit::{CircuitBreaker, CircuitContext, Config},
8
- classifier::FailureClassifier,
9
- };
10
- use std::sync::Arc;
11
-
12
- /// Builder for creating circuit breakers with fluent API
13
- pub struct CircuitBuilder {
14
- name: String,
15
- config: Config,
16
- storage: Option<Arc<dyn StorageBackend>>,
17
- failure_classifier: Option<Arc<dyn FailureClassifier>>,
18
- bulkhead: Option<Arc<BulkheadSemaphore>>,
19
- callbacks: Callbacks,
20
- }
21
-
22
- impl CircuitBuilder {
23
- /// Create a new builder for a circuit with the given name
24
- pub fn new(name: impl Into<String>) -> Self {
25
- Self {
26
- name: name.into(),
27
- config: Config::default(),
28
- storage: None,
29
- failure_classifier: None,
30
- bulkhead: None,
31
- callbacks: Callbacks::new(),
32
- }
33
- }
34
-
35
- /// Set the absolute failure threshold (number of failures to open circuit)
36
- pub fn failure_threshold(mut self, threshold: usize) -> Self {
37
- self.config.failure_threshold = Some(threshold);
38
- self
39
- }
40
-
41
- /// Disable absolute failure threshold (use only rate-based)
42
- pub fn disable_failure_threshold(mut self) -> Self {
43
- self.config.failure_threshold = None;
44
- self
45
- }
46
-
47
- /// Set the failure rate threshold (0.0-1.0)
48
- /// Circuit opens when (failures / total_calls) >= this value
49
- pub fn failure_rate(mut self, rate: f64) -> Self {
50
- self.config.failure_rate_threshold = Some(rate.clamp(0.0, 1.0));
51
- self
52
- }
53
-
54
- /// Set minimum number of calls before rate-based threshold is evaluated
55
- pub fn minimum_calls(mut self, calls: usize) -> Self {
56
- self.config.minimum_calls = calls;
57
- self
58
- }
59
-
60
- /// Set the failure window in seconds
61
- pub fn failure_window_secs(mut self, seconds: f64) -> Self {
62
- self.config.failure_window_secs = seconds;
63
- self
64
- }
65
-
66
- /// Set the half-open timeout in seconds
67
- pub fn half_open_timeout_secs(mut self, seconds: f64) -> Self {
68
- self.config.half_open_timeout_secs = seconds;
69
- self
70
- }
71
-
72
- /// Set the success threshold (successes needed to close from half-open)
73
- pub fn success_threshold(mut self, threshold: usize) -> Self {
74
- self.config.success_threshold = threshold;
75
- self
76
- }
77
-
78
- /// Set the jitter factor (0.0 = no jitter, 1.0 = full jitter)
79
- /// Uses chrono-machines formula: timeout * (1 - jitter + rand * jitter)
80
- pub fn jitter_factor(mut self, factor: f64) -> Self {
81
- self.config.jitter_factor = factor;
82
- self
83
- }
84
-
85
- /// Set custom storage backend
86
- pub fn storage(mut self, storage: Arc<dyn StorageBackend>) -> Self {
87
- self.storage = Some(storage);
88
- self
89
- }
90
-
91
- /// Set a failure classifier to filter which errors should trip the circuit
92
- ///
93
- /// The classifier determines whether a given error should count toward
94
- /// opening the circuit. Use this to ignore "expected" errors like validation
95
- /// failures or client errors (4xx), while still tripping on server errors (5xx).
96
- ///
97
- /// # Examples
98
- ///
99
- /// ```rust
100
- /// use breaker_machines::{CircuitBreaker, PredicateClassifier};
101
- /// use std::sync::Arc;
102
- ///
103
- /// let circuit = CircuitBreaker::builder("api")
104
- /// .failure_classifier(Arc::new(PredicateClassifier::new(|ctx| {
105
- /// // Only trip on slow errors
106
- /// ctx.duration > 1.0
107
- /// })))
108
- /// .build();
109
- /// ```
110
- pub fn failure_classifier(mut self, classifier: Arc<dyn FailureClassifier>) -> Self {
111
- self.failure_classifier = Some(classifier);
112
- self
113
- }
114
-
115
- /// Set maximum concurrency limit (bulkheading)
116
- ///
117
- /// When set, the circuit breaker will reject calls with `BulkheadFull` error
118
- /// if the number of concurrent calls exceeds this limit. This prevents
119
- /// resource exhaustion by limiting how many operations can run simultaneously.
120
- ///
121
- /// # Panics
122
- ///
123
- /// Panics if `limit` is 0.
124
- ///
125
- /// # Examples
126
- ///
127
- /// ```rust
128
- /// use breaker_machines::CircuitBreaker;
129
- ///
130
- /// let mut circuit = CircuitBreaker::builder("api")
131
- /// .max_concurrency(10) // Allow max 10 concurrent calls
132
- /// .build();
133
- ///
134
- /// // This will succeed until 10 calls are running concurrently
135
- /// let result = circuit.call(|| Ok::<_, String>("success"));
136
- /// ```
137
- pub fn max_concurrency(mut self, limit: usize) -> Self {
138
- self.bulkhead = Some(Arc::new(BulkheadSemaphore::new(limit)));
139
- self
140
- }
141
-
142
- /// Set callback for when circuit opens
143
- pub fn on_open<F>(mut self, f: F) -> Self
144
- where
145
- F: Fn(&str) + Send + Sync + 'static,
146
- {
147
- self.callbacks.on_open = Some(Arc::new(f));
148
- self
149
- }
150
-
151
- /// Set callback for when circuit closes
152
- pub fn on_close<F>(mut self, f: F) -> Self
153
- where
154
- F: Fn(&str) + Send + Sync + 'static,
155
- {
156
- self.callbacks.on_close = Some(Arc::new(f));
157
- self
158
- }
159
-
160
- /// Set callback for when circuit enters half-open
161
- pub fn on_half_open<F>(mut self, f: F) -> Self
162
- where
163
- F: Fn(&str) + Send + Sync + 'static,
164
- {
165
- self.callbacks.on_half_open = Some(Arc::new(f));
166
- self
167
- }
168
-
169
- /// Build the circuit breaker
170
- pub fn build(self) -> CircuitBreaker {
171
- let storage = self
172
- .storage
173
- .unwrap_or_else(|| Arc::new(MemoryStorage::new()));
174
-
175
- let context = CircuitContext {
176
- name: self.name,
177
- config: self.config,
178
- storage,
179
- failure_classifier: self.failure_classifier,
180
- bulkhead: self.bulkhead,
181
- };
182
-
183
- CircuitBreaker::with_context_and_callbacks(context, self.callbacks)
184
- }
185
- }
186
-
187
- #[cfg(test)]
188
- mod tests {
189
- use super::*;
190
-
191
- #[test]
192
- fn test_builder_defaults() {
193
- let circuit = CircuitBuilder::new("test").build();
194
-
195
- assert_eq!(circuit.state_name(), "Closed");
196
- assert!(circuit.is_closed());
197
- }
198
-
199
- #[test]
200
- fn test_builder_custom_config() {
201
- let circuit = CircuitBuilder::new("test")
202
- .failure_threshold(10)
203
- .failure_window_secs(120.0)
204
- .half_open_timeout_secs(60.0)
205
- .success_threshold(3)
206
- .build();
207
-
208
- assert!(circuit.is_closed());
209
- }
210
-
211
- #[test]
212
- fn test_builder_with_callbacks() {
213
- use std::sync::atomic::{AtomicBool, Ordering};
214
-
215
- let opened = Arc::new(AtomicBool::new(false));
216
- let opened_clone = opened.clone();
217
-
218
- let mut circuit = CircuitBuilder::new("test")
219
- .failure_threshold(2)
220
- .on_open(move |_name| {
221
- opened_clone.store(true, Ordering::SeqCst);
222
- })
223
- .build();
224
-
225
- // Trigger failures to open circuit
226
- let _ = circuit.call(|| Err::<(), _>("error 1"));
227
- let _ = circuit.call(|| Err::<(), _>("error 2"));
228
-
229
- // Callback should have been triggered
230
- assert!(opened.load(Ordering::SeqCst));
231
- }
232
- }