fix(scheduler): close async task lifecycle

This commit is contained in:
jxxghp
2026-08-23 15:41:07 +08:00
parent ba193cce08
commit cd97b1300a
6 changed files with 175 additions and 12 deletions
+28 -1
View File
@@ -1,5 +1,6 @@
import asyncio
import threading
from unittest.mock import Mock
from unittest.mock import AsyncMock, Mock
from app import scheduler as scheduler_module
from app.scheduler import Scheduler
@@ -46,6 +47,32 @@ def test_scheduler_initializer_starts_background_jobs(monkeypatch):
scheduler.init.assert_called_once_with()
def test_scheduler_initializer_stop_preserves_sync_abi(monkeypatch):
"""同步调用停止入口仍应立即关闭 Scheduler,不返回 coroutine。"""
scheduler = Mock()
monkeypatch.setattr(scheduler_initializer, "Scheduler", Mock(return_value=scheduler))
assert scheduler_initializer.stop_scheduler() is None
scheduler.stop.assert_called_once_with()
scheduler.async_stop.assert_not_called()
def test_scheduler_initializer_stop_awaits_in_running_loop(monkeypatch):
"""生命周期事件循环中的停止入口应返回可等待的异步收口。"""
scheduler = Mock()
scheduler.async_stop = AsyncMock()
monkeypatch.setattr(scheduler_initializer, "Scheduler", Mock(return_value=scheduler))
async def scenario():
result = scheduler_initializer.stop_scheduler()
assert result is not None
await result
asyncio.run(scenario())
scheduler.async_stop.assert_awaited_once_with()
scheduler.stop.assert_not_called()
def test_clear_cache_is_manual_only(monkeypatch):
"""缓存清理任务应仅手动执行,不注册到调度器自动运行。"""
background_scheduler = _BackgroundSchedulerStub()
+34
View File
@@ -11,7 +11,11 @@ from app.scheduler import Scheduler
def _build_scheduler(job_id, func):
"""构造不启动 APScheduler 的定时服务测试对象。"""
scheduler = object.__new__(Scheduler)
scheduler._scheduler = None
scheduler._event = threading.Event()
scheduler._lock = threading.RLock()
scheduler._async_tasks = set()
scheduler._accepting_async_tasks = True
scheduler._jobs = {
job_id: {
"name": "测试定时服务",
@@ -169,6 +173,36 @@ def test_scheduler_records_cancelled_async_job_as_failed():
assert progress.error == "任务已取消"
def test_scheduler_async_stop_cancels_owned_async_jobs():
"""Scheduler 关停应取消并等待自身登记的异步作业。"""
job_id = f"test-owned-task-{uuid4()}"
started = asyncio.Event()
cancelled = asyncio.Event()
async def task():
"""等待关停信号,验证任务确实由 Scheduler 持有。"""
started.set()
try:
await asyncio.Event().wait()
except asyncio.CancelledError:
cancelled.set()
raise
scheduler = _build_scheduler(job_id, task)
async def run_task():
"""在当前事件循环启动并收口异步作业。"""
scheduler.start(job_id)
await started.wait()
assert len(scheduler._async_tasks) == 1
await scheduler.async_stop(timeout_seconds=1)
asyncio.run(run_task())
assert cancelled.is_set()
assert scheduler._async_tasks == set()
def test_scheduler_returns_none_for_unknown_job():
"""未注册且无历史进度的定时服务应返回空。"""
job_id = f"test-unknown-{uuid4()}"