-
Notifications
You must be signed in to change notification settings - Fork 1
Description
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 :)