Skip to content

deps: libuv: cherry-pick fsevents path cleanup - #67293

Open
fatihguzeldev wants to merge 1 commit into
nodejs:mainfrom
fatihguzeldev:libuv-fsevents-leak
Open

fatihguzeldev wants to merge 1 commit into
nodejs:mainfrom
fatihguzeldev:libuv-fsevents-leak

Conversation

@fatihguzeldev

@fatihguzeldev fatihguzeldev commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

cherry-picks libuv/libuv#5283 to fix the
macos fs.watch() memory leak. the fix is merged upstream but hasn't
been released yet.

fixes: #67284

Release temporary path allocations after each FSEventStream rebuild.
Include the upstream regression test for allocation balance and event
delivery.

Refs: libuv/libuv#5283
Fixes: nodejs#67284
Signed-off-by: Fatih Güzel <fatihguzeldev@gmail.com>
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/security-wg

@nodejs-github-bot nodejs-github-bot added libuv Issues and PRs related to the libuv dependency or the uv binding. needs-ci PRs that need a full CI run. labels Nov 6, 2026
Brooooooklyn added a commit to oxc-project/oxc-node that referenced this pull request Nov 12, 2026
…ced (#906) (#921)

Closes #906.

## Cause

On macOS, libuv uses one FSEvents stream for all directory watches.
Every `fs.watch` open or close rebuilds that stream and leaks memory for
every watched folder (libuv/libuv#5283, not in a Node release yet;
nodejs/node#67293). `oxnode watch` reopened a folder's watch whenever
the folder got a new inode, so replacing folders made memory grow
without limit.

## Fix (macOS only)

FSEvents watches the path, not the inode, so a folder's watch now
follows its path:

```
folder replaced, same real path   → look at its files again, no reopen
folder missing                    → check its files once, keep the watch for `linger` (60 s)
watch no longer needed            → keep it for `linger`, then close
new real path (link moved)        → reopen
```

Linux and Windows behave as before (`linger` is 0 there, and a new inode
still means a new watch).

## Results

The issue's repro: 400 folders, each replaced with
`mv`/`mkdir`/`mv`/`rmdir` every round, measured with `vmmap`.

| | footprint | watches opened |
|---|---|---|
| main, Node 26, 5 rounds | 25 → 131 MB, still growing | 401 → 2915 |
| this PR, Node 26, 10 rounds | about 36–40 MB, flat | 401 → 402 |

Node 22 and 24 give the same shape: on main memory grows to 117–143 MB,
with this PR it stays flat.

## Tests

New macOS cases in `watch-units.spec.ts` use a fake `fs.watch`, so they
run on every OS:
- a folder replaced by `rm -rf` + `mkdir` or by a rename keeps its watch
- a missing folder: its files are reported gone, its watch closes after
`linger`, and the parent's watch closes once the folder is back
- a parent renamed away, and a walked folder removed with its
subfolders: the files in them are reported gone
- a watch reached through a link that is repointed, or a folder moved
away and then linked back: the watch is reopened
- a watch no longer needed lingers, serves again, then closes

There is also one test on real FSEvents (macOS only): 40 folders
replaced 3 times open no new watch.

## Known trade-off

A folder deleted for good keeps its watch for 60 s, where before it
closed at the next sync. This only adds cost when a tool keeps creating
and deleting new folders inside an `--include` root. The extra watches
are about rate × 60 s, and their number stops growing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libuv Issues and PRs related to the libuv dependency or the uv binding. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fs: fs.watch on MacOS leaks native memory each time a directory watcher is closed and reopened

2 participants