Enum OperationError

Source
pub enum OperationError {
    Transient {
        err: Error,
        retry_strategy: RetryStrategy,
        fatal_strategy: FatalStrategy,
    },
    Fatal {
        err: Error,
        strategy: FatalStrategy,
    },
}
Expand description

Operation error types.

Given the distributed nature of the Paladin system, it is important to be able to specify how the system should respond to various flavors of errors. This type provides the error types and strategies for handling errors.

Paladin recognizes two types of errors: transient and fatal. Transient errors are those that are expected to be resolved by retrying the operation. For example, a transient error might be a network timeout or a database deadlock. Fatal errors are those that are not expected to be resolved by retrying the operation. For example, a fatal error might be a malformed request or a database connection error.

§Example

§An Operation failing with a fatal error:

use paladin::{
    RemoteExecute,
    runtime::Runtime,
    operation::{Operation, Result, FatalError, FatalStrategy},
    directive::{Directive, IndexedStream},
};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, RemoteExecute)]
struct WillFail;

impl Operation for WillFail {
    type Input = i64;
    type Output = i64;
     
    fn execute(&self, _: Self::Input) -> Result<Self::Output> {
        FatalError::from_str(
            "This operation will always fail.",
            FatalStrategy::default()
        )
        .into()
    }
}


#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let computation = IndexedStream::from([1, 2, 3]).map(&WillFail);
    let result = computation
        .run(&runtime).await?
        .into_values_sorted().await
        .map(|values| values.into_iter().collect::<Vec<_>>());

    assert_eq!(result.unwrap_err().to_string(), "Fatal operation error: This operation will always fail.");
}

§A Monoid failing with a fatal error:

use paladin::{
    RemoteExecute,
    runtime::Runtime,
    operation::{Operation, Monoid, Result, FatalError, FatalStrategy},
    directive::{Directive, IndexedStream},
};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, RemoteExecute)]
struct WillFail;

impl Monoid for WillFail {
    type Elem = i64;

    fn empty(&self) -> Self::Elem {
        0
    }

    fn combine(&self, _: Self::Elem, _: Self::Elem) -> Result<Self::Elem> {
        FatalError::from_str(
            "This operation will always fail.",
            FatalStrategy::default()
        )
        .into()
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let computation = IndexedStream::from([1, 2, 3]).fold(&WillFail);
    let result = computation.run(&runtime).await;

    assert_eq!(result.unwrap_err().to_string(), "Fatal operation error: This operation will always fail.");
}

§An IndexedStream containing errors:

use paladin::{
    RemoteExecute,
    runtime::Runtime,
    operation::{Operation, Monoid, Result},
    directive::{Directive, IndexedStream, indexed_stream::try_from_into_iterator},
};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, RemoteExecute)]
struct Multiply;

impl Monoid for Multiply {
    type Elem = i64;

    fn empty(&self) -> Self::Elem {
        0
    }

    fn combine(&self, a: Self::Elem, b: Self::Elem) -> Result<Self::Elem> {
        Ok(a * b)
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let computation = try_from_into_iterator([
        Ok(1), Err(anyhow::anyhow!("Failure")), Ok(3)
    ])
    .fold(&Multiply);
    let result = computation.run(&runtime).await;

    assert_eq!(result.unwrap_err().to_string(), "Failure");
}

§A TransientError recovering after retry:

use paladin::{
    RemoteExecute,
    runtime::Runtime,
    operation::{
        Operation,
        Monoid,
        Result,
        OperationError,
        TransientError,
        RetryStrategy,
        FatalStrategy
    },
    directive::{Directive, IndexedStream},
};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, atomic::{Ordering, AtomicBool}};

#[derive(Serialize, Deserialize, Default, RemoteExecute)]
struct Multiply;

static DID_TRY: AtomicBool = AtomicBool::new(false);

impl Monoid for Multiply {
    type Elem = i64;

    fn empty(&self) -> Self::Elem {
        0
    }

    fn combine(&self, a: Self::Elem, b: Self::Elem) -> Result<Self::Elem> {
        if DID_TRY.swap(true, Ordering::SeqCst) {
            return Ok(a * b);
        }

        TransientError::from_str(
            "will retry",
            RetryStrategy::default(),
            FatalStrategy::default()
        )
        .into()
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let computation = IndexedStream::from([1, 2, 3]).fold(&Multiply);
    let result = computation.run(&runtime).await?;

    assert_eq!(result, 6);
}

§A TransientError timing out after exhausting retries:

use std::{num::NonZeroU32, sync::{Arc, atomic::{Ordering, AtomicU32}}};

use paladin::{
    RemoteExecute,
    runtime::Runtime,
    operation::{
        Operation,
        Monoid,
        Result,
        OperationError,
        TransientError,
        RetryStrategy,
        FatalStrategy
    },
    directive::{Directive, IndexedStream},
};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Default, RemoteExecute)]
struct Multiply;

static NUM_TRIES: AtomicU32 = AtomicU32::new(0);
const MAX_TRIES: u32 = 3;

impl Monoid for Multiply {
    type Elem = i64;

    fn empty(&self) -> Self::Elem {
        0
    }

    fn combine(&self, a: Self::Elem, b: Self::Elem) -> Result<Self::Elem> {
        let prev = NUM_TRIES.fetch_add(1, Ordering::SeqCst);

        TransientError::from_str(
            &format!("tried {}/{}", prev, MAX_TRIES + 1),
            RetryStrategy::Immediate {
                max_retries: NonZeroU32::new(MAX_TRIES).unwrap()
            },
            FatalStrategy::default()
        )
        .into()
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let computation = IndexedStream::from([1, 2, 3]).fold(&Multiply);
    let result = computation.run(&runtime).await;

    assert_eq!(NUM_TRIES.load(Ordering::SeqCst), MAX_TRIES + 2);
}

Variants§

§

Transient

An error that is expected to be resolved by retrying the operation.

Fields

§err: Error

The underlying error.

§retry_strategy: RetryStrategy

The retry strategy.

§fatal_strategy: FatalStrategy

The strategy to employ once the maximum number of retries is exceeded.

§

Fatal

An error that is not expected to be resolved by retrying the operation.

Fields

§err: Error

The underlying error.

§strategy: FatalStrategy

The failure strategy.

Implementations§

Source§

impl OperationError

Source

pub async fn retry<O, Fut, F>(self, f: F) -> Result<O>
where Fut: Future<Output = Result<O>>, F: Fn() -> Fut,

Retry the operation according to the strategy.

If the error is not a transient error, it is returned unchanged. If the error is a transient error, it is retried according to the provided strategy, and if the retry policy is exhausted, the error is converted into a fatal error.

Source

pub async fn retry_trace<O, Fut, F, T>(self, f: F, tracer: T) -> Result<O>
where Fut: Future<Output = Result<O>>, F: Fn() -> Fut, T: Fn(OperationError),

Retry the operation according to the strategy and the provided tracer.

If the error is not a transient error, it is returned unchanged. If the error is a transient error, it is retried according to the provided strategy, and if the retry policy is exhausted, the error is converted into a fatal error.

Source

pub fn into_err(self) -> Error

Extract the underlying error.

Source

pub fn as_err(&self) -> &Error

Extract the underlying error as a reference.

Source

pub fn into_fatal(self) -> Self

Convert an error into a fatal error.

If the error is already a fatal error, it is returned unchanged.

Source

pub fn fatal_strategy(&self) -> FatalStrategy

Trait Implementations§

Source§

impl Debug for OperationError

Source§

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

Formats the value using the given formatter. Read more
Source§

impl Display for OperationError

Source§

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

Formats the value using the given formatter. Read more
Source§

impl Error for OperationError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<FatalError> for OperationError

Source§

fn from(value: FatalError) -> Self

Converts to this type from the input type.
Source§

impl From<TransientError> for OperationError

Source§

fn from(value: TransientError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

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<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

Source§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

Source§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

Source§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

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> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

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

Source§

type Error = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,