fix(pipeline): cancel sibling loops on failure (TaskGroup, no close-during-use)

Pipeline.run() used asyncio.gather, so if one loop raised, the failing
coroutine propagated while the sibling loops kept running detached; aclose()
in the finally then closed a source/stt out from under a still-live loop.
Switch to asyncio.TaskGroup so a failing loop cancels+awaits the siblings
before teardown. Add a regression test asserting an error in the conversation
loop cancels the perception loop and still closes every source.
This commit is contained in:
EJClaw
2026-08-15 20:57:49 +09:00
parent cfad568029
commit 3cc262ed18
2 changed files with 50 additions and 2 deletions

View File

@@ -102,9 +102,16 @@ class Pipeline:
# -- lifecycle --------------------------------------------------------- #
async def run(self) -> None:
loops = [self._perceive(), self._listen_voice(), self._listen_text()]
# A TaskGroup (not bare gather) so that if ONE loop raises, the others
# are cancelled and awaited before teardown. With plain gather the
# failing loop propagated while the siblings kept running detached, and
# aclose() in the finally then closed a source/stt out from under a
# still-live loop (close-during-use).
try:
await asyncio.gather(*loops)
async with asyncio.TaskGroup() as tg:
tg.create_task(self._perceive())
tg.create_task(self._listen_voice())
tg.create_task(self._listen_text())
finally:
await self.aclose()