From d59e121db4b0159b3f9f0a0125634274e62ab8af Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Wed, 13 Nov 2024 14:19:12 +0100 Subject: [PATCH] fix(pool): print a warning if gather times out on del In Python 3.13 something changed on interpreter shutdown and now stopping threads on __del__ fails. See #930, #954. --- docs/news_pool.rst | 11 +++++++++++ psycopg_pool/psycopg_pool/_acompat.py | 12 ++++++++++-- psycopg_pool/psycopg_pool/pool.py | 3 ++- psycopg_pool/psycopg_pool/pool_async.py | 6 +++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/news_pool.rst b/docs/news_pool.rst index f5c4ff973..eacba27d5 100644 --- a/docs/news_pool.rst +++ b/docs/news_pool.rst @@ -7,6 +7,17 @@ ``psycopg_pool`` release notes ============================== +Future releases +--------------- + +psycopg_pool 3.2.4 +^^^^^^^^^^^^^^^^^^ + +- Add a hint to the warning printed if threads fail to stop during + ``__del__``, which has been reported happening during interpreter shutdown + on Python 3.13 (see #954). + + Current release --------------- diff --git a/psycopg_pool/psycopg_pool/_acompat.py b/psycopg_pool/psycopg_pool/_acompat.py index d58548515..9081d3ecd 100644 --- a/psycopg_pool/psycopg_pool/_acompat.py +++ b/psycopg_pool/psycopg_pool/_acompat.py @@ -130,7 +130,9 @@ def spawn( return t -async def agather(*tasks: asyncio.Task[Any], timeout: float | None = None) -> None: +async def agather( + *tasks: asyncio.Task[Any], timeout: float | None = None, timeout_hint: str = "" +) -> None: """ Equivalent to asyncio.gather or Thread.join() """ @@ -149,9 +151,13 @@ async def agather(*tasks: asyncio.Task[Any], timeout: float | None = None) -> No if t.done(): continue logger.warning("couldn't stop task %r within %s seconds", t.get_name(), timeout) + if timeout_hint: + logger.warning("hint: %s", timeout_hint) -def gather(*tasks: threading.Thread, timeout: float | None = None) -> None: +def gather( + *tasks: threading.Thread, timeout: float | None = None, timeout_hint: str = "" +) -> None: """ Equivalent to asyncio.gather or Thread.join() """ @@ -162,6 +168,8 @@ def gather(*tasks: threading.Thread, timeout: float | None = None) -> None: if not t.is_alive(): continue logger.warning("couldn't stop thread %r within %s seconds", t.name, timeout) + if timeout_hint: + logger.warning("hint: %s", timeout_hint) def asleep(seconds: float) -> Coroutine[Any, Any, None]: diff --git a/psycopg_pool/psycopg_pool/pool.py b/psycopg_pool/psycopg_pool/pool.py index 4a086827c..e93440e21 100644 --- a/psycopg_pool/psycopg_pool/pool.py +++ b/psycopg_pool/psycopg_pool/pool.py @@ -107,7 +107,8 @@ class ConnectionPool(Generic[CT], BasePool): return workers = self._signal_stop_worker() - gather(*workers, timeout=5.0) + hint = "you can try to call 'close()' explicitly or to use the pool as context manager" + gather(*workers, timeout=5.0, timeout_hint=hint) def _check_open_getconn(self) -> None: super()._check_open_getconn() diff --git a/psycopg_pool/psycopg_pool/pool_async.py b/psycopg_pool/psycopg_pool/pool_async.py index ac6925aed..08b39477e 100644 --- a/psycopg_pool/psycopg_pool/pool_async.py +++ b/psycopg_pool/psycopg_pool/pool_async.py @@ -112,7 +112,11 @@ class AsyncConnectionPool(Generic[ACT], BasePool): return workers = self._signal_stop_worker() - agather(*workers, timeout=5.0) + hint = ( + "you can try to call 'close()' explicitly " + "or to use the pool as context manager" + ) + agather(*workers, timeout=5.0, timeout_hint=hint) def _check_open_getconn(self) -> None: super()._check_open_getconn() -- 2.47.3