pub struct DoubleCheckedLockExecutor<L = (), T = ()> { /* private fields */ }Expand description
Reusable double-checked lock executor.
The executor owns the lock handle, condition tester, execution logger, and optional prepare lifecycle callbacks. Each execution performs:
- A first condition check outside the lock.
- Optional prepare action.
- Lock acquisition.
- A second condition check inside the lock.
- The submitted task.
- Optional prepare commit or rollback after the lock is released.
The tester is intentionally run both outside and inside the lock. Any state read by the first check must therefore use atomics or another synchronization mechanism that is safe without this executor’s lock.
§Type Parameters
L- The lock type implementingLock<T>.T- The data type protected by the lock.
§Examples
Use DoubleCheckedLockExecutor::builder to attach a lock (for example
crate::ArcMutex), set a Tester with
ExecutorLockBuilder::when, then call
Self::call, Self::execute, Self::call_with, or
Self::execute_with on the built executor.
Panics from the tester, prepare callbacks, or task can be captured by
configuring the builder or by deriving a reconfigured executor with
with_panic_capture. Tester and task panics
are reported as super::ExecutorError::Panic. Prepare lifecycle panics
are reported through the corresponding prepare, commit, or rollback error
variants, so rollback can still be executed after captured task or second
condition-check panics.
Cloned executors share their configured prepare callbacks. Concurrent calls may therefore complete prepare in several threads before one call wins the second condition check; calls that lose the second check run prepare rollback if it is configured.
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use qubit_dcl::{ArcMutex, DoubleCheckedLockExecutor, Lock};
use qubit_dcl::double_checked::ExecutionResult;
fn main() {
let data = ArcMutex::new(10);
let skip = Arc::new(AtomicBool::new(false));
let executor = DoubleCheckedLockExecutor::builder()
.on(data.clone())
.when({
let skip = skip.clone();
move || !skip.load(Ordering::Acquire)
})
.build();
let updated = executor
.call_with(|value: &mut i32| {
*value += 5;
Ok::<i32, std::io::Error>(*value)
})
.get_result();
assert!(matches!(updated, ExecutionResult::Success(15)));
assert_eq!(data.read(|value| *value), 15);
skip.store(true, Ordering::Release);
let skipped = executor
.call_with(|value: &mut i32| {
*value += 1;
Ok::<i32, std::io::Error>(*value)
})
.get_result();
assert!(matches!(skipped, ExecutionResult::ConditionNotMet));
assert_eq!(data.read(|value| *value), 15);
}Implementations§
Source§impl DoubleCheckedLockExecutor<(), ()>
impl DoubleCheckedLockExecutor<(), ()>
Sourcepub fn builder() -> ExecutorBuilder
pub fn builder() -> ExecutorBuilder
Creates a builder for a reusable double-checked lock executor.
§Returns
A builder in the initial state. Attach a lock with
ExecutorBuilder::on, then configure a tester with
ExecutorLockBuilder::when.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // Create shared state
31 let running = Arc::new(AtomicBool::new(false));
32 let data = ArcMutex::new(42);
33
34 println!(
35 "Initial state: running = {}",
36 running.load(Ordering::Acquire)
37 );
38 println!("Initial data: {}", data.read(|d| *d));
39
40 let executor = DoubleCheckedLockExecutor::builder()
41 .on(data.clone())
42 .when({
43 let running = running.clone();
44 move || running.load(Ordering::Acquire)
45 })
46 .build();
47
48 // Try to execute when service is not running (should fail)
49 let result = executor
50 .call_with(|value: &mut i32| {
51 *value += 1;
52 Ok::<_, std::io::Error>(*value)
53 })
54 .get_result();
55
56 if result.is_success() {
57 println!("Unexpected success: {}", result.unwrap());
58 } else {
59 println!("Expected failure: Condition not met.");
60 }
61
62 // Start the service
63 running.store(true, Ordering::Release);
64 println!(
65 "Service started: running = {}",
66 running.load(Ordering::Acquire)
67 );
68
69 // Now execute should succeed
70 let result = executor
71 .call_with(|value: &mut i32| {
72 *value += 1;
73 Ok::<_, std::io::Error>(*value)
74 })
75 .get_result();
76
77 if result.is_success() {
78 println!("Success: new value = {}", result.unwrap());
79 } else {
80 println!("Unexpected failure: {:?}", result);
81 }
82
83 // Verify the data was updated
84 println!("Final data: {}", data.read(|d| *d));
85
86 // Stop the service
87 running.store(false, Ordering::Release);
88 println!(
89 "Service stopped: running = {}",
90 running.load(Ordering::Acquire)
91 );
92
93 // Try to execute when service is stopped (should fail)
94 let result = executor
95 .call_with(|value: &mut i32| {
96 *value += 1;
97 Ok::<_, std::io::Error>(*value)
98 })
99 .get_result();
100
101 if result.is_success() {
102 println!("Unexpected success: {}", result.unwrap());
103 } else {
104 println!("Expected failure: Condition not met.");
105 }
106
107 Ok(())
108}Source§impl<L, T> DoubleCheckedLockExecutor<L, T>where
L: Lock<T>,
impl<L, T> DoubleCheckedLockExecutor<L, T>where
L: Lock<T>,
Sourcepub fn new(builder: ExecutorReadyBuilder<L, T>) -> Self
pub fn new(builder: ExecutorReadyBuilder<L, T>) -> Self
Sourcepub fn call<C, R, E>(&self, task: C) -> ExecutionContext<R, E>
pub fn call<C, R, E>(&self, task: C) -> ExecutionContext<R, E>
Executes a zero-argument callable while holding the write lock.
Use Self::call_with when the task needs direct mutable access to the
protected data.
§Parameters
task- The callable task to execute after both condition checks pass.
§Returns
An ExecutionContext containing success, unmet-condition, or failure
information.
Sourcepub fn execute<Rn, E>(&self, task: Rn) -> ExecutionContext<(), E>
pub fn execute<Rn, E>(&self, task: Rn) -> ExecutionContext<(), E>
Executes a zero-argument runnable while holding the write lock.
§Parameters
task- The runnable task to execute after both condition checks pass.
§Returns
An ExecutionContext containing success, unmet-condition, or failure
information.
Sourcepub fn call_with<C, R, E>(&self, task: C) -> ExecutionContext<R, E>where
C: CallableWith<T, R, E>,
E: Display,
pub fn call_with<C, R, E>(&self, task: C) -> ExecutionContext<R, E>where
C: CallableWith<T, R, E>,
E: Display,
Executes a callable with mutable access to the protected data.
§Parameters
task- The callable receiving&mut Tafter both condition checks pass.
§Returns
An ExecutionContext containing success, unmet-condition, or failure
information.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // Create shared state
31 let running = Arc::new(AtomicBool::new(false));
32 let data = ArcMutex::new(42);
33
34 println!(
35 "Initial state: running = {}",
36 running.load(Ordering::Acquire)
37 );
38 println!("Initial data: {}", data.read(|d| *d));
39
40 let executor = DoubleCheckedLockExecutor::builder()
41 .on(data.clone())
42 .when({
43 let running = running.clone();
44 move || running.load(Ordering::Acquire)
45 })
46 .build();
47
48 // Try to execute when service is not running (should fail)
49 let result = executor
50 .call_with(|value: &mut i32| {
51 *value += 1;
52 Ok::<_, std::io::Error>(*value)
53 })
54 .get_result();
55
56 if result.is_success() {
57 println!("Unexpected success: {}", result.unwrap());
58 } else {
59 println!("Expected failure: Condition not met.");
60 }
61
62 // Start the service
63 running.store(true, Ordering::Release);
64 println!(
65 "Service started: running = {}",
66 running.load(Ordering::Acquire)
67 );
68
69 // Now execute should succeed
70 let result = executor
71 .call_with(|value: &mut i32| {
72 *value += 1;
73 Ok::<_, std::io::Error>(*value)
74 })
75 .get_result();
76
77 if result.is_success() {
78 println!("Success: new value = {}", result.unwrap());
79 } else {
80 println!("Unexpected failure: {:?}", result);
81 }
82
83 // Verify the data was updated
84 println!("Final data: {}", data.read(|d| *d));
85
86 // Stop the service
87 running.store(false, Ordering::Release);
88 println!(
89 "Service stopped: running = {}",
90 running.load(Ordering::Acquire)
91 );
92
93 // Try to execute when service is stopped (should fail)
94 let result = executor
95 .call_with(|value: &mut i32| {
96 *value += 1;
97 Ok::<_, std::io::Error>(*value)
98 })
99 .get_result();
100
101 if result.is_success() {
102 println!("Unexpected success: {}", result.unwrap());
103 } else {
104 println!("Expected failure: Condition not met.");
105 }
106
107 Ok(())
108}Sourcepub fn execute_with<Rn, E>(&self, task: Rn) -> ExecutionContext<(), E>where
Rn: RunnableWith<T, E>,
E: Display,
pub fn execute_with<Rn, E>(&self, task: Rn) -> ExecutionContext<(), E>where
Rn: RunnableWith<T, E>,
E: Display,
Executes a runnable with mutable access to the protected data.
§Parameters
task- The runnable receiving&mut Tafter both condition checks pass.
§Returns
An ExecutionContext containing success, unmet-condition, or failure
information.
Sourcepub fn with_panic_capture(self, catch_panics: bool) -> Self
pub fn with_panic_capture(self, catch_panics: bool) -> Self
Sourcepub fn catch_panics(&self) -> bool
pub fn catch_panics(&self) -> bool
Returns whether panic capture is enabled.
§Returns
true when tester, prepare callback, and task panics are converted into
execution errors instead of unwinding.