mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-24 08:55:59 +00:00
feat: asyncio-native Actor framework with supervision, middleware, and pluggable mailbox
Lightweight actor library built on asyncio primitives (~800 lines): - Actor base class with lifecycle hooks (on_started/on_stopped/on_restart) - ActorRef with tell (fire-and-forget) and ask (request-response) - Supervision: OneForOne/AllForOne strategies with restart limits - Middleware pipeline for cross-cutting concerns - Pluggable Mailbox interface (MemoryMailbox default, RedisMailbox optional) - ReplyRegistry + ReplyChannel: ask() works across any mailbox backend - System-level thread pool for blocking I/O (run_in_executor) - Dead letter handling, poison message quarantine, parallel shutdown - 22 tests + benchmark suite
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
"""Actor base class and per-actor context."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
||||
|
||||
from .supervision import OneForOneStrategy, SupervisorStrategy
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .ref import ActorRef
|
||||
|
||||
# Message type variable — use Actor[MyMsg] for typed actors
|
||||
M = TypeVar("M")
|
||||
R = TypeVar("R")
|
||||
|
||||
|
||||
class ActorContext:
|
||||
"""Per-actor runtime context, injected before ``on_started``.
|
||||
|
||||
Provides access to the actor's identity, parent, children,
|
||||
and the ability to spawn child actors.
|
||||
"""
|
||||
|
||||
__slots__ = ("_cell",)
|
||||
|
||||
def __init__(self, cell: Any) -> None:
|
||||
self._cell = cell
|
||||
|
||||
@property
|
||||
def self_ref(self) -> ActorRef:
|
||||
return self._cell.ref
|
||||
|
||||
@property
|
||||
def parent(self) -> ActorRef | None:
|
||||
p = self._cell.parent
|
||||
return p.ref if p is not None else None
|
||||
|
||||
@property
|
||||
def children(self) -> dict[str, ActorRef]:
|
||||
return {name: c.ref for name, c in self._cell.children.items()}
|
||||
|
||||
@property
|
||||
def system(self) -> Any:
|
||||
return self._cell.system
|
||||
|
||||
async def spawn(
|
||||
self,
|
||||
actor_cls: type[Actor],
|
||||
name: str,
|
||||
*,
|
||||
mailbox_size: int = 256,
|
||||
middlewares: list | None = None,
|
||||
) -> ActorRef:
|
||||
"""Spawn a child actor supervised by this actor."""
|
||||
return await self._cell.spawn_child(actor_cls, name, mailbox_size=mailbox_size, middlewares=middlewares)
|
||||
|
||||
async def run_in_executor(self, fn: Callable[..., Any], *args: Any) -> Any:
|
||||
"""Run a blocking function in the system's thread pool.
|
||||
|
||||
Usage::
|
||||
|
||||
result = await self.context.run_in_executor(requests.get, url)
|
||||
"""
|
||||
import asyncio
|
||||
executor = self._cell.system._executor
|
||||
return await asyncio.get_running_loop().run_in_executor(executor, fn, *args)
|
||||
|
||||
|
||||
class Actor(Generic[M]):
|
||||
"""Base class for all actors.
|
||||
|
||||
Type parameter ``M`` constrains the message type::
|
||||
|
||||
class Greeter(Actor[str]):
|
||||
async def on_receive(self, message: str) -> str:
|
||||
return f"Hello, {message}!"
|
||||
|
||||
class Calculator(Actor[int | tuple[str, int, int]]):
|
||||
async def on_receive(self, message: int | tuple[str, int, int]) -> int:
|
||||
...
|
||||
|
||||
Unparameterized ``Actor`` accepts ``Any`` (backward-compatible).
|
||||
"""
|
||||
|
||||
context: ActorContext
|
||||
|
||||
async def on_receive(self, message: M) -> Any:
|
||||
"""Handle an incoming message.
|
||||
|
||||
Return value is sent back as reply for ``ask`` calls.
|
||||
For ``tell`` calls, the return value is discarded.
|
||||
"""
|
||||
|
||||
async def on_started(self) -> None:
|
||||
"""Called after creation, before receiving messages."""
|
||||
|
||||
async def on_stopped(self) -> None:
|
||||
"""Called on graceful shutdown. Release resources here."""
|
||||
|
||||
async def on_restart(self, error: Exception) -> None:
|
||||
"""Called on the *new* instance before resuming after a crash."""
|
||||
|
||||
def supervisor_strategy(self) -> SupervisorStrategy:
|
||||
"""Override to customize how this actor supervises its children.
|
||||
|
||||
Default: OneForOne, up to 3 restarts per 60 seconds, always restart.
|
||||
"""
|
||||
return OneForOneStrategy()
|
||||
Reference in New Issue
Block a user