From 426fad10edd823b192e53d68c87199f8a460091d Mon Sep 17 00:00:00 2001 From: disqualifier Date: Tue, 30 Jun 2026 21:04:43 -0400 Subject: [PATCH] fix: transaction() releases the pooled connection when start() fails (psql-1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _Transaction.__aenter__ acquired a connection then called tx.start(); if start() raised (stale pooled conn after failover/idle-timeout — a fail-loud path), __aexit__ never ran so the connection leaked, draining the pool until transaction() deadlocked on acquire(). now release-on-failure. verified: 6 forced start-failures (2x pool) no longer exhaust the pool. bump v0.1.0 -> v0.1.1 Signed-off-by: disqualifier --- README.md | 6 +++--- pyproject.toml | 2 +- src/psql/psql.py | 12 ++++++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3358c98..d41b164 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,18 @@ a sibling of the `mongo` lib. Class is **`PsqlDB`**. `requirements.txt`: ``` -psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.0 +psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.1 ``` Direct: ```bash -pip install "psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.0" +pip install "psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.1" ``` Pulls `asyncpg`. -Drop the `@v0.1.0` suffix from the line above to install the latest unpinned. +Drop the `@v0.1.1` suffix from the line above to install the latest unpinned. ## The two-layer API diff --git a/pyproject.toml b/pyproject.toml index 1de159a..8579d95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "psql" -version = "0.1.0" +version = "0.1.1" description = "async postgres wrapper over asyncpg: two-layer API (friendly verbs + raw escape hatch), fail-loud, config-free" requires-python = ">=3.10" dependencies = [ diff --git a/src/psql/psql.py b/src/psql/psql.py index d4c4e79..39204c5 100644 --- a/src/psql/psql.py +++ b/src/psql/psql.py @@ -338,8 +338,16 @@ class _Transaction: async def __aenter__(self): self._conn = await self._pool.acquire() - self._tx = self._conn.transaction() - await self._tx.start() + try: + self._tx = self._conn.transaction() + await self._tx.start() + except BaseException: + # start() (or transaction()) failing after acquire would otherwise leak the + # pooled connection — __aexit__ is not called when __aenter__ raises. release + # it and reset so a failed transaction start never burns a pool slot. + await self._pool.release(self._conn) + self._conn = None + raise return self._conn async def __aexit__(self, exc_type, exc, tb) -> None: