acpmux: the login-env watcher wakes only on changes, not on every open (Linux)

Merged
TE
2d ago
+145-14

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:

  • /etc produced 22,297 events and /usr/bin 17,038, about 1,300 per second together.
  • The daemon's acpmux-login-env-watch thread 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 run exactly as before.
  • A queue overflow is sent as an error, which touches reads 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.

metricbeforeafterdeltaharness or run link
login-env watcher thread wakeups27,731 /minexpected ~0 when no startup file changesabout -27.7k /min/proc/PID/task/*/status, 90 s
notify inotify thread wakeups (all daemon watchers)36,811 /minexpected lower by the /etc + /usr/bin sharesame
daemon wakeups total76,347 /minsame
daemon CPU at idle3.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 changesinotify 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