use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
pub struct WakePipe {
read_fd: OwnedFd,
write_fd: OwnedFd,
}
impl WakePipe {
pub fn new() -> Self {
let mut fds = [0i32; 2];
let ret = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert!(
ret == 0,
"pipe() failed: {}",
std::io::Error::last_os_error()
);
unsafe {
set_nonblock_cloexec(fds[0]);
set_nonblock_cloexec(fds[1]);
}
Self {
read_fd: unsafe { OwnedFd::from_raw_fd(fds[0]) },
write_fd: unsafe { OwnedFd::from_raw_fd(fds[1]) },
}
}
pub fn wake(&self) {
unsafe {
libc::write(self.write_fd.as_raw_fd(), [1u8].as_ptr().cast(), 1);
}
}
pub fn drain(&self) {
let mut buf = [0u8; 512];
loop {
let n =
unsafe { libc::read(self.read_fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) };
if n <= 0 {
break;
}
}
}
pub fn as_raw_fd(&self) -> RawFd {
self.read_fd.as_raw_fd()
}
}
impl Default for WakePipe {
fn default() -> Self {
Self::new()
}
}
unsafe fn set_nonblock_cloexec(fd: RawFd) {
unsafe {
let flags = libc::fcntl(fd, libc::F_GETFL);
assert!(
flags >= 0,
"fcntl(F_GETFL) failed: {}",
std::io::Error::last_os_error()
);
let ret = libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
assert!(
ret >= 0,
"fcntl(F_SETFL) failed: {}",
std::io::Error::last_os_error()
);
let flags = libc::fcntl(fd, libc::F_GETFD);
assert!(
flags >= 0,
"fcntl(F_GETFD) failed: {}",
std::io::Error::last_os_error()
);
let ret = libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC);
assert!(
ret >= 0,
"fcntl(F_SETFD) failed: {}",
std::io::Error::last_os_error()
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wake_and_drain() {
let pipe = WakePipe::new();
pipe.drain();
pipe.wake();
pipe.wake();
pipe.drain();
pipe.wake();
pipe.drain();
}
#[test]
fn fd_is_valid() {
let pipe = WakePipe::new();
let fd = pipe.as_raw_fd();
assert!(fd >= 0);
}
#[test]
fn nonblocking_read() {
let pipe = WakePipe::new();
pipe.drain();
}
}