(Dec. 9th 2024) Flawless Beta 3 is here. Try it out!
  Flawless Logo, a beautiful woman with freckles head illustration. flawless.dev docs replay discord

Retry Safety

Retry safety is a property specific to the implementation of the Flawless side effect log system. If a workflow is abruptly stopped, Flawless will retry it again using the existing side effect log to assure that no side effect is executed twice. However, retries are tricky and not always safe to do. It very much depends on what external systems you are talking to and if they are idempotent.

Flawless follows Rust's philosophy here, and always fails in case it can't guarantee complete safety, but offers an escape hatch for developers if they know that the operation is safe to repeat. This escape hatch comes in the form of the .idempotent() function.

Let's look at an example and failure scenario from which Flawless can't recover.

use flawless::workflow;
use flawless_http::post;

#[workflow("http-call")]
fn http_call() {
    let response = post("https://example.com/safe-to-repeat")
        .set_header("Accept", "application/json")
        .send()
        .unwrap();
    let response_txt = String::from_utf8(response.body()).unwrap();
    log::info!("{}", response_txt.trim());
}

If this workflow is interrupted before or after the .send() call, Flawless will always be able to resume it. In case this workflow stops exactly when the request is sent, but no answer yet received, then Flawless can't tell if the external endpoint observed the call and will not repeat it to provide a called at most once guarantee. Instead, the .send() call will return an error of kind ErrorKind::RequestInterrupted.

It's also important to mention that Flawless doesn't care if the endpoint returns an error. It's the developer's duty to handle this case. Flawless is only concerned with failure scenarios that the developer can't handle directly in code. If the machine is shut down while some code is running, no amount of if-elses is going to help you. But once the machine is back up, Flawless will make it seem like the code just continued running from where it stopped.

Just in case you hit an edge case, from where Flawless can't automatically continue without knowing the properties of the underlying system, the developer gets the tools to manually handle this kind of failure.

By marking the request as .idempotent(), we tell Flawless that this endpoint is idempotent and can be called multiple times. This makes the workflow completely invincible, and now it always can be executed until completion.

use flawless::workflow;
use flawless_http::post;

#[workflow("http-call")]
fn http_call() {
    let response = post("https://example.com/safe-to-repeat")
        .set_header("Accept", "application/json")
        .idempotent()
        .send()
        .unwrap();
    let response_txt = String::from_utf8(response.body()).unwrap();
    log::info!("{}", response_txt.trim());
}

Idempotence is not only limited to the flawless_http crate, it's general concept and the Idempotence trait is part of the flawless crate.