mod task;
use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::Duration,
};
use futures_util::Stream;
use polyoxide_rt::{default_runtime, Runtime};
use tokio::sync::{mpsc, oneshot};
use crate::{
default_socket_transport,
tungstenite::{self, handshake::client::Request, Message},
BoxSocket, Connection, SocketTransport, 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,
transport: Arc<dyn SocketTransport>,
runtime: Arc<dyn Runtime>,
timeout: Duration,
) -> impl Future<Output = Result<BoxSocket, Self::Error>> + Send {
let request = self.handshake();
async move {
let socket = transport.connect(request?, timeout, runtime).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,
runtime: Option<Arc<dyn Runtime>>,
transport: Option<Arc<dyn SocketTransport>>,
}
impl<P: Protocol> Supervisor<P> {
pub fn new(protocol: P, timing: Timing) -> Self {
Self {
protocol,
timing,
runtime: None,
transport: None,
}
}
pub fn runtime(mut self, runtime: Arc<dyn Runtime>) -> Self {
self.runtime = Some(runtime);
self
}
pub fn socket_transport(mut self, transport: Arc<dyn SocketTransport>) -> Self {
self.transport = Some(transport);
self
}
fn injected(&self) -> (Arc<dyn Runtime>, Arc<dyn SocketTransport>) {
(
self.runtime.clone().unwrap_or_else(default_runtime),
self.transport
.clone()
.unwrap_or_else(default_socket_transport),
)
}
pub async fn connect(
mut self,
mut membership: P::Membership,
) -> Result<Supervised<P>, P::Error> {
let (runtime, transport) = self.injected();
let live = task::attempt(
&mut self.protocol,
&self.timing,
&mut membership,
&transport,
&runtime,
)
.await?;
Ok(self.spawn(membership, Start::Up(Box::new(live)), runtime, transport))
}
pub async fn start(mut self, mut membership: P::Membership) -> Result<Supervised<P>, P::Error> {
let (runtime, transport) = self.injected();
match task::attempt(
&mut self.protocol,
&self.timing,
&mut membership,
&transport,
&runtime,
)
.await
{
Ok(live) => Ok(self.spawn(membership, Start::Up(Box::new(live)), runtime, transport)),
Err(err) => match self.protocol.recover(&err) {
Recovery::Stop => Err(err),
Recovery::Reconnect | Recovery::Skip => {
Ok(self.spawn(membership, Start::Outage(err), runtime, transport))
}
},
}
}
fn spawn(
self,
membership: P::Membership,
start: Start<P>,
runtime: Arc<dyn Runtime>,
transport: Arc<dyn SocketTransport>,
) -> 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,
Arc::clone(&runtime),
transport,
);
Supervised {
events: receiver,
commands,
task: polyoxide_rt::spawn(runtime.as_ref(), task.run(start)),
}
}
}
pub struct Supervised<P: Protocol> {
events: Receiver<task::Item<P>>,
commands: mpsc::Sender<Command<P>>,
task: polyoxide_rt::Task<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 {
Some(false) => Ok(()),
Some(true) | None => 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;