Files
deer-flow/backend/packages/harness/deerflow/actor/middleware.py
T
greatmengqi 3e17417122 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
2026-03-30 23:50:54 +08:00

80 lines
2.5 KiB
Python

"""Middleware pipeline — cross-cutting concerns for actors.
Inspired by Proto.Actor's sender/receiver middleware model.
Middleware intercepts messages before/after the actor processes them.
Usage::
class LoggingMiddleware(Middleware):
async def on_receive(self, ctx, message, next_fn):
logger.info("Received: %s", message)
result = await next_fn(ctx, message)
logger.info("Replied: %s", result)
return result
system = ActorSystem("app")
ref = await system.spawn(MyActor, "a", middlewares=[LoggingMiddleware()])
"""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from typing import Any
class ActorMailboxContext:
"""Context passed to middleware on each message."""
__slots__ = ("actor_ref", "sender", "message_type")
def __init__(self, actor_ref: Any, sender: Any, message_type: str) -> None:
self.actor_ref = actor_ref
self.sender = sender
self.message_type = message_type # "tell" or "ask"
# The inner handler signature: (ctx, message) -> result
NextFn = Callable[[ActorMailboxContext, Any], Awaitable[Any]]
class Middleware:
"""Base class for actor middleware.
Override ``on_receive`` to intercept inbound messages.
Must call ``await next_fn(ctx, message)`` to continue the chain.
"""
async def on_receive(self, ctx: ActorMailboxContext, message: Any, next_fn: NextFn) -> Any:
"""Intercept a message. Call next_fn to continue the chain."""
return await next_fn(ctx, message)
async def on_started(self, actor_ref: Any) -> None:
"""Called when the actor starts."""
async def on_stopped(self, actor_ref: Any) -> None:
"""Called when the actor stops."""
async def on_restart(self, actor_ref: Any, error: Exception) -> None:
"""Called when the actor restarts after a crash.
Override to reset per-actor-instance state (caches, counters, etc.)
that should not bleed across restarts.
"""
def build_middleware_chain(middlewares: list[Middleware], handler: NextFn) -> NextFn:
"""Build a nested middleware chain ending with *handler*.
Execution order: first middleware in list wraps outermost.
``[A, B, C]`` → ``A(B(C(handler)))``
"""
chain = handler
for mw in reversed(middlewares):
outer = chain
async def _wrap(ctx: ActorMailboxContext, msg: Any, _mw: Middleware = mw, _next: NextFn = outer) -> Any:
return await _mw.on_receive(ctx, msg, _next)
chain = _wrap
return chain