Repository navigation
fix: recover events macOS drops when a watcher starts or stops - #354
Conversation
🦋 Changeset detectedLatest commit: 71146c4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Merging this PR will not alter performance
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #354 +/- ##
==========================================
+ Coverage 92.45% 92.56% +0.11%
==========================================
Files 8 8
Lines 1338 1359 +21
Branches 396 402 +6
==========================================
+ Hits 1237 1258 +21
Misses 95 95
Partials 6 6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
libuv shares one FSEventStream between all directory watchers of a process and recreates it from "now" whenever one starts or stops, so events it had not delivered yet are lost. Every directory watcher now rescans once that settles, which reports the lost removals and changes.
Polling reports the removal before the second watcher starts; the handler then threw on the missing watcher, and the one started after it kept jest alive.
WalkthroughThe event source now schedules rescan events after watcher starts or stops. Rescanning is enabled by default on macOS and can be enabled on other platforms with an environment setting. DirectoryWatcher responds to eligible rescan events with a non-initial scan. The change also adds event type declarations, a test for reporting a removed file, and a patch changeset. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to
|
There was a problem hiding this comment.
🧹 Nitpick comments: 1
lib/watchEventSource.js: 1
64-64: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueIsolate listener errors in the rescan loop.
w.emit("rescan")runs inside asetTimeoutcallback. A throwingrescanlistener propagates out of the timer callback. Node then raises an uncaught exception. The loop also stops, so the remaining watchers get norescan. The current listener inDirectoryWatchercallsdoScan, which defers its work withprocess.nextTick, so the risk is low today. A try/catch per watcher would make the loop robust if listeners change.Proposed fix
- for (const w of activeWatchers) w.emit("rescan"); + for (const w of [...activeWatchers]) { + try { + w.emit("rescan"); + } catch (err) { + process.nextTick(() => { + throw err; + }); + } + }
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Organization UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
be95a562-c1ce-48ba-8cf7-8009e63e4f1d
📒 Files selected for processing: 5
.changeset/macos-lost-events.mdlib/DirectoryWatcher.jslib/watchEventSource.jstest/RestartRescan.test.jstypes/watchEventSource.d.ts
Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.
Draft: investigation first, fix to follow in this PR.
webpack's macOS CI loses a file deletion between watch builds (webpack/webpack#22681,
restored-commonjs-reexport): no build ever reports it, and the next build reuses the stale module. On Linux the same failure appears only when the deletion event is dropped, never when it is late.Suspected cause: on macOS, libuv (
src/unix/fsevents.c) shares one FSEventStream between every directoryfs.watchhandle in the process. Adding or removing any handle destroys the stream and creates a new one fromkFSEventStreamEventIdSinceNow, so events not yet delivered are lost. watchpack adds and removes handles whenever the set of watched directories changes.This commit adds a temporary
fsevents-probejob (macOS, Node 20/22/24) that deletes a watched file, starts another watcher 0–100 ms later, and counts lost deletions, with plainfs.watchand with watchpack. It will be removed once the numbers are in.🤖 Generated with Claude Code
https://claude.ai/code/session_017TkBWTk3wcyUcJYDp5ZhaP
Generated by Claude Code
Summary by CodeRabbit