use std::{
collections::HashMap,
sync::{Arc, OnceLock},
task::{Context, Poll},
};
use futures_util::StreamExt;
use polyoxide_rt::{default_runtime, spawn, Runtime, Task};
use polyoxide_ws::{
default_socket_transport, MembershipHandle as PathHandle, SocketTransport, Supervised,
Supervisor, Timing,
};
use tokio::sync::{mpsc, oneshot, watch};
use super::{
protocol::{BinanceProtocol, Change},
Shape, SocketError, StreamSet, EVENT_BUFFER, MAX_STREAMS_PER_CONNECTION,
};
pub(crate) struct Settings<S: StreamSet> {
pub(crate) base_url: String,
pub(crate) timing: Timing,
pub(crate) streams: Vec<S::Name>,
pub(crate) runtime: Option<Arc<dyn Runtime>>,
pub(crate) socket_transport: Option<Arc<dyn SocketTransport>>,
}
impl<S: StreamSet> Settings<S> {
fn supervisor(&self, path: S::Path) -> Supervisor<BinanceProtocol<S>> {
let supervisor = Supervisor::new(
BinanceProtocol::new(self.base_url.clone(), path),
self.timing,
);
let supervisor = match &self.runtime {
Some(runtime) => supervisor.runtime(Arc::clone(runtime)),
None => supervisor,
};
match &self.socket_transport {
Some(transport) => supervisor.socket_transport(Arc::clone(transport)),
None => supervisor,
}
}
}
type Cause<S> = <<S as StreamSet>::Error as SocketError>::Cause;
type Item<S> = Result<<S as StreamSet>::Event, <S as StreamSet>::Error>;
type Events<S> = mpsc::Sender<Item<S>>;
type Reply<S> = oneshot::Sender<Result<(), <S as StreamSet>::Error>>;
pub(crate) async fn connect<S: StreamSet>(
mut settings: Settings<S>,
) -> Result<Running<S>, S::Error> {
let runtime = Arc::clone(settings.runtime.get_or_insert_with(default_runtime));
settings
.socket_transport
.get_or_insert_with(default_socket_transport);
let mut membership: HashMap<S::Path, Vec<S::Name>> = HashMap::new();
for stream in settings.streams.iter().cloned() {
let wanted = membership.entry(S::path(&stream)).or_default();
if !wanted.contains(&stream) {
wanted.push(stream);
}
}
for (path, wanted) in &membership {
if wanted.len() > MAX_STREAMS_PER_CONNECTION {
return Err(<S::Error as SocketError>::too_many_streams(
*path,
MAX_STREAMS_PER_CONNECTION,
));
}
}
let mut opened: Vec<(S::Path, Supervised<BinanceProtocol<S>>)> = Vec::new();
for path in S::PATHS {
let Some(wanted) = membership.get(path) else {
continue;
};
match settings.supervisor(*path).connect(wanted.clone()).await {
Ok(supervised) => opened.push((*path, supervised)),
Err(err) => {
for (_, supervised) in opened {
let _ = supervised.close().await;
}
return Err(err);
}
}
}
let (events, events_rx) = mpsc::channel(EVENT_BUFFER);
let (commands, commands_rx) = mpsc::channel(16);
let (ended, ended_rx) = mpsc::unbounded_channel();
let (closing, _) = watch::channel(false);
let stop = Stop::<S>::new();
let mut router = Router {
config: settings,
runtime: Arc::clone(&runtime),
paths: HashMap::new(),
membership,
events,
closing,
ended,
forwarders: Vec::new(),
generation: 0,
stop: stop.clone(),
};
for (path, supervised) in opened {
router.adopt(path, supervised);
}
let task = spawn(runtime.as_ref(), route(router, commands_rx, ended_rx));
Ok(Running {
events: events_rx,
commands,
task,
stop,
})
}
enum Command<S: StreamSet> {
Subscribe(Vec<S::Name>, Reply<S>),
Unsubscribe(Vec<S::Name>, Reply<S>),
Close,
}
pub(crate) struct Handle<S: StreamSet> {
commands: mpsc::Sender<Command<S>>,
stop: Stop<S>,
}
impl<S: StreamSet> Clone for Handle<S> {
fn clone(&self) -> Self {
Self {
commands: self.commands.clone(),
stop: self.stop.clone(),
}
}
}
impl<S: StreamSet> std::fmt::Debug for Handle<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Handle").finish_non_exhaustive()
}
}
impl<S: StreamSet> Handle<S> {
pub(crate) async fn subscribe(&self, streams: Vec<S::Name>) -> Result<(), S::Error> {
self.send(|reply| Command::Subscribe(streams, reply)).await
}
pub(crate) async fn unsubscribe(&self, streams: Vec<S::Name>) -> Result<(), S::Error> {
self.send(|reply| Command::Unsubscribe(streams, reply))
.await
}
async fn send(&self, make: impl FnOnce(Reply<S>) -> Command<S>) -> Result<(), S::Error> {
let (reply_tx, reply_rx) = oneshot::channel();
self.commands
.send(make(reply_tx))
.await
.map_err(|_| self.stop.error())?;
reply_rx.await.map_err(|_| self.stop.error())?
}
}
struct Stop<S: StreamSet>(Arc<OnceLock<Cause<S>>>);
impl<S: StreamSet> Clone for Stop<S> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<S: StreamSet> Stop<S> {
fn new() -> Self {
Self(Arc::new(OnceLock::new()))
}
fn set(&self, cause: Cause<S>) {
let _ = self.0.set(cause);
}
fn error(&self) -> S::Error {
<S::Error as SocketError>::stopped(self.0.get().cloned())
}
}
pub(crate) struct Running<S: StreamSet> {
events: mpsc::Receiver<Item<S>>,
commands: mpsc::Sender<Command<S>>,
task: Task<Result<(), S::Error>>,
stop: Stop<S>,
}
impl<S: StreamSet> Running<S> {
pub(crate) fn membership(&self) -> Handle<S> {
Handle {
commands: self.commands.clone(),
stop: self.stop.clone(),
}
}
pub(crate) fn is_running(&self) -> bool {
!self.task.is_finished()
}
pub(crate) fn poll_event(&mut self, cx: &mut Context<'_>) -> Poll<Option<Item<S>>> {
self.events.poll_recv(cx)
}
pub(crate) async fn close(self) -> Result<(), S::Error> {
let Self {
events,
commands,
task,
stop,
} = self;
drop(events);
let _ = commands.send(Command::Close).await;
drop(commands);
match task.await {
Some(result) => result,
None => Err(stop.error()),
}
}
}
struct Path<S: StreamSet> {
generation: u64,
handle: PathHandle<BinanceProtocol<S>>,
}
struct Ended<S: StreamSet> {
path: S::Path,
generation: u64,
stopped: bool,
cause: Option<Cause<S>>,
}
struct Router<S: StreamSet> {
config: Settings<S>,
runtime: Arc<dyn Runtime>,
paths: HashMap<S::Path, Path<S>>,
membership: HashMap<S::Path, Vec<S::Name>>,
events: Events<S>,
closing: watch::Sender<bool>,
ended: mpsc::UnboundedSender<Ended<S>>,
forwarders: Vec<Task<()>>,
generation: u64,
stop: Stop<S>,
}
impl<S: StreamSet> Router<S> {
fn adopt(&mut self, path: S::Path, supervised: Supervised<BinanceProtocol<S>>) {
self.generation += 1;
let handle = supervised.membership();
self.forwarders.retain(|forwarder| !forwarder.is_finished());
self.forwarders.push(spawn(
self.runtime.as_ref(),
forward::<S>(
path,
self.generation,
supervised,
self.events.clone(),
self.closing.subscribe(),
self.ended.clone(),
),
));
self.paths.insert(
path,
Path {
generation: self.generation,
handle,
},
);
}
fn ended(&mut self, ended: Ended<S>) -> Result<(), S::Error> {
if ended.stopped {
if let Some(cause) = ended.cause {
self.stop.set(cause);
}
return Err(self.stop.error());
}
if self
.paths
.get(&ended.path)
.is_some_and(|path| path.generation == ended.generation)
{
self.paths.remove(&ended.path);
self.membership.remove(&ended.path);
}
Ok(())
}
async fn subscribe(&mut self, streams: Vec<S::Name>) -> Result<(), S::Error> {
let by_path = new_by_path::<S>(&self.membership, streams);
for (path, add) in &by_path {
let held = self.membership.get(path).map_or(0, Vec::len);
if held + add.len() > MAX_STREAMS_PER_CONNECTION {
return Err(<S::Error as SocketError>::too_many_streams(
*path,
MAX_STREAMS_PER_CONNECTION,
));
}
}
for path in S::PATHS {
let Some(add) = by_path.get(path) else {
continue;
};
match self.paths.get(path) {
Some(slot) => {
let handle = slot.handle.clone();
self.change(*path, &handle, Change::Subscribe(add.clone()))
.await?;
}
None => self.open(*path, add.clone()).await?,
}
}
Ok(())
}
async fn unsubscribe(&mut self, streams: Vec<S::Name>) -> Result<(), S::Error> {
let mut by_path: HashMap<S::Path, Vec<S::Name>> = HashMap::new();
for stream in streams {
let path = S::path(&stream);
if self
.membership
.get(&path)
.is_some_and(|m| m.contains(&stream))
{
let batch = by_path.entry(path).or_default();
if !batch.contains(&stream) {
batch.push(stream);
}
}
}
for path in S::PATHS {
let (Some(remove), Some(slot)) = (by_path.get(path), self.paths.get(path)) else {
continue;
};
let handle = slot.handle.clone();
self.change(*path, &handle, Change::Unsubscribe(remove.clone()))
.await?;
}
Ok(())
}
async fn open(&mut self, path: S::Path, streams: Vec<S::Name>) -> Result<(), S::Error> {
let supervised = self.config.supervisor(path).start(streams.clone()).await?;
self.adopt(path, supervised);
self.membership.insert(path, streams);
Ok(())
}
async fn change(
&mut self,
path: S::Path,
handle: &PathHandle<BinanceProtocol<S>>,
change: Change<S::Name>,
) -> Result<(), S::Error> {
match handle.apply(change.clone()).await {
Ok(streams) => {
self.settle(path, streams);
Ok(())
}
Err(err) if matches!(err.shape(), Shape::NoAnswer) => {
let mut streams = self.membership.get(&path).cloned().unwrap_or_default();
match change {
Change::Subscribe(add) => streams.extend(add),
Change::Unsubscribe(remove) => streams.retain(|s| !remove.contains(s)),
}
self.settle(path, streams);
Ok(())
}
Err(err) if matches!(err.shape(), Shape::Refused) => {
if let Ok(streams) = handle.apply(Change::Subscribe(Vec::new())).await {
self.settle(path, streams);
}
Err(err)
}
Err(err) => Err(err),
}
}
fn settle(&mut self, path: S::Path, streams: Vec<S::Name>) {
if streams.is_empty() {
self.membership.remove(&path);
self.paths.remove(&path);
} else {
self.membership.insert(path, streams);
}
}
async fn close(self) {
let _ = self.closing.send(true);
for forwarder in self.forwarders {
let _ = forwarder.await;
}
}
}
fn new_by_path<S: StreamSet>(
membership: &HashMap<S::Path, Vec<S::Name>>,
streams: Vec<S::Name>,
) -> HashMap<S::Path, Vec<S::Name>> {
let mut by_path: HashMap<S::Path, Vec<S::Name>> = HashMap::new();
for stream in streams {
let path = S::path(&stream);
let known = membership.get(&path).is_some_and(|m| m.contains(&stream));
let batch = by_path.entry(path).or_default();
if !known && !batch.contains(&stream) {
batch.push(stream);
}
}
by_path.retain(|_, batch| !batch.is_empty());
by_path
}
async fn route<S: StreamSet>(
mut router: Router<S>,
mut commands: mpsc::Receiver<Command<S>>,
mut ended: mpsc::UnboundedReceiver<Ended<S>>,
) -> Result<(), S::Error> {
let events = router.events.clone();
let result = loop {
tokio::select! {
biased;
Some(path) = ended.recv() => {
if let Err(err) = router.ended(path) {
break Err(err);
}
}
() = events.closed() => break Ok(()),
command = commands.recv() => match command {
None | Some(Command::Close) => break Ok(()),
Some(Command::Subscribe(streams, reply)) => {
let _ = reply.send(router.subscribe(streams).await);
}
Some(Command::Unsubscribe(streams, reply)) => {
let _ = reply.send(router.unsubscribe(streams).await);
}
},
}
};
drop(events);
router.close().await;
result
}
async fn forward<S: StreamSet>(
path: S::Path,
generation: u64,
mut supervised: Supervised<BinanceProtocol<S>>,
events: Events<S>,
mut closing: watch::Receiver<bool>,
ended: mpsc::UnboundedSender<Ended<S>>,
) {
let mut last_error: Option<Cause<S>> = None;
loop {
let next = tokio::select! {
biased;
_ = closing.wait_for(|closing| *closing) => break,
() = events.closed() => break,
next = supervised.next() => next,
};
let Some(item) = next else {
let stopped = supervised.close().await.is_err();
let _ = ended.send(Ended {
path,
generation,
stopped,
cause: last_error.filter(|_| stopped),
});
return;
};
last_error = item.as_ref().err().map(<S::Error as SocketError>::cause);
let slot = tokio::select! {
biased;
_ = closing.wait_for(|closing| *closing) => break,
slot = events.reserve() => slot,
};
match slot {
Ok(slot) => slot.send(item),
Err(_) => break,
}
}
let _ = supervised.close().await;
}