Skip to content

Navigation Menu

Sign in
Sign up

metro-file-map: Watch directories before listing them in fallback (Linux + Windows) watcher - #1907

Open
robhogan wants to merge 1 commit into
main from
pr1907
Open

metro-file-map: Watch directories before listing them in fallback (Linux + Windows) watcher #1907
robhogan wants to merge 1 commit into
main from
pr1907

Conversation

@robhogan

@robhogan robhogan commented Sep 7, 2026
edited
Loading

Copy link
Copy Markdown
Collaborator

Summary:
FallbackWatcher starts an fs.watch on each directory from recReaddir's dirCallback, and the crawl calls that after readdir has returned.

Anything written into a directory between the two appears in neither the listing nor the watch, so it stays invisible until the next full crawl. There's no recovery on Linux or Windows.

This is expo/expo#48950 - npx expo install against a running dev server leaves the new modules unresolvable until restart, which is a lot more painful than it sounds now that agents routinely run Metro in a VM. @brentvatne diagnosed it in expo/expo#49363, with a fix in Expo's fork that we can hopefully replace with this one.

Now that we own the crawl (#1906), the fix is very simply to call dirCallback before readdir instead of after, which also moves it inside recReaddir's existing try.

Separate issues not fixed here, both pre-existing and both covered by expo/expo#49363 :

  • An fs.watch that emits error is never removed from #watched. Node emits no close after error, so the path can never be re-watched, and #stopWatching waits on a close that will not arrive.
  • On win32, fs.watch can report an event with no filename. #detectChangedFile drops it when #dirRegistry[dir] is empty, which is exactly the state a newly watched directory is in.

Changelog:

 - **[Fix]**: `FallbackWatcher` no longer misses files written to a directory while it is being crawled

Test Plan:

yarn jest packages/metro-file-map
yarn flow check
yarn lint

New watchers/__tests__/FallbackWatcher-test.js asserts the ordering directly, that fs.watch precedes readdir for the same directory, on the initial crawl and on a directory created while watching, plus that a directory whose fs.watch throws is skipped without failing the crawl. All three fail on the parent commit and pass here.

The race can't be asserted behaviourally on macOS, because FSEvents delivers with a latency window and a watch started immediately after a write still reports it:

$ node -e "fs.writeFileSync(d+'/raced.js', ''); fs.watch(d, (e, f) => console.log(e, f))"
rename raced.js

That's why the issue is Linux/Windows only, and why the assertion is on the ordering rather than on a missed event. Metro has no Windows coverage for this backend, so the win32 path is unexercised either way.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 7, 2026
@robhogan robhogan changed the title (削除) metro-file-map: Watch directories before listing them (削除ここまで) (追記) metro-file-map: Watch directories before listing them, fix dropped files on (eg) npm installs (追記ここまで) Sep 7, 2026

robhogan commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

NB: This is stacked on #1906. I assume shipit doesn't support stacks (?), but this should automatically rebase against main and be importable once D119076649 lands.

@robhogan robhogan changed the title (削除) metro-file-map: Watch directories before listing them, fix dropped files on (eg) npm installs (削除ここまで) (追記) metro-file-map: Watch directories before listing them (追記ここまで) Sep 7, 2026
robhogan changed the base branch from pr1906 to main September 7, 2026 15:06
Summary:
`FallbackWatcher` starts an `fs.watch` on each directory from `recReaddir`'s `dirCallback`, and the crawl calls that after `readdir` has returned.
Anything written into a directory *between the two* appears in neither the listing nor the watch, so it stays invisible until the next full crawl. There's no recovery on Linux or Windows.
This is expo/expo#48950 - `npx expo install` against a running dev server leaves the new modules unresolvable until restart, which is a lot more painful than it sounds now that agents routinely run Metro in a VM. @brentvatne diagnosed it in expo/expo#49363, with a fix in Expo's fork that we can hopefully replace with this one.
Now that we own the crawl (#1906), the fix is very simply to call `dirCallback` before `readdir` instead of after, which also moves it inside `recReaddir`'s existing `try`.
Separate issues not fixed here, both pre-existing and both covered by expo/expo#49363 :
- An `fs.watch` that emits `error` is never removed from `#watched`. Node emits no `close` after `error`, so the path can never be re-watched, and `#stopWatching` waits on a `close` that will not arrive.
- On win32, `fs.watch` can report an event with no filename. `#detectChangedFile` drops it when `#dirRegistry[dir]` is empty, which is exactly the state a newly watched directory is in.
Changelog:
```
 - **[Fix]**: `FallbackWatcher` no longer misses files written to a directory while it is being crawled
```
Test Plan:
```
yarn jest packages/metro-file-map
yarn flow check
yarn lint
```
New `watchers/__tests__/FallbackWatcher-test.js` asserts the ordering directly, that `fs.watch` precedes `readdir` for the same directory, on the initial crawl and on a directory created while watching, plus that a directory whose `fs.watch` throws is skipped without failing the crawl. All three fail on the parent commit and pass here.
The race can't be asserted behaviourally on macOS, because FSEvents delivers with a latency window and a watch started immediately after a write still reports it:
```
$ node -e "fs.writeFileSync(d+'/raced.js', ''); fs.watch(d, (e, f) => console.log(e, f))"
rename raced.js
```
That's why the issue is Linux/Windows only, and why the assertion is on the ordering rather than on a missed event. Metro has no Windows coverage for this backend, so the win32 path is unexercised either way.

robhogan commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

(Rebasing wasn't automatic because GH sees a ship-it merged PR as closed and assumes the stack is broken. Anyway, did it the old-fashioned way)

@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Sep 7, 2026
@robhogan robhogan changed the title (削除) metro-file-map: Watch directories before listing them (削除ここまで) (追記) metro-file-map: Watch directories before listing them in fallback (Linux + Windows) watcher (追記ここまで) Sep 7, 2026

robhogan commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

@javache / @GijsWeterings / @vzaidman this is quite a high impact fix, it turns out. Metro on Linux outside Meta (eg, agentic dev) gets in a real mess during package installs without this.

Meta doesn't use FallbackWatcher so there should be no risk there.

Thanks 🙏

vzaidman reacted with heart emoji

meta-codesync Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

@vzaidman has imported this pull request. If you are a Meta employee, you can view this in D119187785.

robhogan reacted with heart emoji

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

Reviewers

No reviews

Assignees

No one assigned

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /