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}