Skip to main content

TryStages

Struct TryStages 

Source
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>

Source

pub fn new( parent: &'a dyn Pulse, parts: &[PhaseSpec<'_>], ) -> Result<Self, PlanError>

Split parent into sequential stages, before any of their work starts.

Source

pub fn skip(&mut self) -> Result<(), PlanError>

Discharge an intentionally unnecessary stage, without running it.

Source

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.

Source

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();
Source

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.

Source

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.

Source

pub fn finish(self) -> Result<(), PlanError>

Confirm that every declared stage ran. Remaining stages are recorded as abandoned. This does not finish the parent pulse.

Trait Implementations§

Source§

impl Debug for TryStages<'_>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<'a> !RefUnwindSafe for TryStages<'a>

§

impl<'a> !UnwindSafe for TryStages<'a>

§

impl<'a> Freeze for TryStages<'a>

§

impl<'a> Send for TryStages<'a>

§

impl<'a> Sync for TryStages<'a>

§

impl<'a> Unpin for TryStages<'a>

§

impl<'a> UnsafeUnpin for TryStages<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.