• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

qubit-ltd / rust-concurrent / 53a83ac2-4e27-4ade-80fc-d8a40935e243

10 Apr 2026 05:27PM UTC coverage: 98.258%. Remained the same
53a83ac2-4e27-4ade-80fc-d8a40935e243

push

circleci

Haixing-Hu
chore(cargo): bump package version to 0.2.2

- update Cargo.toml package version to 0.2.2 to reflect the new release
- refresh Cargo.lock so dependency revisions match the updated toolkit versions

564 of 574 relevant lines covered (98.26%)

104.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.2
/src/double_checked/builder.rs
1
/*******************************************************************************
2
 *
3
 *    Copyright (c) 2025 - 2026.
4
 *    Haixing Hu, Qubit Co. Ltd.
5
 *
6
 *    All rights reserved.
7
 *
8
 ******************************************************************************/
9
//! # Double-Checked Locking Execution Builder
10
//!
11
//! Provides a fluent API builder using the typestate pattern.
12
//!
13
//! # Author
14
//!
15
//! Haixing Hu
16
use std::{
17
    error::Error,
18
    marker::PhantomData,
19
};
20

21
use qubit_function::{
22
    BoxFunctionOnce,
23
    BoxMutatingFunctionOnce,
24
    BoxSupplierOnce,
25
    BoxTester,
26
    FunctionOnce,
27
    MutatingFunctionOnce,
28
    SupplierOnce,
29
    Tester,
30
};
31

32
use super::{
33
    states::{
34
        Conditioned,
35
        Configuring,
36
        Initial,
37
    },
38
    ExecutionContext,
39
    ExecutionResult,
40
    LogConfig,
41
};
42
use crate::{
43
    double_checked::error::ExecutorError,
44
    lock::Lock,
45
};
46

47
/// Execution builder (using typestate pattern)
48
///
49
/// This builder uses the type system to enforce the correct call sequence
50
/// at compile time.
51
///
52
/// # Type Parameters
53
///
54
/// * `'a` - Lifetime of the lock
55
/// * `L` - Lock type (implements the Lock<T> trait)
56
/// * `T` - Type of data protected by the lock
57
/// * `State` - Current state (Initial, Configuring, Conditioned)
58
///
59
/// # Author
60
///
61
/// Haixing Hu
62
pub struct ExecutionBuilder<'a, L, T, State = Initial>
63
where
64
    L: Lock<T>,
65
{
66
    /// Reference to the lock that protects the shared data
67
    lock: &'a L,
68

69
    /// Optional logging configuration for execution events
70
    logger: Option<LogConfig>,
71

72
    /// Optional test condition that determines if execution should proceed
73
    tester: Option<BoxTester>,
74

75
    /// Optional preparation action executed between first check and locking
76
    prepare_action: Option<BoxSupplierOnce<Result<(), Box<dyn Error + Send + Sync>>>>,
77

78
    /// Phantom data for typestate pattern, tracks current builder state
79
    _phantom: PhantomData<(T, State)>,
80
}
81

82
/// Implementation for the `Initial` state of `ExecutionBuilder`.
83
///
84
/// In this state, the builder has just been created and allows:
85
/// - Configuring optional logging via `logger()`
86
/// - Setting the required test condition via `when()`
87
///
88
/// This is the starting state where users begin building their execution.
89
impl<'a, L, T> ExecutionBuilder<'a, L, T, Initial>
90
where
91
    L: Lock<T>,
92
{
93
    /// Creates a new execution builder
94
    ///
95
    /// # Arguments
96
    ///
97
    /// * `lock` - Reference to the lock object
98
    #[inline]
99
    pub(super) fn new(lock: &'a L) -> Self {
68✔
100
        Self {
68✔
101
            lock,
68✔
102
            logger: None,
68✔
103
            tester: None,
68✔
104
            prepare_action: None,
68✔
105
            _phantom: PhantomData,
68✔
106
        }
68✔
107
    }
68✔
108

109
    /// Configures logging (optional)
110
    ///
111
    /// # State Transition
112
    ///
113
    /// Initial → Configuring
114
    ///
115
    /// # Arguments
116
    ///
117
    /// * `level` - Log level
118
    /// * `message` - Log message
119
    #[inline]
120
    pub fn logger(
7✔
121
        mut self,
7✔
122
        level: log::Level,
7✔
123
        message: &str,
7✔
124
    ) -> ExecutionBuilder<'a, L, T, Configuring> {
7✔
125
        self.logger = Some(LogConfig {
7✔
126
            level,
7✔
127
            message: message.to_string(),
7✔
128
        });
7✔
129
        ExecutionBuilder {
7✔
130
            lock: self.lock,
7✔
131
            logger: self.logger,
7✔
132
            tester: self.tester,
7✔
133
            prepare_action: self.prepare_action,
7✔
134
            _phantom: PhantomData,
7✔
135
        }
7✔
136
    }
7✔
137

138
    /// Sets the test condition (required)
139
    ///
140
    /// # Safety Warning
141
    ///
142
    /// The `tester` closure is executed twice: first without the lock (fast
143
    /// path) and then with the lock held (slow path).
144
    ///
145
    /// For the first check (fast path) to be thread-safe, the `tester` closure
146
    /// MUST access shared state using atomic operations with appropriate memory
147
    /// ordering (e.g., `Ordering::SeqCst` or `Ordering::Acquire`). Relying on
148
    /// non-atomic shared state without locking leads to data races and
149
    /// undefined behavior.
150
    ///
151
    /// # State Transition
152
    ///
153
    /// Initial → Conditioned
154
    ///
155
    /// # Arguments
156
    ///
157
    /// * `tester` - The test condition
158
    #[inline]
159
    pub fn when<Tst>(mut self, tester: Tst) -> ExecutionBuilder<'a, L, T, Conditioned>
55✔
160
    where
55✔
161
        Tst: Tester + 'static,
55✔
162
    {
163
        self.tester = Some(tester.into_box());
55✔
164
        ExecutionBuilder {
55✔
165
            lock: self.lock,
55✔
166
            logger: self.logger,
55✔
167
            tester: self.tester,
55✔
168
            prepare_action: self.prepare_action,
55✔
169
            _phantom: PhantomData,
55✔
170
        }
55✔
171
    }
55✔
172
}
173

174
/// Implementation for the `Configuring` state of `ExecutionBuilder`.
175
///
176
/// In this state, logging has been configured and the builder allows:
177
/// - Overriding the logging configuration via `logger()`
178
/// - Setting the required test condition via `when()`
179
///
180
/// Users can stay in this state to adjust logging settings or transition
181
/// to the `Conditioned` state by setting a test condition.
182
impl<'a, L, T> ExecutionBuilder<'a, L, T, Configuring>
183
where
184
    L: Lock<T>,
185
{
186
    /// Continues configuring logging (can override previous configuration)
187
    ///
188
    /// # State Transition
189
    ///
190
    /// Configuring → Configuring
191
    ///
192
    /// # Arguments
193
    ///
194
    /// * `level` - Log level
195
    /// * `message` - Log message
196
    #[inline]
197
    pub fn logger(mut self, level: log::Level, message: &str) -> Self {
3✔
198
        self.logger = Some(LogConfig {
3✔
199
            level,
3✔
200
            message: message.to_string(),
3✔
201
        });
3✔
202
        self
3✔
203
    }
3✔
204

205
    /// Sets the test condition (required)
206
    ///
207
    /// # Safety Warning
208
    ///
209
    /// The `tester` closure is executed twice: first without the lock (fast
210
    /// path) and then with the lock held (slow path).
211
    ///
212
    /// For the first check (fast path) to be thread-safe, the `tester` closure
213
    /// MUST access shared state using atomic operations with appropriate memory
214
    /// ordering (e.g., `Ordering::SeqCst` or `Ordering::Acquire`). Relying on
215
    /// non-atomic shared state without locking leads to data races and
216
    /// undefined behavior.
217
    ///
218
    /// # State Transition
219
    ///
220
    /// Configuring → Conditioned
221
    ///
222
    /// # Arguments
223
    ///
224
    /// * `tester` - The test condition
225
    #[inline]
226
    pub fn when<Tst>(mut self, tester: Tst) -> ExecutionBuilder<'a, L, T, Conditioned>
5✔
227
    where
5✔
228
        Tst: Tester + 'static,
5✔
229
    {
230
        self.tester = Some(tester.into_box());
5✔
231
        ExecutionBuilder {
5✔
232
            lock: self.lock,
5✔
233
            logger: self.logger,
5✔
234
            tester: self.tester,
5✔
235
            prepare_action: self.prepare_action,
5✔
236
            _phantom: PhantomData,
5✔
237
        }
5✔
238
    }
5✔
239
}
240

241
/// Implementation for the `Conditioned` state of `ExecutionBuilder`.
242
///
243
/// In this state, the test condition has been set and the builder allows:
244
/// - Setting an optional prepare action via `prepare()`
245
/// - Executing read-only tasks with return values via `call()`
246
/// - Executing read-write tasks with return values via `call_mut()`
247
/// - Executing read-only tasks without return values via `execute()`
248
/// - Executing read-write tasks without return values via `execute_mut()`
249
///
250
/// This is the final state where users can configure preparation steps
251
/// and execute their tasks with double-checked locking semantics.
252
impl<'a, L, T> ExecutionBuilder<'a, L, T, Conditioned>
253
where
254
    L: Lock<T>,
255
    T: 'static,
256
{
257
    /// Sets prepare action (optional, executed between first check and locking)
258
    ///
259
    /// # State Transition
260
    ///
261
    /// Conditioned → Conditioned
262
    ///
263
    /// # Arguments
264
    ///
265
    /// * `prepare_action` - Any type that implements
266
    ///   `SupplierOnce<Result<(), E>>`
267
    #[inline]
268
    pub fn prepare<S, E>(mut self, prepare_action: S) -> Self
8✔
269
    where
8✔
270
        S: SupplierOnce<Result<(), E>> + 'static,
8✔
271
        E: Error + Send + Sync + 'static,
8✔
272
    {
273
        let boxed = prepare_action.into_box();
8✔
274
        self.prepare_action = Some(BoxSupplierOnce::new(move || {
8✔
275
            boxed
5✔
276
                .get()
5✔
277
                .map_err(|e| Box::new(e) as Box<dyn Error + Send + Sync>)
5✔
278
        }));
5✔
279
        self
8✔
280
    }
8✔
281

282
    /// Executes a read-only task (with return value)
283
    ///
284
    /// # Execution Flow
285
    ///
286
    /// 1. First condition check (outside lock)
287
    /// 2. Execute prepare action (if any)
288
    /// 3. Acquire lock
289
    /// 4. Second condition check (inside lock)
290
    /// 5. Execute task
291
    ///
292
    /// # State Transition
293
    ///
294
    /// Conditioned → ExecutionContext<R>
295
    ///
296
    /// # Arguments
297
    ///
298
    /// * `task` - Any type that implements `FunctionOnce<T, Result<R, E>>`
299
    #[inline]
300
    pub fn call<F, R, E>(self, task: F) -> ExecutionContext<R, E>
42✔
301
    where
42✔
302
        F: FunctionOnce<T, Result<R, E>> + 'static,
42✔
303
        E: Error + Send + Sync + 'static,
42✔
304
        R: 'static,
42✔
305
    {
306
        let task_boxed = task.into_box();
42✔
307
        let result = self.execute_with_read_lock(task_boxed);
42✔
308
        ExecutionContext::new(result)
42✔
309
    }
42✔
310

311
    /// Executes a read-write task (with return value)
312
    ///
313
    /// # State Transition
314
    ///
315
    /// Conditioned → ExecutionContext<R>
316
    ///
317
    /// # Arguments
318
    ///
319
    /// * `task` - Any type that implements
320
    ///   `MutatingFunctionOnce<T, Result<R, E>>`
321
    #[inline]
322
    pub fn call_mut<F, R, E>(self, task: F) -> ExecutionContext<R, E>
15✔
323
    where
15✔
324
        F: MutatingFunctionOnce<T, Result<R, E>> + 'static,
15✔
325
        E: Error + Send + Sync + 'static,
15✔
326
        R: 'static,
15✔
327
    {
328
        let task_boxed = task.into_box();
15✔
329
        let result = self.execute_with_write_lock(task_boxed);
15✔
330
        ExecutionContext::new(result)
15✔
331
    }
15✔
332

333
    /// Executes a read-only task (without return value)
334
    ///
335
    /// # Arguments
336
    ///
337
    /// * `task` - Any type that implements `FunctionOnce<T, Result<(), E>>`
338
    #[inline]
339
    pub fn execute<F, E>(self, task: F) -> ExecutionContext<(), E>
7✔
340
    where
7✔
341
        F: FunctionOnce<T, Result<(), E>> + 'static,
7✔
342
        E: Error + Send + Sync + 'static,
7✔
343
    {
344
        self.call(task)
7✔
345
    }
7✔
346

347
    /// Executes a read-write task (without return value)
348
    ///
349
    /// # Arguments
350
    ///
351
    /// * `task` - Any type that implements
352
    ///   `MutatingFunctionOnce<T, Result<(), E>>`
353
    #[inline]
354
    pub fn execute_mut<F, E>(self, task: F) -> ExecutionContext<(), E>
1✔
355
    where
1✔
356
        F: MutatingFunctionOnce<T, Result<(), E>> + 'static,
1✔
357
        E: Error + Send + Sync + 'static,
1✔
358
    {
359
        self.call_mut(task)
1✔
360
    }
1✔
361

362
    // ========================================================================
363
    // Internal helper methods
364
    // ========================================================================
365

366
    fn execute_with_read_lock<R, E>(
42✔
367
        mut self,
42✔
368
        task: BoxFunctionOnce<T, Result<R, E>>,
42✔
369
    ) -> ExecutionResult<R, E>
42✔
370
    where
42✔
371
        E: Error + Send + Sync + 'static,
42✔
372
    {
373
        // First check (outside lock)
374
        let tester = self
42✔
375
            .tester
42✔
376
            .take()
42✔
377
            .expect("Tester must be set in Conditioned state");
42✔
378
        if !tester.test() {
42✔
379
            if let Some(ref log_config) = self.logger {
10✔
380
                log::log!(log_config.level, "{}", log_config.message);
1✔
381
            }
9✔
382
            return ExecutionResult::ConditionNotMet;
10✔
383
        }
32✔
384

385
        // Execute prepare action
386
        if let Some(prepare_action) = self.prepare_action.take() {
32✔
387
            if let Err(e) = prepare_action.get() {
3✔
388
                log::error!("Prepare action failed: {}", e);
1✔
389
                return ExecutionResult::Failed(ExecutorError::PrepareFailed(e.to_string()));
1✔
390
            }
2✔
391
        }
29✔
392

393
        // Acquire lock and execute
394
        self.lock.read(|data| {
31✔
395
            // Second check (inside lock)
396
            if !tester.test() {
31✔
397
                if let Some(ref log_config) = self.logger {
1✔
398
                    log::log!(log_config.level, "{}", log_config.message);
×
399
                }
1✔
400
                return ExecutionResult::ConditionNotMet;
1✔
401
            }
30✔
402
            // Execute task
403
            match task.apply(data) {
30✔
404
                Ok(v) => ExecutionResult::Success(v),
19✔
405
                Err(e) => ExecutionResult::Failed(ExecutorError::TaskFailed(e)),
11✔
406
            }
407
        })
31✔
408
    }
42✔
409

410
    fn execute_with_write_lock<R, E>(
15✔
411
        mut self,
15✔
412
        task: BoxMutatingFunctionOnce<T, Result<R, E>>,
15✔
413
    ) -> ExecutionResult<R, E>
15✔
414
    where
15✔
415
        E: Error + Send + Sync + 'static,
15✔
416
    {
417
        // First check (outside lock)
418
        let tester = self
15✔
419
            .tester
15✔
420
            .take()
15✔
421
            .expect("Tester must be set in Conditioned state");
15✔
422
        if !tester.test() {
15✔
423
            if let Some(ref log_config) = self.logger {
1✔
424
                log::log!(log_config.level, "{}", log_config.message);
×
425
            }
1✔
426
            return ExecutionResult::ConditionNotMet;
1✔
427
        }
14✔
428

429
        // Execute prepare action
430
        if let Some(prepare_action) = self.prepare_action.take() {
14✔
431
            if let Err(e) = prepare_action.get() {
2✔
432
                log::error!("Prepare action failed: {}", e);
1✔
433
                return ExecutionResult::Failed(ExecutorError::PrepareFailed(e.to_string()));
1✔
434
            }
1✔
435
        }
12✔
436

437
        // Acquire lock and execute
438
        self.lock.write(|data| {
13✔
439
            // Second check (inside lock)
440
            if !tester.test() {
13✔
441
                if let Some(ref log_config) = self.logger {
1✔
442
                    log::log!(log_config.level, "{}", log_config.message);
×
443
                }
1✔
444
                return ExecutionResult::ConditionNotMet;
1✔
445
            }
12✔
446
            // Execute task
447
            match task.apply(data) {
12✔
448
                Ok(v) => ExecutionResult::Success(v),
7✔
449
                Err(e) => ExecutionResult::Failed(ExecutorError::TaskFailed(e)),
5✔
450
            }
451
        })
13✔
452
    }
15✔
453
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc