loopd: read on a socket whose peer closed, instead of failing on macOS

macOS refuses every socket option with EINVAL once the peer has closed
(XNU sosetoptlock, bsd/kern/uipc_socket.c), even with unread data still
buffered. loopd set a read timeout before each read, so on macOS every
frame or response that arrived just before the peer closed was lost:
BrokerPort reported the broker unavailable with "os error 22", and the
llama client failed the same way. Twelve loopd test binaries failed on
macOS; Linux never refuses, so the gate on Talos did not see it.

Both places now go through socket::set_read_timeout, which on Apple
targets takes that one refusal as success: a socket shut in both
directions returns its data or the end at once and cannot block. A
zero timeout is still an error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-22 18:27:31 -07:00
co-authored by Claude Opus 5.5
parent d21baa2954
commit 00a85c124d
5 changed files with 71 additions and 4 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ impl std::io::Read for Deadline<'_> {
if left.is_zero() {
return Err(std::io::Error::from(std::io::ErrorKind::TimedOut));
}
self.stream.set_read_timeout(Some(left))?;
crate::socket::set_read_timeout(self.stream, left)?;
let mut stream = self.stream; // `Read` is implemented for `&UnixStream`
stream.read(buf)
}