Repository navigation
Rust #325: roll back a PostgreSQL transaction cancelled during BEGIN - #761
Conversation
Published sqlx-postgres 0.9.0 raises transaction_depth only after the BEGIN round trip, and start_rollback does nothing at depth 0. A request dropped during that round trip left the pooled connection inside a transaction. In #673's fixtures (run 40195442238) the scheduler's pooled timer probe then ran inside it, native's read snapshot BEGIN failed with 25001 and aborted it, and every later statement on that connection failed with 25P02. Vendor sqlx-postgres 0.9.0 with upstream's unreleased fix (transact-rs/sqlx#4394, f1e94ec34dd5) applied unchanged, as sqlx-mysql is vendored. The new PostgreSQL qualification test cancels a pooled BEGIN in flight and checks that the backend is idle and that the scheduler's order succeeds on it. With the published driver it is "idle in transaction", the read snapshot BEGIN fails with 25001 and the next statement with 25P02. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Security source review of exact head Provenance and scope checks:
The archive itself carries pre-existing executable modes and trailing whitespace on many source files; full-tree comparison confirms these are provenance-preserving archive properties, not additional code changes. This is exact-head source/provenance acceptance only. It does not claim completed CI or a local Rust build (Rust builds belong on the dedicated VPS). Merge remains gated on the required fresh green checks and the owner’s PostgreSQL/SQLite qualification evidence. |
|
Exact-head CI follow-up for Combined with the exact-head source/provenance acceptance in comment 6398515671 and the owner’s PostgreSQL/SQLite qualification evidence, the Security fresh-green merge gate is satisfied. This does not authorize publication or change the separate #673 issuer and Docker Hub credential gates. |
CI receipt: exact head
|
…-pip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…age-pull-retry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st-325-ci-codec-one-job The comparator node test list keeps main's update-validation retry test and this branch's codec deadline cell test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Native PostgreSQL nodes could lose their storage for the rest of a run after one request was dropped mid-
BEGIN. This vendorssqlx-postgres0.9.0 with upstream's unreleased fix, asrust/vendor/sqlx-mysqlis vendored.What failed
Server #673's fixtures run 40195442238,
postgres-storage (existing-php-matrix, sdk-directions): native node A logged oneruntime_storage_unavailablewith SQLSTATE 25001, thenruntime_scheduler_storage_unavailable, then 235 more with 25P02. PostgreSQL logged the scheduler's timer probe on the same backend every 250 ms:That probe runs directly on the pool (
fetch_optional(&self.pool)), not in a transaction. So a pooled connection was sitting in an aborted transaction.Why
Published
PgTransactionManager::beginraisestransaction_depthonly after theBEGINround trip.start_rollback, which both its guard andTransaction's drop call, does nothing while the depth is 0. When a request future is dropped during that round trip (a client closing its connection mid-request), the server stays in the transaction, nothing queues aROLLBACK, and the pool hands the connection to the next borrower:BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLYon that connection fails with 25001 ("SET TRANSACTION ISOLATION LEVEL must be called before any query"), which aborts the transaction.Upstream fixed exactly this in transact-rs/sqlx#4394 (commit
f1e94ec34dd5, merged 2026-09-09), which isn't in a release yet (latest tag v0.9.0).Change
rust/vendor/sqlx-postgres: the official 0.9.0 crate (archive SHA-25687a2bdd6…dcd65e, theCargo.lockchecksum it replaces) with upstream'ssrc/transaction.rschange applied unchanged. The depth is claimed before the round trip and unwound if theBEGINdid not take, so a cancelled or failedBEGINqueues aROLLBACK.DURABLE-PATCH.mdrecords the provenance and says when to remove the patch.rust/Cargo.toml:[patch.crates-io] sqlx-postgres = { path = "vendor/sqlx-postgres" }.Cargo.lockdrops the registry source and checksum lines, as forsqlx-mysql.qualification_a_begin_cancelled_in_flight_leaves_no_transaction_behind(PostgreSQL,#[ignore], run by theruntime::postgres::tests::qualification_CI filter). A one-connection pool cancelspool.begin_with("BEGIN; SELECT pg_sleep(2)")after 300 ms and waits for the abandoned statement to answer. It then checks thatpg_stat_activityshows the backendidle, and replays the scheduler's order on that same backend: a pooled probe, native's read snapshotBEGIN, then a pooled statement.Qualification
src/transaction.rsrestored from the archive), against a disposable PostgreSQL. The new test fails:left: Some("idle in transaction"),right: Some("idle"). With the state assertion temporarily replaced by logging, the same run reproduces the CI signature on that backend: the pooled probe succeeds inside the stray transaction, the read snapshotBEGINfails with 25001, and the next pooled statement fails with 25P02.runtime::postgres::tests::qualification_tests pass, the new one included (40 s)..cratearchive only insrc/transaction.rsand the addedDURABLE-PATCH.md. Sonar already excludes**/vendor/**.No native runtime source changes beyond the test, no fixture changes, and no PHP changes.
🤖 Generated with Claude Code