Skip to content

Commit 7ee7bfd

Browse files
fix: set pool _initialized flag after loop, not before
If _create_connection() failed during initialize(), the flag was already True, making initialize() permanently a no-op. The pool was stuck empty with no recovery path. Now the flag is set only after all connections are successfully created. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 57e28f5 commit 7ee7bfd

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

src/dqliteclient/pool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ async def initialize(self) -> None:
5050
"""Initialize the pool with minimum connections."""
5151
if self._initialized:
5252
return
53-
self._initialized = True
5453
for _ in range(self._min_size):
5554
conn = await self._create_connection()
5655
await self._pool.put(conn)
56+
self._initialized = True
5757

5858
async def _create_connection(self) -> DqliteConnection:
5959
"""Create a new connection to the leader."""

tests/test_pool.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,36 @@ async def mock_connect(**kwargs):
5656

5757
await pool.close()
5858

59+
async def test_initialize_retryable_after_failure(self) -> None:
60+
"""If initialize() fails, it should be retryable (not permanently stuck)."""
61+
pool = ConnectionPool(["localhost:9001"], min_size=1, max_size=5)
62+
63+
call_count = 0
64+
65+
async def fail_then_succeed(**kwargs):
66+
nonlocal call_count
67+
call_count += 1
68+
if call_count == 1:
69+
raise OSError("Connection refused")
70+
mock_conn = MagicMock()
71+
mock_conn.is_connected = True
72+
mock_conn.connect = AsyncMock()
73+
mock_conn.close = AsyncMock()
74+
return mock_conn
75+
76+
with patch.object(pool._cluster, "connect", side_effect=fail_then_succeed):
77+
# First init fails
78+
with pytest.raises(OSError):
79+
await pool.initialize()
80+
81+
# Second init should work (not be a no-op)
82+
await pool.initialize()
83+
84+
assert call_count == 2 # First failed, second succeeded
85+
assert pool._size == 1
86+
87+
await pool.close()
88+
5989
async def test_cancellation_does_not_leak_connection(self) -> None:
6090
"""Cancelling a task that holds a connection should clean it up."""
6191
import asyncio

0 commit comments

Comments
 (0)