Skip to main content

how_far_along/
pulse.rs

1//! One lifecycle owner and shareable, non-owning pulse views.
2use crate::{
3    Child, ChildPulse, Execution, NodeId, Observer, Outcome, Phase, PhaseSpec, PlanError, Pulse,
4    Report, Reporter, SharedPulse, Stop, StopReason, Total, sync::OwnerCell,
5};
6use alloc::{boxed::Box, sync::Arc, vec::Vec};
7use core::{
8    fmt,
9    sync::atomic::{AtomicBool, AtomicU8, Ordering},
10};
11
12/// Owns the root of a tracked operation. Workers borrow it or use `share()`.
13/// Finish after joining workers. Dropping this owner freezes abandonment even
14/// when non-owning shared views remain alive.
15#[must_use = "finish the root after its work joins; dropping it unfinished records Abandoned"]
16pub struct PulseTree {
17    node: TreePulse<Arc<dyn Stop>>,
18}
19impl PulseTree {
20    /// Track a phase with a shared cancellation policy.
21    pub fn new(phase: Phase, stop: impl Stop + 'static) -> Self {
22        Self {
23            node: TreePulse::new(phase, Arc::new(stop) as Arc<dyn Stop>),
24        }
25    }
26    /// Observe the root, including after its owner finishes.
27    pub fn observer(&self) -> Observer {
28        self.node.observer()
29    }
30    /// The root's stable identity.
31    pub fn id(&self) -> NodeId {
32        self.node.state.observer.id()
33    }
34    /// Finish the root after workers and children join. On error it is abandoned.
35    pub fn finish(self, outcome: Outcome) -> Result<(), PlanError> {
36        self.node.finish(outcome)
37    }
38}
39impl fmt::Debug for PulseTree {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        f.debug_struct("PulseTree")
42            .field("root", &self.id())
43            .finish()
44    }
45}
46const UNPLANNED: u8 = 0;
47const COUNTING: u8 = 1;
48const SPLIT: u8 = 2;
49const FINISHED: u8 = 3;
50
51/// Checkpoint behavior is selected by the owner type, not by a Cargo feature
52/// or a per-node Option. Cloning a policy carries it into children and views.
53pub(crate) trait Policy: Clone + Send + Sync + 'static {
54    #[track_caller]
55    fn check(&self, phase: &Observer) -> Result<(), StopReason>;
56    fn may_stop(&self) -> bool;
57}
58impl Policy for Arc<dyn Stop> {
59    #[track_caller]
60    fn check(&self, _phase: &Observer) -> Result<(), StopReason> {
61        Stop::check(self)
62    }
63    fn may_stop(&self) -> bool {
64        Stop::may_stop(self)
65    }
66}
67
68// This state is shared. The TreePulse token, not the last Arc, owns completion.
69struct State<S: Policy> {
70    owner: OwnerCell<Phase>,
71    reporter: Reporter,
72    observer: Observer,
73    stop: S,
74    activity: AtomicU8,
75    closed: AtomicBool,
76}
77pub(crate) struct TreePulse<S: Policy> {
78    state: Arc<State<S>>,
79}
80#[derive(Clone)]
81struct View<S: Policy> {
82    state: Arc<State<S>>,
83}
84impl<S: Policy> TreePulse<S> {
85    pub(crate) fn observer(&self) -> Observer {
86        self.state.observer.clone()
87    }
88    pub(crate) fn finish(self, outcome: Outcome) -> Result<(), PlanError> {
89        self.state.finish(outcome)
90    }
91    pub(crate) fn new(phase: Phase, stop: S) -> Self {
92        Self {
93            state: Arc::new(State {
94                reporter: phase.deferred_reporter(),
95                observer: phase.observer(),
96                owner: OwnerCell::new(phase),
97                stop,
98                activity: AtomicU8::new(UNPLANNED),
99                closed: AtomicBool::new(false),
100            }),
101        }
102    }
103}
104impl<S: Policy> State<S> {
105    fn complete(&self, outcome: Outcome) -> Result<(), PlanError> {
106        self.with_owner(|phase| phase.record_outcome(outcome))??;
107        self.activity.store(FINISHED, Ordering::Release);
108        Ok(())
109    }
110    fn start(&self) -> Result<(), PlanError> {
111        self.with_owner(Phase::start)?
112    }
113    fn set_total(&self, total: Total) -> Result<(), PlanError> {
114        self.with_owner(|p| p.set_total(total))?
115    }
116    fn share(self: &Arc<Self>) -> Result<SharedPulse, PlanError> {
117        Ok(SharedPulse::new(View {
118            state: Arc::clone(self),
119        }))
120    }
121
122    /// Administer the phase. Concurrent administrators, including shared
123    /// views, run one at a time; a closed owner reports `Finished`.
124    fn with_owner<R>(&self, f: impl FnOnce(&mut Phase) -> R) -> Result<R, PlanError> {
125        if self.closed.load(Ordering::Acquire) {
126            return Err(PlanError::Finished);
127        }
128        self.owner.with(f).ok_or(PlanError::Finished)
129    }
130    fn close(&self) {
131        self.closed.store(true, Ordering::Release);
132        self.activity.store(FINISHED, Ordering::Release);
133        // Waits only for an administrative call already under way; an
134        // unfinished phase records Abandoned as it drops, outside the lock.
135        drop(self.owner.take());
136    }
137    fn finish(&self, outcome: Outcome) -> Result<(), PlanError> {
138        let result = self.with_owner(|phase| phase.finish_with(outcome))?;
139        if result.is_ok() {
140            self.activity.store(FINISHED, Ordering::Release);
141        }
142        result
143    }
144    fn split(
145        &self,
146        execution: Execution,
147        parts: &[PhaseSpec<'_>],
148    ) -> Result<Vec<Child<'static>>, PlanError> {
149        PhaseSpec::validate_split(parts)?;
150        self.activity
151            .compare_exchange(UNPLANNED, SPLIT, Ordering::AcqRel, Ordering::Acquire)
152            .map_err(|s| {
153                if s == FINISHED {
154                    PlanError::Finished
155                } else {
156                    PlanError::AlreadyInUse
157                }
158            })?;
159        let mut children = Vec::with_capacity(parts.len());
160        let split = self.with_owner(|phase| {
161            phase.split_each(execution, parts, &mut |phase| {
162                children.push(Child::new(TreePulse::new(phase, self.stop.clone())));
163            })
164        });
165        match split {
166            Ok(Ok(())) => Ok(children),
167            Ok(Err(error)) | Err(error) => {
168                // A concurrent owner drop must never resurrect planning.
169                let _ = self.activity.compare_exchange(
170                    SPLIT,
171                    UNPLANNED,
172                    Ordering::AcqRel,
173                    Ordering::Acquire,
174                );
175                Err(error)
176            }
177        }
178    }
179}
180impl<S: Policy> Stop for State<S> {
181    #[track_caller]
182    fn check(&self) -> Result<(), StopReason> {
183        self.stop.check(&self.observer)
184    }
185    fn may_stop(&self) -> bool {
186        self.stop.may_stop()
187    }
188}
189impl<S: Policy> Report for State<S> {
190    #[track_caller]
191    fn advance(&self, n: u64) {
192        if n == 0 {
193            return;
194        }
195        // During a split the reporter decides: it ignores a phase that became
196        // a branch, and counts work for one whose split failed.
197        if self.activity.load(Ordering::Acquire) == COUNTING
198            || matches!(
199                self.activity.compare_exchange(
200                    UNPLANNED,
201                    COUNTING,
202                    Ordering::AcqRel,
203                    Ordering::Acquire
204                ),
205                Ok(_) | Err(COUNTING) | Err(SPLIT)
206            )
207        {
208            self.reporter.advance(n);
209        }
210    }
211    fn may_report(&self) -> bool {
212        self.activity.load(Ordering::Relaxed) != FINISHED && self.reporter.may_report()
213    }
214}
215impl<S: Policy> Drop for TreePulse<S> {
216    fn drop(&mut self) {
217        self.state.close();
218    }
219}
220impl<S: Policy> ChildPulse for TreePulse<S> {
221    fn complete_inferred(self: Box<Self>, outcome: Outcome) {
222        let _ = self
223            .state
224            .with_owner(|phase| phase.complete_inferred(outcome));
225    }
226    fn complete_as(self: Box<Self>, outcome: Outcome) {
227        let _ = self.state.complete(outcome);
228    }
229    fn finish(self: Box<Self>, outcome: Outcome) -> Result<(), PlanError> {
230        self.state.finish(outcome)
231    }
232}
233
234impl<S: Policy> how_far::Complete for TreePulse<S> {
235    fn complete_as(self, outcome: Outcome) {
236        let _ = self.state.complete(outcome);
237    }
238}
239
240impl how_far::Complete for PulseTree {
241    fn complete_as(self, outcome: Outcome) {
242        let _ = self.node.state.complete(outcome);
243    }
244}
245/// Forward the pulse traits to `self.$field`, a [`State`] or a pulse.
246macro_rules! pulse_view {
247    ([$($generic:ident : $bound:path)?] $ty:ty, $field:ident $(, #[$hint:meta])?) => {
248        impl$(<$generic: $bound>)? Stop for $ty {
249            $(#[$hint])?
250            #[track_caller]
251            fn check(&self) -> Result<(), StopReason> {
252                self.$field.check()
253            }
254            fn may_stop(&self) -> bool {
255                self.$field.may_stop()
256            }
257        }
258        impl$(<$generic: $bound>)? Report for $ty {
259            $(#[$hint])?
260            #[track_caller]
261            fn advance(&self, n: u64) {
262                self.$field.advance(n)
263            }
264            fn may_report(&self) -> bool {
265                self.$field.may_report()
266            }
267        }
268        impl$(<$generic: $bound>)? Pulse for $ty {
269            fn split(
270                &self,
271                e: Execution,
272                parts: &[PhaseSpec<'_>],
273            ) -> Result<Vec<Child<'_>>, PlanError> {
274                self.$field.split(e, parts)
275            }
276            fn start(&self) -> Result<(), PlanError> {
277                self.$field.start()
278            }
279            fn set_total(&self, total: Total) -> Result<(), PlanError> {
280                self.$field.set_total(total)
281            }
282            fn share(&self) -> Result<SharedPulse, PlanError> {
283                self.$field.share()
284            }
285        }
286    };
287}
288pulse_view!([S: Policy] TreePulse<S>, state, #[inline]);
289pulse_view!([S: Policy] View<S>, state, #[inline]);
290pulse_view!([] PulseTree, node);