use std::future::poll_fn;
use std::task::Poll;
use futures::StreamExt;
use futures::channel::{mpsc, oneshot};
use futures::io::{AsyncRead, AsyncWrite};
use tokio::sync::mpsc as tmpsc;
use tokio_util::sync::CancellationToken;
use yamux::{Connection, ConnectionError, Stream};
pub use yamux::{Config as MuxConfig, Mode};
const MAX_STREAMS: usize = 4096;
const MAX_CONNECTION_RECEIVE_WINDOW: usize = MAX_STREAMS * 256 * 1024;
const WRITE_BUF_SIZE: usize = 64 * 1024;
fn mux_config() -> MuxConfig {
let mut config = MuxConfig::default();
config.set_max_connection_receive_window(Some(MAX_CONNECTION_RECEIVE_WINDOW));
config.set_max_num_streams(MAX_STREAMS);
config
}
#[derive(Clone)]
pub struct MuxControl {
open_tx: mpsc::Sender<oneshot::Sender<Result<Stream, ConnectionError>>>,
}
impl MuxControl {
pub async fn open_stream(&self) -> anyhow::Result<Stream> {
let (tx, rx) = oneshot::channel();
self.open_tx
.clone()
.try_send(tx)
.map_err(|_| anyhow::anyhow!("mux connection is closed"))?;
let stream = rx
.await
.map_err(|_| anyhow::anyhow!("mux connection closed before stream opened"))??;
Ok(stream)
}
}
pub fn spawn<T>(socket: T, mode: Mode) -> (MuxControl, tmpsc::UnboundedReceiver<Stream>)
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
spawn_with_shutdown(socket, mode, CancellationToken::new())
}
pub fn spawn_with_shutdown<T>(
socket: T,
mode: Mode,
shutdown: CancellationToken,
) -> (MuxControl, tmpsc::UnboundedReceiver<Stream>)
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
let (open_tx, mut open_rx) =
mpsc::channel::<oneshot::Sender<Result<Stream, ConnectionError>>>(64);
let (inbound_tx, inbound_rx) = tmpsc::unbounded_channel();
tokio::spawn(async move {
let socket = futures::io::BufWriter::with_capacity(WRITE_BUF_SIZE, socket);
let mut conn = Connection::new(socket, mux_config(), mode);
let mut pending: std::collections::VecDeque<
oneshot::Sender<Result<Stream, ConnectionError>>,
> = std::collections::VecDeque::new();
loop {
enum Action {
Continue,
Stop,
}
let action = tokio::select! {
_ = shutdown.cancelled() => Action::Stop,
action = poll_fn(|cx| {
if pending.front().is_some()
&& let Poll::Ready(result) = conn.poll_new_outbound(cx)
{
let waiter = pending.pop_front().unwrap();
let _ = waiter.send(result);
return Poll::Ready(Action::Continue);
}
match open_rx.poll_next_unpin(cx) {
Poll::Ready(Some(waiter)) => {
pending.push_back(waiter);
return Poll::Ready(Action::Continue);
}
Poll::Ready(None) => {
}
Poll::Pending => {}
}
match conn.poll_next_inbound(cx) {
Poll::Ready(Some(Ok(stream))) => {
let _ = inbound_tx.send(stream);
return Poll::Ready(Action::Continue);
}
Poll::Ready(Some(Err(_))) | Poll::Ready(None) => {
return Poll::Ready(Action::Stop);
}
Poll::Pending => {}
}
Poll::Pending
}) => action,
};
match action {
Action::Continue => continue,
Action::Stop => break,
}
}
while let Some(waiter) = pending.pop_front() {
let _ = waiter.send(Err(ConnectionError::Closed));
}
while let Ok(waiter) = open_rx.try_recv() {
let _ = waiter.send(Err(ConnectionError::Closed));
}
});
(MuxControl { open_tx }, inbound_rx)
}