pub struct TryStages<'a> { /* private fields */ }Expand description
Run a sequential plan one stage at a time.
TryStages::new splits a pulse into the declared stages. Each run method
hands its closure the next stage as &dyn Pulse, then finishes that stage:
Succeeded if the closure returned Ok, otherwise the outcome its error
maps to. After an error, every later stage is finished as NotRun, and
the original error is returned unchanged. If the closure panics, the stage
and every later one are recorded as abandoned when TryStages is dropped. A
caller that catches the panic and calls a run method again gets
PlanError::Finished: that stage is recorded as abandoned and the rest as
NotRun.
TryStages never finishes the pulse it was given. That pulse belongs to the
caller, which finishes it: an application finishes the root, and an outer
TryStages finishes the stage it handed to this code. That is what lets one
library call another inside a stage.
use how_far::{PhaseSpec, ProgressExt, Pulse, RunError, TryStages, StopReason, Total};
/// A library that a caller can run inside one of its own stages.
fn resize(rows: u64, pulse: &dyn Pulse) -> Result<(), RunError<StopReason>> {
let mut stages = TryStages::new(pulse, &[
PhaseSpec::new("horizontal", 1, Total::Exact(rows)),
PhaseSpec::new("vertical", 1, Total::Exact(rows)),
])?;
for _ in 0..2 {
stages.run_stoppable(|stage| {
for _ in 0..rows {
stage.step(1)?;
}
Ok(())
})?;
}
stages.finish()?;
Ok(())
}
fn thumbnail(pulse: &dyn Pulse) -> Result<(), RunError<StopReason>> {
let mut stages = TryStages::new(pulse, &[
PhaseSpec::new("decode", 1, Total::Exact(1)),
PhaseSpec::new("resize", 4, Total::Unknown),
])?;
stages.run_stoppable(|stage| stage.step(1))?;
// `resize` splits this stage; `TryStages` finishes it afterwards.
stages.run_nested(|_| true, |stage| resize(8, stage))?;
stages.finish()?;
Ok(())
}
thumbnail(&how_far::NoPulse)?;Workers that count one logical stage can share its &dyn Pulse; join them
before the closure returns. When workers need their own totals or outcomes,
split the stage and use run_nested.
Implementations§
Source§impl<'a> TryStages<'a>
impl<'a> TryStages<'a>
Sourcepub fn new(
parent: &'a dyn Pulse,
parts: &[PhaseSpec<'_>],
) -> Result<Self, PlanError>
pub fn new( parent: &'a dyn Pulse, parts: &[PhaseSpec<'_>], ) -> Result<Self, PlanError>
Split parent into sequential stages, before any of their work starts.
Sourcepub fn skip(&mut self) -> Result<(), PlanError>
pub fn skip(&mut self) -> Result<(), PlanError>
Discharge an intentionally unnecessary stage, without running it.
Sourcepub fn run<T, E>(
&mut self,
work: impl FnOnce(&dyn Pulse) -> Result<T, E>,
) -> Result<T, RunError<E>>
pub fn run<T, E>( &mut self, work: impl FnOnce(&dyn Pulse) -> Result<T, E>, ) -> Result<T, RunError<E>>
Run the next stage. Any error from work marks it Failed.
Sourcepub fn run_stoppable<T>(
&mut self,
work: impl FnOnce(&dyn Pulse) -> Result<T, StopReason>,
) -> Result<T, RunError<StopReason>>
pub fn run_stoppable<T>( &mut self, work: impl FnOnce(&dyn Pulse) -> Result<T, StopReason>, ) -> Result<T, RunError<StopReason>>
Run the next stage whose only errors are stop requests. Any error from
work marks it Cancelled. Use Self::run_classified for operations
that can also fail for other reasons.
Ordinary work errors cannot be classified as cancellation by this helper:
use how_far::{NoPulse, PhaseSpec, TryStages, Total};
let mut stages = TryStages::new(&NoPulse, &[
PhaseSpec::new("decode", 1, Total::Unknown),
]).unwrap();
stages.run_stoppable(|_| Err::<(), &str>("invalid image")).unwrap();Sourcepub fn run_classified<T, E>(
&mut self,
is_stop: impl FnOnce(&E) -> bool,
work: impl FnOnce(&dyn Pulse) -> Result<T, E>,
) -> Result<T, RunError<E>>
pub fn run_classified<T, E>( &mut self, is_stop: impl FnOnce(&E) -> bool, work: impl FnOnce(&dyn Pulse) -> Result<T, E>, ) -> Result<T, RunError<E>>
Run the next stage, marking it Cancelled for errors is_stop
accepts and Failed for any other error. is_stop runs only on error.
Use this when work calls another library whose error can mean a
cancellation or timeout as well as a real failure.
Sourcepub fn run_nested<T, E>(
&mut self,
is_stop: impl FnOnce(&E) -> bool,
work: impl FnOnce(&dyn Pulse) -> Result<T, RunError<E>>,
) -> Result<T, RunError<E>>
pub fn run_nested<T, E>( &mut self, is_stop: impl FnOnce(&E) -> bool, work: impl FnOnce(&dyn Pulse) -> Result<T, RunError<E>>, ) -> Result<T, RunError<E>>
Run the next stage when work plans it, so ? works on both its plan
errors and its work errors. Work errors that is_stop accepts mark the
stage Cancelled; other work errors and plan errors mark it Failed.
The returned error is flat.
work finishes the children it splits; TryStages finishes the stage.