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(self.fd.as_raw_fd(), (&mut value as *mut u64).cast(), size_of::<u64>())
49 };
50 if read == size_of::<u64>() as isize {
51 total = total.saturating_add(value);
52 continue;
53 }
54 if read == 0 {
55 return Err(io::Error::new(
56 io::ErrorKind::UnexpectedEof,
57 "Orbit readiness closed while draining"
58 ));
59 }
60 if read < 0 {
61 let error = io::Error::last_os_error();
62 return match error.raw_os_error() {
63 Some(libc::EINTR) => continue,
64 Some(libc::EAGAIN) => Ok(total),
65 _ => Err(error)
66 };
67 }
68 return Err(io::Error::new(
69 io::ErrorKind::InvalidData,
70 "Orbit readiness returned a partial counter"
71 ));
72 }
73 }
74}
75
76impl Signal {
77 pub fn signal(&self) -> io::Result<()> {
80 let value = 1_u64;
81 loop {
82 let written = unsafe {
85 libc::write(self.fd.as_raw_fd(), (&value as *const u64).cast(), size_of::<u64>())
86 };
87 if written == size_of::<u64>() as isize {
88 return Ok(());
89 }
90 if written < 0 {
91 let error = io::Error::last_os_error();
92 return match error.raw_os_error() {
93 Some(libc::EINTR) => continue,
94 Some(libc::EAGAIN) => Ok(()),
95 _ => Err(error)
96 };
97 }
98 return Err(io::Error::new(
99 io::ErrorKind::WriteZero,
100 "Orbit readiness accepted a partial token"
101 ));
102 }
103 }
104}
105
106impl AsRawFd for Readiness {
107 fn as_raw_fd(&self) -> RawFd {
108 self.fd.as_raw_fd()
109 }
110}
111
112impl AsFd for Readiness {
113 fn as_fd(&self) -> BorrowedFd<'_> {
114 self.fd.as_fd()
115 }
116}
117
118impl AsRawFd for Signal {
119 fn as_raw_fd(&self) -> RawFd {
120 self.fd.as_raw_fd()
121 }
122}
123
124impl fmt::Debug for Readiness {
125 fn fmt(
126 &self,
127 f: &mut fmt::Formatter<'_>
128 ) -> fmt::Result {
129 f.debug_struct("Readiness").field("fd", &self.fd.as_raw_fd()).finish_non_exhaustive()
130 }
131}
132
133impl fmt::Debug for Signal {
134 fn fmt(
135 &self,
136 f: &mut fmt::Formatter<'_>
137 ) -> fmt::Result {
138 f.debug_struct("Signal").field("fd", &self.fd.as_raw_fd()).finish_non_exhaustive()
139 }
140}
141
142#[cfg(any(target_os = "linux", target_os = "freebsd"))]
145pub fn pair() -> io::Result<(Readiness, Signal)> {
146 let raw = unsafe { libc::eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) };
148 if raw < 0 {
149 return Err(io::Error::last_os_error());
150 }
151 let fd = unsafe { OwnedFd::from_raw_fd(raw) };
153 let signal = fd.try_clone()?;
154 Ok((Readiness { fd }, Signal { fd: signal }))
155}
156
157#[cfg(target_os = "macos")]
161pub fn pair() -> io::Result<(Readiness, Signal)> {
162 let mut raw = [-1; 2];
163 if unsafe { libc::pipe(raw.as_mut_ptr()) } < 0 {
165 return Err(io::Error::last_os_error());
166 }
167 let (read, write) = unsafe { (OwnedFd::from_raw_fd(raw[0]), OwnedFd::from_raw_fd(raw[1])) };
170 for fd in [&read, &write] {
171 let set = unsafe {
173 libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC) >= 0
174 && libc::fcntl(fd.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK) >= 0
175 };
176 if !set {
177 return Err(io::Error::last_os_error());
178 }
179 }
180 Ok((Readiness { fd: read }, Signal { fd: write }))
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn a_token_survives_the_pair_and_coalesces() {
189 let (readiness, signal) = pair().expect("a pair");
190 assert_eq!(readiness.drain().expect("empty"), 0);
191 signal.signal().expect("signal");
192 signal.signal().expect("signal");
193 assert!(readiness.drain().expect("drain") >= 1);
194 assert_eq!(readiness.drain().expect("drained"), 0);
195 }
196}
197
198#[cfg(all(test, target_os = "macos"))]
199mod pipe_tests {
200 use super::*;
201
202 #[test]
203 fn a_full_pipe_coalesces_and_rearms() {
204 let (read, write) = pair().unwrap();
205 for fd in [read.as_raw_fd(), write.as_raw_fd()] {
206 assert_ne!(unsafe { libc::fcntl(fd, libc::F_GETFL) } & libc::O_NONBLOCK, 0);
207 assert_ne!(unsafe { libc::fcntl(fd, libc::F_GETFD) } & libc::FD_CLOEXEC, 0);
208 }
209 let token = 1u64;
211 loop {
212 let n = unsafe { libc::write(write.as_raw_fd(), (&token as *const u64).cast(), 8) };
213 if n < 0 {
214 assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EAGAIN));
215 break;
216 }
217 assert_eq!(n, 8);
218 }
219 write.signal().unwrap();
220 let mut buffer = [0u64; 128];
221 loop {
222 let n = unsafe {
223 libc::read(read.as_raw_fd(), buffer.as_mut_ptr().cast(), size_of_val(&buffer))
224 };
225 if n < 0 {
226 assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EAGAIN));
227 break;
228 }
229 assert!(n > 0);
230 }
231 write.signal().unwrap();
232 let mut value = 0u64;
233 assert_eq!(unsafe { libc::read(read.as_raw_fd(), (&mut value as *mut u64).cast(), 8) }, 8);
234 assert_eq!(value, 1);
235 }
236}