rescript.json. #8839Keep watching a source folder when an overlapping one is removed
Rust rewatch (rescript-rust) watches each source folder separately, and source folders may overlap. On a full rebuild it unwatched the paths that went away and registered only the new ones. With inotify on Linux, notify keeps one watch per directory, shared by every registered path that reaches it, so unwatching a removed recursive folder also dropped a folder that is still configured, and edits there were no longer compiled. Outside macOS, every configured path is now registered again after the stale ones are unwatched; registering an already watched directory only updates its watch, so unchanged watches have no gap. macOS keeps registering only the new paths, because FSEvents restarts its event stream on every watch change and its watches don't depend on each other.
Watches are also registered non-recursive first. notify keeps one recursive flag per directory, set by the last registration that reaches it, so a non-recursive source folder inside a recursive one could clear the flag, and folders created there later weren't watched.
Example output
rescript.json of a package, while `rescript-rust watch` runs:
"sources": [{"dir": "src", "subdirs": true}, "src/retained"]
changed to
"sources": ["src", "src/retained"]
then src/retained/Retained.res is edited. Before the change, a file is
also created in a new folder src/retained/nested.
The edit isn't compiled (Linux). The new folder's file is missed in mostruns.
Both are compiled.Source rewatch/tests/watch/08-watch-overlapping-source-removed.sh, run against master's rescript-rust (the edit fails 2 of 2), this branch without the registration order (the new folder fails 7 of 8) and this branch (passes 8 of 8).
Review
- Is OCaml rewatch (
rescript) affected? No. It resolves every watched folder to its canonical directory, keeps one libuv handle per directory, and on a refresh closes only the directories no longer wanted. The new test runs against both executables in the shared suite and passes forrescripton master already. - Why not keep the incremental update everywhere? Watches can share inotify state through overlapping folders,
..and symlinks, including directory symlinks inside a recursive folder; working out every way an unwatch can drop another watch took several review rounds. Registering every path again needs no such reasoning and costs no gap on inotify. - Can an edit be missed while the watches are updated? Only after a config change that removes a recursive folder overlapping a kept one: the kept folder's watch is dropped by the unwatch and restored when every path is registered again, so an edit in between is picked up only with the next change. Ordinary full rebuilds unwatch nothing. Closing that window would need a rescan after registration, which isn't worth it for the Rust fallback.
- Why does macOS keep the incremental update? #8730 introduced it because FSEvents restarts its event stream on every watch or unwatch call and loses the events in between. FSEvents watches are independent paths, so an unwatch never drops another watch there.
- No effect
- Output: unchanged
- Tests
- rewatch integration
rewatch/tests/+ watch/08-watch-overlapping-source-removed.sh~ suite.sh
- Changelog entry matches the line the diff adds
- No committed generated JS changes
4 files ยท +173 โ6 ยท changelog 1 ยท rewatch 1 ยท tests 2
| A | changes/rewatch-overlapping-unwatch.fix.md | +1 | โ0 |
| M | rewatch/src/watcher.rs | +36 | โ6 |
| M | rewatch/tests/suite.sh | +1 | โ0 |
| A | rewatch/tests/watch/08-watch-overlapping-source-removed.sh | +135 | โ0 |