refactor: own shutdown lifecycle boundaries

This commit is contained in:
jxxghp
2026-08-23 20:20:26 +08:00
parent 59f020f226
commit 7f09927c47
59 changed files with 6393 additions and 958 deletions
+25 -13
View File
@@ -4,7 +4,9 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from typing import Any, ContextManager
from app.schemas.exception import PluginMutationRejectedError
@dataclass(frozen=True, slots=True)
@@ -29,6 +31,7 @@ class PluginConfigCommand:
reload_runtime: Callable[[str], Any],
publish_reset: Callable[[str], Any],
refresh_registrations: Callable[[str], Any],
mutation: Callable[[str], ContextManager[None]],
) -> None:
"""保存插件管理 Facade 和运行时注册刷新端口。"""
self._save_config = save_config
@@ -39,21 +42,30 @@ class PluginConfigCommand:
self._reload_runtime = reload_runtime
self._publish_reset = publish_reset
self._refresh_registrations = refresh_registrations
self._mutation = mutation
def update(self, plugin_id: str, config: dict) -> PluginConfigResult:
"""保存配置并按既有顺序重新初始化实例及运行时注册。"""
if not self._save_config(plugin_id, config, False):
return PluginConfigResult(False, "插件配置保存失败")
self._initialize(plugin_id, config)
self._refresh_registrations(plugin_id)
return PluginConfigResult(True)
try:
with self._mutation(f"更新插件 {plugin_id} 配置"):
if not self._save_config(plugin_id, config, False):
return PluginConfigResult(False, "插件配置保存失败")
self._initialize(plugin_id, config)
self._refresh_registrations(plugin_id)
return PluginConfigResult(True)
except PluginMutationRejectedError as error:
return PluginConfigResult(False, str(error))
def reset(self, plugin_id: str) -> PluginConfigResult:
"""通知插件补偿后停止实例、删除配置数据并重建运行态。"""
self._publish_reset(plugin_id)
self._stop(plugin_id)
self._delete_config(plugin_id, True)
self._delete_data(plugin_id, True)
self._reload_runtime(plugin_id)
self._refresh_registrations(plugin_id)
return PluginConfigResult(True)
try:
with self._mutation(f"重置插件 {plugin_id} 配置和数据"):
self._publish_reset(plugin_id)
self._stop(plugin_id)
self._delete_config(plugin_id, True)
self._delete_data(plugin_id, True)
self._reload_runtime(plugin_id)
self._refresh_registrations(plugin_id)
return PluginConfigResult(True)
except PluginMutationRejectedError as error:
return PluginConfigResult(False, str(error))
+30 -14
View File
@@ -5,11 +5,12 @@ from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from typing import Any, Optional
from typing import Any, ContextManager, Optional
from app.schemas.exception import PersistenceUnavailableError
from app.application.plugin.lifecycle import plugin_lifecycle
from app.runtime.log import logger
from app.schemas.exception import PluginMutationRejectedError
InstalledPluginsReader = Callable[[], list[str]]
@@ -25,6 +26,8 @@ PackageCheckpointAction = Callable[[Any], Awaitable[object]]
InstallReporter = Callable[[str, Optional[str]], Awaitable[object]]
PluginReloader = Callable[[str], Awaitable[object]]
PluginRegistrationRefresher = Callable[[str], Awaitable[object]]
PluginMutationAdmission = Callable[[str], ContextManager[None]]
PluginPackageWriteGuard = Callable[[str], ContextManager[None]]
@dataclass(frozen=True, slots=True)
@@ -94,6 +97,8 @@ class PluginInstallCommand:
install_reporter: InstallReporter,
plugin_reloader: PluginReloader,
registration_refresher: PluginRegistrationRefresher,
mutation: PluginMutationAdmission,
package_write_guard: PluginPackageWriteGuard,
) -> None:
"""保存安装用例所需端口,不绑定数据库、网络或运行时实现。"""
self._installed_plugins_reader = installed_plugins_reader
@@ -107,6 +112,8 @@ class PluginInstallCommand:
self._install_reporter = install_reporter
self._plugin_reloader = plugin_reloader
self._registration_refresher = registration_refresher
self._mutation = mutation
self._package_write_guard = package_write_guard
async def execute(
self,
@@ -120,20 +127,29 @@ class PluginInstallCommand:
state = _InstallState()
async with plugin_lifecycle.hold(plugin_id):
try:
return await self._execute_locked(
plugin_id=plugin_id,
repo_url=repo_url,
release_version=release_version,
force=force,
state=state,
with self._mutation(f"安装插件 {plugin_id}"):
with self._package_write_guard(plugin_id):
try:
return await self._execute_locked(
plugin_id=plugin_id,
repo_url=repo_url,
release_version=release_version,
force=force,
state=state,
)
except asyncio.CancelledError:
await self._rollback_cancelled(
plugin_id=plugin_id,
original_plugins=state.original_plugins,
state=state,
)
raise
except PluginMutationRejectedError as error:
return PluginInstallResult(
success=False,
message=str(error),
failure_stage="admission",
)
except asyncio.CancelledError:
await self._rollback_cancelled(
plugin_id=plugin_id,
original_plugins=state.original_plugins,
state=state,
)
raise
async def _execute_locked(
self,
+5 -2
View File
@@ -3,12 +3,15 @@
from __future__ import annotations
from collections.abc import Callable
from typing import Any, Protocol
from typing import Any, ContextManager, Protocol
class PluginRuntime(Protocol):
"""声明入口层消费的插件宿主能力。"""
def mutation(self, operation: str) -> ContextManager[None]:
"""为完整插件可变事务取得停机准入 lease。"""
...
def __getattr__(self, name: str) -> Any:
"""允许兼容门面按既有 V3 方法名访问插件宿主能力。"""