From 2372b1ecd119105c05c6aae8d2d0f2471d67ff49 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 begin() fails (lockstep w/ psql-1) same acquire-then-fail leak as psql: begin() failing after acquire leaked the conn. release on failure. verified against MariaDB: 6 forced begin-failures no longer drain the pool. bump v0.1.0 -> v0.1.1 Signed-off-by: disqualifier --- README.md | 6 +++--- pyproject.toml | 2 +- src/mysql/mysql.py | 10 +++++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a9b585e..c6c40cd 100644 --- a/README.md +++ b/README.md @@ -11,18 +11,18 @@ wire-compatible and share the driver, so this covers both. `requirements.txt`: ``` -mysql @ git+ssh://git@git.rethinkstudios.io/rethink-public/mysql.git@v0.1.0 +mysql @ git+ssh://git@git.rethinkstudios.io/rethink-public/mysql.git@v0.1.1 ``` Direct: ```bash -pip install "mysql @ git+ssh://git@git.rethinkstudios.io/rethink-public/mysql.git@v0.1.0" +pip install "mysql @ git+ssh://git@git.rethinkstudios.io/rethink-public/mysql.git@v0.1.1" ``` Pulls `aiomysql` (which pulls `PyMySQL`). -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 451d2b1..d57e484 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mysql" -version = "0.1.0" +version = "0.1.1" description = "async mysql/mariadb wrapper over aiomysql: two-layer API (friendly verbs + raw escape hatch), fail-loud, config-free" requires-python = ">=3.10" dependencies = [ diff --git a/src/mysql/mysql.py b/src/mysql/mysql.py index c6241ec..5f3cd53 100644 --- a/src/mysql/mysql.py +++ b/src/mysql/mysql.py @@ -373,7 +373,15 @@ class _Transaction: async def __aenter__(self): self._conn = await self._pool.acquire() - await self._conn.begin() + try: + await self._conn.begin() + except BaseException: + # begin() 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. + self._pool.release(self._conn) + self._conn = None + raise return self._conn async def __aexit__(self, exc_type, exc, tb) -> None: