RecursionGuardMiddleware 递归防护中间件文档¶
概述¶
RecursionGuardMiddleware 提供双重检测机制,防止事件发布出现无限递归(包括自递归和互递归)。所有检测在 before_publish 阶段执行,在入队前即可拦截,不消耗队列资源。
当检测到递归时,抛出 RecursionDetectedError(继承自 RuntimeError),该异常会触发 on_publish_error 通知链中所有中间件。
使用场景¶
- 自递归防护:Handler A 处理事件时又发布了同名事件,导致自身被反复调用。
- 互递归防护:Handler A 发布事件触发 Handler B,Handler B 又发布事件触发 Handler A,形成循环。
- 链式调用保护:限制事件链的最大长度,防止业务逻辑异常导致链无限增长。
函数签名¶
RecursionGuardMiddleware¶
class RecursionGuardMiddleware(Middleware):
def __init__(
self,
max_depth: int = 3,
max_chain_length: Optional[int] = 50,
ignore_sources: Optional[Set[str]] = None,
) -> None
| 参数 | 类型 | 说明 |
|---|---|---|
max_depth |
int |
同一 source 在事件链的 sources 中允许出现的最大次数,默认 3。 |
max_chain_length |
Optional[int] |
事件链绝对最大长度,默认 50。设为 None 禁用此层检测。 |
ignore_sources |
Optional[Set[str]] |
不参与 per-source 计数的发布者名称集合。不影响绝对链长检测。 |
RecursionDetectedError¶
class RecursionDetectedError(RuntimeError):
"""事件发布递归调用被检测并拦截。"""
双重检测机制¶
flowchart TD
A[before_publish 被调用] --> B{old_event 存在?}
B -->|否| G[放行: next]
B -->|是| C{max_chain_length 检查}
C -->|链长超限| E[抛出 RecursionDetectedError]
C -->|通过| D{source 在 ignore_sources?}
D -->|是| G
D -->|否| F{per-source 计数检查}
F -->|计数超 max_depth| E
F -->|通过| G
第一层:per-source 计数¶
同一 source 在事件链的 sources 列表中出现次数 ≥ max_depth 时拒绝。
示例:
root(test) → Handler A 发布(system) → Handler B 发布(system) → Handler A 发布(system) ❌ 被拦截
system 在 sources 中出现了 3 次(默认 max_depth=3),第 4 次发布前被拦截。
第二层:绝对链长¶
事件链 event_ids 长度 > max_chain_length 时拒绝,无论各 source 计数如何。
示例:max_depth=3 时,K 个模块互递归可达 3 × K 轮。设 max_chain_length=10 可在此场景下提前拦截。
使用示例¶
基础防护¶
from infinity_bus.templates.middlewares import RecursionGuardMiddleware
from infinity_bus import MiddlewareChain
# 默认配置:同一 source 最多出现 3 次,链长最多 50
mw = RecursionGuardMiddleware()
chain = MiddlewareChain()
chain.add(mw)
严格模式¶
# 更严格的限制:同一 source 最多出现 2 次,链长最多 10
mw = RecursionGuardMiddleware(max_depth=2, max_chain_length=10)
忽略特定来源¶
# 某些中间件或系统组件允许在链中多次出现
mw = RecursionGuardMiddleware(
max_depth=3,
ignore_sources={"LoggingMiddleware", "MetricsCollector"},
)
被忽略的 source 不参与 per-source 计数,但仍受绝对链长限制。
仅使用链长检测¶
# 禁用 per-source 检测,仅靠链长上限防护
mw = RecursionGuardMiddleware(
max_depth=0, # 允许同一 source 无限出现
max_chain_length=20, # 但链长超过 20 时拦截
)
仅使用 per-source 检测¶
# 禁用链长检测,仅靠 per-source 计数
mw = RecursionGuardMiddleware(
max_depth=2,
max_chain_length=None, # 禁用绝对链长检测
)
递归场景详解¶
自递归¶
Handler X 订阅 "order.updated"
→ 处理时发布 "order.updated"
→ Handler X 再次被调用
→ 又发布 "order.updated" ...
max_depth=2 可在此场景下第 3 次发布前拦截。
互递归¶
Handler A 订阅 "event.a" → 处理时发布 "event.b"
Handler B 订阅 "event.b" → 处理时发布 "event.a"
Per-source 计数各为 2,不触发拦截。但链长会持续增长,max_chain_length 可在一定轮数后拦截。
链式传递(正常场景)¶
Service A 发布 "data.received"
→ Service B 处理并发布 "data.validated"
→ Service C 处理并发布 "data.stored"
三个不同的 source,per-source 计数各为 1,链长为 3——正常通过。
注意事项¶
- 根事件豁免:
old_event为None的根事件始终放行,不进行任何检测。 ignore_sources仅影响第一层:被忽略的 source 仍受绝对链长检测约束。- 异常传播:
RecursionDetectedError会中断当前发布流程,并通过on_publish_error通知所有中间件。 - 性能:检测仅涉及列表遍历和计数,时间复杂度 O(n),n 为当前链长。
- 默认值:
max_depth=3和max_chain_length=50适用于大多数场景。过于严格可能误杀正常的链式调用。
完整示例¶
参见 tests/templates/middlewares/recursion_guard_test.py
其中包含了正常链式发布、递归拦截、根事件豁免、忽略源、不同源互不干扰、绝对链长限制等场景的测试用例。