use std::io;
use std::time::Duration;
#[cfg(unix)]
pub trait Source: std::os::fd::AsRawFd {}
#[cfg(unix)]
impl<T: std::os::fd::AsRawFd> Source for T {}
#[cfg(windows)]
pub trait Source: std::os::windows::io::AsRawSocket {}
#[cfg(windows)]
impl<T: std::os::windows::io::AsRawSocket> Source for T {}
#[cfg(not(any(unix, windows)))]
pub trait Source {}
#[cfg(not(any(unix, windows)))]
impl<T> Source for T {}
pub struct Poller {
inner: Inner,
}
unsafe impl Send for Poller {}
impl Poller {
pub fn new() -> io::Result<Poller> {
Ok(Poller {
inner: Inner::new()?,
})
}
pub fn add(&mut self, src: &impl Source, token: u64) -> io::Result<()> {
self.inner.add(src, token)
}
pub fn remove(&mut self, token: u64) {
self.inner.remove(token);
}
pub fn wait(&mut self, out: &mut Vec<u64>, timeout: Duration) -> io::Result<()> {
out.clear();
self.inner.wait(out, timeout)
}
}
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "ios"))]
const EVENTS: usize = 64;
#[cfg(any(target_os = "macos", target_os = "ios"))]
use bsd as backend;
#[cfg(target_os = "linux")]
use linux as backend;
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "ios", windows)))]
use scan as backend;
#[cfg(windows)]
use win as backend;
use backend::Inner;
#[cfg(target_os = "linux")]
mod linux {
use super::{EVENTS, Source};
use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::time::Duration;
pub struct Inner {
epfd: OwnedFd,
events: Vec<libc::epoll_event>,
}
impl Inner {
pub fn new() -> io::Result<Inner> {
let fd = unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) };
if fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(Inner {
epfd: unsafe { OwnedFd::from_raw_fd(fd) },
events: vec![libc::epoll_event { events: 0, u64: 0 }; EVENTS],
})
}
pub fn add(&mut self, src: &impl Source, token: u64) -> io::Result<()> {
let mut ev = libc::epoll_event {
events: libc::EPOLLIN as u32,
u64: token,
};
let rc = unsafe {
libc::epoll_ctl(
self.epfd.as_raw_fd(),
libc::EPOLL_CTL_ADD,
src.as_raw_fd() as RawFd,
&raw mut ev,
)
};
if rc < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub fn remove(&mut self, _token: u64) {}
pub fn wait(&mut self, out: &mut Vec<u64>, timeout: Duration) -> io::Result<()> {
let ms = i32::try_from(timeout.as_millis()).unwrap_or(i32::MAX);
let n = unsafe {
libc::epoll_wait(
self.epfd.as_raw_fd(),
self.events.as_mut_ptr(),
EVENTS as i32,
ms,
)
};
if n < 0 {
let e = io::Error::last_os_error();
if e.kind() == io::ErrorKind::Interrupted {
return Ok(());
}
return Err(e);
}
for ev in &self.events[..n as usize] {
out.push(ev.u64);
}
Ok(())
}
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod bsd {
use super::{EVENTS, Source};
use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::ptr;
use std::time::Duration;
pub struct Inner {
kq: OwnedFd,
events: Vec<libc::kevent>,
}
fn blank() -> libc::kevent {
libc::kevent {
ident: 0,
filter: 0,
flags: 0,
fflags: 0,
data: 0,
udata: ptr::null_mut(),
}
}
impl Inner {
pub fn new() -> io::Result<Inner> {
let fd = unsafe { libc::kqueue() };
if fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(Inner {
kq: unsafe { OwnedFd::from_raw_fd(fd) },
events: vec![blank(); EVENTS],
})
}
pub fn add(&mut self, src: &impl Source, token: u64) -> io::Result<()> {
let mut change = blank();
change.ident = src.as_raw_fd() as usize;
change.filter = libc::EVFILT_READ;
change.flags = libc::EV_ADD | libc::EV_ENABLE;
change.udata = usize::try_from(token).unwrap_or(usize::MAX) as *mut libc::c_void;
let rc = unsafe {
libc::kevent(
self.kq.as_raw_fd(),
&raw const change,
1,
ptr::null_mut(),
0,
ptr::null(),
)
};
if rc < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub fn remove(&mut self, _token: u64) {}
pub fn wait(&mut self, out: &mut Vec<u64>, timeout: Duration) -> io::Result<()> {
let ts = libc::timespec {
tv_sec: libc::time_t::try_from(timeout.as_secs()).unwrap_or(libc::time_t::MAX),
tv_nsec: libc::c_long::from(timeout.subsec_nanos()),
};
let n = unsafe {
libc::kevent(
self.kq.as_raw_fd(),
ptr::null(),
0,
self.events.as_mut_ptr(),
EVENTS as i32,
&raw const ts,
)
};
if n < 0 {
let e = io::Error::last_os_error();
if e.kind() == io::ErrorKind::Interrupted {
return Ok(());
}
return Err(e);
}
for ev in &self.events[..n as usize] {
out.push(ev.udata as u64);
}
Ok(())
}
}
}
#[cfg(windows)]
mod win {
use super::Source;
use std::io;
use std::time::Duration;
use windows_sys::Win32::Networking::WinSock::{
POLLRDNORM, WSAGetLastError, WSAPOLLFD, WSAPoll,
};
pub struct Inner {
fds: Vec<WSAPOLLFD>,
tokens: Vec<u64>,
}
impl Inner {
pub fn new() -> io::Result<Inner> {
Ok(Inner {
fds: Vec::new(),
tokens: Vec::new(),
})
}
pub fn add(&mut self, src: &impl Source, token: u64) -> io::Result<()> {
self.fds.push(WSAPOLLFD {
fd: src.as_raw_socket() as usize,
events: POLLRDNORM,
revents: 0,
});
self.tokens.push(token);
Ok(())
}
pub fn remove(&mut self, token: u64) {
if let Some(i) = self.tokens.iter().position(|t| *t == token) {
self.fds.swap_remove(i);
self.tokens.swap_remove(i);
}
}
pub fn wait(&mut self, out: &mut Vec<u64>, timeout: Duration) -> io::Result<()> {
if self.fds.is_empty() {
if !timeout.is_zero() {
std::thread::sleep(timeout);
}
return Ok(());
}
let ms = i32::try_from(timeout.as_millis()).unwrap_or(i32::MAX);
let n = {
let len = u32::try_from(self.fds.len()).unwrap_or(u32::MAX);
unsafe { WSAPoll(self.fds.as_mut_ptr(), len, ms) }
};
if n < 0 {
return Err(io::Error::from_raw_os_error(unsafe { WSAGetLastError() }));
}
for (row, token) in self.fds.iter().zip(&self.tokens) {
if row.revents != 0 {
out.push(*token);
}
}
Ok(())
}
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "ios", windows)))]
mod scan {
use super::Source;
use std::io;
use std::time::Duration;
pub struct Inner {
tokens: Vec<u64>,
}
impl Inner {
pub fn new() -> io::Result<Inner> {
Ok(Inner { tokens: Vec::new() })
}
pub fn add(&mut self, _src: &impl Source, token: u64) -> io::Result<()> {
if !self.tokens.contains(&token) {
self.tokens.push(token);
}
Ok(())
}
pub fn remove(&mut self, token: u64) {
self.tokens.retain(|t| *t != token);
}
pub fn wait(&mut self, out: &mut Vec<u64>, timeout: Duration) -> io::Result<()> {
if !timeout.is_zero() {
std::thread::sleep(timeout);
}
out.extend_from_slice(&self.tokens);
Ok(())
}
}
}
#[cfg(test)]
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "ios", windows))]
mod tests {
use super::*;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
#[test]
fn a_listener_is_ready_only_when_somebody_is_waiting() {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind");
listener.set_nonblocking(true).expect("nonblocking");
let addr = listener.local_addr().expect("addr");
let mut poller = Poller::new().expect("poller");
poller.add(&listener, 7).expect("add");
let mut ready = Vec::new();
poller.wait(&mut ready, Duration::ZERO).expect("wait");
assert!(ready.is_empty(), "nothing has connected yet");
let _client = TcpStream::connect(addr).expect("connect");
poller
.wait(&mut ready, Duration::from_secs(2))
.expect("wait");
assert_eq!(ready, vec![7]);
}
#[test]
fn a_quiet_connection_is_not_reported_and_a_busy_one_is() {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind");
let addr = listener.local_addr().expect("addr");
let mut client = TcpStream::connect(addr).expect("connect");
let (server, _) = listener.accept().expect("accept");
server.set_nonblocking(true).expect("nonblocking");
let mut poller = Poller::new().expect("poller");
poller.add(&server, 11).expect("add");
let mut ready = Vec::new();
poller.wait(&mut ready, Duration::ZERO).expect("wait");
assert!(ready.is_empty(), "the client has not said anything");
client.write_all(b"PING\r\n").expect("write");
poller
.wait(&mut ready, Duration::from_secs(2))
.expect("wait");
assert_eq!(ready, vec![11]);
poller.wait(&mut ready, Duration::ZERO).expect("wait");
assert_eq!(ready, vec![11]);
let mut buf = [0u8; 16];
let mut server = server;
let n = server.read(&mut buf).expect("read");
assert_eq!(&buf[..n], b"PING\r\n");
poller.wait(&mut ready, Duration::ZERO).expect("wait");
assert!(ready.is_empty(), "everything on it has been read");
}
}