Skip to content

[PR] fix(#5236): sessions rm must not full-VACUUM under a live foreign writer - #5330

Open
ranxianglei wants to merge 2 commits into
masterfrom
2026-10-31_rm-full-vacuum-live-writer-guard
Open

ranxianglei wants to merge 2 commits into
masterfrom
2026-10-31_rm-full-vacuum-live-writer-guard

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Refs #5236 — post-merge supplement to PR #5213 / owner ruling 3a6ab628 (via PR #5294).

What

bili sessions rm <id> defaults to a full VACUUM after the deletion. While another bili process (e.g. a running proxy) holds index.db, that full VACUUM can win the exclusive write lock and starve the other instance's writes across the whole O(db-size) rewrite — exactly the hazard class #4612 fixed for storage gc --vacuum full. After the owner ruling 3a6ab62 (2026-10-31: a live foreign writer never blocks startup), per-operation guards are the sole defense line — and runRm was missing its guard while runGc kept its (#4612).

Found while closing out #5236 on current master (caaab21): the ruling's commit message names runGc's store.unifiedActiveWriter() guard as the surviving enforcement point; a call-site audit shows four full-VACUUM sites, three already guarded (runGc CLI refusal, admin.ts panel 409, store.gc internal), one not (runRm).

Fix

runRm checks store.unifiedActiveWriter() before the shrink (same source of truth as runGc's refusal): live foreign writer ⇒ skip ONLY the shrink up front, with an actionable pid-named note (shrink skipped: another process (pid N) is actively writing index.db — … or run 'bili storage gc --vacuum incremental'). The deletion itself is a short transaction and stays committed — same graceful shape as the pre-existing SQLITE_BUSY path in the same function. No-writer and --no-vacuum paths unchanged. JSON lane carries vacuumed=false + vacuumNote (fields pre-existed; no schema change).

Behavior change (old → new)

  • OLD: sessions rm <id> with a live external writer attempted the full VACUUM anyway — either it won the exclusive lock and starved the co-resident instance's writes for the whole rewrite (minutes on multi-GB dbs), or it lost the race with SQLITE_BUSY and skipped the shrink with a generic "database busy" note.
  • NEW: deterministic upfront skip of the shrink with an actionable pid-named note whenever a live foreign writer is detected; the deletion always commits. Unchanged when no foreign writer is present.

Tests

New tests/issue5236-rm-vacuum-writer-guard.test.ts: positive gate (skip + pid-named note + deletion-committed proof + json honesty) and both negative gates per #3617 discipline (no writer ⇒ vacuum runs; dead holder ⇒ vacuum runs). Local: typecheck clean, suite 3/3 green on caaab21. Full-suite verdict = CI on this head.

Single-module scope (src/storage-maintenance.ts + one new test file); no config surface, no kernel/, no wire changes, no version touch.

@ranxianglei ranxianglei changed the title fix(#5236): sessions rm must not full-VACUUM under a live foreign writer [PR] fix(#5236): sessions rm must not full-VACUUM under a live foreign writer Oct 31, 2026
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-31_rm-full-vacuum-live-writer-guard (4932575)

Latest build — install this exact version

npm install -g billion-context@0.0.9077-pr.5330.ebe6adf

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

Recent builds (commit → version)

commit version build
4932575 billion-context@0.0.9077-pr.5330.ebe6adf build
24bfdb0 billion-context@0.0.9061-pr.5330.834037b build
f97b257 billion-context@0.0.7477-pr.5330.98655df build
8eb0e37 billion-context@0.0.7461-pr.5330.057e735 build

Download artifact

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

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

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

CI 状态说明:首跑三条红全部为继承性 master 失败,与本 PR diff 无关——

@ranxianglei ranxianglei added bug Something isn't working p2 scope/storage labels Oct 31, 2026
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

查重:不重复——与 #4612/#5236 同属「活的外写者下 full-VACUUM 饿死共驻实例」机制家族,但 #4612 只覆盖了 gc 侧三个守卫位(runGc CLI 拒绝、admin 面板 409、store.gc 内部);本 PR 修的是 owner 裁决 3a6ab62 点位审计里唯一漏网的第四处 runRm,是本家族的收尾修复,跟踪 issue 即 #5236。

标签:bug、scope/storage、p2(存储车道 CLI 缺陷;特定并发条件下共驻实例写入被饿死,有 --no-vacuum 绕行,无数据丢失 → p2 非 p1)。

代码审查结论(独立重审,非复读 issue):

CI 现状(head f97b257,已 merge origin/ci-fixes 0371089):

八问:

  1. 是什么问题:bili sessions rm <id> 默认删后做 full VACUUM;另一个 bili 进程正在写 index.db 时,这次 VACUUM 可能抢到独占写锁,把共驻实例的写入在整个 O(db-size) 重写期间(多 GB 库=分钟级)全部饿死。
  2. 什么原因导致的:owner 裁决 3a6ab62 之后,逐操作守卫是唯一防线;storage gc full VACUUM holds the write lock across the whole O(db) rewrite with no progress — online proxy shadow disabled mid-run | storage gc 全量 VACUUM 全程持写锁且无进度:在线代理影子层被降级 #4612 给 gc 侧三处加了守卫,runRm 这处在点位审计中漏掉,仍无条件尝试 full VACUUM。
  3. 影响范围与严重程度:触发条件=sessions rm 恰好撞上另一进程活跃写入(如运行中的 proxy)。后果=共驻实例写入被阻塞整个重写时长,或竞态失败得一句泛化 busy note;删除本身(短事务)始终提交,无数据丢失。特定并发条件下的可用性降级,体验受损但有绕行(--no-vacuum)→ p2。
  4. 怎么修复的:shrink 前用与 runGc 同一事实源查活外写者,有则确定性跳过 shrink 并给出带 pid 的可操作提示,删除不受影响;把「赌锁竞争」变成「先问再动」。
  5. 是否有回归问题:无。逐项过既有行为:无写者路径(vacuum 照跑,负门钉住)、死持有者路径(负门钉住)、--no-vacuum(不变)、JSON 输出字段(预存在,未改)、删除语义(恒提交)、SQLITE_BUSY 兜底(保留)。高危模式(语义漂移/吞错/阈值时序/输出格式)逐项无命中;不涉及两种压缩模式(wire 无关)。
  6. 是否破坏缓存:不涉及(不动前缀缓存/重试/去重键)。
  7. 是否改了设计方向:否——沿用 storage gc full VACUUM holds the write lock across the whole O(db) rewrite with no progress — online proxy shadow disabled mid-run | storage gc 全量 VACUUM 全程持写锁且无进度:在线代理影子层被降级 #4612 既定守卫模式补第四处,同机制家族内收尾。
  8. 是否引入新配置:无。

收工判定:代码与测试达到可合并标准;当前两条红均为继承性 master 门禁失败(根因与修法证据已交 #4599 哨兵),待 ci-fixes 收敛后重跑转绿即可合并——本 PR 侧无需再做任何改动。

…reign writer holds index.db

After owner ruling 3a6ab62 (a live second writer never blocks startup),
per-operation guards are the sole defense against an O(db-size) exclusive
VACUUM starving a co-resident instance. storage gc kept its #4612 up-front
refusal (CLI + panel endpoint); sessions rm's default full vacuum had none —
while a proxy held index.db it could win the exclusive lock and starve that
proxy's writes across the whole rewrite.

runRm now checks store.unifiedActiveWriter() (same source of truth as
runGc's refusal) before the shrink: a live foreign writer ⇒ skip ONLY the
shrink with an actionable pid-named note; the deletion is a short
transaction and stays committed (same graceful shape as the pre-existing
SQLITE_BUSY path). No-writer and --no-vacuum paths unchanged. All four
full-VACUUM sites are now guarded: runGc, runRm, admin.ts panel endpoint
(#4612 409), store.gc.
@ranxianglei
ranxianglei force-pushed the 2026-10-31_rm-full-vacuum-live-writer-guard branch from f97b257 to 24bfdb0 Compare November 11, 2026 04:52
…ES (PR #5330 red CI)

The suite spawns one bare `node -e` dummy child (live PID holder for the
<db>.lock fixtures); SPAWN_RE matched it but it was never registered, so the
pretest tripwire aborted all 11 test shards before any test ran
(run 42962029584). Concurrency reviewed per #1248: no ports bound, no
network I/O, mkdtemp-isolated per test — same shape as the #4612/#5046-B
entries; appended last so existing slot indices are untouched.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

CI lane attempt 1/3 — conflict resolved AND the red-CI root cause found and fixed this run. Summary of everything done:

1. Conflict resolution. The branch (2026-10-31_rm-full-vacuum-live-writer-guard, head f97b257cd4) was built from the Oct-31 master snapshot plus a now-stale ci-fixes merge; master had moved 1159 commits past it. Its real content is ONE commit (8eb0e37fd9: src/storage-maintenance.ts + new tests/issue5236-rm-vacuum-writer-guard.test.ts). Rebased onto current master tip 33104b1f95: zero conflicts (master's five later commits to storage-maintenance.ts never touched the runRm vacuum region), the redundant ci-fixes merge dropped (its content is 100% inside master — verified 0 commits ahead via rev-list), then force-with-lease pushed f97b257cd4 → 24bfdb031e after confirming the remote head was untouched (no concurrent agent on the branch). GitHub now reports mergeable: true — the dirty state is cleared.

2. Red-CI root cause (found while waiting for the rerun). All 11 test shards on the rebased head failed. A single job-log fetch confirmed every shard died at the pretest tripwire before running any test:
process-spawning test files missing from LIVE_FILES in scripts/live-files.mjs: tests/issue5236-rm-vacuum-writer-guard.test.ts
This is a PR-side defect, not inherited: the PR's new suite spawns one bare node -e dummy child (live-PID holder for the <db>.lock fixtures) but was never registered in scripts/live-files.mjs.

3. Fix. Independent labeled commit 4932575239 (test(ci): register issue5236 rm-vacuum-writer-guard suite in LIVE_FILES): appended the entry last (append-only, existing slot indices untouched) with the #1248 concurrency note — no ports bound, no network I/O, mkdtemp-isolated per test, same shape as the existing #4612/#5046-B entries. Verified locally: tripwire scan now 0 offenders, LIVE_FILES existence check OK, the PR suite still 3/3 green. Plain push (remote head verified unchanged first): 24bfdb031e..4932575239. One-line conclusion posted to #4599 per the CI-coordination policy.

Live status honestly stated: the mirror check-cache still reflects the previous head (poller lag); the fresh run on 4932575239 is pending, so I am NOT asserting green yet. If the unblocked shards surface further failures, they'll be triaged by signature next run (my targeted local runs of the touched area are green; full-suite verdict belongs to remote CI). No design decisions needed, nothing escalated.


八问(中文结构化结论)

  1. 是什么问题:PR 分支与 master 冲突(dirty)无法合并;rebase 后新 head 的 CI 又全红——11 个 test 分片在跑任何测试前就退出。
  2. 什么原因导致的:① 冲突=分支基于 10-31 旧快照(master 已前进 1159 commits)+ 一个过期 ci-fixes 合并提交;② CI 红=本 PR 新增的 spawn 子进程测试套件未登记进 scripts/live-files.mjs 的 LIVE_FILES 表,pretest tripwire(tests/claude-native.test.ts linux live-E2E subtests fail/hang in shared multi-agent sandboxes #1248 门禁)逐字报错杀掉全部测试分片——是 PR 自身缺陷,非继承自 master。
  3. 影响范围与严重程度:仅本 PR 的合并性与 CI 信号;无用户侧运行时影响。严重度=阻塞合并,属流程级。
  4. 怎么修复的:rebase 到 master tip(零冲突,丢弃内容已全部并入 master 的 ci-fixes 合并,force-with-lease 推送);另以独立标注 commit 把新测试套件 append 登记进 LIVE_FILES(slot 索引不动,tests/claude-native.test.ts linux live-E2E subtests fail/hang in shared multi-agent sandboxes #1248 并发假设复核:不绑端口、无网络 I/O、每测试 mkdtemp 隔离)。
  5. 是否有回归问题:无。rebase 后对 master 的净 diff 与原修复逐字节一致(2 文件,+174/-8);LIVE_FILES 登记纯追加、只影响 CI 调度不影响运行行为。本地验证:typecheck 绿、build 绿、PR 回归套件 3/3、相邻 storage-maintenance-cli 套件 7/7。高危模式逐项过:无语义漂移、无阈值/时序/输出格式变化、无 fail-fast 变吞错。
  6. 是否破坏缓存:不涉及(纯 git 历史操作 + CI 登记,未触碰缓存/重试/游标/去重键)。
  7. 是否改了设计方向:没有。
  8. 是否引入新配置:没有。

判定:冲突已清(mergeable: true),红根因已修并推送(4932575239),等远端 CI 在新 head 上出结果;本轮无需 owner 决策。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) Status update (full run summary is in my previous comment above): conflict resolved via rebase onto master tip 33104b1f95 (zero conflicts; stale ci-fixes merge dropped) → head 24bfdb031e; red-CI root cause fixed — the PR's new spawn-based suite was missing from LIVE_FILES, registered in independent commit 4932575239 → pushed. Just re-verified against the live mirror check-cache (fetched 05:58Z, sha 4932575239): 0 failures — 12 success, 11 skipped, 7 still running (test shards in flight). Not asserting final green until those land; if any shard fails, it'll be triaged by signature next run. No owner decision needed.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b)

CI lane run — verified live, no action was needed this time. The "merge state: dirty" in the trigger is stale mirror data predating last run's rebase; live sources say otherwise:

Verified in THIS run (not from memory):

  1. Merge state — not dirty. GitHub API (single PR read): state: open, mergeable: true, mergeable_state: behind. Local fetch cross-check: origin/master tip = 33104b1f95 — exactly the PR's base sha, i.e. master has moved zero commits past my rebase target; branch head = 4932575239, remote head unchanged (no concurrent agent pushed). There is nothing to rebase against — re-running a force-push would only churn the branch. ("behind" is a stale state label from before the rebase landed.)
  2. CI on head 4932575239 — no reds. Mirror check-cache (fetched 06:31Z): 18 success / 11 skipped / 1 in_progress / 0 failures. Last run's LIVE_FILES tripwire fix is confirmed working: the test shards now actually execute tests instead of all 11 dying at pretest. The one in-flight shard is test (ubuntu-latest, 22, 3); if it fails it'll be triaged by signature next run — I'm not asserting final green until it lands.

So this run: zero git operations, zero pushes, zero new commits — the blockers were already cleared by last run (24bfdb031e rebase + 4932575239 tripwire registration), and both now verify clean against live sources. No design decisions needed, nothing escalated.


八问(中文结构化结论)

  1. 是什么问题:系统触发称 PR 与 base 冲突(dirty),需 rebase 解决。
  2. 什么原因导致的:触发信息是过期数据——上一轮已完成 rebase(force-with-lease 推送 24bfdb031e)并修掉了红 CI 根因(4932575239);本轮实时核对后确认不存在冲突。
  3. 影响范围与严重程度:无实际影响——PR 可合并性完好(mergeable: true),master 自 rebase 目标以来零前进。
  4. 怎么修复的:无需修复。本轮做的是实时核验(GitHub API 单次读 + 本地 fetch 比对 + 镜像 check-cache 单次读),确认触发条件已不成立,故不做任何 git 操作/推送(避免无谓扰动分支)。
  5. 是否有回归问题:不涉及(未触碰任何代码或历史)。
  6. 是否破坏缓存:不涉及。
  7. 是否改了设计方向:没有。
  8. 是否引入新配置:没有。

判定:PR 当前唯一待办 = 最后一个 test 分片跑完出结果(0 failed / 1 in_progress);若绿即可合并,若红下轮按签名分账。本轮无需 owner 决策。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[ework] CI fix verified green ✓

2 similar comments
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[ework] CI fix verified green ✓

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[ework] CI fix verified green ✓

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working p2 scope/storage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant