keen-retry

A simple -- yet powerful -- zero-cost-abstractions & zero-copy lib for error handling & recovery -- with the following functionalities, focusing on performance and code expressiveness: * Provides an idiomatic, Rust-like, API by allowing operations to return a retryable type -- using a functional approach for expressing the retry logic in par with what we have for Result<> and Option<>. This eases building pipelines of retryable operations immune to the callback-hell phenomenon; * Zero-cost-abstractions: if the operation succeeds or fails fatably in the initial attempt, no extra code is run -- zero-cost if no retrying is necessary; * Distinct operations for the first (initial) attempt and the retrying ones, allowing the retryable operations to do extra (expensive) checks for detailed instrumentation, error reporting and, possibly, more elaborated (nested) retrying logics (such as reconnecting to a server if a channel detected a connection drop while sending a payload -- in this case, a failure in retrying a connection might either be transient or fatal: the server might say it is full and that we should try another peer, or it might say the given credentials are not recognized); * Full support for both sync and async operations.

Components

Taste it

First, the operation must adhere to the keen-retry API -- to distinguish whether retrying is necessary:

nocompile async fn connect_to_server_retry(&self) -> RetryConsumerResult<(), (), ConnectionErrors> { self.connect_to_server_raw().await .map_or_else(|error| match error.is_fatal() { true => RetryConsumerResult::Fatal { input: (), error }, false => RetryConsumerResult::Retry { input: (), error }, }, |_| RetryConsumerResult::Ok { reported_input: (), output: () }) }

Then, a simple retryable logic:

``nocompile pub async fn connect_to_server(self: &Arc<Self>) -> Result<(), ConnectionErrors> { let cloned_self = Arc::clone(&self); self.connect_to_server_retry().await .retry_with_async(|_| cloned_self.connect_to_server_retry()) .with_delays((10..=130).step_by(10).map(|millis| Duration::from_millis(millis))) // 13 attempts with an arithmetic progression on the sleeping time between attempts .await .inspect_recovered(|_, _, loggable_retry_errors, retry_errors_list| println!("##connecttoserver()`: successfully connected after retrying {} times (failed attempts: [{loggableretryerrors}])", retryerrorslist.len())) .into() }

```

For a fully fledged instrumentation, as seen in production applications, take a look at tests/use_cases.rs, where all operations, errors, and nested levels of retries are demonstrated.