fix(project): Add polling file watcher for containers - #1511
Draft
RandomByte wants to merge 1 commit into
Draft
Conversation
RandomByte
force-pushed
the
fix/fs-watch-in-containers
branch
6 times, most recently
from
August 12, 2026 16:15
8b901c7 to
00ad237
Compare
RandomByte
force-pushed
the
fix/fs-watch-in-containers
branch
4 times, most recently
from
August 13, 2026 14:27
e43cbec to
209649c
Compare
On a bind-mounted volume inside a container, Linux inotify reports the
container's own writes but not writes made to the same volume from
outside the container. Podman is the common case. The incremental build
derives "what changed" solely from @parcel/watcher events, so those
missed events break rebuilds and live reload with no error to catch.
Add a fileWatcher.js module that all three watcher consumers call instead
of @parcel/watcher directly. It selects a backend once per process and
exposes a subscribe() matching @parcel/watcher's exact contract, so a
caller cannot tell which backend is active. The native backend stays
@parcel/watcher.
Both backends are imported lazily, only when selected. @parcel/watcher
resolves a native binding at load time and throws when the platform's
prebuilt binary is not installed (for example on an unsupported platform
or after an install with --omit=optional), so a static import would break
every consumer before the polling fallback could run. Loading it on demand
contains that failure: when the native backend is selected but cannot
load, subscribe() logs a warning and falls back to polling, which needs no
native code.
Parcel offers no polling subscription (its brute-force backend is
query-only), so the polling backend lives in a separate pollingWatcher.js
that fileWatcher.js imports lazily. It walks the tree and diffs an
mtimeMs+size snapshot every 250 ms (rescheduling itself after each walk so
a slow crawl cannot overlap the next), emitting the same {type, path}
events the native backend would. Errors flow through the callback so each
consumer's existing recovery path fires unchanged. The 250 ms interval
stays below WATCHER_BURST_SETTLE_MS (550 ms) so downstream event batching
still holds. Ignore globs reuse micromatch (already a direct dependency)
and prune ignored directories so node_modules is never crawled.
The backend is chosen once per process and memoized. UI5_WATCH_MODE
forces it (polling or native); otherwise polling is the default inside a
container and the native backend elsewhere. A container is detected from
the runtime marker files (/.dockerenv, /run/.containerenv) and PID 1's
cgroup. Detection is a heuristic: it can miss an exotic sandbox or match
a host whose PID 1 cgroup carries those names, so UI5_WATCH_MODE is the
escape hatch either way, documented under Troubleshooting.
WatchHandler, ProjectDefinitionWatcher, and projectGraphSettleWatcher
swap their parcelWatcher.subscribe call for fileWatcher.js; callback
bodies, ignore globs, and settle windows are untouched.
Fixes: #1479
RandomByte
force-pushed
the
fix/fs-watch-in-containers
branch
from
August 13, 2026 14:52
209649c to
a2178cd
Compare
Member
Author
|
@matz3 kindly re-test in podman. Hopefully the new environment detection will choose the polling backend automatically now. |
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.
What
Add a polling file watcher for the incremental build. It is used automatically inside containers and can be forced anywhere with
UI5_WATCH_MODE.Why
On a bind-mounted volume inside a container, Linux inotify reports the container's own writes but not writes made to the same volume from outside the container. Podman is the common case. The incremental build derives "what changed" solely from
@parcel/watcherevents, so those missed events break rebuilds and live reload, with no error to catch. Polling reads the tree directly, so it sees every change regardless of where it originated.How it works
A new module,
fileWatcher.js, selects the backend and exposes asubscribe()matching@parcel/watcher's exact contract, so a caller cannot tell which backend is active. All three watcher consumers (WatchHandler,ProjectDefinitionWatcher,projectGraphSettleWatcher) call it instead of@parcel/watcherdirectly. The native backend stays@parcel/watcher; the polling backend lives in a separatepollingWatcher.jsthatfileWatcher.jsimports lazily, only when polling is selected, so the module stays off the common native path.Both backends are imported lazily.
@parcel/watcherresolves a native binding at load time and throws when the platform's prebuilt binary is not installed (for example on an unsupported platform or after an install with--omit=optional), so a static import would break every consumer before the polling fallback could run. Loading it on demand keeps that failure contained: when the native backend is selected but cannot load,subscribe()logs a warning and falls back to polling, which needs no native code.The backend is chosen once per process and memoized:
UI5_WATCH_MODE=polling|nativeforces it./.dockerenv,/run/.containerenv) and PID 1's cgroup.Container detection is a heuristic: it can miss an exotic sandbox or match a host whose PID 1 cgroup carries those names, so
UI5_WATCH_MODEis the escape hatch either way.The polling path walks the tree and diffs an mtimeMs+size snapshot every 250 ms (rescheduling after each walk so a slow crawl cannot overlap the next), emitting the same
{type, path}events the native backend would. Errors flow through the callback so each consumer's existing recovery path fires unchanged. The 250 ms interval stays belowWATCHER_BURST_SETTLE_MS(550 ms) so downstream event batching still holds. Ignore globs reuse micromatch and prune ignored directories sonode_modulesis never crawled.Configuration
UI5_WATCH_MODE=polling|nativeforces the file watcher backend.Documented under Troubleshooting.
Fixes: #1479