Watcher rooted at the parent of the workspace folder causes a recursive crawl of sibling trees (inotify exhaustion)
bugplug-in systemfile-watchers
### Bug Description:
Theia's backend file watcher ignores the `recursive` flag and **always watches recursively** (`DiskFileSystemProvider.watch` forwards only `{ ignored }` to the watcher server, and `@parcel/watcher` always subscribes recursively). As a result, a **non-recursive** watch rooted at a directory **outside** the workspace is silently turned into a full recursive crawl of that directory's entire subtree.
In practice this is triggered by language servers. `redhat.java` / JDT-LS registers a watcher rooted at the **parent** of the workspace folder — `RelativePattern(parentDir, folderName)`, with no globstar in the pattern — purely to detect deletion of the workspace folder itself. `ExtHostFileSystemEventService.ensureWatching` computes `recursive = pattern.includes('**')`, so this arrives as a **non-recursive** `$watch` on `parentDir`. Because the backend watches recursively anyway, Theia ends up recursively crawling every **sibling** of the workspace folder (other projects, build/output directories, …) — thousands of inodes the workspace does not own — and can exhaust `fs.inotify.max_user_watches` (`ENOSPC` / "Unable to watch for file changes in this large workspace"), even on a small workspace and even with `files.watcherExclude` configured (the excludes cannot match, since the watch root is outside the workspace).
This is related to #17596 / #17598 (which bounded excludes for in-workspace and plugin watchers) but is a **distinct driver**: the problematic watch is rooted *outside* the workspace, so `files.watcherExclude` cannot help. It is likely the same root cause as #16747.
### Possible fixes
1. **Targeted (small).** In `MainFileSystemEventService.$watch` (where plugin watchers enter), do not register a **non-recursive** watch whose root is a strict **ancestor** of a workspace root. The backend cannot honor non-recursive anyway, and `files.watcherExclude` cannot bound an out-of-workspace root, so skipping is the only effective mitigation. Trade-off: loses workspace-folder-self-deletion detection for such watchers (an edge case the editor surfaces through other means).
2. **Root cause (larger).** Honor the `recursive` flag end-to-end by adding a non-recursive watcher (e.g. Node `fs.watch(dir, { recursive: false })`) so a non-recursive request places a single watch on the directory instead of recursively crawling it — mirroring VS Code, which uses a dedicated non-recursive watcher distinct from its recursive (parcel) one. This requires threading `recursive` through the watcher protocol/provider and a new backend watcher with its own event mapping and cross-platform handling.
### Steps to Reproduce:
1. On Linux, open a workspace folder that sits alongside large sibling directories under a shared parent (e.g. `…/projects/my-app`, with other large projects under `…/projects`).
2. Use an extension that registers a watcher rooted at the parent of the workspace folder — e.g. the Red Hat Java extension with JDT-LS in standard (non-`LightWeight`) mode.
3. Observe (e.g. via the `inotify-consumers` script) that the watch count grows far beyond the workspace's own size — covering sibling trees — and can reach `fs.inotify.max_user_watches` with `ENOSPC` and the "Unable to watch for file changes in this large workspace" message, regardless of `files.watcherExclude`.
### Additional Information
- Setting `java.server.launchMode: LightWeight` masks the issue (JDT-LS stops registering the watcher) but is only a workaround.
- The watch-*count* impact is Linux/inotify-specific; the over-watching itself is cross-platform.
- Operating System: Linux
- Theia Version: master
关闭于 2026-06-25 0 条评论