use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(transparent)]
pub struct RawSocket(pub i64);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Interest {
Read,
Write,
}
pub fn wait(sockets: &[(RawSocket, Interest)], deadline: Option<Instant>) -> Vec<RawSocket> {
if sockets.is_empty() {
if let Some(at) = deadline {
let now = Instant::now();
if at > now {
std::thread::sleep(at - now);
}
}
return Vec::new();
}
platform_wait(sockets, deadline)
}
fn remaining(deadline: Option<Instant>) -> Option<Duration> {
deadline.map(|at| {
let now = Instant::now();
if at > now {
at - now
} else {
Duration::ZERO
}
})
}
const ERROR_RETRY_BACKOFF: Duration = Duration::from_millis(10);
const LOG_RATE_LIMIT: Duration = Duration::from_secs(1);
struct LogGate(AtomicU64);
impl LogGate {
const fn new() -> Self {
Self(AtomicU64::new(0))
}
fn allow(&self) -> bool {
self.allow_at(crate::time::epoch().elapsed().as_millis() as u64)
}
fn allow_at(&self, now_ms: u64) -> bool {
let prev_raw = self.0.load(Ordering::Relaxed);
if prev_raw != 0 {
let prev_ms = prev_raw - 1;
if now_ms.saturating_sub(prev_ms) < LOG_RATE_LIMIT.as_millis() as u64 {
return false;
}
}
self.0.store(now_ms.saturating_add(1), Ordering::Relaxed);
true
}
}
pub fn set_nonblocking(socket: RawSocket) -> std::io::Result<()> {
platform_set_nonblocking(socket)
}
#[cfg(unix)]
fn platform_wait(sockets: &[(RawSocket, Interest)], deadline: Option<Instant>) -> Vec<RawSocket> {
use std::os::unix::io::RawFd;
static FD_SETSIZE_LOG_GATE: LogGate = LogGate::new();
static SELECT_ERROR_LOG_GATE: LogGate = LogGate::new();
loop {
let mut read_set: libc::fd_set = unsafe { std::mem::zeroed() };
let mut write_set: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut read_set);
libc::FD_ZERO(&mut write_set);
}
let mut max_fd: RawFd = -1;
let mut any_watched = false;
for (sock, interest) in sockets {
let fd = sock.0 as RawFd;
if fd < 0 || fd as usize >= libc::FD_SETSIZE {
if FD_SETSIZE_LOG_GATE.allow() {
tracing::warn!(
raw_socket = sock.0,
fd_setsize = libc::FD_SETSIZE,
"poll::wait: at least one socket is outside select's \
representable range and will never be watched on this \
platform (rate-limited: at most one line per second)"
);
}
continue;
}
any_watched = true;
match interest {
Interest::Read => unsafe { libc::FD_SET(fd, &mut read_set) },
Interest::Write => unsafe { libc::FD_SET(fd, &mut write_set) },
}
if fd > max_fd {
max_fd = fd;
}
}
let timeout = remaining(deadline);
if !any_watched {
std::thread::sleep(timeout.unwrap_or(ERROR_RETRY_BACKOFF));
return Vec::new();
}
let mut tv = timeout.map(select_timeout);
let tv_ptr = match tv.as_mut() {
None => std::ptr::null_mut(),
Some(tv) => std::ptr::addr_of_mut!(*tv),
};
let rc = unsafe {
libc::select(
max_fd + 1,
&mut read_set,
&mut write_set,
std::ptr::null_mut(),
tv_ptr,
)
};
if rc < 0 {
let err = std::io::Error::last_os_error();
if err.kind() == std::io::ErrorKind::Interrupted {
continue;
}
if SELECT_ERROR_LOG_GATE.allow() {
tracing::warn!(
error = %err,
"poll::wait: select failed; backing off rather than \
spinning (rate-limited: at most one line per second)"
);
}
std::thread::sleep(remaining(deadline).unwrap_or(ERROR_RETRY_BACKOFF));
return Vec::new();
}
let mut ready = Vec::new();
for (sock, interest) in sockets {
let fd = sock.0 as RawFd;
if fd < 0 || fd as usize >= libc::FD_SETSIZE {
continue;
}
let is_ready = match interest {
Interest::Read => unsafe { libc::FD_ISSET(fd, &read_set) },
Interest::Write => unsafe { libc::FD_ISSET(fd, &write_set) },
};
if is_ready {
ready.push(*sock);
}
}
return ready;
}
}
#[cfg(unix)]
fn select_timeout(d: std::time::Duration) -> libc::timeval {
let micros = d.as_micros();
let micros = if micros == 0 && !d.is_zero() {
1
} else {
micros
};
libc::timeval {
tv_sec: libc::time_t::try_from(micros / 1_000_000).unwrap_or(libc::time_t::MAX),
tv_usec: libc::suseconds_t::try_from(micros % 1_000_000).unwrap_or(libc::suseconds_t::MAX),
}
}
#[cfg(unix)]
fn platform_set_nonblocking(socket: RawSocket) -> std::io::Result<()> {
let fd = socket.0 as std::os::unix::io::RawFd;
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
if flags < 0 {
return Err(std::io::Error::last_os_error());
}
let rc = unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
if rc < 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
#[cfg(windows)]
fn platform_wait(sockets: &[(RawSocket, Interest)], deadline: Option<Instant>) -> Vec<RawSocket> {
use windows_sys::Win32::Networking::WinSock::{
WSAGetLastError, WSAPoll, POLLRDNORM, POLLWRNORM, SOCKET_ERROR, WSAEINTR, WSAPOLLFD,
};
static WSAPOLL_ERROR_LOG_GATE: LogGate = LogGate::new();
loop {
let mut fds: Vec<WSAPOLLFD> = sockets
.iter()
.map(|(sock, interest)| WSAPOLLFD {
fd: sock.0 as usize,
events: match interest {
Interest::Read => POLLRDNORM,
Interest::Write => POLLWRNORM,
},
revents: 0,
})
.collect();
let timeout_ms: i32 = match remaining(deadline) {
None => -1,
Some(d) => wsapoll_timeout_ms(d),
};
let rc = unsafe { WSAPoll(fds.as_mut_ptr(), fds.len() as u32, timeout_ms) };
if rc == SOCKET_ERROR {
let err = unsafe { WSAGetLastError() };
if err == WSAEINTR {
continue;
}
if WSAPOLL_ERROR_LOG_GATE.allow() {
let io_err = std::io::Error::from_raw_os_error(err);
tracing::warn!(
error = %io_err,
"poll::wait: WSAPoll failed; backing off rather than \
spinning (rate-limited: at most one line per second)"
);
}
std::thread::sleep(remaining(deadline).unwrap_or(ERROR_RETRY_BACKOFF));
return Vec::new();
}
let mut ready = Vec::new();
for (i, (sock, _)) in sockets.iter().enumerate() {
if let Some(entry) = fds.get(i) {
if entry.revents != 0 {
ready.push(*sock);
}
}
}
return ready;
}
}
#[cfg(windows)]
fn wsapoll_timeout_ms(d: std::time::Duration) -> i32 {
let millis = d.as_millis();
let millis = if millis == 0 && !d.is_zero() {
1
} else {
millis
};
i32::try_from(millis).unwrap_or(i32::MAX)
}
#[cfg(windows)]
fn platform_set_nonblocking(socket: RawSocket) -> std::io::Result<()> {
use windows_sys::Win32::Networking::WinSock::{ioctlsocket, FIONBIO, SOCKET_ERROR};
let sock = socket.0 as usize;
let mut mode: u32 = 1; let rc = unsafe { ioctlsocket(sock, FIONBIO, &mut mode) };
if rc == SOCKET_ERROR {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
const LIMIT_MS: u64 = LOG_RATE_LIMIT.as_millis() as u64;
#[test]
fn a_fresh_log_gate_allows_its_first_call() {
let gate = LogGate::new();
assert!(
gate.allow(),
"a fresh gate's first call must not be swallowed"
);
}
#[test]
fn allow_stamps_the_gate_with_a_live_clock_reading() {
let _ = crate::time::epoch();
std::thread::sleep(Duration::from_millis(30));
let before = crate::time::epoch().elapsed().as_millis() as u64;
let gate = LogGate::new();
assert!(gate.allow(), "a fresh gate's first call must be allowed");
let after = crate::time::epoch().elapsed().as_millis() as u64;
let recorded = gate.0.load(Ordering::Relaxed);
assert!(recorded != 0, "an allowed call must record a timestamp");
let stamped = recorded - 1;
assert!(
stamped >= before && stamped <= after,
"allow() must stamp the gate with what crate::time::epoch() reads at the \
moment of the call ({before}..={after}ms), not a constant: got \
{stamped}ms"
);
}
#[test]
fn a_log_gate_denies_a_second_call_inside_the_rate_limit_window() {
let gate = LogGate::new();
assert!(gate.allow_at(0), "first call must be allowed");
assert!(
!gate.allow_at(LIMIT_MS - 1),
"a second call inside the rate-limit window must be denied, \
or the gate is not rate-limiting anything"
);
}
#[test]
fn a_log_gate_allows_a_call_exactly_at_the_window_boundary() {
let gate = LogGate::new();
assert!(gate.allow_at(0), "first call must be allowed");
assert!(
gate.allow_at(LIMIT_MS),
"a call exactly LOG_RATE_LIMIT after the last allowed line is \
outside the window and must be allowed"
);
}
#[test]
fn a_log_gate_allows_again_once_the_rate_limit_window_elapses() {
let past_window = LIMIT_MS + 50;
let gate = LogGate::new();
assert!(gate.allow_at(0), "first call must be allowed");
assert!(
gate.allow_at(past_window),
"a call after the rate-limit window elapses must be allowed \
again, not stuck denied forever"
);
assert!(
!gate.allow_at(past_window + 1),
"allowing a line must re-arm the gate: the call right after one \
is inside a fresh window and must be denied"
);
}
#[test]
fn an_empty_socket_set_with_no_deadline_returns_immediately_with_nothing_ready() {
let ready = wait(&[], None);
assert!(
ready.is_empty(),
"no sockets and no deadline must not block or report anything ready"
);
}
#[test]
fn an_empty_socket_set_with_a_past_deadline_returns_immediately_with_nothing_ready() {
let past = Instant::now();
let ready = wait(&[], Some(past));
assert!(
ready.is_empty(),
"a due deadline must not sleep, and an empty socket set has \
nothing to report ready regardless"
);
}
#[cfg(unix)]
fn raw_of(s: &std::net::TcpStream) -> i64 {
use std::os::unix::io::AsRawFd;
i64::from(s.as_raw_fd())
}
#[cfg(windows)]
fn raw_of(s: &std::net::TcpStream) -> i64 {
use std::os::windows::io::AsRawSocket;
s.as_raw_socket() as i64
}
#[test]
fn wait_reports_a_socket_with_data_waiting() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind loopback");
let addr = listener.local_addr().expect("local addr");
let mut client = std::net::TcpStream::connect(addr).expect("connect");
let (mut server, _) = listener.accept().expect("accept");
std::io::Write::write_all(&mut server, b"hi").expect("write");
let sock = RawSocket(raw_of(&client));
let ready = wait(&[(sock, Interest::Read)], None);
assert_eq!(
ready,
vec![sock],
"the client socket has data and must be ready"
);
let _ = &mut client;
}
#[test]
fn wait_reports_a_socket_ready_for_write() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind loopback");
let addr = listener.local_addr().expect("local addr");
let client = std::net::TcpStream::connect(addr).expect("connect");
let (_server, _) = listener.accept().expect("accept");
let sock = RawSocket(raw_of(&client));
let ready = wait(&[(sock, Interest::Write)], None);
assert_eq!(
ready,
vec![sock],
"a freshly connected socket's send buffer is empty and must be write-ready"
);
}
#[test]
fn wait_returns_empty_when_the_deadline_passes_first() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind loopback");
let addr = listener.local_addr().expect("local addr");
let client = std::net::TcpStream::connect(addr).expect("connect");
let (_server, _) = listener.accept().expect("accept");
let sock = RawSocket(raw_of(&client));
let deadline = Instant::now() + Duration::from_millis(50);
let ready = wait(&[(sock, Interest::Read)], Some(deadline));
assert!(
ready.is_empty(),
"no data was ever written to the connected socket; the deadline must fire first"
);
}
#[test]
fn wait_with_no_sockets_and_a_deadline_sleeps_until_it() {
let interval = Duration::from_millis(40);
let start = Instant::now();
let ready = wait(&[], Some(start + interval));
assert!(
ready.is_empty(),
"an empty socket set has nothing to report ready"
);
assert!(
start.elapsed() >= interval,
"the timer-only path must actually sleep out the deadline, not return early"
);
}
#[test]
fn wait_with_no_deadline_blocks_until_a_socket_becomes_ready() {
const WRITE_DELAY: Duration = Duration::from_millis(50);
const MIN_BLOCKED: Duration = Duration::from_millis(40);
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind loopback");
let addr = listener.local_addr().expect("local addr");
let client = std::net::TcpStream::connect(addr).expect("connect");
let (mut server, _) = listener.accept().expect("accept");
let sock = RawSocket(raw_of(&client));
let start = Instant::now();
let writer = std::thread::spawn(move || {
std::thread::sleep(WRITE_DELAY);
std::io::Write::write_all(&mut server, b"late").expect("write");
});
let ready = wait(&[(sock, Interest::Read)], None);
let elapsed = start.elapsed();
writer.join().expect("writer thread");
assert_eq!(
ready,
vec![sock],
"an untimed wait must block until the socket genuinely becomes \
ready and then report it, not return early with nothing"
);
assert!(
elapsed >= MIN_BLOCKED,
"an untimed wait must actually block: nothing was written for \
{WRITE_DELAY:?}, so this wait cannot legitimately have returned \
after {elapsed:?}"
);
}
#[test]
fn set_nonblocking_makes_a_read_return_would_block_instead_of_blocking() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind loopback");
let addr = listener.local_addr().expect("local addr");
let mut client = std::net::TcpStream::connect(addr).expect("connect");
let _server = listener.accept().expect("accept");
set_nonblocking(RawSocket(raw_of(&client))).expect("set nonblocking");
let mut buf = [0u8; 8];
let err = std::io::Read::read(&mut client, &mut buf)
.expect_err("no data is waiting; a non-blocking read must not succeed");
assert_eq!(
err.kind(),
std::io::ErrorKind::WouldBlock,
"a non-blocking socket with nothing to read must fail with WouldBlock, not block"
);
}
#[cfg(unix)]
#[test]
fn a_sub_microsecond_wait_rounds_up_to_one_microsecond() {
let tv = select_timeout(Duration::from_nanos(1));
assert_eq!((tv.tv_sec, tv.tv_usec), (0, 1));
}
#[cfg(unix)]
#[test]
fn a_zero_wait_stays_zero() {
let tv = select_timeout(Duration::ZERO);
assert_eq!((tv.tv_sec, tv.tv_usec), (0, 0));
}
#[cfg(unix)]
#[test]
fn a_whole_second_is_carried_in_tv_sec() {
let tv = select_timeout(Duration::from_millis(1500));
assert_eq!((tv.tv_sec, tv.tv_usec), (1, 500_000));
}
#[cfg(windows)]
#[test]
fn a_sub_millisecond_wait_rounds_up_to_one_millisecond() {
assert_eq!(wsapoll_timeout_ms(Duration::from_micros(500)), 1);
}
#[cfg(windows)]
#[test]
fn a_zero_wait_stays_zero() {
assert_eq!(wsapoll_timeout_ms(Duration::ZERO), 0);
}
#[cfg(windows)]
#[test]
fn a_long_wait_saturates_rather_than_wrapping() {
assert_eq!(
wsapoll_timeout_ms(Duration::from_secs(u64::MAX / 1000)),
i32::MAX
);
}
}