Skip to main content

qubit_dcl/double_checked/
double_checked_lock_executor.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! # Double-Checked Lock Executor
10//!
11//! Provides a reusable executor for double-checked locking workflows.
12//!
13//! # Author
14//!
15//! Haixing Hu
16
17use std::{
18    fmt::Display,
19    marker::PhantomData,
20};
21
22use qubit_function::{
23    ArcRunnable,
24    ArcTester,
25    Callable,
26    CallableWith,
27    Runnable,
28    RunnableWith,
29    Tester,
30};
31
32use super::{
33    ExecutionContext,
34    ExecutionLogger,
35    ExecutionResult,
36    executor_builder::ExecutorBuilder,
37    executor_ready_builder::ExecutorReadyBuilder,
38};
39use crate::lock::Lock;
40
41/// Reusable double-checked lock executor.
42///
43/// The executor owns the lock handle, condition tester, execution logger, and
44/// optional prepare lifecycle callbacks. Each execution performs:
45///
46/// 1. A first condition check outside the lock.
47/// 2. Optional prepare action.
48/// 3. Lock acquisition.
49/// 4. A second condition check inside the lock.
50/// 5. The submitted task.
51/// 6. Optional prepare commit or rollback after the lock is released.
52///
53/// The tester is intentionally run both outside and inside the lock. Any state
54/// read by the first check must therefore use atomics or another synchronization
55/// mechanism that is safe without this executor's lock.
56///
57/// # Type Parameters
58///
59/// * `L` - The lock type implementing [`Lock<T>`].
60/// * `T` - The data type protected by the lock.
61///
62/// # Examples
63///
64/// Use [`DoubleCheckedLockExecutor::builder`] to attach a lock (for example
65/// [`crate::ArcMutex`]), set a [`Tester`](qubit_function::Tester) with
66/// [`ExecutorLockBuilder::when`], then call [`Self::call`], [`Self::execute`],
67/// [`Self::call_with`], or [`Self::execute_with`] on the built executor.
68///
69/// ```rust
70/// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
71///
72/// use qubit_dcl::{DoubleCheckedLockExecutor, Lock};
73/// use qubit_lock::ArcMutex;
74/// use qubit_dcl::double_checked::ExecutionResult;
75///
76/// fn main() {
77///     let data = ArcMutex::new(10);
78///     let skip = Arc::new(AtomicBool::new(false));
79///
80///     let executor = DoubleCheckedLockExecutor::builder()
81///         .on(data.clone())
82///         .when({
83///             let skip = skip.clone();
84///             move || !skip.load(Ordering::Acquire)
85///         })
86///         .build();
87///
88///     let updated = executor
89///         .call_with(|value: &mut i32| {
90///             *value += 5;
91///             Ok::<i32, std::io::Error>(*value)
92///         })
93///         .get_result();
94///
95///     assert!(matches!(updated, ExecutionResult::Success(15)));
96///     assert_eq!(data.read(|value| *value), 15);
97///
98///     skip.store(true, Ordering::Release);
99///     let skipped = executor
100///         .call_with(|value: &mut i32| {
101///             *value += 1;
102///             Ok::<i32, std::io::Error>(*value)
103///         })
104///         .get_result();
105///
106///     assert!(matches!(skipped, ExecutionResult::ConditionNotMet));
107///     assert_eq!(data.read(|value| *value), 15);
108/// }
109/// ```
110///
111/// # Author
112///
113/// Haixing Hu
114#[derive(Clone)]
115pub struct DoubleCheckedLockExecutor<L = (), T = ()> {
116    /// The lock protecting the target data.
117    lock: L,
118
119    /// Condition checked before and after acquiring the lock.
120    tester: ArcTester,
121
122    /// Logger for unmet conditions and prepare lifecycle failures.
123    logger: ExecutionLogger,
124
125    /// Optional action executed after the first check and before locking.
126    prepare_action: Option<ArcRunnable<String>>,
127
128    /// Optional action executed when prepare must be rolled back.
129    rollback_prepare_action: Option<ArcRunnable<String>>,
130
131    /// Optional action executed when prepare should be committed.
132    commit_prepare_action: Option<ArcRunnable<String>>,
133
134    /// Carries the protected data type.
135    _phantom: PhantomData<fn() -> T>,
136}
137
138impl DoubleCheckedLockExecutor<(), ()> {
139    /// Creates a builder for a reusable double-checked lock executor.
140    ///
141    /// # Returns
142    ///
143    /// A builder in the initial state. Attach a lock with
144    /// [`ExecutorBuilder::on`], then configure a tester with
145    /// [`ExecutorLockBuilder::when`].
146    #[inline]
147    pub fn builder() -> ExecutorBuilder {
148        ExecutorBuilder::default()
149    }
150}
151
152impl<L, T> DoubleCheckedLockExecutor<L, T>
153where
154    L: Lock<T>,
155{
156    /// Assembles an executor from the ready builder state.
157    ///
158    /// # Parameters
159    ///
160    /// * `builder` - Ready builder carrying the lock, tester, logger, and
161    ///   prepare lifecycle callbacks.
162    ///
163    /// # Returns
164    ///
165    /// A reusable executor containing the supplied builder state.
166    #[inline]
167    pub fn new(builder: ExecutorReadyBuilder<L, T>) -> Self {
168        Self {
169            lock: builder.lock,
170            tester: builder.tester,
171            logger: builder.logger,
172            prepare_action: builder.prepare_action,
173            rollback_prepare_action: builder.rollback_prepare_action,
174            commit_prepare_action: builder.commit_prepare_action,
175            _phantom: builder._phantom,
176        }
177    }
178
179    /// Executes a zero-argument callable while holding the write lock.
180    ///
181    /// Use [`Self::call_with`] when the task needs direct mutable access to the
182    /// protected data.
183    ///
184    /// # Parameters
185    ///
186    /// * `task` - The callable task to execute after both condition checks pass.
187    ///
188    /// # Returns
189    ///
190    /// An [`ExecutionContext`] containing success, unmet-condition, or failure
191    /// information.
192    #[inline]
193    pub fn call<C, R, E>(&self, task: C) -> ExecutionContext<R, E>
194    where
195        C: Callable<R, E> + Send + 'static,
196        R: Send + 'static,
197        E: Display + Send + 'static,
198    {
199        let mut task = task;
200        let result = self.execute_with_write_lock(move |_data| task.call());
201        ExecutionContext::new(result)
202    }
203
204    /// Executes a zero-argument runnable while holding the write lock.
205    ///
206    /// # Parameters
207    ///
208    /// * `task` - The runnable task to execute after both condition checks pass.
209    ///
210    /// # Returns
211    ///
212    /// An [`ExecutionContext`] containing success, unmet-condition, or failure
213    /// information.
214    #[inline]
215    pub fn execute<Rn, E>(&self, task: Rn) -> ExecutionContext<(), E>
216    where
217        Rn: Runnable<E> + Send + 'static,
218        E: Display + Send + 'static,
219    {
220        let mut task = task;
221        let result = self.execute_with_write_lock(move |_data| task.run());
222        ExecutionContext::new(result)
223    }
224
225    /// Executes a callable with mutable access to the protected data.
226    ///
227    /// # Parameters
228    ///
229    /// * `task` - The callable receiving `&mut T` after both condition checks
230    ///   pass.
231    ///
232    /// # Returns
233    ///
234    /// An [`ExecutionContext`] containing success, unmet-condition, or failure
235    /// information.
236    #[inline]
237    pub fn call_with<C, R, E>(&self, task: C) -> ExecutionContext<R, E>
238    where
239        C: CallableWith<T, R, E> + Send + 'static,
240        R: Send + 'static,
241        E: Display + Send + 'static,
242    {
243        let mut task = task;
244        let result = self.execute_with_write_lock(move |data| task.call_with(data));
245        ExecutionContext::new(result)
246    }
247
248    /// Executes a runnable with mutable access to the protected data.
249    ///
250    /// # Parameters
251    ///
252    /// * `task` - The runnable receiving `&mut T` after both condition checks
253    ///   pass.
254    ///
255    /// # Returns
256    ///
257    /// An [`ExecutionContext`] containing success, unmet-condition, or failure
258    /// information.
259    #[inline]
260    pub fn execute_with<Rn, E>(&self, task: Rn) -> ExecutionContext<(), E>
261    where
262        Rn: RunnableWith<T, E> + Send + 'static,
263        E: Display + Send + 'static,
264    {
265        let mut task = task;
266        let result = self.execute_with_write_lock(move |data| task.run_with(data));
267        ExecutionContext::new(result)
268    }
269
270    /// Runs the configured double-checked sequence under a write lock.
271    ///
272    /// # Parameters
273    ///
274    /// * `task` - The task to run with mutable access after both condition
275    ///   checks pass.
276    ///
277    /// # Returns
278    ///
279    /// The final execution result, including prepare finalization.
280    ///
281    /// # Errors
282    ///
283    /// Task errors are captured as [`ExecutionResult::Failed`] with
284    /// [`super::ExecutorError::TaskFailed`]. Prepare, commit, and rollback
285    /// failures are also captured in the returned [`ExecutionResult`] rather
286    /// than returned as a separate `Result`.
287    fn execute_with_write_lock<R, E, F>(&self, task: F) -> ExecutionResult<R, E>
288    where
289        E: Display + Send + 'static,
290        F: FnOnce(&mut T) -> Result<R, E>,
291    {
292        if !self.tester.test() {
293            self.log_unmet_condition();
294            return ExecutionResult::unmet();
295        }
296
297        let prepare_completed = match self.run_prepare_action() {
298            Ok(completed) => completed,
299            Err(error) => return ExecutionResult::prepare_failed(error),
300        };
301
302        let result = self.lock.write(|data| {
303            if !self.tester.test() {
304                self.log_unmet_condition();
305                return ExecutionResult::unmet();
306            }
307            match task(data) {
308                Ok(value) => ExecutionResult::success(value),
309                Err(error) => ExecutionResult::task_failed(error),
310            }
311        });
312
313        if prepare_completed {
314            self.finalize_prepare(result)
315        } else {
316            result
317        }
318    }
319
320    /// Executes the optional prepare action.
321    ///
322    /// # Returns
323    ///
324    /// `Ok(true)` if prepare exists and succeeds, `Ok(false)` if no prepare
325    /// action is configured, or `Err(message)` if prepare fails.
326    ///
327    /// # Errors
328    ///
329    /// Returns `Err(message)` when the configured prepare action returns an
330    /// error. The message is already converted to [`String`].
331    fn run_prepare_action(&self) -> Result<bool, String> {
332        let Some(mut prepare_action) = self.prepare_action.clone() else {
333            return Ok(false);
334        };
335        if let Err(error) = prepare_action.run() {
336            self.logger.log_prepare_failed(&error);
337            return Err(error);
338        }
339        Ok(true)
340    }
341
342    /// Commits or rolls back a successfully completed prepare action.
343    ///
344    /// This method runs after the write lock has been released.
345    ///
346    /// # Parameters
347    ///
348    /// * `result` - Result produced by the condition check and task execution.
349    ///
350    /// # Returns
351    ///
352    /// `result` unchanged when no finalization action fails. Returns a failed
353    /// result when prepare commit or prepare rollback fails.
354    fn finalize_prepare<R, E>(&self, mut result: ExecutionResult<R, E>) -> ExecutionResult<R, E>
355    where
356        E: Display + Send + 'static,
357    {
358        if result.is_success() {
359            if let Some(mut commit_prepare_action) = self.commit_prepare_action.clone()
360                && let Err(error) = commit_prepare_action.run()
361            {
362                self.logger.log_prepare_commit_failed(&error);
363                result = ExecutionResult::prepare_commit_failed(error);
364            }
365            return result;
366        }
367
368        let original = if let ExecutionResult::Failed(error) = &result {
369            error.to_string()
370        } else {
371            "Condition not met".to_string()
372        };
373
374        if let Some(mut rollback_prepare_action) = self.rollback_prepare_action.clone()
375            && let Err(error) = rollback_prepare_action.run()
376        {
377            self.logger.log_prepare_rollback_failed(&error);
378            result = ExecutionResult::prepare_rollback_failed(original, error);
379        }
380        result
381    }
382
383    /// Logs that the double-checked condition was not met.
384    fn log_unmet_condition(&self) {
385        self.logger.log_unmet_condition();
386    }
387}