mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-30 03:48:03 +08:00
993 lines
38 KiB
Python
993 lines
38 KiB
Python
"""
|
||
进程内事件注册与分发,管理处理器绑定、并发执行及关停收敛。
|
||
"""
|
||
|
||
import asyncio
|
||
import concurrent.futures
|
||
import random
|
||
import threading
|
||
import traceback
|
||
import uuid
|
||
from contextvars import ContextVar
|
||
from dataclasses import dataclass
|
||
from queue import Empty, PriorityQueue
|
||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union, cast
|
||
|
||
from app.foundation.singleton import Singleton
|
||
from app.runtime.correlation import get_correlation_id
|
||
from app.runtime.event.binding import (
|
||
EventBindingResolver,
|
||
EventHandlerBinding,
|
||
HandlerInstanceResolver,
|
||
)
|
||
from app.runtime.event.contracts import normalize_event_type, validate_event_payload
|
||
from app.runtime.event.dispatch import EventDispatcher
|
||
from app.runtime.event.errors import EventErrorNotifier, EventErrorPolicy
|
||
from app.runtime.event.registry import EventRegistry
|
||
from app.runtime.log import logger
|
||
from app.runtime.loop import main_loop_registry
|
||
from app.runtime.observability import record_metric
|
||
from app.runtime.rate import ExponentialBackoffRateLimiter
|
||
from app.runtime.thread import ThreadHelper
|
||
from app.schemas.event import BaseEventData
|
||
from app.schemas.types import ChainEventType, EventType
|
||
|
||
if TYPE_CHECKING:
|
||
from app.runtime.event.snapshot import EventPayloadSnapshot
|
||
|
||
# 事件的默认优先级
|
||
DEFAULT_EVENT_PRIORITY = 10
|
||
# 最小事件消费者线程数
|
||
MIN_EVENT_CONSUMER_THREADS = 1
|
||
# 事件队列空闲时的初始超时时间(秒)
|
||
INITIAL_EVENT_QUEUE_IDLE_TIMEOUT_SECONDS = 1
|
||
# 事件队列空闲时的最大超时时间(秒)
|
||
MAX_EVENT_QUEUE_IDLE_TIMEOUT_SECONDS = 5
|
||
_EVENT_STOP_SENTINEL = object()
|
||
_CURRENT_EVENT_HANDLER_OWNER: ContextVar[object | None] = ContextVar(
|
||
"current_event_handler_owner",
|
||
default=None,
|
||
)
|
||
_EventHandler = TypeVar("_EventHandler", bound=Callable[..., Any])
|
||
_EventKind = Union[EventType, ChainEventType]
|
||
_EventRegistration = Union[
|
||
_EventKind,
|
||
List[_EventKind],
|
||
Type[EventType],
|
||
Type[ChainEventType],
|
||
]
|
||
|
||
|
||
@dataclass(slots=True)
|
||
class _EventAsyncHandle:
|
||
"""
|
||
记录异步广播的取消代理和真实完成信号。
|
||
|
||
:param handle: 提供给广播调用方的取消代理
|
||
:param completion: 真实异步执行完成后才结束的信号,用于关停等待
|
||
"""
|
||
|
||
handle: concurrent.futures.Future[Any]
|
||
completion: concurrent.futures.Future[Any]
|
||
|
||
|
||
class Event:
|
||
"""
|
||
事件类,封装事件的基本信息。
|
||
"""
|
||
|
||
def __init__(self, event_type: Union[EventType, ChainEventType, str],
|
||
event_data: Optional[Union[Dict[str, Any], BaseEventData]] = None,
|
||
priority: Optional[int] = DEFAULT_EVENT_PRIORITY,
|
||
correlation_id: Optional[str] = None):
|
||
"""
|
||
归一化事件类型并创建携带关联标识的事件对象。
|
||
|
||
:param event_type: 事件的类型,支持 EventType 或 ChainEventType
|
||
:param event_data: 可选,事件携带的数据,默认为空字典
|
||
:param priority: 可选,事件的优先级,默认为 10
|
||
:param correlation_id: 生产事件时固化的请求关联 ID
|
||
"""
|
||
event_type = normalize_event_type(event_type)
|
||
payload_problems = validate_event_payload(event_type, event_data)
|
||
if payload_problems:
|
||
logger.warning(
|
||
"事件 %s payload 与登记契约不一致:%s;当前保留旧 payload 继续投递",
|
||
getattr(event_type, "value", event_type),
|
||
"; ".join(payload_problems),
|
||
)
|
||
# 事件ID
|
||
self.event_id = str(uuid.uuid4())
|
||
# 事件类型
|
||
self.event_type = event_type
|
||
# 事件数据
|
||
self.event_data = event_data or {}
|
||
# 事件优先级
|
||
self.priority = priority if priority is not None else DEFAULT_EVENT_PRIORITY
|
||
self.correlation_id = correlation_id or get_correlation_id()
|
||
|
||
def __repr__(self) -> str:
|
||
"""
|
||
重写 __repr__ 方法,用于返回事件的详细信息,包括事件类型、事件ID和优先级。
|
||
"""
|
||
event_kind = Event.get_event_kind(self.event_type)
|
||
event_name = getattr(self.event_type, "value", self.event_type)
|
||
return f"<{event_kind}: {event_name}, ID: {self.event_id}, Priority: {self.priority}>"
|
||
|
||
def snapshot(self) -> "EventPayloadSnapshot":
|
||
"""
|
||
返回插件可安全读取的类型化 payload 快照,原始 event_data 保持不变。
|
||
"""
|
||
from app.runtime.event.snapshot import snapshot_event_data
|
||
|
||
return snapshot_event_data(self.event_type, self.event_data)
|
||
|
||
def __lt__(self, other: "Event") -> bool:
|
||
"""
|
||
定义事件对象的比较规则,基于优先级比较
|
||
优先级小的事件会被认为“更小”,优先级高的事件将被认为“更大”
|
||
"""
|
||
return self.priority < other.priority
|
||
|
||
@staticmethod
|
||
def get_event_kind(event_type: Union[EventType, ChainEventType, str]) -> str:
|
||
"""
|
||
根据事件类型判断事件是广播事件还是链式事件。
|
||
|
||
:param event_type: 事件类型,支持 EventType 或 ChainEventType
|
||
:return: 返回 Broadcast Event 或 Chain Event
|
||
"""
|
||
return "Broadcast Event" if isinstance(event_type, EventType) else "Chain Event"
|
||
|
||
|
||
class EventManager(metaclass=Singleton):
|
||
"""
|
||
EventManager 负责管理和调度广播事件和链式事件,包括订阅、发送和处理事件。
|
||
"""
|
||
|
||
def __init__(self) -> None:
|
||
"""
|
||
初始化订阅表、处理队列、解析器和消费者状态。
|
||
"""
|
||
# 动态线程池,用于消费事件
|
||
self.__executor = ThreadHelper()
|
||
# 用于保存启动的事件消费者线程
|
||
self.__consumer_threads: List[threading.Thread] = []
|
||
# 优先级队列
|
||
self.__event_queue: PriorityQueue[Any] = PriorityQueue()
|
||
# 广播事件的订阅者
|
||
self.__broadcast_subscribers: Dict[EventType, Dict[str, Callable[..., Any]]] = {}
|
||
# 链式事件的订阅者
|
||
self.__chain_subscribers: Dict[ChainEventType, Dict[str, tuple[int, Callable[..., Any]]]] = {}
|
||
# 禁用的事件处理器集合
|
||
self.__disabled_handlers: Set[str] = set()
|
||
# 禁用的事件处理器类集合
|
||
self.__disabled_classes: Set[str] = set()
|
||
# 线程锁
|
||
self.__lock = threading.Lock()
|
||
# 退出事件
|
||
self.__event = threading.Event()
|
||
# 广播处理器由事件总线统一持有,确保插件卸载前可以建立结算屏障。
|
||
self.__lifecycle_lock = threading.RLock()
|
||
self.__lifecycle_state = "new"
|
||
self.__sync_handles: Dict[object, concurrent.futures.Future[Any]] = {}
|
||
self.__async_handles: Dict[object, _EventAsyncHandle] = {}
|
||
# 由上层管理器注册的处理器实例解析器
|
||
self.__handler_instance_resolvers: Dict[str, HandlerInstanceResolver] = {}
|
||
# 由启动组合层注入的错误通知回调
|
||
self.__error_notifier: Optional[EventErrorNotifier] = None
|
||
self.__registry = EventRegistry(
|
||
lock=self.__lock,
|
||
broadcast_subscribers=lambda: self.__broadcast_subscribers,
|
||
chain_subscribers=lambda: self.__chain_subscribers,
|
||
disabled_handlers=lambda: self.__disabled_handlers,
|
||
disabled_classes=lambda: self.__disabled_classes,
|
||
)
|
||
self.__binding_resolver = EventBindingResolver(
|
||
lock=self.__lock,
|
||
resolvers=lambda: self.__handler_instance_resolvers,
|
||
)
|
||
self.__error_policy = EventErrorPolicy(
|
||
notifier=lambda: self.__error_notifier,
|
||
emit_system_error=lambda payload: self.send_event(
|
||
EventType.SystemError,
|
||
payload,
|
||
),
|
||
)
|
||
self.__dispatcher = EventDispatcher(
|
||
registry=self.__registry,
|
||
binding_resolver=self.__binding_resolver,
|
||
event_factory=Event,
|
||
error_handler=lambda **kwargs: self.__handle_event_error(**kwargs),
|
||
async_handle_sink=self.__register_async_handle,
|
||
sync_handle_sink=self.__register_sync_handle,
|
||
)
|
||
|
||
def register_handler_instance_resolver(
|
||
self,
|
||
name: str,
|
||
resolver: HandlerInstanceResolver,
|
||
) -> None:
|
||
"""
|
||
注册上层运行时的事件处理器实例解析器。
|
||
|
||
同名解析器会被替换,避免测试重建单例或热重载后保留旧实例引用。
|
||
"""
|
||
self.__binding_resolver.register(name, resolver)
|
||
|
||
def unregister_handler_instance_resolver(self, name: str) -> None:
|
||
"""
|
||
撤销命名实例解析器;重复撤销不影响其它 owner。
|
||
"""
|
||
self.__binding_resolver.unregister(name)
|
||
|
||
def unresolved_handler_bindings(self) -> tuple[str, ...]:
|
||
"""
|
||
返回未命中显式 resolver 的类处理器诊断清单。
|
||
"""
|
||
return self.__binding_resolver.unresolved_handlers()
|
||
|
||
def set_error_notifier(self, notifier: Optional[EventErrorNotifier]) -> None:
|
||
"""
|
||
设置事件处理异常的外部通知回调。
|
||
"""
|
||
with self.__lock:
|
||
self.__error_notifier = notifier
|
||
|
||
def reset_error_notifier(self) -> None:
|
||
"""
|
||
撤销当前 lifespan 的事件错误通知回调。
|
||
"""
|
||
self.set_error_notifier(None)
|
||
|
||
def start(self) -> None:
|
||
"""
|
||
开始广播事件处理线程。
|
||
"""
|
||
with self.__lifecycle_lock:
|
||
if self.__lifecycle_state == "running":
|
||
return
|
||
if self.__lifecycle_state == "stopping":
|
||
logger.warning("事件处理仍在停止,忽略重复启动")
|
||
return
|
||
if self.__lifecycle_state == "sealed":
|
||
logger.warning("事件处理已封口,忽略重复启动")
|
||
return
|
||
self.__lifecycle_state = "running"
|
||
self.__event.set()
|
||
self.__consumer_threads = []
|
||
for _ in range(MIN_EVENT_CONSUMER_THREADS):
|
||
thread = threading.Thread(target=self.__broadcast_consumer_loop, daemon=True)
|
||
thread.start()
|
||
self.__consumer_threads.append(thread)
|
||
|
||
def stop(self) -> None:
|
||
"""
|
||
兼容同步关闭入口,等待同步处理器并请求取消异步处理器。
|
||
|
||
调用线程无法安全等待主事件循环完成异步清理;需要完整异步收口时应使用
|
||
stop_async()。
|
||
"""
|
||
logger.info("正在停止事件处理...")
|
||
consumer_threads = self.__begin_stop()
|
||
try:
|
||
self.__join_consumer_threads(consumer_threads)
|
||
self.__discard_stop_sentinels()
|
||
current_owner = _CURRENT_EVENT_HANDLER_OWNER.get()
|
||
self.__cancel_async_handles(exclude_owner=current_owner)
|
||
self.__wait_sync_handles(exclude_owner=current_owner)
|
||
logger.info("事件处理停止完成")
|
||
except Exception as e:
|
||
logger.error(f"停止事件处理线程出错:{str(e)} - {traceback.format_exc()}")
|
||
finally:
|
||
with self.__lifecycle_lock:
|
||
self.__lifecycle_state = "stopped"
|
||
self.__consumer_threads = []
|
||
|
||
async def stop_async(self) -> None:
|
||
"""
|
||
停止广播消费者,等待同步处理器并取消收口异步处理器。
|
||
"""
|
||
logger.info("正在停止事件处理...")
|
||
consumer_threads = self.__begin_stop()
|
||
try:
|
||
if consumer_threads:
|
||
await asyncio.to_thread(
|
||
self.__join_consumer_threads,
|
||
consumer_threads,
|
||
)
|
||
self.__discard_stop_sentinels()
|
||
current_owner = _CURRENT_EVENT_HANDLER_OWNER.get()
|
||
with self.__lifecycle_lock:
|
||
async_handles = tuple(
|
||
handle
|
||
for owner, handle in self.__async_handles.items()
|
||
if owner is not current_owner
|
||
)
|
||
sync_handles = tuple(
|
||
handle
|
||
for owner, handle in self.__sync_handles.items()
|
||
if owner is not current_owner
|
||
)
|
||
for handle in async_handles:
|
||
handle.handle.cancel()
|
||
if async_handles or sync_handles:
|
||
await asyncio.gather(
|
||
*(
|
||
asyncio.shield(asyncio.wrap_future(handle.completion))
|
||
for handle in async_handles
|
||
),
|
||
*(
|
||
asyncio.shield(asyncio.wrap_future(handle))
|
||
for handle in sync_handles
|
||
),
|
||
return_exceptions=True,
|
||
)
|
||
logger.info("事件处理停止完成")
|
||
except Exception as e:
|
||
logger.error(f"停止事件处理线程出错:{str(e)} - {traceback.format_exc()}")
|
||
raise
|
||
with self.__lifecycle_lock:
|
||
self.__lifecycle_state = "stopped"
|
||
self.__consumer_threads = []
|
||
|
||
def __begin_stop(self) -> tuple[threading.Thread, ...]:
|
||
"""
|
||
关闭提交入口并唤醒消费者线程。
|
||
"""
|
||
with self.__lifecycle_lock:
|
||
self.__lifecycle_state = "stopping"
|
||
self.__event.clear()
|
||
consumer_threads = tuple(self.__consumer_threads)
|
||
if consumer_threads:
|
||
self.__event_queue.put((float("-inf"), _EVENT_STOP_SENTINEL))
|
||
return consumer_threads
|
||
|
||
@staticmethod
|
||
def __join_consumer_threads(consumer_threads: tuple[threading.Thread, ...]) -> None:
|
||
"""
|
||
在线程池或同步兼容入口中等待事件消费者退出。
|
||
"""
|
||
for consumer_thread in consumer_threads:
|
||
consumer_thread.join()
|
||
|
||
def __discard_stop_sentinels(self) -> None:
|
||
"""
|
||
清理仅用于唤醒消费者的标记,保留尚未消费的业务事件。
|
||
"""
|
||
pending = []
|
||
while True:
|
||
try:
|
||
item = self.__event_queue.get_nowait()
|
||
except Empty:
|
||
break
|
||
if item[1] is not _EVENT_STOP_SENTINEL:
|
||
pending.append(item)
|
||
self.__event_queue.task_done()
|
||
for item in pending:
|
||
self.__event_queue.put(item)
|
||
|
||
def __cancel_async_handles(
|
||
self,
|
||
*,
|
||
exclude_owner: object | None = None,
|
||
) -> None:
|
||
"""
|
||
请求取消异步处理器,并避免 handler 关闭事件总线时取消自身。
|
||
"""
|
||
with self.__lifecycle_lock:
|
||
handles = tuple(
|
||
handle
|
||
for owner, handle in self.__async_handles.items()
|
||
if owner is not exclude_owner
|
||
)
|
||
for handle in handles:
|
||
handle.handle.cancel()
|
||
|
||
def __wait_sync_handles(
|
||
self,
|
||
*,
|
||
exclude_owner: object | None = None,
|
||
) -> None:
|
||
"""
|
||
等待同步处理器完成,并避免 handler 关闭事件总线时等待自身。
|
||
"""
|
||
with self.__lifecycle_lock:
|
||
handles = tuple(
|
||
handle
|
||
for owner, handle in self.__sync_handles.items()
|
||
if owner is not exclude_owner
|
||
)
|
||
if handles:
|
||
concurrent.futures.wait(handles)
|
||
|
||
def __register_sync_handle(
|
||
self,
|
||
callback: Callable[..., Any],
|
||
args: tuple[Any, ...],
|
||
) -> bool:
|
||
"""
|
||
在同一生命周期临界区提交并登记同步广播处理器。
|
||
"""
|
||
with self.__lifecycle_lock:
|
||
if self.__lifecycle_state != "running":
|
||
logger.warning(
|
||
"事件处理处于 %s 状态,拒绝同步广播处理器",
|
||
self.__lifecycle_state,
|
||
)
|
||
return False
|
||
try:
|
||
owner = object()
|
||
completion: concurrent.futures.Future[Any] = concurrent.futures.Future()
|
||
self.__sync_handles[owner] = completion
|
||
|
||
def _tracked_sync() -> Any:
|
||
"""
|
||
在同步 handler 调用栈中发布当前事件 owner。
|
||
"""
|
||
context_token = _CURRENT_EVENT_HANDLER_OWNER.set(owner)
|
||
try:
|
||
return callback(*args)
|
||
finally:
|
||
_CURRENT_EVENT_HANDLER_OWNER.reset(context_token)
|
||
|
||
handle = self.__executor.submit(_tracked_sync)
|
||
except RuntimeError:
|
||
self.__sync_handles.pop(owner, None)
|
||
logger.warning("同步事件处理器无法投递,线程池已停止")
|
||
return False
|
||
|
||
def _complete_sync_handle(
|
||
completed: concurrent.futures.Future[Any],
|
||
) -> None:
|
||
"""
|
||
真实线程结束后先清理 owner,再向结算句柄发布结果,避免关闭等待者先被唤醒。
|
||
|
||
:param completed: 已经完成或取消的原始线程任务句柄
|
||
"""
|
||
self.__remove_sync_handle(owner)
|
||
if completed.cancelled():
|
||
completion.cancel()
|
||
else:
|
||
error = completed.exception()
|
||
if error is not None:
|
||
completion.set_exception(error)
|
||
else:
|
||
completion.set_result(completed.result())
|
||
|
||
handle.add_done_callback(_complete_sync_handle)
|
||
return True
|
||
|
||
def __remove_sync_handle(self, owner: object) -> None:
|
||
"""
|
||
同步处理器完成后移除其 owner 句柄。
|
||
"""
|
||
with self.__lifecycle_lock:
|
||
self.__sync_handles.pop(owner, None)
|
||
|
||
def __register_async_handle(
|
||
self,
|
||
coroutine: Any,
|
||
) -> bool:
|
||
"""
|
||
在同一生命周期临界区提交并登记异步广播处理器。
|
||
"""
|
||
with self.__lifecycle_lock:
|
||
if self.__lifecycle_state != "running":
|
||
coroutine.close()
|
||
logger.warning(
|
||
"事件处理处于 %s 状态,拒绝异步广播处理器",
|
||
self.__lifecycle_state,
|
||
)
|
||
return False
|
||
completion: concurrent.futures.Future[Any] = concurrent.futures.Future()
|
||
started = threading.Event()
|
||
owner = object()
|
||
|
||
async def _tracked() -> None:
|
||
"""
|
||
在所属上下文中执行处理器,真实结束后先清理 owner,再发布完成信号。
|
||
"""
|
||
started.set()
|
||
context_token = _CURRENT_EVENT_HANDLER_OWNER.set(owner)
|
||
try:
|
||
result = await coroutine
|
||
except asyncio.CancelledError:
|
||
self.__remove_async_handle(owner)
|
||
if not completion.done():
|
||
completion.cancel()
|
||
except Exception as err:
|
||
self.__remove_async_handle(owner)
|
||
if not completion.done():
|
||
completion.set_exception(err)
|
||
else:
|
||
self.__remove_async_handle(owner)
|
||
if not completion.done():
|
||
completion.set_result(result)
|
||
finally:
|
||
_CURRENT_EVENT_HANDLER_OWNER.reset(context_token)
|
||
|
||
tracked = _tracked()
|
||
try:
|
||
handle = asyncio.run_coroutine_threadsafe(
|
||
tracked,
|
||
main_loop_registry.require(),
|
||
)
|
||
except RuntimeError:
|
||
tracked.close()
|
||
coroutine.close()
|
||
logger.warning("异步事件处理器无法投递,事件循环已停止")
|
||
return False
|
||
self.__async_handles[owner] = _EventAsyncHandle(
|
||
handle=handle,
|
||
completion=completion,
|
||
)
|
||
|
||
def _complete_unstarted_submission(
|
||
submitted: concurrent.futures.Future[Any],
|
||
) -> None:
|
||
"""
|
||
关闭尚未启动就被取消的协程,先移除 owner,再发布取消完成信号。
|
||
|
||
:param submitted: 跨线程提交到所属事件循环的代理句柄
|
||
"""
|
||
if submitted.cancelled() and not started.is_set():
|
||
coroutine.close()
|
||
self.__remove_async_handle(owner)
|
||
completion.cancel()
|
||
|
||
handle.add_done_callback(_complete_unstarted_submission)
|
||
return True
|
||
|
||
def __remove_async_handle(self, owner: object) -> None:
|
||
"""
|
||
异步处理器完成后移除其 owner 句柄。
|
||
"""
|
||
with self.__lifecycle_lock:
|
||
self.__async_handles.pop(owner, None)
|
||
|
||
async def drain_async(
|
||
self,
|
||
timeout: Optional[float] = None,
|
||
*,
|
||
seal: bool = False,
|
||
) -> bool:
|
||
"""
|
||
等待已接纳的广播事件及其派生事件自然结算。
|
||
|
||
seal=True 会在确认稳定空闲的同一临界区关闭后续广播提交,供插件卸载前
|
||
建立不可穿透的投递屏障;超时或停止交错时保持原提交状态并返回 False。
|
||
广播处理器调用栈内无法等待自身完成,因此会立即返回 False,且不执行封口。
|
||
"""
|
||
if _CURRENT_EVENT_HANDLER_OWNER.get() is not None:
|
||
logger.warning("事件处理器内部不能建立事件投递屏障")
|
||
return False
|
||
loop = asyncio.get_running_loop()
|
||
deadline = None if timeout is None else loop.time() + max(timeout, 0)
|
||
while True:
|
||
with self.__lifecycle_lock:
|
||
if self.__lifecycle_state not in {"running", "sealed"}:
|
||
return False
|
||
with self.__event_queue.all_tasks_done:
|
||
queue_idle = self.__event_queue.unfinished_tasks == 0
|
||
handlers_idle = not self.__sync_handles and not self.__async_handles
|
||
if queue_idle and handlers_idle:
|
||
if seal:
|
||
self.__lifecycle_state = "sealed"
|
||
return True
|
||
if deadline is not None:
|
||
remaining = deadline - loop.time()
|
||
if remaining <= 0:
|
||
return False
|
||
await asyncio.sleep(min(0.01, remaining))
|
||
else:
|
||
await asyncio.sleep(0.01)
|
||
|
||
def check(self, etype: Union[EventType, ChainEventType]) -> bool:
|
||
"""
|
||
检查是否有启用的事件处理器可以响应某个事件类型。
|
||
|
||
:param etype: 事件类型 (EventType 或 ChainEventType)
|
||
:return: 返回是否存在可用的处理器
|
||
"""
|
||
return self.__registry.check(etype)
|
||
|
||
def send_event(self, etype: Union[EventType, ChainEventType], data: Optional[Union[Dict[str, Any], BaseEventData]] = None,
|
||
priority: Optional[int] = DEFAULT_EVENT_PRIORITY) -> Optional[Event]:
|
||
"""
|
||
发送事件,根据事件类型决定是广播事件还是链式事件。
|
||
|
||
:param etype: 事件类型 (EventType 或 ChainEventType)
|
||
:param data: 可选,事件数据
|
||
:param priority: 广播事件的优先级,默认为 10
|
||
:return: 如果是链式事件,返回处理后的事件数据;否则返回 None
|
||
"""
|
||
event = Event(etype, data, priority)
|
||
if isinstance(etype, EventType):
|
||
self.__trigger_broadcast_event(event)
|
||
return None
|
||
elif isinstance(etype, ChainEventType):
|
||
return self.__trigger_chain_event(event)
|
||
else:
|
||
logger.error(f"Unknown event type: {etype}")
|
||
return None
|
||
|
||
def send_event_strict(
|
||
self,
|
||
etype: EventType,
|
||
data: Optional[Union[dict[str, object], BaseEventData]] = None,
|
||
priority: Optional[int] = DEFAULT_EVENT_PRIORITY,
|
||
) -> Event:
|
||
"""
|
||
同步等待全部广播处理器完成,任一失败时阻止 durable 消息结算。
|
||
"""
|
||
event = Event(etype, data, priority)
|
||
with self.__lifecycle_lock:
|
||
if self.__lifecycle_state != "running":
|
||
raise RuntimeError(f"事件处理处于 {self.__lifecycle_state} 状态")
|
||
self.__dispatcher.dispatch_broadcast_strict(
|
||
event,
|
||
self.__wait_strict_async_handler,
|
||
)
|
||
return event
|
||
|
||
async def async_send_event_strict(
|
||
self,
|
||
etype: EventType,
|
||
data: Optional[Union[dict[str, object], BaseEventData]] = None,
|
||
priority: Optional[int] = DEFAULT_EVENT_PRIORITY,
|
||
) -> Event:
|
||
"""
|
||
异步发送广播事件并等待全部处理器完成,保证配置写入返回时已完成重载。
|
||
"""
|
||
event = Event(etype, data, priority)
|
||
with self.__lifecycle_lock:
|
||
if self.__lifecycle_state != "running":
|
||
raise RuntimeError(f"事件处理处于 {self.__lifecycle_state} 状态")
|
||
await self.__dispatcher.dispatch_broadcast_async_strict(event)
|
||
return event
|
||
|
||
@staticmethod
|
||
def __wait_strict_async_handler(coroutine: Any) -> Any:
|
||
"""
|
||
在主事件循环等待异步处理器,禁止循环线程同步等待自身。
|
||
"""
|
||
loop = main_loop_registry.require()
|
||
try:
|
||
running_loop = asyncio.get_running_loop()
|
||
except RuntimeError:
|
||
running_loop = None
|
||
if running_loop is loop:
|
||
coroutine.close()
|
||
raise RuntimeError("主事件循环线程不能同步等待 durable 事件处理器")
|
||
return asyncio.run_coroutine_threadsafe(coroutine, loop).result()
|
||
|
||
async def async_send_event(self, etype: Union[EventType, ChainEventType],
|
||
data: Optional[Union[Dict[str, Any], BaseEventData]] = None,
|
||
priority: Optional[int] = DEFAULT_EVENT_PRIORITY) -> Optional[Event]:
|
||
"""
|
||
异步发送事件,根据事件类型决定是广播事件还是链式事件。
|
||
|
||
:param etype: 事件类型 (EventType 或 ChainEventType)
|
||
:param data: 可选,事件数据
|
||
:param priority: 广播事件的优先级,默认为 10
|
||
:return: 如果是链式事件,返回处理后的事件数据;否则返回 None
|
||
"""
|
||
event = Event(etype, data, priority)
|
||
if isinstance(etype, EventType):
|
||
self.__trigger_broadcast_event(event)
|
||
return None
|
||
elif isinstance(etype, ChainEventType):
|
||
return await self.__trigger_chain_event_async(event)
|
||
else:
|
||
logger.error(f"Unknown event type: {etype}")
|
||
return None
|
||
|
||
def add_event_listener(self, event_type: Union[EventType, ChainEventType], handler: Callable[..., Any],
|
||
priority: Optional[int] = DEFAULT_EVENT_PRIORITY) -> None:
|
||
"""
|
||
注册事件处理器,将处理器添加到对应的事件订阅列表中。
|
||
|
||
:param event_type: 事件类型 (EventType 或 ChainEventType)
|
||
:param handler: 处理器
|
||
:param priority: 可选,链式事件的优先级,默认为 10;广播事件不需要优先级
|
||
"""
|
||
self.__registry.add(event_type, handler, priority or DEFAULT_EVENT_PRIORITY)
|
||
|
||
def remove_event_listener(self, event_type: Union[EventType, ChainEventType], handler: Callable[..., Any]) -> None:
|
||
"""
|
||
移除事件处理器,将处理器从对应事件的订阅列表中删除。
|
||
|
||
:param event_type: 事件类型 (EventType 或 ChainEventType)
|
||
:param handler: 要移除的处理器
|
||
"""
|
||
self.__registry.remove(event_type, handler)
|
||
|
||
def disable_event_handler(self, target: Union[Callable[..., Any], type]) -> None:
|
||
"""
|
||
禁用指定的事件处理器或事件处理器类。
|
||
|
||
:param target: 处理器函数或类
|
||
"""
|
||
self.__registry.disable(target)
|
||
|
||
def enable_event_handler(self, target: Union[Callable[..., Any], type]) -> None:
|
||
"""
|
||
启用指定的事件处理器或事件处理器类。
|
||
|
||
:param target: 处理器函数或类
|
||
"""
|
||
self.__registry.enable(target)
|
||
|
||
def visualize_handlers(self) -> List[Dict[str, Any]]:
|
||
"""
|
||
可视化所有事件处理器,包括是否被禁用的状态。
|
||
|
||
:return: 处理器列表,包含事件类型、处理器标识符、优先级(如果有)和状态
|
||
"""
|
||
|
||
return self.__registry.visualize()
|
||
|
||
@classmethod
|
||
def __get_handler_identifier(cls, target: Union[Callable[..., Any], type]) -> Optional[str]:
|
||
"""
|
||
获取处理器或处理器类的唯一标识符,包括模块名和类名/方法名。
|
||
|
||
:param target: 处理器函数或类
|
||
:return: 唯一标识符
|
||
"""
|
||
return EventRegistry.handler_identifier(target)
|
||
|
||
@classmethod
|
||
def __get_class_from_callable(cls, handler: Callable[..., Any]) -> Optional[str]:
|
||
"""
|
||
获取可调用对象所属类的唯一标识符。
|
||
|
||
:param handler: 可调用对象(函数、方法等)
|
||
:return: 类的唯一标识符
|
||
"""
|
||
return EventRegistry.handler_class_identifier(handler)
|
||
|
||
def __is_handler_enabled(self, handler: Callable[..., Any]) -> bool:
|
||
"""
|
||
检查处理器是否已启用(没有被禁用)。
|
||
|
||
:param handler: 处理器函数
|
||
:return: 如果处理器启用则返回 True,否则返回 False
|
||
"""
|
||
return self.__registry.is_handler_enabled(handler)
|
||
|
||
def __trigger_chain_event(self, event: Event) -> Optional[Event]:
|
||
"""
|
||
触发链式事件,按顺序调用订阅的处理器,并记录处理耗时。
|
||
"""
|
||
logger.debug(f"Triggering synchronous chain event: {event}")
|
||
dispatch = self.__dispatch_chain_event(event)
|
||
return event if dispatch else None
|
||
|
||
async def __trigger_chain_event_async(self, event: Event) -> Optional[Event]:
|
||
"""
|
||
异步触发链式事件,按顺序调用订阅的处理器,并记录处理耗时。
|
||
"""
|
||
logger.debug(f"Triggering asynchronous chain event: {event}")
|
||
dispatch = await self.__dispatch_chain_event_async(event)
|
||
return event if dispatch else None
|
||
|
||
def __trigger_broadcast_event(self, event: Event) -> None:
|
||
"""
|
||
触发广播事件,将事件插入到优先级队列中。
|
||
|
||
:param event: 要处理的事件对象
|
||
"""
|
||
logger.debug(f"Triggering broadcast event: {event}")
|
||
with self.__lifecycle_lock:
|
||
if self.__lifecycle_state in {"sealed", "stopping", "stopped"}:
|
||
logger.warning(
|
||
"事件处理处于 %s 状态,拒绝广播事件 %s",
|
||
self.__lifecycle_state,
|
||
event.event_type,
|
||
)
|
||
return None
|
||
self.__event_queue.put((event.priority, event))
|
||
record_metric(
|
||
"event.queue.depth",
|
||
1,
|
||
delivery="broadcast",
|
||
)
|
||
|
||
def __dispatch_chain_event(self, event: Event) -> bool:
|
||
"""
|
||
同步方式调度链式事件,按优先级顺序逐个调用事件处理器,并记录每个处理器的处理时间。
|
||
|
||
:param event: 要调度的事件对象
|
||
"""
|
||
return self.__dispatcher.dispatch_chain(event)
|
||
|
||
async def __dispatch_chain_event_async(self, event: Event) -> bool:
|
||
"""
|
||
异步方式调度链式事件,按优先级顺序逐个调用事件处理器,并记录每个处理器的处理时间。
|
||
|
||
:param event: 要调度的事件对象
|
||
"""
|
||
return await self.__dispatcher.async_dispatch_chain(event)
|
||
|
||
def __dispatch_broadcast_event(self, event: Event) -> None:
|
||
"""
|
||
异步方式调度广播事件,通过线程池逐个调用事件处理器。
|
||
|
||
:param event: 要调度的事件对象
|
||
"""
|
||
self.__dispatcher.dispatch_broadcast(event)
|
||
|
||
@classmethod
|
||
def __should_dispatch_to_target_plugin(
|
||
cls,
|
||
handler: Callable[..., Any],
|
||
handler_identifier: str,
|
||
target_plugin_id: str,
|
||
) -> bool:
|
||
"""
|
||
限定插件输入事件只投递给目标插件,避免自由文本被其他插件观察到。
|
||
"""
|
||
return EventDispatcher.should_dispatch_to_target_plugin(
|
||
handler,
|
||
handler_identifier,
|
||
target_plugin_id,
|
||
)
|
||
|
||
def __safe_invoke_handler(self, handler: Callable[..., Any], event: Event) -> None:
|
||
"""
|
||
调用处理器,处理链式或广播事件。
|
||
|
||
:param handler: 处理器
|
||
:param event: 事件对象
|
||
"""
|
||
self.__dispatcher.safe_invoke_sync(handler, event)
|
||
|
||
async def __safe_invoke_handler_async(self, handler: Callable[..., Any], event: Event) -> None:
|
||
"""
|
||
异步调用处理器,处理链式事件。
|
||
|
||
:param handler: 处理器
|
||
:param event: 事件对象
|
||
"""
|
||
await self.__dispatcher.safe_invoke_async(handler, event)
|
||
|
||
def __invoke_handler_by_type_sync(self, handler: Callable[..., Any], event: Event) -> None:
|
||
"""
|
||
同步方式根据处理器类型调用相应的方法。
|
||
|
||
:param handler: 处理器
|
||
:param event: 要处理的事件对象
|
||
"""
|
||
self.__dispatcher.invoke_sync(handler, event)
|
||
|
||
async def __invoke_handler_by_type_async(self, handler: Callable[..., Any], event: Event) -> None:
|
||
"""
|
||
异步方式根据处理器类型调用相应的方法。
|
||
|
||
:param handler: 处理器
|
||
:param event: 要处理的事件对象
|
||
"""
|
||
await self.__dispatcher.invoke_async(handler, event)
|
||
|
||
@staticmethod
|
||
def __parse_handler_names(handler: Callable[..., Any]) -> Tuple[str, str]:
|
||
"""
|
||
解析处理器的类名和方法名。
|
||
|
||
:param handler: 处理器
|
||
:return: 类名与方法名的二元组,用于查找已绑定处理器
|
||
"""
|
||
return EventBindingResolver.parse_handler_names(handler)
|
||
|
||
@staticmethod
|
||
def __get_handler_owner_class(handler: Callable[..., Any]) -> Optional[Type[Any]]:
|
||
"""
|
||
从处理器对象本身解析声明它的类,不按命名约定动态导入模块。
|
||
"""
|
||
return EventBindingResolver.owner_class(handler)
|
||
|
||
def __resolve_handler(
|
||
self,
|
||
handler: Callable[..., Any],
|
||
) -> Optional[Tuple[Callable[..., Any], EventHandlerBinding, str, str]]:
|
||
"""
|
||
将装饰阶段保存的函数解析为当前运行实例上的可调用方法。
|
||
"""
|
||
return self.__binding_resolver.resolve(handler)
|
||
|
||
def __broadcast_consumer_loop(self) -> None:
|
||
"""
|
||
持续从队列中提取事件的后台广播消费者线程。
|
||
"""
|
||
jitter_factor = 0.1
|
||
rate_limiter = ExponentialBackoffRateLimiter(base_wait=INITIAL_EVENT_QUEUE_IDLE_TIMEOUT_SECONDS,
|
||
max_wait=MAX_EVENT_QUEUE_IDLE_TIMEOUT_SECONDS,
|
||
backoff_factor=2.0,
|
||
source="BroadcastConsumer",
|
||
enable_logging=False)
|
||
while self.__event.is_set():
|
||
try:
|
||
priority, event = self.__event_queue.get(timeout=rate_limiter.current_wait)
|
||
try:
|
||
if event is _EVENT_STOP_SENTINEL:
|
||
break
|
||
record_metric(
|
||
"event.queue.depth",
|
||
-1,
|
||
delivery="broadcast",
|
||
)
|
||
rate_limiter.reset()
|
||
self.__dispatch_broadcast_event(event)
|
||
finally:
|
||
self.__event_queue.task_done()
|
||
except Empty:
|
||
rate_limiter.current_wait = rate_limiter.current_wait * random.uniform(1, 1 + jitter_factor)
|
||
rate_limiter.trigger_limit()
|
||
|
||
@staticmethod
|
||
def __log_event_lifecycle(event: Event, stage: str) -> None:
|
||
"""
|
||
记录事件的生命周期日志。
|
||
"""
|
||
logger.debug(f"{stage} - {event}")
|
||
|
||
def __handle_event_error(self, event: Event, module_name: str,
|
||
class_name: str, method_name: str, e: Exception) -> None:
|
||
"""
|
||
全局错误处理器,用于处理事件处理中的异常。
|
||
"""
|
||
self.__error_policy.handle(
|
||
event=event,
|
||
module_name=module_name,
|
||
class_name=class_name,
|
||
method_name=method_name,
|
||
error=e,
|
||
)
|
||
|
||
def register(
|
||
self,
|
||
etype: _EventRegistration,
|
||
priority: Optional[int] = DEFAULT_EVENT_PRIORITY,
|
||
) -> Callable[[_EventHandler], _EventHandler]:
|
||
"""
|
||
事件注册装饰器,用于将函数注册为事件的处理器。
|
||
|
||
:param etype: 要登记的事件范围,支持以下形式
|
||
- 单个事件类型成员 (如 EventType.MetadataScrape, ChainEventType.PluginAction)
|
||
- 事件类型类 (EventType, ChainEventType)
|
||
- 或事件类型成员的列表
|
||
:param priority: 可选,链式事件的优先级,默认为 DEFAULT_EVENT_PRIORITY
|
||
"""
|
||
|
||
def decorator(f: _EventHandler) -> _EventHandler:
|
||
"""
|
||
登记处理器的事件类型和优先级,返回原函数以保持直接调用行为。
|
||
"""
|
||
if isinstance(etype, list):
|
||
event_list = etype
|
||
else:
|
||
event_list = [cast(_EventKind, etype)]
|
||
|
||
for event in event_list:
|
||
if isinstance(event, (EventType, ChainEventType)):
|
||
self.add_event_listener(event, f, priority)
|
||
elif event is EventType or event is ChainEventType:
|
||
for et in event.__members__.values():
|
||
self.add_event_listener(et, f, priority)
|
||
else:
|
||
raise ValueError(f"无效的事件类型: {event}")
|
||
|
||
return f
|
||
|
||
return decorator
|
||
|
||
|
||
# 模块热重载时类对象会重新创建,但插件和 SDK 可能仍持有旧全局实例。把旧实例登记到
|
||
# 新 EventManager 类的单例键,确保所有公开入口继续共享同一个事件总线。
|
||
_existing_eventmanager = globals().get("eventmanager")
|
||
if _existing_eventmanager is not None:
|
||
Singleton._instances[(EventManager, (), frozenset())] = _existing_eventmanager
|
||
eventmanager = EventManager()
|