mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-30 20:05:42 +08:00
fix(startup): order plugin service composition
This commit is contained in:
@@ -3,7 +3,7 @@ import uuid
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from app.adapters.external.market import (
|
||||
LOCAL_REPO_PREFIX,
|
||||
@@ -66,6 +66,9 @@ from app.application.plugin.release import (
|
||||
reset_plugin_release_service,
|
||||
)
|
||||
from app.application.plugin.routes import register_plugin_api
|
||||
from app.application.plugin.runtime import (
|
||||
PluginRuntime as PluginRuntimePort,
|
||||
)
|
||||
from app.application.plugin.runtime import (
|
||||
configure_plugin_runtime,
|
||||
get_plugin_manager,
|
||||
@@ -168,17 +171,21 @@ def build_plugin_runtime_graph(host: PluginRuntimeHost) -> PluginRuntime:
|
||||
)
|
||||
|
||||
|
||||
def configure_plugin_runtime_services() -> None:
|
||||
"""在模块对象图构造前发布插件 Runtime 工厂和应用层提供器。"""
|
||||
configure_plugin_catalog_factory(_build_plugin_catalog)
|
||||
configure_plugin_runtime_factory(build_plugin_runtime_graph)
|
||||
configure_plugin_runtime(lambda: cast(PluginRuntimePort, PluginManager()))
|
||||
|
||||
|
||||
def configure_plugin_services() -> None:
|
||||
"""把兼容诊断、远程上报和站点认证等级装配到插件管理器。"""
|
||||
"""在模块持久化端口就绪后装配完整插件应用服务。"""
|
||||
market_transport = PluginMarketTransport()
|
||||
market_client = PluginMarketClient(market_transport)
|
||||
package_manager = PluginPackageManager(
|
||||
source=PluginPackageSourceClient(market_transport),
|
||||
plugin_root=Path(get_runtime_setting('ROOT_PATH')) / "app" / "plugins",
|
||||
)
|
||||
configure_plugin_catalog_factory(_build_plugin_catalog)
|
||||
configure_plugin_runtime_factory(build_plugin_runtime_graph)
|
||||
configure_plugin_runtime(lambda: PluginManager())
|
||||
plugin_manager = get_plugin_manager()
|
||||
inventory_reader = PluginCandidateInventoryReader(
|
||||
market_loader=market_client.get_plugin_index_result,
|
||||
@@ -519,7 +526,6 @@ async def sync_plugins(
|
||||
plugin_manager = None
|
||||
try:
|
||||
loop = main_loop_registry.require()
|
||||
configure_plugin_services()
|
||||
plugin_manager = PluginManager()
|
||||
with plugin_manager.mutation("启动后同步插件"):
|
||||
await get_plugin_identity_migration().migrate()
|
||||
@@ -714,7 +720,6 @@ def init_plugins():
|
||||
"""
|
||||
初始化插件
|
||||
"""
|
||||
configure_plugin_services()
|
||||
plugin_manager = PluginManager()
|
||||
if not plugin_manager.reopen_plugins():
|
||||
raise RuntimeError("上一应用生命周期的插件后台服务仍未收敛")
|
||||
|
||||
@@ -63,6 +63,7 @@ from app.startup.initializers.network import ( # noqa: E402
|
||||
reset_chain_network_ports,
|
||||
)
|
||||
from app.startup.initializers.plugins import (
|
||||
configure_plugin_runtime_services,
|
||||
configure_plugin_services,
|
||||
execute_task,
|
||||
finalize_plugins,
|
||||
@@ -469,9 +470,9 @@ def build_lifecycle_components(app: FastAPI) -> tuple[LifecycleComponent, ...]:
|
||||
start_timeout_seconds=30,
|
||||
),
|
||||
LifecycleComponent(
|
||||
name="插件服务装配",
|
||||
name="插件运行时装配",
|
||||
dependencies=("路由",),
|
||||
start=configure_plugin_services,
|
||||
start=configure_plugin_runtime_services,
|
||||
start_order=65,
|
||||
start_timeout_seconds=30,
|
||||
),
|
||||
@@ -479,7 +480,7 @@ def build_lifecycle_components(app: FastAPI) -> tuple[LifecycleComponent, ...]:
|
||||
name="模块服务",
|
||||
dependencies=(
|
||||
"路由",
|
||||
"插件服务装配",
|
||||
"插件运行时装配",
|
||||
"站点访问端口",
|
||||
"Chain 外部端口",
|
||||
"Chain 网络端口",
|
||||
@@ -491,9 +492,16 @@ def build_lifecycle_components(app: FastAPI) -> tuple[LifecycleComponent, ...]:
|
||||
start_timeout_seconds=300,
|
||||
stop_timeout_seconds=300,
|
||||
),
|
||||
LifecycleComponent(
|
||||
name="插件服务装配",
|
||||
dependencies=("模块服务",),
|
||||
start=configure_plugin_services,
|
||||
start_order=72,
|
||||
start_timeout_seconds=30,
|
||||
),
|
||||
LifecycleComponent(
|
||||
name="插件备份恢复",
|
||||
dependencies=("模块服务", "消息队列"),
|
||||
dependencies=("插件服务装配", "消息队列"),
|
||||
mode=LifecycleMode.NORMAL_ONLY,
|
||||
start=prepare_plugin_restore,
|
||||
start_order=80,
|
||||
|
||||
@@ -78,7 +78,7 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain`
|
||||
| Event Contract | 53 | 均已有 payload model,但当前全部是 diagnostic enforcement |
|
||||
| Python 源码量 | 约 298,700 行 | 60 个文件超过 1,000 行,10 个超过 2,000 行 |
|
||||
| 长方法 | 299 个超过 80 行 | 70 个超过 150 行,23 个超过 250 行;大量是私有方法 |
|
||||
| 全量 mypy 历史债务 | 10,415 / 593 文件 | strict frontier 当前覆盖 41 个文件,Plugin、LLM Provider 与 Agent Manager owner 拆分及受益调用方低水位已固化 |
|
||||
| 全量 mypy 历史债务 | 10,413 / 593 文件 | strict frontier 当前覆盖 41 个文件,Plugin、LLM Provider 与 Agent Manager owner 拆分及受益调用方低水位已固化 |
|
||||
| Ruff 历史诊断 | 651 | 低水位门禁通过,但规则集只覆盖 `E4/E7/E9/F/I` |
|
||||
| 覆盖率低水位 | Application 80.92%,Domain 79.89% | Chain、Runtime、Agent、Adapter、Startup 未进入包级覆盖率门禁 |
|
||||
|
||||
|
||||
@@ -3285,14 +3285,14 @@
|
||||
"no-untyped-def": 2
|
||||
},
|
||||
"app/startup/initializers/modules.py": {
|
||||
"arg-type": 7,
|
||||
"arg-type": 6,
|
||||
"assignment": 1,
|
||||
"attr-defined": 2,
|
||||
"misc": 1,
|
||||
"no-any-return": 2,
|
||||
"no-untyped-call": 28,
|
||||
"no-untyped-def": 6,
|
||||
"return-value": 3
|
||||
"return-value": 2
|
||||
},
|
||||
"app/startup/initializers/plugins.py": {
|
||||
"arg-type": 5,
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"generated_at": "2026-08-29T05:54:31.505070+00:00",
|
||||
"generated_at": "2026-08-29T07:27:24.582316+00:00",
|
||||
"platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O",
|
||||
"python": "3.14.3",
|
||||
"repeat": 3,
|
||||
"targets": {
|
||||
"app.startup.lifecycle": {
|
||||
"loaded_app_module_count": 491,
|
||||
"max_ms": 1180.874,
|
||||
"median_ms": 1117.895,
|
||||
"min_ms": 1107.448,
|
||||
"max_ms": 1159.462,
|
||||
"median_ms": 1074.592,
|
||||
"min_ms": 1072.211,
|
||||
"samples_ms": [
|
||||
1107.448,
|
||||
1117.895,
|
||||
1180.874
|
||||
1159.462,
|
||||
1074.592,
|
||||
1072.211
|
||||
]
|
||||
},
|
||||
"app.factory": {
|
||||
"loaded_app_module_count": 503,
|
||||
"max_ms": 1160.129,
|
||||
"median_ms": 1133.423,
|
||||
"min_ms": 1109.307,
|
||||
"max_ms": 1094.111,
|
||||
"median_ms": 1092.394,
|
||||
"min_ms": 1090.171,
|
||||
"samples_ms": [
|
||||
1160.129,
|
||||
1133.423,
|
||||
1109.307
|
||||
1090.171,
|
||||
1092.394,
|
||||
1094.111
|
||||
]
|
||||
},
|
||||
"app.main": {
|
||||
"loaded_app_module_count": 505,
|
||||
"max_ms": 1194.892,
|
||||
"median_ms": 1191.744,
|
||||
"min_ms": 1190.037,
|
||||
"max_ms": 1165.991,
|
||||
"median_ms": 1159.713,
|
||||
"min_ms": 1143.531,
|
||||
"samples_ms": [
|
||||
1194.892,
|
||||
1191.744,
|
||||
1190.037
|
||||
1143.531,
|
||||
1159.713,
|
||||
1165.991
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -46,69 +46,33 @@
|
||||
"samples": [
|
||||
{
|
||||
"mode": "normal",
|
||||
"enabled_component_count": 31,
|
||||
"startup_ms": 0.667,
|
||||
"full_lifespan_ms": 1.794,
|
||||
"stage_ms": {
|
||||
"文件日志": 0.069,
|
||||
"后台任务登记器": 0.036,
|
||||
"数据库准备": 0.027,
|
||||
"HTTP 基础能力": 0.025,
|
||||
"站点访问端口": 0.02,
|
||||
"Chain 外部端口": 0.019,
|
||||
"Chain 网络端口": 0.018,
|
||||
"领域依赖装配": 0.018,
|
||||
"数据库引擎预热": 0.018,
|
||||
"数据库连接预算": 0.018,
|
||||
"路由": 0.021,
|
||||
"插件服务装配": 0.018,
|
||||
"模块服务": 0.018,
|
||||
"消息队列": 0.018,
|
||||
"插件备份恢复": 0.018,
|
||||
"插件": 0.017,
|
||||
"定时器": 0.017,
|
||||
"监控器": 0.017,
|
||||
"待处理整理回放": 0.018,
|
||||
"命令服务": 0.017,
|
||||
"工作流": 0.017,
|
||||
"插件同步与启动收尾": 0.017
|
||||
},
|
||||
"threads_before": 1,
|
||||
"threads_started": 1,
|
||||
"threads_after": 1,
|
||||
"tasks_before": 1,
|
||||
"tasks_started": 1,
|
||||
"tasks_after": 1,
|
||||
"database_connections_started": 0
|
||||
},
|
||||
{
|
||||
"mode": "normal",
|
||||
"enabled_component_count": 31,
|
||||
"startup_ms": 0.656,
|
||||
"full_lifespan_ms": 1.933,
|
||||
"enabled_component_count": 32,
|
||||
"startup_ms": 0.756,
|
||||
"full_lifespan_ms": 1.874,
|
||||
"stage_ms": {
|
||||
"文件日志": 0.07,
|
||||
"后台任务登记器": 0.036,
|
||||
"数据库准备": 0.027,
|
||||
"HTTP 基础能力": 0.022,
|
||||
"站点访问端口": 0.022,
|
||||
"Chain 外部端口": 0.019,
|
||||
"Chain 网络端口": 0.018,
|
||||
"领域依赖装配": 0.018,
|
||||
"数据库引擎预热": 0.018,
|
||||
"数据库连接预算": 0.017,
|
||||
"路由": 0.019,
|
||||
"插件服务装配": 0.018,
|
||||
"模块服务": 0.017,
|
||||
"消息队列": 0.018,
|
||||
"插件备份恢复": 0.017,
|
||||
"插件": 0.017,
|
||||
"定时器": 0.018,
|
||||
"后台任务登记器": 0.043,
|
||||
"数据库准备": 0.032,
|
||||
"HTTP 基础能力": 0.028,
|
||||
"站点访问端口": 0.024,
|
||||
"Chain 外部端口": 0.023,
|
||||
"Chain 网络端口": 0.02,
|
||||
"领域依赖装配": 0.022,
|
||||
"数据库引擎预热": 0.02,
|
||||
"数据库连接预算": 0.022,
|
||||
"路由": 0.023,
|
||||
"插件运行时装配": 0.022,
|
||||
"模块服务": 0.02,
|
||||
"插件服务装配": 0.022,
|
||||
"消息队列": 0.02,
|
||||
"插件备份恢复": 0.021,
|
||||
"插件": 0.021,
|
||||
"定时器": 0.021,
|
||||
"监控器": 0.017,
|
||||
"待处理整理回放": 0.017,
|
||||
"待处理整理回放": 0.019,
|
||||
"命令服务": 0.017,
|
||||
"工作流": 0.017,
|
||||
"插件同步与启动收尾": 0.017
|
||||
"工作流": 0.02,
|
||||
"插件同步与启动收尾": 0.021
|
||||
},
|
||||
"threads_before": 1,
|
||||
"threads_started": 1,
|
||||
@@ -120,32 +84,71 @@
|
||||
},
|
||||
{
|
||||
"mode": "normal",
|
||||
"enabled_component_count": 31,
|
||||
"startup_ms": 0.737,
|
||||
"full_lifespan_ms": 1.891,
|
||||
"enabled_component_count": 32,
|
||||
"startup_ms": 0.709,
|
||||
"full_lifespan_ms": 1.824,
|
||||
"stage_ms": {
|
||||
"文件日志": 0.074,
|
||||
"文件日志": 0.064,
|
||||
"后台任务登记器": 0.034,
|
||||
"数据库准备": 0.026,
|
||||
"HTTP 基础能力": 0.025,
|
||||
"站点访问端口": 0.021,
|
||||
"Chain 外部端口": 0.021,
|
||||
"Chain 网络端口": 0.02,
|
||||
"领域依赖装配": 0.02,
|
||||
"数据库引擎预热": 0.017,
|
||||
"数据库连接预算": 0.019,
|
||||
"路由": 0.02,
|
||||
"插件运行时装配": 0.019,
|
||||
"模块服务": 0.02,
|
||||
"插件服务装配": 0.021,
|
||||
"消息队列": 0.021,
|
||||
"插件备份恢复": 0.021,
|
||||
"插件": 0.02,
|
||||
"定时器": 0.02,
|
||||
"监控器": 0.021,
|
||||
"待处理整理回放": 0.021,
|
||||
"命令服务": 0.021,
|
||||
"工作流": 0.02,
|
||||
"插件同步与启动收尾": 0.021
|
||||
},
|
||||
"threads_before": 1,
|
||||
"threads_started": 1,
|
||||
"threads_after": 1,
|
||||
"tasks_before": 1,
|
||||
"tasks_started": 1,
|
||||
"tasks_after": 1,
|
||||
"database_connections_started": 0
|
||||
},
|
||||
{
|
||||
"mode": "normal",
|
||||
"enabled_component_count": 32,
|
||||
"startup_ms": 0.751,
|
||||
"full_lifespan_ms": 1.889,
|
||||
"stage_ms": {
|
||||
"文件日志": 0.069,
|
||||
"后台任务登记器": 0.037,
|
||||
"数据库准备": 0.029,
|
||||
"HTTP 基础能力": 0.03,
|
||||
"数据库准备": 0.028,
|
||||
"HTTP 基础能力": 0.029,
|
||||
"站点访问端口": 0.024,
|
||||
"Chain 外部端口": 0.023,
|
||||
"Chain 网络端口": 0.022,
|
||||
"领域依赖装配": 0.021,
|
||||
"领域依赖装配": 0.022,
|
||||
"数据库引擎预热": 0.021,
|
||||
"数据库连接预算": 0.021,
|
||||
"路由": 0.022,
|
||||
"插件服务装配": 0.021,
|
||||
"模块服务": 0.02,
|
||||
"消息队列": 0.02,
|
||||
"插件备份恢复": 0.02,
|
||||
"插件": 0.019,
|
||||
"定时器": 0.02,
|
||||
"监控器": 0.02,
|
||||
"待处理整理回放": 0.02,
|
||||
"命令服务": 0.02,
|
||||
"工作流": 0.02,
|
||||
"插件同步与启动收尾": 0.017
|
||||
"插件运行时装配": 0.021,
|
||||
"模块服务": 0.021,
|
||||
"插件服务装配": 0.022,
|
||||
"消息队列": 0.021,
|
||||
"插件备份恢复": 0.021,
|
||||
"插件": 0.021,
|
||||
"定时器": 0.021,
|
||||
"监控器": 0.021,
|
||||
"待处理整理回放": 0.021,
|
||||
"命令服务": 0.021,
|
||||
"工作流": 0.021,
|
||||
"插件同步与启动收尾": 0.021
|
||||
},
|
||||
"threads_before": 1,
|
||||
"threads_started": 1,
|
||||
@@ -156,9 +159,9 @@
|
||||
"database_connections_started": 0
|
||||
}
|
||||
],
|
||||
"median_startup_ms": 0.667,
|
||||
"median_full_lifespan_ms": 1.891,
|
||||
"enabled_component_count": 31,
|
||||
"median_startup_ms": 0.751,
|
||||
"median_full_lifespan_ms": 1.874,
|
||||
"enabled_component_count": 32,
|
||||
"enabled_components": [
|
||||
"文件日志",
|
||||
"后台任务登记器",
|
||||
@@ -171,8 +174,9 @@
|
||||
"数据库引擎预热",
|
||||
"数据库连接预算",
|
||||
"路由",
|
||||
"插件服务装配",
|
||||
"插件运行时装配",
|
||||
"模块服务",
|
||||
"插件服务装配",
|
||||
"插件备份恢复",
|
||||
"消息队列",
|
||||
"插件",
|
||||
@@ -197,24 +201,56 @@
|
||||
"samples": [
|
||||
{
|
||||
"mode": "safe",
|
||||
"enabled_component_count": 19,
|
||||
"startup_ms": 0.556,
|
||||
"full_lifespan_ms": 1.16,
|
||||
"enabled_component_count": 20,
|
||||
"startup_ms": 0.614,
|
||||
"full_lifespan_ms": 1.263,
|
||||
"stage_ms": {
|
||||
"文件日志": 0.072,
|
||||
"后台任务登记器": 0.04,
|
||||
"数据库准备": 0.026,
|
||||
"HTTP 基础能力": 0.023,
|
||||
"站点访问端口": 0.02,
|
||||
"Chain 外部端口": 0.019,
|
||||
"Chain 网络端口": 0.018,
|
||||
"领域依赖装配": 0.018,
|
||||
"数据库引擎预热": 0.018,
|
||||
"数据库连接预算": 0.017,
|
||||
"路由": 0.018,
|
||||
"插件服务装配": 0.018,
|
||||
"模块服务": 0.017,
|
||||
"消息队列": 0.017,
|
||||
"文件日志": 0.069,
|
||||
"后台任务登记器": 0.039,
|
||||
"数据库准备": 0.03,
|
||||
"HTTP 基础能力": 0.027,
|
||||
"站点访问端口": 0.024,
|
||||
"Chain 外部端口": 0.024,
|
||||
"Chain 网络端口": 0.022,
|
||||
"领域依赖装配": 0.02,
|
||||
"数据库引擎预热": 0.022,
|
||||
"数据库连接预算": 0.026,
|
||||
"路由": 0.023,
|
||||
"插件运行时装配": 0.021,
|
||||
"模块服务": 0.022,
|
||||
"插件服务装配": 0.021,
|
||||
"消息队列": 0.021,
|
||||
"插件同步与启动收尾": 0.02
|
||||
},
|
||||
"threads_before": 1,
|
||||
"threads_started": 1,
|
||||
"threads_after": 1,
|
||||
"tasks_before": 1,
|
||||
"tasks_started": 1,
|
||||
"tasks_after": 1,
|
||||
"database_connections_started": 0
|
||||
},
|
||||
{
|
||||
"mode": "safe",
|
||||
"enabled_component_count": 20,
|
||||
"startup_ms": 0.609,
|
||||
"full_lifespan_ms": 1.252,
|
||||
"stage_ms": {
|
||||
"文件日志": 0.071,
|
||||
"后台任务登记器": 0.036,
|
||||
"数据库准备": 0.028,
|
||||
"HTTP 基础能力": 0.026,
|
||||
"站点访问端口": 0.021,
|
||||
"Chain 外部端口": 0.021,
|
||||
"Chain 网络端口": 0.021,
|
||||
"领域依赖装配": 0.019,
|
||||
"数据库引擎预热": 0.027,
|
||||
"数据库连接预算": 0.022,
|
||||
"路由": 0.026,
|
||||
"插件运行时装配": 0.022,
|
||||
"模块服务": 0.021,
|
||||
"插件服务装配": 0.021,
|
||||
"消息队列": 0.02,
|
||||
"插件同步与启动收尾": 0.017
|
||||
},
|
||||
"threads_before": 1,
|
||||
@@ -227,55 +263,26 @@
|
||||
},
|
||||
{
|
||||
"mode": "safe",
|
||||
"enabled_component_count": 19,
|
||||
"startup_ms": 0.637,
|
||||
"full_lifespan_ms": 1.26,
|
||||
"enabled_component_count": 20,
|
||||
"startup_ms": 0.582,
|
||||
"full_lifespan_ms": 1.229,
|
||||
"stage_ms": {
|
||||
"文件日志": 0.081,
|
||||
"后台任务登记器": 0.038,
|
||||
"数据库准备": 0.03,
|
||||
"HTTP 基础能力": 0.027,
|
||||
"站点访问端口": 0.024,
|
||||
"Chain 外部端口": 0.022,
|
||||
"Chain 网络端口": 0.021,
|
||||
"领域依赖装配": 0.022,
|
||||
"数据库引擎预热": 0.022,
|
||||
"数据库连接预算": 0.022,
|
||||
"路由": 0.021,
|
||||
"插件服务装配": 0.022,
|
||||
"模块服务": 0.021,
|
||||
"消息队列": 0.021,
|
||||
"插件同步与启动收尾": 0.021
|
||||
},
|
||||
"threads_before": 1,
|
||||
"threads_started": 1,
|
||||
"threads_after": 1,
|
||||
"tasks_before": 1,
|
||||
"tasks_started": 1,
|
||||
"tasks_after": 1,
|
||||
"database_connections_started": 0
|
||||
},
|
||||
{
|
||||
"mode": "safe",
|
||||
"enabled_component_count": 19,
|
||||
"startup_ms": 0.578,
|
||||
"full_lifespan_ms": 1.189,
|
||||
"stage_ms": {
|
||||
"文件日志": 0.071,
|
||||
"后台任务登记器": 0.036,
|
||||
"文件日志": 0.072,
|
||||
"后台任务登记器": 0.042,
|
||||
"数据库准备": 0.028,
|
||||
"HTTP 基础能力": 0.027,
|
||||
"HTTP 基础能力": 0.025,
|
||||
"站点访问端口": 0.023,
|
||||
"Chain 外部端口": 0.022,
|
||||
"Chain 外部端口": 0.021,
|
||||
"Chain 网络端口": 0.022,
|
||||
"领域依赖装配": 0.02,
|
||||
"数据库引擎预热": 0.021,
|
||||
"数据库连接预算": 0.021,
|
||||
"路由": 0.022,
|
||||
"插件服务装配": 0.021,
|
||||
"领域依赖装配": 0.017,
|
||||
"数据库引擎预热": 0.019,
|
||||
"数据库连接预算": 0.019,
|
||||
"路由": 0.019,
|
||||
"插件运行时装配": 0.02,
|
||||
"模块服务": 0.02,
|
||||
"插件服务装配": 0.019,
|
||||
"消息队列": 0.021,
|
||||
"插件同步与启动收尾": 0.02
|
||||
"插件同步与启动收尾": 0.019
|
||||
},
|
||||
"threads_before": 1,
|
||||
"threads_started": 1,
|
||||
@@ -286,9 +293,9 @@
|
||||
"database_connections_started": 0
|
||||
}
|
||||
],
|
||||
"median_startup_ms": 0.578,
|
||||
"median_full_lifespan_ms": 1.189,
|
||||
"enabled_component_count": 19,
|
||||
"median_startup_ms": 0.609,
|
||||
"median_full_lifespan_ms": 1.252,
|
||||
"enabled_component_count": 20,
|
||||
"enabled_components": [
|
||||
"文件日志",
|
||||
"后台任务登记器",
|
||||
@@ -301,8 +308,9 @@
|
||||
"数据库引擎预热",
|
||||
"数据库连接预算",
|
||||
"路由",
|
||||
"插件服务装配",
|
||||
"插件运行时装配",
|
||||
"模块服务",
|
||||
"插件服务装配",
|
||||
"消息队列",
|
||||
"整理后台服务",
|
||||
"AI智能体会话",
|
||||
|
||||
@@ -45,6 +45,7 @@ def _patch_lifespan(monkeypatch, *, failing_step: str | None = None) -> dict:
|
||||
"init_workflow",
|
||||
):
|
||||
monkeypatch.setattr(lifecycle, name, MagicMock())
|
||||
monkeypatch.setattr(lifecycle, "configure_plugin_runtime_services", MagicMock())
|
||||
monkeypatch.setattr(lifecycle, "configure_plugin_services", MagicMock())
|
||||
plugin_recovery = MagicMock()
|
||||
plugin_recovery.replay = AsyncMock()
|
||||
@@ -547,12 +548,13 @@ def test_lifespan_waits_for_uncancellable_plugin_settlement_before_shutdown(
|
||||
assert order[:2] == ["settled", "backup"]
|
||||
|
||||
|
||||
def test_lifespan_configures_plugin_services_before_modules_and_restore(monkeypatch):
|
||||
"""插件 Runtime 必须先于模块 Chain 上下文和恢复阶段完成装配。"""
|
||||
def test_lifespan_configures_plugin_runtime_and_services_in_dependency_order(monkeypatch):
|
||||
"""插件 Runtime、模块持久化和应用服务必须按依赖顺序装配。"""
|
||||
shutdown_steps = _patch_lifespan(monkeypatch)
|
||||
order = []
|
||||
lifecycle.configure_plugin_services.side_effect = lambda: order.append("configure")
|
||||
lifecycle.configure_plugin_runtime_services.side_effect = lambda: order.append("runtime")
|
||||
lifecycle.init_modules.side_effect = lambda: order.append("modules")
|
||||
lifecycle.configure_plugin_services.side_effect = lambda: order.append("services")
|
||||
lifecycle.get_plugin_installation_recovery.return_value.replay.side_effect = (
|
||||
lambda: order.append("replay")
|
||||
)
|
||||
@@ -566,7 +568,7 @@ def test_lifespan_configures_plugin_services_before_modules_and_restore(monkeypa
|
||||
|
||||
asyncio.run(run_lifespan())
|
||||
|
||||
assert order == ["configure", "modules", "replay", "restore"]
|
||||
assert order == ["runtime", "modules", "services", "replay", "restore"]
|
||||
_assert_completed_once(shutdown_steps["close_http"])
|
||||
|
||||
|
||||
@@ -671,8 +673,9 @@ def test_lifecycle_manifest_declares_normal_and_safe_mode_order() -> None:
|
||||
"数据库引擎预热",
|
||||
"数据库连接预算",
|
||||
"路由",
|
||||
"插件服务装配",
|
||||
"插件运行时装配",
|
||||
"模块服务",
|
||||
"插件服务装配",
|
||||
"消息队列",
|
||||
"插件备份恢复",
|
||||
"插件",
|
||||
@@ -719,8 +722,9 @@ def test_lifecycle_manifest_declares_normal_and_safe_mode_order() -> None:
|
||||
"数据库引擎预热",
|
||||
"数据库连接预算",
|
||||
"路由",
|
||||
"插件服务装配",
|
||||
"插件运行时装配",
|
||||
"模块服务",
|
||||
"插件服务装配",
|
||||
"消息队列",
|
||||
"AI智能体会话",
|
||||
"整理后台服务",
|
||||
|
||||
@@ -505,11 +505,6 @@ async def test_sync_runs_identity_migration_before_automatic_install(
|
||||
"require",
|
||||
lambda: asyncio.get_running_loop(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
plugins_initializer,
|
||||
"configure_plugin_services",
|
||||
lambda: order.append("configure"),
|
||||
)
|
||||
monkeypatch.setattr(plugins_initializer, "PluginManager", lambda: manager)
|
||||
monkeypatch.setattr(
|
||||
plugins_initializer,
|
||||
@@ -529,7 +524,7 @@ async def test_sync_runs_identity_migration_before_automatic_install(
|
||||
monkeypatch.setattr(plugins_initializer, "execute_task", execute)
|
||||
|
||||
assert await plugins_initializer.sync_plugins() is False
|
||||
assert order == ["configure", "migrate", "identity", "sync"]
|
||||
assert order == ["migrate", "identity", "sync"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
import ast
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from app.startup.initializers import plugins as plugins_initializer
|
||||
|
||||
@@ -99,9 +96,9 @@ def test_startup_injects_runtime_factory_before_manager_materialization() -> Non
|
||||
for node in _parse(startup_path).body
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||||
}
|
||||
service_calls = sorted(
|
||||
runtime_calls = sorted(
|
||||
(node.lineno, ast.unparse(node.func))
|
||||
for node in ast.walk(functions["configure_plugin_services"])
|
||||
for node in ast.walk(functions["configure_plugin_runtime_services"])
|
||||
if isinstance(node, ast.Call)
|
||||
)
|
||||
sync_calls = sorted(
|
||||
@@ -110,36 +107,22 @@ def test_startup_injects_runtime_factory_before_manager_materialization() -> Non
|
||||
if isinstance(node, ast.Call)
|
||||
)
|
||||
|
||||
assert service_calls.index(
|
||||
next(item for item in service_calls if item[1] == "configure_plugin_runtime_factory")
|
||||
) < service_calls.index(
|
||||
next(item for item in service_calls if item[1] == "configure_plugin_runtime")
|
||||
) < service_calls.index(
|
||||
next(item for item in service_calls if item[1] == "get_plugin_manager")
|
||||
)
|
||||
assert sync_calls.index(
|
||||
next(item for item in sync_calls if item[1] == "configure_plugin_services")
|
||||
) < sync_calls.index(
|
||||
next(item for item in sync_calls if item[1] == "PluginManager")
|
||||
assert runtime_calls.index(
|
||||
next(item for item in runtime_calls if item[1] == "configure_plugin_runtime_factory")
|
||||
) < runtime_calls.index(
|
||||
next(item for item in runtime_calls if item[1] == "configure_plugin_runtime")
|
||||
)
|
||||
assert not any(item[1] == "configure_plugin_services" for item in sync_calls)
|
||||
|
||||
|
||||
def test_plugin_services_publish_application_runtime_before_lookup(monkeypatch) -> None:
|
||||
"""插件服务装配必须先发布 Application provider,再读取 Manager。"""
|
||||
def test_plugin_runtime_services_publish_application_runtime(monkeypatch) -> None:
|
||||
"""模块对象图构造前必须先发布 Runtime 工厂和 Application provider。"""
|
||||
order: list[str] = []
|
||||
|
||||
class _LookupReached(RuntimeError):
|
||||
"""表示测试已执行到首次插件 Manager 读取。"""
|
||||
|
||||
def factory(*_args, **_kwargs):
|
||||
"""返回不触发真实构造逻辑的测试替身。"""
|
||||
return Mock()
|
||||
|
||||
monkeypatch.setattr(plugins_initializer, "PluginMarketTransport", factory)
|
||||
monkeypatch.setattr(plugins_initializer, "PluginMarketClient", factory)
|
||||
monkeypatch.setattr(plugins_initializer, "PluginPackageSourceClient", factory)
|
||||
monkeypatch.setattr(plugins_initializer, "PluginPackageManager", factory)
|
||||
monkeypatch.setattr(plugins_initializer, "configure_plugin_catalog_factory", Mock())
|
||||
monkeypatch.setattr(
|
||||
plugins_initializer,
|
||||
"configure_plugin_catalog_factory",
|
||||
lambda _factory: order.append("catalog"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
plugins_initializer,
|
||||
"configure_plugin_runtime_factory",
|
||||
@@ -151,17 +134,9 @@ def test_plugin_services_publish_application_runtime_before_lookup(monkeypatch)
|
||||
lambda _provider: order.append("provider"),
|
||||
)
|
||||
|
||||
def lookup_manager():
|
||||
"""记录首次读取并终止无关的后续服务图构造。"""
|
||||
order.append("lookup")
|
||||
raise _LookupReached
|
||||
plugins_initializer.configure_plugin_runtime_services()
|
||||
|
||||
monkeypatch.setattr(plugins_initializer, "get_plugin_manager", lookup_manager)
|
||||
|
||||
with pytest.raises(_LookupReached):
|
||||
plugins_initializer.configure_plugin_services()
|
||||
|
||||
assert order == ["factory", "provider", "lookup"]
|
||||
assert order == ["catalog", "factory", "provider"]
|
||||
|
||||
|
||||
def test_plugin_manager_does_not_reach_raw_system_adapters() -> None:
|
||||
|
||||
@@ -89,11 +89,6 @@ def test_init_plugins_starts_monitor_after_runtime_and_routes(monkeypatch) -> No
|
||||
manager.start_monitor.side_effect = lambda **_kwargs: order.append("monitor")
|
||||
manager.get_local_repo_plugins.return_value = []
|
||||
manager.get_plugin_source_id.side_effect = lambda plugin_id: plugin_id
|
||||
monkeypatch.setattr(
|
||||
plugins_initializer,
|
||||
"configure_plugin_services",
|
||||
lambda: order.append("services"),
|
||||
)
|
||||
monkeypatch.setattr(plugins_initializer, "PluginManager", lambda: manager)
|
||||
monkeypatch.setattr(
|
||||
plugins_initializer,
|
||||
@@ -104,7 +99,6 @@ def test_init_plugins_starts_monitor_after_runtime_and_routes(monkeypatch) -> No
|
||||
plugins_initializer.init_plugins()
|
||||
|
||||
assert order == [
|
||||
"services",
|
||||
"reopen",
|
||||
"plugin:ReadyPlugin",
|
||||
"routes",
|
||||
|
||||
Reference in New Issue
Block a user