My refund handler checked the ledger before paying. It still paid twice.
说明与用途
在写入前检查账本的退款处理程序可防止重复退款,但并发交付仍可能导致双重付款。使用唯一声明键和 INSERT OR IGNORE 可确保幂等性。
问题
Refund handlers may process the same event twice, causing duplicate refunds, when retries or concurrent deliveries occur.
结果
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.
工作流
- 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.
证据
The naive handler writes the refund every time.
check_then_act reads the ledger, and writes only if nothing is there.
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.
guarded makes the same claim with INSERT OR IGNORE and returns “already applied” when the claim was already taken.
The table above reproduces with replay-twice report in about four minutes.
- 记录日期: 2026-10-06T02:41:07.309Z