Skip to content

Commit efb1ae4

Browse files
fix: add timeout to pool acquire() to prevent deadlocks
When all connections are checked out, pool.acquire() now times out using self._timeout instead of blocking forever. Raises ConnectionError with a descriptive message on timeout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3e3843c commit efb1ae4

2 files changed

Lines changed: 28 additions & 2 deletions

File tree

src/dqliteclient/pool.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,15 @@ async def acquire(self) -> AsyncIterator[DqliteConnection]:
7474

7575
# Wait for one if at max
7676
if conn is None:
77-
conn = await self._pool.get()
77+
try:
78+
conn = await asyncio.wait_for(
79+
self._pool.get(), timeout=self._timeout
80+
)
81+
except TimeoutError:
82+
raise DqliteConnectionError(
83+
f"Timed out waiting for a connection from the pool "
84+
f"(max_size={self._max_size}, timeout={self._timeout}s)"
85+
) from None
7886

7987
try:
8088
# Verify connection is still good

tests/test_pool.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ async def test_acquire_when_closed(self) -> None:
3232
async with pool.acquire():
3333
pass
3434

35-
3635
async def test_cancellation_does_not_leak_connection(self) -> None:
3736
"""Cancelling a task that holds a connection should clean it up."""
3837
import asyncio
@@ -66,6 +65,25 @@ async def hold_connection():
6665
mock_conn.close.assert_called()
6766
assert pool._size < initial_size
6867

68+
async def test_acquire_timeout_when_pool_exhausted(self) -> None:
69+
"""acquire() should timeout, not block forever, when pool is exhausted."""
70+
pool = ConnectionPool(["localhost:9001"], max_size=1, timeout=0.1)
71+
72+
mock_conn = MagicMock()
73+
mock_conn.is_connected = True
74+
mock_conn.connect = AsyncMock()
75+
mock_conn.close = AsyncMock()
76+
77+
with patch.object(pool._cluster, "connect", return_value=mock_conn):
78+
await pool.initialize()
79+
80+
# Check out the only connection
81+
async with pool.acquire():
82+
# Try to acquire another - should timeout
83+
with pytest.raises(DqliteConnectionError, match="[Tt]imed out"):
84+
async with pool.acquire():
85+
pass
86+
6987

7088
class TestConnectionPoolIntegration:
7189
"""Integration tests requiring mocked connections."""

0 commit comments

Comments
 (0)