fix(runtime): enforce main event loop ownership (#6424)

This commit is contained in:
InfinityPacer
2026-08-23 20:14:31 +08:00
committed by GitHub
parent c72b4fe88e
commit 1d2984b95f
14 changed files with 181 additions and 55 deletions
+2 -2
View File
@@ -143,8 +143,8 @@ def _pooled_loop() -> Optional[Any]:
只认常驻主循环:它承载了绝大多数异步 DB 流量,且生命周期与进程一致,
池中连接不会因循环销毁而失效。
直接读 CURRENT_EVENT_LOOP 而不用 global_vars.loop——后者在未设置时会
新建一个事件循环,仅为判断就产生副作用是不可接受的
直接读 CURRENT_EVENT_LOOP 而不用 global_vars.loop,避免主循环尚未就绪时
把正常的回退引擎选择转换成生命周期异常
"""
if not _async_pool_enabled():
return None
+14 -23
View File
@@ -1,4 +1,3 @@
import asyncio
import copy
import json
import os
@@ -1359,18 +1358,8 @@ class GlobalVar(object):
EMERGENCY_STOP_WORKFLOWS: List[int] = []
# 需应急停止文件整理
EMERGENCY_STOP_TRANSFER: List[str] = []
# 当前事件循环
CURRENT_EVENT_LOOP: AbstractEventLoop = None
@classmethod
def _get_event_loop(cls) -> AbstractEventLoop:
"""返回当前线程事件循环,缺失时创建并绑定新循环。"""
try:
return asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop
# 生命周期登记的主事件循环
CURRENT_EVENT_LOOP: Optional[AbstractEventLoop] = None
def stop_system(self):
"""
@@ -1461,19 +1450,21 @@ class GlobalVar(object):
@property
def loop(self) -> AbstractEventLoop:
"""
当前循环
"""
if self.CURRENT_EVENT_LOOP is None:
self.CURRENT_EVENT_LOOP = self._get_event_loop()
return self.CURRENT_EVENT_LOOP
"""返回由应用生命周期登记的主事件循环。"""
loop = self.CURRENT_EVENT_LOOP
if loop is None or not loop.is_running() or loop.is_closed():
raise RuntimeError("主事件循环尚未启动或已经停止")
return loop
def set_loop(self, loop: AbstractEventLoop):
"""
设置循环
"""
def set_loop(self, loop: AbstractEventLoop) -> None:
"""登记承载主程序异步任务的事件循环。"""
self.CURRENT_EVENT_LOOP = loop
def clear_loop(self, loop: AbstractEventLoop) -> None:
"""仅在登记值仍为目标循环时清除主事件循环。"""
if self.CURRENT_EVENT_LOOP is loop:
self.CURRENT_EVENT_LOOP = None
# 全局标识
global_vars = GlobalVar()
+9 -4
View File
@@ -368,14 +368,14 @@ async def lifespan(app: FastAPI):
"""
health = get_application_health(app)
health.begin_startup()
main_loop = asyncio.get_running_loop()
try:
validate_process_topology(
workers=settings.API_WORKERS,
safe_mode=settings.MOVIEPILOT_SAFE_MODE,
)
print("Starting up...")
# 存储当前循环
global_vars.set_loop(asyncio.get_event_loop())
global_vars.set_loop(main_loop)
components = build_lifecycle_components(app)
enabled_components = tuple(
component
@@ -411,6 +411,8 @@ async def lifespan(app: FastAPI):
await stop_task_registry(app)
except Exception as cleanup_error:
logger.error(f"启动失败后的后台任务清理失败:{cleanup_error}")
finally:
global_vars.clear_loop(main_loop)
raise
try:
# 在此处 yield,表示应用已经启动,控制权交回 FastAPI 主事件循环
@@ -435,5 +437,8 @@ async def lifespan(app: FastAPI):
component.stop_timeout_seconds,
)
finally:
# 日志最后关闭,确保其他组件的收尾信息已写入文件
LoggerManager.shutdown()
try:
# 日志最后关闭,确保其他组件的收尾信息已写入文件
LoggerManager.shutdown()
finally:
global_vars.clear_loop(main_loop)