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}