Skip to content

Windows output tracking duplicates per-directory HANDLE cost across workspace #36563

Description

@sdjayna

Current Behavior

The Nx daemon starts a workspace-wide output-tracking watcher in addition to its source watcher. On Windows, both use non-recursive per-directory registrations.

This duplicates resource cost for normal source directories and extends watcher cost into Git-ignored directories:

Directory type Source watcher Output watcher Current HANDLE cost
Normal source directory Yes Yes 4 per directory
Git-ignored directory No Yes 2 per directory
Hard-excluded (node_modules, .git, .nx/cache, .nx/workspace-data) No No 0

The public fixture is Git-ignored specifically to isolate the output watcher's two-HANDLE slope. The defect itself is workspace-wide duplication, not Git-ignore handling.

Nx uses notify 8.2.0. For every Windows directory registration, notify retains:

  1. a directory HANDLE created by CreateFileW, and
  2. a completion-semaphore HANDLE created by CreateSemaphoreW.

A HANDLE is a Windows kernel resource owned by the process. The count therefore grows with workspace directories and remains allocated for the daemon lifetime.

The public synthetic reproduction proves the slope on native Windows:

Generated directories Daemon HANDLEs Private memory (MB) Working set (MB) Graph command
0 288 96.6 91.1 2,664ms
8,192 16,674 236.3 230.4 3,912ms
32,768 65,826 644.4 639.7 8,110ms
131,072 262,434 2,311.2 2,303.7 26,146ms

Across all four points:

  • HANDLE slope: 2.000008 per directory, R² effectively 1.0.
  • Private-memory slope: 0.0169 MB per directory (~16.9 KB), R² 0.99999.
  • Graph-command slope: 0.180ms per directory, R² 0.99969.
  • The measured daemon PID exited after reset at all four points.

A separate large-workspace field observation reached 161,876 handles and approximately 2.48 GB. It motivated the investigation; the public synthetic result is the independently reproducible proof.

Expected Behavior

Optional output tracking should not add a second workspace-wide per-directory watcher on Windows. Nx must preserve cache correctness through its existing conservative fallback while leaving source watching unchanged.

After the fix, normal source directories remain watched once by the source watcher, while Git-ignored directories no longer incur output-watcher registrations.

GitHub Repo

https://github.com/sdjayna/nx-daemon-reproductions

Successful native Windows evidence run:
https://github.com/sdjayna/nx-daemon-reproductions/actions/runs/30946807413

Steps to Reproduce

  1. On Windows, install the reproduction dependencies.
  2. Run ./scripts/measure-windows-handles.ps1 -DirectoryCount 1000.
  3. Record daemon PID, HANDLE count and memory.
  4. Run ./scripts/measure-windows-handles.ps1 -DirectoryCount 5000.
  5. Compare the two output blocks. The script resets the daemon before each measurement.
  6. Run corepack yarn nx reset afterwards and confirm the measured PID exits.

Nx Report

Node           : 24.18.0
OS             : win32-x64
Native Target  : x86_64-windows
yarn           : 4.15.0
daemon         : Available

nx  : 23.1.1

Failure Logs

There is no thrown application error. The failure is linear process-resource retention; the workflow emits the process metrics above.

Package Manager Version

Yarn 4.15.0. The watcher mechanism itself is package-manager independent.

Operating System

  • Windows

Additional Information

Draft PR #36566 fixes this scoped output-watcher defect:
#36566

The PR disables the optional output watcher on Windows and uses Nx's existing conservative output-cache fallback. Source watching remains unchanged and is explicitly outside this issue's scope.

A future source-watcher redesign must be tracked separately and must not silently lose changes. notify 8.2.0 lacks the rescan behavior required for a safe recursive-root change; current notify main has added that support, but Nx does not yet consume a released version containing it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions