mod task;
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use futures_util::Stream;
use tokio::sync::{mpsc, oneshot};
use crate::{
tungstenite::{self, handshake::client::Request, Message},
Connection, Socket, Timing, WsError,
};
use task::{Command, Receiver, Start, Task};
const COMMANDS: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Recovery {
Reconnect,
Skip,
Stop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Queue {
Bounded(usize),
Unbounded,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Decoded<E, Err> {
Nothing,
Event(E),
Events(Vec<E>),
Error(Err),
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Offline<C, A, E> {
Recorded(Result<A, E>),
Held(C),
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Outage<E> {
Closed {
code: Option<u16>,
reason: String,
},
Error(E),
Stale {
silent: Duration,
},
Rotation,
}
pub trait Protocol: Send + Sized + 'static {
type Event: Send + 'static;
type Error: std::error::Error + From<WsError> + Send + 'static;
type Membership: Send + 'static;
type Change: Send + 'static;
type Ack: Send + 'static;
fn handshake(&mut self) -> Result<Request, Self::Error>;
fn connect(
&mut self,
timeout: Duration,
) -> impl Future<Output = Result<Socket, Self::Error>> + Send {
let request = self.handshake();
async move {
let socket = crate::connect(request?, timeout).await?;
Ok::<_, Self::Error>(socket)
}
}
fn configure(&self, connection: Connection) -> Connection {
connection
}
fn replay(
&mut self,
connection: &mut Connection,
membership: &Self::Membership,
) -> impl Future<Output = Result<(), Self::Error>> + Send;
fn apply(
&mut self,
connection: &mut Connection,
membership: &mut Self::Membership,
change: Self::Change,
) -> impl Future<Output = Result<Self::Ack, Self::Error>> + Send;
fn record(
&mut self,
membership: &mut Self::Membership,
change: Self::Change,
) -> Offline<Self::Change, Self::Ack, Self::Error> {
let _ = membership;
Offline::Held(change)
}
fn decode(&mut self, frame: &Message) -> Decoded<Self::Event, Self::Error>;
fn is_live(&self, frame: &Message, decoded: &Decoded<Self::Event, Self::Error>) -> bool;
fn delivered(&self, frame: &Message, decoded: &Decoded<Self::Event, Self::Error>) -> bool;
fn ping(&mut self, connection: &mut Connection) -> Option<Message>;
fn follow_up(&mut self) -> Option<Message> {
None
}
fn wanted(membership: &Self::Membership) -> bool {
let _ = membership;
true
}
fn is_retriable(err: &Self::Error) -> bool;
fn recover(&self, err: &Self::Error) -> Recovery {
if Self::is_retriable(err) {
Recovery::Reconnect
} else {
Recovery::Stop
}
}
fn retry_after(&self, err: &Self::Error) -> Option<Duration> {
let _ = err;
None
}
fn disconnected(&mut self, outage: Outage<Self::Error>) -> Option<Self::Event> {
let _ = outage;
None
}
fn reconnected(&mut self) -> Self::Event;
fn reset(&mut self) {}
fn queue(&self) -> Queue;
fn stalled(silent: Duration) -> Self::Error {
WsError::no_answer(silent).into()
}
fn stopped() -> Self::Error {
WsError::from(tungstenite::Error::AlreadyClosed).into()
}
}
#[derive(Debug)]
pub struct Supervisor<P: Protocol> {
protocol: P,
timing: Timing,
}
impl<P: Protocol> Supervisor<P> {
pub fn new(protocol: P, timing: Timing) -> Self {
Self { protocol, timing }
}
pub async fn connect(
mut self,
mut membership: P::Membership,
) -> Result<Supervised<P>, P::Error> {
let live = task::attempt(&mut self.protocol, &self.timing, &mut membership).await?;
Ok(self.spawn(membership, Start::Up(Box::new(live))))
}
pub async fn start(mut self, mut membership: P::Membership) -> Result<Supervised<P>, P::Error> {
match task::attempt(&mut self.protocol, &self.timing, &mut membership).await {
Ok(live) => Ok(self.spawn(membership, Start::Up(Box::new(live)))),
Err(err) => match self.protocol.recover(&err) {
Recovery::Stop => Err(err),
Recovery::Reconnect | Recovery::Skip => {
Ok(self.spawn(membership, Start::Outage(err)))
}
},
}
}
fn spawn(self, membership: P::Membership, start: Start<P>) -> Supervised<P> {
let (events, receiver) = task::queue(self.protocol.queue());
let (commands, commands_rx) = mpsc::channel(COMMANDS);
let task = Task::new(self.protocol, self.timing, membership, events, commands_rx);
Supervised {
events: receiver,
commands,
task: tokio::spawn(task.run(start)),
}
}
}
pub struct Supervised<P: Protocol> {
events: Receiver<task::Item<P>>,
commands: mpsc::Sender<Command<P>>,
task: tokio::task::JoinHandle<bool>,
}
impl<P: Protocol> std::fmt::Debug for Supervised<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Supervised")
.field("running", &!self.task.is_finished())
.finish_non_exhaustive()
}
}
impl<P: Protocol> Supervised<P> {
pub fn membership(&self) -> MembershipHandle<P> {
MembershipHandle {
commands: self.commands.clone(),
}
}
pub async fn close(self) -> Result<(), P::Error> {
let Self {
events,
commands,
task,
} = self;
drop(events);
drop(commands);
match task.await {
Ok(false) => Ok(()),
Ok(true) | Err(_) => Err(P::stopped()),
}
}
}
impl<P: Protocol> Stream for Supervised<P> {
type Item = Result<P::Event, P::Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.get_mut().events.poll_recv(cx)
}
}
pub struct MembershipHandle<P: Protocol> {
commands: mpsc::Sender<Command<P>>,
}
impl<P: Protocol> Clone for MembershipHandle<P> {
fn clone(&self) -> Self {
Self {
commands: self.commands.clone(),
}
}
}
impl<P: Protocol> std::fmt::Debug for MembershipHandle<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MembershipHandle")
.field("running", &!self.commands.is_closed())
.finish()
}
}
impl<P: Protocol> MembershipHandle<P> {
pub async fn apply(&self, change: P::Change) -> Result<P::Ack, P::Error> {
let (reply, answer) = oneshot::channel();
self.commands
.send(Command { change, reply })
.await
.map_err(|_| P::stopped())?;
answer.await.map_err(|_| P::stopped())?
}
}
#[cfg(test)]
mod tests;