Skip to content

fix(bridge): 事件测试钩子在读到 events.dispatch 时计数,修复 order-across-fast-path 偶发失败 - #511

Merged
eric8810 merged 1 commit into
mainfrom
fix/tap-counts-dispatch-on-read
Oct 21, 2026
Merged

eric8810 merged 1 commit into
mainfrom
fix/tap-counts-dispatch-on-read

Conversation

@eric8810

@eric8810 eric8810 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

问题

PR #503 的 network-macos(job 118970595241)在 crates/rutis-loader/tests/events_conformance.rs 的 events_with_x_a_go_plugin 失败:

FAIL emit/order-across-fast-path [X=go Y=bun a@X b@Y e@X]: step 0 (emit from e): viaHost: expected true, 0 events.dispatch arrived

#503 只改 Swift,与它无关。本地单跑这个用例(RUTIS_EVENTS_PLACEMENTS=all RUTIS_EVENTS_CASE=emit/order-across-fast-path)同样偶发,Y 为 bun、python 都出现过。

根因

不是 Go SDK 的快速路径,也不是监听器表推送的竞争。在 Go 运行时里加跟踪后,失败的那一次里 Go 在 emit 前已经收到 events.elsewhere {add: [t/switch]},判断为经宿主(via=true),并在回复发出方的调用之前发出了 events.dispatch。帧的顺序完全符合设计(§4.4、§5.2)。

问题在宿主的测试钩子计数的时机:

  • 宿主的读线程读到 events.* 帧后,把它放进会话的有序任务(run_ordered,在执行器上运行),然后接着读下一帧。事件操作之间保持顺序,但它们与其他帧(比如回复)之间不保证先后(这是有意的:读线程不运行插件代码)。
  • Tap::dispatches 原先在有序任务执行 events.dispatch 时(SessionEvents::invoke)才加一。
  • Go 发出 events.dispatch 后回复 emitter-e.dispatch。读线程先读到 dispatch 帧,放进有序任务;再读到回复,套件的调用随即返回,读取计数。有序任务还没轮到时,计数仍是 0。

所以钩子测的是“宿主已开始执行”,而它声称、套件也需要的是“宿主已收到”。

修改

  • session::Dispatch 增加带默认空实现的 arrived(peer, method):读线程接纳一个事件操作时、读下一帧之前调用,不得阻塞。
  • 运行时会话的调度链转发它:peer::Handler(默认空实现)、Operations(按族找到处理者)、runtime::access::Routed,到 Imports,再到 SessionEvents::arrived。
  • 测试钩子在 SessionEvents::arrived 里对 events.dispatch 计数(TapState::received);hold 仍在执行前等待(TapState::starting),读线程不会被它阻塞。

产品行为不变:没有 testing 特性时 arrived 什么也不做。测试没有放宽:viaHost: true 仍要求发出方在返回之前已经把 events.dispatch 发给宿主,viaHost: false 反而更严格(经宿主的 dispatch 在读到时就计入,不会因为执行晚而漏算)。

其他语言:钩子在宿主侧,Node、Bun、Python、Swift、Kotlin 的 viaHost 检查走同一个计数,一并修好。它们的快速路径与 Go 相同,都是在回复之前把 events.dispatch 按序发出,没有发现同样的竞争。

验证

只跑这个用例的 5 种放置(RUTIS_EVENTS_PLACEMENTS=all RUTIS_EVENTS_CASE=emit/order-across-fast-path,events_with_x_a_go_plugin):

  • 修改前(main):本地很少复现。第一轮循环里失败 1 次(Y=python);带跟踪的一轮在第 23 次失败(Y=bun,即上面的跟踪);之后两个并行循环各 250 次没有再出现。
  • 为了确定原因,临时在会话有序任务开头加 2 ms 延迟(只放大读线程与有序任务之间的窗口,不提交):main 上 3 次运行全部失败,5 种放置都报 step 0 (emit from e): viaHost: expected true, 0 events.dispatch arrived,与 CI 的报错一致;加上本修改后 10 次运行里 step 0 全部通过。
  • 修改后(不加延迟):两个循环并行,各 250 次,共 500 次全部通过。
  • RUTIS_EVENTS_PLACEMENTS=sampled RUTIS_EVENTS_SETTLE_SCALE=2 跑完整的 events_conformance(X = node-leaf、bun、go、cordis、python):全部通过。
  • cargo test -p rutis-bridge --features testing,node,python --lib --tests、cargo clippy -p rutis-bridge --all-features --all-targets、cargo fmt --check:通过。

附带观察:加 2 ms 延迟时,修改后的 step 2(viaHost: false)会失败,因为宿主对 step 0 那次 emit 的回复晚于 b 的卸载,Go 按 §5.5 在回复之前继续经宿主发出。这符合设计;不加延迟时卸载一行远慢于宿主回复,没有出现过。

宿主读到 events.* 帧后放进会话的有序任务里执行,读线程接着读下一帧。
测试钩子原先在有序任务执行 events.dispatch 时才计数,于是运行时在
dispatch 之后发出的回复(发出方 emit 的返回)可能先被读到,套件读计数时
这次 dispatch 还没算上,emit/order-across-fast-path 偶发报
viaHost: expected true, 0 events.dispatch arrived。

现在读线程接纳事件操作时调用 Dispatch::arrived(经 peer 的 Operations、
Handler 与 Routed 转发到 Imports),钩子在这里计数;hold 仍在执行时等待。
@eric8810 eric8810 added this to the 0.15 milestone Oct 21, 2026
@eric8810
eric8810 merged commit f8f75ac into main Oct 21, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant