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 +4 -4
- data/lib/breaker_machines/version.rb +1 -1
- data/sig/README.md +1 -1
- data/sig/all.rbs +1 -1
- data/sig/breaker_machines/circuit.rbs +1 -1
- data/sig/breaker_machines/console.rbs +1 -1
- data/sig/breaker_machines/dsl.rbs +1 -1
- data/sig/breaker_machines/errors.rbs +1 -1
- data/sig/breaker_machines/interfaces.rbs +1 -1
- data/sig/breaker_machines/registry.rbs +1 -1
- data/sig/breaker_machines/storage.rbs +1 -1
- data/sig/breaker_machines/types.rbs +1 -1
- data/sig/manifest.yaml +1 -1
- metadata +16 -31
- data/ext/breaker_machines_native/core/Cargo.toml +0 -21
- data/ext/breaker_machines_native/core/examples/basic.rs +0 -61
- data/ext/breaker_machines_native/core/src/builder.rs +0 -232
- data/ext/breaker_machines_native/core/src/bulkhead.rs +0 -223
- data/ext/breaker_machines_native/core/src/callbacks.rs +0 -147
- data/ext/breaker_machines_native/core/src/circuit.rs +0 -1436
- data/ext/breaker_machines_native/core/src/classifier.rs +0 -177
- data/ext/breaker_machines_native/core/src/errors.rs +0 -47
- data/ext/breaker_machines_native/core/src/lib.rs +0 -62
- data/ext/breaker_machines_native/core/src/storage.rs +0 -377
- data/ext/breaker_machines_native/extconf.rb +0 -3
- data/ext/breaker_machines_native/ffi/Cargo.toml +0 -18
- data/ext/breaker_machines_native/ffi/extconf.rb +0 -62
- data/ext/breaker_machines_native/ffi/src/lib.rs +0 -216
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 302ad7f4383ff797f0f9bf89c4da1252f27f33ebcfab3cc7c3c114ba32afe5e0
|
|
4
|
+
data.tar.gz: 6fc626f0001941fe023d02417c384673b4df74c3cbbb82c28c8a1e932992ba00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b84a777ed7f9bb88bca15ec3935193123bcdf47e9642d2a4ffe4c8de35ea9dcd1d335717922739675f144915815e10e2adc638bb6bb747bc2f0ca35e7968211d
|
|
7
|
+
data.tar.gz: 91e832c67256d91f61692fe7f7ca7b006fef2752560b98a95f9ac3d0169a4af5f9fe4d1e8ebb8d794f6d9dbbe72a2f4e7e71b7b3b7d1d4b1c1de115d96d3707d
|
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
data/sig/manifest.yaml
CHANGED
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.
|
|
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
|
-
}
|