use std::collections::hash_map::Entry;
use std::collections::{HashMap, VecDeque};
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use bytes::Bytes;
use futures::SinkExt;
use tokio::sync::{Mutex, mpsc, oneshot, watch};
use tokio_util::sync::CancellationToken;
use crate::Protocol;
use crate::proto::resumption::message::Data;
use crate::proto::resumption::{Command, ResumptionCodec, ResumptionMessage};
use crate::sync::{ChannelCommand, ChannelSink, ChannelStream, SharedReplaceableStream};
use crate::util::{DecodedSink, EncodedStream};
pub struct ResumptionMap<T: Protocol> {
inner: Arc<Mutex<HashMap<u64, ResumeHandle<T>>>>,
}
impl<T: Protocol> Default for ResumptionMap<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Protocol> ResumptionMap<T> {
pub fn new() -> Self {
Self::new_inner(HashMap::new())
}
fn new_inner(map: HashMap<u64, ResumeHandle<T>>) -> Self {
Self {
inner: Arc::new(Mutex::new(map)),
}
}
pub async fn clear(&mut self) {
self.inner.lock().await.clear();
}
}
impl<T: Protocol> Clone for ResumptionMap<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
#[derive(Debug)]
struct ResumeHandle<T: Protocol> {
tx: mpsc::Sender<oneshot::Sender<State<T>>>,
}
pub struct ResumptionServer<T>
where
T: Protocol + 'static,
{
stream: ChannelStream<ResumptionMessage, SharedReplaceableStream<EncodedStream<T>>>,
sink: ChannelSink<ResumptionMessage>,
resume_map: ResumptionMap<T>,
}
impl<T> ResumptionServer<T>
where
T: Protocol + Send,
T::Message: Send,
T::SinkHalf: Send,
T::StreamHalf: Send,
T::Codec: Send,
{
pub fn new(next: T) -> Self {
Self::new_with(next, ResumptionMap::new())
}
pub fn new_with(next: T, map: ResumptionMap<T>) -> Self {
let (resume_tx, resume_rx) = mpsc::channel(5);
let handle = ResumeHandle { tx: resume_tx };
let (stream, sink) = next.into_split();
let stream = SharedReplaceableStream::new(EncodedStream::new(stream, T::codec()));
let sink = Box::pin(DecodedSink::new(sink, T::codec()));
let state = State {
id: None,
stream: stream.clone(),
sink,
sink_closed: false,
pending_sink: VecDeque::new(),
old_stream: None,
old_sink: None,
};
let (sink_tx_ctrl, ctrl_rx_sink) = mpsc::channel(1);
let (ctrl_tx_stream, stream_rx_ctrl) = mpsc::unbounded_channel();
let (stream_next_tx, stream_next_rx) = watch::channel(false);
let token = CancellationToken::new();
let controller = Controller {
rx: ctrl_rx_sink,
tx: Some(ctrl_tx_stream),
state,
resume_rx,
resume_handle: Some(handle),
resume_map: map.clone(),
stream_open: token.clone(),
stream_next_tx,
};
controller.into_background();
Self {
stream: ChannelStream::new(stream_rx_ctrl, stream, Some(stream_next_rx), Some(token)),
sink: ChannelSink::new(sink_tx_ctrl),
resume_map: map,
}
}
pub fn handle(&self) -> &ResumptionMap<T> {
&self.resume_map
}
}
impl<T: Protocol> Protocol for ResumptionServer<T> {
type Message = ResumptionMessage;
type Codec = ResumptionCodec;
type StreamHalf = ChannelStream<ResumptionMessage, SharedReplaceableStream<EncodedStream<T>>>;
type SinkHalf = ChannelSink<ResumptionMessage>;
fn into_split(self) -> (Self::StreamHalf, Self::SinkHalf) {
(self.stream, self.sink)
}
fn codec() -> Self::Codec {
ResumptionCodec::new()
}
}
struct Controller<T: Protocol> {
rx: mpsc::Receiver<ChannelCommand<ResumptionMessage>>,
tx: Option<mpsc::UnboundedSender<ResumptionMessage>>,
state: State<T>,
resume_rx: mpsc::Receiver<oneshot::Sender<State<T>>>,
resume_handle: Option<ResumeHandle<T>>,
resume_map: ResumptionMap<T>,
stream_open: CancellationToken,
stream_next_tx: watch::Sender<bool>,
}
struct State<T: Protocol> {
id: Option<u64>,
stream: SharedReplaceableStream<EncodedStream<T>>,
sink: Pin<Box<DecodedSink<T>>>,
sink_closed: bool,
pending_sink: VecDeque<Bytes>,
old_stream: Option<Pin<Box<EncodedStream<T>>>>,
old_sink: Option<Pin<Box<DecodedSink<T>>>>,
}
#[derive(Debug, Eq, PartialEq, Clone)]
enum ControllerError {
StreamSendFailed,
StreamSenderDropped,
SinkSendFailed,
SinkFlushFailed,
SinkCloseFailed,
ResumeServerChannelClosed,
ResumeServerStateSendFailed,
ResumeServerTimeout,
ResumeClientMapTimeout,
ResumeClientHandleNone,
ResumeClientReplySendTimeout,
ResumeClientReplyRecvTimeout,
}
impl<T> Controller<T>
where
T: Protocol + Send + 'static,
T::Message: Send,
T::SinkHalf: Send,
T::StreamHalf: Send,
T::Codec: Send,
{
fn into_background(self) {
tokio::spawn(async move {
let result = self.run().await;
log::debug!("Controller finished: {result:?}");
result
});
}
async fn run(mut self) -> Result<(), ControllerError> {
loop {
let ready = *self.stream_next_tx.borrow();
tokio::select! {
biased;
result = async {
if let Some(chunk) = self.state.pending_sink.front() {
self.state.sink.send(chunk.clone()).await
} else {
std::future::pending().await
}
}, if !self.state.sink_closed && ready && !self.state.pending_sink.is_empty() => {
if result.map_err(|_| ControllerError::SinkSendFailed).is_ok() {
self.state.pending_sink.pop_front();
} else {
self.state.sink_closed = true;
}
}
result = self.resume_rx.recv(),
if !(self.resume_rx.is_closed() && self.resume_rx.is_empty()) => {
if let Some(reply_tx) = result {
return self.into_resumed(reply_tx);
}
}
result = self.rx.recv(),
if !(self.rx.is_closed() && self.rx.is_empty())
&& (!ready || self.state.pending_sink.is_empty()) => {
if let Some(cmd) = result
&& let Ok(Some(payload)) = self.process_cmd(cmd).await {
self.state.pending_sink.push_back(payload.chunk)
}
}
_ = self.stream_open.cancelled(),
if self.tx.is_some() => {
self.tx.take();
self.state.old_stream = None;
self.state.old_sink = None;
}
else => return Ok(())
}
if self.rx.is_closed()
&& self.rx.is_empty()
&& self.tx.is_none()
&& self.state.pending_sink.is_empty()
{
if self.state.sink_closed {
return Ok(());
} else {
return self.into_wait_resume(Duration::from_secs(120)).await;
}
}
}
}
async fn into_wait_resume(mut self, timeout: Duration) -> Result<(), ControllerError> {
let future = async {
match self.resume_rx.recv().await {
Some(reply_tx) => self.into_resumed(reply_tx),
None => Err(ControllerError::ResumeServerChannelClosed),
}
};
match tokio::time::timeout(timeout, future).await {
Ok(result) => result,
Err(_) => Err(ControllerError::ResumeServerTimeout),
}
}
fn into_resumed(mut self, reply_tx: oneshot::Sender<State<T>>) -> Result<(), ControllerError> {
self.close_sink_channel();
log::trace!("Trying to send my state to controller reply channel");
match reply_tx.send(self.state) {
Ok(()) => {
log::debug!("Successfully sent resume state to another controller");
Ok(())
}
Err(_) => {
log::debug!("Failed to send resume state to another controller");
Err(ControllerError::ResumeServerStateSendFailed)
}
}
}
fn close_sink_channel(&mut self) {
self.rx.close();
while let Ok(cmd) = self.rx.try_recv() {
if let ChannelCommand::Message(msg) = cmd
&& let Command::Forward(payload) = msg.command
{
self.state.pending_sink.push_back(payload.chunk);
}
}
}
fn send_stream(&mut self, cmd: Command) -> Result<(), ControllerError> {
if let Some(sender) = self.tx.as_mut() {
sender
.send(ResumptionMessage::from(cmd))
.map_err(|_| ControllerError::StreamSendFailed)
} else {
Err(ControllerError::StreamSenderDropped)
}
}
fn set_stream_next(&mut self, new_val: bool) {
self.stream_next_tx.send_if_modified(|cur_val| {
if *cur_val != new_val {
*cur_val = new_val;
true } else {
false }
});
}
async fn flush_sink(&mut self) -> Result<(), ControllerError> {
let ready = *self.stream_next_tx.borrow();
while ready && let Some(chunk) = self.state.pending_sink.pop_front() {
self.state
.sink
.send(chunk)
.await
.map_err(|_| ControllerError::SinkSendFailed)?;
}
self.state
.sink
.flush()
.await
.map_err(|_| ControllerError::SinkFlushFailed)
}
async fn close_sink(&mut self) -> Result<(), ControllerError> {
let _ = self.flush_sink().await;
self.state.sink_closed = true;
self.state
.sink
.close()
.await
.map_err(|_| ControllerError::SinkCloseFailed)
}
async fn process_cmd(
&mut self,
cmd: ChannelCommand<ResumptionMessage>,
) -> Result<Option<Data>, ControllerError> {
match cmd {
ChannelCommand::Message(msg) => self.process_msg(msg).await,
ChannelCommand::ExplicitFlush => self.flush_sink().await.map(|_| None),
ChannelCommand::ExplicitClose => self.close_sink().await.map(|_| None),
}
}
async fn process_msg(
&mut self,
msg: ResumptionMessage,
) -> Result<Option<Data>, ControllerError> {
match msg.command {
Command::Start(id) => self.start_id(id).await?,
Command::Resume(id) => self.resume_id(id).await?,
Command::ResumeOk(_) => {}
Command::ResumeErr(_) => {}
Command::Forward(payload) => return Ok(Some(payload)),
}
Ok(None)
}
async fn start_id(&mut self, id: u64) -> Result<(), ControllerError> {
let Some(handle) = self.resume_handle.take() else {
return Ok(());
};
self.set_stream_next(true);
let lock = tokio::time::timeout(Duration::from_secs(1), self.resume_map.inner.lock());
let Ok(mut map) = lock.await else {
return Ok(());
};
if let Entry::Vacant(entry) = map.entry(id) {
entry.insert(handle);
}
self.state.id = Some(id);
Ok(())
}
async fn resume_id(&mut self, id: u64) -> Result<(), ControllerError> {
match self.replace_and_resume(id).await {
Ok(()) => self.send_resume_ok(id),
Err(e) => {
log::debug!("Resumption attempt failed: {e:?}");
self.send_resume_err(id)
}
}
}
fn send_resume_err(&mut self, id: u64) -> Result<(), ControllerError> {
self.send_stream(Command::ResumeErr(id))?;
self.set_stream_next(false);
Ok(())
}
fn send_resume_ok(&mut self, id: u64) -> Result<(), ControllerError> {
self.send_stream(Command::ResumeOk(id))?;
self.set_stream_next(true);
Ok(())
}
async fn replace_and_resume(&mut self, id: u64) -> Result<(), ControllerError> {
log::debug!("Controller attempting to resume from {id}");
let (state_tx, state_rx) = oneshot::channel();
let Ok(mut map) =
tokio::time::timeout(Duration::from_secs(1), self.resume_map.inner.lock()).await
else {
return Err(ControllerError::ResumeClientMapTimeout);
};
let Some(handle) = map.remove(&id) else {
return Err(ControllerError::ResumeClientHandleNone);
};
let Ok(()) = handle
.tx
.send_timeout(state_tx, Duration::from_secs(1))
.await
else {
map.insert(id, handle);
return Err(ControllerError::ResumeClientReplySendTimeout);
};
let Ok(Ok(prev_state)) = tokio::time::timeout(Duration::from_secs(3), state_rx).await
else {
map.insert(id, handle);
return Err(ControllerError::ResumeClientReplyRecvTimeout);
};
log::debug!("Controller proceeding to resume from {id}");
let maybe_handle = self.resume_handle.take();
self.state.replace(prev_state, id).await;
if let Some(handle) = maybe_handle {
map.insert(id, handle);
}
self.state.id = Some(id);
Ok(())
}
}
impl<T> State<T>
where
T: Protocol + Send + 'static,
T::Message: Send,
T::SinkHalf: Send,
T::StreamHalf: Send,
T::Codec: Send,
{
async fn replace(&mut self, mut src: Self, id: u64) {
log::trace!("Controller {id} replacing stream");
let my_old_stream = match src.stream.take_inner().await {
Some(prev) => {
self.stream.replace_inner(prev).await
}
None => {
self.stream.take_inner().await
}
};
log::trace!("Controller {id} stream is replaced");
log::trace!("Controller {id} replacing sink");
let my_old_sink = std::mem::replace(&mut self.sink, src.sink);
log::trace!("Controller {id} sink is replaced");
for chunk in src.pending_sink {
self.pending_sink.push_back(chunk);
}
log::trace!("Controller {id} is replaced");
self.old_stream = my_old_stream;
self.old_sink = Some(my_old_sink);
}
}
#[cfg(test)]
pub mod tests {
use std::future::poll_fn;
use std::io;
use std::task::{Context, Poll, Waker};
use bytes::Bytes;
use futures::{FutureExt, SinkExt, StreamExt};
use rand::Rng;
use tokio::task::yield_now;
use tokio_test::io::Builder;
use crate::proto::resumption::message::{Command, ResumptionMessage};
use crate::proto::resumption::{ResumptionMap, ResumptionServer};
use crate::test::{TestCodec, TestIoProtocol};
fn encode_io(data: &'static str) -> Bytes {
TestCodec::encode_str(data)
}
fn forward_msg(data: &'static str) -> ResumptionMessage {
ResumptionMessage::from(Command::Forward(encode_io(data).into()))
}
async fn poll_next(
server: &mut ResumptionServer<TestIoProtocol>,
) -> Poll<Option<ResumptionMessage>> {
let mut fut = server.stream.next().boxed();
let mut cx = Context::from_waker(Waker::noop());
let mut f = poll_fn(move |cx| fut.poll_unpin(cx));
yield_now().await;
f.poll_unpin(&mut cx)
}
async fn assert_handshake(server: &mut ResumptionServer<TestIoProtocol>) -> u64 {
let id = rand::rng().next_u64();
assert!(server.sink.send(Command::Start(id).into()).await.is_ok());
yield_now().await;
id
}
#[tokio::test]
async fn replace() {
let io_a = Builder::new().read(b"a").read(b"a").build();
let proto_a = TestIoProtocol::from_mock(io_a);
let mut srv_a = ResumptionServer::new(proto_a);
let id_a: u64 = 123456789;
srv_a.sink.send(Command::Start(id_a).into()).await.unwrap();
yield_now().await;
assert_eq!(srv_a.stream.next().await, Some(encode_io("a").into()));
let map = srv_a.handle().clone();
let io_b = Builder::new().read(b"b").read(b"b").build();
let proto_b = TestIoProtocol::from_mock(io_b);
let mut srv_b = ResumptionServer::new_with(proto_b, map);
srv_b.sink.send(Command::Resume(id_a).into()).await.unwrap();
yield_now().await;
let msg = srv_b.stream.next().await.unwrap();
let Command::ResumeOk(id_b) = msg.command else {
unreachable!()
};
assert_eq!(id_a, id_b);
assert!(srv_a.stream.next().await.is_none());
assert!(srv_a.sink.send(Command::Start(123).into()).await.is_err());
assert_eq!(
srv_b.stream.next().await,
Some(ResumptionMessage::from(encode_io("a")))
);
assert!(srv_b.stream.next().await.is_none());
}
#[tokio::test]
async fn stream_data_before_handshake() {
let io = Builder::new().read(b"a").read(b"b").read(b"c").build();
let mut server = ResumptionServer::new(TestIoProtocol::from_mock(io));
for _ in 0..3 {
assert_eq!(poll_next(&mut server).await, Poll::Pending);
}
}
#[tokio::test]
async fn stream_data_after_handshake() {
let io = Builder::new().read(b"a").read(b"b").read(b"c").build();
let mut server = ResumptionServer::new(TestIoProtocol::from_mock(io));
assert_handshake(&mut server).await;
for char in ["a", "b", "c"] {
assert_eq!(
poll_next(&mut server).await,
Poll::Ready(Some(forward_msg(char)))
);
}
}
#[tokio::test]
async fn stream_data_after_sink_close() {
let io = Builder::new().read(b"a").read(b"b").read(b"c").build();
let mut server = ResumptionServer::new(TestIoProtocol::from_mock(io));
assert_handshake(&mut server).await;
server.sink.close().await.unwrap();
yield_now().await;
for char in ["a", "b", "c"] {
assert_eq!(
poll_next(&mut server).await,
Poll::Ready(Some(forward_msg(char)))
);
}
assert_eq!(server.stream.next().await, None);
}
#[tokio::test]
async fn sink_data_before_handshake() {
let io = Builder::new().write(b"a").write(b"b").write(b"c").build();
let mut server = ResumptionServer::new(TestIoProtocol::from_mock(io));
for char in ["a", "b", "c"] {
assert!(server.sink.send(forward_msg(char)).await.is_ok());
yield_now().await;
}
assert_handshake(&mut server).await;
}
#[tokio::test]
async fn sink_data_after_handshake() {
let io = Builder::new().write(b"a").write(b"b").write(b"c").build();
let mut server = ResumptionServer::new(TestIoProtocol::from_mock(io));
assert_handshake(&mut server).await;
for char in ["a", "b", "c"] {
assert!(server.sink.send(forward_msg(char)).await.is_ok());
yield_now().await;
}
}
#[tokio::test]
async fn sink_data_after_stream_eof() {
let io = Builder::new()
.read_error(io::ErrorKind::UnexpectedEof.into())
.write(b"a")
.write(b"b")
.write(b"c")
.build();
let mut server = ResumptionServer::new(TestIoProtocol::from_mock(io));
assert_handshake(&mut server).await;
assert_eq!(server.stream.next().await, None);
yield_now().await;
for char in ["a", "b", "c"] {
assert!(server.sink.send(forward_msg(char)).await.is_ok());
yield_now().await;
}
}
async fn server_init() -> (
ResumptionMap<TestIoProtocol>,
ResumptionServer<TestIoProtocol>,
u64,
) {
let io = Builder::new().read(b"a").read(b"b").build();
let proto = TestIoProtocol::from_mock(io);
let mut server = ResumptionServer::new(proto);
let handle = server.handle().clone();
let id = assert_handshake(&mut server).await;
assert_eq!(server.stream.next().await, Some(forward_msg("a")));
(handle, server, id)
}
async fn server_resume(
handle: ResumptionMap<TestIoProtocol>,
id: u64,
) -> ResumptionServer<TestIoProtocol> {
let io = Builder::new().build();
let proto = TestIoProtocol::from_mock(io);
let mut server = ResumptionServer::new_with(proto, handle);
let resume_msg = ResumptionMessage::new(Command::Resume(id));
assert!(server.sink.send(resume_msg).await.is_ok());
server
}
async fn assert_resume_success(mut server: ResumptionServer<TestIoProtocol>) {
assert!(matches!(
server.stream.next().await,
Some(ResumptionMessage {
command: Command::ResumeOk(_id),
})
));
assert_eq!(server.stream.next().await, Some(forward_msg("b")));
assert_eq!(server.stream.next().await, None);
}
#[tokio::test]
async fn resume_request_response_ok() {
let (map, mut server, id) = server_init().await;
let server2 = server_resume(map.clone(), id).await;
assert_resume_success(server2).await;
assert_eq!(server.stream.next().await, None);
}
#[tokio::test]
async fn resume_request_response_err() {
let (map, _, id) = server_init().await;
let mut server2 = server_resume(map.clone(), id + 1).await;
assert!(matches!(
server2.stream.next().await,
Some(ResumptionMessage {
command: Command::ResumeErr(_id),
})
));
}
#[tokio::test]
async fn resume_request_response_retry() {
let (map, mut server, id) = server_init().await;
let mut server2 = server_resume(map.clone(), id + 1).await;
assert!(matches!(
server2.stream.next().await,
Some(ResumptionMessage {
command: Command::ResumeErr(_id),
})
));
let server3 = server_resume(map.clone(), id).await;
assert_resume_success(server3).await;
assert_eq!(server.stream.next().await, None);
}
#[tokio::test]
async fn resume_with_data_first() {
let (map, mut server, id) = server_init().await;
let io = Builder::new().build();
let proto = TestIoProtocol::from_mock(io);
let mut server2 = ResumptionServer::new_with(proto, map);
for char in ["a", "b", "c"] {
assert!(server2.sink.send(forward_msg(char)).await.is_ok());
}
let resume_msg = ResumptionMessage::new(Command::Resume(id));
assert!(server2.sink.send(resume_msg).await.is_ok());
assert_resume_success(server2).await;
assert_eq!(server.stream.next().await, None);
}
}