Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-psycopg-pool for openSUSE:Factory checked in at 2024-01-07 21:41:02 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-psycopg-pool (Old) and /work/SRC/openSUSE:Factory/.python-psycopg-pool.new.28375 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-psycopg-pool" Sun Jan 7 21:41:02 2024 rev:3 rq:1137427 version:3.2.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-psycopg-pool/python-psycopg-pool.changes 2023-12-15 21:48:24.744639866 +0100 +++ /work/SRC/openSUSE:Factory/.python-psycopg-pool.new.28375/python-psycopg-pool.changes 2024-01-07 21:41:20.298280204 +0100 @@ -1,0 +2,10 @@ +Sun Jan 7 19:25:21 UTC 2024 - Dirk Müller <dmuel...@suse.com> + +- update to 3.2.1: + * Respect the timeout parameter on connection() when check fails. + Also avoid a busy-loop of checking; separate check attempts + using an exponential backoff (ticket #709). + * Use typing.Self as a more correct return value annotation of + context managers and other self-returning methods (see ticket + +------------------------------------------------------------------- Old: ---- psycopg-pool-3.2.0.tar.gz New: ---- psycopg-pool-3.2.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-psycopg-pool.spec ++++++ --- /var/tmp/diff_new_pack.G3R6Xm/_old 2024-01-07 21:41:20.818299120 +0100 +++ /var/tmp/diff_new_pack.G3R6Xm/_new 2024-01-07 21:41:20.818299120 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-psycopg-pool # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: python-psycopg-pool -Version: 3.2.0 +Version: 3.2.1 Release: 0 Summary: Connection Pool for Psycopg License: LGPL-3.0-only @@ -28,7 +28,7 @@ BuildRequires: %{python_module wheel >= 0.37} BuildRequires: fdupes BuildRequires: python-rpm-macros -Requires: python-typing-extensions >= 3.10 +Requires: python-typing-extensions >= 4.4 BuildArch: noarch %python_subpackages ++++++ psycopg-pool-3.2.0.tar.gz -> psycopg-pool-3.2.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/PKG-INFO new/psycopg-pool-3.2.1/PKG-INFO --- old/psycopg-pool-3.2.0/PKG-INFO 2023-11-11 16:14:36.800819200 +0100 +++ new/psycopg-pool-3.2.1/PKG-INFO 2024-01-07 13:24:41.375857400 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: psycopg-pool -Version: 3.2.0 +Version: 3.2.1 Summary: Connection Pool for Psycopg Home-page: https://psycopg.org/psycopg3/ Author: Daniele Varrazzo @@ -24,6 +24,8 @@ Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Database Classifier: Topic :: Database :: Front-Ends Classifier: Topic :: Software Development @@ -31,7 +33,7 @@ Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE.txt -Requires-Dist: typing-extensions>=3.10 +Requires-Dist: typing-extensions>=4.4 Psycopg 3: PostgreSQL database adapter for Python - Connection Pool =================================================================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/psycopg_pool/_acompat.py new/psycopg-pool-3.2.1/psycopg_pool/_acompat.py --- old/psycopg-pool-3.2.0/psycopg_pool/_acompat.py 2023-11-11 16:14:26.000000000 +0100 +++ new/psycopg-pool-3.2.1/psycopg_pool/_acompat.py 2024-01-07 13:24:30.000000000 +0100 @@ -10,14 +10,17 @@ from __future__ import annotations +import time import queue import asyncio import logging import threading -from typing import Any, Callable, Coroutine, TypeVar, TYPE_CHECKING +from typing import Any, Callable, Coroutine, TYPE_CHECKING from typing_extensions import TypeAlias +from ._compat import TypeVar + logger = logging.getLogger("psycopg.pool") T = TypeVar("T") @@ -26,6 +29,7 @@ Condition = threading.Condition Lock = threading.RLock ALock = asyncio.Lock +sleep = time.sleep Worker: TypeAlias = threading.Thread AWorker: TypeAlias = "asyncio.Task[None]" @@ -160,3 +164,10 @@ if not t.is_alive(): continue logger.warning("couldn't stop thread %r within %s seconds", t.name, timeout) + + +def asleep(seconds: float) -> Coroutine[Any, Any, None]: + """ + Equivalent to asyncio.sleep(), converted to time.sleep() by async_to_sync. + """ + return asyncio.sleep(seconds) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/psycopg_pool/_compat.py new/psycopg-pool-3.2.1/psycopg_pool/_compat.py --- old/psycopg-pool-3.2.0/psycopg_pool/_compat.py 2023-11-11 16:14:26.000000000 +0100 +++ new/psycopg-pool-3.2.1/psycopg_pool/_compat.py 2024-01-07 13:24:30.000000000 +0100 @@ -14,9 +14,21 @@ else: from typing import Counter, Deque +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + +if sys.version_info >= (3, 13): + from typing import TypeVar +else: + from typing_extensions import TypeVar + __all__ = [ "Counter", "Deque", + "Self", + "TypeVar", ] # Workaround for psycopg < 3.0.8. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/psycopg_pool/abc.py new/psycopg-pool-3.2.1/psycopg_pool/abc.py --- old/psycopg-pool-3.2.0/psycopg_pool/abc.py 2023-11-11 16:14:26.000000000 +0100 +++ new/psycopg-pool-3.2.1/psycopg_pool/abc.py 2024-01-07 13:24:30.000000000 +0100 @@ -6,18 +6,21 @@ from __future__ import annotations -from typing import Any, Awaitable, Callable, TypeVar, Union, TYPE_CHECKING +from typing import Any, Awaitable, Callable, Union, TYPE_CHECKING from typing_extensions import TypeAlias +from ._compat import TypeVar + if TYPE_CHECKING: from .pool import ConnectionPool from .pool_async import AsyncConnectionPool - from psycopg import Connection, AsyncConnection + from psycopg import Connection, AsyncConnection # noqa: F401 + from psycopg.rows import TupleRow # noqa: F401 # Connection types to make the pool generic -CT = TypeVar("CT", bound="Connection[Any]") -ACT = TypeVar("ACT", bound="AsyncConnection[Any]") +CT = TypeVar("CT", bound="Connection[Any]", default="Connection[TupleRow]") +ACT = TypeVar("ACT", bound="AsyncConnection[Any]", default="AsyncConnection[TupleRow]") # Callbacks taking a connection from the pool ConnectionCB: TypeAlias = Callable[[CT], None] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/psycopg_pool/base.py new/psycopg-pool-3.2.1/psycopg_pool/base.py --- old/psycopg-pool-3.2.0/psycopg_pool/base.py 2023-11-11 16:14:26.000000000 +0100 +++ new/psycopg-pool-3.2.1/psycopg_pool/base.py 2024-01-07 13:24:30.000000000 +0100 @@ -200,22 +200,24 @@ conn._expire_at = monotonic() + self._jitter(self.max_lifetime, -0.05, 0.0) -class ConnectionAttempt: - """Keep the state of a connection attempt.""" +class AttemptWithBackoff: + """ + Keep the state of a repeated operation attempt with exponential backoff. + """ INITIAL_DELAY = 1.0 DELAY_JITTER = 0.1 DELAY_BACKOFF = 2.0 - def __init__(self, *, reconnect_timeout: float): - self.reconnect_timeout = reconnect_timeout + def __init__(self, *, timeout: float): + self.timeout = timeout self.delay = 0.0 self.give_up_at = 0.0 def update_delay(self, now: float) -> None: """Calculate how long to wait for a new connection attempt""" if self.delay == 0.0: - self.give_up_at = now + self.reconnect_timeout + self.give_up_at = now + self.timeout self.delay = BasePool._jitter( self.INITIAL_DELAY, -self.DELAY_JITTER, self.DELAY_JITTER ) @@ -226,5 +228,5 @@ self.delay = max(0.0, self.give_up_at - now) def time_to_give_up(self, now: float) -> bool: - """Return True if we are tired of trying to connect. Meh.""" + """Return True if we are tired of trying this attempt. Meh.""" return self.give_up_at > 0.0 and now >= self.give_up_at diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/psycopg_pool/null_pool.py new/psycopg-pool-3.2.1/psycopg_pool/null_pool.py --- old/psycopg-pool-3.2.0/psycopg_pool/null_pool.py 2023-11-11 16:14:26.000000000 +0100 +++ new/psycopg-pool-3.2.1/psycopg_pool/null_pool.py 2024-01-07 13:24:30.000000000 +0100 @@ -10,11 +10,10 @@ from __future__ import annotations import logging -from typing import Any, cast, Dict, Optional, overload, Type +from typing import Any, cast, Dict, Optional, Type from psycopg import Connection from psycopg.pq import TransactionStatus -from psycopg.rows import TupleRow from .abc import CT, ConnectionCB, ConnectFailedCB from .errors import PoolTimeout, TooManyRequests @@ -27,53 +26,6 @@ class NullConnectionPool(_BaseNullConnectionPool, ConnectionPool[CT]): - @overload - def __init__( - self: NullConnectionPool[Connection[TupleRow]], - conninfo: str = "", - *, - kwargs: Optional[Dict[str, Any]] = ..., - min_size: int = ..., - max_size: Optional[int] = ..., - open: bool | None = ..., - configure: Optional[ConnectionCB[CT]] = ..., - check: Optional[ConnectionCB[CT]] = ..., - reset: Optional[ConnectionCB[CT]] = ..., - name: Optional[str] = ..., - timeout: float = ..., - max_waiting: int = ..., - max_lifetime: float = ..., - max_idle: float = ..., - reconnect_timeout: float = ..., - reconnect_failed: Optional[ConnectFailedCB] = ..., - num_workers: int = ..., - ): - ... - - @overload - def __init__( - self: NullConnectionPool[CT], - conninfo: str = "", - *, - connection_class: Type[CT] = ..., - kwargs: Optional[Dict[str, Any]] = ..., - min_size: int = ..., - max_size: Optional[int] = ..., - open: bool | None = ..., - configure: Optional[ConnectionCB[CT]] = ..., - check: Optional[ConnectionCB[CT]] = ..., - reset: Optional[ConnectionCB[CT]] = ..., - name: Optional[str] = ..., - timeout: float = ..., - max_waiting: int = ..., - max_lifetime: float = ..., - max_idle: float = ..., - reconnect_timeout: float = ..., - reconnect_failed: Optional[ConnectFailedCB] = ..., - num_workers: int = ..., - ): - ... - def __init__( self, conninfo: str = "", @@ -144,6 +96,9 @@ logger.info("pool %r is ready to use", self.name) def _get_ready_connection(self, timeout: Optional[float]) -> Optional[CT]: + if timeout is not None and timeout <= 0.0: + raise PoolTimeout() + conn: Optional[CT] = None if self.max_size == 0 or self._nconns < self.max_size: # Create a new connection for the client diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/psycopg_pool/null_pool_async.py new/psycopg-pool-3.2.1/psycopg_pool/null_pool_async.py --- old/psycopg-pool-3.2.0/psycopg_pool/null_pool_async.py 2023-11-11 16:14:26.000000000 +0100 +++ new/psycopg-pool-3.2.1/psycopg_pool/null_pool_async.py 2024-01-07 13:24:30.000000000 +0100 @@ -7,11 +7,10 @@ from __future__ import annotations import logging -from typing import Any, cast, Dict, Optional, overload, Type +from typing import Any, cast, Dict, Optional, Type from psycopg import AsyncConnection from psycopg.pq import TransactionStatus -from psycopg.rows import TupleRow from .abc import ACT, AsyncConnectionCB, AsyncConnectFailedCB from .errors import PoolTimeout, TooManyRequests @@ -24,53 +23,6 @@ class AsyncNullConnectionPool(_BaseNullConnectionPool, AsyncConnectionPool[ACT]): - @overload - def __init__( - self: AsyncNullConnectionPool[AsyncConnection[TupleRow]], - conninfo: str = "", - *, - kwargs: Optional[Dict[str, Any]] = ..., - min_size: int = ..., - max_size: Optional[int] = ..., - open: bool | None = ..., - configure: Optional[AsyncConnectionCB[ACT]] = ..., - check: Optional[AsyncConnectionCB[ACT]] = ..., - reset: Optional[AsyncConnectionCB[ACT]] = ..., - name: Optional[str] = ..., - timeout: float = ..., - max_waiting: int = ..., - max_lifetime: float = ..., - max_idle: float = ..., - reconnect_timeout: float = ..., - reconnect_failed: Optional[AsyncConnectFailedCB] = ..., - num_workers: int = ..., - ): - ... - - @overload - def __init__( - self: AsyncNullConnectionPool[ACT], - conninfo: str = "", - *, - connection_class: Type[ACT] = ..., - kwargs: Optional[Dict[str, Any]] = ..., - min_size: int = ..., - max_size: Optional[int] = ..., - open: bool | None = ..., - configure: Optional[AsyncConnectionCB[ACT]] = ..., - check: Optional[AsyncConnectionCB[ACT]] = ..., - reset: Optional[AsyncConnectionCB[ACT]] = ..., - name: Optional[str] = ..., - timeout: float = ..., - max_waiting: int = ..., - max_lifetime: float = ..., - max_idle: float = ..., - reconnect_timeout: float = ..., - reconnect_failed: Optional[AsyncConnectFailedCB] = ..., - num_workers: int = ..., - ): - ... - def __init__( self, conninfo: str = "", @@ -141,6 +93,9 @@ logger.info("pool %r is ready to use", self.name) async def _get_ready_connection(self, timeout: Optional[float]) -> Optional[ACT]: + if timeout is not None and timeout <= 0.0: + raise PoolTimeout() + conn: Optional[ACT] = None if self.max_size == 0 or self._nconns < self.max_size: # Create a new connection for the client diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/psycopg_pool/pool.py new/psycopg-pool-3.2.1/psycopg_pool/pool.py --- old/psycopg-pool-3.2.0/psycopg_pool/pool.py 2023-11-11 16:14:26.000000000 +0100 +++ new/psycopg-pool-3.2.1/psycopg_pool/pool.py 2024-01-07 13:24:30.000000000 +0100 @@ -15,21 +15,20 @@ from time import monotonic from types import TracebackType from typing import Any, Iterator, cast, Dict, Generic, List -from typing import Optional, overload, Sequence, Type, TypeVar +from typing import Optional, Sequence, Type from weakref import ref from contextlib import contextmanager from psycopg import errors as e from psycopg import Connection from psycopg.pq import TransactionStatus -from psycopg.rows import TupleRow from .abc import CT, ConnectionCB, ConnectFailedCB -from .base import ConnectionAttempt, BasePool +from .base import AttemptWithBackoff, BasePool from .errors import PoolClosed, PoolTimeout, TooManyRequests -from ._compat import Deque +from ._compat import Deque, Self from ._acompat import Condition, Event, Lock, Queue, Worker, spawn, gather -from ._acompat import current_thread_name +from ._acompat import sleep, current_thread_name from .sched import Scheduler @@ -37,56 +36,8 @@ class ConnectionPool(Generic[CT], BasePool): - _Self = TypeVar("_Self", bound="ConnectionPool[CT]") _pool: Deque[CT] - @overload - def __init__( - self: ConnectionPool[Connection[TupleRow]], - conninfo: str = "", - *, - kwargs: Optional[Dict[str, Any]] = ..., - min_size: int = ..., - max_size: Optional[int] = ..., - open: bool | None = ..., - configure: Optional[ConnectionCB[CT]] = ..., - check: Optional[ConnectionCB[CT]] = ..., - reset: Optional[ConnectionCB[CT]] = ..., - name: Optional[str] = ..., - timeout: float = ..., - max_waiting: int = ..., - max_lifetime: float = ..., - max_idle: float = ..., - reconnect_timeout: float = ..., - reconnect_failed: Optional[ConnectFailedCB] = ..., - num_workers: int = ..., - ): - ... - - @overload - def __init__( - self: ConnectionPool[CT], - conninfo: str = "", - *, - connection_class: Type[CT] = ..., - kwargs: Optional[Dict[str, Any]] = ..., - min_size: int = ..., - max_size: Optional[int] = ..., - open: bool | None = ..., - configure: Optional[ConnectionCB[CT]] = ..., - check: Optional[ConnectionCB[CT]] = ..., - reset: Optional[ConnectionCB[CT]] = ..., - name: Optional[str] = ..., - timeout: float = ..., - max_waiting: int = ..., - max_lifetime: float = ..., - max_idle: float = ..., - reconnect_timeout: float = ..., - reconnect_failed: Optional[ConnectFailedCB] = ..., - num_workers: int = ..., - ): - ... - def __init__( self, conninfo: str = "", @@ -236,10 +187,9 @@ failing to do so will deplete the pool. A depleted pool is a sad pool: you don't want a depleted pool. """ - t0 = monotonic() if timeout is None: timeout = self.timeout - deadline = t0 + timeout + deadline = monotonic() + timeout logger.info("connection requested from %r", self.name) self._stats[self._REQUESTS_NUM] += 1 @@ -247,15 +197,7 @@ self._check_open_getconn() try: - while True: - conn = self._getconn_unchecked(deadline - monotonic()) - try: - self._check_connection(conn) - except Exception: - self._putconn(conn, from_getconn=True) - else: - logger.info("connection given by %r", self.name) - return conn + return self._getconn_with_check_loop(deadline) # Re-raise the timeout exception presenting the user the global # timeout, not the per-attempt one. except PoolTimeout: @@ -263,6 +205,32 @@ f"couldn't get a connection after {timeout:.2f} sec" ) from None + def _getconn_with_check_loop(self, deadline: float) -> CT: + attempt: AttemptWithBackoff | None = None + + while True: + conn = self._getconn_unchecked(deadline - monotonic()) + try: + self._check_connection(conn) + except Exception: + self._putconn(conn, from_getconn=True) + else: + logger.info("connection given by %r", self.name) + return conn + + # Delay further checks to avoid a busy loop, using the same + # backoff policy used in reconnection attempts. + now = monotonic() + if not attempt: + attempt = AttemptWithBackoff(timeout=deadline - now) + else: + attempt.update_delay(now) + + if attempt.time_to_give_up(now): + raise PoolTimeout() + else: + sleep(attempt.delay) + def _getconn_unchecked(self, timeout: float) -> CT: # Critical section: decide here if there's a connection ready # or if the client needs to wait. @@ -298,6 +266,9 @@ def _get_ready_connection(self, timeout: Optional[float]) -> Optional[CT]: """Return a connection, if the client deserves one.""" + if timeout is not None and timeout <= 0.0: + raise PoolTimeout() + conn: Optional[CT] = None if self._pool: # Take a connection ready out of the pool @@ -491,7 +462,7 @@ sched_runner, self._sched_runner = (self._sched_runner, None) gather(sched_runner, *workers, timeout=timeout) - def __enter__(self: _Self) -> _Self: + def __enter__(self) -> Self: self._open_implicit = False self.open() return self @@ -626,7 +597,7 @@ kwargs["connect_timeout"] = max(round(timeout), 1) t0 = monotonic() try: - conn: CT = cast(CT, self.connection_class.connect(self.conninfo, **kwargs)) + conn = self.connection_class.connect(self.conninfo, **kwargs) except Exception: self._stats[self._CONNECTIONS_ERRORS] += 1 raise @@ -651,7 +622,7 @@ return conn def _add_connection( - self, attempt: Optional[ConnectionAttempt], growing: bool = False + self, attempt: Optional[AttemptWithBackoff], growing: bool = False ) -> None: """Try to connect and add the connection to the pool. @@ -662,7 +633,7 @@ """ now = monotonic() if not attempt: - attempt = ConnectionAttempt(reconnect_timeout=self.reconnect_timeout) + attempt = AttemptWithBackoff(timeout=self.reconnect_timeout) try: conn = self._connect() @@ -957,7 +928,7 @@ def __init__( self, pool: ConnectionPool[Any], - attempt: Optional[ConnectionAttempt] = None, + attempt: Optional[AttemptWithBackoff] = None, growing: bool = False, ): super().__init__(pool) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/psycopg_pool/pool_async.py new/psycopg-pool-3.2.1/psycopg_pool/pool_async.py --- old/psycopg-pool-3.2.0/psycopg_pool/pool_async.py 2023-11-11 16:14:26.000000000 +0100 +++ new/psycopg-pool-3.2.1/psycopg_pool/pool_async.py 2024-01-07 13:24:30.000000000 +0100 @@ -12,21 +12,20 @@ from time import monotonic from types import TracebackType from typing import Any, AsyncIterator, cast, Dict, Generic, List -from typing import Optional, overload, Sequence, Type, TypeVar +from typing import Optional, Sequence, Type from weakref import ref from contextlib import asynccontextmanager from psycopg import errors as e from psycopg import AsyncConnection from psycopg.pq import TransactionStatus -from psycopg.rows import TupleRow from .abc import ACT, AsyncConnectionCB, AsyncConnectFailedCB -from .base import ConnectionAttempt, BasePool +from .base import AttemptWithBackoff, BasePool from .errors import PoolClosed, PoolTimeout, TooManyRequests -from ._compat import Deque +from ._compat import Deque, Self from ._acompat import ACondition, AEvent, ALock, AQueue, AWorker, aspawn, agather -from ._acompat import current_task_name +from ._acompat import asleep, current_task_name from .sched_async import AsyncScheduler if True: # ASYNC @@ -36,56 +35,8 @@ class AsyncConnectionPool(Generic[ACT], BasePool): - _Self = TypeVar("_Self", bound="AsyncConnectionPool[ACT]") _pool: Deque[ACT] - @overload - def __init__( - self: AsyncConnectionPool[AsyncConnection[TupleRow]], - conninfo: str = "", - *, - kwargs: Optional[Dict[str, Any]] = ..., - min_size: int = ..., - max_size: Optional[int] = ..., - open: bool | None = ..., - configure: Optional[AsyncConnectionCB[ACT]] = ..., - check: Optional[AsyncConnectionCB[ACT]] = ..., - reset: Optional[AsyncConnectionCB[ACT]] = ..., - name: Optional[str] = ..., - timeout: float = ..., - max_waiting: int = ..., - max_lifetime: float = ..., - max_idle: float = ..., - reconnect_timeout: float = ..., - reconnect_failed: Optional[AsyncConnectFailedCB] = ..., - num_workers: int = ..., - ): - ... - - @overload - def __init__( - self: AsyncConnectionPool[ACT], - conninfo: str = "", - *, - connection_class: Type[ACT] = ..., - kwargs: Optional[Dict[str, Any]] = ..., - min_size: int = ..., - max_size: Optional[int] = ..., - open: bool | None = ..., - configure: Optional[AsyncConnectionCB[ACT]] = ..., - check: Optional[AsyncConnectionCB[ACT]] = ..., - reset: Optional[AsyncConnectionCB[ACT]] = ..., - name: Optional[str] = ..., - timeout: float = ..., - max_waiting: int = ..., - max_lifetime: float = ..., - max_idle: float = ..., - reconnect_timeout: float = ..., - reconnect_failed: Optional[AsyncConnectFailedCB] = ..., - num_workers: int = ..., - ): - ... - def __init__( self, conninfo: str = "", @@ -256,10 +207,9 @@ failing to do so will deplete the pool. A depleted pool is a sad pool: you don't want a depleted pool. """ - t0 = monotonic() if timeout is None: timeout = self.timeout - deadline = t0 + timeout + deadline = monotonic() + timeout logger.info("connection requested from %r", self.name) self._stats[self._REQUESTS_NUM] += 1 @@ -267,15 +217,7 @@ self._check_open_getconn() try: - while True: - conn = await self._getconn_unchecked(deadline - monotonic()) - try: - await self._check_connection(conn) - except Exception: - await self._putconn(conn, from_getconn=True) - else: - logger.info("connection given by %r", self.name) - return conn + return await self._getconn_with_check_loop(deadline) # Re-raise the timeout exception presenting the user the global # timeout, not the per-attempt one. @@ -284,6 +226,32 @@ f"couldn't get a connection after {timeout:.2f} sec" ) from None + async def _getconn_with_check_loop(self, deadline: float) -> ACT: + attempt: AttemptWithBackoff | None = None + + while True: + conn = await self._getconn_unchecked(deadline - monotonic()) + try: + await self._check_connection(conn) + except Exception: + await self._putconn(conn, from_getconn=True) + else: + logger.info("connection given by %r", self.name) + return conn + + # Delay further checks to avoid a busy loop, using the same + # backoff policy used in reconnection attempts. + now = monotonic() + if not attempt: + attempt = AttemptWithBackoff(timeout=deadline - now) + else: + attempt.update_delay(now) + + if attempt.time_to_give_up(now): + raise PoolTimeout() + else: + await asleep(attempt.delay) + async def _getconn_unchecked(self, timeout: float) -> ACT: # Critical section: decide here if there's a connection ready # or if the client needs to wait. @@ -319,6 +287,9 @@ async def _get_ready_connection(self, timeout: Optional[float]) -> Optional[ACT]: """Return a connection, if the client deserves one.""" + if timeout is not None and timeout <= 0.0: + raise PoolTimeout() + conn: Optional[ACT] = None if self._pool: # Take a connection ready out of the pool @@ -519,7 +490,7 @@ sched_runner, self._sched_runner = self._sched_runner, None await agather(sched_runner, *workers, timeout=timeout) - async def __aenter__(self: _Self) -> _Self: + async def __aenter__(self) -> Self: self._open_implicit = False await self.open() return self @@ -670,9 +641,7 @@ kwargs["connect_timeout"] = max(round(timeout), 1) t0 = monotonic() try: - conn: ACT = cast( - ACT, await self.connection_class.connect(self.conninfo, **kwargs) - ) + conn = await self.connection_class.connect(self.conninfo, **kwargs) except Exception: self._stats[self._CONNECTIONS_ERRORS] += 1 raise @@ -697,7 +666,7 @@ return conn async def _add_connection( - self, attempt: Optional[ConnectionAttempt], growing: bool = False + self, attempt: Optional[AttemptWithBackoff], growing: bool = False ) -> None: """Try to connect and add the connection to the pool. @@ -708,7 +677,7 @@ """ now = monotonic() if not attempt: - attempt = ConnectionAttempt(reconnect_timeout=self.reconnect_timeout) + attempt = AttemptWithBackoff(timeout=self.reconnect_timeout) try: conn = await self._connect() @@ -1008,7 +977,7 @@ def __init__( self, pool: AsyncConnectionPool[Any], - attempt: Optional[ConnectionAttempt] = None, + attempt: Optional[AttemptWithBackoff] = None, growing: bool = False, ): super().__init__(pool) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/psycopg_pool.egg-info/PKG-INFO new/psycopg-pool-3.2.1/psycopg_pool.egg-info/PKG-INFO --- old/psycopg-pool-3.2.0/psycopg_pool.egg-info/PKG-INFO 2023-11-11 16:14:36.000000000 +0100 +++ new/psycopg-pool-3.2.1/psycopg_pool.egg-info/PKG-INFO 2024-01-07 13:24:41.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: psycopg-pool -Version: 3.2.0 +Version: 3.2.1 Summary: Connection Pool for Psycopg Home-page: https://psycopg.org/psycopg3/ Author: Daniele Varrazzo @@ -24,6 +24,8 @@ Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Database Classifier: Topic :: Database :: Front-Ends Classifier: Topic :: Software Development @@ -31,7 +33,7 @@ Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE.txt -Requires-Dist: typing-extensions>=3.10 +Requires-Dist: typing-extensions>=4.4 Psycopg 3: PostgreSQL database adapter for Python - Connection Pool =================================================================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/psycopg_pool.egg-info/requires.txt new/psycopg-pool-3.2.1/psycopg_pool.egg-info/requires.txt --- old/psycopg-pool-3.2.0/psycopg_pool.egg-info/requires.txt 2023-11-11 16:14:36.000000000 +0100 +++ new/psycopg-pool-3.2.1/psycopg_pool.egg-info/requires.txt 2024-01-07 13:24:41.000000000 +0100 @@ -1 +1 @@ -typing-extensions>=3.10 +typing-extensions>=4.4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/psycopg-pool-3.2.0/setup.cfg new/psycopg-pool-3.2.1/setup.cfg --- old/psycopg-pool-3.2.0/setup.cfg 2023-11-11 16:14:36.804819000 +0100 +++ new/psycopg-pool-3.2.1/setup.cfg 2024-01-07 13:24:41.375857400 +0100 @@ -5,7 +5,7 @@ author = Daniele Varrazzo author_email = daniele.varra...@gmail.com license = GNU Lesser General Public License v3 (LGPLv3) -version = 3.2.0 +version = 3.2.1 project_urls = Homepage = https://psycopg.org/ Documentation = https://www.psycopg.org/psycopg3/docs/advanced/pool.html @@ -26,6 +26,8 @@ Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: Implementation :: CPython + Programming Language :: Python :: Implementation :: PyPy Topic :: Database Topic :: Database :: Front-Ends Topic :: Software Development @@ -39,7 +41,7 @@ packages = find: zip_safe = False install_requires = - typing-extensions >= 3.10 + typing-extensions >= 4.4 [options.package_data] psycopg_pool = py.typed