fix(cgroup): stop swallowing inotify read errors in the memory monitor

wait_and_drain treated every read() <= 0 as "queue drained", so a real
error (bad fd, EINVAL) silently ended the drain and the monitor kept
polling a broken fd. Distinguish the cases: EAGAIN ends the drain, EINTR
retries, and any other error propagates. monitor_loop logs the error
before exiting so a dead memory monitor is visible in the session log.
This commit is contained in:
2026-07-24 11:30:20 -04:00
parent a02b555e66
commit f5b9000c5d
@@ -127,8 +127,8 @@ mod linux {
async fn wait_and_drain(&self) -> std::io::Result<()> { async fn wait_and_drain(&self) -> std::io::Result<()> {
let mut guard = self.fd.readable().await?; let mut guard = self.fd.readable().await?;
// Drain all pending inotify events; the fd is non-blocking, so the // Drain all pending inotify events so the next readiness wakeup
// read returns <= 0 once the queue is empty. // corresponds to a fresh modification.
let mut buf = [0u8; 4096]; let mut buf = [0u8; 4096];
loop { loop {
// SAFETY: writing at most `buf.len()` bytes into `buf`, through // SAFETY: writing at most `buf.len()` bytes into `buf`, through
@@ -143,7 +143,16 @@ mod linux {
if n > 0 { if n > 0 {
continue; continue;
} }
break; if n == 0 {
break;
}
let err = std::io::Error::last_os_error();
match err.kind() {
// The fd is non-blocking: the queue is empty.
std::io::ErrorKind::WouldBlock => break,
std::io::ErrorKind::Interrupted => continue,
_ => return Err(err),
}
} }
guard.clear_ready(); guard.clear_ready();
Ok(()) Ok(())
@@ -304,7 +313,10 @@ mod linux {
let mut last_high_count = Self::read_high_counter(&events_path).await.unwrap_or(0); let mut last_high_count = Self::read_high_counter(&events_path).await.unwrap_or(0);
loop { loop {
if inotify.wait_and_drain().await.is_err() { if let Err(e) = inotify.wait_and_drain().await {
tracing::warn!(
"memory.events inotify read failed; stopping memory monitor: {e}"
);
break; break;
} }