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.