Repository navigation
bug: make deposit/withdraw/transfer transactional with row locks (sfind-baf6d2f9) - #376
Open
devin-ai-integration[bot] wants to merge 3 commits into
Open
devin-ai-integration[bot] wants to merge 3 commits into
devin-ai-integration[bot] wants to merge 3 commits into
Conversation
deposit, withdraw and transferAmount now run in one @transactional unit and re-read the account rows with SELECT ... FOR UPDATE (PESSIMISTIC_WRITE) before checking/changing the balance. Transfers lock both rows in ascending id order to avoid deadlocks, and all balance + transaction record writes commit or roll back together. Adds AccountServiceConcurrencyTest (20 concurrent full-balance withdrawals -> exactly one succeeds, double-spend via transfers, lost credit, concurrent deposits, opposite-direction transfers, rollback on failed transfer). Refs: sfind-baf6d2f929354c71b4c749980422fad2
With spring.jpa.open-in-view the controller's Account stays managed for the whole request, so a locking query returned that cached instance with the balance read before the lock. lockAccount now refreshes the entity with PESSIMISTIC_WRITE, which issues SELECT ... FOR UPDATE and overwrites the in-memory state. Adds BankControllerConcurrencyTest, which fires concurrent POST /withdraw and /transfer requests over real HTTP. Refs: sfind-baf6d2f929354c71b4c749980422fad2
Author
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
Author
|
Runtime-tested b9821ca locally in the browser against real MySQL 5.7. Concurrent transfer and rollback evidence
Other flows and coverage limitsDeposit 100, withdraw 30, rejected withdraw of 1000, transfer 20, recipient history, and the nonexistent-recipient error all showed the expected balances. The Bootstrap CDN was unreachable, so the forms were submitted unstyled. The base-branch comparison and multi-replica scenarios were not tested in the browser. |
This branch has not been deployed
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
Fixes code-scan finding
sfind-baf6d2f929354c71b4c749980422fad2(high):deposit,withdrawandtransferAmountinAccountServicedid read-check-write on balances with no transaction or row lock, so overlapping requests (2 replicas inkubernetes/bankapp-deployment.yml) could overdraw an account, drop another user's incoming credit, or leave a transfer half-applied.Each method is now one
@Transactionalunit that re-reads the account row under a database lock before checking or changing the balance:transferAmountlocks both rows in ascending id order (no deadlock between A->B and B->A), then checks funds on the locked sender. Both balance updates and bothTransactionrecords commit or roll back together.refresh(..., PESSIMISTIC_WRITE)instead of a@Lockrepository query:spring.jpa.open-in-viewis on by default, so theAccountthe controller loaded stays managed for the whole request. A locking query would return that cached copy with the balance read before the lock. A first version of this branch did exactly that, and the new HTTP test caught it (fixed in 8b9ffb4).@Version: no schema change needed (ddl-auto=updateon existing MySQL rows). Method signatures are unchanged.Tests
AccountServiceConcurrencyTestcovers 20 concurrent full-balance withdrawals (exactly 1 succeeds, final balance 0), 10 concurrent transfers of the whole balance (1 succeeds), Bob's 1000 credit racing 20 x 0.01 credits to Alice (nothing lost), 20 concurrent deposits, opposite-direction transfers, and rollback when the"Transfer Out to " + namelabel overflows the 255-chartypecolumn.BankControllerConcurrencyTestsends concurrentPOST /withdrawand/transferover real HTTP (RANDOM_PORT, form login), which exercises open-in-view.application-test.properties: H2LOCK_TIMEOUT=30000(H2's default of 1s would time out contended threads).Run locally (Maven Central isn't reachable from the build box, so the JUnit Platform launcher ran directly against the Maven test classpath):
DevOpscontextLoads1/1HTTP replay against the running app on MySQL (Mallory has 100 and sends 10 concurrent
POST /transfer amount=100to 10 accomplices):DevOps: Mallory ends at 0.00, accomplices hold 900.00 in totalNot covered:
./mvnw testvia surefire (the surefire-junit-platform provider isn't cached and Maven Central is blocked; access has been requested). The MySQL runs used 5.7 because themysql:8.0image can't be pulled here.Overlap
Open PRs #307, #312 and #286 (atomic transfer part) address the same race. This PR exists because a new finding was filed, and only one of them should land. Unlike a plain
@Lockrepository query, this PR also handles the open-in-view stale-entity case and coversdeposit/withdraw.Not in scope, but related: non-positive amounts are still accepted (#290), and CSRF is disabled in
SecurityConfig.Devin-Org: engineering
Link to Devin session: https://devin-gtm.devinenterprise.com/sessions/5f067f8fb1924711b7e1e5e6b849bf09
Open in Devin Desktop: https://devin-gtm.devinenterprise.com/desktop/session/5f067f8fb1924711b7e1e5e6b849bf09?variant=devin
Devin Review