acpmux: the login-env watcher wakes only on changes, not on every open (Linux)
Leo: "acpmux/cmux-next should do better too" / "i mean wrt perf/efficiency"
What was wrong
The login-env watcher (login_env/watch.rs) watches the folder of each shell startup file so an edit refreshes the login environment at once. Those folders include $HOME, /etc and the shell's own folder (/usr/bin, since $SHELL is listed).
On Linux, notify 8.2's inotify watcher always subscribes to IN_OPEN, and there is no setting to turn it off. So every program on the machine that opens a file in /etc or /usr/bin (every exec does) wakes the daemon twice: notify's event thread, then the watcher thread, which drops the event as access (touches).
Measured on one agent VM with about 50 live sessions, over the same 30 s window:
/etcproduced 22,297 events and/usr/bin17,038, about 1,300 per second together.- The daemon's
acpmux-login-env-watchthread woke 14,433 times, about 27.7k per minute.
This happens with every agent idle, because what drives it is activity anywhere on the machine.
Fix
On Linux, the watch is inotify directly (the inotify crate, already in the tree through notify) with a mask that asks only for events that can change a file: attrib, create, delete, close-write, modify, moved-from, moved-to, delete-self and move-self.
- Events reach
runexactly as before. - A queue overflow is sent as an error, which
touchesreads as a change, so a lost event still refreshes. - macOS keeps notify, whose FSEvents backend reports no opens.
Performance
Daemon threads, per-thread voluntary plus involuntary context switches, sampled over 90 s on the same VM. "After" is to be measured on this VM after the next daemon update; the confirmed numbers go in a comment.
| metric | before | after | delta | harness or run link |
|---|---|---|---|---|
| login-env watcher thread wakeups | 27,731 /min | expected ~0 when no startup file changes | about -27.7k /min | /proc/PID/task/*/status, 90 s |
| notify inotify thread wakeups (all daemon watchers) | 36,811 /min | expected lower by the /etc + /usr/bin share | same | |
| daemon wakeups total | 76,347 /min | same | ||
| daemon CPU at idle | 3.08 % of a core | /proc/PID/task/*/stat, 90 s | ||
| events in the watched folders | ~1,300 /s (/etc 743, /usr/bin 568) | only real changes | inotify sampler on the same folders, 30 s |
Tests
login_env::watch::tests::a_read_in_a_watched_folder_sends_nothing_and_a_write_arrives (Linux): watches a temporary folder, reads a file there 20 times (no event may arrive within 300 ms), then writes it (an event naming the file must arrive). It fails on the red commit, which still uses notify (an open sends an event), and passes on the fix.
Observed / Not run
Not run yet: the red and green runs of the new test go through cmux-ci on this PR. Read: notify 8.2 inotify.rs:425-432 (IN_OPEN always set), inotify 0.11.5 Watches::add and read_events_blocking.
Follow-ups: the other notify watchers in acpmux (chats/watch.rs holds 14,466 inotify watches on transcript folders on this VM, plus git_watch, harness_watch and settings_watch) get the same IN_OPEN events. Their folders are quieter than /etc, so this PR leaves them. An upstream issue against notify for a configurable mask is also a follow-up.
Changelog
none
risk:normal
Agent session: agent session
š¤ Generated with Claude Code