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.
- checksums.yaml +4 -4
- data/ext/breaker_machines_native/Cargo.toml +8 -0
- data/ext/breaker_machines_native/core/Cargo.toml +18 -0
- data/ext/breaker_machines_native/core/examples/basic.rs +61 -0
- data/ext/breaker_machines_native/core/src/builder.rs +232 -0
- data/ext/breaker_machines_native/core/src/bulkhead.rs +223 -0
- data/ext/breaker_machines_native/core/src/callbacks.rs +58 -0
- data/ext/breaker_machines_native/core/src/circuit.rs +1156 -0
- data/ext/breaker_machines_native/core/src/classifier.rs +177 -0
- data/ext/breaker_machines_native/core/src/errors.rs +47 -0
- data/ext/breaker_machines_native/core/src/lib.rs +62 -0
- data/ext/breaker_machines_native/core/src/storage.rs +377 -0
- data/ext/breaker_machines_native/extconf.rb +40 -0
- data/ext/breaker_machines_native/ffi/Cargo.toml +16 -0
- data/ext/breaker_machines_native/ffi/src/lib.rs +218 -0
- data/ext/breaker_machines_native/target/debug/build/clang-sys-d961dfabd5f43fba/out/common.rs +355 -0
- data/ext/breaker_machines_native/target/debug/build/clang-sys-d961dfabd5f43fba/out/dynamic.rs +276 -0
- data/ext/breaker_machines_native/target/debug/build/clang-sys-d961dfabd5f43fba/out/macros.rs +49 -0
- 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
- 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
- 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
- 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
- data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/Cargo.toml +48 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/examples/basic.rs +61 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/builder.rs +154 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/callbacks.rs +55 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/circuit.rs +607 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/errors.rs +38 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/lib.rs +58 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.1.0/src/storage.rs +377 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/Cargo.toml +48 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/examples/basic.rs +61 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/builder.rs +173 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/callbacks.rs +55 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/circuit.rs +855 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/errors.rs +38 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/lib.rs +58 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.2.0/src/storage.rs +377 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/Cargo.toml +48 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/examples/basic.rs +61 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/builder.rs +154 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/callbacks.rs +55 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/circuit.rs +607 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/errors.rs +38 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/lib.rs +58 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.5.0/src/storage.rs +377 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/Cargo.toml +48 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/examples/basic.rs +61 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/builder.rs +232 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/bulkhead.rs +223 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/callbacks.rs +58 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/circuit.rs +1156 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/classifier.rs +177 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/errors.rs +47 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/lib.rs +62 -0
- data/ext/breaker_machines_native/target/package/breaker-machines-0.6.0/src/storage.rs +377 -0
- data/ext/breaker_machines_native/target/release/build/clang-sys-ef8ad8b846ac8b75/out/common.rs +355 -0
- data/ext/breaker_machines_native/target/release/build/clang-sys-ef8ad8b846ac8b75/out/dynamic.rs +276 -0
- data/ext/breaker_machines_native/target/release/build/clang-sys-ef8ad8b846ac8b75/out/macros.rs +49 -0
- 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
- data/lib/breaker_machines/circuit/async_state_management.rb +1 -1
- data/lib/breaker_machines/circuit/base.rb +2 -1
- data/lib/breaker_machines/circuit/coordinated_state_management.rb +3 -3
- data/lib/breaker_machines/circuit/native.rb +127 -0
- data/lib/breaker_machines/circuit/state_callbacks.rb +17 -5
- data/lib/breaker_machines/circuit/state_management.rb +1 -1
- data/lib/breaker_machines/native_extension.rb +36 -0
- data/lib/breaker_machines/native_speedup.rb +6 -0
- data/lib/breaker_machines/storage/bucket_memory.rb +4 -1
- data/lib/breaker_machines/storage/memory.rb +4 -1
- data/lib/breaker_machines/storage/native.rb +90 -0
- data/lib/breaker_machines/version.rb +1 -1
- data/lib/breaker_machines.rb +98 -11
- data/lib/breaker_machines_native/breaker_machines_native.bundle +0 -0
- data/sig/breaker_machines.rbs +20 -8
- metadata +99 -6
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
//! Storage backends for circuit breaker events
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides different storage implementations:
|
|
4
|
+
//! - `MemoryStorage`: Thread-safe in-memory storage with sliding window
|
|
5
|
+
//! - `NullStorage`: No-op storage for testing and benchmarking
|
|
6
|
+
|
|
7
|
+
use crate::{Event, EventKind};
|
|
8
|
+
use std::collections::HashMap;
|
|
9
|
+
use std::sync::RwLock;
|
|
10
|
+
use std::time::Instant;
|
|
11
|
+
|
|
12
|
+
/// Abstract storage backend for circuit breaker events
|
|
13
|
+
pub trait StorageBackend: Send + Sync + std::fmt::Debug {
|
|
14
|
+
/// Record a successful operation
|
|
15
|
+
fn record_success(&self, circuit_name: &str, duration: f64);
|
|
16
|
+
|
|
17
|
+
/// Record a failed operation
|
|
18
|
+
fn record_failure(&self, circuit_name: &str, duration: f64);
|
|
19
|
+
|
|
20
|
+
/// Count successful operations within a time window
|
|
21
|
+
fn success_count(&self, circuit_name: &str, window_seconds: f64) -> usize;
|
|
22
|
+
|
|
23
|
+
/// Count failed operations within a time window
|
|
24
|
+
fn failure_count(&self, circuit_name: &str, window_seconds: f64) -> usize;
|
|
25
|
+
|
|
26
|
+
/// Clear all events for a circuit
|
|
27
|
+
fn clear(&self, circuit_name: &str);
|
|
28
|
+
|
|
29
|
+
/// Clear all events for all circuits
|
|
30
|
+
fn clear_all(&self);
|
|
31
|
+
|
|
32
|
+
/// Get event log for a circuit (limited to last N events)
|
|
33
|
+
fn event_log(&self, circuit_name: &str, limit: usize) -> Vec<Event>;
|
|
34
|
+
|
|
35
|
+
/// Get monotonic time in seconds (relative to storage creation)
|
|
36
|
+
fn monotonic_time(&self) -> f64;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Thread-safe in-memory storage for circuit breaker events
|
|
40
|
+
#[derive(Debug)]
|
|
41
|
+
pub struct MemoryStorage {
|
|
42
|
+
/// Events keyed by circuit name
|
|
43
|
+
events: RwLock<HashMap<String, Vec<Event>>>,
|
|
44
|
+
/// Maximum events to keep per circuit
|
|
45
|
+
max_events: usize,
|
|
46
|
+
/// Monotonic time anchor (prevents clock skew issues from NTP)
|
|
47
|
+
start_time: Instant,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
impl MemoryStorage {
|
|
51
|
+
/// Create a new storage instance
|
|
52
|
+
pub fn new() -> Self {
|
|
53
|
+
Self::with_max_events(1000)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Create storage with custom max events per circuit
|
|
57
|
+
pub fn with_max_events(max_events: usize) -> Self {
|
|
58
|
+
Self {
|
|
59
|
+
events: RwLock::new(HashMap::new()),
|
|
60
|
+
max_events,
|
|
61
|
+
start_time: Instant::now(),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Private helper methods
|
|
66
|
+
|
|
67
|
+
fn record_event(&self, circuit_name: &str, kind: EventKind, duration: f64) {
|
|
68
|
+
let mut events = self.events.write().unwrap();
|
|
69
|
+
let circuit_events = events.entry(circuit_name.to_string()).or_default();
|
|
70
|
+
|
|
71
|
+
circuit_events.push(Event {
|
|
72
|
+
kind,
|
|
73
|
+
timestamp: self.monotonic_time(),
|
|
74
|
+
duration,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Cleanup old events if we exceed max_events
|
|
78
|
+
if circuit_events.len() > self.max_events {
|
|
79
|
+
// Remove oldest 10% to avoid cleanup on every event
|
|
80
|
+
// Ensure we remove at least 1 event even with small max_events
|
|
81
|
+
let remove_count = (self.max_events / 10).max(1);
|
|
82
|
+
circuit_events.drain(0..remove_count);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fn count_events(&self, circuit_name: &str, kind: EventKind, window_seconds: f64) -> usize {
|
|
87
|
+
let events = self.events.read().unwrap();
|
|
88
|
+
let cutoff = self.monotonic_time() - window_seconds;
|
|
89
|
+
|
|
90
|
+
events
|
|
91
|
+
.get(circuit_name)
|
|
92
|
+
.map(|ev| {
|
|
93
|
+
ev.iter()
|
|
94
|
+
.filter(|e| e.kind == kind && e.timestamp >= cutoff)
|
|
95
|
+
.count()
|
|
96
|
+
})
|
|
97
|
+
.unwrap_or(0)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
impl Default for MemoryStorage {
|
|
102
|
+
fn default() -> Self {
|
|
103
|
+
Self::new()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
impl StorageBackend for MemoryStorage {
|
|
108
|
+
fn record_success(&self, circuit_name: &str, duration: f64) {
|
|
109
|
+
self.record_event(circuit_name, EventKind::Success, duration);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn record_failure(&self, circuit_name: &str, duration: f64) {
|
|
113
|
+
self.record_event(circuit_name, EventKind::Failure, duration);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fn success_count(&self, circuit_name: &str, window_seconds: f64) -> usize {
|
|
117
|
+
self.count_events(circuit_name, EventKind::Success, window_seconds)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fn failure_count(&self, circuit_name: &str, window_seconds: f64) -> usize {
|
|
121
|
+
self.count_events(circuit_name, EventKind::Failure, window_seconds)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fn clear(&self, circuit_name: &str) {
|
|
125
|
+
let mut events = self.events.write().unwrap();
|
|
126
|
+
events.remove(circuit_name);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn clear_all(&self) {
|
|
130
|
+
let mut events = self.events.write().unwrap();
|
|
131
|
+
events.clear();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn event_log(&self, circuit_name: &str, limit: usize) -> Vec<Event> {
|
|
135
|
+
let events = self.events.read().unwrap();
|
|
136
|
+
events
|
|
137
|
+
.get(circuit_name)
|
|
138
|
+
.map(|ev| {
|
|
139
|
+
let start = if ev.len() > limit {
|
|
140
|
+
ev.len() - limit
|
|
141
|
+
} else {
|
|
142
|
+
0
|
|
143
|
+
};
|
|
144
|
+
ev[start..].to_vec()
|
|
145
|
+
})
|
|
146
|
+
.unwrap_or_default()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fn monotonic_time(&self) -> f64 {
|
|
150
|
+
self.start_time.elapsed().as_secs_f64()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// No-op storage backend for testing and benchmarking
|
|
155
|
+
///
|
|
156
|
+
/// This storage implementation discards all events and always returns zero counts.
|
|
157
|
+
/// Useful for:
|
|
158
|
+
/// - Testing circuit breaker logic without storage overhead
|
|
159
|
+
/// - Benchmarking pure state machine performance
|
|
160
|
+
/// - Scenarios where external systems track metrics
|
|
161
|
+
///
|
|
162
|
+
/// # Example
|
|
163
|
+
///
|
|
164
|
+
/// ```rust
|
|
165
|
+
/// use breaker_machines::{CircuitBreaker, NullStorage};
|
|
166
|
+
/// use std::sync::Arc;
|
|
167
|
+
///
|
|
168
|
+
/// let storage = Arc::new(NullStorage::new());
|
|
169
|
+
/// let mut circuit = CircuitBreaker::builder("test")
|
|
170
|
+
/// .storage(storage)
|
|
171
|
+
/// .build();
|
|
172
|
+
/// ```
|
|
173
|
+
#[derive(Debug, Clone, Copy)]
|
|
174
|
+
pub struct NullStorage {
|
|
175
|
+
start_time: Instant,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
impl NullStorage {
|
|
179
|
+
/// Create a new null storage instance
|
|
180
|
+
pub fn new() -> Self {
|
|
181
|
+
Self {
|
|
182
|
+
start_time: Instant::now(),
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
impl Default for NullStorage {
|
|
188
|
+
fn default() -> Self {
|
|
189
|
+
Self::new()
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
impl StorageBackend for NullStorage {
|
|
194
|
+
fn record_success(&self, _circuit_name: &str, _duration: f64) {
|
|
195
|
+
// No-op
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fn record_failure(&self, _circuit_name: &str, _duration: f64) {
|
|
199
|
+
// No-op
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fn success_count(&self, _circuit_name: &str, _window_seconds: f64) -> usize {
|
|
203
|
+
0
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fn failure_count(&self, _circuit_name: &str, _window_seconds: f64) -> usize {
|
|
207
|
+
0
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
fn clear(&self, _circuit_name: &str) {
|
|
211
|
+
// No-op
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
fn clear_all(&self) {
|
|
215
|
+
// No-op
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fn event_log(&self, _circuit_name: &str, _limit: usize) -> Vec<Event> {
|
|
219
|
+
Vec::new()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
fn monotonic_time(&self) -> f64 {
|
|
223
|
+
self.start_time.elapsed().as_secs_f64()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#[cfg(test)]
|
|
228
|
+
mod tests {
|
|
229
|
+
use super::*;
|
|
230
|
+
|
|
231
|
+
#[test]
|
|
232
|
+
fn test_memory_storage_record_and_count() {
|
|
233
|
+
let storage = MemoryStorage::new();
|
|
234
|
+
|
|
235
|
+
storage.record_success("test_circuit", 0.1);
|
|
236
|
+
storage.record_success("test_circuit", 0.2);
|
|
237
|
+
storage.record_failure("test_circuit", 0.5);
|
|
238
|
+
|
|
239
|
+
assert_eq!(storage.success_count("test_circuit", 60.0), 2);
|
|
240
|
+
assert_eq!(storage.failure_count("test_circuit", 60.0), 1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
#[test]
|
|
244
|
+
fn test_memory_storage_clear() {
|
|
245
|
+
let storage = MemoryStorage::new();
|
|
246
|
+
|
|
247
|
+
storage.record_success("test_circuit", 0.1);
|
|
248
|
+
assert_eq!(storage.success_count("test_circuit", 60.0), 1);
|
|
249
|
+
|
|
250
|
+
storage.clear("test_circuit");
|
|
251
|
+
assert_eq!(storage.success_count("test_circuit", 60.0), 0);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#[test]
|
|
255
|
+
fn test_memory_storage_event_log() {
|
|
256
|
+
let storage = MemoryStorage::new();
|
|
257
|
+
|
|
258
|
+
storage.record_success("test_circuit", 0.1);
|
|
259
|
+
storage.record_failure("test_circuit", 0.2);
|
|
260
|
+
storage.record_success("test_circuit", 0.3);
|
|
261
|
+
|
|
262
|
+
let log = storage.event_log("test_circuit", 10);
|
|
263
|
+
assert_eq!(log.len(), 3);
|
|
264
|
+
assert_eq!(log[0].kind, EventKind::Success);
|
|
265
|
+
assert_eq!(log[1].kind, EventKind::Failure);
|
|
266
|
+
assert_eq!(log[2].kind, EventKind::Success);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#[test]
|
|
270
|
+
fn test_memory_storage_max_events_cleanup() {
|
|
271
|
+
let storage = MemoryStorage::with_max_events(100);
|
|
272
|
+
|
|
273
|
+
for i in 0..150 {
|
|
274
|
+
storage.record_success("test_circuit", i as f64 * 0.01);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let events = storage.events.read().unwrap();
|
|
278
|
+
let circuit_events = events.get("test_circuit").unwrap();
|
|
279
|
+
|
|
280
|
+
assert!(circuit_events.len() <= 100);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#[test]
|
|
284
|
+
fn test_memory_storage_small_max_events() {
|
|
285
|
+
let storage = MemoryStorage::with_max_events(5);
|
|
286
|
+
|
|
287
|
+
for i in 0..20 {
|
|
288
|
+
storage.record_success("test_circuit", i as f64 * 0.01);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let events = storage.events.read().unwrap();
|
|
292
|
+
let circuit_events = events.get("test_circuit").unwrap();
|
|
293
|
+
|
|
294
|
+
assert!(
|
|
295
|
+
circuit_events.len() <= 5,
|
|
296
|
+
"Expected <= 5 events, got {}",
|
|
297
|
+
circuit_events.len()
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
#[test]
|
|
302
|
+
fn test_memory_storage_monotonic_time() {
|
|
303
|
+
let storage = MemoryStorage::new();
|
|
304
|
+
|
|
305
|
+
storage.record_success("test_circuit", 0.1);
|
|
306
|
+
let time1 = storage.monotonic_time();
|
|
307
|
+
|
|
308
|
+
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
309
|
+
|
|
310
|
+
storage.record_success("test_circuit", 0.2);
|
|
311
|
+
let time2 = storage.monotonic_time();
|
|
312
|
+
|
|
313
|
+
assert!(time2 > time1);
|
|
314
|
+
assert_eq!(storage.success_count("test_circuit", 1.0), 2);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
#[test]
|
|
318
|
+
fn test_null_storage_discards_events() {
|
|
319
|
+
let storage = NullStorage::new();
|
|
320
|
+
|
|
321
|
+
storage.record_success("test_circuit", 0.1);
|
|
322
|
+
storage.record_failure("test_circuit", 0.2);
|
|
323
|
+
|
|
324
|
+
assert_eq!(storage.success_count("test_circuit", 60.0), 0);
|
|
325
|
+
assert_eq!(storage.failure_count("test_circuit", 60.0), 0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#[test]
|
|
329
|
+
fn test_null_storage_empty_event_log() {
|
|
330
|
+
let storage = NullStorage::new();
|
|
331
|
+
|
|
332
|
+
storage.record_success("test_circuit", 0.1);
|
|
333
|
+
storage.record_failure("test_circuit", 0.2);
|
|
334
|
+
|
|
335
|
+
let log = storage.event_log("test_circuit", 10);
|
|
336
|
+
assert_eq!(log.len(), 0);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#[test]
|
|
340
|
+
fn test_null_storage_clear_operations() {
|
|
341
|
+
let storage = NullStorage::new();
|
|
342
|
+
|
|
343
|
+
storage.clear("test_circuit");
|
|
344
|
+
storage.clear_all();
|
|
345
|
+
|
|
346
|
+
assert_eq!(storage.success_count("test_circuit", 60.0), 0);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
#[test]
|
|
350
|
+
fn test_null_storage_monotonic_time() {
|
|
351
|
+
let storage = NullStorage::new();
|
|
352
|
+
|
|
353
|
+
let time1 = storage.monotonic_time();
|
|
354
|
+
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
355
|
+
let time2 = storage.monotonic_time();
|
|
356
|
+
|
|
357
|
+
assert!(time2 > time1);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
#[test]
|
|
361
|
+
fn test_null_storage_with_circuit_breaker() {
|
|
362
|
+
use std::sync::Arc;
|
|
363
|
+
|
|
364
|
+
let storage = Arc::new(NullStorage::new());
|
|
365
|
+
let mut circuit = crate::CircuitBreaker::builder("test")
|
|
366
|
+
.storage(storage)
|
|
367
|
+
.failure_threshold(3)
|
|
368
|
+
.build();
|
|
369
|
+
|
|
370
|
+
let _ = circuit.call(|| Err::<(), _>("error 1"));
|
|
371
|
+
let _ = circuit.call(|| Err::<(), _>("error 2"));
|
|
372
|
+
let _ = circuit.call(|| Err::<(), _>("error 3"));
|
|
373
|
+
|
|
374
|
+
assert!(circuit.is_closed());
|
|
375
|
+
assert!(!circuit.is_open());
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
|
2
|
+
#
|
|
3
|
+
# When uploading crates to the registry Cargo will automatically
|
|
4
|
+
# "normalize" Cargo.toml files for maximal compatibility
|
|
5
|
+
# with all versions of Cargo and also rewrite `path` dependencies
|
|
6
|
+
# to registry (e.g., crates.io) dependencies.
|
|
7
|
+
#
|
|
8
|
+
# If you are reading this file be aware that the original Cargo.toml
|
|
9
|
+
# will likely look very different (and much more reasonable).
|
|
10
|
+
# See Cargo.toml.orig for the original contents.
|
|
11
|
+
|
|
12
|
+
[package]
|
|
13
|
+
edition = "2024"
|
|
14
|
+
name = "breaker-machines"
|
|
15
|
+
version = "0.6.0"
|
|
16
|
+
authors = ["Abdelkader Boudih <terminale@gmail.com>"]
|
|
17
|
+
build = false
|
|
18
|
+
autolib = false
|
|
19
|
+
autobins = false
|
|
20
|
+
autoexamples = false
|
|
21
|
+
autotests = false
|
|
22
|
+
autobenches = false
|
|
23
|
+
description = "High-performance circuit breaker with fallback support and rate-based thresholds"
|
|
24
|
+
readme = "README.md"
|
|
25
|
+
keywords = [
|
|
26
|
+
"circuit-breaker",
|
|
27
|
+
"reliability",
|
|
28
|
+
"fault-tolerance",
|
|
29
|
+
]
|
|
30
|
+
categories = ["concurrency"]
|
|
31
|
+
license = "MIT"
|
|
32
|
+
repository = "https://github.com/seuros/breaker_machines"
|
|
33
|
+
resolver = "2"
|
|
34
|
+
|
|
35
|
+
[lib]
|
|
36
|
+
name = "breaker_machines"
|
|
37
|
+
crate-type = ["lib"]
|
|
38
|
+
path = "src/lib.rs"
|
|
39
|
+
|
|
40
|
+
[[example]]
|
|
41
|
+
name = "basic"
|
|
42
|
+
path = "examples/basic.rs"
|
|
43
|
+
|
|
44
|
+
[dependencies.chrono-machines]
|
|
45
|
+
version = "0.2.1"
|
|
46
|
+
|
|
47
|
+
[dependencies.state-machines]
|
|
48
|
+
version = "0.6"
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
}
|