Skip to main content

orbit_core/ring/
readiness.rs

1//! Native readiness bridge for an SHM ring.
2//!
3//! The shared signal is a generation in the ring header, waited through Linux
4//! futex, FreeBSD umtx, or macOS shared address waits. Each process owns a
5//! private readiness fd (`eventfd`, or a pipe on macOS) and a small
6//! blocking driver thread that converts generation changes into fd readiness.
7//! Async runtimes can therefore wait on their normal reactor without sharing
8//! one drainable eventfd across readers.
9
10#![cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
11
12use std::fmt;
13use std::io;
14use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
15use std::sync::Arc;
16use std::sync::atomic::{AtomicBool, Ordering};
17use std::thread::JoinHandle;
18
19use super::shm::ShmRing;
20
21/// Process-local fd readiness bridge for one shared Orbit ring.
22///
23/// The established name is retained on macOS, where a nonblocking pipe backs
24/// the fd. Native macOS readiness requires 14.4 or later; older systems return
25/// `ErrorKind::Unsupported` from construction so callers can use polling.
26///
27/// Every subscribing process creates its own instance. Ring publishers bump a
28/// generation stored in SHM and wake all platform waiters; the local driver
29/// then marks this fd readable. Multiple publishes may coalesce into one wake,
30/// so a consumer must drain the fd and poll the ring through its own cursor.
31pub struct RingEventFd {
32    fd: OwnedFd,
33    ring: Arc<ShmRing>,
34    stop: Arc<AtomicBool>,
35    driver: Option<JoinHandle<()>>,
36}
37
38impl RingEventFd {
39    pub(crate) fn new(ring: Arc<ShmRing>) -> io::Result<Self> {
40        let (fd, driver_fd) = local_notification_pair()?;
41        let stop = Arc::new(AtomicBool::new(false));
42        let driver_stop = stop.clone();
43        let driver_ring = ring.clone();
44        let mut observed = ring.notification_generation().load(Ordering::Acquire);
45
46        // A pipe has two distinct ends; an eventfd is one descriptor cloned.
47        #[cfg(target_os = "macos")]
48        debug_assert_ne!(fd.as_raw_fd(), driver_fd.as_raw_fd());
49
50        let driver = std::thread::Builder::new()
51            .name(format!("orbit-ring-{}-eventfd", ring.kind()))
52            .spawn(move || {
53                while !driver_stop.load(Ordering::Acquire) {
54                    let current = driver_ring
55                        .notification_generation()
56                        .load(Ordering::Acquire);
57                    if current != observed {
58                        observed = current;
59                        if signal_event_fd(driver_fd.as_raw_fd()).is_err() {
60                            break;
61                        }
62                        continue;
63                    }
64                    if crate::sync::wait_word(driver_ring.notification_generation(), observed)
65                        .is_err()
66                    {
67                        break;
68                    }
69                }
70            })?;
71
72        Ok(Self {
73            fd,
74            ring,
75            stop,
76            driver: Some(driver),
77        })
78    }
79
80    pub(crate) fn notify(ring: &ShmRing) -> io::Result<()> {
81        ring.notification_generation()
82            .fetch_add(1, Ordering::Release);
83        crate::sync::wake_word(ring.notification_generation())
84    }
85
86    /// Drain coalesced readiness tokens from the nonblocking local fd.
87    ///
88    /// Ring events themselves remain in SHM; the returned number is only the
89    /// local wake count and must not be interpreted as an event count.
90    pub fn drain(&self) -> io::Result<u64> {
91        let mut total = 0u64;
92        loop {
93            let mut value = 0u64;
94            let read = unsafe {
95                libc::read(
96                    self.fd.as_raw_fd(),
97                    (&mut value as *mut u64).cast(),
98                    std::mem::size_of::<u64>(),
99                )
100            };
101            if read == std::mem::size_of::<u64>() as isize {
102                #[cfg(target_os = "macos")]
103                debug_assert_eq!(value, 1, "local pipe carries unit readiness tokens only");
104                total = total.saturating_add(value);
105                continue;
106            }
107            if read == 0 {
108                return Err(io::Error::new(
109                    io::ErrorKind::UnexpectedEof,
110                    "Orbit eventfd closed while draining",
111                ));
112            }
113            if read < 0 {
114                let error = io::Error::last_os_error();
115                match error.raw_os_error() {
116                    Some(libc::EINTR) => continue,
117                    Some(libc::EAGAIN) => return Ok(total),
118                    _ => return Err(error),
119                }
120            }
121            return Err(io::Error::new(
122                io::ErrorKind::InvalidData,
123                "Orbit eventfd returned a partial counter",
124            ));
125        }
126    }
127}
128
129impl AsRawFd for RingEventFd {
130    fn as_raw_fd(&self) -> RawFd {
131        self.fd.as_raw_fd()
132    }
133}
134
135impl AsFd for RingEventFd {
136    fn as_fd(&self) -> BorrowedFd<'_> {
137        self.fd.as_fd()
138    }
139}
140
141impl fmt::Debug for RingEventFd {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        f.debug_struct("RingEventFd")
144            .field("fd", &self.fd.as_raw_fd())
145            .field("ring_kind", &self.ring.kind())
146            .finish_non_exhaustive()
147    }
148}
149
150impl Drop for RingEventFd {
151    fn drop(&mut self) {
152        self.stop.store(true, Ordering::Release);
153        // Change the generation before waking. If the driver passed its stop
154        // check but has not entered the platform wait yet, the atomic compare
155        // prevents it from parking after our wake and deadlocking join.
156        self.ring
157            .notification_generation()
158            .fetch_add(1, Ordering::Release);
159        let _ = crate::sync::wake_word(self.ring.notification_generation());
160        if let Some(driver) = self.driver.take() {
161            let _ = driver.join();
162        }
163    }
164}
165
166fn signal_event_fd(fd: RawFd) -> io::Result<()> {
167    let value = 1u64;
168    loop {
169        let written = unsafe {
170            libc::write(
171                fd,
172                (&value as *const u64).cast(),
173                std::mem::size_of::<u64>(),
174            )
175        };
176        if written == std::mem::size_of::<u64>() as isize {
177            return Ok(());
178        }
179        if written < 0 {
180            let error = io::Error::last_os_error();
181            match error.raw_os_error() {
182                Some(libc::EINTR) => continue,
183                // A full eventfd or pipe is already readable, so the notification is
184                // represented even though this increment could not be added.
185                Some(libc::EAGAIN) => return Ok(()),
186                _ => return Err(error),
187            }
188        }
189        return Err(io::Error::new(
190            io::ErrorKind::WriteZero,
191            "Orbit eventfd accepted a partial counter",
192        ));
193    }
194}
195
196#[cfg(any(target_os = "linux", target_os = "freebsd"))]
197fn local_notification_pair() -> io::Result<(OwnedFd, OwnedFd)> {
198    let raw = unsafe { libc::eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) };
199    if raw < 0 {
200        return Err(io::Error::last_os_error());
201    }
202    let fd = unsafe { OwnedFd::from_raw_fd(raw) };
203    let driver = fd.try_clone()?;
204    Ok((fd, driver))
205}
206
207#[cfg(target_os = "macos")]
208fn local_notification_pair() -> io::Result<(OwnedFd, OwnedFd)> {
209    if crate::sync::macos::api().is_none() {
210        return Err(io::Error::new(
211            io::ErrorKind::Unsupported,
212            "Orbit native readiness requires macOS 14.4 or later",
213        ));
214    }
215    let mut raw = [-1; 2];
216    if unsafe { libc::pipe(raw.as_mut_ptr()) } < 0 {
217        return Err(io::Error::last_os_error());
218    }
219    // Take ownership of both ends before any fallible setup. Drop joins the
220    // driver before closing the read end, so writes cannot hit a closed pipe.
221    let read = unsafe { OwnedFd::from_raw_fd(raw[0]) };
222    let write = unsafe { OwnedFd::from_raw_fd(raw[1]) };
223    for fd in [&read, &write] {
224        if unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC) } < 0
225            || unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK) } < 0
226        {
227            return Err(io::Error::last_os_error());
228        }
229    }
230    Ok((read, write))
231}
232
233#[cfg(all(test, target_os = "macos"))]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn full_local_pipe_coalesces_and_rearms() {
239        let (read, write) = local_notification_pair().unwrap();
240        for fd in [&read, &write] {
241            assert_ne!(
242                unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GETFL) } & libc::O_NONBLOCK,
243                0
244            );
245            assert_ne!(
246                unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GETFD) } & libc::FD_CLOEXEC,
247                0
248            );
249        }
250        // Fill the pipe deliberately, then exercise the bridge's EAGAIN path.
251        let token = 1u64;
252        loop {
253            let n = unsafe { libc::write(write.as_raw_fd(), (&token as *const u64).cast(), 8) };
254            if n < 0 {
255                assert_eq!(
256                    io::Error::last_os_error().raw_os_error(),
257                    Some(libc::EAGAIN)
258                );
259                break;
260            }
261            assert_eq!(n, 8);
262        }
263        signal_event_fd(write.as_raw_fd()).unwrap();
264        let mut buffer = [0u64; 128];
265        loop {
266            let n = unsafe {
267                libc::read(
268                    read.as_raw_fd(),
269                    buffer.as_mut_ptr().cast(),
270                    size_of_val(&buffer),
271                )
272            };
273            if n < 0 {
274                assert_eq!(
275                    io::Error::last_os_error().raw_os_error(),
276                    Some(libc::EAGAIN)
277                );
278                break;
279            }
280            assert!(n > 0);
281        }
282        signal_event_fd(write.as_raw_fd()).unwrap();
283        let mut value = 0u64;
284        assert_eq!(
285            unsafe { libc::read(read.as_raw_fd(), (&mut value as *mut u64).cast(), 8) },
286            8
287        );
288        assert_eq!(value, 1);
289    }
290}