From 09203e8a905c041e5cc0bf8b802c7b2c61651a50 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 24 Mar 2026 20:17:27 +0800 Subject: [PATCH 1/2] fix: keep weixin_oc polling after inbound timeout --- .../sources/weixin_oc/weixin_oc_adapter.py | 8 ++++- tests/test_weixin_oc_adapter.py | 36 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/test_weixin_oc_adapter.py diff --git a/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py b/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py index b9caa0b093..e2e527941d 100644 --- a/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py +++ b/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py @@ -895,7 +895,13 @@ async def run(self) -> None: await asyncio.sleep(self.qr_poll_interval) continue - await self._poll_inbound_updates() + try: + await self._poll_inbound_updates() + except asyncio.TimeoutError: + logger.debug( + "weixin_oc(%s): inbound getupdates long-poll timeout", + self.meta().id, + ) except asyncio.CancelledError: raise except Exception as e: diff --git a/tests/test_weixin_oc_adapter.py b/tests/test_weixin_oc_adapter.py new file mode 100644 index 0000000000..8d9f1af04d --- /dev/null +++ b/tests/test_weixin_oc_adapter.py @@ -0,0 +1,36 @@ +import asyncio +from unittest.mock import AsyncMock + +import pytest + +from astrbot.core.platform.sources.weixin_oc.weixin_oc_adapter import WeixinOCAdapter + + +@pytest.mark.asyncio +async def test_run_continues_after_inbound_long_poll_timeout(): + adapter = WeixinOCAdapter( + platform_config={ + "id": "weixin_main", + "type": "weixin_oc", + "weixin_oc_token": "test-token", + }, + platform_settings={}, + event_queue=asyncio.Queue(), + ) + adapter.client.close = AsyncMock() + + call_count = 0 + + async def fake_poll_inbound_updates(): + nonlocal call_count + call_count += 1 + if call_count == 1: + raise asyncio.TimeoutError() + adapter._shutdown_event.set() + + adapter._poll_inbound_updates = fake_poll_inbound_updates # type: ignore[method-assign] + + await adapter.run() + + assert call_count == 2 + adapter.client.close.assert_awaited_once() From b83c08730a17216bd3d940c8f1c1ee18c5e68567 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 24 Mar 2026 20:26:28 +0800 Subject: [PATCH 2/2] fix: narrow weixin_oc getupdates timeout handling --- .../sources/weixin_oc/weixin_oc_adapter.py | 37 ++++++++++--------- tests/test_weixin_oc_adapter.py | 33 ++++++++++++++++- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py b/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py index e2e527941d..ba67becf8d 100644 --- a/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py +++ b/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py @@ -734,18 +734,25 @@ async def _handle_inbound_message(self, msg: dict[str, Any]) -> None: ) async def _poll_inbound_updates(self) -> None: - data = await self.client.request_json( - "POST", - "ilink/bot/getupdates", - payload={ - "base_info": { - "channel_version": "astrbot", + try: + data = await self.client.request_json( + "POST", + "ilink/bot/getupdates", + payload={ + "base_info": { + "channel_version": "astrbot", + }, + "get_updates_buf": self._sync_buf, }, - "get_updates_buf": self._sync_buf, - }, - token_required=True, - timeout_ms=self.long_poll_timeout_ms, - ) + token_required=True, + timeout_ms=self.long_poll_timeout_ms, + ) + except asyncio.TimeoutError: + logger.debug( + "weixin_oc(%s): inbound getupdates long-poll timeout", + self.meta().id, + ) + return ret = int(data.get("ret") or 0) errcode = data.get("errcode", 0) if ret != 0 and ret is not None: @@ -895,13 +902,7 @@ async def run(self) -> None: await asyncio.sleep(self.qr_poll_interval) continue - try: - await self._poll_inbound_updates() - except asyncio.TimeoutError: - logger.debug( - "weixin_oc(%s): inbound getupdates long-poll timeout", - self.meta().id, - ) + await self._poll_inbound_updates() except asyncio.CancelledError: raise except Exception as e: diff --git a/tests/test_weixin_oc_adapter.py b/tests/test_weixin_oc_adapter.py index 8d9f1af04d..12a1646a04 100644 --- a/tests/test_weixin_oc_adapter.py +++ b/tests/test_weixin_oc_adapter.py @@ -21,16 +21,45 @@ async def test_run_continues_after_inbound_long_poll_timeout(): call_count = 0 - async def fake_poll_inbound_updates(): + async def fake_request_json(*args, **kwargs): nonlocal call_count call_count += 1 if call_count == 1: raise asyncio.TimeoutError() adapter._shutdown_event.set() + return {"msgs": []} - adapter._poll_inbound_updates = fake_poll_inbound_updates # type: ignore[method-assign] + adapter.client.request_json = fake_request_json # type: ignore[method-assign] await adapter.run() assert call_count == 2 adapter.client.close.assert_awaited_once() + + +@pytest.mark.asyncio +async def test_run_stops_on_non_timeout_inbound_error(): + adapter = WeixinOCAdapter( + platform_config={ + "id": "weixin_main", + "type": "weixin_oc", + "weixin_oc_token": "test-token", + }, + platform_settings={}, + event_queue=asyncio.Queue(), + ) + adapter.client.close = AsyncMock() + + call_count = 0 + + async def fake_request_json(*args, **kwargs): + nonlocal call_count + call_count += 1 + raise RuntimeError("boom") + + adapter.client.request_json = fake_request_json # type: ignore[method-assign] + + await adapter.run() + + assert call_count == 1 + adapter.client.close.assert_awaited_once()