use std::io;
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
pub struct EventFd {
#[cfg(target_os = "linux")]
fd: OwnedFd,
#[cfg(not(target_os = "linux"))]
read_fd: OwnedFd,
#[cfg(not(target_os = "linux"))]
write_fd: OwnedFd,
}
impl EventFd {
pub fn new() -> io::Result<Self> {
#[cfg(target_os = "linux")]
{
let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) };
if fd < 0 {
return Err(io::Error::last_os_error());
}
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
Ok(Self { fd })
}
#[cfg(not(target_os = "linux"))]
{
let mut fds = [0; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } < 0 {
return Err(io::Error::last_os_error());
}
if let Err(err) = set_pipe_flags(fds[0]).and_then(|()| set_pipe_flags(fds[1])) {
unsafe {
libc::close(fds[0]);
libc::close(fds[1]);
}
return Err(err);
}
let read_fd = unsafe { OwnedFd::from_raw_fd(fds[0]) };
let write_fd = unsafe { OwnedFd::from_raw_fd(fds[1]) };
Ok(Self { read_fd, write_fd })
}
}
pub fn notify(&self) -> io::Result<()> {
let val: u64 = 1;
#[cfg(target_os = "linux")]
let fd = self.fd.as_raw_fd();
#[cfg(not(target_os = "linux"))]
let fd = self.write_fd.as_raw_fd();
let ret = unsafe { libc::write(fd, &val as *const u64 as *const libc::c_void, 8) };
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn try_read(&self) -> io::Result<u64> {
#[cfg(target_os = "linux")]
{
let mut val: u64 = 0;
let ret = unsafe {
libc::read(
self.fd.as_raw_fd(),
&mut val as *mut u64 as *mut libc::c_void,
8,
)
};
if ret < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::WouldBlock {
Ok(0)
} else {
Err(err)
}
} else {
Ok(val)
}
}
#[cfg(not(target_os = "linux"))]
{
let mut count = 0u64;
loop {
let mut val: u64 = 0;
let ret = unsafe {
libc::read(
self.read_fd.as_raw_fd(),
&mut val as *mut u64 as *mut libc::c_void,
8,
)
};
if ret == 8 {
count = count.saturating_add(val.max(1));
continue;
}
if ret < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::WouldBlock {
return Ok(count);
}
return Err(err);
}
return Ok(count);
}
}
}
pub fn as_fd(&self) -> RawFd {
#[cfg(target_os = "linux")]
{
self.fd.as_raw_fd()
}
#[cfg(not(target_os = "linux"))]
{
self.read_fd.as_raw_fd()
}
}
}
#[cfg(not(target_os = "linux"))]
fn set_pipe_flags(fd: RawFd) -> io::Result<()> {
let status_flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
if status_flags < 0 {
return Err(io::Error::last_os_error());
}
if unsafe { libc::fcntl(fd, libc::F_SETFL, status_flags | libc::O_NONBLOCK) } < 0 {
return Err(io::Error::last_os_error());
}
let fd_flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
if fd_flags < 0 {
return Err(io::Error::last_os_error());
}
if unsafe { libc::fcntl(fd, libc::F_SETFD, fd_flags | libc::FD_CLOEXEC) } < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
unsafe impl Send for EventFd {}
unsafe impl Sync for EventFd {}
pub struct WakePair {
pub producer_wake: EventFd,
pub consumer_wake: EventFd,
}
impl WakePair {
pub fn new() -> io::Result<Self> {
Ok(Self {
producer_wake: EventFd::new()?,
consumer_wake: EventFd::new()?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn notify_and_read() {
let efd = EventFd::new().unwrap();
assert_eq!(efd.try_read().unwrap(), 0);
efd.notify().unwrap();
assert_eq!(efd.try_read().unwrap(), 1);
assert_eq!(efd.try_read().unwrap(), 0);
}
#[test]
fn multiple_notifies_accumulate() {
let efd = EventFd::new().unwrap();
efd.notify().unwrap();
efd.notify().unwrap();
efd.notify().unwrap();
assert_eq!(efd.try_read().unwrap(), 3);
assert_eq!(efd.try_read().unwrap(), 0);
}
#[test]
fn wake_pair_bidirectional() {
let pair = WakePair::new().unwrap();
pair.consumer_wake.notify().unwrap();
assert_eq!(pair.consumer_wake.try_read().unwrap(), 1);
pair.producer_wake.notify().unwrap();
assert_eq!(pair.producer_wake.try_read().unwrap(), 1);
}
#[test]
fn fd_is_valid() {
let efd = EventFd::new().unwrap();
assert!(efd.as_fd() >= 0);
}
}