#[cfg(unix)]
use std::os::unix::io::RawFd;
#[cfg(not(unix))]
type RawFd = i32;
pub struct EventFd {
#[cfg(target_os = "linux")]
fd: RawFd,
#[cfg(all(unix, not(target_os = "linux")))]
read_fd: RawFd,
#[cfg(all(unix, not(target_os = "linux")))]
write_fd: RawFd,
}
impl EventFd {
pub fn new() -> crate::Result<Self> {
#[cfg(target_os = "linux")]
{
let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_SEMAPHORE) };
if fd < 0 {
return Err(crate::Error::Io(std::io::Error::last_os_error()));
}
Ok(Self { fd })
}
#[cfg(all(unix, not(target_os = "linux")))]
{
let mut fds = [0 as libc::c_int; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } < 0 {
return Err(crate::Error::Io(std::io::Error::last_os_error()));
}
if let Err(e) = set_pipe_flags(fds[0]).and_then(|()| set_pipe_flags(fds[1])) {
unsafe {
libc::close(fds[0]);
libc::close(fds[1]);
}
return Err(crate::Error::Io(e));
}
Ok(Self {
read_fd: fds[0],
write_fd: fds[1],
})
}
#[cfg(not(unix))]
{
Err(crate::Error::Io(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"data-plane core wake is only available on Unix platforms",
)))
}
}
pub fn as_raw_fd(&self) -> RawFd {
#[cfg(target_os = "linux")]
{
self.fd
}
#[cfg(all(unix, not(target_os = "linux")))]
{
self.read_fd
}
#[cfg(not(unix))]
{
-1
}
}
pub fn drain(&self) -> u64 {
#[cfg(target_os = "linux")]
{
let mut buf = 0u64;
let ret = unsafe {
libc::read(
self.fd,
&mut buf as *mut u64 as *mut libc::c_void,
std::mem::size_of::<u64>(),
)
};
if ret == 8 { buf } else { 0 }
}
#[cfg(all(unix, not(target_os = "linux")))]
{
let mut total: u64 = 0;
let mut buf = [0u8; 256];
loop {
let ret = unsafe {
libc::read(
self.read_fd,
buf.as_mut_ptr() as *mut libc::c_void,
buf.len(),
)
};
if ret > 0 {
total = total.saturating_add(ret as u64);
if (ret as usize) < buf.len() {
return total;
}
continue;
}
return total;
}
}
#[cfg(not(unix))]
{
0
}
}
pub fn poll_wait(&self, timeout_ms: i32) -> bool {
#[cfg(unix)]
{
let mut pfd = libc::pollfd {
fd: self.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
ret > 0 && (pfd.revents & libc::POLLIN) != 0
}
#[cfg(not(unix))]
{
let _ = timeout_ms;
false
}
}
pub fn notifier(&self) -> EventFdNotifier {
#[cfg(target_os = "linux")]
{
EventFdNotifier { fd: self.fd }
}
#[cfg(all(unix, not(target_os = "linux")))]
{
EventFdNotifier { fd: self.write_fd }
}
#[cfg(not(unix))]
{
EventFdNotifier {}
}
}
}
#[cfg(target_os = "linux")]
impl Drop for EventFd {
fn drop(&mut self) {
unsafe {
libc::close(self.fd);
}
}
}
#[cfg(all(unix, not(target_os = "linux")))]
impl Drop for EventFd {
fn drop(&mut self) {
unsafe {
libc::close(self.read_fd);
libc::close(self.write_fd);
}
}
}
#[cfg(all(unix, not(target_os = "linux")))]
fn set_pipe_flags(fd: RawFd) -> std::io::Result<()> {
let status_flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
if status_flags < 0 {
return Err(std::io::Error::last_os_error());
}
if unsafe { libc::fcntl(fd, libc::F_SETFL, status_flags | libc::O_NONBLOCK) } < 0 {
return Err(std::io::Error::last_os_error());
}
let fd_flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
if fd_flags < 0 {
return Err(std::io::Error::last_os_error());
}
if unsafe { libc::fcntl(fd, libc::F_SETFD, fd_flags | libc::FD_CLOEXEC) } < 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
#[derive(Clone, Copy)]
pub struct EventFdNotifier {
#[cfg(unix)]
fd: RawFd,
}
unsafe impl Send for EventFdNotifier {}
unsafe impl Sync for EventFdNotifier {}
impl EventFdNotifier {
pub fn notify(&self) {
#[cfg(target_os = "linux")]
{
let val: u64 = 1;
unsafe {
libc::write(
self.fd,
&val as *const u64 as *const libc::c_void,
std::mem::size_of::<u64>(),
);
}
}
#[cfg(all(unix, not(target_os = "linux")))]
{
let val: u8 = 1;
unsafe {
libc::write(self.fd, &val as *const u8 as *const libc::c_void, 1);
}
}
}
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[test]
fn create_and_signal() {
let efd = EventFd::new().unwrap();
let notifier = efd.notifier();
assert_eq!(efd.drain(), 0);
notifier.notify();
assert_eq!(efd.drain(), 1);
assert_eq!(efd.drain(), 0);
}
#[test]
#[cfg(target_os = "linux")]
fn multiple_signals_accumulate() {
let efd = EventFd::new().unwrap();
let notifier = efd.notifier();
notifier.notify();
notifier.notify();
notifier.notify();
assert_eq!(efd.drain(), 1);
assert_eq!(efd.drain(), 1);
assert_eq!(efd.drain(), 1);
assert_eq!(efd.drain(), 0);
}
#[test]
#[cfg(all(unix, not(target_os = "linux")))]
fn multiple_signals_coalesce() {
let efd = EventFd::new().unwrap();
let notifier = efd.notifier();
notifier.notify();
notifier.notify();
notifier.notify();
assert_eq!(efd.drain(), 3);
assert_eq!(efd.drain(), 0);
}
#[test]
fn poll_wait_timeout() {
let efd = EventFd::new().unwrap();
assert!(!efd.poll_wait(1));
}
#[test]
fn poll_wait_signaled() {
let efd = EventFd::new().unwrap();
let notifier = efd.notifier();
notifier.notify();
assert!(efd.poll_wait(100));
}
#[test]
fn notifier_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<EventFdNotifier>();
}
}