Skip to content

fix(events): 运行时按帧的顺序接收 emit 的回复,order-across-fast-path 第 2 步不再依赖时序 - #513

Merged
eric8810 merged 1 commit into
mainfrom
fix/order-across-fast-path-settle
Oct 21, 2026
Merged

eric8810 merged 1 commit into
mainfrom
fix/order-across-fast-path-settle

Conversation

@eric8810

Copy link
Copy Markdown
Contributor

问题

#511 修好了 emit/order-across-fast-path 第 0 步(viaHost: true)的偶发失败。验证时发现第 2 步(b 卸载后 viaHost: false)还依赖时序:在宿主会话的有序任务开头临时加 2 ms 延迟,第 2 步每次都失败(viaHost: expected false, 1 events.dispatch arrived)。

按 §5.5,经宿主发出的 N 的 emit 得到回复之前,N 的后续 emit 继续经宿主,Go 那时走宿主是对的。但“得到回复”在运行时里没有确定的时刻:

  • 各运行时都是读到回复帧之后才异步记下它:Go 在 goroutine 里,Node/Bun 在 Promise 的 then 里,Python 在 future 的 done callback 里(call_soon),Kotlin 在单独的线程里,Swift 在 Task 里。所以即使宿主已经回复,之后才到达的一帧(第 2 步对发出方服务的调用)里发出的 emit 仍可能看到“未回复”。
  • 宿主对排队中或尚未开始调用的 emit 先回一个 future。Go 把这个 future 当作已回复,没有等它,与 §5.5 “宿主在开始各调用之后才回复”不符。

用例只靠第 1 步卸载 b 的耗时盖过了这个窗口,并没有等待一个设计保证的条件。

修改

设计(design-events-protocol-2026-10-18 §5.5 与 §13.2,design-events-2026-10-18 第 1 节,中英文):emit 的回复是普通值,宿主在各调用开始之后写出它。运行时像处理 events.elsewhere(§4.1)一样就地、按帧的顺序接收:读到回复帧时就把这次 emit 记为已得到回复,再读下一帧。规格新增期望 answered。

宿主(rutis-bridge):

  • emit 方式的 events.dispatch 结果是 future 时,宿主先等它完成再回复普通值,不再回 future。
  • Dispatch::replied(默认空实现,经 peer 的 Operations、Handler、Routed 转发到 Imports)在写出事件操作的回复后调用,测试钩子在这里统计已回复的 dispatch。Tap::answered 等到宿主对收到的每个 events.dispatch 都写出回复。

运行时:都在读到回复帧时记下 emit 已得到回复,回复如果仍是 future(较旧的宿主),等它完成后再记:

  • Go:peer.SendAnswered,回调在读 goroutine 上执行。
  • Node(rutis-runtime)与 Bun:session.invokeAnswered / callAnswered,在 receive 里同步回调;events.mjs 三份副本相同。
  • Python:Peer.request_answered,在帧处理中直接回调。
  • Kotlin:Peer.sendAnswered,回调在发出帧之前挂到等待者上,由读线程完成。
  • Swift:Peer.sendAnswered,Pending.resolve 在读到回复时回调。

Rust 侧:宿主的 Rust 插件(含 dylib)总是自己在整张表上分发,节点连接也一样,没有快速路径,也没有“未回复的 emit”状态;叶子运行时的 emit 只在本进程里。所以 Rust 侧没有对应的改动。

用例:emit/order-across-fast-path 第 0 步加 "answered": true。运行器在进入第 1 步之前等宿主写出回复;之后发给 Go 的帧都排在回复之后,Go 按帧的顺序接收,第 2 步一定走本地。

验证

  • 2 ms 延迟探针(只用于验证,未提交):修改前,main(含 fix(bridge): 事件测试钩子在读到 events.dispatch 时计数,修复 order-across-fast-path 偶发失败 #511)上第 2 步 10/10 次失败;修改后 7 列(node-leaf、cordis、bun、python、go、kotlin、swift)× 5 种放置跑 40 轮,全部通过,第 0 步与第 2 步都通过。
  • 去掉探针:7 列跑这个用例 100 轮全部通过;RUTIS_EVENTS_PLACEMENTS=sampled RUTIS_EVENTS_SETTLE_SCALE=2 跑完整的 events_conformance 7 列,全部通过。
  • go test ./...;node --test(node/rutis-runtime 129 项,node/rutis 100 项);bun test;pytest(websocket 有一项失败,与本修改无关);./gradlew build;swift run RutisSelfTest;cargo test -p rutis-bridge --features testing,node,python;clippy、fmt。

§5.5:经宿主发出的 emit 得到回复之前,同名的后续 emit 继续经宿主。各运行时
原先在读到回复之后异步记下它(Go 的 goroutine、Node/Bun 的 Promise、Python
的 done callback、Kotlin 的线程、Swift 的 Task),宿主对排队中的 emit 还以
future 回复,所以宿主已回复、随后才到达的调用发出的 emit 仍可能经宿主,
用例第 2 步 viaHost: false 依赖时序。

- 设计:回复是普通值,宿主在各调用开始之后写出;运行时像 events.elsewhere
  一样就地、按帧的顺序接收(两份设计文档,中英文)。
- 宿主:emit 的 events.dispatch 以 future 结果时先等它再回复;
  Dispatch::replied 在写出回复后通知测试钩子。
- 运行时:Go(peer.SendAnswered)、Node 与 Bun(session.invokeAnswered)、
  Python(Peer.request_answered)、Kotlin(Peer.sendAnswered)、
  Swift(Peer.sendAnswered)在读到回复帧时记下 emit 已得到回复。
- 规格:新期望 answered(运行器等宿主对收到的每个 events.dispatch 都写出
  回复),emit/order-across-fast-path 第 0 步使用它。
@eric8810 eric8810 added this to the 0.15 milestone Oct 21, 2026
@eric8810
eric8810 merged commit e048517 into main Oct 21, 2026
27 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