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