use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock};
use std::time::{Duration, Instant};
use log::{debug, error, warn};
use super::common::{
collect_step, debug_assert_request_id_routable, drain_outcome, filter_notice, is_undeclared, notice_item, Bound, CollectStop, DecoderContext,
Drained, RoutedItem, SubscriptionItem,
};
use super::{log_cancel_error, StreamDecoder};
use crate::client::ids::RequestId;
use crate::errors::Error;
use crate::messages::Notice;
use crate::transport::{InternalSubscription, MessageBus, SharedTicket};
#[allow(private_bounds)]
#[must_use = "Subscription must be iterated (via .next(), .iter_data(), or .into_iter()) to receive data; dropping it cancels the request"]
pub struct Subscription<T: StreamDecoder<T>> {
context: DecoderContext,
message_bus: Arc<dyn MessageBus>,
session_generation: u64,
request_id: Option<i32>,
order_id: Option<i32>,
shared: Option<SharedTicket>,
phantom: PhantomData<T>,
cancelled: AtomicBool,
snapshot_ended: AtomicBool,
stream_ended: AtomicBool,
ended_natively: AtomicBool,
rejected: OnceLock<Notice>,
subscription: InternalSubscription,
}
enum NextAction<T> {
Return(Option<T>),
Skip,
}
#[allow(private_bounds)]
impl<T: StreamDecoder<T>> Subscription<T> {
pub(crate) fn new(message_bus: Arc<dyn MessageBus>, subscription: InternalSubscription, context: DecoderContext) -> Self {
let request_id = subscription.request_id.map(RequestId::raw);
let order_id = subscription.order_id.map(|id| id.value());
let shared = subscription.shared;
debug_assert_request_id_routable::<T, T>(request_id);
Subscription {
context,
session_generation: subscription.session,
message_bus,
request_id,
order_id,
shared,
subscription,
phantom: PhantomData,
cancelled: AtomicBool::new(false),
snapshot_ended: AtomicBool::new(false),
stream_ended: AtomicBool::new(false),
ended_natively: AtomicBool::new(false),
rejected: OnceLock::new(),
}
}
pub fn cancel(&self) {
if self.cancelled.swap(true, Ordering::Relaxed) {
return;
}
if let Some(ticket) = self.shared {
let message = T::cancel_message(self.context.server_version, self.request_id, Some(&self.context)).ok();
if let Err(e) = self.message_bus.cancel_shared_subscription(ticket, message.as_deref()) {
log_cancel_error("shared subscription", &e);
}
} else if let Some(message) = self.pending_cancel() {
if let Err(e) = self.message_bus.send_message(&message) {
log_cancel_error("subscription", &e);
}
}
self.subscription.cancel();
}
pub fn request_id(&self) -> Option<i32> {
self.request_id
}
pub(crate) fn ended_natively(&self) -> bool {
self.ended_natively.load(Ordering::Relaxed)
}
fn id(&self) -> Option<i32> {
self.request_id.or(self.order_id)
}
fn note_rejection(&self, error: &Error) {
if let (Some(_), Error::Notice(notice)) = (self.request_id, error) {
let _ = self.rejected.set(notice.clone());
}
}
fn pending_cancel(&self) -> Option<Vec<u8>> {
if self.message_bus.session_generation() != self.session_generation {
debug!("request {:?} outlived its session: cancel skipped", self.request_id);
return None;
}
if self.ended_natively() || self.rejected.get().is_some_and(T::rejection_ends_request) || self.snapshot_ended.load(Ordering::Relaxed) {
return None;
}
T::cancel_message(self.context.server_version, self.id(), Some(&self.context)).ok()
}
pub fn cancel_and_drain(self, deadline: Instant) -> Result<Drained, Error> {
if self.ended_natively() || self.snapshot_ended.load(Ordering::Relaxed) {
return Ok(Drained::Ended);
}
if let Some(notice) = self.rejected.get() {
return Ok(Drained::Rejected(notice.clone()));
}
if self.stream_ended.load(Ordering::Relaxed) {
return Ok(Drained::Unconfirmed);
}
if self.request_id.is_none() {
self.cancel();
return Ok(Drained::Unconfirmed);
}
if !self.cancelled.swap(true, Ordering::Relaxed) {
if let Some(message) = self.pending_cancel() {
if let Err(e) = self.message_bus.send_message(&message) {
log_cancel_error("subscription", &e);
}
}
}
loop {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return Ok(Drained::Unconfirmed);
}
match self.next_timeout(remaining) {
Some(item) => {
if let Some(outcome) = drain_outcome(item) {
return outcome;
}
}
None if self.ended_natively() => return Ok(Drained::Ended),
None => {}
}
}
}
pub fn next(&self) -> Option<Result<SubscriptionItem<T>, Error>> {
if self.stream_ended.load(Ordering::Relaxed) {
return None;
}
loop {
match self.handle_response(self.subscription.next_routed()) {
NextAction::Return(val) => return val,
NextAction::Skip => continue,
}
}
}
fn handle_response(&self, response: Option<RoutedItem>) -> NextAction<Result<SubscriptionItem<T>, Error>> {
match response {
Some(RoutedItem::Response(message)) if is_undeclared(T::RESPONSE_MESSAGE_IDS, &message) => {
log::trace!("skipping {:?} — not declared by this subscription's decoder", message.message_type());
NextAction::Skip
}
Some(RoutedItem::Response(message)) => match T::decode(&self.context, &message) {
Ok(val) => {
if val.is_snapshot_end() {
self.snapshot_ended.store(true, Ordering::Relaxed);
}
NextAction::Return(Some(Ok(SubscriptionItem::Data(val))))
}
Err(Error::EndOfStream) => {
self.stream_ended.store(true, Ordering::Relaxed);
self.ended_natively.store(true, Ordering::Relaxed);
NextAction::Return(None)
}
Err(err) => {
match &err {
Error::Notice(n) => warn!("subscription terminated by TWS error {n}"),
_ => error!("error decoding message: {err}"),
}
self.stream_ended.store(true, Ordering::Relaxed);
self.note_rejection(&err);
NextAction::Return(Some(Err(err)))
}
},
Some(RoutedItem::Notice(notice)) => NextAction::Return(Some(Ok(notice_item(notice)))),
Some(RoutedItem::Error(Error::Notice(notice))) if T::is_nonterminal_notice(¬ice) => {
NextAction::Return(Some(Ok(SubscriptionItem::Notice(notice))))
}
Some(RoutedItem::Error(Error::EndOfStream)) => {
self.stream_ended.store(true, Ordering::Relaxed);
self.ended_natively.store(true, Ordering::Relaxed);
NextAction::Return(None)
}
Some(RoutedItem::Error(e)) => {
self.stream_ended.store(true, Ordering::Relaxed);
self.note_rejection(&e);
NextAction::Return(Some(Err(e)))
}
None => NextAction::Return(None),
}
}
pub fn try_next(&self) -> Option<Result<SubscriptionItem<T>, Error>> {
if self.stream_ended.load(Ordering::Relaxed) {
return None;
}
loop {
match self.handle_response(self.subscription.try_next_routed()) {
NextAction::Return(val) => return val,
NextAction::Skip => continue,
}
}
}
pub fn next_timeout(&self, timeout: Duration) -> Option<Result<SubscriptionItem<T>, Error>> {
if self.stream_ended.load(Ordering::Relaxed) {
return None;
}
let deadline = Instant::now() + timeout;
loop {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return None;
}
match self.handle_response(self.subscription.next_timeout_routed(remaining)) {
NextAction::Return(val) => return val,
NextAction::Skip => continue,
}
}
}
pub fn next_data(&self) -> Option<Result<T, Error>> {
self.iter_data().next()
}
pub fn iter(&self) -> SubscriptionIter<'_, T> {
SubscriptionIter { subscription: self }
}
pub fn try_iter(&self) -> SubscriptionTryIter<'_, T> {
SubscriptionTryIter { subscription: self }
}
pub fn timeout_iter(&self, timeout: Duration) -> SubscriptionTimeoutIter<'_, T> {
SubscriptionTimeoutIter { subscription: self, timeout }
}
pub fn iter_data(&self) -> FilterData<SubscriptionIter<'_, T>> {
self.iter().filter_data()
}
pub fn try_iter_data(&self) -> FilterData<SubscriptionTryIter<'_, T>> {
self.try_iter().filter_data()
}
pub fn timeout_iter_data(&self, timeout: Duration) -> FilterData<SubscriptionTimeoutIter<'_, T>> {
self.timeout_iter(timeout).filter_data()
}
pub fn collect_for(&self, timeout: Duration) -> Vec<T> {
self.collect_until(timeout, |_| false)
}
pub fn collect_until(&self, timeout: Duration, stop: impl FnMut(&[T]) -> bool) -> Vec<T> {
let (rows, stopped) = self.collect_core(Bound::At(Instant::now() + timeout), stop);
if let CollectStop::Error(e) = stopped {
warn!("subscription error during collect: {e}");
}
rows
}
pub fn collect_to_end_within(&self, timeout: Duration) -> Result<Vec<T>, Error> {
let (rows, stopped) = self.collect_core(Bound::At(Instant::now() + timeout), |_| false);
stopped.into_result(rows)
}
pub fn next_batch(&self, quiet: Duration) -> Option<Result<Vec<T>, Error>> {
let (rows, stopped) = self.collect_core(Bound::Idle(quiet), |rows| rows.last().is_some_and(T::is_batch_end));
stopped.into_batch(rows)
}
pub(crate) fn collect_to_end(&self) -> Result<Vec<T>, Error> {
let (rows, stopped) = self.collect_core(Bound::None, |_| false);
stopped.into_result(rows)
}
fn collect_core(&self, bound: Bound<Instant>, mut stop: impl FnMut(&[T]) -> bool) -> (Vec<T>, CollectStop) {
let mut rows = Vec::new();
let mut deadline = match bound {
Bound::At(deadline) => Some(deadline),
Bound::None | Bound::Idle(_) => None,
};
loop {
let item = match deadline {
None => self.next(),
Some(deadline) => {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return (rows, CollectStop::Deadline);
}
self.next_timeout(remaining)
}
};
let Some(item) = item else {
let stopped = if self.ended_natively() {
CollectStop::EndMarker
} else if deadline.is_some_and(|deadline| Instant::now() >= deadline) {
CollectStop::Deadline
} else {
CollectStop::Closed
};
return (rows, stopped);
};
let read = rows.len();
if let Some(stopped) = collect_step(&mut rows, item, &mut stop) {
return (rows, stopped);
}
if let Bound::Idle(quiet) = bound {
if rows.len() > read {
deadline = Some(Instant::now() + quiet);
}
}
}
}
}
impl<T: StreamDecoder<T>> Drop for Subscription<T> {
fn drop(&mut self) {
debug!("dropping subscription");
self.cancel();
}
}
#[must_use = "iterator adapters are lazy and do nothing unless consumed"]
pub struct FilterData<I> {
inner: I,
}
impl<I, T> Iterator for FilterData<I>
where
I: Iterator<Item = Result<SubscriptionItem<T>, Error>>,
{
type Item = Result<T, Error>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(out) = filter_notice(self.inner.next()?) {
return Some(out);
}
}
}
}
pub trait SubscriptionItemIterExt: Iterator + Sized {
fn filter_data<T>(self) -> FilterData<Self>
where
Self: Iterator<Item = Result<SubscriptionItem<T>, Error>>,
{
FilterData { inner: self }
}
}
impl<I: Iterator> SubscriptionItemIterExt for I {}
#[allow(private_bounds)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct SubscriptionIter<'a, T: StreamDecoder<T>> {
subscription: &'a Subscription<T>,
}
impl<T: StreamDecoder<T>> Iterator for SubscriptionIter<'_, T> {
type Item = Result<SubscriptionItem<T>, Error>;
fn next(&mut self) -> Option<Self::Item> {
self.subscription.next()
}
}
impl<'a, T: StreamDecoder<T>> IntoIterator for &'a Subscription<T> {
type Item = Result<SubscriptionItem<T>, Error>;
type IntoIter = SubscriptionIter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[allow(private_bounds)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct SubscriptionOwnedIter<T: StreamDecoder<T>> {
subscription: Subscription<T>,
}
impl<T: StreamDecoder<T>> Iterator for SubscriptionOwnedIter<T> {
type Item = Result<SubscriptionItem<T>, Error>;
fn next(&mut self) -> Option<Self::Item> {
self.subscription.next()
}
}
impl<T: StreamDecoder<T>> IntoIterator for Subscription<T> {
type Item = Result<SubscriptionItem<T>, Error>;
type IntoIter = SubscriptionOwnedIter<T>;
fn into_iter(self) -> Self::IntoIter {
SubscriptionOwnedIter { subscription: self }
}
}
#[allow(private_bounds)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct SubscriptionTryIter<'a, T: StreamDecoder<T>> {
subscription: &'a Subscription<T>,
}
impl<T: StreamDecoder<T>> Iterator for SubscriptionTryIter<'_, T> {
type Item = Result<SubscriptionItem<T>, Error>;
fn next(&mut self) -> Option<Self::Item> {
self.subscription.try_next()
}
}
#[allow(private_bounds)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct SubscriptionTimeoutIter<'a, T: StreamDecoder<T>> {
subscription: &'a Subscription<T>,
timeout: Duration,
}
impl<T: StreamDecoder<T>> Iterator for SubscriptionTimeoutIter<'_, T> {
type Item = Result<SubscriptionItem<T>, Error>;
fn next(&mut self) -> Option<Self::Item> {
self.subscription.next_timeout(self.timeout)
}
}
#[cfg(all(test, feature = "sync"))]
#[path = "sync_tests.rs"]
mod tests;