Files
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

1652 lines
57 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
能力解析、启动、重载和关闭状态机,协调并发调用与遗留资源清理。
"""
from __future__ import annotations
import asyncio
import inspect
import sys
import threading
import time
from collections import deque
from concurrent.futures import Future
from dataclasses import dataclass, field
from types import MappingProxyType
from typing import Any, Callable, Mapping, Optional, Union
from app.runtime.capabilities.errors import (
CapabilityAdapterContractError,
CapabilityAdapterModeError,
CapabilityOperationError,
CapabilityRuntimeClosedError,
)
from app.runtime.capabilities.model import (
AdapterExecutionMode,
CapabilityLifecycleState,
CapabilityMaterializationState,
CapabilityObservation,
CapabilitySnapshot,
CapabilitySpec,
)
from app.runtime.capabilities.registry import CapabilityRegistry
@dataclass(slots=True)
class _CapabilityState:
"""
Runtime 私有状态;所有可变字段均由 lock 保护。
:param spec: 注册时冻结的能力声明
:param lock: 保护该能力所有可变状态的可重入锁
:param materialization: 实现对象的解析状态,初始为未解析
:param lifecycle: 外部资源的生命周期状态,初始为已发现
:param generation: 状态转换代次,用于拒绝过期结果,初始为 0
:param implementation: 已解析的实现对象;尚未解析时为 None
:param instance: 当前已发布实例;不可见时为 None
:param pending_stop: 尚未成功停止、仍由运行时持有的资源;没有时为 None
:param pending_cleanup: 失败启动后仍待清理的候选资源;没有时为 None
:param pending_cleanup_error: 触发候选资源清理的异常;没有时为 None
:param last_error: 最近一次状态转换异常;没有时为 None
:param inflight: 正在进行的状态转换完成信号;空闲时为 None
:param inflight_operation: 正在进行的操作名称;空闲时为 None
"""
spec: CapabilitySpec
lock: threading.RLock = field(default_factory=threading.RLock)
materialization: CapabilityMaterializationState = CapabilityMaterializationState.UNRESOLVED
lifecycle: CapabilityLifecycleState = CapabilityLifecycleState.DISCOVERED
generation: int = 0
implementation: Any = None
instance: Any = None
pending_stop: Any = None
pending_cleanup: Any = None
pending_cleanup_error: Optional[BaseException] = None
last_error: Optional[BaseException] = None
inflight: Optional[Future[Any]] = None
inflight_operation: Optional[str] = None
@dataclass(frozen=True, slots=True)
class _PublishedTransition:
"""
表示状态机已经拥有可直接返回的发布结果。
:param result: 状态机已经发布、可直接交给调用方的结果
"""
result: Any
@dataclass(frozen=True, slots=True)
class _SettledTransition:
"""
表示停止状态无需 I/O 即已真实收敛。
"""
@dataclass(frozen=True, slots=True)
class _TransitionWait:
"""
表示调用方必须等待另一位 owner 完成当前状态转换。
:param future: 其他执行方负责完成的状态转换信号
:param operation: 正在等待的操作名称;未标记时为 None
"""
future: Future[Any]
operation: Optional[str]
@dataclass(frozen=True, slots=True)
class _MaterializationClaim:
"""
记录一次物化状态转换的唯一 owner。
:param generation: 本次解析占用的代次
:param future: 由本次解析完成或异常终结的共享信号
"""
generation: int
future: Future[Any]
@dataclass(frozen=True, slots=True)
class _ActivationClaim:
"""
记录启动状态转换以及需要先收敛的遗留资源。
:param generation: 本次激活占用的代次
:param future: 向等待者发布本次激活结果的信号
:param implementation: 已解析且用于创建候选资源的实现对象
:param pending_stop: 激活前需要完成停止的旧资源;没有时为 None
:param pending_cleanup: 激活前需要清理的失败候选;没有时为 None
:param pending_cleanup_error: 遗留候选启动失败的原因;没有时为 None
"""
generation: int
future: Future[Any]
implementation: Any
pending_stop: Any
pending_cleanup: Any
pending_cleanup_error: Optional[BaseException]
@dataclass(frozen=True, slots=True)
class _ReloadClaim:
"""
记录重载状态转换及已撤销可见性的旧实例。
:param generation: 本次重载占用的代次
:param future: 向等待者发布本次重载结果的信号
:param implementation: 创建新一代资源使用的实现对象
:param previous: 已撤销可见性的上一代实例,供重载交接使用
"""
generation: int
future: Future[Any]
implementation: Any
previous: Any
@dataclass(frozen=True, slots=True)
class _StopClaim:
"""
记录停止状态转换及 Runtime 仍持有的全部资源 owner。
:param generation: 本次停止占用的代次
:param future: 向等待者发布停止结果的信号
:param stop_owner: 运行时仍负责停止的资源实例
:param pending_cleanup: 仍需清理的失败候选;没有时为 None
:param pending_cleanup_error: 失败候选的原始异常;没有时为 None
"""
generation: int
future: Future[Any]
stop_owner: Any
pending_cleanup: Any
pending_cleanup_error: Optional[BaseException]
class CapabilityRuntime:
"""
协调能力实现物化和资源生命周期的通用运行时。
"""
def __init__(
self,
registry: CapabilityRegistry,
*,
adapters: Mapping[str, Any],
observer: Optional[Callable[[CapabilityObservation], None]] = None,
observation_limit: int = 1024,
) -> None:
if observation_limit <= 0:
raise ValueError("observation_limit 必须大于 0")
missing_adapters = {spec.kind for spec in registry.list_specs()} - set(adapters)
if missing_adapters:
raise CapabilityAdapterContractError(
f"缺少 capability adapter:{sorted(missing_adapters)}"
)
self._registry = registry
self._adapters = MappingProxyType(dict(adapters))
self._states = {
spec.id: _CapabilityState(spec=spec)
for spec in registry.list_specs()
}
self._observer = observer
self._observations: deque[CapabilityObservation] = deque(maxlen=observation_limit)
self._observation_lock = threading.Lock()
self._shutdown_lock = threading.Lock()
self._shutdown = False
@property
def is_shutdown(self) -> bool:
"""
返回 Runtime 是否已进入不可逆关闭态。
"""
return self._shutdown
def _state(self, capability_id: str) -> _CapabilityState:
self._registry.require_spec(capability_id)
return self._states[capability_id]
def get_spec(self, capability_id: str) -> CapabilitySpec | None:
"""
返回保留在 Registry 中的声明,不受运行失败影响。
"""
return self._registry.get_spec(capability_id)
def list_specs(self) -> tuple[CapabilitySpec, ...]:
"""
返回全部声明;FAILED 能力不会从列表中消失。
"""
return self._registry.list_specs()
def _adapter(self, state: _CapabilityState, expected: AdapterExecutionMode) -> Any:
adapter = self._adapters[state.spec.kind]
actual = getattr(adapter, "execution_mode", None)
if actual is not expected:
raise CapabilityAdapterModeError(
f"能力 {state.spec.id} 的 adapter mode={actual!r},"
f"不能通过 {expected.value} 入口调用"
)
return adapter
def _ensure_open(self) -> None:
if self._shutdown:
raise CapabilityRuntimeClosedError("Capability Runtime 已关闭")
@staticmethod
def _sync_callback(adapter: Any, name: str, *args: Any) -> Any:
callback = getattr(adapter, name, None)
if not callable(callback):
raise CapabilityAdapterContractError(f"同步 adapter 缺少 {name}()")
result = callback(*args)
if inspect.isawaitable(result):
close = getattr(result, "close", None)
if callable(close):
close()
raise CapabilityAdapterContractError(
f"同步 adapter 的 {name}() 不能返回 awaitable"
)
return result
@staticmethod
async def _async_callback(adapter: Any, name: str, *args: Any) -> Any:
callback = getattr(adapter, name, None)
if not callable(callback):
raise CapabilityAdapterContractError(f"异步 adapter 缺少 {name}()")
result = callback(*args)
if not inspect.isawaitable(result):
raise CapabilityAdapterContractError(
f"异步 adapter 的 {name}() 必须返回 awaitable"
)
return await result
@staticmethod
def _wait_sync(future: Future[Any]) -> Any:
return future.result()
@staticmethod
async def _wait_async(future: Future[Any]) -> Any:
return await asyncio.shield(asyncio.wrap_future(future))
def _emit(
self,
state: _CapabilityState,
*,
generation: int,
operation: str,
outcome: str,
reason: str,
started_at: float,
error: Optional[BaseException] = None,
) -> None:
with state.lock:
observation = CapabilityObservation(
capability_id=state.spec.id,
generation=generation,
operation=operation,
outcome=outcome,
reason=reason,
materialization=state.materialization,
lifecycle=state.lifecycle,
duration_ms=max(0.0, (time.monotonic() - started_at) * 1000),
error=str(error) if error else None,
)
with self._observation_lock:
self._observations.append(observation)
if self._observer:
try:
self._observer(observation)
except Exception:
# 观测消费者不能改变能力生命周期的成功或失败语义。
pass
@staticmethod
def _finish_transition(
state: _CapabilityState,
future: Future[Any],
*,
result: Any = None,
error: Optional[BaseException] = None,
) -> None:
with state.lock:
if state.inflight is future:
state.inflight = None
state.inflight_operation = None
if error is None:
future.set_result(result)
else:
future.set_exception(error)
@staticmethod
def _claim_materialization(
state: _CapabilityState,
*,
retry: bool,
) -> Union[_PublishedTransition, _TransitionWait, _MaterializationClaim]:
"""
声明一次物化转换,且不执行任何 adapter I/O。
"""
with state.lock:
if state.materialization is CapabilityMaterializationState.RESOLVED:
return _PublishedTransition(state.implementation)
if state.materialization is CapabilityMaterializationState.FAILED and not retry:
raise CapabilityOperationError(
state.spec.id,
"materialize",
RuntimeError("能力处于 FAILED,必须显式 retry=True"),
)
if state.inflight is not None:
return _TransitionWait(state.inflight, state.inflight_operation)
state.generation += 1
future: Future[Any] = Future()
state.inflight = future
state.inflight_operation = "materialize"
state.materialization = CapabilityMaterializationState.RESOLVING
return _MaterializationClaim(state.generation, future)
@staticmethod
def _claim_activation(
state: _CapabilityState,
*,
operation: str,
retry: bool,
) -> Union[_PublishedTransition, _TransitionWait, _ActivationClaim]:
"""
声明一次启动转换并原子撤销遗留 owner 的共享状态。
"""
with state.lock:
if operation == "activate" and state.instance is not None:
return _PublishedTransition(state.instance)
if state.pending_stop is not None and not retry:
raise CapabilityOperationError(
state.spec.id,
operation,
RuntimeError("能力仍持有未释放资源,必须先重试 stop"),
)
if state.lifecycle is CapabilityLifecycleState.FAILED and not retry:
raise CapabilityOperationError(
state.spec.id,
operation,
RuntimeError("能力处于 FAILED,必须显式 retry=True"),
)
if state.inflight is not None:
return _TransitionWait(state.inflight, state.inflight_operation)
state.generation += 1
future: Future[Any] = Future()
state.inflight = future
state.inflight_operation = operation
pending_stop = state.pending_stop
state.lifecycle = (
CapabilityLifecycleState.STOPPING
if pending_stop is not None
else CapabilityLifecycleState.STARTING
)
if state.implementation is None:
state.materialization = CapabilityMaterializationState.RESOLVING
claim = _ActivationClaim(
generation=state.generation,
future=future,
implementation=state.implementation,
pending_stop=pending_stop,
pending_cleanup=state.pending_cleanup,
pending_cleanup_error=state.pending_cleanup_error,
)
state.pending_stop = None
state.pending_cleanup = None
state.pending_cleanup_error = None
return claim
@staticmethod
def _claim_reload(
state: _CapabilityState,
) -> Union[_TransitionWait, _ReloadClaim]:
"""
声明一次重载转换并先撤销旧实例的外部可见性。
"""
with state.lock:
if state.inflight is not None:
return _TransitionWait(state.inflight, state.inflight_operation)
if state.pending_stop is not None:
raise CapabilityOperationError(
state.spec.id,
"reload",
RuntimeError("能力仍持有未释放资源,必须先重试 stop"),
)
if state.instance is None:
raise CapabilityOperationError(
state.spec.id,
"reload",
RuntimeError("只有 RUNNING 能力可以 reload"),
)
state.generation += 1
future: Future[Any] = Future()
state.inflight = future
state.inflight_operation = "reload"
claim = _ReloadClaim(
generation=state.generation,
future=future,
implementation=state.implementation,
previous=state.instance,
)
state.instance = None
state.lifecycle = CapabilityLifecycleState.RELOADING
return claim
@staticmethod
def _claim_stop(
state: _CapabilityState,
) -> Union[_SettledTransition, _TransitionWait, _StopClaim]:
"""
声明一次停止转换并原子接管尚未释放的资源。
"""
with state.lock:
if state.inflight is not None:
return _TransitionWait(state.inflight, state.inflight_operation)
stop_owner = (
state.instance
if state.instance is not None
else state.pending_stop
)
pending_cleanup = state.pending_cleanup
if stop_owner is None and pending_cleanup is None:
if state.lifecycle is not CapabilityLifecycleState.FAILED:
state.lifecycle = CapabilityLifecycleState.STOPPED
return _SettledTransition()
state.generation += 1
future: Future[Any] = Future()
state.inflight = future
state.inflight_operation = "stop"
claim = _StopClaim(
generation=state.generation,
future=future,
stop_owner=stop_owner,
pending_cleanup=pending_cleanup,
pending_cleanup_error=state.pending_cleanup_error,
)
state.instance = None
state.pending_stop = None
state.pending_cleanup = None
state.pending_cleanup_error = None
state.lifecycle = CapabilityLifecycleState.STOPPING
return claim
@classmethod
def _publish_materialization(
cls,
state: _CapabilityState,
claim: _MaterializationClaim,
implementation: Any,
) -> None:
"""
提交物化成功状态并唤醒全部等待者。
"""
with state.lock:
state.implementation = implementation
state.materialization = CapabilityMaterializationState.RESOLVED
state.last_error = None
cls._finish_transition(state, claim.future, result=implementation)
@classmethod
def _fail_materialization(
cls,
state: _CapabilityState,
claim: _MaterializationClaim,
error: BaseException,
) -> BaseException:
"""
提交物化失败状态并返回稳定的公开异常。
"""
with state.lock:
state.materialization = CapabilityMaterializationState.FAILED
state.last_error = error
operation_error = cls._wrap_error(state.spec.id, "materialize", error)
cls._finish_transition(state, claim.future, error=operation_error)
return operation_error
@classmethod
def _publish_activation(
cls,
state: _CapabilityState,
claim: _ActivationClaim,
*,
implementation: Any,
candidate: Any,
) -> None:
"""
仅在候选启动成功后提交实现与可见实例。
"""
with state.lock:
state.implementation = implementation
state.materialization = CapabilityMaterializationState.RESOLVED
state.instance = candidate
state.lifecycle = CapabilityLifecycleState.RUNNING
state.last_error = None
cls._finish_transition(state, claim.future, result=candidate)
@classmethod
def _fail_activation(
cls,
state: _CapabilityState,
claim: _ActivationClaim,
*,
operation: str,
implementation: Any,
pending_stop: Any,
error: BaseException,
lifecycle_error: BaseException,
) -> BaseException:
"""
提交启动失败状态,并保留尚未释放的旧 owner。
"""
with state.lock:
state.implementation = implementation
state.materialization = (
CapabilityMaterializationState.RESOLVED
if implementation is not None
else CapabilityMaterializationState.FAILED
)
state.instance = None
if pending_stop is not None:
state.pending_stop = pending_stop
state.lifecycle = cls._failure_lifecycle(lifecycle_error)
state.last_error = error
operation_error = cls._wrap_error(state.spec.id, operation, error)
cls._finish_transition(state, claim.future, error=operation_error)
return operation_error
@classmethod
def _publish_reload(
cls,
state: _CapabilityState,
claim: _ReloadClaim,
candidate: Any,
) -> None:
"""
提交重载后的新实例并恢复对外可见性。
"""
with state.lock:
state.instance = candidate
state.lifecycle = CapabilityLifecycleState.RUNNING
state.last_error = None
cls._finish_transition(state, claim.future, result=candidate)
@classmethod
def _fail_reload(
cls,
state: _CapabilityState,
claim: _ReloadClaim,
*,
previous_released: bool,
error: BaseException,
lifecycle_error: BaseException,
) -> BaseException:
"""
提交重载失败状态,必要时保留未释放的旧实例。
"""
with state.lock:
state.instance = None
if not previous_released:
state.pending_stop = claim.previous
state.lifecycle = cls._failure_lifecycle(lifecycle_error)
state.last_error = error
operation_error = cls._wrap_error(state.spec.id, "reload", error)
cls._finish_transition(state, claim.future, error=operation_error)
return operation_error
@classmethod
def _publish_stop(
cls,
state: _CapabilityState,
claim: _StopClaim,
) -> None:
"""
提交资源真实收敛后的停止状态。
"""
with state.lock:
state.lifecycle = CapabilityLifecycleState.STOPPED
state.last_error = None
cls._finish_transition(state, claim.future, result=None)
@classmethod
def _fail_stop(
cls,
state: _CapabilityState,
claim: _StopClaim,
*,
stop_owner: Any,
pending_cleanup: Any,
error: BaseException,
) -> BaseException:
"""
提交停止失败状态并归还仍未释放的资源 owner。
"""
with state.lock:
state.lifecycle = CapabilityLifecycleState.FAILED
state.last_error = error
if stop_owner is not None:
state.pending_stop = stop_owner
if pending_cleanup is not None:
state.pending_cleanup = pending_cleanup
state.pending_cleanup_error = error
operation_error = cls._wrap_error(state.spec.id, "stop", error)
cls._finish_transition(state, claim.future, error=operation_error)
return operation_error
@staticmethod
def _publish_pending_stop_released(state: _CapabilityState) -> None:
"""
在遗留停止成功后把启动转换推进到 STARTING。
"""
with state.lock:
state.lifecycle = CapabilityLifecycleState.STARTING
@staticmethod
def _restore_pending_cleanup(
state: _CapabilityState,
candidate: Any,
error: Optional[BaseException],
) -> None:
"""
清理重试失败时归还 Runtime 对候选资源的所有权。
"""
with state.lock:
state.pending_cleanup = candidate
state.pending_cleanup_error = error
@staticmethod
def _failure_lifecycle(error: BaseException) -> CapabilityLifecycleState:
"""
把关闭竞态与普通失败映射到统一生命周期结果。
"""
if isinstance(error, CapabilityRuntimeClosedError):
return CapabilityLifecycleState.STOPPED
return CapabilityLifecycleState.FAILED
def _calibrate_consumer_materialization(self, state: _CapabilityState) -> None:
"""
从 sys.modules 校准显式 consumer import,不触发任何新导入。
"""
module_name, symbol_name = state.spec.entrypoint.split(":", maxsplit=1)
module = sys.modules.get(module_name)
namespace = getattr(module, "__dict__", None) if module is not None else None
if not isinstance(namespace, dict) or symbol_name not in namespace:
return
canonical = namespace[symbol_name]
with state.lock:
if state.inflight is not None:
return
if state.materialization is CapabilityMaterializationState.RESOLVED:
if state.implementation is not canonical:
state.last_error = CapabilityAdapterContractError(
f"能力 {state.spec.id} 的 canonical implementation identity 发生变化"
)
return
state.implementation = canonical
state.materialization = CapabilityMaterializationState.RESOLVED
if state.lifecycle is CapabilityLifecycleState.DISCOVERED:
state.last_error = None
generation = state.generation
self._emit(
state,
generation=generation,
operation="consumer_materialize",
outcome="succeeded",
reason="sys_modules",
started_at=time.monotonic(),
)
def snapshot(self, capability_id: str) -> CapabilitySnapshot:
"""
返回状态快照,并校准外部显式导入产生的 canonical 对象。
"""
state = self._state(capability_id)
self._calibrate_consumer_materialization(state)
with state.lock:
return CapabilitySnapshot(
capability_id=state.spec.id,
materialization=state.materialization,
lifecycle=state.lifecycle,
generation=state.generation,
visible=state.instance is not None,
error=str(state.last_error) if state.last_error else None,
)
def observations(self, capability_id: Optional[str] = None) -> tuple[CapabilityObservation, ...]:
"""
返回按发生顺序记录的不可变观测快照。
"""
with self._observation_lock:
items = tuple(self._observations)
if capability_id is None:
return items
return tuple(item for item in items if item.capability_id == capability_id)
def get_running(self, capability_id: str) -> Any:
"""
只查询已发布实例,不触发物化或启动。
"""
state = self._state(capability_id)
with state.lock:
return state.instance
def materialize(self, capability_id: str, *, reason: str, retry: bool = False) -> Any:
"""
通过同步 adapter 显式物化实现,不启动领域资源。
"""
state = self._state(capability_id)
adapter = self._adapter(state, AdapterExecutionMode.SYNC)
self._calibrate_consumer_materialization(state)
while True:
with self._shutdown_lock:
self._ensure_open()
decision = self._claim_materialization(state, retry=retry)
if isinstance(decision, _PublishedTransition):
return decision.result
if isinstance(decision, _TransitionWait):
result = self._wait_sync(decision.future)
if decision.operation == "materialize":
return result
continue
claim = decision
break
started_at = time.monotonic()
self._emit(
state,
generation=claim.generation,
operation="materialize",
outcome="started",
reason=reason,
started_at=started_at,
)
try:
implementation = self._sync_callback(adapter, "materialize", state.spec)
implementation = self._canonical_implementation(state.spec, implementation)
self._ensure_open()
self._publish_materialization(state, claim, implementation)
self._emit(
state,
generation=claim.generation,
operation="materialize",
outcome="succeeded",
reason=reason,
started_at=started_at,
)
return implementation
except BaseException as error:
operation_error = self._fail_materialization(state, claim, error)
self._emit(
state,
generation=claim.generation,
operation="materialize",
outcome="failed",
reason=reason,
started_at=started_at,
error=error,
)
raise operation_error from error
async def materialize_async(
self,
capability_id: str,
*,
reason: str,
retry: bool = False,
) -> Any:
"""
通过异步 adapter 显式物化实现,不阻塞事件循环。
"""
state = self._state(capability_id)
adapter = self._adapter(state, AdapterExecutionMode.ASYNC)
self._calibrate_consumer_materialization(state)
while True:
with self._shutdown_lock:
self._ensure_open()
decision = self._claim_materialization(state, retry=retry)
if isinstance(decision, _PublishedTransition):
return decision.result
if isinstance(decision, _TransitionWait):
result = await self._wait_async(decision.future)
if decision.operation == "materialize":
return result
continue
claim = decision
break
started_at = time.monotonic()
self._emit(
state,
generation=claim.generation,
operation="materialize",
outcome="started",
reason=reason,
started_at=started_at,
)
try:
implementation = await self._async_callback(adapter, "materialize", state.spec)
implementation = self._canonical_implementation(state.spec, implementation)
self._ensure_open()
self._publish_materialization(state, claim, implementation)
self._emit(
state,
generation=claim.generation,
operation="materialize",
outcome="succeeded",
reason=reason,
started_at=started_at,
)
return implementation
except BaseException as error:
operation_error = self._fail_materialization(state, claim, error)
self._emit(
state,
generation=claim.generation,
operation="materialize",
outcome="failed",
reason=reason,
started_at=started_at,
error=error,
)
raise operation_error from error
@staticmethod
def _canonical_implementation(spec: CapabilitySpec, implementation: Any) -> Any:
if implementation is None:
raise CapabilityAdapterContractError(
f"adapter 没有返回 {spec.id} 的 implementation"
)
module_name, symbol_name = spec.entrypoint.split(":", maxsplit=1)
module = sys.modules.get(module_name)
namespace = getattr(module, "__dict__", None) if module is not None else None
if not isinstance(namespace, dict):
raise CapabilityAdapterContractError(
f"adapter 返回 {spec.id} 的 implementation 后,"
f"canonical 模块 {module_name} 未加载"
)
if symbol_name not in namespace:
raise CapabilityAdapterContractError(
f"adapter 返回 {spec.id} 的 implementation 后,"
f"canonical 符号 {spec.entrypoint} 不存在"
)
canonical = namespace[symbol_name]
if implementation is not canonical:
raise CapabilityAdapterContractError(
f"adapter 返回的 {spec.id} 实现不是 sys.modules 中的 canonical 对象"
)
return canonical
@staticmethod
def _wrap_error(capability_id: str, operation: str, error: BaseException) -> BaseException:
if isinstance(error, (CapabilityOperationError, CapabilityRuntimeClosedError)):
return error
return CapabilityOperationError(capability_id, operation, error)
def activate(self, capability_id: str, *, reason: str, retry: bool = False) -> Any:
"""
同步启动能力,并在 start 成功后原子发布候选实例。
"""
return self._activate_sync(capability_id, reason=reason, retry=retry, previous=None)
async def activate_async(
self,
capability_id: str,
*,
reason: str,
retry: bool = False,
) -> Any:
"""
异步启动能力,并以 Future 协调并发调用者。
"""
return await self._activate_async(capability_id, reason=reason, retry=retry, previous=None)
def _activate_sync(
self,
capability_id: str,
*,
reason: str,
retry: bool,
previous: Any,
operation: str = "activate",
) -> Any:
state = self._state(capability_id)
adapter = self._adapter(state, AdapterExecutionMode.SYNC)
self._calibrate_consumer_materialization(state)
while True:
with self._shutdown_lock:
self._ensure_open()
decision = self._claim_activation(
state,
operation=operation,
retry=retry,
)
if isinstance(decision, _PublishedTransition):
return decision.result
if isinstance(decision, _TransitionWait):
result = self._wait_sync(decision.future)
if decision.operation == operation:
return result
continue
claim = decision
break
implementation = claim.implementation
pending_stop = claim.pending_stop
started_at = time.monotonic()
self._emit(
state,
generation=claim.generation,
operation=operation,
outcome="started",
reason=reason,
started_at=started_at,
)
candidate = None
try:
if pending_stop is not None:
self._sync_callback(
adapter,
"stop",
state.spec,
pending_stop,
claim.generation,
)
pending_stop = None
self._publish_pending_stop_released(state)
if claim.pending_cleanup is not None:
try:
self._sync_callback(
adapter,
"cleanup",
state.spec,
claim.pending_cleanup,
claim.generation,
claim.pending_cleanup_error or RuntimeError("pending cleanup"),
)
except BaseException:
self._restore_pending_cleanup(
state,
claim.pending_cleanup,
claim.pending_cleanup_error,
)
raise
if implementation is None:
materialized = self._sync_callback(adapter, "materialize", state.spec)
implementation = self._canonical_implementation(state.spec, materialized)
candidate = self._sync_callback(
adapter,
"create",
state.spec,
implementation,
claim.generation,
previous,
)
if candidate is None:
raise CapabilityAdapterContractError(
f"adapter 没有返回 {state.spec.id} 的 candidate"
)
self._sync_callback(
adapter,
"start",
state.spec,
candidate,
claim.generation,
)
self._ensure_open()
self._publish_activation(
state,
claim,
implementation=implementation,
candidate=candidate,
)
self._emit(
state,
generation=claim.generation,
operation=operation,
outcome="succeeded",
reason=reason,
started_at=started_at,
)
return candidate
except BaseException as error:
cleanup_error = self._cleanup_failed_sync(
state,
adapter,
candidate,
claim.generation,
error,
)
final_error = cleanup_error or error
operation_error = self._fail_activation(
state,
claim,
operation=operation,
implementation=implementation,
pending_stop=pending_stop,
error=final_error,
lifecycle_error=error,
)
self._emit(
state,
generation=claim.generation,
operation=operation,
outcome="failed",
reason=reason,
started_at=started_at,
error=final_error,
)
raise operation_error from error
async def _activate_async(
self,
capability_id: str,
*,
reason: str,
retry: bool,
previous: Any,
operation: str = "activate",
) -> Any:
state = self._state(capability_id)
adapter = self._adapter(state, AdapterExecutionMode.ASYNC)
self._calibrate_consumer_materialization(state)
while True:
with self._shutdown_lock:
self._ensure_open()
decision = self._claim_activation(
state,
operation=operation,
retry=retry,
)
if isinstance(decision, _PublishedTransition):
return decision.result
if isinstance(decision, _TransitionWait):
result = await self._wait_async(decision.future)
if decision.operation == operation:
return result
continue
claim = decision
break
implementation = claim.implementation
pending_stop = claim.pending_stop
started_at = time.monotonic()
self._emit(
state,
generation=claim.generation,
operation=operation,
outcome="started",
reason=reason,
started_at=started_at,
)
candidate = None
try:
if pending_stop is not None:
await self._async_callback(
adapter,
"stop",
state.spec,
pending_stop,
claim.generation,
)
pending_stop = None
self._publish_pending_stop_released(state)
if claim.pending_cleanup is not None:
try:
await self._async_callback(
adapter,
"cleanup",
state.spec,
claim.pending_cleanup,
claim.generation,
claim.pending_cleanup_error or RuntimeError("pending cleanup"),
)
except BaseException:
self._restore_pending_cleanup(
state,
claim.pending_cleanup,
claim.pending_cleanup_error,
)
raise
if implementation is None:
materialized = await self._async_callback(adapter, "materialize", state.spec)
implementation = self._canonical_implementation(state.spec, materialized)
candidate = await self._async_callback(
adapter,
"create",
state.spec,
implementation,
claim.generation,
previous,
)
if candidate is None:
raise CapabilityAdapterContractError(
f"adapter 没有返回 {state.spec.id} 的 candidate"
)
await self._async_callback(
adapter,
"start",
state.spec,
candidate,
claim.generation,
)
self._ensure_open()
self._publish_activation(
state,
claim,
implementation=implementation,
candidate=candidate,
)
self._emit(
state,
generation=claim.generation,
operation=operation,
outcome="succeeded",
reason=reason,
started_at=started_at,
)
return candidate
except BaseException as error:
cleanup_error = await self._cleanup_failed_async(
state,
adapter,
candidate,
claim.generation,
error,
)
final_error = cleanup_error or error
operation_error = self._fail_activation(
state,
claim,
operation=operation,
implementation=implementation,
pending_stop=pending_stop,
error=final_error,
lifecycle_error=error,
)
self._emit(
state,
generation=claim.generation,
operation=operation,
outcome="failed",
reason=reason,
started_at=started_at,
error=final_error,
)
raise operation_error from error
def _cleanup_failed_sync(
self,
state: _CapabilityState,
adapter: Any,
candidate: Any,
generation: int,
error: BaseException,
) -> Optional[BaseException]:
if candidate is None:
return None
try:
self._sync_callback(
adapter,
"cleanup",
state.spec,
candidate,
generation,
error,
)
return None
except BaseException as cleanup_error:
self._restore_pending_cleanup(state, candidate, cleanup_error)
return RuntimeError(f"{error}; cleanup failed: {cleanup_error}")
async def _cleanup_failed_async(
self,
state: _CapabilityState,
adapter: Any,
candidate: Any,
generation: int,
error: BaseException,
) -> Optional[BaseException]:
if candidate is None:
return None
try:
await self._async_callback(
adapter,
"cleanup",
state.spec,
candidate,
generation,
error,
)
return None
except BaseException as cleanup_error:
self._restore_pending_cleanup(state, candidate, cleanup_error)
return RuntimeError(f"{error}; cleanup failed: {cleanup_error}")
def reload(self, capability_id: str, *, reason: str) -> Any:
"""
撤销当前实例后,通过同步 adapter 完成新 generation 的资源切换。
"""
state = self._state(capability_id)
adapter = self._adapter(state, AdapterExecutionMode.SYNC)
while True:
with self._shutdown_lock:
self._ensure_open()
decision = self._claim_reload(state)
if isinstance(decision, _TransitionWait):
result = self._wait_sync(decision.future)
if decision.operation == "reload":
return result
continue
claim = decision
break
started_at = time.monotonic()
self._emit(
state,
generation=claim.generation,
operation="reload",
outcome="started",
reason=reason,
started_at=started_at,
)
candidate = None
previous_released = False
try:
self._sync_callback(
adapter,
"stop",
state.spec,
claim.previous,
claim.generation,
)
previous_released = True
candidate = self._sync_callback(
adapter,
"create",
state.spec,
claim.implementation,
claim.generation,
claim.previous,
)
if candidate is None:
raise CapabilityAdapterContractError(
f"adapter 没有返回 {state.spec.id} 的 candidate"
)
self._sync_callback(
adapter,
"start",
state.spec,
candidate,
claim.generation,
)
self._ensure_open()
self._publish_reload(state, claim, candidate)
self._emit(
state,
generation=claim.generation,
operation="reload",
outcome="succeeded",
reason=reason,
started_at=started_at,
)
return candidate
except BaseException as error:
cleanup_error = self._cleanup_failed_sync(
state,
adapter,
candidate,
claim.generation,
error,
)
final_error = cleanup_error or error
operation_error = self._fail_reload(
state,
claim,
previous_released=previous_released,
error=final_error,
lifecycle_error=error,
)
self._emit(
state,
generation=claim.generation,
operation="reload",
outcome="failed",
reason=reason,
started_at=started_at,
error=final_error,
)
raise operation_error from error
async def reload_async(self, capability_id: str, *, reason: str) -> Any:
"""
撤销当前实例后,通过异步 adapter 完成新 generation 的资源切换。
"""
state = self._state(capability_id)
adapter = self._adapter(state, AdapterExecutionMode.ASYNC)
while True:
with self._shutdown_lock:
self._ensure_open()
decision = self._claim_reload(state)
if isinstance(decision, _TransitionWait):
result = await self._wait_async(decision.future)
if decision.operation == "reload":
return result
continue
claim = decision
break
started_at = time.monotonic()
self._emit(
state,
generation=claim.generation,
operation="reload",
outcome="started",
reason=reason,
started_at=started_at,
)
candidate = None
previous_released = False
try:
await self._async_callback(
adapter,
"stop",
state.spec,
claim.previous,
claim.generation,
)
previous_released = True
candidate = await self._async_callback(
adapter,
"create",
state.spec,
claim.implementation,
claim.generation,
claim.previous,
)
if candidate is None:
raise CapabilityAdapterContractError(
f"adapter 没有返回 {state.spec.id} 的 candidate"
)
await self._async_callback(
adapter,
"start",
state.spec,
candidate,
claim.generation,
)
self._ensure_open()
self._publish_reload(state, claim, candidate)
self._emit(
state,
generation=claim.generation,
operation="reload",
outcome="succeeded",
reason=reason,
started_at=started_at,
)
return candidate
except BaseException as error:
cleanup_error = await self._cleanup_failed_async(
state,
adapter,
candidate,
claim.generation,
error,
)
final_error = cleanup_error or error
operation_error = self._fail_reload(
state,
claim,
previous_released=previous_released,
error=final_error,
lifecycle_error=error,
)
self._emit(
state,
generation=claim.generation,
operation="reload",
outcome="failed",
reason=reason,
started_at=started_at,
error=final_error,
)
raise operation_error from error
def stop(self, capability_id: str, *, reason: str) -> None:
"""
同步撤销并停止能力资源;物化实现保留供后续显式重启。
"""
self._stop_sync(capability_id, reason=reason, shutdown=False)
def _stop_sync(self, capability_id: str, *, reason: str, shutdown: bool) -> bool:
"""
停止同步能力并返回资源 owner 是否已经真实收敛。
"""
state = self._state(capability_id)
adapter = self._adapter(state, AdapterExecutionMode.SYNC)
while True:
decision = self._claim_stop(state)
if isinstance(decision, _SettledTransition):
return True
if isinstance(decision, _TransitionWait):
try:
self._wait_sync(decision.future)
except BaseException:
if not shutdown:
raise
if decision.operation == "stop":
return False
if decision.operation == "stop":
return True
continue
claim = decision
break
stop_owner = claim.stop_owner
pending_cleanup = claim.pending_cleanup
started_at = time.monotonic()
self._emit(
state,
generation=claim.generation,
operation="stop",
outcome="started",
reason=reason,
started_at=started_at,
)
try:
if stop_owner is not None:
self._sync_callback(
adapter,
"stop",
state.spec,
stop_owner,
claim.generation,
)
stop_owner = None
if pending_cleanup is not None:
self._sync_callback(
adapter,
"cleanup",
state.spec,
pending_cleanup,
claim.generation,
claim.pending_cleanup_error or RuntimeError("pending cleanup"),
)
pending_cleanup = None
self._publish_stop(state, claim)
self._emit(
state,
generation=claim.generation,
operation="stop",
outcome="succeeded",
reason=reason,
started_at=started_at,
)
return True
except BaseException as error:
operation_error = self._fail_stop(
state,
claim,
stop_owner=stop_owner,
pending_cleanup=pending_cleanup,
error=error,
)
self._emit(
state,
generation=claim.generation,
operation="stop",
outcome="failed",
reason=reason,
started_at=started_at,
error=error,
)
if not shutdown:
raise operation_error from error
return False
async def stop_async(self, capability_id: str, *, reason: str) -> None:
"""
异步撤销并停止能力资源。
"""
await self._stop_async(capability_id, reason=reason, shutdown=False)
async def _stop_async(
self,
capability_id: str,
*,
reason: str,
shutdown: bool,
) -> bool:
"""
停止异步能力并返回资源 owner 是否已经真实收敛。
"""
state = self._state(capability_id)
adapter = self._adapter(state, AdapterExecutionMode.ASYNC)
while True:
decision = self._claim_stop(state)
if isinstance(decision, _SettledTransition):
return True
if isinstance(decision, _TransitionWait):
try:
await self._wait_async(decision.future)
except BaseException:
if not shutdown:
raise
if decision.operation == "stop":
return False
if decision.operation == "stop":
return True
continue
claim = decision
break
stop_owner = claim.stop_owner
pending_cleanup = claim.pending_cleanup
started_at = time.monotonic()
self._emit(
state,
generation=claim.generation,
operation="stop",
outcome="started",
reason=reason,
started_at=started_at,
)
try:
if stop_owner is not None:
await self._async_callback(
adapter,
"stop",
state.spec,
stop_owner,
claim.generation,
)
stop_owner = None
if pending_cleanup is not None:
await self._async_callback(
adapter,
"cleanup",
state.spec,
pending_cleanup,
claim.generation,
claim.pending_cleanup_error or RuntimeError("pending cleanup"),
)
pending_cleanup = None
self._publish_stop(state, claim)
self._emit(
state,
generation=claim.generation,
operation="stop",
outcome="succeeded",
reason=reason,
started_at=started_at,
)
return True
except BaseException as error:
operation_error = self._fail_stop(
state,
claim,
stop_owner=stop_owner,
pending_cleanup=pending_cleanup,
error=error,
)
self._emit(
state,
generation=claim.generation,
operation="stop",
outcome="failed",
reason=reason,
started_at=started_at,
error=error,
)
if not shutdown:
raise operation_error from error
return False
def shutdown(self, *, reason: str) -> bool:
"""
不可逆关闭同步 Runtime,并返回全部 owner 是否收敛。
"""
async_kinds = {
kind
for kind, adapter in self._adapters.items()
if getattr(adapter, "execution_mode", None) is AdapterExecutionMode.ASYNC
}
if async_kinds:
raise CapabilityAdapterModeError(
f"同步 shutdown 不能处理异步 adapter:{sorted(async_kinds)}"
)
with self._shutdown_lock:
self._shutdown = True
converged = True
for spec in self._registry.list_specs():
if not self._stop_sync(spec.id, reason=reason, shutdown=True):
converged = False
return converged
async def shutdown_async(self, *, reason: str) -> bool:
"""
不可逆关闭混合 Runtime,并返回全部 owner 是否收敛。
"""
with self._shutdown_lock:
self._shutdown = True
converged = True
for spec in self._registry.list_specs():
adapter = self._adapters[spec.kind]
if getattr(adapter, "execution_mode", None) is AdapterExecutionMode.ASYNC:
stopped = await self._stop_async(
spec.id,
reason=reason,
shutdown=True,
)
else:
stopped = await asyncio.to_thread(
self._stop_sync,
spec.id,
reason=reason,
shutdown=True,
)
if not stopped:
converged = False
return converged