Description
PgTransactionManager::begin queues BEGIN, then awaits wait_until_ready() before incrementing transaction_depth:
let rollback = Rollback::new(conn);
rollback.conn.queue_simple_query(&statement)?;
rollback.conn.wait_until_ready().await?; // cancellation window
if !rollback.conn.in_transaction() {
return Err(Error::BeginFailed);
}
rollback.conn.inner.transaction_depth += 1; // never reached on cancel
rollback.defuse();
If the future is dropped at that await (task abort, select!, tokio::time::timeout), the BEGIN is already in the connection's write buffer or on the wire, but depth is still 0, so the Rollback guard's start_rollback no-ops (if conn.inner.transaction_depth > 0). No ROLLBACK is queued and the connection returns to the pool with an open server-side transaction.
In 0.8.3 the increment happened before the await, so a cancelled begin left depth = 1 and the guard queued the compensating ROLLBACK. The reorder came in with #3765 (merged for 0.8.4) and is still present in 0.9.0.
Reproduction steps
- Create a
PgPool with max_connections(1) so the same connection must be reused.
- Start
pool.begin() but drop the future before it completes.
- Use the pool again: the next
pool.begin() on that connection elicits WARNING: there is already a transaction in progress
- Run the same sequence on 0.8.3: no warning, because the drop guard queues the compensating
ROLLBACK.
SQLx version
0.8.6 (regression present in 0.8.4 through 0.9.0; 0.8.3 not affected)
Enabled SQLx features
postgres, runtime-tokio, tls-native-tls, chrono, bit-vec
Database server and version
PostgreSQL (reproduces against the official postgres Docker image)
Operating system
Linux x86_64; also reproduces on macOS
Rust version
rustc 1.97.1
Description
PgTransactionManager::beginqueuesBEGIN, then awaitswait_until_ready()before incrementingtransaction_depth:If the future is dropped at that await (task abort,
select!,tokio::time::timeout), theBEGINis already in the connection's write buffer or on the wire, but depth is still 0, so theRollbackguard'sstart_rollbackno-ops (if conn.inner.transaction_depth > 0). NoROLLBACKis queued and the connection returns to the pool with an open server-side transaction.In 0.8.3 the increment happened before the await, so a cancelled begin left depth = 1 and the guard queued the compensating
ROLLBACK. The reorder came in with #3765 (merged for 0.8.4) and is still present in 0.9.0.Reproduction steps
PgPoolwithmax_connections(1)so the same connection must be reused.pool.begin()but drop the future before it completes.pool.begin()on that connection elicitsWARNING: there is already a transaction in progressROLLBACK.SQLx version
0.8.6 (regression present in 0.8.4 through 0.9.0; 0.8.3 not affected)
Enabled SQLx features
postgres, runtime-tokio, tls-native-tls, chrono, bit-vec
Database server and version
PostgreSQL (reproduces against the official
postgresDocker image)Operating system
Linux x86_64; also reproduces on macOS
Rust version
rustc 1.97.1