Loading page...Загружаем страницу...正在加载页面...
AtlasRepo

My refund handler checked the ledger before paying. It still paid twice.

Description and use

A refund handler that checks the ledger before writing prevents duplicate refunds, but concurrent deliveries can still cause double payments. Using a unique claim key and INSERT OR IGNORE ensures idempotency.

Problem

Refund handlers may process the same event twice, causing duplicate refunds, when retries or concurrent deliveries occur.

Outcome

The naive handler paid twice in all scenarios. check_then_act succeeded only in sequential scenarios but failed under concurrency. guarded_raises prevented double payments but raised errors that caused retries. The guarded handler with INSERT OR IGNORE and early return succeeded in all scenarios, including 200 concurrent trials.

Workflow

  • 1. Deliver the same event twice in four scenarios: duplicate, timeout_retry, crash_before_ack, concurrent.
  • 2. Run a pytest plugin (replay-twice) to replay the event and count ledger entries.
  • 3. Compare four handler implementations: naive, check_then_act, guarded_raises, guarded.
  • 4. Observe results for each scenario and concurrent trials.
  • 5. Analyze why check_then_act fails under concurrency and why guarded_raises still causes retries.
  • 6. Recommend using INSERT OR IGNORE with a claim table and returning "already_applied" to avoid retries.
  • 7. Provide code snippet for guarded implementation and instructions to run replay-twice locally.

Evidence

The naive handler writes the refund every time.

Source

check_then_act reads the ledger, and writes only if nothing is there.

Source

guarded_raises claims the order id in a table with a unique key, in the same transaction as the refund. A second claim raises an integrity error.

Source

guarded makes the same claim with INSERT OR IGNORE and returns “already applied” when the claim was already taken.

Source

The table above reproduces with replay-twice report in about four minutes.

Source

  • Record date: 2026-10-06T02:41:07.309Z