""" 链式和广播事件的独立调度算法。 """ from __future__ import annotations import inspect import time from collections.abc import Callable from contextlib import AbstractContextManager, nullcontext from typing import Any from app.runtime.correlation import correlation_scope from app.runtime.event.binding import EventBindingResolver from app.runtime.event.registry import EventRegistry from app.runtime.execution import run_in_threadpool from app.runtime.log import bind_plugin_instance, logger from app.runtime.observability import observe_duration from app.schemas.types import EventType def _instance_binding_scope(class_name: str) -> AbstractContextManager[None]: """ 处理器归属某个类时绑定其插件实例日志上下文,自由函数处理器不绑定。 ``class_name`` 就是声明该处理器的类的 ``__name__``;宿主类处理器同样会 命中这里,但缓存里没有它们的等级覆盖记录,过滤时自然回落全局等级。 """ return bind_plugin_instance(class_name) if class_name else nullcontext() class EventDispatcher: """ 基于订阅快照执行链式或广播事件,不拥有注册和生命周期状态。 """ def __init__( self, *, registry: EventRegistry, binding_resolver: EventBindingResolver, event_factory: Callable[..., Any], error_handler: Callable[..., None], async_handle_sink: Callable[[Any], bool], sync_handle_sink: Callable[ [Callable[..., Any], tuple[Any, ...]], bool, ], ) -> None: """ 注入注册表、绑定器、生命周期提交器和错误策略回调。 """ self._registry = registry self._binding_resolver = binding_resolver self._event_factory = event_factory self._error_handler = error_handler self._async_handle_sink = async_handle_sink self._sync_handle_sink = sync_handle_sink def _begin_chain_dispatch( self, event: Any ) -> tuple[tuple[str, int, Callable[..., Any]], ...]: """ 冻结启用处理器快照,并统一空快照与开始生命周期记录。 """ handlers = self._registry.chain_snapshot(event.event_type) enabled = tuple( (handler_id, priority, handler) for handler_id, (priority, handler) in handlers if self._registry.is_handler_enabled(handler) ) if not enabled: logger.debug("No enabled handlers found for chain event: %s", event) return () self._log_lifecycle(event, "Started") return enabled @staticmethod def _log_chain_handler_completed( handler: Callable[..., Any], priority: int, started_at: float, event: Any, ) -> None: """ 统一记录同步与异步处理器完成耗时和优先级。 """ logger.debug( "%s (Priority: %s), completed in %.3fs for event: %s", EventRegistry.handler_identifier(handler), priority, time.time() - started_at, event, ) def _complete_chain_dispatch(self, event: Any) -> bool: """ 统一提交链式事件完成生命周期并返回已处理标识。 """ self._log_lifecycle(event, "Completed") return True def dispatch_chain(self, event: Any) -> bool: """ 同步按优先级顺序执行链式事件快照。 """ enabled = self._begin_chain_dispatch(event) if not enabled: return False for _handler_id, priority, handler in enabled: started_at = time.time() self.invoke_sync(handler, event) EventDispatcher._log_chain_handler_completed( handler, priority, started_at, event ) return self._complete_chain_dispatch(event) async def async_dispatch_chain(self, event: Any) -> bool: """ 异步按优先级顺序执行链式事件快照。 """ enabled = self._begin_chain_dispatch(event) if not enabled: return False for _handler_id, priority, handler in enabled: started_at = time.time() await self.invoke_async(handler, event) EventDispatcher._log_chain_handler_completed( handler, priority, started_at, event ) return self._complete_chain_dispatch(event) def dispatch_broadcast(self, event: Any) -> None: """ 按订阅快照把广播事件投递到线程池或主事件循环。 """ handlers = self._registry.broadcast_snapshot(event.event_type) if not handlers: logger.debug("No handlers found for broadcast event: %s", event) return target_plugin_id = None if event.event_type == EventType.MessageAction and isinstance( event.event_data, dict, ): target_plugin_id = event.event_data.get("__mp_target_plugin_id") for handler_id, handler in handlers: if target_plugin_id and not self.should_dispatch_to_target_plugin( handler, handler_id, str(target_plugin_id), ): continue if isinstance(event.event_data, dict): event_data = event.event_data.copy() event_data.pop("__mp_target_plugin_id", None) else: event_data = event.event_data isolated = self._event_factory( event_type=event.event_type, event_data=event_data, priority=event.priority, correlation_id=event.correlation_id, ) if inspect.iscoroutinefunction(handler): coroutine = self.safe_invoke_async(handler, isolated) self._async_handle_sink(coroutine) else: self._sync_handle_sink( self.safe_invoke_sync, (handler, isolated), ) def dispatch_broadcast_strict( self, event: Any, async_runner: Callable[[Any], Any], ) -> None: """ 串行执行广播处理器并等待完成,任一处理失败时向调用方抛出。 """ handlers = self._registry.broadcast_snapshot(event.event_type) target_plugin_id = None if event.event_type == EventType.MessageAction and isinstance( event.event_data, dict, ): target_plugin_id = event.event_data.get("__mp_target_plugin_id") for handler_id, handler in handlers: if not self._registry.is_handler_enabled(handler): continue if target_plugin_id and not self.should_dispatch_to_target_plugin( handler, handler_id, str(target_plugin_id), ): continue if isinstance(event.event_data, dict): event_data = event.event_data.copy() event_data.pop("__mp_target_plugin_id", None) else: event_data = event.event_data isolated = self._event_factory( event_type=event.event_type, event_data=event_data, priority=event.priority, correlation_id=event.correlation_id, ) if inspect.iscoroutinefunction(handler): async_runner(self.invoke_async_strict(handler, isolated)) else: self.invoke_sync_strict(handler, isolated) async def dispatch_broadcast_async_strict(self, event: Any) -> None: """ 异步串行等待广播处理器完成,供实时配置重载入口使用。 """ handlers = self._registry.broadcast_snapshot(event.event_type) target_plugin_id = None if event.event_type == EventType.MessageAction and isinstance( event.event_data, dict, ): target_plugin_id = event.event_data.get("__mp_target_plugin_id") for handler_id, handler in handlers: if not self._registry.is_handler_enabled(handler): continue if target_plugin_id and not self.should_dispatch_to_target_plugin( handler, handler_id, str(target_plugin_id), ): continue if isinstance(event.event_data, dict): event_data = event.event_data.copy() event_data.pop("__mp_target_plugin_id", None) else: event_data = event.event_data isolated = self._event_factory( event_type=event.event_type, event_data=event_data, priority=event.priority, correlation_id=event.correlation_id, ) if inspect.iscoroutinefunction(handler): await self.invoke_async_strict( handler, isolated, skip_unresolved=True, run_sync_in_threadpool=True, ) else: # 复用统一严格调用路径,保持 owner 的线程池声明和错误策略一致。 await self.invoke_async_strict( handler, isolated, skip_unresolved=True, run_sync_in_threadpool=True, ) def safe_invoke_sync(self, handler: Callable[..., object], event: Any) -> None: """ 仅在处理器启用时执行同步调用。 """ if self._registry.is_handler_enabled(handler): self.invoke_sync(handler, event) async def safe_invoke_async(self, handler: Callable[..., object], event: Any) -> None: """ 仅在处理器启用时执行异步调用。 """ if self._registry.is_handler_enabled(handler): await self.invoke_async(handler, event) def invoke_sync(self, handler: Callable[..., object], event: Any) -> None: """ 解析实例绑定并同步调用处理器。 """ resolved = self._binding_resolver.resolve(handler) if not resolved: return method, binding, class_name, method_name = resolved with correlation_scope(event.correlation_id): try: with observe_duration( "event.handler.duration", event_type=event.event_type.value, handler_type="bound" if class_name else "function", ), _instance_binding_scope(class_name): method(event) except Exception as err: self._error_handler( event=event, module_name=binding.owner_name, class_name=class_name, method_name=method_name, e=err, ) def invoke_sync_strict( self, handler: Callable[..., object], event: Any, *, skip_unresolved: bool = False, ) -> None: """ 解析并执行同步处理器,按需跳过未激活 owner,实际错误向调用方传播。 """ resolved = self._binding_resolver.resolve(handler) if not resolved: if skip_unresolved: return raise RuntimeError("事件处理器实例不可用") method, binding, class_name, method_name = resolved with correlation_scope(event.correlation_id): try: with observe_duration( "event.handler.duration", event_type=event.event_type.value, handler_type="bound" if class_name else "function", ), _instance_binding_scope(class_name): method(event) except Exception as err: self._error_handler( event=event, module_name=binding.owner_name, class_name=class_name, method_name=method_name, e=err, ) raise async def invoke_async(self, handler: Callable[..., object], event: Any) -> None: """ 解析实例绑定,并按处理器类型选择协程、线程池或同步调用。 """ resolved = self._binding_resolver.resolve(handler) if not resolved: return method, binding, class_name, method_name = resolved with correlation_scope(event.correlation_id): try: with observe_duration( "event.handler.duration", event_type=event.event_type.value, handler_type="bound" if class_name else "function", ), _instance_binding_scope(class_name): if inspect.iscoroutinefunction(method): await method(event) elif binding.run_sync_in_threadpool or not class_name: await run_in_threadpool(method, event) else: method(event) except Exception as err: self._error_handler( event=event, module_name=binding.owner_name, class_name=class_name, method_name=method_name, e=err, ) async def invoke_async_strict( self, handler: Callable[..., object], event: Any, *, skip_unresolved: bool = False, run_sync_in_threadpool: bool = False, ) -> None: """ 解析并等待处理器完成,按需跳过未激活 owner 或移交同步处理器线程池。 """ resolved = self._binding_resolver.resolve(handler) if not resolved: if skip_unresolved: return raise RuntimeError("事件处理器实例不可用") method, binding, class_name, method_name = resolved with correlation_scope(event.correlation_id): try: with observe_duration( "event.handler.duration", event_type=event.event_type.value, handler_type="bound" if class_name else "function", ), _instance_binding_scope(class_name): if inspect.iscoroutinefunction(method): await method(event) elif run_sync_in_threadpool or binding.run_sync_in_threadpool or not class_name: await run_in_threadpool(method, event) else: method(event) except Exception as err: self._error_handler( event=event, module_name=binding.owner_name, class_name=class_name, method_name=method_name, e=err, ) raise @staticmethod def should_dispatch_to_target_plugin( handler: Callable[..., object], handler_identifier: str, target_plugin_id: str, ) -> bool: """ 只把定向输入事件投递给标识和声明均匹配的目标插件。 目标匹配按运行实例身份判断,不能只看处理器的限定名:分身共享源码,其 ``__qualname__`` 保持源类名不变,只比类名会让定向到分身的事件全部落空, 而定向到本体的事件被本体连同它的全部分身一起收到。实例身份编码在处理器 标识的模块段里(``app.plugins.<实例ID>``);不是插件处理器时回落到类名比较, 保持宿主侧处理器的既有行为。 """ class_name, method_name = EventBindingResolver.parse_handler_names(handler) parts = (handler_identifier or "").split(".") if len(parts) < 2 or parts[-2:] != [class_name, method_name]: return False module_path = ".".join(parts[:-2]) prefix = "app.plugins." if module_path.startswith(prefix): owner_id = module_path[len(prefix):].split(".")[0] return owner_id.casefold() == target_plugin_id.casefold() return class_name == target_plugin_id @staticmethod def _log_lifecycle(event: Any, stage: str) -> None: """ 记录事件调度的开始和完成阶段。 """ logger.debug("%s - %s", stage, event)