Skip to content

Commit 7acca14

Browse files
SAY-5PsiACE
andauthored
fix: assign receive-loop attributes before spawning recv task (#102)
* fix: assign receive-loop attributes before spawning recv task * fix: guard eager task regression test by Python version --------- Co-authored-by: Chojan Shang <psiace@apache.org>
1 parent fd3de6e commit 7acca14

2 files changed

Lines changed: 31 additions & 2 deletions

File tree

src/acp/connection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ def __init__(
8686
self._closed = False
8787
self._disconnected = False
8888
self._sender = (sender_factory or self._default_sender_factory)(self._writer, self._tasks)
89+
self._observers: list[StreamObserver] = list(observers or [])
90+
self._receive_timeout = receive_timeout
8991
if listening:
9092
self._recv_task = self._tasks.create(
9193
self._receive_loop(),
@@ -103,8 +105,6 @@ def __init__(
103105
self._run_notification,
104106
)
105107
self._dispatcher.start()
106-
self._observers: list[StreamObserver] = list(observers or [])
107-
self._receive_timeout = receive_timeout
108108

109109
async def close(self) -> None:
110110
"""Stop the receive loop and cancel any in-flight handler tasks."""

tests/test_rpc.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,3 +676,32 @@ async def test_spawn_agent_process_roundtrip(tmp_path):
676676
assert test_client.notifications
677677

678678
assert process.returncode is not None
679+
680+
681+
@pytest.mark.asyncio
682+
async def test_connection_init_under_eager_task_factory(server):
683+
eager_task_factory = getattr(asyncio, "eager_task_factory", None)
684+
if eager_task_factory is None:
685+
pytest.skip("asyncio.eager_task_factory requires Python 3.12+")
686+
687+
# Regression: under asyncio.eager_task_factory the receive loop runs synchronously
688+
# up to its first await inside Connection.__init__, so every attribute it reads
689+
# (e.g. _receive_timeout) must be assigned before _tasks.create(_receive_loop()).
690+
loop = asyncio.get_running_loop()
691+
previous_factory = loop.get_task_factory()
692+
loop.set_task_factory(eager_task_factory)
693+
try:
694+
conn = Connection(
695+
lambda method, params, is_notification: None,
696+
server.client_writer,
697+
server.client_reader,
698+
receive_timeout=0.5,
699+
)
700+
finally:
701+
loop.set_task_factory(previous_factory)
702+
703+
assert conn._receive_timeout == 0.5
704+
# Let the loop tick once so any deferred receive-task crash would land.
705+
await asyncio.sleep(0)
706+
assert conn._disconnected is False
707+
await conn.close()

0 commit comments

Comments
 (0)