mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-06 17:38:35 +00:00
fix(gateway): tolerate cancelled channel tasks during shutdown
This commit is contained in:
parent
f80a78d5a8
commit
e624943bac
@ -252,6 +252,10 @@ class ChannelManager:
|
|||||||
try:
|
try:
|
||||||
await channel.stop()
|
await channel.stop()
|
||||||
logger.info("Stopped {} channel", name)
|
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:
|
except Exception:
|
||||||
logger.exception("Error stopping {}", name)
|
logger.exception("Error stopping {}", name)
|
||||||
|
|
||||||
|
|||||||
@ -827,6 +827,10 @@ class WebSocketChannel(BaseChannel):
|
|||||||
if self._server_task:
|
if self._server_task:
|
||||||
try:
|
try:
|
||||||
await self._server_task
|
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:
|
except Exception as e:
|
||||||
self.logger.warning("server task error during shutdown: {}", e)
|
self.logger.warning("server task error during shutdown: {}", e)
|
||||||
self._server_task = None
|
self._server_task = None
|
||||||
|
|||||||
@ -1222,6 +1222,43 @@ async def test_stop_all_handles_channel_exception():
|
|||||||
assert mgr._dispatch_task is None
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_start_all_no_channels_logs_warning():
|
async def test_start_all_no_channels_logs_warning():
|
||||||
"""start_all should log warning when no channels are enabled."""
|
"""start_all should log warning when no channels are enabled."""
|
||||||
|
|||||||
@ -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()
|
@pytest.fixture()
|
||||||
def bus() -> MagicMock:
|
def bus() -> MagicMock:
|
||||||
b = MagicMock()
|
b = MagicMock()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user