#![cfg(all(target_os = "linux", feature = "noq"))]
#[path = "support.rs"]
mod support;
use std::net::UdpSocket;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::task::Poll;
use moq_sock::shard::Group;
use moq_uring::{Config, Error, Worker, quic, udp};
fn worker() -> Option<Worker> {
match Worker::new(Config::default()) {
Ok(worker) => Some(worker),
Err(Error::Unsupported(reason)) => {
eprintln!("skipping io_uring workers test: {reason}");
None
}
Err(err) => panic!("worker setup failed: {err}"),
}
}
const ALPN: &str = "moq-uring-workers";
const WORKERS: u16 = 2;
const DIALS: usize = 32;
#[derive(Default)]
struct Stop {
stopped: AtomicBool,
waker: Mutex<Option<std::task::Waker>>,
}
impl Stop {
async fn wait(self: &Arc<Self>) {
std::future::poll_fn(|cx| {
if self.stopped.load(Ordering::Acquire) {
return Poll::Ready(());
}
*self.waker.lock().unwrap() = Some(cx.waker().clone());
Poll::Pending
})
.await
}
fn stop(&self) {
self.stopped.store(true, Ordering::Release);
if let Some(waker) = self.waker.lock().unwrap().take() {
waker.wake();
}
}
}
#[test]
fn a_steered_group_serves_a_shared_port() {
let Some(client_worker) = worker() else { return };
let certs = support::certs().expect("certificates");
let mut group = Group::acquire("127.0.0.1:0".parse().expect("addr"), WORKERS).expect("group");
let mut claims = Vec::new();
while let Some(member) = group.member() {
claims.push(member.bind().expect("bind group member"));
}
let mut group = group.complete(claims).expect("complete group");
let mut members = Vec::new();
while let Some(member) = group.member().expect("clone retained socket") {
members.push(member);
}
let addr = group.addr();
let accepted: Arc<Vec<AtomicUsize>> = Arc::new((0..WORKERS).map(|_| AtomicUsize::new(0)).collect());
let stops: Vec<Arc<Stop>> = (0..WORKERS).map(|_| Arc::new(Stop::default())).collect();
let (ready, started) = std::sync::mpsc::channel();
let threads: Vec<_> = members
.into_iter()
.zip(&stops)
.map(|(member, stop)| {
let accepted = accepted.clone();
let stop = stop.clone();
let cert = certs.cert.clone();
let key = certs.key.clone();
let ready = ready.clone();
std::thread::spawn(move || {
let shard = member.shard();
let mut worker = Worker::new(Config::default()).expect("worker");
let handle = worker.handle();
let socket = handle.udp(member, udp::Config::default()).expect("socket");
let mut server = quic::server::Config::new(quic::Identity::open(cert, key).expect("identity"));
server.alpn = vec![ALPN.to_string()];
let endpoint = quic::Endpoint::new(socket, quic::endpoint::Config::default().with_server(server))
.expect("endpoint");
handle.spawn(async move {
while endpoint.accept().await.is_ok() {
accepted[usize::from(shard.index())].fetch_add(1, Ordering::AcqRel);
}
});
ready.send(()).expect("test alive");
worker.block_on(stop.wait()).expect("worker loop");
})
})
.collect();
drop(ready);
for _ in 0..WORKERS {
started.recv().expect("a worker thread failed to start");
}
let mut client_worker = client_worker;
let handle = client_worker.handle();
let mut dial = quic::client::Config::new(addr, "localhost");
dial.alpn = vec![ALPN.to_string()];
dial.verify = false;
client_worker
.block_on(async {
for _ in 0..DIALS {
let socket = handle
.udp(UdpSocket::bind("127.0.0.1:0").expect("bind"), udp::Config::default())
.expect("client socket");
let mut conn = quic::client::connect(socket, &dial).await.expect("connect");
assert_eq!(
web_transport_trait::poll::Session::protocol(&conn),
Some(ALPN),
"negotiated ALPN"
);
web_transport_trait::poll::Session::close(&mut conn, 0, "done");
}
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
loop {
let total: usize = accepted.iter().map(|count| count.load(Ordering::Acquire)).sum();
if total == DIALS {
break;
}
assert!(
std::time::Instant::now() < deadline,
"only {total} of {DIALS} dials were accepted"
);
moq_uring::Timer::after(&handle, std::time::Duration::from_millis(10))
.wait()
.await;
}
})
.expect("client worker");
for (index, count) in accepted.iter().enumerate() {
assert!(count.load(Ordering::Acquire) > 0, "worker {index} accepted nothing");
}
for stop in &stops {
stop.stop();
}
for thread in threads {
thread.join().expect("worker thread");
}
}