fix(gateway): tolerate cancelled channel tasks during shutdown

This commit is contained in:
Xubin Ren
2026-06-22 23:20:45 +08:00
parent f80a78d5a8
commit e624943bac
4 changed files with 66 additions and 0 deletions
+4
View File
@@ -252,6 +252,10 @@ class ChannelManager:
try:
await channel.stop()
logger.info("Stopped {} channel", name)
except asyncio.CancelledError:
if asyncio.current_task() and asyncio.current_task().cancelling():
raise
logger.debug("Channel {} stop task was already cancelled", name)
except Exception:
logger.exception("Error stopping {}", name)
+4
View File
@@ -827,6 +827,10 @@ class WebSocketChannel(BaseChannel):
if self._server_task:
try:
await self._server_task
except asyncio.CancelledError:
if asyncio.current_task() and asyncio.current_task().cancelling():
raise
self.logger.debug("server task was already cancelled during shutdown")
except Exception as e:
self.logger.warning("server task error during shutdown: {}", e)
self._server_task = None
+37
View File
@@ -1222,6 +1222,43 @@ async def test_stop_all_handles_channel_exception():
assert mgr._dispatch_task is None
@pytest.mark.asyncio
async def test_stop_all_handles_channel_stop_cancelled_task():
"""stop_all should treat a channel's already-cancelled internals as stopped."""
class _StopCancelledChannel(BaseChannel):
name = "stopcancelled"
display_name = "Stop Cancelled"
async def start(self) -> None:
pass
async def stop(self) -> None:
raise asyncio.CancelledError("server task cancelled")
async def send(self, msg: OutboundMessage) -> None:
pass
fake_config = SimpleNamespace(
channels=ChannelsConfig(),
providers=SimpleNamespace(groq=SimpleNamespace(api_key="")),
)
mgr = ChannelManager.__new__(ChannelManager)
mgr.config = fake_config
mgr.bus = MessageBus()
next_channel = _StartableChannel(fake_config, mgr.bus)
mgr.channels = {
"stopcancelled": _StopCancelledChannel(fake_config, mgr.bus),
"next": next_channel,
}
mgr._dispatch_task = None
await mgr.stop_all()
assert next_channel.stopped is True
@pytest.mark.asyncio
async def test_start_all_no_channels_logs_warning():
"""start_all should log warning when no channels are enabled."""
+21
View File
@@ -96,6 +96,27 @@ def _basic_handler(bus: Any, **kw: Any) -> GatewayServices:
)
@pytest.mark.asyncio
async def test_stop_treats_cancelled_server_task_as_shutdown() -> None:
channel = _ch(MessageBus())
channel._running = True
channel._stop_event = asyncio.Event()
async def _server_task() -> None:
await asyncio.Event().wait()
task = asyncio.create_task(_server_task())
await asyncio.sleep(0)
task.cancel()
await asyncio.sleep(0)
channel._server_task = task
await channel.stop()
assert channel._server_task is None
assert task.cancelled()
@pytest.fixture()
def bus() -> MagicMock:
b = MagicMock()