Skip to main content

orbit_core/
readiness.rs

1//! A descriptor pair for turning a shared-memory change into fd readiness.
2//!
3//! The waiting primitive in Orbit is a word ([`crate::sync`]), which a
4//! runtime with a reactor of its own cannot park on. The bridge is always
5//! the same: something already watching the word — a ring's driver, a
6//! stream table's driver — writes a token, and the consumer's poll set
7//! wakes. This is that pair, and nothing more: no thread, no policy about
8//! who signals or when.
9//!
10//! An `eventfd` where there is one, a pipe where there is not. Both ends
11//! are nonblocking and close-on-exec, so a signal never blocks its writer
12//! and neither end survives an `exec`.
13//!
14//! Readiness is edge-triggered and coalescing by nature: a token says
15//! something may have changed, never what or how much. A consumer drains
16//! and then re-reads the shared state it cares about.
17
18#![cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
19
20use std::fmt;
21use std::io;
22use std::mem::size_of;
23use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
24
25/// The consumer's end: put it in a poll set, drain it, re-read your state.
26pub struct Readiness {
27    fd: OwnedFd,
28}
29
30/// The signaller's end, held by whoever watches the shared word.
31pub struct Signal {
32    fd: OwnedFd,
33}
34
35impl Readiness {
36    /// Take every token the descriptor holds and return how many there
37    /// were. Never blocks; a drained descriptor answers `Ok(0)`.
38    ///
39    /// The count is a local wake count. It says nothing about how many
40    /// things changed in shared memory, which is what the consumer must
41    /// re-read for itself.
42    pub fn drain(&self) -> io::Result<u64> {
43        let mut total = 0_u64;
44        loop {
45            let mut value = 0_u64;
46            // SAFETY: a nonblocking descriptor this type owns, and a u64
47            // of our own to read into.
48            let read = unsafe {
49                libc::read(
50                    self.fd.as_raw_fd(),
51                    (&mut value as *mut u64).cast(),
52                    size_of::<u64>(),
53                )
54            };
55            if read == size_of::<u64>() as isize {
56                total = total.saturating_add(value);
57                continue;
58            }
59            if read == 0 {
60                return Err(io::Error::new(
61                    io::ErrorKind::UnexpectedEof,
62                    "Orbit readiness closed while draining",
63                ));
64            }
65            if read < 0 {
66                let error = io::Error::last_os_error();
67                return match error.raw_os_error() {
68                    Some(libc::EINTR) => continue,
69                    Some(libc::EAGAIN) => Ok(total),
70                    _ => Err(error),
71                };
72            }
73            return Err(io::Error::new(
74                io::ErrorKind::InvalidData,
75                "Orbit readiness returned a partial counter",
76            ));
77        }
78    }
79}
80
81impl Signal {
82    /// Make the consumer's end readable. A full descriptor is already
83    /// readable, so a token that cannot be added is not a lost signal.
84    pub fn signal(&self) -> io::Result<()> {
85        let value = 1_u64;
86        loop {
87            // SAFETY: a nonblocking descriptor this type owns, and a u64
88            // of our own to write from.
89            let written = unsafe {
90                libc::write(
91                    self.fd.as_raw_fd(),
92                    (&value as *const u64).cast(),
93                    size_of::<u64>(),
94                )
95            };
96            if written == size_of::<u64>() as isize {
97                return Ok(());
98            }
99            if written < 0 {
100                let error = io::Error::last_os_error();
101                return match error.raw_os_error() {
102                    Some(libc::EINTR) => continue,
103                    Some(libc::EAGAIN) => Ok(()),
104                    _ => Err(error),
105                };
106            }
107            return Err(io::Error::new(
108                io::ErrorKind::WriteZero,
109                "Orbit readiness accepted a partial token",
110            ));
111        }
112    }
113}
114
115impl AsRawFd for Readiness {
116    fn as_raw_fd(&self) -> RawFd {
117        self.fd.as_raw_fd()
118    }
119}
120
121impl AsFd for Readiness {
122    fn as_fd(&self) -> BorrowedFd<'_> {
123        self.fd.as_fd()
124    }
125}
126
127impl AsRawFd for Signal {
128    fn as_raw_fd(&self) -> RawFd {
129        self.fd.as_raw_fd()
130    }
131}
132
133impl fmt::Debug for Readiness {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        f.debug_struct("Readiness")
136            .field("fd", &self.fd.as_raw_fd())
137            .finish_non_exhaustive()
138    }
139}
140
141impl fmt::Debug for Signal {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        f.debug_struct("Signal")
144            .field("fd", &self.fd.as_raw_fd())
145            .finish_non_exhaustive()
146    }
147}
148
149/// One `eventfd`, cloned: both ends are the same object, so the order
150/// they are dropped in does not matter.
151#[cfg(any(target_os = "linux", target_os = "freebsd"))]
152pub fn pair() -> io::Result<(Readiness, Signal)> {
153    // SAFETY: a plain syscall with constant flags.
154    let raw = unsafe { libc::eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) };
155    if raw < 0 {
156        return Err(io::Error::last_os_error());
157    }
158    // SAFETY: a fresh descriptor this call owns.
159    let fd = unsafe { OwnedFd::from_raw_fd(raw) };
160    let signal = fd.try_clone()?;
161    Ok((Readiness { fd }, Signal { fd: signal }))
162}
163
164/// A pipe, whose two ends are distinct: closing the read end first makes
165/// the write end's `signal` fail, which is how a signaller learns its
166/// consumer is gone.
167#[cfg(target_os = "macos")]
168pub fn pair() -> io::Result<(Readiness, Signal)> {
169    let mut raw = [-1; 2];
170    // SAFETY: a plain syscall writing two descriptors into our array.
171    if unsafe { libc::pipe(raw.as_mut_ptr()) } < 0 {
172        return Err(io::Error::last_os_error());
173    }
174    // Own both ends before any fallible setup, so neither leaks.
175    // SAFETY: two fresh descriptors this call owns.
176    let (read, write) = unsafe {
177        (
178            OwnedFd::from_raw_fd(raw[0]),
179            OwnedFd::from_raw_fd(raw[1]),
180        )
181    };
182    for fd in [&read, &write] {
183        // SAFETY: descriptors this call owns.
184        let set = unsafe {
185            libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC) >= 0
186                && libc::fcntl(fd.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK) >= 0
187        };
188        if !set {
189            return Err(io::Error::last_os_error());
190        }
191    }
192    Ok((Readiness { fd: read }, Signal { fd: write }))
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn a_token_survives_the_pair_and_coalesces() {
201        let (readiness, signal) = pair().expect("a pair");
202        assert_eq!(readiness.drain().expect("empty"), 0);
203        signal.signal().expect("signal");
204        signal.signal().expect("signal");
205        assert!(readiness.drain().expect("drain") >= 1);
206        assert_eq!(readiness.drain().expect("drained"), 0);
207    }
208}
209
210#[cfg(all(test, target_os = "macos"))]
211mod pipe_tests {
212    use super::*;
213
214    #[test]
215    fn a_full_pipe_coalesces_and_rearms() {
216        let (read, write) = pair().unwrap();
217        for fd in [read.as_raw_fd(), write.as_raw_fd()] {
218            assert_ne!(
219                unsafe { libc::fcntl(fd, libc::F_GETFL) } & libc::O_NONBLOCK,
220                0
221            );
222            assert_ne!(
223                unsafe { libc::fcntl(fd, libc::F_GETFD) } & libc::FD_CLOEXEC,
224                0
225            );
226        }
227        // Fill the pipe deliberately, then exercise the bridge's EAGAIN path.
228        let token = 1u64;
229        loop {
230            let n = unsafe { libc::write(write.as_raw_fd(), (&token as *const u64).cast(), 8) };
231            if n < 0 {
232                assert_eq!(
233                    io::Error::last_os_error().raw_os_error(),
234                    Some(libc::EAGAIN)
235                );
236                break;
237            }
238            assert_eq!(n, 8);
239        }
240        write.signal().unwrap();
241        let mut buffer = [0u64; 128];
242        loop {
243            let n = unsafe {
244                libc::read(
245                    read.as_raw_fd(),
246                    buffer.as_mut_ptr().cast(),
247                    size_of_val(&buffer),
248                )
249            };
250            if n < 0 {
251                assert_eq!(
252                    io::Error::last_os_error().raw_os_error(),
253                    Some(libc::EAGAIN)
254                );
255                break;
256            }
257            assert!(n > 0);
258        }
259        write.signal().unwrap();
260        let mut value = 0u64;
261        assert_eq!(
262            unsafe { libc::read(read.as_raw_fd(), (&mut value as *mut u64).cast(), 8) },
263            8
264        );
265        assert_eq!(value, 1);
266    }
267}