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}