Skip to main content

how_far/
try_stages.rs

1//! Running a sequential plan.
2
3use crate::{Child, Execution, Outcome, PhaseSpec, PlanError, Pulse, StopReason};
4use alloc::collections::VecDeque;
5use core::fmt;
6
7/// An operation error, or an error applying the progress plan.
8///
9/// Libraries usually convert it into their own error type with `From`.
10/// Match the variants you handle and keep a wildcard arm.
11#[derive(Debug, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum RunError<E> {
14    /// The operation failed or stopped.
15    Work(E),
16    /// The phase plan could not be applied.
17    Plan(PlanError),
18}
19
20impl<E> From<PlanError> for RunError<E> {
21    fn from(error: PlanError) -> Self {
22        Self::Plan(error)
23    }
24}
25
26impl<E: fmt::Display> fmt::Display for RunError<E> {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            Self::Work(error) => error.fmt(f),
30            Self::Plan(error) => error.fmt(f),
31        }
32    }
33}
34
35impl<E: core::error::Error + 'static> core::error::Error for RunError<E> {
36    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
37        match self {
38            Self::Work(error) => Some(error),
39            Self::Plan(error) => Some(error),
40        }
41    }
42}
43
44impl<E> RunError<RunError<E>> {
45    fn flatten(self) -> RunError<E> {
46        match self {
47            Self::Work(error) => error,
48            Self::Plan(error) => RunError::Plan(error),
49        }
50    }
51}
52
53/// Run a sequential plan one stage at a time.
54///
55/// [`TryStages::new`] splits a pulse into the declared stages. Each `run` method
56/// hands its closure the next stage as `&dyn Pulse`, then finishes that stage:
57/// `Succeeded` if the closure returned `Ok`, otherwise the outcome its error
58/// maps to. After an error, every later stage is finished as `NotRun`, and
59/// the original error is returned unchanged. If the closure panics, the stage
60/// and every later one are recorded as abandoned when `TryStages` is dropped. A
61/// caller that catches the panic and calls a `run` method again gets
62/// `PlanError::Finished`: that stage is recorded as abandoned and the rest as
63/// `NotRun`.
64///
65/// `TryStages` never finishes the pulse it was given. That pulse belongs to the
66/// caller, which finishes it: an application finishes the root, and an outer
67/// `TryStages` finishes the stage it handed to this code. That is what lets one
68/// library call another inside a stage.
69///
70/// ```
71/// use how_far::{PhaseSpec, ProgressExt, Pulse, RunError, TryStages, StopReason, Total};
72///
73/// /// A library that a caller can run inside one of its own stages.
74/// fn resize(rows: u64, pulse: &dyn Pulse) -> Result<(), RunError<StopReason>> {
75///     let mut stages = TryStages::new(pulse, &[
76///         PhaseSpec::new("horizontal", 1, Total::Exact(rows)),
77///         PhaseSpec::new("vertical", 1, Total::Exact(rows)),
78///     ])?;
79///     for _ in 0..2 {
80///         stages.run_stoppable(|stage| {
81///             for _ in 0..rows {
82///                 stage.step(1)?;
83///             }
84///             Ok(())
85///         })?;
86///     }
87///     stages.finish()?;
88///     Ok(())
89/// }
90///
91/// fn thumbnail(pulse: &dyn Pulse) -> Result<(), RunError<StopReason>> {
92///     let mut stages = TryStages::new(pulse, &[
93///         PhaseSpec::new("decode", 1, Total::Exact(1)),
94///         PhaseSpec::new("resize", 4, Total::Unknown),
95///     ])?;
96///     stages.run_stoppable(|stage| stage.step(1))?;
97///     // `resize` splits this stage; `TryStages` finishes it afterwards.
98///     stages.run_nested(|_| true, |stage| resize(8, stage))?;
99///     stages.finish()?;
100///     Ok(())
101/// }
102///
103/// thumbnail(&how_far::NoPulse)?;
104/// # Ok::<(), RunError<StopReason>>(())
105/// ```
106///
107/// Workers that count one logical stage can share its `&dyn Pulse`; join them
108/// before the closure returns. When workers need their own totals or outcomes,
109/// split the stage and use [`run_nested`](Self::run_nested).
110#[must_use = "run and finish the stages; dropping unfinished stages abandons them"]
111pub struct TryStages<'a> {
112    /// The stages that have not finished, in declared order.
113    pending: VecDeque<Child<'a>>,
114    state: State,
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118enum State {
119    Ready,
120    /// The front stage was handed to work that has not returned.
121    Running,
122    Stopped,
123}
124
125impl fmt::Debug for TryStages<'_> {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        f.debug_struct("TryStages")
128            .field("remaining", &self.pending.len())
129            .field("state", &self.state)
130            .finish()
131    }
132}
133
134// Each `run` method is instantiated once per closure, in the caller's crate,
135// so it only fetches the stage, calls the closure and hands the result on.
136// The bookkeeping is in `next_stage` and `end_stage`, which are compiled once
137// here, and in `complete`, which is shared by calls with the same types.
138impl<'a> TryStages<'a> {
139    /// Split `parent` into sequential stages, before any of their work starts.
140    pub fn new(parent: &'a dyn Pulse, parts: &[PhaseSpec<'_>]) -> Result<Self, PlanError> {
141        Ok(Self {
142            pending: parent.split(Execution::Sequence, parts)?.into(),
143            state: State::Ready,
144        })
145    }
146
147    /// Discharge an intentionally unnecessary stage, without running it.
148    pub fn skip(&mut self) -> Result<(), PlanError> {
149        self.next_stage()?;
150        self.end_stage(Outcome::Skipped)
151    }
152
153    /// Run the next stage. Any error from `work` marks it `Failed`.
154    pub fn run<T, E>(
155        &mut self,
156        work: impl FnOnce(&dyn Pulse) -> Result<T, E>,
157    ) -> Result<T, RunError<E>> {
158        let result = match self.next_stage() {
159            Ok(stage) => work(stage),
160            Err(error) => return Err(RunError::Plan(error)),
161        };
162        self.complete(result, Outcome::Failed)
163    }
164
165    /// Run the next stage whose only errors are stop requests. Any error from
166    /// `work` marks it `Cancelled`. Use [`Self::run_classified`] for operations
167    /// that can also fail for other reasons.
168    ///
169    /// Ordinary work errors cannot be classified as cancellation by this helper:
170    ///
171    /// ```compile_fail,E0308
172    /// use how_far::{NoPulse, PhaseSpec, TryStages, Total};
173    /// let mut stages = TryStages::new(&NoPulse, &[
174    ///     PhaseSpec::new("decode", 1, Total::Unknown),
175    /// ]).unwrap();
176    /// stages.run_stoppable(|_| Err::<(), &str>("invalid image")).unwrap();
177    /// ```
178    pub fn run_stoppable<T>(
179        &mut self,
180        work: impl FnOnce(&dyn Pulse) -> Result<T, StopReason>,
181    ) -> Result<T, RunError<StopReason>> {
182        let result = match self.next_stage() {
183            Ok(stage) => work(stage),
184            Err(error) => return Err(RunError::Plan(error)),
185        };
186        self.complete(result, Outcome::Cancelled)
187    }
188
189    /// Run the next stage, marking it `Cancelled` for errors `is_stop`
190    /// accepts and `Failed` for any other error. `is_stop` runs only on error.
191    ///
192    /// Use this when `work` calls another library whose error can mean a
193    /// cancellation or timeout as well as a real failure.
194    pub fn run_classified<T, E>(
195        &mut self,
196        is_stop: impl FnOnce(&E) -> bool,
197        work: impl FnOnce(&dyn Pulse) -> Result<T, E>,
198    ) -> Result<T, RunError<E>> {
199        let result = match self.next_stage() {
200            Ok(stage) => work(stage),
201            Err(error) => return Err(RunError::Plan(error)),
202        };
203        let on_error = match &result {
204            Err(error) if is_stop(error) => Outcome::Cancelled,
205            _ => Outcome::Failed,
206        };
207        self.complete(result, on_error)
208    }
209
210    /// Run the next stage when `work` plans it, so `?` works on both its plan
211    /// errors and its work errors. Work errors that `is_stop` accepts mark the
212    /// stage `Cancelled`; other work errors and plan errors mark it `Failed`.
213    /// The returned error is flat.
214    ///
215    /// `work` finishes the children it splits; `TryStages` finishes the stage.
216    pub fn run_nested<T, E>(
217        &mut self,
218        is_stop: impl FnOnce(&E) -> bool,
219        work: impl FnOnce(&dyn Pulse) -> Result<T, RunError<E>>,
220    ) -> Result<T, RunError<E>> {
221        let result = match self.next_stage() {
222            Ok(stage) => work(stage),
223            Err(error) => return Err(RunError::Plan(error)),
224        };
225        let on_error = match &result {
226            Err(RunError::Work(error)) if is_stop(error) => Outcome::Cancelled,
227            _ => Outcome::Failed,
228        };
229        match self.complete(result, on_error) {
230            Ok(value) => Ok(value),
231            Err(error) => Err(error.flatten()),
232        }
233    }
234
235    /// Finish the current stage as `Succeeded`, or as `on_error` if `result`
236    /// is an error. A stage that cannot record an error outcome is recorded
237    /// as abandoned, and the work error stays the one the caller sees.
238    #[inline(never)]
239    fn complete<T, E>(
240        &mut self,
241        result: Result<T, E>,
242        on_error: Outcome,
243    ) -> Result<T, RunError<E>> {
244        match result {
245            Ok(value) => match self.end_stage(Outcome::Succeeded) {
246                Ok(()) => Ok(value),
247                Err(error) => Err(RunError::Plan(error)),
248            },
249            Err(error) => {
250                let _ = self.end_stage(on_error);
251                Err(RunError::Work(error))
252            }
253        }
254    }
255
256    fn next_stage(&mut self) -> Result<&dyn Pulse, PlanError> {
257        if self.state == State::Running {
258            // The last stage's work panicked and the caller caught the panic.
259            let _ = self.end_stage(Outcome::Abandoned);
260        }
261        match self.pending.front() {
262            _ if self.state == State::Stopped => Err(PlanError::Finished),
263            Some(stage) => {
264                stage.start()?;
265                self.state = State::Running;
266                Ok(stage.pulse())
267            }
268            None => Err(PlanError::NoMoreStages),
269        }
270    }
271
272    /// Finish the current stage. After anything but success, finish the rest
273    /// as `NotRun`.
274    #[inline(never)]
275    fn end_stage(&mut self, outcome: Outcome) -> Result<(), PlanError> {
276        self.state = State::Ready;
277        let Some(stage) = self.pending.pop_front() else {
278            return Err(PlanError::NoMoreStages);
279        };
280        let finished = stage.finish(outcome);
281        if finished.is_err() || !matches!(outcome, Outcome::Succeeded | Outcome::Skipped) {
282            self.state = State::Stopped;
283            while let Some(stage) = self.pending.pop_front() {
284                let _ = stage.finish(Outcome::NotRun);
285            }
286        }
287        finished
288    }
289
290    /// Confirm that every declared stage ran. Remaining stages are recorded as
291    /// abandoned. This does not finish the parent pulse.
292    pub fn finish(self) -> Result<(), PlanError> {
293        if self.state == State::Stopped {
294            Err(PlanError::Finished)
295        } else if !self.pending.is_empty() {
296            Err(PlanError::UnfinishedChildren)
297        } else {
298            Ok(())
299        }
300    }
301}