diff --git a/README.md b/README.md index 957e63b..4993faf 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.6 +psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.7 ``` Direct: ```bash -pip install "psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.6" +pip install "psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.7" ``` Pulls `asyncpg`. -Drop the `@v0.1.6` suffix from the line above to install the latest unpinned. +Drop the `@v0.1.7` suffix from the line above to install the latest unpinned. ## The two-layer API diff --git a/pyproject.toml b/pyproject.toml index 9792361..c7cab2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "psql" -version = "0.1.6" +version = "0.1.7" 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 5728a5f..b511572 100644 --- a/src/psql/psql.py +++ b/src/psql/psql.py @@ -123,23 +123,36 @@ class PsqlDB: """ async with self._connect_lock: if self._pool is not None: - await self.close() - pool = await asyncpg.create_pool(**self._config) + await self._close_locked() + pool = None try: + pool = await asyncpg.create_pool(**self._config) await pool.fetchval("SELECT 1") except _DRIVER_ERRORS: - log.exception("psql.connect() validation failed") - await pool.close() + log.exception("psql.connect() failed") + if pool is not None: + await pool.close() raise except BaseException: - await pool.close() + if pool is not None: + await pool.close() raise self._pool = pool return self async def close(self) -> None: """close the pool on shutdown and null the reference (so pool/connected checks - report not-connected against a dead pool)""" + report not-connected against a dead pool) + + guarded by the same lock as connect() - a close() racing an in-flight connect() + waits for it rather than no-opping against a not-yet-installed pool and leaving + the just-built one live. + """ + async with self._connect_lock: + await self._close_locked() + + async def _close_locked(self) -> None: + """close the pool and null the reference; caller must hold `_connect_lock`""" if self._pool is None: return try: