Repository navigation
fix: back the file watcher with an mtime poll net (pollIntervalMs, default 5s) - #6
Merged
Merged
Conversation
…fault 5s) FSEvents can drop events under load — observed 2026-10-04 on macOS CI, where no event arrived within 60 s and the hot reload was missed until restart. The event watcher stays the fast path; an unref'd low-frequency poll (one stat per interval) compares existence+mtime against the last applied signature and schedules the debounced reload on divergence, so a dropped event degrades to a few seconds' delay. - New pollIntervalMs option (default 5000, 0 disables, gated by watch). - Signature taken BEFORE the read: a mid-read write then records the older signature, yielding one extra diffed reload instead of a miss. - The tick skips while a debounce is pending or a reload is in flight — re-arming would starve the 300 ms debounce (poll interval < debounce) and pin the event loop forever. - Poll path is covered by a test that stubs fs.watch via t.mock.module; the unit runner now passes --experimental-test-module-mocks (available since node 22.3, still gated in 24). Mocking node:fs must enumerate the used exports — spreading the whole namespace dies on the getter-only 'constants' export. - READMEs document the option; CHANGELOG [Unreleased] updated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
今天 CI 抖动调查的终点:热加载测试在 macOS 上以 15s 上限失败 4 次后,把上限提到 60s 依然超时(
darwin, gave up after 60008 ms)。这不是延迟而是事件丢失——FSEvents 在负载下内核队列溢出时会直接丢事件,Node 的fs.watch不会把溢出翻译成文件事件。这是插件的产品级可靠性问题:真实用户的 macOS 机器高负载时,改secrets.env也可能永远等不到热加载。What
事件监听保留为快速路径(约 1 秒生效的卖点不变),新增低频 mtime 轮询兜底(默认 5s,一次 stat,unref'd):签名(存在性+mtime)与上次成功应用的基线不一致就触发防抖 reload。丢事件的后果从永久错过直到重启降级为最多慢一个轮询间隔。
实现要点(建议 review 时重点看)
fileSignaturebeforereadSecrets):读到一半发生写入会记录旧签名,下一次 tick 补一次多余的 diffed reload(无害),反向则会漏更新t.mock.module把fs.watch桩成哑对象,隔离验证 poll 路径;为此单测入口加了--experimental-test-module-mocks(22.3+ 可用,24 仍需 flag;CI 矩阵会验证 22.18)。踩到的坑:node:fs命名空间有 getter-only 导出(constants),mock 时必须枚举用到的导出,不能整体展开watch: false语义不变(完全关闭实时更新);poll 随watch门控Docs
pollIntervalMs行 + 热更新章节一句兜底说明[Unreleased]新增 Fixed 条目