From 5f4eef8b4aba756d32e14ea41f71ef2919c26b84 Mon Sep 17 00:00:00 2001 From: Jordan Pittier Date: Wed, 18 Nov 2020 11:23:12 +0100 Subject: [PATCH] QueuePool: support subsecond timeout Make it explicit in the documentation and in the default value for the 'timeout' parameter that `timeout` can be a float. Because Python timing is not very accurate, warn about the precision. --- lib/sqlalchemy/engine/create.py | 6 ++++-- lib/sqlalchemy/pool/impl.py | 8 +++++--- test/engine/test_pool.py | 13 +++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/sqlalchemy/engine/create.py b/lib/sqlalchemy/engine/create.py index 7f5b5e8f5d..1aa9f12ccf 100644 --- a/lib/sqlalchemy/engine/create.py +++ b/lib/sqlalchemy/engine/create.py @@ -440,9 +440,11 @@ def create_engine(url, **kwargs): :paramref:`_pool.Pool.reset_on_return` - :param pool_timeout=30: number of seconds to wait before giving + :param pool_timeout=30.0: number of seconds to wait before giving up on getting a connection from the pool. This is only used - with :class:`~sqlalchemy.pool.QueuePool`. + with :class:`~sqlalchemy.pool.QueuePool`. This can be a float but is + subject to the limitations of Python time functions which may not be + reliable in the tens of milliseconds. :param pool_use_lifo=False: use LIFO (last-in-first-out) when retrieving connections from :class:`.QueuePool` instead of FIFO diff --git a/lib/sqlalchemy/pool/impl.py b/lib/sqlalchemy/pool/impl.py index fc543053d3..38afbc7a1a 100644 --- a/lib/sqlalchemy/pool/impl.py +++ b/lib/sqlalchemy/pool/impl.py @@ -40,7 +40,7 @@ class QueuePool(Pool): creator, pool_size=5, max_overflow=10, - timeout=30, + timeout=30.0, use_lifo=False, **kw ): @@ -73,7 +73,9 @@ class QueuePool(Pool): connections. Defaults to 10. :param timeout: The number of seconds to wait before giving up - on returning a connection. Defaults to 30. + on returning a connection. Defaults to 30.0. This can be a float + but is subject to the limitations of Python time functions which + may not be reliable in the tens of milliseconds. :param use_lifo: use LIFO (last-in-first-out) when retrieving connections instead of FIFO (first-in-first-out). Using LIFO, a @@ -129,7 +131,7 @@ class QueuePool(Pool): else: raise exc.TimeoutError( "QueuePool limit of size %d overflow %d reached, " - "connection timed out, timeout %d" + "connection timed out, timeout %0.2f" % (self.size(), self.overflow(), self._timeout), code="3o7r", ) diff --git a/test/engine/test_pool.py b/test/engine/test_pool.py index eb705da61a..bf08528f22 100644 --- a/test/engine/test_pool.py +++ b/test/engine/test_pool.py @@ -4,6 +4,8 @@ import threading import time import weakref +import pytest + import sqlalchemy as tsa from sqlalchemy import event from sqlalchemy import pool @@ -866,6 +868,17 @@ class QueuePoolTest(PoolTestBase): assert_raises(tsa.exc.TimeoutError, p.connect) assert int(time.time() - now) == 2 + @testing.requires.timing_intensive + def test_timeout_subsecond_precision(self): + p = self._queuepool_fixture(pool_size=1, max_overflow=0, timeout=0.5) + c1 = p.connect() # noqa + with pytest.raises(tsa.exc.TimeoutError, match=r".* timeout 0.50 .*"): + now = time.time() + c2 = p.connect() # noqa + # Python timing is not very accurate, the time diff should be very + # close to 0.5s but we give 200ms of slack. + assert 0.3 <= time.time() - now <= 0.7, "Pool timeout not respected" + @testing.requires.threading_with_mock @testing.requires.timing_intensive def test_timeout_race(self): -- 2.47.3