fix(workspace-fs): wait for released watchers during shutdown

PR
3h ago
+60-2

What & why

A caller can stop the last listener, await filesystem shutdown, and then remove its checkout while the native watcher is still attached. The release path removes the entry from the path map immediately (so another listener can reattach), but that also hid the in-flight teardown from FsWatcherManager.close().

Keep that cleanup visible to shutdown, both when registration is in flight and when the watch is already live. Reattachment still gets a fresh entry; the existing timeout for a dead native stream is unchanged.

Found while verifying #9625 on macOS. The original crash report shows FSEventsCallback → FSEventStreamStop in Parcel's deleted-root path. The early return from shutdown is reproducible; the native SIGTRAP itself did not recur on the unchanged PR in the latest default-worker run. This fixes a teardown race, not every possible native watcher crash.

How I tested it

  • Held native cleanup behind a gate and checked whether shutdown observed a detached watcher. Both the attaching and attached cases failed before the fix and passed after it.
  • On the #9625 checkout with this patch, macOS arm64, Bun 1.4.3: host-service 2,861 passed, workspace-fs 196 passed. Used the normal package scripts, default worker count and default timeout. On the same checkout before the patch, an unrelated Git directory worker test timed out once; no SIGTRAP that run.
  • On this branch, repo-wide lint and typecheck passed. The repo-wide test command stopped at the independent locale-sensitive recurrence assertion tracked in #9658.

Checklist

  • PR title follows conventional commits
  • Repo-wide lint and typecheck pass
  • Allow edits from maintainers
  • Signed CLA

Summary by cubic

Fixes a shutdown race in FsWatcherManager where close() could resolve while the last listener's native watch was still tearing down, crashing when the checkout was removed immediately after.

  • Tracks in-flight watcher releases so close() waits on them alongside still-registered watchers.
  • Reattachment still gets a fresh watcher entry; the dead-stream timeout is unchanged.

Summary by CodeRabbit

  • Bug Fixes
    • Workspace shutdown now waits for in-progress file-watcher setup and cleanup to finish before completing. This improves reliability when watchers are starting or stopping as the workspace closes, and helps prevent shutdown from completing while watcher cleanup is still pending.