refactor: unify command shutdown ownership

This commit is contained in:
jxxghp
2026-08-24 13:11:10 +08:00
parent af9a141f34
commit 6e5617db51
4 changed files with 15 additions and 22 deletions
-7
View File
@@ -14,13 +14,6 @@ def init_command():
Command() Command()
def stop_command():
"""
停止命令
"""
pass
def restart_command() -> Future: def restart_command() -> Future:
""" """
重建命令并返回完成信号。 重建命令并返回完成信号。
+1 -4
View File
@@ -37,7 +37,7 @@ from app.runtime.tasks import TaskRegistry, configure_task_registry
from app.adapters.external.server import MoviePilotServerHelper from app.adapters.external.server import MoviePilotServerHelper
from app.runtime.state import SystemHelper from app.runtime.state import SystemHelper
from app.runtime.log import logger, LoggerManager from app.runtime.log import logger, LoggerManager
from app.startup.initializers.command import init_command, stop_command, restart_command from app.startup.initializers.command import init_command, restart_command
from app.startup.initializers.agent import stop_agent from app.startup.initializers.agent import stop_agent
from app.startup.initializers.domain import configure_domain_dependencies from app.startup.initializers.domain import configure_domain_dependencies
from app.startup.initializers.modules import ( from app.startup.initializers.modules import (
@@ -469,11 +469,8 @@ def build_lifecycle_components(app: FastAPI) -> tuple[LifecycleComponent, ...]:
dependencies=("待处理整理回放",), dependencies=("待处理整理回放",),
mode=LifecycleMode.NORMAL_ONLY, mode=LifecycleMode.NORMAL_ONLY,
start=init_command, start=init_command,
stop=stop_command,
start_order=130, start_order=130,
stop_order=30,
start_timeout_seconds=120, start_timeout_seconds=120,
stop_timeout_seconds=120,
), ),
LifecycleComponent( LifecycleComponent(
name="工作流", name="工作流",
@@ -2,13 +2,13 @@
> 文档性质:当前架构复核、优秀 Python 后端实践对标、AI 可执行任务手册 > 文档性质:当前架构复核、优秀 Python 后端实践对标、AI 可执行任务手册
> 适用仓库:`MoviePilot`,分支 `v3` > 适用仓库:`MoviePilot`,分支 `v3`
> 审计基线:`7c97d1742`2026-08-24 > 审计基线:`af9a141f`2026-08-24
> 审计范围:宿主后端;排除 `app/plugins/**` 运行时插件副本 > 审计范围:宿主后端;排除 `app/plugins/**` 运行时插件副本
> 规范优先级:`AGENTS.md` 与 `docs/rules/` 高于本文 > 规范优先级:`AGENTS.md` 与 `docs/rules/` 高于本文
> 相关文档:`docs/architecture-overview.md`、`docs/refactor/backend-architecture-governance.md`、`docs/refactor/backend-module-refactor-compatibility.md` > 相关文档:`docs/architecture-overview.md`、`docs/refactor/backend-architecture-governance.md`、`docs/refactor/backend-module-refactor-compatibility.md`
> 实施进度:阶段 0~6 的宿主架构能力已完成收口;API/Application 公共复杂度基线已清零,启动组合根的 SystemConfigOper 构造点已由 14 降至 1;API 进程内后台任务已完成首批统一登记,插件仓适配和 Outbox 外围扩展仍按风险切片推进。Model/Base 查询与写装饰器、legacy 隐式会话外壳均已清零,插件 SDK 也不再导出宿主 Model。2026-08-23 的长期整改阶段 0 已恢复宿主、启动性能、官方插件和 SDK 契约门禁的可信基线;阶段 1a 已补齐 TaskRegistry owner 零债务门禁和诚实的关停超时语义;阶段 1b1 已收口整理 worker、pending 回放、失败通知、进程内 AI 重试、插件监控与事件投递的生命周期所有权;2026-08-24 的阶段 2 已将 212 个已观察宿主模块方法的 legacy aggregation 清零,并补齐可执行 fanout 与下载器文件 DTO 边界;阶段 3 已将消息交互和远程命令的订阅删除统一到 Application/UoW/outbox,宿主不再调用裸线程统计入口;阶段 4 已统一七种消息渠道的宿主回环与后台执行边界;阶段 5 已补齐事件窗口聚合任务的生命周期所有权;阶段 6 已统一插件文件操作的取消完成语义;阶段 7 已统一插件协程补偿的终态等待;阶段 8 已统一宿主同步函数的异步线程池入口;阶段 9 已统一工作流运行时的宿主获取路径;阶段 10 已统一模块、插件与调度运行时的显式 getter 调用;阶段 11 已清除系统配置 getter 的 Oper 形别名;阶段 12 已完成工作流域的显式 Chain 数据端口迁移;阶段 13 已收口用户、交互与消息链的数据端口;阶段 14 已收口音乐订阅数据端口;阶段 15 已收口站点数据端口;阶段 16 已收口媒体服务器数据端口;阶段 17 已收口下载数据端口;阶段 18 已收口主订阅数据端口;阶段 19 已收口整理数据端口;阶段 20 已收口 Agent 数据端口;阶段 21 已收口监控历史端口;阶段 22 已统一服务配置应用边界;阶段 23 已补齐媒体服务器 API 遗留的类形配置读取路径;阶段 24 已清除 Scheduler 内部无 owner 的协程提交双轨;阶段 25 已补齐 TaskRegistry 跨线程 owner 并迁移整理 AI 接管;阶段 26 已统一 Agent 会话清理提交;阶段 27 已统一历史 AI 进度 owner;阶段 28 已托管旧插件订阅统计线程;阶段 29 已统一 Emby 系条目转换并清零重复代码白名单;阶段 30 已收口插件市场请求级子任务;阶段 31 已托管搜索 AI 推荐任务;阶段 32 已清除事件调度器绕过生命周期 owner 的投递回退;阶段 33 已统一宿主 Agent 运行时的获取路径;阶段 34 已统一 durable-required 事件与 Outbox topic 事实源;阶段 35 已统一 LLM provider 管理 API 的运行时解析路径;阶段 36 已统一 WebAgent 音频能力访问边界;阶段 37 已统一插件输入事件发布路径;阶段 38 已统一 WebAgent 通知事件监听与队列边界;阶段 39 已补齐搜索 SSE 断线时的上游任务清理;阶段 40 已补齐异步防抖取消的终态所有权;阶段 41 已统一优雅重启兜底线程的唯一所有权;阶段 42 已补齐 Telegram typing 的多实例隔离和终态 owner;阶段 43 已统一 Discord typing 的异步 owner 和 shutdown 收尾;阶段 44 已清除 WebAgent 测试临时事件循环提前关闭产生的 CI 红注解;阶段 45 已统一影视与字幕搜索的请求级逐页任务编排;阶段 46 已收口启动性能门禁的托管 runner 假失败与诊断输出;阶段 47 已补齐 Agent 渠道流式刷新任务的重入 owner;阶段 48 已统一工件上传 action 的 Node 24 主版本;阶段 49 已统一插件安装的同步/异步代际解析事实源;阶段 50 已统一插件市场 GitHub 请求降级策略;阶段 51 已统一插件索引请求与响应三态策略;阶段 52 已统一插件 Release 分页策略;阶段 53 已统一远端插件安装模式决策;阶段 54 已补齐同步安装成功后的临时回滚备份清理;阶段 55~56 已收口官方插件观察基线与报告保留策略;阶段 57 已统一进程级运行时 Facade 门禁并补齐 ModuleManager 边界;阶段 58 已消除 AgentTask 关闭回归的跨线程零时长等待竞态。 > 实施进度:阶段 0~6 的宿主架构能力已完成收口;API/Application 公共复杂度基线已清零,启动组合根的 SystemConfigOper 构造点已由 14 降至 1;API 进程内后台任务已完成首批统一登记,插件仓适配和 Outbox 外围扩展仍按风险切片推进。Model/Base 查询与写装饰器、legacy 隐式会话外壳均已清零,插件 SDK 也不再导出宿主 Model。2026-08-23 的长期整改阶段 0 已恢复宿主、启动性能、官方插件和 SDK 契约门禁的可信基线;阶段 1a 已补齐 TaskRegistry owner 零债务门禁和诚实的关停超时语义;阶段 1b1 已收口整理 worker、pending 回放、失败通知、进程内 AI 重试、插件监控与事件投递的生命周期所有权;2026-08-24 的阶段 2 已将 212 个已观察宿主模块方法的 legacy aggregation 清零,并补齐可执行 fanout 与下载器文件 DTO 边界;阶段 3 已将消息交互和远程命令的订阅删除统一到 Application/UoW/outbox,宿主不再调用裸线程统计入口;阶段 4 已统一七种消息渠道的宿主回环与后台执行边界;阶段 5 已补齐事件窗口聚合任务的生命周期所有权;阶段 6 已统一插件文件操作的取消完成语义;阶段 7 已统一插件协程补偿的终态等待;阶段 8 已统一宿主同步函数的异步线程池入口;阶段 9 已统一工作流运行时的宿主获取路径;阶段 10 已统一模块、插件与调度运行时的显式 getter 调用;阶段 11 已清除系统配置 getter 的 Oper 形别名;阶段 12 已完成工作流域的显式 Chain 数据端口迁移;阶段 13 已收口用户、交互与消息链的数据端口;阶段 14 已收口音乐订阅数据端口;阶段 15 已收口站点数据端口;阶段 16 已收口媒体服务器数据端口;阶段 17 已收口下载数据端口;阶段 18 已收口主订阅数据端口;阶段 19 已收口整理数据端口;阶段 20 已收口 Agent 数据端口;阶段 21 已收口监控历史端口;阶段 22 已统一服务配置应用边界;阶段 23 已补齐媒体服务器 API 遗留的类形配置读取路径;阶段 24 已清除 Scheduler 内部无 owner 的协程提交双轨;阶段 25 已补齐 TaskRegistry 跨线程 owner 并迁移整理 AI 接管;阶段 26 已统一 Agent 会话清理提交;阶段 27 已统一历史 AI 进度 owner;阶段 28 已托管旧插件订阅统计线程;阶段 29 已统一 Emby 系条目转换并清零重复代码白名单;阶段 30 已收口插件市场请求级子任务;阶段 31 已托管搜索 AI 推荐任务;阶段 32 已清除事件调度器绕过生命周期 owner 的投递回退;阶段 33 已统一宿主 Agent 运行时的获取路径;阶段 34 已统一 durable-required 事件与 Outbox topic 事实源;阶段 35 已统一 LLM provider 管理 API 的运行时解析路径;阶段 36 已统一 WebAgent 音频能力访问边界;阶段 37 已统一插件输入事件发布路径;阶段 38 已统一 WebAgent 通知事件监听与队列边界;阶段 39 已补齐搜索 SSE 断线时的上游任务清理;阶段 40 已补齐异步防抖取消的终态所有权;阶段 41 已统一优雅重启兜底线程的唯一所有权;阶段 42 已补齐 Telegram typing 的多实例隔离和终态 owner;阶段 43 已统一 Discord typing 的异步 owner 和 shutdown 收尾;阶段 44 已清除 WebAgent 测试临时事件循环提前关闭产生的 CI 红注解;阶段 45 已统一影视与字幕搜索的请求级逐页任务编排;阶段 46 已收口启动性能门禁的托管 runner 假失败与诊断输出;阶段 47 已补齐 Agent 渠道流式刷新任务的重入 owner;阶段 48 已统一工件上传 action 的 Node 24 主版本;阶段 49 已统一插件安装的同步/异步代际解析事实源;阶段 50 已统一插件市场 GitHub 请求降级策略;阶段 51 已统一插件索引请求与响应三态策略;阶段 52 已统一插件 Release 分页策略;阶段 53 已统一远端插件安装模式决策;阶段 54 已补齐同步安装成功后的临时回滚备份清理;阶段 55~56 已收口官方插件观察基线与报告保留策略;阶段 57 已统一进程级运行时 Facade 门禁并补齐 ModuleManager 边界;阶段 58 已消除 AgentTask 关闭回归的跨线程零时长等待竞态;阶段 59 已统一 Feishu 多实例长连接的 SDK 循环路由;阶段 60 已清除命令服务虚假的关停 owner 声明
> 当前 canonical 状态:API/Application 公共复杂度基线已清零,组合根外 `SystemConfigOper()` 构造和 Model/Oper 隐式事务均为 0;命名 Chain/Agent 数据端口、TaskRegistry owner、Module Contract V2、typed Event、Outbox durable intent、请求关联和插件运行时 getter 已形成当前路径。插件仓适配、未知第三方 fallback 和其它 E1/E3 副作用仍按风险持续治理。 > 当前 canonical 状态:API/Application 公共复杂度基线已清零,组合根外 `SystemConfigOper()` 构造和 Model/Oper 隐式事务均为 0;命名 Chain/Agent 数据端口、TaskRegistry owner、Module Contract V2、typed Event、Outbox durable intent、请求关联和插件运行时 getter 已形成当前路径。插件仓适配、未知第三方 fallback 和其它 E1/E3 副作用仍按风险持续治理。
> 最新阶段:阶段 59 已统一 Feishu 多实例长连接的 SDK 循环路由 > 最新阶段:阶段 60 已统一命令重建任务的唯一关停 owner
## 当前复核结论(2026-08-24 ## 当前复核结论(2026-08-24
@@ -620,6 +620,17 @@
- 并发回归用屏障强制两个模拟 SDK Client 同时读取模块级入口,证明二者运行在不同循环且均可独立停止。 - 并发回归用屏障强制两个模拟 SDK Client 同时读取模块级入口,证明二者运行在不同循环且均可独立停止。
Feishu 配置、消息/卡片 API、模块类身份、插件 SDK/Compat 与其他 V1/V2/V3 插件行为均未修改。 Feishu 配置、消息/卡片 API、模块类身份、插件 SDK/Compat 与其他 V1/V2/V3 插件行为均未修改。
### 长期整改阶段 60:命令重建任务关停 owner 统一(2026-08-24
- 命令服务的初始化和热更新通过 `ThreadHelper` 共享线程池提交,真实任务 owner 始终是
模块服务关闭阶段的线程池;原生命周期清单又为命令服务登记了一个空 `stop_command()`
形成声明上的第二套 owner,却无法拒绝、取消或等待任何任务。
- 当前删除空关闭入口及其 stop/timeout/order 声明;命令服务仍是正常模式的显式启动组件,
命令重建 Future 仍由共享线程池在模块、事件和插件资源收口后统一等待。生命周期快照
不再把无行为的回调计为已关闭 owner。
- `Command` 类身份、Application 命令门面、插件命令 Hook、热更新时序和 SDK/Compat 均未修改;
V1/V2/V3 插件仍通过原有注册链路生效,且未修改插件仓。
### 总体判断 ### 总体判断
当前架构总体合理,已经从跨层混合的遗留单体收敛为**边界清晰的模块化单体**: 当前架构总体合理,已经从跨层混合的遗留单体收敛为**边界清晰的模块化单体**:
-8
View File
@@ -63,7 +63,6 @@ def _patch_lifespan(monkeypatch, *, failing_step: str | None = None) -> dict:
"backup_plugins": system_chain.backup_plugins, "backup_plugins": system_chain.backup_plugins,
"stop_plugin_monitor": MagicMock(return_value=True), "stop_plugin_monitor": MagicMock(return_value=True),
"stop_workflow": MagicMock(), "stop_workflow": MagicMock(),
"stop_command": MagicMock(),
"stop_monitor": MagicMock(), "stop_monitor": MagicMock(),
"stop_scheduler": MagicMock(), "stop_scheduler": MagicMock(),
"stop_agent": AsyncMock(return_value=True), "stop_agent": AsyncMock(return_value=True),
@@ -78,7 +77,6 @@ def _patch_lifespan(monkeypatch, *, failing_step: str | None = None) -> dict:
} }
for name in ( for name in (
"stop_workflow", "stop_workflow",
"stop_command",
"stop_monitor", "stop_monitor",
"stop_scheduler", "stop_scheduler",
"stop_plugin_monitor", "stop_plugin_monitor",
@@ -130,7 +128,6 @@ def _patch_lifespan(monkeypatch, *, failing_step: str | None = None) -> dict:
[ [
"backup_plugins", "backup_plugins",
"stop_workflow", "stop_workflow",
"stop_command",
"stop_modules", "stop_modules",
"close_http", "close_http",
], ],
@@ -240,7 +237,6 @@ _ORDERED_SHUTDOWN_STEPS = (
"stop_plugin_monitor", "stop_plugin_monitor",
"backup_plugins", "backup_plugins",
"stop_workflow", "stop_workflow",
"stop_command",
"stop_monitor", "stop_monitor",
"stop_scheduler", "stop_scheduler",
"stop_agent", "stop_agent",
@@ -444,7 +440,6 @@ def test_lifespan_safe_mode_skips_optional_runtime(monkeypatch):
for name in ( for name in (
"backup_plugins", "backup_plugins",
"stop_workflow", "stop_workflow",
"stop_command",
"stop_monitor", "stop_monitor",
"stop_scheduler", "stop_scheduler",
"stop_plugin_monitor", "stop_plugin_monitor",
@@ -531,7 +526,6 @@ def test_lifecycle_manifest_declares_normal_and_safe_mode_order() -> None:
"插件变更监控", "插件变更监控",
"插件备份", "插件备份",
"工作流", "工作流",
"命令服务",
"监控器", "监控器",
"定时器", "定时器",
"AI智能体会话", "AI智能体会话",
@@ -815,7 +809,6 @@ def test_lifespan_cleans_started_owners_after_late_startup_failure(monkeypatch):
for name in ( for name in (
"stop_plugin_monitor", "stop_plugin_monitor",
"backup_plugins", "backup_plugins",
"stop_command",
"stop_monitor", "stop_monitor",
"stop_scheduler", "stop_scheduler",
"stop_agent", "stop_agent",
@@ -853,7 +846,6 @@ def test_startup_failure_cleanup_honors_transfer_fail_fast(monkeypatch):
for name in ( for name in (
"stop_plugin_monitor", "stop_plugin_monitor",
"backup_plugins", "backup_plugins",
"stop_command",
"stop_monitor", "stop_monitor",
"stop_scheduler", "stop_scheduler",
"stop_agent", "stop_agent",