1#![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
24pub struct Readiness {
26 fd: OwnedFd,
27}
28
29pub struct Signal {
31 fd: OwnedFd,
32}
33
34impl Readiness {
35 pub fn drain(&self) -> io::Result<u64> {
42 let mut total = 0_u64;
43 loop {
44 let mut value = 0_u64;
45 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 pub fn signal(&self) -> io::Result<()> {
84 let value = 1_u64;
85 loop {
86 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#[cfg(any(target_os = "linux", target_os = "freebsd"))]
157pub fn pair() -> io::Result<(Readiness, Signal)> {
158 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 let fd = unsafe { OwnedFd::from_raw_fd(raw) };
166 let signal = fd.try_clone()?;
167 Ok((Readiness { fd }, Signal { fd: signal }))
168}
169
170#[cfg(target_os = "macos")]
174pub fn pair() -> io::Result<(Readiness, Signal)> {
175 let mut raw = [-1; 2];
176 if unsafe { libc::pipe(raw.as_mut_ptr()) } < 0 {
178 return Err(io::Error::last_os_error());
179 }
180 let (read, write) =
183 unsafe { (OwnedFd::from_raw_fd(raw[0]), OwnedFd::from_raw_fd(raw[1])) };
184 for fd in [&read, &write] {
185 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 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}