Skip to main content

DoubleCheckedLockExecutor

Struct DoubleCheckedLockExecutor 

Source
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:

  1. A first condition check outside the lock.
  2. Optional prepare action.
  3. Lock acquisition.
  4. A second condition check inside the lock.
  5. The submitted task.
  6. 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 implementing Lock<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<(), ()>

Source

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?
examples/double_checked_lock_executor_demo.rs (line 40)
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>,

Source

pub fn new(builder: ExecutorReadyBuilder<L, T>) -> Self

Assembles an executor from the ready builder state.

§Parameters
  • builder - Ready builder carrying the lock, tester, logger, and prepare lifecycle callbacks.
§Returns

A reusable executor containing the supplied builder state.

Source

pub fn call<C, R, E>(&self, task: C) -> ExecutionContext<R, E>
where C: Callable<R, E>, E: Display,

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.

Source

pub fn execute<Rn, E>(&self, task: Rn) -> ExecutionContext<(), E>
where Rn: Runnable<E>, E: Display,

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.

Source

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 T after both condition checks pass.
§Returns

An ExecutionContext containing success, unmet-condition, or failure information.

Examples found in repository?
examples/double_checked_lock_executor_demo.rs (lines 50-53)
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

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 T after both condition checks pass.
§Returns

An ExecutionContext containing success, unmet-condition, or failure information.

Source

pub fn with_panic_capture(self, catch_panics: bool) -> Self

Derives an executor with panic capture enabled or disabled for tester, callbacks, and task execution.

§Parameters
  • catch_panics - true to convert panic payloads into execution errors, or false to let panics unwind normally.
§Returns

A reconfigured executor with the updated panic-capture setting.

Source

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.

Trait Implementations§

Source§

impl<L: Clone, T: Clone> Clone for DoubleCheckedLockExecutor<L, T>

Source§

fn clone(&self) -> DoubleCheckedLockExecutor<L, T>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

§

impl<L, T> Freeze for DoubleCheckedLockExecutor<L, T>
where L: Freeze,

§

impl<L = (), T = ()> !RefUnwindSafe for DoubleCheckedLockExecutor<L, T>

§

impl<L, T> Send for DoubleCheckedLockExecutor<L, T>
where L: Send,

§

impl<L, T> Sync for DoubleCheckedLockExecutor<L, T>
where L: Sync,

§

impl<L, T> Unpin for DoubleCheckedLockExecutor<L, T>
where L: Unpin,

§

impl<L, T> UnsafeUnpin for DoubleCheckedLockExecutor<L, T>
where L: UnsafeUnpin,

§

impl<L = (), T = ()> !UnwindSafe for DoubleCheckedLockExecutor<L, T>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.