Skip to content

Abort-and-Retry After State Mismatch

Reactive conflict policy — instantiates Use-Time Precondition Binding

When a use-time check finds the state has changed since it was first read, it abandons the stale attempt cleanly and re-runs the operation on fresh state — instead of forcing the old decision through.

Once a mismatch has been detected, something has to decide what to do about it — and the wrong answer is to shove the stale action through anyway. Abort-and-Retry After State Mismatch is that decision, made into policy. It doesn't prevent drift or notice it; a version token or a final revalidation does that. Its job is the reaction: on a detected mismatch, abort — unwind any partial or tentative effects so the world is left clean — then retry by re-reading current state and recomputing the decision from scratch, not replaying the old one. Its defining move is that a retry is not a redo: it discards the stale premise entirely and starts the operation over against reality as it now is, bounded so it cannot loop forever.

Example

A ride-hailing dispatcher checks that driver D is idle and tentatively assigns trip T to her. In the half-second between the check and the assignment, D accepts a different trip she was already negotiating. At commit, the system sees D is no longer idle — a state mismatch. Abort-and-Retry takes over: it aborts the tentative assignment (so D is never double-booked and the rider is never told about a car that isn't coming), then retries — re-reads the pool of genuinely-idle drivers now and assigns the next-best one, E. It allows a small number of such attempts with a brief backoff between them; if a rare surge means no driver can be found after, say, ≈4 tries, it stops retrying and takes the escape path — surfacing "no cars available right now" or routing the rider to a wait queue rather than spinning forever.

The rider sees, at worst, a couple of seconds of "finding your driver." What they never see is a driver assigned to two trips, because every failed attempt was cleanly abandoned before the next began.

How it works

  • Triggered by a mismatch, not a clock. It runs only when a use-time check reports the state has moved; on the happy path it never fires.
  • Abort leaves no residue. Any partial write, tentative claim, or side effect from the failed attempt is unwound first, so the retry starts from a clean slate rather than compounding.
  • Retry recomputes, never replays. Fresh state is read and the decision is made again from it; the stale inputs that caused the mismatch are thrown away.
  • Bounded, backed-off, idempotent. Attempts are capped and spaced (backoff with jitter) to avoid thrash, and the operation carries an idempotency key so a retry can't double-apply an effect that actually succeeded.

Tuning parameters

  • Attempt budget — how many retries before giving up. High budgets ride out transient conflicts; low ones fail fast and shed load under contention.
  • Backoff schedule — immediate, linear, or exponential-with-jitter. Steeper backoff prevents retry storms and thundering-herd pile-ups at the cost of latency.
  • Retry scope — re-run just the failed step, or unwind and redo the whole transaction. Narrow is cheaper; wide is safer when steps are entangled.
  • Idempotency strategy — the dedupe key or token that makes a re-executed side effect safe to repeat.
  • Escape target — where exhausted retries go: hard failure, a wait queue, an escalation, or a degraded fallback.

When it helps, and when it misleads

Its strength is that it lets a system stay optimistic: skip expensive locks and holds, assume conflicts are rare, and pay the cost only on the uncommon occasion one actually happens. Under low contention that is close to free, and it composes with any detector — a version token, a revalidation gate — that can report "the state moved."

It misleads under exactly the opposite conditions. When contention is high, retries thrash: every attempt collides with another, latency spikes, and the system can livelock, all parties forever aborting and re-trying. The sharpest misuse is retrying a non-idempotent side effect without a dedupe key — a payment or an email that actually went through on the attempt that "failed," now sent twice[1]. And an unbounded retry loop quietly converts a permanent problem (a resource that will never be free) into an outage. The discipline is to cap and back off attempts, make the operation idempotent so repeats are harmless, and route exhausted retries to a real escape path rather than looping in hope.

How it implements the components

Abort-and-Retry realizes the reaction side of the archetype — what to do once drift is found, not how to find it:

  • conflict_abort_and_retry_policy — its core: the rule for aborting cleanly and re-running on fresh state, with its attempt cap, backoff, and idempotency.
  • stale_authority_escape_path — the terminal exit when retries are exhausted or the mismatch is permanent, so a stale premise never forces a bad commit and the loop never runs forever.
  • check_use_audit_trace — the recorded trail of check → mismatch → abort → retry → outcome, which is what makes conflicts debuggable and surfaces thrash before it becomes an outage.

It does not produce the mismatch signal it reacts to — the state_version_or_freshness_token and its detection come from Compare-and-Swap Version Token or Final Revalidation Before Commit — and it does not prevent the conflict with a reservation_or_hold_record, which is Reservation-Commit Protocol and Lock or Hold Until Use.

References

[1] An operation is idempotent when applying it twice has the same effect as applying it once; an idempotency key lets a server recognize a repeated request and return the original result instead of doing the work again. It is the standard precondition for safe retries — without it, "abort and retry" can turn one charge into two.