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::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::{Arc, Condvar, Mutex};
15use std::thread::JoinHandle;
16use std::{fmt, io};
17
18use super::shm::ShmRing;
19use crate::readiness::{Readiness, Signal};
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: Readiness,
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 =
45            ring.notification_generation().load(Ordering::Acquire);
46
47        // A pipe has two distinct ends; an eventfd is one descriptor cloned.
48        #[cfg(target_os = "macos")]
49        debug_assert_ne!(fd.as_raw_fd(), driver_fd.as_raw_fd());
50
51        let driver = std::thread::Builder::new()
52            .name(format!("orbit-ring-{}-eventfd", ring.kind()))
53            .spawn(move || {
54                while !driver_stop.load(Ordering::Acquire) {
55                    let current = driver_ring
56                        .notification_generation()
57                        .load(Ordering::Acquire);
58                    if current != observed {
59                        observed = current;
60                        if driver_fd.signal().is_err() {
61                            break;
62                        }
63                        continue;
64                    }
65                    if crate::sync::wait_word(
66                        driver_ring.notification_generation(),
67                        observed,
68                    )
69                    .is_err()
70                    {
71                        break;
72                    }
73                }
74            })?;
75
76        Ok(Self { fd, ring, stop, driver: Some(driver) })
77    }
78
79    pub(crate) fn notify(ring: &ShmRing) -> io::Result<()> {
80        ring.notification_generation().fetch_add(1, Ordering::Release);
81        crate::sync::wake_word(ring.notification_generation())
82    }
83
84    /// Drain coalesced readiness tokens from the nonblocking local fd.
85    ///
86    /// Ring events themselves remain in SHM; the returned number is only the
87    /// local wake count and must not be interpreted as an event count.
88    pub fn drain(&self) -> io::Result<u64> {
89        self.fd.drain()
90    }
91}
92
93impl AsRawFd for RingEventFd {
94    fn as_raw_fd(&self) -> RawFd {
95        self.fd.as_raw_fd()
96    }
97}
98
99impl AsFd for RingEventFd {
100    fn as_fd(&self) -> BorrowedFd<'_> {
101        self.fd.as_fd()
102    }
103}
104
105impl fmt::Debug for RingEventFd {
106    fn fmt(
107        &self,
108        f: &mut fmt::Formatter<'_>,
109    ) -> fmt::Result {
110        f.debug_struct("RingEventFd")
111            .field("fd", &self.fd.as_raw_fd())
112            .field("ring_kind", &self.ring.kind())
113            .finish_non_exhaustive()
114    }
115}
116
117impl Drop for RingEventFd {
118    fn drop(&mut self) {
119        self.stop.store(true, Ordering::Release);
120        // Change the generation before waking. If the driver passed its stop
121        // check but has not entered the platform wait yet, the atomic compare
122        // prevents it from parking after our wake and deadlocking join.
123        self.ring.notification_generation().fetch_add(1, Ordering::Release);
124        let _ = crate::sync::wake_word(self.ring.notification_generation());
125        if let Some(driver) = self.driver.take() {
126            let _ = driver.join();
127        }
128    }
129}
130
131/// Readiness for a ring whose publishers wake only parked readers.
132///
133/// [`RingEventFd`]'s driver parks on the generation again as soon as it has
134/// marked its fd readable, so a publisher cannot tell a busy reader from an
135/// idle one and must wake on every publish: one syscall per frame, and one
136/// driver wake and fd signal on the reading side. This driver instead waits
137/// for its consumer to [`drain`](Self::drain) before it parks again, and
138/// counts itself in the ring header while it is parked. A publisher using
139/// [`Fleet::publish_notified_parked`](crate::Fleet::publish_notified_parked)
140/// reads that count and wakes only when it is non-zero — under load the
141/// reader is busy draining and publishing costs no syscall at all.
142///
143/// Every reader of a ring published with the parked variant must use this
144/// type: a [`RingEventFd`] driver does not count itself and would not be
145/// woken. Rings published with [`Fleet::publish_notified`](crate::Fleet)
146/// are unaffected by this type's existence.
147///
148/// The contract is [`RingEventFd`]'s plus one step: drain the fd, then poll
149/// the ring through your own cursor. Draining is what lets the driver park.
150///
151/// **Clear the reactor's readiness before draining, not after.** The driver
152/// signals once per drain: a signal that arrives just after [`drain`](Self::drain)
153/// is the only one until the next drain, and a reactor that clears readiness
154/// after draining (tokio's `clear_ready`) erases it — the consumer then waits
155/// for an edge that never comes, and the driver for a drain that never
156/// happens. [`RingEventFd`] tolerates either order because its driver
157/// signals on every publish.
158pub struct ParkedRingEventFd {
159    fd: Readiness,
160    ring: Arc<ShmRing>,
161    stop: Arc<AtomicBool>,
162    drained: Arc<Drained>,
163    driver: Option<JoinHandle<()>>,
164}
165
166/// Whether the consumer has taken the last signal. The driver waits here,
167/// in this process, until it has — not on the shared word.
168#[derive(Default)]
169struct Drained {
170    pending: Mutex<bool>,
171    taken: Condvar,
172}
173
174impl ParkedRingEventFd {
175    pub(crate) fn new(ring: Arc<ShmRing>) -> io::Result<Self> {
176        let (fd, driver_fd) = local_notification_pair()?;
177        let stop = Arc::new(AtomicBool::new(false));
178        let drained = Arc::new(Drained::default());
179        let (driver_stop, driver_drained, driver_ring) =
180            (stop.clone(), drained.clone(), ring.clone());
181        let mut observed =
182            ring.notification_generation().load(Ordering::Acquire);
183
184        let driver = std::thread::Builder::new()
185            .name(format!("orbit-ring-{}-parked", ring.kind()))
186            .spawn(move || {
187                let generation = driver_ring.notification_generation();
188                let waiters = driver_ring.notification_waiters();
189                while !driver_stop.load(Ordering::Acquire) {
190                    // Count in, then look once more. A publisher bumps the
191                    // generation before it reads the count; one of the two
192                    // sees the other, so a publish cannot fall between them.
193                    waiters.fetch_add(1, Ordering::SeqCst);
194                    let current = generation.load(Ordering::SeqCst);
195                    if current == observed
196                        && !driver_stop.load(Ordering::Acquire)
197                    {
198                        let parked =
199                            crate::sync::wait_word(generation, observed);
200                        waiters.fetch_sub(1, Ordering::SeqCst);
201                        if parked.is_err() {
202                            break;
203                        }
204                        continue;
205                    }
206                    waiters.fetch_sub(1, Ordering::SeqCst);
207                    if driver_stop.load(Ordering::Acquire) {
208                        break;
209                    }
210                    observed = current;
211                    *driver_drained.pending.lock().unwrap() = true;
212                    if driver_fd.signal().is_err() {
213                        break;
214                    }
215                    let mut pending = driver_drained.pending.lock().unwrap();
216                    while *pending && !driver_stop.load(Ordering::Acquire) {
217                        pending = driver_drained.taken.wait(pending).unwrap();
218                    }
219                }
220            })?;
221
222        Ok(Self { fd, ring, stop, drained, driver: Some(driver) })
223    }
224
225    /// Wake parked readers of `ring`, and only them.
226    pub(crate) fn notify(ring: &ShmRing) -> io::Result<()> {
227        ring.notification_generation().fetch_add(1, Ordering::SeqCst);
228        if ring.notification_waiters().load(Ordering::SeqCst) == 0 {
229            return Ok(());
230        }
231        crate::sync::wake_word(ring.notification_generation())
232    }
233
234    /// Drain the fd and let the driver park again. Poll the ring after this.
235    pub fn drain(&self) -> io::Result<u64> {
236        let tokens = self.fd.drain()?;
237        *self.drained.pending.lock().unwrap() = false;
238        self.drained.taken.notify_one();
239        Ok(tokens)
240    }
241}
242
243impl AsRawFd for ParkedRingEventFd {
244    fn as_raw_fd(&self) -> RawFd {
245        self.fd.as_raw_fd()
246    }
247}
248
249impl AsFd for ParkedRingEventFd {
250    fn as_fd(&self) -> BorrowedFd<'_> {
251        self.fd.as_fd()
252    }
253}
254
255impl fmt::Debug for ParkedRingEventFd {
256    fn fmt(
257        &self,
258        f: &mut fmt::Formatter<'_>,
259    ) -> fmt::Result {
260        f.debug_struct("ParkedRingEventFd")
261            .field("fd", &self.fd.as_raw_fd())
262            .field("ring_kind", &self.ring.kind())
263            .finish_non_exhaustive()
264    }
265}
266
267impl Drop for ParkedRingEventFd {
268    fn drop(&mut self) {
269        self.stop.store(true, Ordering::Release);
270        // Release a driver waiting for a drain, then one parked on the word:
271        // the generation changes first so a driver between its check and its
272        // wait does not park after the wake.
273        {
274            let _pending = self.drained.pending.lock().unwrap();
275            self.drained.taken.notify_one();
276        }
277        self.ring.notification_generation().fetch_add(1, Ordering::SeqCst);
278        let _ = crate::sync::wake_word(self.ring.notification_generation());
279        if let Some(driver) = self.driver.take() {
280            let _ = driver.join();
281        }
282    }
283}
284
285#[cfg(any(target_os = "linux", target_os = "freebsd"))]
286fn local_notification_pair() -> io::Result<(Readiness, Signal)> {
287    crate::readiness::pair()
288}
289
290/// macOS needs 14.4 for the shared address wait the driver parks on, so a
291/// pair is refused there rather than handed out with nothing to feed it.
292#[cfg(target_os = "macos")]
293fn local_notification_pair() -> io::Result<(Readiness, Signal)> {
294    if crate::sync::macos::api().is_none() {
295        return Err(io::Error::new(
296            io::ErrorKind::Unsupported,
297            "Orbit native readiness requires macOS 14.4 or later",
298        ));
299    }
300    crate::readiness::pair()
301}