From: Daniele Varrazzo Date: Thu, 6 Jan 2022 21:16:09 +0000 (+0100) Subject: Allow pools to have min_size = 0 as long as they can grow X-Git-Tag: pool-3.1~39 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8be9776c3a59c2b5ceb5d2dfb4117fb9fa4717bf;p=thirdparty%2Fpsycopg.git Allow pools to have min_size = 0 as long as they can grow Add tests to verify they can grow from 0 no problem. --- diff --git a/docs/news_pool.rst b/docs/news_pool.rst index dd055345e..7cfed1d91 100644 --- a/docs/news_pool.rst +++ b/docs/news_pool.rst @@ -20,9 +20,9 @@ psycopg_pool 3.1.0 (unreleased) psycopg_pool 3.0.3 (unreleased) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- Throw `!ValueError` if the pool `!min_size` is set to 0 (instead of - hanging). -- Throw `!PoolClosed` calling `~ConnectionPool.wait()` on a closed pool. +- Throw `!ValueError` if `ConnectionPool` `!min_size` and `!max_size` are both + set to 0 (instead of hanging). +- Throw `PoolClosed` calling `~ConnectionPool.wait()` on a closed pool. Current release diff --git a/psycopg_pool/psycopg_pool/base.py b/psycopg_pool/psycopg_pool/base.py index 251256a57..7c9d96223 100644 --- a/psycopg_pool/psycopg_pool/base.py +++ b/psycopg_pool/psycopg_pool/base.py @@ -121,13 +121,17 @@ class BasePool(Generic[ConnectionType]): def _check_size( self, min_size: int, max_size: Optional[int] ) -> Tuple[int, int]: - if min_size <= 0: - raise ValueError("min_size must be greater than 0") + if min_size < 0: + raise ValueError("min_size cannot be negative") if max_size is None: max_size = min_size if max_size < min_size: raise ValueError("max_size must be greater or equal than min_size") + if min_size == max_size == 0: + raise ValueError( + "if min_size is 0 max_size must be greater or than 0" + ) return min_size, max_size diff --git a/tests/pool/test_pool.py b/tests/pool/test_pool.py index 1a57367bf..14297cdbf 100644 --- a/tests/pool/test_pool.py +++ b/tests/pool/test_pool.py @@ -40,16 +40,16 @@ def test_defaults(dsn): assert p.num_workers == 3 -def test_min_size_max_size(dsn): - with pool.ConnectionPool(dsn, min_size=2) as p: - assert p.min_size == p.max_size == 2 - - with pool.ConnectionPool(dsn, min_size=2, max_size=4) as p: - assert p.min_size == 2 - assert p.max_size == 4 +@pytest.mark.parametrize("min_size, max_size", [(2, None), (0, 2), (2, 4)]) +def test_min_size_max_size(dsn, min_size, max_size): + with pool.ConnectionPool(dsn, min_size=min_size, max_size=max_size) as p: + assert p.min_size == min_size + assert p.max_size == max_size if max_size is not None else min_size -@pytest.mark.parametrize("min_size, max_size", [(0, 0), (-1, None), (4, 2)]) +@pytest.mark.parametrize( + "min_size, max_size", [(0, 0), (0, None), (-1, None), (4, 2)] +) def test_bad_size(dsn, min_size, max_size): with pytest.raises(ValueError): pool.ConnectionPool(min_size=min_size, max_size=max_size) @@ -762,31 +762,40 @@ def test_reopen(dsn): @pytest.mark.slow @pytest.mark.timing -def test_grow(dsn, monkeypatch, retries): +@pytest.mark.parametrize( + "min_size, want_times", + [ + (2, [0.25, 0.25, 0.35, 0.45, 0.50, 0.50, 0.60, 0.70]), + (0, [0.35, 0.45, 0.55, 0.60, 0.65, 0.70, 0.80, 0.85]), + ], +) +def test_grow(dsn, monkeypatch, retries, min_size, want_times): delay_connection(monkeypatch, 0.1) def worker(n): t0 = time() with p.connection() as conn: - conn.execute("select 1 from pg_sleep(0.2)") + conn.execute("select 1 from pg_sleep(0.25)") t1 = time() results.append((n, t1 - t0)) for retry in retries: with retry: with pool.ConnectionPool( - dsn, min_size=2, max_size=4, num_workers=3 + dsn, min_size=min_size, max_size=4, num_workers=3 ) as p: p.wait(1.0) results: List[Tuple[int, float]] = [] - ts = [Thread(target=worker, args=(i,)) for i in range(6)] + ts = [ + Thread(target=worker, args=(i,)) + for i in range(len(want_times)) + ] for t in ts: t.start() for t in ts: t.join() - want_times = [0.2, 0.2, 0.3, 0.4, 0.4, 0.4] times = [item[1] for item in results] for got, want in zip(times, want_times): assert got == pytest.approx(want, 0.1), times diff --git a/tests/pool/test_pool_async.py b/tests/pool/test_pool_async.py index 4d37bef77..7d75d7ba6 100644 --- a/tests/pool/test_pool_async.py +++ b/tests/pool/test_pool_async.py @@ -35,16 +35,18 @@ async def test_defaults(dsn): assert p.num_workers == 3 -async def test_min_size_max_size(dsn): - async with pool.AsyncConnectionPool(dsn, min_size=2) as p: - assert p.min_size == p.max_size == 2 - - async with pool.AsyncConnectionPool(dsn, min_size=2, max_size=4) as p: - assert p.min_size == 2 - assert p.max_size == 4 +@pytest.mark.parametrize("min_size, max_size", [(2, None), (0, 2), (2, 4)]) +async def test_min_size_max_size(dsn, min_size, max_size): + async with pool.AsyncConnectionPool( + dsn, min_size=min_size, max_size=max_size + ) as p: + assert p.min_size == min_size + assert p.max_size == max_size if max_size is not None else min_size -@pytest.mark.parametrize("min_size, max_size", [(0, 0), (-1, None), (4, 2)]) +@pytest.mark.parametrize( + "min_size, max_size", [(0, 0), (0, None), (-1, None), (4, 2)] +) async def test_bad_size(dsn, min_size, max_size): with pytest.raises(ValueError): pool.AsyncConnectionPool(min_size=min_size, max_size=max_size) @@ -738,29 +740,35 @@ async def test_reopen(dsn): @pytest.mark.slow @pytest.mark.timing -async def test_grow(dsn, monkeypatch, retries): +@pytest.mark.parametrize( + "min_size, want_times", + [ + (2, [0.25, 0.25, 0.35, 0.45, 0.50, 0.50, 0.60, 0.70]), + (0, [0.35, 0.45, 0.55, 0.60, 0.65, 0.70, 0.80, 0.85]), + ], +) +async def test_grow(dsn, monkeypatch, retries, min_size, want_times): delay_connection(monkeypatch, 0.1) async def worker(n): t0 = time() async with p.connection() as conn: - await conn.execute("select 1 from pg_sleep(0.2)") + await conn.execute("select 1 from pg_sleep(0.25)") t1 = time() results.append((n, t1 - t0)) async for retry in retries: with retry: async with pool.AsyncConnectionPool( - dsn, min_size=2, max_size=4, num_workers=3 + dsn, min_size=min_size, max_size=4, num_workers=3 ) as p: await p.wait(1.0) ts = [] results: List[Tuple[int, float]] = [] - ts = [create_task(worker(i)) for i in range(6)] + ts = [create_task(worker(i)) for i in range(len(want_times))] await asyncio.gather(*ts) - want_times = [0.2, 0.2, 0.3, 0.4, 0.4, 0.4] times = [item[1] for item in results] for got, want in zip(times, want_times): assert got == pytest.approx(want, 0.1), times