mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-28 19:47:41 +08:00
refactor: 收敛 factory 模块级副作用并统一 async 路径进度为异步后端
- 将 configure_token_codec/configure_plugin_routes 移入 create_app(),消除 import 期副作用 - runtime 层新增 AsyncCacheProxy/AsyncTTLCache/AsyncProgressHelper,共享 progress region - search/system/history/scheduler/dashboard 的事件循环路径切换异步进度后端 - 同步进度回调经事件循环提交或线程池执行,避免阻塞事件循环
This commit is contained in:
+83
-13
@@ -31,7 +31,7 @@ from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.application.maintenance import build_cleanup_service
|
||||
from app.application.image import WallpaperHelper
|
||||
from app.application.messaging.message import MessageHelper
|
||||
from app.runtime.progress import ProgressHelper
|
||||
from app.runtime.progress import AsyncProgressHelper, ProgressHelper
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.runtime.extensions.service_config import ServiceConfigHelper
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module
|
||||
@@ -609,7 +609,7 @@ class Scheduler(ConfigReloadMixin, metaclass=SingletonClass):
|
||||
)
|
||||
return job
|
||||
|
||||
def __finish_job(
|
||||
async def __finish_job(
|
||||
self,
|
||||
job_id: str,
|
||||
success: bool = True,
|
||||
@@ -627,10 +627,11 @@ class Scheduler(ConfigReloadMixin, metaclass=SingletonClass):
|
||||
job["last_finished_at"] = finished_at
|
||||
job["last_error"] = error
|
||||
job_name = job.get("name") if job else job_id
|
||||
progress = ProgressHelper(self._get_progress_key(job_id))
|
||||
current_progress = progress.get() or {}
|
||||
# 收尾可能发生在事件循环上(__run_coro_job),使用异步进度后端避免阻塞
|
||||
progress = AsyncProgressHelper(self._get_progress_key(job_id))
|
||||
current_progress = await progress.get() or {}
|
||||
progress_value = 100 if success else current_progress.get("value", 0)
|
||||
progress.end(
|
||||
await progress.end(
|
||||
text=f"{job_name} {'执行完成' if success else '执行失败'}",
|
||||
data={
|
||||
"id": job_id,
|
||||
@@ -682,6 +683,45 @@ class Scheduler(ConfigReloadMixin, metaclass=SingletonClass):
|
||||
data=data,
|
||||
)
|
||||
|
||||
async def aget_progress(self, job_id: str) -> Optional[_SchemaScheduleProgress]:
|
||||
"""
|
||||
查询指定定时服务的执行进度(异步版本,供事件循环上的端点使用)。
|
||||
"""
|
||||
if not job_id:
|
||||
return None
|
||||
with self._lock:
|
||||
job = self._jobs.get(job_id)
|
||||
job_name = job.get("name") if job else job_id
|
||||
provider_name = job.get("provider_name", "[系统]") if job else None
|
||||
running = bool(job.get("running")) if job else False
|
||||
last_started_at = job.get("last_started_at") if job else None
|
||||
last_finished_at = job.get("last_finished_at") if job else None
|
||||
last_error = job.get("last_error") if job else None
|
||||
# 异步后端读取,避免在事件循环上阻塞
|
||||
detail = await AsyncProgressHelper(self._get_progress_key(job_id)).get() or {}
|
||||
if not job and not detail:
|
||||
return None
|
||||
data = detail.get("data") or {}
|
||||
value = detail.get("value", 0)
|
||||
try:
|
||||
value = float(value)
|
||||
except (TypeError, ValueError):
|
||||
value = 0.0
|
||||
return _SchemaScheduleProgress(
|
||||
id=job_id,
|
||||
name=data.get("name") or job_name,
|
||||
provider=data.get("provider") or provider_name,
|
||||
enable=bool(detail.get("enable", running)),
|
||||
value=max(min(value, 100), 0),
|
||||
text=detail.get("text"),
|
||||
status=data.get("status") or ("running" if running else "waiting"),
|
||||
success=data.get("success"),
|
||||
started_at=data.get("started_at") or last_started_at,
|
||||
finished_at=data.get("finished_at") or last_finished_at,
|
||||
error=data.get("error") or last_error,
|
||||
data=data,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def __handle_job_error(job_id: str, job: dict, error: Exception) -> None:
|
||||
"""
|
||||
@@ -726,11 +766,19 @@ class Scheduler(ConfigReloadMixin, metaclass=SingletonClass):
|
||||
}
|
||||
if data:
|
||||
progress_data.update(data)
|
||||
ProgressHelper(self._get_progress_key(job_id)).update(
|
||||
value=value,
|
||||
text=text,
|
||||
data=progress_data,
|
||||
)
|
||||
key = self._get_progress_key(job_id)
|
||||
|
||||
async def _update() -> None:
|
||||
# 异步后端更新,避免任务函数在事件循环内调用回调时阻塞
|
||||
await AsyncProgressHelper(key).update(
|
||||
value=value,
|
||||
text=text,
|
||||
data=progress_data,
|
||||
)
|
||||
|
||||
# 回调可能在事件循环内(async 任务)或线程池中(sync 任务)被调用,
|
||||
# 统一经事件循环提交;无运行中循环时同步执行兜底
|
||||
self._submit_to_loop(_update())
|
||||
|
||||
return update_progress
|
||||
|
||||
@@ -778,7 +826,8 @@ class Scheduler(ConfigReloadMixin, metaclass=SingletonClass):
|
||||
error = str(err)
|
||||
self.__handle_job_error(job_id=job_id, job=job, error=err)
|
||||
finally:
|
||||
self.__finish_job(job_id=job_id, success=success, error=error)
|
||||
# 协程收尾在事件循环上完成,同步路径(线程池/调用线程)提交到事件循环执行
|
||||
await self.__finish_job(job_id=job_id, success=success, error=error)
|
||||
|
||||
def start(self, job_id: str, *args, **kwargs) -> None:
|
||||
"""
|
||||
@@ -845,8 +894,29 @@ class Scheduler(ConfigReloadMixin, metaclass=SingletonClass):
|
||||
self.__handle_job_error(job_id=job_id, job=job, error=e)
|
||||
finally:
|
||||
if not deferred_finish:
|
||||
# 运行结束
|
||||
self.__finish_job(job_id=job_id, success=success, error=error)
|
||||
# 同步上下文执行异步收尾:优先提交到当前/全局事件循环,无循环时新建循环
|
||||
self._submit_to_loop(self.__finish_job(
|
||||
job_id=job_id, success=success, error=error
|
||||
))
|
||||
|
||||
@staticmethod
|
||||
def _submit_to_loop(coro: Any) -> None:
|
||||
"""
|
||||
把协程提交到事件循环执行,兼容以下调用环境:
|
||||
- 已在事件循环内(async 任务内部):排队为独立任务,避免阻塞
|
||||
- 外部线程且全局循环在运行:跨线程提交,非阻塞
|
||||
- 无运行中循环(测试/CLI):新建循环同步执行,确保进度不丢失
|
||||
"""
|
||||
try:
|
||||
running_loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
running_loop = None
|
||||
if running_loop:
|
||||
asyncio.create_task(coro)
|
||||
elif global_vars.loop and global_vars.loop.is_running():
|
||||
asyncio.run_coroutine_threadsafe(coro, global_vars.loop)
|
||||
else:
|
||||
asyncio.run(coro)
|
||||
|
||||
@staticmethod
|
||||
def _get_agent_task_job_id(task_id: int) -> str:
|
||||
|
||||
Reference in New Issue
Block a user