Commit Graph
5 Commits
Author SHA1 Message Date
Erik JohnstonandClaude Opus 4.8 98a7e37c59 Apply per-connection session setup in the Rust connection pool
Move the equivalent of the engine's `on_new_connection` into the pool's
`ConnectionManager::create`, so every pooled connection is configured once when
it is opened rather than on each checkout. A new `SessionConfig` (carried by the
manager) drives it:

  - the default isolation level is set to REPEATABLE READ, matching the engine's
    `default_isolation_level`, so a plain BEGIN behaves as it did under psycopg2;
  - `synchronous_commit` is turned off when configured;
  - `statement_timeout` is set when configured.

`bytea_output` is deliberately not set: tokio-postgres uses the binary protocol
for prepared statements, so that text-format GUC is irrelevant to how we decode
bytea.

`create_pool` keeps its signature (default session); a new
`create_pool_with_session` and two new keyword-only `ConnectionPool(...)`
arguments (`synchronous_commit`, `statement_timeout_ms`) expose the settings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3G4M92AmwSSZCbmtMJU3d
2026-07-06 09:47:10 +00:00
Erik JohnstonandClaude Opus 4.8 9f7024a78a Add a threadpool bridge for running DB functions against pooled connections
`RustConnectionPool` runs synchronous Synapse transaction functions off the
reactor thread against the native Rust connection pool. Each
`run_with_connection(func, *args)` call runs, on a dedicated Twisted thread
pool, `func(conn, *args)` with a `Connection` checked out of the Rust pool, and
returns a Deferred that fires on the reactor with the result (or an errback if
it raised). It builds on `defer_to_threadpool`, so log contexts are preserved
across the hop, and returns the connection to the pool afterwards (clean →
reused, mid-transaction/broken → discarded, per the shim's disposal rules).

The Rust pool sizes 1:1 with the thread count, and `PyConnectionPool.close()`
(new) closes all idle connections so the owner can drop the server connections
deterministically. The bridge is lifecycle-owner-managed (start/close) and
takes no clock, keeping it trivially testable; slotting it into
`DatabasePool.make_pool` — which also needs engine-level support for the shim
connection (in_transaction, is_connection_closed, autocommit/isolation,
reconnect) — is left to a follow-up.

Tested against a live Postgres over the real reactor (Synapse's in-memory test
reactor mocks the DB thread pool out, so a plain Twisted trial TestCase is used):
result/return-value, arg forwarding, exception-to-errback, connection reuse
across calls, and concurrent checkouts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3G4M92AmwSSZCbmtMJU3d
2026-07-06 09:46:05 +00:00
Erik JohnstonandClaude Opus 4.8 28e9f95279 Make the Postgres Connection shim pool-backed
The Python-facing `Connection`/`Cursor` shim now always wraps a connection
checked out of the `deadpool` pool. `ConnInner` holds a `PooledConnection` and
disposes of it correctly when the last reference (the `Connection` and all its
cursors) goes away, via a `Drop` impl plus `release`/`discard` helpers:

  - a clean connection is returned to the pool for reuse;
  - a connection is *discarded* (detached with `Object::take`, which also
    shrinks the pool) whenever reuse would be unsafe — a failed COMMIT/ROLLBACK,
    a poisoned mutex, or being dropped with a transaction still open (which
    can't be rolled back synchronously from `Drop`, so the socket close makes
    the server do it).

A plain query error still does *not* throw the connection away — it stays
open+aborted for the driver to `rollback()`, exactly as psycopg2 behaves.

The pool is the only way to obtain a connection: the standalone `connect(dsn)`
free function is replaced by a Python-facing `ConnectionPool` class (Rust
`PyConnectionPool`, exposed as `postgres.ConnectionPool`). Build it once from a
DSN, then check connections out with `pool.connect()`. Checkout failures map
onto the DBAPI2 hierarchy — a backend connect error reuses the query-error
mapping (and its `pgcode`), while a timeout / closed pool becomes
`OperationalError` — so `connect()` behaves like psycopg2's for callers.

Adds live-Postgres tests (gated on SYNAPSE_TEST_POSTGRES_DSN) asserting which
connections end up back in the pool, and drives the Python test suite through a
pool in `setUp`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3G4M92AmwSSZCbmtMJU3d
2026-07-06 09:45:16 +00:00
Erik JohnstonandClaude Opus 4.8 bd6705cf3e Add backend-agnostic Rust-native query helpers over the pool
Introduce a small enum-based layer so simple queries can be written once
regardless of backend: DbPool/DbConn dispatch over each backend's own
pool, and DbConn::query(sql, params) binds positional `?` placeholders
and returns rows as backend-agnostic DbValue cells (read out with
DbRowExt::try_get). No trait/async_trait machinery — a closed enum keeps
the pool.get().await? / conn.query(...).await? surface simple.

The Postgres arm rewrites `?` to `$1, $2, ...` (matching
convert_param_style), reuses PgValue for parameter binding, and adds a
Rust-native DbValueFromSql decoder (the non-Python counterpart of
PythonPgFromSql). The shared column-type list is factored into
accepts_column_type so the three mappings can't drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 09:45:16 +00:00
Erik JohnstonandClaude Opus 4.8 fe9a896db9 Add a deadpool-managed pool of tokio-postgres connections
Introduce the native Rust connection pool that both the Rust-native
query path and the Python-facing Connection/Cursor shim will draw from,
so we run a single pool rather than two that could exhaust the server's
connection limit between them.

Uses the generic deadpool::managed pool with our own ConnectionManager
(rather than deadpool-postgres) so connection creation reuses the
existing connect/default-host logic and connection-task spawning. The
pooled item is a plain tokio_postgres::Client, used with the standard
async query functions; recycle() drops connections the driver reports
closed. Live tests are gated on SYNAPSE_TEST_POSTGRES_DSN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 09:45:16 +00:00