Repository navigation
fix(core): don't roll back the enclosing level when a nested begin fails or is cancelled - #4446
Closed
CalebFenton wants to merge 2 commits into
Closed
CalebFenton wants to merge 2 commits into
CalebFenton wants to merge 2 commits into
Conversation
…nsaction Both tests fail on every driver: the nested begin rolls back the enclosing level, so the outer transaction's earlier write is lost and later writes escape its rollback.
`Transaction::begin` armed its rollback-on-drop guard before awaiting `TransactionManager::begin`. Every in-tree driver already undoes its own failed or cancelled `begin`, so the guard's `start_rollback` ran one level too many: at depth 0 it was a no-op, but inside a transaction it rolled back the caller's enclosing transaction or savepoint and lowered the depth. Arm the guard only once `begin` has succeeded, and document that contract on `TransactionManager::begin`. Fixes transact-rs#4445
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.
Does your PR solve an issue?
fixes #4445
What changes
Transaction::beginbuilt theTransactionwithopen: truebefore it awaitedTransactionManager::begin, so a failed or droppedbeginalso ranTransaction::drop'sstart_rollback. Every in-tree driver already cleans up its own failedbegin:Rollbackguard, which since fix(postgres): roll back a transaction cancelled during BEGIN #4394 raises the depth first;The extra
start_rollbacktherefore ran one level too many. At depth 0 that was a no-op. Inside a transaction, it rolled back the caller's own level.The fix arms the guard only after
beginsucceeds and documents that contract onTransactionManager::begin. The commits are split so that the test commit fails on its own.Tests
Two tests in
tests/any/any.rs, so they run in every driver's lane:it_keeps_the_outer_transaction_when_a_nested_begin_failsusesbegin_with("BEGIN")at depth 1, which returnsInvalidSavePointStatement. It is deterministic and involves no cancellation.it_keeps_the_outer_transaction_when_a_nested_begin_is_cancelledpollsbegin()once and drops it. A fast server can answer within that poll; MySQL did about once in 300 runs locally. In that case the test rolls back the completed savepoint and tries again, so it cannot pass without cancelling.Both tests use one connection and a temporary table. Inside the outer transaction, the row count must be 2 after the nested
begin. Afterouter.rollback(), it must be 0.Is this a breaking change?
No for the in-tree drivers. A third-party
TransactionManagerwhosebeginrelied onTransaction::dropto clean up after it would now need to do that itself. The new rustdoc states this contract.One thing this PR does not change: a cancelled top-level
begin()on MySQL still leaves the transaction open on the connection, as described in #4445. That was already true before this change, becausestart_rollbackat depth 0 is a no-op.Verification
Run locally with Rust 1.94.1 on Linux:
main(8b65c2f) against Postgres 17, MySQL 8, and SQLite, and passes with the fix.cargo test --no-default-features --features any,<db>,macros,migrate,_unstable-all-types,runtime-tokio,tls-nonepasses in full against thepostgres_17andmysql_8compose services and against SQLite (--test-threads=1, withsqlite-preupdate-hook).cargo fmt --all -- --checkpasses.cargo clippy -p sqlx-core -- -D warningsis clean. Clippy on--test anyreports only warnings that already exist insqlx-testand in older parts oftests/any/any.rs.This PR was written with an AI coding agent (Claude Code) on behalf of @CalebFenton. The agent reproduced the bug, wrote the fix and the tests, and ran the checks listed above.