#![cfg_attr(
not(all(feature = "alpaca", feature = "lse", feature = "massive")),
allow(dead_code, unused_imports)
)]
mod actor;
#[cfg(any(feature = "alpaca", feature = "massive"))]
pub(crate) mod elements;
mod registry;
pub(crate) use registry::{Registration, Registry};
use self::actor::Actor;
use fnv::FnvHashMap;
use futures::Stream;
use rustrade_instrument::exchange::ExchangeId;
use rustrade_integration::{
error::SocketError,
protocol::websocket::{WebSocket, WsError, WsMessage},
};
use std::{
fmt::Debug,
future::Future,
hash::Hash,
pin::Pin,
sync::{Mutex, PoisonError},
task::{Context, Poll},
time::Duration,
};
use tokio::sync::{mpsc, oneshot};
use tracing::error;
use url::Url;
pub const REATTACH_GRACE: Duration = Duration::from_secs(30);
const RELEASE_TIMEOUT: Duration = Duration::from_secs(10);
pub(crate) type AttachId = u64;
pub(crate) trait Protocol: Debug + Sized + Send + 'static {
const NAME: &'static str;
const CONNECTION_PER_ENDPOINT: bool;
const REFUSAL_IS_ATOMIC: bool;
const KEEPALIVE: Option<Duration> = None;
const DISCARDED_ON_LOSS: &'static str = "";
const DISCARDED_ON_EXPIRY: &'static str = "";
type Credentials: Clone + Debug + Send + Sync + 'static;
type Slot: Clone + Eq + Hash + Debug + Send + 'static;
type Batch: Batch;
type Session: Send;
type Answer: Debug + Send;
type Handshake: Handshake<Self::Answer>;
fn connect(
credentials: &Self::Credentials,
url: &Url,
) -> impl Future<Output = Result<(WebSocket, Self::Session), SocketError>> + Send;
fn check(
_request: &AttachRequest<Self>,
_session: &Self::Session,
_registry: &Registry<Self>,
) -> Result<(), SocketError> {
Ok(())
}
fn subscribe(
registry: &mut Registry<Self>,
id: AttachId,
sending: &[Self::Slot],
) -> (Vec<WsMessage>, Self::Handshake);
fn release(slots: &[Self::Slot]) -> (Vec<WsMessage>, Option<Self::Handshake>);
fn read(registry: &mut Registry<Self>, message: &WsMessage) -> Frame<Self::Answer>;
fn refused(error: SocketError) -> SocketError {
error
}
}
pub(crate) trait Batch: Debug + Send + 'static {
type Attached: Debug + Default + Send;
fn is_same_stream(&self, other: &Self) -> bool;
fn attached(&self) -> Self::Attached;
fn reset(&mut self);
}
impl Batch for () {
type Attached = ();
fn is_same_stream(&self, _: &Self) -> bool {
true
}
fn attached(&self) {}
fn reset(&mut self) {}
}
pub(crate) trait Handshake<Answer>: Send {
fn is_settled(&self) -> bool;
fn observe(&mut self, answer: Answer);
fn timed_out(&self, timeout: Duration) -> String;
fn refusal(&self, error: SocketError) -> SocketError {
error
}
}
pub(crate) enum Frame<Answer> {
Answers(Vec<Result<Answer, SocketError>>),
Closed(String),
}
#[derive(Debug)]
pub(crate) struct AttachRequest<P: Protocol> {
pub(crate) exchange: ExchangeId,
pub(crate) url: Url,
pub(crate) kind: &'static str,
pub(crate) slots: Vec<P::Slot>,
pub(crate) timeout: Duration,
pub(crate) batch: P::Batch,
}
#[derive(Debug)]
enum Command<P: Protocol> {
Attach(
Box<AttachRequest<P>>,
oneshot::Sender<Result<Attachment<P>, SocketError>>,
),
Detach(AttachId),
}
#[derive(Debug)]
pub(crate) struct Connections<P: Protocol> {
credentials: P::Credentials,
tasks: Mutex<FnvHashMap<Option<Url>, mpsc::UnboundedSender<Command<P>>>>,
}
impl<P: Protocol> Connections<P> {
pub(crate) fn new(credentials: P::Credentials) -> Self {
Self {
credentials,
tasks: Mutex::new(FnvHashMap::default()),
}
}
pub(crate) async fn attach(
&self,
request: AttachRequest<P>,
) -> Result<Attachment<P>, SocketError> {
let (reply, answer) = oneshot::channel();
self.commands(&request.url)
.send(Command::Attach(Box::new(request), reply))
.map_err(|_| task_stopped::<P>())?;
answer.await.map_err(|_| task_stopped::<P>())?
}
fn commands(&self, url: &Url) -> mpsc::UnboundedSender<Command<P>> {
let key = P::CONNECTION_PER_ENDPOINT.then(|| url.clone());
let mut tasks = self.tasks.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(commands) = tasks.get(&key)
&& !commands.is_closed()
{
return commands.clone();
}
let (tx, rx) = mpsc::unbounded_channel();
let task = Actor::<P>::new(self.credentials.clone(), rx, tx.downgrade());
tokio::spawn(report_panic::<P>(tokio::spawn(task.run())));
tasks.insert(key, tx.clone());
tx
}
}
async fn report_panic<P: Protocol>(task: tokio::task::JoinHandle<()>) {
if let Err(error) = task.await
&& error.is_panic()
{
error!(
%error,
"the {} connection task panicked; every stream sharing the connection ends, and the \
next to re-attach starts a new connection",
P::NAME,
);
}
}
fn task_stopped<P: Protocol>() -> SocketError {
SocketError::Subscribe(format!(
"the {} connection task stopped before answering",
P::NAME
))
}
#[derive(Debug)]
pub(crate) struct Attachment<P: Protocol> {
frames: mpsc::UnboundedReceiver<WsMessage>,
attached: <P::Batch as Batch>::Attached,
_detach: Detach<P>,
}
impl<P: Protocol> Attachment<P> {
pub(crate) fn take_attached(&mut self) -> <P::Batch as Batch>::Attached {
std::mem::take(&mut self.attached)
}
}
impl<P: Protocol> Unpin for Attachment<P> {}
impl<P: Protocol> Stream for Attachment<P> {
type Item = Result<WsMessage, WsError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.frames.poll_recv(cx).map(|frame| frame.map(Ok))
}
}
#[derive(Debug)]
struct Detach<P: Protocol> {
id: AttachId,
commands: Option<mpsc::UnboundedSender<Command<P>>>,
}
impl<P: Protocol> Drop for Detach<P> {
fn drop(&mut self) {
if let Some(commands) = &self.commands {
let _ = commands.send(Command::Detach(self.id));
}
}
}