Skip to content

Non-async API #7

@quietlychris

Description

@quietlychris

I've been really curious about building operations on potentially-fallible synchronous code as well, without necessarily needing to pulling in an async runtime and block on a given function. The context of this is also for code that non-deterministically fails; right now, I'm just spawning a thread and crossing my fingers, but it would be cool to add some level of fault-tolerancy to that. However, most of that code is not explicitly asynchronous, and it would be a bit of a pain to convert the entire codebase over to async to support what ends up being (for me) like 12 lines of code (which spawn functions that are much larger).

use std::time::Duration;

fn fallible_operation(msg: &str) -> std::io::Result<()> {
    // Your potentially failing operation here
    Err(std::io::Error::other(msg))
}

fn main() {
    // Spawns a background thread with some fault tolerancy
    let result = mulligan::until_ok()
        .stop_after(5)                     // Try up to 5 times
        .max_delay(Duration::from_secs(3)) // Cap maximum delay at 3 seconds
        .exponential(Duration::from_secs(1)) // Use exponential backoff
        .full_jitter()                     // Add randomized jitter
        .retry(move || {
            fallible_operation("connection failed").await
        }); 
    
    // Do other stuff
    loop {}

}

I've also been thinking about composable ways to joining a bunch of these functions together with some level of fault tolerance; in my head, it's looked a bit like Bevy's way of adding systems to an App, although for mulligan, it might not need schedule markers like Startup alongside the function definitions. That way, it could end up sort of similar to creating fallible actors but without needing to manage overhead of things like the messaging structures that typically come along with those frameworks.

use std::time::Duration;

fn fallible_operation(msg: &str) -> std::io::Result<()> {
    // Your potentially failing operation here
    Err(std::io::Error::other(msg))
}

fn fallible_operation_1(other_msg: &str) -> std::io::Result<()> {
    // A different potentially-failing operation
    Err(std::io::Error::other(other_msg))
}

fn main() {

    // Have a manager for lots of functions, where we want the same re-start strategy for all of them
    // If you wanted different strategies, you 
    let result = mulligan::Manager::new()
        .stop_after(5)                     // Try up to 5 times
        .max_delay(Duration::from_secs(3)) // Cap maximum delay at 3 seconds
        .exponential(Duration::from_secs(1)) // Use exponential backoff
        .full_jitter()                     // Add randomized jitter
        .manage(move ||  {
            fallible_operation("connection failed")
        })
        .manage(move || {
            fallible_operation_1("rng condition eventually led to a panic")
        })
        .run();
; 
    
    // Do other stuff
    loop {}

}

Apologies for hijacking your thread a bit (and of course feel free to ignore any/all of this), but having a library that does stuff like mulligan does even now has been something I've been wishing existed for a bit, and I'm really excited to see someone building stuff in the space :)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions