Skip to content

handleFileEvent 的 catch 对 loader 读/解析失败不可达 —— 坏文件被当作 data: null 广播出去 #5228

Description

@os-zhuang

发现于 #5218(FS 监听改动不失效 listCache)实现期,不在该单范围内,故单独立卡。

现象

packages/metadata/src/node-metadata-manager.tshandleFileEvent() 在非 deleted 事件上这样保护自己:

    let data: any = undefined;
    if (eventType !== 'deleted') {
      try {
        data = await this.load(type, name, { useCache: false });
      } catch (error) {
        this.logger.error('Failed to load changed file', undefined, { filePath, error: ... });
        return;                      // ← 想表达「读不出来就别广播」
      }
    }

这个 catchloader 的读/解析失败不可达load() 只是 loadDiagnosed() 的取值包装:

  async load(type, name, options) {
    return (await this.loadDiagnosed(type, name, options)).data;   // metadata-manager.ts
  }

loadDiagnosed() 按 ADR-0110 D3 的设计吞掉 loader 抛出的异常,把它记进 errors[] 并返回 { data: null, degraded: true }。于是:FilesystemLoader.load() 对坏 JSON 确实 throw(它自己先 logger.errorthrow error),但那个异常在 loadDiagnosed 里就被吃掉了,load() 返回 null 而不是抛出 —— handleFileEventcatch 永不进入,return 永不执行。

结果是 data: null 一路进到广播出去的事件里:

    const event: MetadataWatchEvent = { type: eventType, metadataType: type, name, path: filePath, data, ... };
    this.notifyWatchers(type, event);

即:「这个文件我读不出来/解析不了」与「这个文件合法地什么都没有」在事件上完全同形,正是 loadDiagnosed 存在的理由所要区分的那两件事(ADR-0110 D3:miss 与 outage 是意义相反的两个事实),而这个调用点用的恰好是丢掉该区分的那个变体。

复现

已由 #5218 的回归测试间接钉住(node-metadata-manager-fs-invalidation.test.ts 的 “still invalidates when the changed file cannot be parsed”):写入一个 { not jsonview/v_broken.json 并投递 added 事件,handleFileEvent 提前返回 —— 该用例先前按「会提前返回」写,实测失败,才发现此事。

后果(为什么标 finding 而不是缺陷)

目前没有仓内消费者读这个 data,所以今天没有用户会踩到:

  • ObjectQLPluginsubscribe('object', …)(packages/objectql/src/plugin.ts :690-724)在收到事件后是回头 metadataService.get('object', name) 重读的,不看 event.data;
  • applyRepoEvent() 干脆显式写 data: undefined,并在注释里记着 “HMR consumers don't read data so this is fine for M0”。

所以这是一条休眠缺陷:契约上已经在说谎(事件声称该元数据为空),但当前无人据此下判断。一旦将来有消费者(HMR 前端、Studio 预览、任何想省一次重读的订阅者)开始信任 event.data,一个开发期的拼写错误就会被当作「作者把这个视图清空了」。同理,那个 logger.error('Failed to load changed file') 分支从不打印,坏文件在监听路径上只剩 FilesystemLoader 自己那行日志。

建议修法(留给分诊裁决)

两条方向,倾向 A:

  • A. 该调用点改用 loadDiagnosed(),按 degraded 分流:degraded === true 时走原本 catch 想走的路(记 error 日志并 return,不广播一个内容可疑的事件);干净的 miss 仍按现状处理。与 ADR-0110 D3 的既有分法一致,也让那条 logger.error 真正可达。
  • B. 只删掉这段 try/catch,承认它不可达。成本是把「坏文件广播成 data: null」固化为契约,与 A 相比是把问题记账而不是修掉。

另注:#5218 落地后,坏文件事件仍会失效缓存 —— 那是对的(loadMany 会跳过读不出的文件,清单确实变了),与本单无关。

关联

packages/metadata/src/node-metadata-manager.ts handleFileEvent()packages/metadata/src/metadata-manager.ts load() / loadDiagnosed()、ADR-0110 D3、#5218(发现来源)、#5108(DatabaseLoader 曾把读失败吞成 [] 的同类问题)。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions