Skip to main content

qubit_progress/running/
running_progress_guard.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use std::{
11    panic::resume_unwind,
12    thread::ScopedJoinHandle,
13};
14
15use super::{
16    running_progress_notifier::RunningProgressNotifier,
17    running_progress_point_handle::RunningProgressPointHandle,
18};
19
20/// Owns a scoped running progress reporter thread.
21///
22/// `RunningProgressGuard` is created by
23/// [`Progress::spawn_running_reporter`](crate::Progress::spawn_running_reporter).
24/// Keep this guard on the coordinating thread, pass
25/// [`RunningProgressPointHandle`] clones to workers, and call
26/// [`Self::stop_and_join`] after worker execution completes.
27///
28/// # Examples
29///
30/// ```
31/// use std::{
32///     sync::{
33///         Arc,
34///         atomic::{
35///             AtomicUsize,
36///             Ordering,
37///         },
38///     },
39///     thread,
40///     time::Duration,
41/// };
42///
43/// use qubit_progress::{
44///     NoOpProgressReporter,
45///     Progress,
46///     ProgressCounter,
47///     ProgressSchema,
48/// };
49///
50/// let reporter = NoOpProgressReporter;
51/// let completed = Arc::new(AtomicUsize::new(0));
52///
53/// thread::scope(|scope| {
54///     let loop_completed = Arc::clone(&completed);
55///     let progress = Progress::new(
56///         &reporter,
57///         Duration::ZERO,
58///         ProgressSchema::single("entries", "Entries"),
59///     );
60///     let running_progress = progress.spawn_running_reporter(scope, move || {
61///         vec![ProgressCounter::new("entries")
62///             .total(3)
63///             .completed(loop_completed.load(Ordering::Acquire) as u64)]
64///     });
65///     let progress_point_handle = running_progress.point_handle();
66///
67///     let mut handles = Vec::new();
68///     for _ in 0..3 {
69///         let c = Arc::clone(&completed);
70///         let p = progress_point_handle.clone();
71///         handles.push(scope.spawn(move || {
72///             c.fetch_add(1, Ordering::AcqRel);
73///             assert!(p.report());
74///         }));
75///     }
76///     for h in handles {
77///         h.join().unwrap();
78///     }
79///
80///     running_progress.stop_and_join();
81/// });
82/// ```
83///
84/// # Author
85///
86/// Haixing Hu
87pub struct RunningProgressGuard<'scope> {
88    /// Notifier used to stop the reporter thread.
89    notifier: RunningProgressNotifier,
90    /// Scoped reporter thread handle.
91    progress_thread: ScopedJoinHandle<'scope, ()>,
92    /// Whether worker point notifications should wake the reporter loop.
93    report_points: bool,
94}
95
96impl<'scope> RunningProgressGuard<'scope> {
97    /// Creates a scoped running progress guard.
98    ///
99    /// # Parameters
100    ///
101    /// * `notifier` - Notifier used to stop the reporter thread.
102    /// * `progress_thread` - Scoped reporter thread handle.
103    /// * `report_points` - Whether worker point notifications wake the loop.
104    ///
105    /// # Returns
106    ///
107    /// A guard owning the reporter thread lifecycle.
108    #[inline]
109    pub(crate) const fn new(
110        notifier: RunningProgressNotifier,
111        progress_thread: ScopedJoinHandle<'scope, ()>,
112        report_points: bool,
113    ) -> Self {
114        Self {
115            notifier,
116            progress_thread,
117            report_points,
118        }
119    }
120
121    /// Returns a worker-side running point handle.
122    ///
123    /// # Returns
124    ///
125    /// A cloneable handle that wakes the reporter loop for zero intervals and
126    /// becomes a no-op for positive intervals.
127    #[inline]
128    pub fn point_handle(&self) -> RunningProgressPointHandle {
129        RunningProgressPointHandle::new(self.report_points.then(|| self.notifier.clone()))
130    }
131
132    /// Stops the reporter loop and joins the scoped reporter thread.
133    ///
134    /// # Panics
135    ///
136    /// Propagates any panic raised by the reporter thread.
137    #[inline]
138    pub fn stop_and_join(self) {
139        self.notifier.stop();
140        if let Err(payload) = self.progress_thread.join() {
141            resume_unwind(payload);
142        }
143    }
144}