pub(crate) mod common;
#[cfg(feature = "sync")]
use std::{sync::Mutex, time::Duration};
#[cfg(feature = "sync")]
use crossbeam::channel::{Receiver, Sender};
#[cfg(feature = "sync")]
use common::{Lease, LeaseRef};
#[cfg(feature = "sync")]
use crate::client::ids::{OrderId, RequestId};
use crate::errors::Error;
use crate::messages::ResponseMessage;
#[cfg(any(feature = "sync", feature = "async"))]
use crate::accounts::types::AccountId;
#[cfg(any(feature = "sync", feature = "async"))]
use crate::messages::OutgoingMessages;
#[cfg(feature = "sync")]
pub mod sync;
#[cfg(feature = "async")]
pub mod r#async;
pub(crate) mod order_taps;
pub(crate) mod rate_limiter;
#[cfg(any(feature = "sync", feature = "async"))]
pub(crate) use crate::subscriptions::common::RoutedItem;
#[derive(Clone, Copy, Debug)]
pub(crate) struct BufferBound {
pub limit: usize,
pub end: &'static [crate::messages::IncomingMessages],
}
pub const MAX_BUFFER_LIMIT: usize = 65_535;
impl BufferBound {
#[cfg(any(feature = "sync", feature = "async"))]
pub(crate) fn for_stream<T: crate::subscriptions::StreamDecoder<T>>(limit: Option<usize>) -> Result<Option<Self>, Error> {
let Some(limit) = limit else {
return Ok(None);
};
if !(1..=MAX_BUFFER_LIMIT).contains(&limit) {
return Err(Error::InvalidArgument(format!(
"buffer_limit must be 1..={MAX_BUFFER_LIMIT}, got {limit}"
)));
}
Ok(Some(Self { limit, end: T::END_MESSAGES }))
}
}
#[derive(Debug, PartialEq)]
pub(crate) enum Admit {
Deliver,
Overflow,
Discard,
}
#[derive(Debug)]
pub(crate) struct BoundState {
bound: BufferBound,
closed: std::sync::atomic::AtomicBool,
}
impl BoundState {
pub(crate) fn new(bound: BufferBound) -> Self {
Self {
bound,
closed: std::sync::atomic::AtomicBool::new(false),
}
}
pub(crate) fn closed(&self) -> bool {
self.closed.load(std::sync::atomic::Ordering::Relaxed)
}
pub(crate) fn admit(&self, item: &RoutedItem, unread: usize) -> Admit {
use std::sync::atomic::Ordering;
if self.closed() {
return Admit::Discard;
}
let terminal = match item {
RoutedItem::Error(_) => true,
RoutedItem::Response(message) => self.bound.end.contains(&message.message_type()),
RoutedItem::Notice(_) => false,
};
if terminal {
self.closed.store(true, Ordering::Relaxed);
Admit::Deliver
} else if unread >= self.bound.limit {
self.closed.store(true, Ordering::Relaxed);
Admit::Overflow
} else {
Admit::Deliver
}
}
pub(crate) fn overflow_error(&self) -> RoutedItem {
Error::BufferLimitExceeded { limit: self.bound.limit }.into()
}
}
#[allow(dead_code)]
pub(crate) type Response = Result<ResponseMessage, Error>;
#[cfg(any(feature = "sync", feature = "async"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct SharedTicket {
pub(crate) message_type: OutgoingMessages,
pub(crate) generation: u64,
}
#[cfg(any(feature = "sync", feature = "async"))]
#[derive(Debug, Default)]
pub(crate) struct SharedCounts {
generation: u64,
live: std::collections::HashMap<OutgoingMessages, usize>,
account_updates: Option<AccountId>,
}
#[cfg(any(feature = "sync", feature = "async"))]
impl SharedCounts {
pub(crate) fn subscribe(&mut self, message_type: OutgoingMessages, account: Option<&AccountId>) -> SharedTicket {
if let Some(account) = account {
self.account_updates = Some(account.clone());
}
if !crate::messages::shared_channel_configuration::is_one_shot_request(message_type) {
*self.live.entry(message_type).or_insert(0) += 1;
}
SharedTicket {
message_type,
generation: self.generation,
}
}
pub(crate) fn check_account_updates(&self, account: Option<&AccountId>) -> Result<(), Error> {
match (&self.account_updates, account) {
(Some(active), Some(account)) if active != account => Err(Error::AccountUpdatesInUse {
active: active.clone(),
requested: account.clone(),
}),
_ => Ok(()),
}
}
pub(crate) fn unsubscribe(&mut self, ticket: SharedTicket) -> bool {
if ticket.generation != self.generation {
log::debug!("shared subscription {:?} outlived its session: cancel skipped", ticket.message_type);
return false;
}
let count = self.live.entry(ticket.message_type).or_insert(0);
*count = count.saturating_sub(1);
if *count > 0 {
log::debug!("shared subscription {:?} ended, {count} still live: cancel withheld", ticket.message_type);
return false;
}
if ticket.message_type == OutgoingMessages::RequestAccountData {
self.account_updates = None;
}
true
}
pub(crate) fn reset(&mut self) {
self.generation += 1;
self.live.clear();
self.account_updates = None;
}
#[cfg(test)]
pub(crate) fn live(&self, message_type: OutgoingMessages) -> usize {
self.live.get(&message_type).copied().unwrap_or(0)
}
#[cfg(test)]
pub(crate) fn account_updates(&self) -> Option<&AccountId> {
self.account_updates.as_ref()
}
}
#[cfg(feature = "sync")]
pub(crate) trait MessageBus: Send + Sync {
fn send_request_capped(&self, request_id: RequestId, packet: &[u8], bound: Option<BufferBound>) -> Result<InternalSubscription, Error>;
fn send_request(&self, request_id: RequestId, packet: &[u8]) -> Result<InternalSubscription, Error> {
self.send_request_capped(request_id, packet, None)
}
fn send_shared_request(&self, message_id: OutgoingMessages, packet: &[u8]) -> Result<InternalSubscription, Error>;
fn send_account_updates_request(&self, account: &AccountId, packet: &[u8]) -> Result<InternalSubscription, Error>;
fn cancel_shared_subscription(&self, ticket: SharedTicket, packet: Option<&[u8]>) -> Result<(), Error>;
fn send_order_request(&self, order_id: OrderId, packet: &[u8]) -> Result<InternalSubscription, Error>;
fn send_message(&self, packet: &[u8]) -> Result<(), Error>;
fn create_order_update_subscription(&self) -> Result<InternalSubscription, Error>;
fn create_order_status_subscription(&self, order_id: OrderId) -> Result<InternalSubscription, Error>;
fn notice_subscribe(&self) -> crate::subscriptions::notice_stream::sync_impl::NoticeStream;
fn ensure_shutdown(&self);
fn wait_connected(&self) -> Result<(), Error>;
fn is_connected(&self) -> bool;
}
#[cfg(feature = "sync")]
#[derive(Debug)]
pub(crate) struct InternalSubscription {
receiver: Option<Receiver<RoutedItem>>, sender: Option<Sender<RoutedItem>>, signaler: Sender<Signal>, lease: Mutex<Option<Lease>>, pub(crate) request_id: Option<RequestId>, pub(crate) order_id: Option<OrderId>, pub(crate) shared: Option<SharedTicket>, order_status: Option<OrderId>, }
#[cfg(feature = "sync")]
impl InternalSubscription {
fn pick_receiver(&self) -> Option<&Receiver<RoutedItem>> {
self.receiver.as_ref()
}
pub(crate) fn next(&self) -> Option<Response> {
Self::receive(self.pick_receiver()?)
}
#[cfg(test)]
pub(crate) fn try_next(&self) -> Option<Response> {
Self::try_receive(self.pick_receiver()?)
}
#[cfg(test)]
pub(crate) fn next_timeout(&self, timeout: Duration) -> Option<Response> {
Self::timeout_receive(self.pick_receiver()?, timeout)
}
pub(crate) fn next_routed(&self) -> Option<RoutedItem> {
self.pick_receiver()?.recv().ok()
}
pub(crate) fn try_next_routed(&self) -> Option<RoutedItem> {
self.pick_receiver()?.try_recv().ok()
}
pub(crate) fn next_timeout_routed(&self, timeout: Duration) -> Option<RoutedItem> {
self.pick_receiver()?.recv_timeout(timeout).ok()
}
pub(crate) fn cancel(&self) {
if let Some(sender) = &self.sender {
if let Err(e) = sender.send(Error::Cancelled.into()) {
log::warn!("error sending cancel notification: {e}")
}
}
self.release("cancel");
}
fn release(&self, cause: &str) {
let lease = common::lock(&self.lease).take();
let Some(lease) = lease else {
return;
};
let lease_ref = lease.downgrade();
drop(lease);
if let Err(e) = self.signaler.send(self.signal(lease_ref)) {
log::warn!("error sending {cause} signal: {e}");
}
}
fn signal(&self, lease: LeaseRef) -> Signal {
match (self.request_id, self.order_id, self.shared, self.order_status) {
(Some(request_id), _, _, _) => Signal::Request(request_id, lease),
(_, Some(order_id), _, _) => Signal::Order(order_id, lease),
(_, _, Some(_), _) => Signal::Shared(lease),
(_, _, _, Some(order_id)) => Signal::OrderStatus(order_id, lease),
_ => Signal::OrderUpdateStream(lease),
}
}
fn receive(receiver: &Receiver<RoutedItem>) -> Option<Response> {
loop {
if let Some(legacy) = receiver.recv().ok()?.into_legacy() {
return Some(legacy);
}
}
}
#[cfg(test)]
fn try_receive(receiver: &Receiver<RoutedItem>) -> Option<Response> {
loop {
if let Some(legacy) = receiver.try_recv().ok()?.into_legacy() {
return Some(legacy);
}
}
}
#[cfg(test)]
fn timeout_receive(receiver: &Receiver<RoutedItem>, timeout: Duration) -> Option<Response> {
let deadline = std::time::Instant::now() + timeout;
loop {
let remaining = deadline.saturating_duration_since(std::time::Instant::now());
if let Some(legacy) = receiver.recv_timeout(remaining).ok()?.into_legacy() {
return Some(legacy);
}
}
}
}
#[cfg(feature = "sync")]
impl Drop for InternalSubscription {
fn drop(&mut self) {
self.release("drop");
}
}
#[cfg(feature = "sync")]
pub(crate) enum Signal {
Request(RequestId, LeaseRef),
Order(OrderId, LeaseRef),
OrderUpdateStream(LeaseRef),
OrderStatus(OrderId, LeaseRef),
Shared(LeaseRef),
}
#[cfg(feature = "sync")]
pub(crate) struct SubscriptionBuilder {
receiver: Option<Receiver<RoutedItem>>,
sender: Option<Sender<RoutedItem>>,
signaler: Option<Sender<Signal>>,
lease: Option<Lease>,
order_id: Option<OrderId>,
request_id: Option<RequestId>,
shared: Option<SharedTicket>,
order_status: Option<OrderId>,
}
#[cfg(feature = "sync")]
impl SubscriptionBuilder {
pub(crate) fn new() -> Self {
Self {
receiver: None,
sender: None,
signaler: None,
lease: None,
order_id: None,
request_id: None,
shared: None,
order_status: None,
}
}
pub(crate) fn receiver(mut self, receiver: Receiver<RoutedItem>) -> Self {
self.receiver = Some(receiver);
self
}
pub(crate) fn sender(mut self, sender: Sender<RoutedItem>) -> Self {
self.sender = Some(sender);
self
}
pub(crate) fn signaler(mut self, signaler: Sender<Signal>) -> Self {
self.signaler = Some(signaler);
self
}
pub(crate) fn lease(mut self, lease: Lease) -> Self {
self.lease = Some(lease);
self
}
pub(crate) fn order_id(mut self, order_id: OrderId) -> Self {
self.order_id = Some(order_id);
self
}
pub(crate) fn request_id(mut self, request_id: RequestId) -> Self {
self.request_id = Some(request_id);
self
}
pub(crate) fn shared(mut self, ticket: SharedTicket) -> Self {
self.shared = Some(ticket);
self
}
pub(crate) fn order_status(mut self, order_id: OrderId) -> Self {
self.order_status = Some(order_id);
self
}
pub(crate) fn build(self) -> InternalSubscription {
let (Some(receiver), Some(signaler), Some(lease)) = (self.receiver, self.signaler, self.lease) else {
panic!("bad configuration");
};
InternalSubscription {
receiver: Some(receiver),
sender: self.sender,
signaler,
lease: Mutex::new(Some(lease)),
request_id: self.request_id,
order_id: self.order_id,
shared: self.shared,
order_status: self.order_status,
}
}
}
#[cfg(feature = "sync")]
pub use sync::TcpMessageBus;
#[cfg(feature = "async")]
pub use r#async::{AsyncInternalSubscription, AsyncMessageBus};
pub mod connection;
pub(crate) mod raw_capture;
pub mod recorder;
pub mod routing;
#[cfg(all(test, any(feature = "sync", feature = "async")))]
mod tests;