181 lines
5.7 KiB
Python
181 lines
5.7 KiB
Python
# Copyright 2026 zhaoxi826
|
||
#
|
||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
# you may not use this file except in compliance with the License.
|
||
# You may obtain a copy of the License at
|
||
#
|
||
# http://www.apache.org/licenses/LICENSE-2.0
|
||
#
|
||
# Unless required by applicable law or agreed to in writing, software
|
||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
# See the License for the specific language governing permissions and
|
||
# limitations under the License.
|
||
|
||
"""进程内 actor 运行时(位置透明的组件寻址层)。
|
||
|
||
设计目标:**一处编写,处处运行**。业务代码永远通过 ``get_actor(name)`` 拿到一个
|
||
``ActorHandle``,再 ``await handle.method(args)`` 调用,**不关心对端在哪**。
|
||
|
||
今天所有核心组件(PostgresDatabase / GlobalStateMachine / ...)都是本进程内的
|
||
普通异步对象,``get_actor`` 返回 ``LocalActorHandle``,直接转发到本地实例。
|
||
|
||
未来若要把某一层(如跑 vLLM 的重型 worker)拆到独立进程/机器上,只需新增一个
|
||
``RemoteActorHandle``(gRPC / 子进程 / 消息队列后端)并在注册时选择它——业务侧的
|
||
调用代码一行都不用改。这层接缝就是当初 Ray ``.remote()`` 提供的价值,这里用零依赖
|
||
的方式把它保留下来。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import asyncio
|
||
from typing import Any, Awaitable, Dict, Optional, Set
|
||
|
||
_registry: Dict[str, Any] = {}
|
||
_handle_cache: Dict[str, "ActorHandle"] = {}
|
||
|
||
# 已 spawn 的后台任务:持强引用,防止事件循环只持弱引用导致任务被 GC 掉
|
||
_background_tasks: Set["asyncio.Task[Any]"] = set()
|
||
|
||
|
||
class _BoundMethod:
|
||
"""包装目标对象的一个方法,使调用统一返回 awaitable。
|
||
|
||
- 目标方法是 ``async def`` → 直接返回其协程
|
||
- 目标方法是普通函数 → 把返回值包成一个已完成的 awaitable
|
||
这样调用方永远可以 ``await handle.method(...)``,无需关心同步/异步、本地/远程。
|
||
"""
|
||
|
||
__slots__ = ("_fn",)
|
||
|
||
def __init__(self, fn: Any) -> None:
|
||
self._fn = fn
|
||
|
||
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
||
result = self._fn(*args, **kwargs)
|
||
if asyncio.iscoroutine(result):
|
||
return result
|
||
|
||
async def _wrap() -> Any:
|
||
return result
|
||
|
||
return _wrap()
|
||
|
||
|
||
class ActorHandle:
|
||
"""actor 句柄基类:定义位置透明的调用接口。
|
||
|
||
未来的 ``RemoteActorHandle`` 继承本类,改写方法解析为跨进程 RPC 即可。
|
||
"""
|
||
|
||
|
||
class LocalActorHandle(ActorHandle):
|
||
"""本进程句柄:方法调用直接转发到已注册的实例。"""
|
||
|
||
__slots__ = ("_impl",)
|
||
|
||
def __init__(self, impl: Any) -> None:
|
||
object.__setattr__(self, "_impl", impl)
|
||
|
||
def __getattr__(self, name: str) -> Any:
|
||
attr = getattr(object.__getattribute__(self, "_impl"), name)
|
||
if callable(attr):
|
||
return _BoundMethod(attr)
|
||
return attr
|
||
|
||
|
||
def register_actor(name: str, instance: Any) -> None:
|
||
"""注册一个 actor 实例。重复注册会覆盖(支持插件热重载)。"""
|
||
_registry[name] = instance
|
||
_handle_cache.pop(name, None)
|
||
|
||
|
||
def unregister_actor(name: str) -> None:
|
||
"""注销一个 actor(插件卸载用);不存在时静默。"""
|
||
_registry.pop(name, None)
|
||
_handle_cache.pop(name, None)
|
||
|
||
|
||
def get_actor(name: str) -> ActorHandle:
|
||
"""按名字取 actor 句柄;未注册时抛 KeyError。
|
||
|
||
动态名(如 ``f"org_{plugin}"``)也走这个统一入口。
|
||
"""
|
||
if name not in _registry:
|
||
raise KeyError(f"actor {name!r} 未注册")
|
||
handle = _handle_cache.get(name)
|
||
if handle is None:
|
||
handle = LocalActorHandle(_registry[name])
|
||
_handle_cache[name] = handle
|
||
return handle
|
||
|
||
|
||
def try_get_actor(name: str) -> Optional[ActorHandle]:
|
||
"""按名字取 actor 句柄;未注册时返回 None(可选依赖用)。"""
|
||
try:
|
||
return get_actor(name)
|
||
except KeyError:
|
||
return None
|
||
|
||
|
||
def clear_actors() -> None:
|
||
"""清空注册表(测试用)。"""
|
||
_registry.clear()
|
||
_handle_cache.clear()
|
||
_background_tasks.clear()
|
||
|
||
|
||
def spawn_background(coro: "Awaitable[Any]") -> "asyncio.Task[Any]":
|
||
"""在当前事件循环中后台调度一个协程,并维持强引用防止被 GC。
|
||
|
||
等价于旧 Ray `.remote()` 的 fire-and-forget 语义:
|
||
- 协程被调度执行;
|
||
- 异常不会静默消失,而是记录到日志;
|
||
- 调用方无需 await,也无需保存返回值。
|
||
"""
|
||
from kilostar.utils.logger import get_logger
|
||
|
||
task: asyncio.Task[Any] = asyncio.ensure_future(coro)
|
||
_background_tasks.add(task)
|
||
|
||
def _on_done(t: "asyncio.Task[Any]") -> None:
|
||
_background_tasks.discard(t)
|
||
if not t.cancelled() and t.exception() is not None:
|
||
get_logger("actor").exception(
|
||
f"后台任务 {t.get_name()!r} 异常退出", exc_info=t.exception()
|
||
)
|
||
|
||
task.add_done_callback(_on_done)
|
||
return task
|
||
|
||
|
||
# ─── 类型化 getter(核心单例,语义糖)──────────────────────────
|
||
|
||
|
||
def get_postgres() -> ActorHandle:
|
||
return get_actor("postgres_database")
|
||
|
||
|
||
def get_gsm() -> ActorHandle:
|
||
return get_actor("global_state_machine")
|
||
|
||
|
||
def get_gwm() -> ActorHandle:
|
||
return get_actor("global_workflow_manager")
|
||
|
||
|
||
def get_regulatory() -> ActorHandle:
|
||
return get_actor("regulatory_node")
|
||
|
||
|
||
def get_consciousness() -> ActorHandle:
|
||
return get_actor("consciousness_node")
|
||
|
||
|
||
def get_worker_cluster() -> ActorHandle:
|
||
return get_actor("worker_cluster")
|
||
|
||
|
||
def get_plugin_manager() -> ActorHandle:
|
||
return get_actor("global_plugin_manager")
|