Limits on retrying writes without duplication in Rust's retry crate
0 reputation · 30 Oct 2020, 05:13 UTC
0 reputation · 30 Oct 2020, 05:13 UTC
Rust developers often need to retry fallible operations while guaranteeing that a write side‑effect occurs only once. The retry crate (v0.2.0) supplies a Retry struct and a RetryFn helper that repeatedly invoke a closure until success or policy exhaustion.
To avoid duplicate writes, the operation closure typically performs an idempotency guard—such as checking a version number, using an AtomicUsize flag, or executing a compare‑and‑swap on shared state—before executing the write. The guard must be thread‑safe if the retry may run concurrently, and the closure must be cheap to clone.
Although the crate allows embedding such guards, it does not expose a standard retry trait or a dedicated API that guarantees at most one write per operation. Developers must therefore decide whether to rely on manual guards inside the closure or to seek a higher‑level abstraction that enforces this invariant.
Specific questions for further investigation:
retry crate provide that the write closure is invoked at most once when an idempotency guard is present?AtomicUsize flag be safely used across retries to enforce single write without race conditions?35275 reputation · 30 Oct 2020, 17:07 UTC
The retry crate (v0.2.0) does **not** guarantee that the supplied closure is invoked only once. It will call the closure on each retry attempt until the operation succeeds or the retry policy is exhausted, regardless of any idempotency guard inside the closure.
Retry struct and RetryFn helper; there is no built‑in trait or API that enforces at‑most‑one execution.AtomicUsize (or AtomicBool) can be safely shared across retries to enforce a single write, provided the flag is checked and set using atomic compare‑and‑swap with appropriate memory ordering (e.g., SeqCst or AcqRel) inside the closure.backoff or tokio‑retry offer similar retry mechanics where idempotency can be imposed via std::sync::Once or atomic flags.Arc initialized to 0).compare_exchange to change the flag from 0 to 1.Ok(()); if it fails (flag already 1), return an error that the retry policy treats as a transient failure or simply return Ok(()) to stop further retries.To decide whether the atomic flag needs to be wrapped in an Arc (for sharing across threads) or can be a plain AtomicUsize, we need to know: Are the retry attempts executed concurrently or strictly sequentially? If retries run concurrently, the flag must be shared via Arc; if they are sequential, a plain atomic is sufficient.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.