fix(discord): preserve cleanup during startup stop (#6426)

This commit is contained in:
InfinityPacer
2026-08-23 21:28:12 +08:00
committed by GitHub
parent e1509c4e0d
commit 7d10483961
2 changed files with 48 additions and 5 deletions
-5
View File
@@ -264,11 +264,6 @@ class Discord:
).result(timeout=10)
except Exception as err:
logger.error(f"关闭 Discord Bot 失败:{err}")
elif not loop.is_closed():
try:
loop.call_soon_threadsafe(loop.stop)
except RuntimeError:
pass
self._ready_event.clear()
thread.join(timeout=5)
if thread.is_alive():
+48
View File
@@ -27,6 +27,19 @@ class _DiscordClientStub:
self._release.set()
class _YieldingCloseDiscordClientStub(_DiscordClientStub):
"""关闭协程至少让出一次执行权,用于覆盖启动窗口内的停止竞态。"""
def __init__(self) -> None:
super().__init__()
self.closed = threading.Event()
async def close(self) -> None:
self.close_calls += 1
await asyncio.sleep(0)
self.closed.set()
def _discord(client: _DiscordClientStub) -> Discord:
"""构造只包含线程与事件循环生命周期状态的 Discord 实例。"""
instance = Discord.__new__(Discord)
@@ -81,3 +94,38 @@ def test_start_failure_closes_discord_thread_loop() -> None:
assert instance._loop.is_closed()
finally:
_cleanup(instance)
def test_stop_during_thread_bootstrap_preserves_runner_cleanup(monkeypatch) -> None:
"""线程已登记但循环尚未运行时,停止请求不得打断 runner 的关闭流程。"""
client = _YieldingCloseDiscordClientStub()
instance = _discord(client)
runner_entered = threading.Event()
release_runner = threading.Event()
original_set_event_loop = asyncio.set_event_loop
def block_runner(loop: asyncio.AbstractEventLoop | None) -> None:
if loop is instance._loop:
runner_entered.set()
assert release_runner.wait(timeout=1)
original_set_event_loop(loop)
monkeypatch.setattr(
"app.modules.discord.discord.asyncio.set_event_loop",
block_runner,
)
instance._start()
assert runner_entered.wait(timeout=1)
stop_thread = threading.Thread(target=instance.stop)
stop_thread.start()
assert instance._stop_requested.wait(timeout=1)
release_runner.set()
try:
stop_thread.join(timeout=2)
assert not stop_thread.is_alive()
assert client.closed.is_set()
assert instance._loop.is_closed()
finally:
release_runner.set()
_cleanup(instance)