Skip to main content

how_far/
stop_only.rs

1//! A pulse made from a cancellation policy alone.
2use crate::{Child, Execution, PhaseSpec, PlanError, Pulse, Report, SharedPulse, Total};
3use alloc::{sync::Arc, vec::Vec};
4use core::fmt;
5use enough::{Stop, StopReason};
6
7/// A stop policy held by value or by reference. Only an owned one can be shared.
8#[derive(Clone)]
9pub(crate) enum StopSource<'a> {
10    Borrowed(&'a dyn Stop),
11    Owned(Arc<dyn Stop>),
12}
13impl StopSource<'_> {
14    pub(crate) fn get(&self) -> &dyn Stop {
15        match self {
16            Self::Borrowed(stop) => *stop,
17            Self::Owned(stop) => stop.as_ref(),
18        }
19    }
20    pub(crate) fn owned(&self) -> Result<Arc<dyn Stop>, PlanError> {
21        match self {
22            Self::Borrowed(_) => Err(PlanError::NotShareable),
23            Self::Owned(stop) => Ok(stop.clone()),
24        }
25    }
26}
27
28/// Cancellation without progress: a pulse made from any [`Stop`].
29///
30/// For an application that holds a cancellation token, such as a `Stopper` or
31/// a deadline, and calls a library that takes `&dyn Pulse`. Every phase the
32/// library plans checks the same stop; reports go nowhere, and plans are only
33/// validated. An owned stop can be [shared](Pulse::share) with spawned work; a
34/// borrowed one serves scoped work and refuses to be shared.
35///
36/// ```
37/// use how_far::{prelude::*, PhaseSpec, Stages, StopOnly, StopReason, Total};
38///
39/// // A library that knows nothing about the caller's token.
40/// fn encode(rows: u64, pulse: &dyn Pulse) -> Result<(), StopReason> {
41///     Stages::new(pulse, &[PhaseSpec::new("rows", 1, Total::Exact(rows))])
42///         .complete_with(|stages| stages.run(|stage| {
43///             for _ in 0..rows {
44///                 stage.step(1)?;
45///             }
46///             Ok(())
47///         }))
48/// }
49///
50/// struct Cancelled;
51/// impl Stop for Cancelled {
52///     fn check(&self) -> Result<(), StopReason> {
53///         Err(StopReason::Cancelled)
54///     }
55/// }
56/// assert_eq!(encode(8, &StopOnly::new(Cancelled)), Err(StopReason::Cancelled));
57/// assert_eq!(encode(8, &StopOnly::borrowed(&how_far::Unstoppable)), Ok(()));
58/// ```
59pub struct StopOnly<'a> {
60    stop: StopSource<'a>,
61}
62
63impl StopOnly<'static> {
64    /// Own `stop`; the pulse and its shared views can outlive the caller.
65    pub fn new(stop: impl Stop + 'static) -> Self {
66        Self {
67            stop: StopSource::Owned(Arc::new(stop)),
68        }
69    }
70}
71
72impl<'a> StopOnly<'a> {
73    /// Borrow `stop`, without allocating. [`Pulse::share`] then fails with
74    /// [`PlanError::NotShareable`], since the view could outlive the borrow.
75    pub fn borrowed(stop: &'a dyn Stop) -> Self {
76        Self {
77            stop: StopSource::Borrowed(stop),
78        }
79    }
80}
81
82impl fmt::Debug for StopOnly<'_> {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        f.debug_struct("StopOnly")
85            .field("owned", &matches!(self.stop, StopSource::Owned(_)))
86            .finish()
87    }
88}
89
90impl Stop for StopOnly<'_> {
91    #[track_caller]
92    fn check(&self) -> Result<(), StopReason> {
93        self.stop.get().check()
94    }
95    fn may_stop(&self) -> bool {
96        self.stop.get().may_stop()
97    }
98}
99
100impl Report for StopOnly<'_> {
101    fn advance(&self, _: u64) {}
102    fn may_report(&self) -> bool {
103        false
104    }
105}
106
107impl Pulse for StopOnly<'_> {
108    fn split(&self, _: Execution, parts: &[PhaseSpec<'_>]) -> Result<Vec<Child<'_>>, PlanError> {
109        PhaseSpec::validate_split(parts)?;
110        // Untracked children check this token at every level and share
111        // through it.
112        Ok(crate::pulse::untracked_children(self, parts.len()))
113    }
114    /// Nobody observes the total, so a revision is accepted and discarded.
115    fn set_total(&self, _: Total) -> Result<(), PlanError> {
116        Ok(())
117    }
118    fn share(&self) -> Result<SharedPulse, PlanError> {
119        Ok(SharedPulse::new(StopOnly {
120            stop: StopSource::Owned(self.stop.owned()?),
121        }))
122    }
123}