fs.watch/fs.watchFile: remove unsafe from the watcher modules

JA
2mo ago
+2482-2896

What

Same programme as #40055 / #40135 / #40136 / #40139 / #40187 / #40190, applied to the fs.watch / fs.watchFile machinery: path_watcher.rs 66 → 0, fs_events.rs 64 → 0, node_fs_stat_watcher.rs 44 → 0, node_fs_watcher.rs 28 → 0 (one unsafe extern "C" { safe fn } decl for a C++ symbol that takes this file's EventType).

  • Registrations, not pointers. A PathWatcher is owned by the manager's Guarded<State> and addressed by a never-reused WatcherId; inotify wds / kqueue entries / FSEvents streams carry ids. fs.watch gets a path_watcher::Registration (RAII; drop = detach). The reader thread's per-handler state is a typed EventSink (batch of ≤8 events, Arc<Activity>, VmHandle) inside the locked state; batches reach the JS thread as owned Box<FSWatchTask> via a new VmHandle::post_boxed (refused ⇒ box handed back).
  • FSWatcher: Activity { mutex, closed, pending } in an Arc shared with sinks/tasks/GC thread; the abort listener is a new bun_jsc::AbortSubscription (holds a signal ref, unregisters on drop; subscriber passed as BackRef); wrapper via to_js_boxed; finalize_js_box.
  • StatWatcher / scheduler: queue is Guarded<Vec<RefPtr<StatWatcher>>>; the stat pass is an owned, ticketed StatPassTask holding the scheduler ref; timer hops are boxed payloads owning their RefPtr; the per-thread scheduler slot moves from RareData (erased pointer) to RuntimeState with a scoped with_stat_watcher_scheduler(|slot| ..); deinit → Drop.
  • FSEvents (macOS): new bun_sys::core_foundation — dlsym'd typed function table, owned CFString/CFStringArray/RunLoop/RunLoopSource/EventStream (created and scheduled on the calling thread's run loop; start/stop/drop assert that thread), typed &'static H handler trampolines. Cross-thread requests are two atomic bits + RunLoopSourceHandler::perform; state under Guarded; registrations by id.
  • bun_ptr::ThreadBound<T>: a BackRef plus the owning std thread id; any thread may carry it, only the owner thread may get() (asserted) — the typed way to hand a JS-thread object's address to a watcher thread purely so it can be handed back. bun_sys::linux::inotify_add_watch_z.

Pre-existing issue fixed in passing: the old FSWatchTaskPosix::release_unrun dereferenced the FSWatcher (which may already be finalized at teardown); the new one only touches the shared Arc<Activity>.

Testing

Debug+ASAN (Linux): test/js/node/watch/*.test.ts, all 38 test-fs-watch* / test-fs-watchfile* node parallel tests, fs.test.ts -t watch, test/cli/hot/{hot,watch,watch-many-dirs}, test/cli/watch/{watch,watcher-trace}, source lints. Hand-driven: recursive watch with create/rename/delete and close inside the listener; watchFile + unwatchFile mid-poll and immediately; 200 watchers on one path then GC; an unclosed unref'd watcher collected; a worker with fs.watch/fs.watchFile terminated while main keeps watching — exit 0, no ASAN output, outputs match 1.4.0. clippy clean on every touched crate (host + aarch64-apple-darwin); rust-check-all x86_64-pc-windows-msvc + aarch64-apple-darwin pass (macOS FSEvents / Windows ReadDirectoryChangesW paths are type-checked, not executed here).