1#![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
25pub struct Readiness {
27 fd: OwnedFd,
28}
29
30pub struct Signal {
32 fd: OwnedFd,
33}
34
35impl Readiness {
36 pub fn drain(&self) -> io::Result<u64> {
43 let mut total = 0_u64;
44 loop {
45 let mut value = 0_u64;
46 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 pub fn signal(&self) -> io::Result<()> {
85 let value = 1_u64;
86 loop {
87 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#[cfg(any(target_os = "linux", target_os = "freebsd"))]
152pub fn pair() -> io::Result<(Readiness, Signal)> {
153 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 let fd = unsafe { OwnedFd::from_raw_fd(raw) };
160 let signal = fd.try_clone()?;
161 Ok((Readiness { fd }, Signal { fd: signal }))
162}
163
164#[cfg(target_os = "macos")]
168pub fn pair() -> io::Result<(Readiness, Signal)> {
169 let mut raw = [-1; 2];
170 if unsafe { libc::pipe(raw.as_mut_ptr()) } < 0 {
172 return Err(io::Error::last_os_error());
173 }
174 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 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 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}