fix(postgres): send the cleanup ROLLBACK only when a transaction block is open
What
release_sync and the Drop guard send ROLLBACK unconditionally before returning a pinned connection to the pool. When no transaction block is open — the steady-state autocommit pin case, hit once per scheduler tick — PostgreSQL answers with WARNING: there is no transaction in progress and logs it server-side by default: ~86,400 lines/day on an idle GlitchTip 6.2.2 install (measured; the code comment assumed a silent NOTICE, but it is a WARNING). Closes #1 (closed).
How
RustTransactiongains anin_tx: Arc<AtomicBool>:begin()starts attrue,pin()atfalse.- Single statements routed through the object update it via
tx_transition(first-keyword inspection:BEGIN/STARTopen,COMMIT/END/ROLLBACKclose,ROLLBACK TOkeeps the block open). The helper lives ingt-postgres(pyo3-free core) with unit tests, per the repo's core/bindings split. - Tracking is submission-time, which errs toward rolling back: a failed BEGIN costs one no-op ROLLBACK; a failed COMMIT still ends the block backend-side.
batch_execute_syncdeliberately does not parse its script: naive splitting misreads PL/pgSQL bodies (BEGIN ... END;inside a dollar-quotedCREATE FUNCTION) as top-level transaction control, which would disarm the Drop guard mid-migration. It conservatively marks the block open — worst case one no-op ROLLBACK on release of a DDL-running session, never the hot path.- The ServerSideCursor micro-transaction safety net is preserved: a DECLARE failing between BEGIN and COMMIT leaves the flag set, so the release still rolls back.
Verification
cargo fmt/clippyclean;gt-postgrestests 90 unit + 17 wire, all green (3 new unit tests fortx_transition).- pytest suite unchanged (221 passed; the 5
test_pg_asynciocopy_in failures are pre-existing onmainin the same environment). - Wire-level check against postgres 17 with
log_statement=all: releasing an idle pin sends no statement at all; releasing with an openBEGINblock sends exactly oneROLLBACK. Same check on v0.6.1 shows the WARNING once per release.
Note
The structurally-complete alternative would be surfacing the backend's authoritative transaction status from the ReadyForQuery wire message, which the vendored tokio-postgres already receives and discards (vendor/tokio-postgres/src/simple_query.rs, src/query.rs) — happy to rework in that direction if you prefer patching the vendored crate (as done for the buffer cap in GT_PATCH.md); the text-level tracking here has the advantage of not touching the vendor tree.