Skip to main content

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