Skip to content

storage: notice-store (#5548) pre-hydration receipts block the one-time legacy→kv migration #5761

Description

@ranxianglei

现象

PR #5757 的自查发现(host-plugin-notices.json 迁移到 sqlite 时):升级通知回执的 kv 迁移存在一个「先 dismiss、后 hydration」的时间窗问题——src/notice-store.ts(#5548 引入)里 addDismissedKeyInMemory 会惰性创建缓存,而 hydrate 顶部是 if (cache !== null || hydrating) return:

  • 如果进程启动期间有一次 dismiss/receipt 操作抢在 cli boot hydration 之前落地,hydration 会直接 bail——该进程的 legacy JSON → kv 一次性迁移被永久跳过(每次启动文件都要重导),且 sibling lane 已写入的行在本进程内不可见;
  • 变体:dismissal 恰好落在 hydrate 的 await 间隙时,会被最后的 cache = merged 整体覆盖,直到重启。

#5757 已用同一模块 host-plugin-notice.ts 验证并修复了这个模式(union 进已有 lazy cache + 独立的 hydrated latch,见 commit 4d74613)。notice-store 是同一套模板的双胞胎,缺同样的修复。

影响

回执数据本身是 advisory(丢一次只是多推一条升级通知),所以定级不高;但 #5548 之后的「legacy-only instance / 一次性导入」载具语义在窗口内名不副实。

建议修法(同 #5757)

hydrate 不再因 cache 非空 bail,改为把 kv rows union 进现有 cache;加独立 hydrated latch(区别于 lazy-init cache);final assignment 前合并 hydrate 期间新增的 key。复用 tests/host-plugin-unified-store.test.ts 里刚加的回归用例形状即可。

Activity

  1. ranxianglei commented on Nov 5, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    查重: 不重复。 open+closed 检索无同机制条目;与 #5757(六个 JSON sidecar 迁 kv,在途 PR)是「同模式、不同模块」的关系——#5757 修的是 host-plugin-notice.ts(commit 4d74613,尚未合入 master),本单是它的双胞胎 notice-store.ts 上缺的同一处修复,issue 正文已自述该关系。

    审核三步(动手前):

    1. 复现/查证 — 成立。 master src/notice-store.ts:90:if (cache !== null || hydrating || !unifiedReceiptsActive()) return; —— addDismissedKeyInMemory(:128-132)首次 dismiss 会惰性建 cache,cli boot 的 void hydrateNoticeReceipts()(src/cli.ts:965)若晚于任何一次回执落地即永久 bail,legacy JSON→kv 一次性迁移跳过。变体也成立:hydrate 内部 await 间隙落地的 dismiss 会被 :120 的最终 cache = merged 整体覆盖。
    2. 层次判断: 根因是 hydrate 把「lazy cache 已存在」与「已完成 hydration」混在一个信号里裁决 bail——缺独立的 hydrated latch;不是迁移逻辑本身有错。
    3. 方案评估: issue 建议修法正确且已被 [PR] storage: move six JSON sidecar states onto the unified sqlite store (#5753) #5757 在同一模板上验证过(union 进现有 lazy cache + 独立 latch + final assignment 前合并间隙新增)。照搬该模式移植到 notice-store。

    两点实现说明(与 #5757 的差异,均有意):

    标签: bug / scope/storage / p3(缺陷;统一存储层;回执是 advisory 数据、触发窗口窄——启动期一次 dismiss 抢跑 hydration,影响是多推一条通知+每次启动重导文件,定级不高与正文一致)。

    接下来:基于 origin/master 开分支实现 + 两个回归用例(复用 #5757 新增用例形状:pre-hydration dismiss 不挡迁移 / hydrate await 间隙的 dismiss 存活),定向测试过后进 PR(Closes 上游 #5761)。

  2. ranxianglei commented on Nov 5, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    **修复完成 → PR: https://github.com/ranxianglei/billion-context/pull/5768**(分支 2026-11-06_notice-store-hydration-race,基于 origin/master,Closes #5761)

    八问:

    1. 是什么问题:启动期一次回执落地抢在 cli-boot hydration 之前时,该进程的 legacy JSON→kv 一次性迁移被永久跳过(每次启动重导文件),且 sibling lane 已写入的 kv 行本进程内不可见直到重启;变体是落在 hydrate await 间隙的回执被最终 cache = merged 整体覆盖。
    2. 什么原因导致的:hydrateNoticeReceipts 把「lazy cache 已存在」当「已完成 hydration」裁决 bail(src/notice-store.ts:90 旧门 cache !== null || ...),而首次 dismiss 会惰性创建同一个 cache——两种状态混在一个信号里,缺独立 hydrated latch;最终赋值又排在全部 await 之后,间隙新增必被覆盖。
    3. 影响范围与严重程度:统一存储实例(shadow/full);触发窗口窄(进程启动→hydration 完成之间需恰好有一次 dismiss);程度低——回执是 advisory 数据,最坏是多推一条升级通知 + 每次启动重导文件的无效功。与正文定级一致(p3)。
    4. 怎么修复的:移植 [PR] storage: move six JSON sidecar states onto the unified sqlite store (#5753) #5757 线在 host-plugin-notice.ts(commit 4d74613)已验证的同模式:hydrate 不再 bail,union 进现有 lazy cache;独立 hydrated latch 区别于 lazy-init;live 视图快照移到 publish 紧前、中间零 await(单线程保证间隙新增存活)。基于 master 既有 API(unifiedKvActive()+getStore())实现,不依赖尚未合入的 kvStore() 探针;[PR] storage: move six JSON sidecar states onto the unified sqlite store (#5753) #5757 分支不触碰这两个文件,先后合入均无冲突。
    5. 是否有回归问题:原 5 用例全过;相邻通知面 5 个套件 32/32 绿;typecheck+build 干净。一处有意行为变更(PR 已披露):hydrate 失败时旧代码仍发布空/半截 map(文件回退失效且永不重试),新代码保持 null→文件回退存活、下次调用重试——advisory 面、严格更正确。无阈值/时序/输出格式漂移。
    6. 是否破坏缓存:不涉及——只动回执存储 advisory 面,前缀缓存/重试/增量游标/去重键均未触碰。
    7. 是否改了设计方向:不涉及——Unified Announcement Panel Design (Approved): Built-in declarations + local JSON + remote GitHub raw — a single renderer | 公告面统一设计(已批准):内置声明 + 本地 JSON + 远程 GitHub raw,三来源一套渲染器 #5548 载具规则不变,仅按既定模式补齐双胞胎模块。
    8. 是否引入新配置:无。

    回归用例有效性(A/B 实测):两个新用例复用 #5757 形状——pre-hydration dismiss 不挡迁移 / hydrate 间隙 dismiss 存活(后者对 list 和 set 双向加门,防止回执自身的持久化写在 list 解析前落地、经 kv 重推把丢失掩盖掉)。stash 掉修复后跑旧代码:两个新用例全红、原 5 绿;恢复修复后 7/7 绿。

    等 CI 全局把关;CI 红了会按 #4599 协调流程处理。

  3. ranxianglei commented on Nov 5, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    CI 全绿:PR #5768 共 30 项 check——test 全分片(ubuntu/windows × node 22/24 × 3 shard)、static-gates、kernel-guard、test-import-graph、pi-subagents-guard、docs-sync、mysql-contract 全部 success;skipped 均为环境门控的 e2e 车道(正常)。

    收工判定:修复 + 回归钉已交付,CI 全局把关通过,等 owner review/merge(https://github.com/ranxianglei/billion-context/pull/5768)。与在途 #5757 无文件交集,合入顺序任意。

  4. ranxianglei commented on Nov 12, 2026

    @ranxianglei
    OwnerAuthor

    (已收)本单已归入根因聚合 issue(token 见聚合单 body),机器人已停止在本单上的工作,修复走聚合 issue 统一出 PR;本单保留不关,修复验证后统一关闭。(聚合单:[Root Cause] storage (6 items),GitHub #6453)

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions