Skip to content

Commit a9dd2e3

Browse files
jensensclaude
andauthored
Close read txn at request end (afterCompletion) + idle-in-xact safety net (#118) (#58)
* test: failing regression for #118 — read txn must close on afterCompletion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(instance): implement afterCompletion to close read txn at request end (#118) ZODB calls _storage.afterCompletion() after every transaction commit/abort and on Connection.close(). Previously PGJsonbStorageInstance did not define it, so the hook was a silent no-op and the REPEATABLE READ snapshot opened by poll_invalidations() persisted across requests. Each lingering read tx held a virtualxid that blocks CREATE INDEX CONCURRENTLY indefinitely under load. The new method delegates to _end_read_txn() (idempotent) and never propagates exceptions — a connection killed externally just gets logged; the next operation rebuilds via the pool. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: edge cases for afterCompletion (idempotent, post-tpc, killed conn, end-to-end) The end-to-end test verifies that after Connection.close(), the backend's pg_stat_activity state is 'idle' (not 'idle in transaction') — exactly the conditions that previously blocked CIC. Note: mid-request transaction.commit/abort fires our afterCompletion, but ZODB.Connection.afterCompletion immediately follows up with newTransaction() → poll_invalidations() which re-opens the read tx for the next implicit transaction. The fix's actual win is at Connection.close() (pool return / request end), which the end-to-end test pins down. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: failing regression for default idle_in_transaction_session_timeout (#118) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(pool): set idle_in_transaction_session_timeout on every conn (#118) Defense in depth for any leaked virtualxid that bypasses afterCompletion (SIGKILL-ed worker, third-party plugin, etc.). Default 60_000 ms, env-overridable via ZODB_PGJSONB_IDLE_IN_XACT_TIMEOUT_MS. Set to 0 to disable. Replaces the inline `lambda` configure with a named helper that also applies the timeout setting alongside autocommit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: changelog + spec + plan for #118 read-tx fix Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ed2a9a5 commit a9dd2e3

6 files changed

Lines changed: 1199 additions & 1 deletion

File tree

CHANGES.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,32 @@
22

33
## Unreleased
44

5+
<<<<<<< fix/idle-in-xact
6+
- Implement `IStorage.afterCompletion()` on `PGJsonbStorageInstance`
7+
so the REPEATABLE READ read-snapshot transaction is committed at
8+
request end (after every `transaction.commit/abort` and on
9+
`Connection.close()`). Previously the read tx persisted across
10+
request boundaries until the connection was reused, leaving
11+
`idle in transaction` sessions with live virtualxids that blocked
12+
`CREATE INDEX CONCURRENTLY` for minutes-to-hours under load.
13+
Idempotent and exception-swallowing — a connection killed
14+
externally is logged and rebuilt on next use. Closes
15+
bluedynamics/plone-pgcatalog#118.
16+
17+
- Set `idle_in_transaction_session_timeout` (default 60_000 ms,
18+
env-overridable via `ZODB_PGJSONB_IDLE_IN_XACT_TIMEOUT_MS`) on
19+
every connection from the instance pool. Defense in depth for
20+
any future leak path that bypasses `afterCompletion` (e.g.
21+
`SIGKILL`-ed worker, buggy plugin). Set to `0` to disable.
22+
=======
523
- Serialize startup DDL across replicas via session-level PostgreSQL
624
advisory lock. New `zodb_pgjsonb.startup_locks` module exposes
725
`startup_ddl_lock(dsn)` context manager. `_apply_pending_ddl` wraps
826
its body in the lock and requeues pending work on timeout. Lock wait
927
timeout is 15 minutes by default, overridable via
1028
`ZODB_PGJSONB_DDL_LOCK_TIMEOUT`. Closes
1129
bluedynamics/plone-pgcatalog#108 (credit: @davisagli).
30+
>>>>>>> main
1231
1332
## 1.10.4
1433

0 commit comments

Comments
 (0)