use crate::{
io::tokio::Clock,
message::{simple::Message, Message as _},
socket::{
io::{rx, tx},
ring::{self, Consumer, Producer},
stats,
},
};
use core::future::Future;
use futures::future::poll_fn;
use s2n_quic_core::{
endpoint::Endpoint,
inet::{self, SocketAddress},
io::event_loop::{select::Select, EventLoop},
path::{self, mtu},
};
use std::{io, io::ErrorKind};
use tokio::runtime::Handle;
use turmoil::net::UdpSocket;
mod builder;
#[cfg(test)]
mod tests;
pub use builder::Builder;
pub type PathHandle = path::Tuple;
#[derive(Default)]
pub struct Io {
builder: Builder,
}
impl Io {
pub fn builder() -> Builder {
Builder::default()
}
pub fn new<A: turmoil::ToSocketAddrs + Send + Sync + 'static>(addr: A) -> io::Result<Self> {
let builder = Builder::default().with_address(addr)?;
Ok(Self { builder })
}
async fn setup<E: Endpoint<PathHandle = PathHandle>>(
self,
mut endpoint: E,
) -> io::Result<(impl Future<Output = ()>, SocketAddress)> {
let Builder {
handle: _,
socket,
addr,
mtu_config_builder,
} = self.builder;
let mtu_config = mtu_config_builder
.build()
.map_err(|err| io::Error::new(ErrorKind::InvalidInput, format!("{err}")))?;
endpoint.set_mtu_config(mtu_config);
let clock = Clock::default();
let socket = if let Some(socket) = socket {
socket
} else if let Some(addr) = addr {
UdpSocket::bind(&*addr).await?
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"missing bind address",
));
};
let local_addr = socket.local_addr()?;
let local_addr: inet::SocketAddress = local_addr.into();
let payload_len: usize = mtu_config.max_mtu().into();
let payload_len = payload_len as u32;
let entries = 1024;
let (rx, rx_producer) = {
let mut consumers = vec![];
let (producer, consumer) = ring::pair(entries, payload_len);
consumers.push(consumer);
let rx = rx::Rx::new(consumers, mtu_config.max_mtu(), local_addr.into());
(rx, producer)
};
let (tx, tx_consumer) = {
let mut producers = vec![];
let (producer, consumer) = ring::pair(entries, payload_len);
producers.push(producer);
let gso = crate::features::Gso::default();
gso.disable();
let tx = tx::Tx::new(producers, gso, mtu_config.max_mtu());
(tx, consumer)
};
let (stats_sender, stats_recv) = stats::channel();
tokio::spawn(run_io(socket, rx_producer, tx_consumer, stats_sender));
let event_loop = EventLoop {
clock,
rx,
tx,
endpoint,
cooldown: Default::default(),
stats: stats_recv,
}
.start(local_addr);
Ok((event_loop, local_addr))
}
pub fn start<E: Endpoint<PathHandle = PathHandle>>(
mut self,
endpoint: E,
) -> io::Result<(tokio::task::JoinHandle<()>, SocketAddress)> {
let handle = if let Some(handle) = self.builder.handle.take() {
handle
} else {
Handle::try_current().map_err(std::io::Error::other)?
};
let guard = handle.enter();
let task = handle.spawn(async move {
let (instance, _local_addr) = self.setup(endpoint).await.unwrap();
instance.await;
});
drop(guard);
let local_addr = Default::default();
Ok((task, local_addr))
}
}
async fn run_io(
socket: UdpSocket,
mut producer: Producer<Message>,
mut consumer: Consumer<Message>,
stats: stats::Sender,
) -> io::Result<()> {
let mut poll_producer = false;
loop {
let socket_ready = socket.readable();
let consumer_ready = poll_fn(|cx| consumer.poll_acquire(u32::MAX, cx));
let producer_ready = async {
if poll_producer {
poll_fn(|cx| producer.poll_acquire(u32::MAX, cx)).await
} else {
core::future::pending().await
}
};
let application_wakeup = core::future::pending();
let is_readable = Select::new(
consumer_ready,
producer_ready,
application_wakeup,
socket_ready,
)
.await
.unwrap()
.timeout_expired;
if is_readable {
let mut count = 0;
for entry in producer.data() {
let res = socket.try_recv_from(entry.payload_mut());
stats.recv().on_operation_result(&res, |_len| 1);
if let Ok((len, addr)) = res {
count += 1;
entry.set_remote_address(&(addr.into()));
unsafe {
entry.set_payload_len(len);
}
} else {
break;
}
}
producer.release(count);
poll_producer = producer.data().is_empty();
}
{
let mut count = 0;
for entry in consumer.data() {
let addr = *entry.remote_address();
let addr: std::net::SocketAddr = addr.into();
let payload = entry.payload_mut();
let res = socket.try_send_to(payload, addr);
stats.recv().on_operation_result(&res, |_len| 1);
if res.is_ok() {
count += 1;
} else {
break;
}
}
consumer.release(count);
}
if !(producer.is_open() && consumer.is_open()) {
return Ok(());
}
}
}