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}