Fix negative balance from concurrent transfers with pessimistic row locks - #307
Open
devin-ai-integration[bot] wants to merge 2 commits into
Open
Fix negative balance from concurrent transfers with pessimistic row locks#307devin-ai-integration[bot] wants to merge 2 commits into
devin-ai-integration[bot] wants to merge 2 commits into
Conversation
Co-Authored-By: Rush Cromer II <rush.cromerii@cognition.ai>
Co-Authored-By: Rush Cromer II <rush.cromerii@cognition.ai>
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AccountService.transferAmount(andwithdraw) did a check-then-write on a detachedAccountpassed in by the controller, with no transaction and no lock. Two concurrent requests both readbalance=100, both pass theInsufficient fundscheck, both write — sender goes negative and money is created.Reproduction:
AccountServiceConcurrencyTest(new,@SpringBootTestagainst MySQL) fires 10 simultaneousalice -> bobtransfers of 100 from a 100 balance. Against the unmodified code:Both pass after the fix (3 consecutive runs).
Fix — pessimistic locking (
SELECT ... FOR UPDATE):withdrawgets the same lock-then-check treatment;depositbecomes@Transactionalso its balance + transaction writes are atomic. The recipient isdetached after the username lookup so the subsequent locking query actually refreshes it from the DB instead of returning the already-managed (unlocked) instance.Why pessimistic locking instead of optimistic
@Version:@Version, the service would have to re-read the sender anyway to get a current version, and any concurrent write would surface asOptimisticLockExceptionneeding retry loops in every caller. Pessimistic locks make the service correct regardless of what the caller passes in.FOR UPDATEis cheaper and simpler than retry-on-conflict, and the user never sees a spurious "please try again".FOR UPDATEon both rows in a fixed order gives serialisable debit/credit with no lost-update window and no deadlock, which is the standard pattern for ledger updates.Devin-Org: engineering
Link to Devin session: https://app.devin.ai/sessions/fb59df058fbe41d3ac939265188c108b
Open in Devin Desktop: https://app.devin.ai/desktop/session/fb59df058fbe41d3ac939265188c108b?variant=devin
Requested by: @rushcromer
Note
Devin errored when opening this Pull Request as rushcromer.
As a fallback, Devin opened this PR as itself.