Repository navigation
fix(cmux-config): macOS file watch uses kqueue, so the daemon sees acpmux bind its socket - #29685
Conversation
…(red on macOS) The agent chat notices watch acpmux's socket with cmux_config::watch. On macOS a folder that holds a watched name is watched by FSEvents only, and binding a socket there gave no hint, so the idle daemon never saw acpmux start (idle_wakeups.rs cmux_next_daemon_idle_has_no_periodic_wakeups). Also pins the settings watcher's file cases and that other entries of the folder do not hint.
…vents The daemon's agent chat notices watch acpmux's socket with cmux_config::watch. On macOS a folder that holds a watched name was watched by FSEvents only, and binding a Unix socket in it gave no hint (cmux-config tests/watch.rs red on macOS, job 6a56674b79d3d0183e77ce84), so a daemon whose acpmux folder already existed never connected when acpmux started: idle_wakeups cmux_next_daemon_idle_has_no_periodic_wakeups "did not connect to acpmux within 3 s". macOS now has its own backend (watch/kqueue.rs), built from the kqueue code that already watched a missing folder's ancestors: a NOTE_WRITE / delete / rename / revoke watch of every planned folder's entries, and a content and metadata watch of each watched regular file (a write in place changes no entry). A folder event names no entry, so each folder keeps the identity (dev, inode, mode, ctime) of its watched names and hints only when one changed; other entries of the folder never hint. One thread blocks in kevent with no timeout outside a burst, so an idle daemon does not wake. Linux (inotify.rs) is unchanged; portable.rs is now only for platforms other than Linux and macOS.
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Shipped in:
|
|
Review (claude-reviewer-kqueue): SHIP at e22fa88 Scope: cmux-config/src/watch.rs, watch/kqueue.rs (new), watch/portable.rs, watch/ancestors.rs (deleted), tests/watch.rs, the agent_chat_notices.rs comment and idle-wakeups.md. I read inotify.rs and the base ancestors.rs to compare. What I checked:
Evidence I read: the head macOS Residual risks (none block):
|
|
Checks at head e22fa88: 68 pass, 77 skipped, 2 pending (R2 publish, Sentry dSYM upload). One non-green check:
The cmux-tui Rust tests for this change ran on the fleet (see Testing in the description): macOS fmt, clippy and tests in 21155f8bc4aacd339b653cd5 and 1b35d91ba116484703fbf888; Linux in 8af7def1c888a8747fdc0a82 and 2a18462dda8e919c20df96fc. |
|
Review (claude-reviewer-kqueue; carried): SHIP at beb78b1 This carries my SHIP at e22fa88 (#29685 (comment)) to the merge head beb78b1. I did a read-only check:
The residual risks in my first receipt are unchanged. |
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
Found 1 test failure on Blacksmith runners: Failure
|
|
Red check classification on head 0558e13 (none is this PR's own red):
|
…nd made again (#30028) * test(cmux-tui): a customize folder deleted and made again reloads a later save Red first: on Linux the inotify watch goes away with the old folder, and nothing watches the new one until the daemon restarts. * fix(cmux-tui): watch a local apps folder again after it is deleted and made again On Linux the inotify watch on the customize folder ended with the old folder (or stayed on it after a rename), so a folder replaced by a git checkout, an editor or rm -rf and a copy was not watched until the daemon restarted, and its saves did not reload. The folder's entry in its parent is now watched through cmux_config::watch (the same Plan the settings watch uses: it climbs to the nearest existing ancestor while the parent is missing too). On each of its hints a folder whose device and inode differ from the watched one is watched again, before the read. No timer and no polling. The local development folder gets the same treatment. macOS: FSEvents watches a path, so a stream there kept reporting the new folder; the same code re-arms it once (one stream restart, then a read). The missing-parent case (the parent of the folder also missing at start) was unwatched on both platforms before and is now covered. * test(cmux-tui): a customize folder whose parent is missing at start installs once made The watch went no higher than the folder's parent, so with ~/.config/cmux missing at daemon start nothing picked the folder up until a restart. * fix(cmux-tui): arm the local apps folders again on every folder hint 75c3cff compared the folder's device and inode to decide whether to watch it again. A folder deleted and made again at once often gets the freed inode number back (ext4), so the new folder looked like the watched one and stayed unwatched: the replace test failed on Linux in job 3baeff7475c95b47351b7de9 and passed in 1579c030db74662cf3887cb1 with other tests beside it. Each folder hint now drops the folder's old watch and watches what is at the path. notify's own event for the watched folder itself (removed or renamed) also arms again, because notify ends its record of a deleted folder's watch only when it reads that event, and read late it can end the watch just armed. The entry watch starts before the folders are armed and its first hint (armed) is skipped, so startup arms and reads once. A unit test drives a real notify watcher: a folder deleted and made again, then renamed away and made again, is watched once armed again, and the old folder's watch names nothing under the path. * fix(cmux-tui): drop the notify::Watcher import local_reload.rs no longer uses Folders::arm takes the watcher as impl notify::Watcher, so the trait import is unused and cmux-tui-core fails clippy -D warnings. * docs(cmux-tui): say what the local apps re-arm costs on macOS Review of #30028: the macOS entry watch is FSEvents on the parent or kqueue on an ancestor, and arming again restarts the FSEvents stream twice per folder (unwatch, then watch). Comment only. * docs(cmux-tui): the macOS entry watch is cmux_config's kqueue watch Second review of #30028: since #29685 cmux_config watches every directory with kqueue on macOS, not FSEvents on the parent. Comment only.
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Summary
On macOS an idle cmux-tui daemon never connected to acpmux when acpmux started after the daemon, as long as acpmux's folder already existed.
cmux-tui::idle_wakeups cmux_next_daemon_idle_has_no_periodic_wakeupsis red on macOS for this reason ("the daemon did not connect to acpmux within 3 s of its socket appearing", also when run alone, job 509e39756a834d8f1fc1b527).The agent chat notices thread (
agent_chat_notices.rswatch()) waits oncmux_config::watchfor acpmux's socket. On macOS that watcher used FSEvents for every folder that holds a watched name. FSEvents does not report a Unix socket being bound at all. Measured with a raw notify FSEvents watch of the folder (scratch diag job 313c5cbdc973cc41db671b58, aws-m4pro-6, two tries): a socketbindgave no event within 8 s, both times; in the same folder a file create came 6 ms after the write and the socket's removal 1.4 ms after the unlink. The paths were already canonical (/private/tmp/...), so it is not a/varvs/private/varmismatch, and not lateness. The focused test below fails the same way throughcmux_config::watch. FSEvents teardown is also slow on a loaded Mac: two existing tests,agent_chat_notices"shutdown/drop ends a watch that waits for acpmux", failed on the first try in 2 of 5 base jobs on macOS ("the watch outlived ...": the watch thread, which starts and stops an FSEvents stream, took over 5 s to end) and passed on the gate rerun.macOS now has its own backend,
cmux-config/src/watch/kqueue.rs, built from the kqueue code that already watched a missing folder's ancestors (ancestors.rs, removed):NOTE_WRITE, plusNOTE_DELETE/RENAME/REVOKEfor the folder itself);keventwith no timeout outside a burst, so an idle daemon does not wake. Dropping the watcher triggers a user event; no fseventsd call on teardown.The folders watched are the same as before (the settings file's folder, the nearest existing ancestor of a missing one, and acpmux's own socket folder); no new folder is watched. Linux (
inotify.rs) is unchanged;portable.rsnow serves only platforms other than Linux and macOS.Testing
Red first:
fefd9a968bfaddscmux-tui/crates/cmux-config/tests/watch.rsalone.test watch_(macOS, aws-m4pro-2)fefd9a968bfwatch_hints_when_a_socket_is_bound_in_a_watched_folderFAIL on the first try and on the gate rerun ("no hint within 2s of a socket bound at a watched name"); the file and other-entry tests PASStest ends_a_watch_that_waits_for_acpmux, 5 jobs (macOS)e820cedb59fgate-notice: FLAKY); the other 3 passed (0.5-5.9 s)e22fa881f4e..._ignores_writes_below_a_missing_settings_folder: PASS)all watch(Linux: fmt, clippy-D warnings, tests)all watch(macOS: fmt, clippy-D warnings, tests)all cmux_next_daemon_idle(macOS: fmt, clippy, tests)test cmux_next_daemon_idle(Linux)The scratch diag ran on branch
scratch/acpmux-hint-diag(never merged).Observed / Not run
Observed: the jobs above, read from their logs (each attempt counted,
gate-notice: FLAKYsearched).Not run:
idle_wakeupsis the test binary's name, and nextest filters test names, so 0 tests ran. They count as not run (d67189fe's fmt and clippy did pass on macOS).portable.rsis now the backend only for platforms other than Linux and macOS, so Windows still uses notify'sRecommendedWatcher(ReadDirectoryChangesW) with the same code as before; this PR only removed its macOS branches.#[cfg(target_os = "macos")]keepskqueue.rsout of every other build. The PR'sbuild-windows / build x86_64-pc-windows-gnucheck compiles it (pass on this head); no Windows test of the watcher ran.watch_hints_when_a_watched_file_is_created_written_and_removedand the idle tests above.Performance
No perf numbers: this changes which kernel API wakes the watch thread, not a measured speed path. The idle wakeup check is
cmux_next_daemon_idle_has_no_periodic_wakeups(and..._ignores_writes_below_a_missing_settings_folder), which pass at the head on macOS.Changelog
Fixed: On macOS, agent chat notifications start as soon as acpmux starts, instead of never when acpmux's folder already existed.