use std::{
os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
sync::mpsc,
thread,
time::{Duration, Instant},
};
use super::tests::ring_or_skip;
use super::*;
const WAKE: u64 = 1;
const READ: u64 = 2;
#[derive(Clone, Copy, Debug, PartialEq)]
enum Via {
Eventfd,
MsgNow,
MsgFolded,
}
struct End {
ring: Ring,
efd: OwnedFd,
buf: Box<u64>,
}
impl End {
fn new() -> Option<Self> {
let ring = ring_or_skip()?;
let efd = unsafe { OwnedFd::from_raw_fd(libc::eventfd(0, libc::EFD_CLOEXEC)) };
Some(Self {
ring,
efd,
buf: Box::new(0),
})
}
fn arm(&mut self) {
let sqe = Sqe::read(
self.efd.as_raw_fd(),
core::ptr::from_mut(&mut *self.buf).cast(),
8,
0,
)
.user_data(READ);
self.ring.push(&sqe).expect("room for the read");
}
fn wake(&mut self, via: Via, ring_fd: RawFd, efd: RawFd) {
match via {
Via::Eventfd => {
let one = 1u64;
let n = unsafe { libc::write(efd, (&one as *const u64).cast(), 8) };
assert_eq!(n, 8);
}
Via::MsgNow | Via::MsgFolded => {
let sqe = Sqe::msg_ring(ring_fd, WAKE, 0)
.flags(IOSQE_CQE_SKIP_SUCCESS)
.user_data(0);
self.ring.push(&sqe).expect("room for the message");
if via == Via::MsgNow {
self.ring.submit().expect("submit");
}
}
}
}
fn wait(&mut self, via: Via) {
loop {
match self.ring.submit_and_wait(1, Some(Duration::from_secs(10))) {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => panic!("wait: {e}"),
}
let mut woken = false;
for cqe in self.ring.completions() {
assert!(cqe.res >= 0, "a completion failed: {cqe:?}");
woken |= matches!(cqe.user_data, WAKE | READ);
}
if woken {
if via == Via::Eventfd {
self.arm();
}
return;
}
}
}
}
fn thread_cpu() -> Duration {
let mut ts = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe { libc::clock_gettime(libc::CLOCK_THREAD_CPUTIME_ID, &mut ts) };
Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32)
}
fn run(via: Via, rounds: u64) -> Option<(f64, f64)> {
let (tx, rx) = mpsc::channel::<Option<(RawFd, RawFd)>>();
let (back_tx, back_rx) = mpsc::channel::<(RawFd, RawFd)>();
let ponger = thread::spawn(move || {
let Some(mut me) = End::new() else {
tx.send(None).unwrap();
return None;
};
if via == Via::Eventfd {
me.arm();
}
tx.send(Some((me.ring.as_raw_fd(), me.efd.as_raw_fd())))
.unwrap();
let (peer_ring, peer_efd) = back_rx.recv().unwrap();
let cpu = thread_cpu();
for _ in 0..rounds {
me.wait(via);
me.wake(via, peer_ring, peer_efd);
}
me.ring.submit().unwrap();
Some(thread_cpu() - cpu)
});
let (peer_ring, peer_efd) = rx.recv().unwrap()?;
let mut me = End::new()?;
if via == Via::Eventfd {
me.arm();
}
back_tx
.send((me.ring.as_raw_fd(), me.efd.as_raw_fd()))
.unwrap();
let (start, cpu) = (Instant::now(), thread_cpu());
for _ in 0..rounds {
me.wake(via, peer_ring, peer_efd);
me.wait(via);
}
let wall = start.elapsed();
let cpu = thread_cpu() - cpu + ponger.join().unwrap()?;
let per = |d: Duration| d.as_nanos() as f64 / rounds as f64;
Some((per(wall), per(cpu)))
}
fn median(mut v: Vec<f64>) -> f64 {
v.sort_by(f64::total_cmp);
v[v.len() / 2]
}
#[test]
fn every_way_of_waking_a_ring_wakes_it() {
for via in [Via::Eventfd, Via::MsgNow, Via::MsgFolded] {
if run(via, 200).is_none() {
return;
}
}
}
#[test]
#[ignore = "measurement: run with --release -- --ignored --nocapture bench_cross_ring_wake"]
fn bench_cross_ring_wake() {
const ROUNDS: u64 = 50_000;
const RUNS: usize = 7;
for via in [Via::Eventfd, Via::MsgNow, Via::MsgFolded] {
let (mut wall, mut cpu) = (Vec::new(), Vec::new());
for _ in 0..RUNS {
let Some((w, c)) = run(via, ROUNDS) else {
return;
};
wall.push(w);
cpu.push(c);
}
println!(
"wake via={via:?} rounds={ROUNDS} runs={RUNS} median_ns_per_round_trip={:.0} \
median_cpu_ns_per_round_trip={:.0}",
median(wall),
median(cpu)
);
}
}