Skip to content

test(#3269): e2e-native-opencode 断言改为等待 shutdown flush 落盘,消除固定睡眠竞态 - #3271

Merged
ranxianglei merged 1 commit into
masterfrom
2026-10-15_fix-3269-e2e-persist-wait
Oct 15, 2026
Merged

ranxianglei merged 1 commit into
masterfrom
2026-10-15_fix-3269-e2e-persist-wait

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #3269

是什么问题

e2e-native-opencode 车道测试 3(compress 折叠)在同提交上偶发红→绿:

not ok 3 - native opencode compress: model-invoked compress really folds context
error: 'at least one persisted session must carry compressed blocks (got 1 sessions)'
location: tests/e2e/e2e-native-opencode.test.ts:732

rerun-failed-jobs 后整条车道转绿——同一代码红→绿,非确定性失败。

什么原因导致的

断言在 proxy 优雅关闭的 flush 完成之前读了盘。因果链:

  1. 每轮持久化写是 500ms 去抖的(persist.debounceMs/BILI_PERSIST_DEBOUNCE_MS,kernel StateStore.scheduleSave 用 unref 定时器)。opencode 客户端在 fold run 最后一次状态变更后 <500ms 就退出,所以 fold 产生的块几乎总是靠优雅关闭时的 flush 才落盘:SIGTERM → server.close 等连接排空 → flushAllSessions() → process.exit(0)(src/server.ts:1269-1290,flush 先于 exit)。
  2. 而测试侧 stopProxiesGracefully 发完 SIGTERM 只固定睡 500ms 就读盘。优雅关闭路径耗时无上界(负载下:信号投递、事件循环饥饿、连接排空、fsync、WAL checkpoint),一旦 >500ms 就读到折前旧文件 → "got 1 sessions"(文件在、blocks 空)。
  3. 同提交 rerun 时序碰巧赶上 → 绿。纯时序竞态,与代码变更无关。

已排除:fake upstream 脚本竞态(红跑中 compress 工具结果已重放进历史 + bili.log 有执行日志,说明内存态折叠成功);persona fork(permitlist 只含 dsh);stale writer 覆盖(#405/#2401 单调计数守卫挡回写)。

怎么修复的

把固定 500ms 睡眠换成有界轮询:新增 waitForPersistedBlocks(ctx, timeoutMs),每 100ms 检查一次直到「至少一个会话文件带非空 blocks」或 20s 期限。关键设计:proxy 进程死亡本身不算成功——crash 的 proxy 未 flush 而死必须响亮失败;超时报错附带诊断(存活 proxy pid、各会话文件内容摘要、bili.log 尾部),让下次真出问题能直接定位而不是裸断言。

产品侧无需改动:行为符合设计(文档化的丢失窗口只有 SIGKILL/断电时 ≤1 个去抖窗口)。

验证

  • 本地最小复现:模拟「shutdown flush 耗时 >500ms」的 proxy,旧逻辑(固定 500ms 睡)读不到块(复现 flake),新逻辑(有界轮询)在 ~2s 读到 → PASS。
  • 本地真实车道:ACP_TEST_E2E_OC_NATIVE=1 node --import tsx --test tests/e2e/e2e-native-opencode.test.ts(opencode 1.14.46 / v1 lane)→ 4/4 pass。
  • npm run typecheck 干净。
  • 全量回归交远程 CI(本 PR 会触发 e2e-native-opencode 车道)。

行为变更披露

仅测试基础设施改动,无运行时行为变化。唯一语义变化:该断言从「SIGTERM 后固定 500ms 快照」变为「最多等 20s 直到落盘后置条件成立」——这是把对关闭耗时的错误假设换成正确等待,不是放宽断言强度(成功判据仍是「有非空 blocks」,且更严格地要求它真正落盘而非进程已死)。

配置面

不涉及(无新 env/config/CLI flag)。

@github-actions

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-15_fix-3269-e2e-persist-wait (b27ebde)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.3771-pr.3271.d05fc12

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr3271.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm/qwen3.8-27b)

查重: 不重复。 按失败签名(at least one persisted session must carry compressed blocks)检索 open+closed issue/PR,只有本 PR 在修 #3269;open PR 列表里也没有其他改动 tests/e2e/e2e-native-opencode.test.ts 的并行修复。

审核已开始,先报进展:

  • diff 面:单 commit b27ebde9f、单文件 tests/e2e/e2e-native-opencode.test.ts(+46/−9),分支就在最新 master 上(落后 0 commit,无需 rebase)。纯测试基础设施改动,未触碰运行时代码。
  • CI(head sha):11 个 run 已出 10 绿——包括此前红→绿的 e2e-native-opencode 车道本体、e2e-native-pi、e2e-pi-codex-ws、E2E (real codex)、registry 等;仅剩主 CI(全量 test 分片)还在跑。
  • 机制核对(对照 master 源码):
    • 去抖链路属实:默认 500ms(kernel/src/persist/store.ts:62 scheduleSave 去抖窗口;src/knobs.ts:193 BILI_PERSIST_DEBOUNCE_MS > persist.debounceMs > 500);
    • 关闭路径属实:SIGTERM → server.close 排空 → flushAllSessions() → exit,且带 10s 强制退出兜底(src/server.ts shutdown handler)——即优雅关闭最坏 ~10s,旧测试固定睡 500ms 读盘确实必然竞态;新断言 20s 轮询窗口覆盖了该上界;
    • 轮询读盘安全:persist 写是 tmp+rename+fsync 原子发布(src/persist.ts),轮询方不会读到半截文件,解析失败按「不可读」跳过下轮重试。
  • 本地定向验证:npm run typecheck 正在跑,完成后贴结构化八问结论。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm/qwen3.8-27b)

Review 结论(结构化八问)

  1. 是什么问题:e2e-native-opencode 车道测试 3(compress 折叠)的偶发红——断言在 proxy 优雅关闭的 flush 落盘之前读盘,负载下关闭耗时超过固定 500ms 睡眠就读到折前旧文件("got 1 sessions"),同提交 rerun 转绿。
  2. 什么原因导致的:持久化写是 500ms 去抖(kernel StateStore.scheduleSave),fold 块几乎必然靠优雅关闭时的 flush 才落盘;而优雅路径(SIGTERM → server.close 等连接排空 → flushAllSessions() → exit,src/server.ts:1269-1290)耗时无上界(有 10s 硬兜底强制退出)。测试侧把「≤500ms」当成了事实。纯时序竞态,产品行为符合设计。
  3. 影响范围与严重程度:仅该 e2e 车道偶发假红——浪费 rerun、可能在红风暴里掩盖真回归;产品零影响。
  4. 怎么修复的:固定睡眠换成有界轮询 waitForPersistedBlocks(ctx, 20_000)(100ms tick,成功判据 = 至少一个会话文件带非空 blocks);20s 覆盖最坏 ~10s 关闭路径;超时报错带诊断(存活 proxy pid、各会话文件摘要、bili.log 尾部);进程死亡本身不算成功,crash 未 flush 必须响亮失败。
  5. 是否有回归问题:无。逐项审过的既有行为:① 运行时代码零改动(diff 仅 tests/e2e/e2e-native-opencode.test.ts +46/−9);② stopProxiesGracefully 未动,其 500ms 睡眠退化为轮询前的 head start,语义无害;③ 其余测试断言未触碰;④ persist 写是原子 tmp+rename+fsync(src/persist.ts),轮询不可能读到半截文件;⑤ 唯一语义变化(断言从「SIGTERM 后固定 500ms 快照」改为「最多等 20s 直到后置条件成立」)已在 PR 正文披露,且是收紧不是放宽(成功判据不变,额外要求真正落盘)。无吞错、无阈值漂移、无输出格式变化。
  6. 是否破坏缓存:不涉及(纯测试基础设施)。
  7. 是否改了设计方向:不涉及。
  8. 是否引入了新的配置:不涉及(无新 env/config/CLI flag)。

机制核实(对照 master 源码逐条验证过,PR 描述全部属实)

  • 去抖默认值:kernel/src/persist/store.ts:62 Default 500;src/knobs.ts:193 优先级 BILI_PERSIST_DEBOUNCE_MS > persist.debounceMs > 500 ✓
  • 关闭顺序 flush 先于 exit + 10s 硬兜底 ✓(20s 窗口覆盖该上界)
  • 原子写保证轮询安全 ✓;ctx 每测试独立,无跨测试陈旧会话误报 ✓

CI 状态与那条红灯

head b27ebde9f 共 11 条车道:10 绿,包括本 PR 要修的那条 e2e-native-opencode 车道本身。1 红 = 主 CI 分片 windows-latest / node22 / shard 2,挂在 tests/issue1993-preflight-400.test.ts:199(「cooldown replays the cached diagnosis」,两条消息仅内嵌本地估算数字不同 ~17396 vs ~16892)。已确认与本 PR 无关:diff 不碰 src/ 也不碰该文件(它不在主 CI test glob 内)、master 最近 4 次完整 CI 全绿、本地定向跑该文件 5/5 通过 ⇒ master 既有的负载相关偶发。按政策已立 #3274(来源标记 + 查重声明,含机制线索与最便宜的判别实验),不在本 PR 里塞修复。

验证证据

  • 本地 npm run typecheck 干净(CI 的 typecheck/build 步骤也在失败测试步骤之前通过);
  • 作者侧真实车道 4/4 pass(opencode 1.14.46 / v1 lane);全量回归以远程 CI 为准。

一个可选观察(非阻塞,不建议本 PR 做)

超时分支可以更快:若所有记录的 proxy pid 已死且仍无非空 blocks,可立即失败而非等满 20s——因为每条优雅路径都 flush 先于 exit,「进程全死 + 无块」意味着永远不会再有块出现。属锦上添花,留给后续小 PR 或忽略。

判定:可合。 建议对主 CI 分片 rerun-failed-jobs(其红与本 diff 无关,#3274 跟踪根因);rerun 转绿后合并即可。

@ranxianglei

Copy link
Copy Markdown
Owner Author

ci失败

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm/qwen3.8-27b)

这条红不是本 PR 的问题,已经定位清楚:

  • 挂在哪:主 CI 分片 windows-latest / node22 / shard 2,断言在 tests/issue1993-preflight-400.test.ts:199——「cooldown replays the cached diagnosis」,两条报错消息只有内嵌的本地 token 估算数字不同(~17396 vs ~16892)。
  • 为什么与这个 PR 无关:本 PR 只改 tests/e2e/e2e-native-opencode.test.ts(不在主 CI test glob 内),零运行时改动;同 sha 其余 10 条车道全绿(包括本 PR 要修的那条 e2e-native-opencode);master 最近 4 次完整 CI 全绿;本地定向跑该文件 5/5 通过。⇒ master 既有的负载相关偶发,不是本 diff 引入。
  • 已立项:主 CI 偶发红灯:#1993 死胡同冷却「重放缓存诊断」断言失败——重放消息里的本地 token 估算数字变了(~17396→~16892) #3274(带来源标记 + 机制线索:重放路径返回的是缓存原串,观测到漂移说明第二次请求大概率没走重放分支;最便宜的判别实验也写进去了)。

我刚触发了 rerun-failed-jobs(attempt 2 已排队),只重跑那一条失败分片。转绿后即可合并——本 PR 本身审核结论是可合。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm/qwen3.8-27b)

rerun 完成:attempt 2 全绿(之前挂的 windows/node22/shard 2 分片在相同代码上转绿——又一次红→绿,坐实了它是负载偶发而非代码问题,#3274 继续跟踪根因)。

现在 11/11 车道全绿,PR 可以合并了。

@ranxianglei
ranxianglei merged commit 30741ab into master Oct 15, 2026
65 of 66 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.458-master.30741ab (rolling master tag).

npm install -g billion-context@0.0.458-master.30741ab  # this exact build
npm install -g billion-context@master  # always the newest merged build

npm view billion-context@master prints the current version at any time.

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.

e2e-native-opencode 车道偶发红:compress 测试找不到折叠块(同提交红→绿)

1 participant