From d67cf553f8c9811057ad13b43474743af938b228 Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Sun, 1 Mar 2026 15:54:15 +0200 Subject: [PATCH] utils/macos/epoll: Fix EV_EOF hangup type mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code used EV_CLEAR (edge-trigger flag) as a proxy for the event direction when mapping EV_EOF to a hangup type. This produced the wrong type: backend WRITE+EOF got READ_HANG_UP, and eventfd READ+EOF got HANG_UP. No impact in practice since the worker treats both HANG_UP and READ_HANG_UP the same way (fatal error, disable networking). Use the filter type instead: - EVFILT_READ + EV_EOF → READ_HANG_UP (half-close) - EVFILT_WRITE + EV_EOF → HANG_UP (connection broken) Unlike epoll where EPOLLRDHUP must be explicitly registered, kqueue always reports EV_EOF. The shim does not track registration, so READ_HANG_UP is always reported on read EOF. Document this as a known difference from epoll in ctl(). Signed-off-by: Nir Soffer --- src/utils/src/macos/epoll.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/utils/src/macos/epoll.rs b/src/utils/src/macos/epoll.rs index 2f1ad5a8a..af3f86a57 100644 --- a/src/utils/src/macos/epoll.rs +++ b/src/utils/src/macos/epoll.rs @@ -144,6 +144,11 @@ impl Epoll { } } + /// Register, modify, or remove interest in events for a file descriptor. + /// + /// Note: `READ_HANG_UP` (`EPOLLRDHUP`) is ignored. kqueue always + /// reports `EV_EOF` without opt-in, so `wait()` reports + /// `READ_HANG_UP` on read EOF regardless of registration. pub fn ctl( &self, operation: ControlOperation, @@ -282,15 +287,14 @@ impl Epoll { for i in 0..nevents { if kevs[i].0.filter == libc::EVFILT_READ { events[i].events = EventSet::IN.bits(); + if kevs[i].0.flags & libc::EV_EOF != 0 { + events[i].events |= EventSet::READ_HANG_UP.bits(); + } } else if kevs[i].0.filter == libc::EVFILT_WRITE { events[i].events = EventSet::OUT.bits(); - } - if kevs[i].0.flags & libc::EV_EOF != 0 { - events[i].events |= if kevs[i].0.flags & libc::EV_CLEAR != 0 { - EventSet::READ_HANG_UP.bits() - } else { - EventSet::HANG_UP.bits() - }; + if kevs[i].0.flags & libc::EV_EOF != 0 { + events[i].events |= EventSet::HANG_UP.bits(); + } } events[i].u64 = kevs[i].udata();