qubit-dcl 0.1.0

Reusable double-checked lock executor for Rust lock abstractions
Documentation

Qubit DCL

CircleCI Coverage Status Crates.io Rust License 中文文档

A standalone crate for double-checked locking over generic lock handles. It packages the usual “test outside the lock, lock, test again, run the task” sequence into a reusable DoubleCheckedLockExecutor, with an optional prepare / rollback / commit pipeline and structured results.

The crate re-exports ArcMutex and the Lock trait from qubit-lock so a typical app can depend on qubit-dcl alone.

Features

  • DoubleCheckedLockExecutor: one builder-built executor, many invocations; integrates with the qubit-function Tester and runnable traits.
  • DoubleCheckedLock: one-shot convenience entry for on(...).when(...).call* style execution without keeping an executor variable.
  • Double-checked flow: first condition check without the lock, optional pre-lock prepare, write lock, second check, then task; after the lock is released, optional prepare commit or rollback.
  • Execution API: call / execute (no direct &mut T in the closure) and call_with / execute_with (mutable access to protected data).
  • Typed outcomes: ExecutionContext and ExecutionResult distinguish success, “condition not met,” task failure, and prepare finalization failures (ExecutorError).
  • Logging hooks via log and configurable ExecutionLogger on the builder for unmet conditions and prepare-step failures.

How it works

  1. The condition tester runs twice (outside the lock, then again under the write lock). Anything the first read relies on must remain safe without this executor’s lock (for example atomics with appropriate orderings).
  2. If the first test passes, an optional prepare runnable may run; then the lock is taken, the second test runs, and the user task runs with &mut T if applicable.
  3. If prepare ran successfully, after releasing the lock the executor may run commit on full success, or rollback when the inner check or task did not succeed.

Installation

[dependencies]
qubit-dcl = "0.1.0"

qubit-dcl already depends on qubit-lock and re-exports ArcMutex and Lock; add a direct qubit-lock dependency only if you use types beyond those re-exports.

Quick start

use std::sync::{Arc, atomic::{AtomicBool, Ordering}};

use qubit_dcl::{DoubleCheckedLockExecutor, ArcMutex, Lock, 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);
}

Side-effect–only run (finish)

For execute or call with no meaningful return value, you can use ExecutionContext::finish on ExecutionContext<(), E> to get a bool success:

use qubit_dcl::{DoubleCheckedLockExecutor, ArcMutex};

let data = ArcMutex::new(());
let ok = DoubleCheckedLockExecutor::builder()
    .on(data)
    .when(|| true)
    .build()
    .execute(|| Ok::<(), std::io::Error>(()))
    .finish();
assert!(ok);

One-shot convenience (DoubleCheckedLock)

When you do not need to keep a reusable executor, use DoubleCheckedLock for a shorter chain:

use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use qubit_dcl::{DoubleCheckedLock, ArcMutex, ExecutionResult, Lock};

let data = ArcMutex::new(10);
let skip = Arc::new(AtomicBool::new(false));

let updated = DoubleCheckedLock::on(data.clone())
    .when({
        let skip = skip.clone();
        move || !skip.load(Ordering::Acquire)
    })
    .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);

Example program

A runnable sample is under examples/double_checked_lock_executor_demo.rs:

cargo run --example double_checked_lock_executor_demo

Builder API (summary)

  • Start with DoubleCheckedLockExecutor::builder().
  • Attach a lock: .on(lock) where L: Lock<T>.
  • Set the double-checked condition: .when(tester).
  • Optionally: .prepare, .rollback_prepare, .commit_prepare for the prepare pipeline; any of .log_unmet_condition, .log_prepare_failure, .log_prepare_commit_failure, .log_prepare_rollback_failure for diagnostics.
  • Finish with .build().

Project layout

  • src/double_checked: executor, builders, ExecutionContext, ExecutionResult, errors, and logging.
  • src/lock: lock-related glue used with the executor.
  • tests/double_checked and tests/docs: unit and README consistency tests.

Quality checks

./align-ci.sh
./ci-check.sh
./coverage.sh json

License

Apache-2.0