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}