use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
use futures::stream::Stream;
use futures::StreamExt;
use log::{debug, warn};
use super::common::{filter_notice, is_undeclared, DecoderContext, RoutedItem, SubscriptionItem};
use super::{log_cancel_error, StreamDecoder};
use crate::transport::{AsyncInternalSubscription, AsyncMessageBus};
use crate::Error;
#[allow(private_bounds)]
#[must_use = "Subscription must be polled (via .next().await or .filter_data()) to receive data; dropping it cancels the request"]
pub struct Subscription<T: StreamDecoder<T>> {
subscription: AsyncInternalSubscription,
request_id: Option<i32>,
order_id: Option<i32>,
context: DecoderContext,
cancelled: Arc<AtomicBool>,
snapshot_ended: Arc<AtomicBool>,
stream_ended: AtomicBool,
message_bus: Arc<dyn AsyncMessageBus>,
phantom: PhantomData<fn() -> T>,
}
impl<T: StreamDecoder<T>> Clone for Subscription<T> {
fn clone(&self) -> Self {
Self {
subscription: self.subscription.clone(),
request_id: self.request_id,
order_id: self.order_id,
context: self.context.clone(),
cancelled: self.cancelled.clone(),
snapshot_ended: self.snapshot_ended.clone(),
stream_ended: AtomicBool::new(false),
message_bus: self.message_bus.clone(),
phantom: PhantomData,
}
}
}
#[allow(private_bounds)]
impl<T: StreamDecoder<T>> Subscription<T> {
pub(crate) fn new_from_internal(
internal: AsyncInternalSubscription,
message_bus: Arc<dyn AsyncMessageBus>,
request_id: Option<i32>,
order_id: Option<i32>,
context: DecoderContext,
) -> Self {
super::common::debug_assert_request_id_routable::<T, T>(request_id);
Self {
subscription: internal,
request_id,
order_id,
context,
cancelled: Arc::new(AtomicBool::new(false)),
snapshot_ended: Arc::new(AtomicBool::new(false)),
stream_ended: AtomicBool::new(false),
message_bus,
phantom: PhantomData,
}
}
pub(crate) fn new_from_internal_simple(
internal: AsyncInternalSubscription,
message_bus: Arc<dyn AsyncMessageBus>,
context: DecoderContext,
) -> Self {
Self::new_from_internal(internal, message_bus, None, None, context)
}
pub fn request_id(&self) -> Option<i32> {
self.request_id
}
}
#[allow(private_bounds)]
impl<T: StreamDecoder<T> + Send + 'static> Subscription<T> {
pub async fn collect_for(&mut self, timeout: Duration) -> Vec<T> {
self.collect_until(timeout, |_| false).await
}
pub async fn collect_until(&mut self, timeout: Duration, mut stop: impl FnMut(&[T]) -> bool) -> Vec<T> {
let deadline = tokio::time::Instant::now() + timeout;
let mut collected = Vec::new();
loop {
match tokio::time::timeout_at(deadline, self.next()).await {
Err(_elapsed) => break,
Ok(None) => break,
Ok(Some(Ok(SubscriptionItem::Data(value)))) => {
if value.is_snapshot_end() {
break;
}
collected.push(value);
if stop(&collected) {
break;
}
}
Ok(Some(Ok(SubscriptionItem::Notice(notice)))) => warn!("ib notice on subscription: {notice}"),
Ok(Some(Err(e))) => {
warn!("subscription error during collect: {e}");
break;
}
}
}
collected
}
}
#[allow(private_bounds)]
impl<T: StreamDecoder<T> + Send + 'static> Stream for Subscription<T> {
type Item = Result<SubscriptionItem<T>, Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
if this.stream_ended.load(Ordering::Relaxed) {
return Poll::Ready(None);
}
let Subscription {
subscription,
context,
stream_ended,
snapshot_ended,
..
} = this;
loop {
let routed = match subscription.poll_next_routed(cx) {
Poll::Ready(Some(item)) => item,
Poll::Ready(None) => return Poll::Ready(None),
Poll::Pending => return Poll::Pending,
};
match routed {
RoutedItem::Response(message) => {
if is_undeclared(T::RESPONSE_MESSAGE_IDS, &message) {
log::trace!("skipping {:?} — not declared by this subscription's decoder", message.message_type());
continue;
}
match T::decode(context, &message) {
Ok(val) => {
if val.is_snapshot_end() {
snapshot_ended.store(true, Ordering::Relaxed);
}
return Poll::Ready(Some(Ok(SubscriptionItem::Data(val))));
}
Err(Error::EndOfStream) => {
stream_ended.store(true, Ordering::Relaxed);
return Poll::Ready(None);
}
Err(err) => {
stream_ended.store(true, Ordering::Relaxed);
return Poll::Ready(Some(Err(err)));
}
}
}
RoutedItem::Notice(notice) => return Poll::Ready(Some(Ok(SubscriptionItem::Notice(notice)))),
RoutedItem::Error(Error::EndOfStream) => {
stream_ended.store(true, Ordering::Relaxed);
return Poll::Ready(None);
}
RoutedItem::Error(e) => {
stream_ended.store(true, Ordering::Relaxed);
return Poll::Ready(Some(Err(e)));
}
}
}
}
}
#[allow(private_bounds)]
impl<T: StreamDecoder<T>> Subscription<T> {
pub async fn cancel(&self) {
if self.snapshot_ended.load(Ordering::Relaxed) {
return;
}
if self.cancelled.load(Ordering::Relaxed) {
return;
}
self.cancelled.store(true, Ordering::Relaxed);
let id = self.request_id.or(self.order_id);
if let Ok(message) = T::cancel_message(self.context.server_version, id, Some(&self.context)) {
if let Err(e) = self.message_bus.send_message(message).await {
log_cancel_error("subscription", &e);
}
}
}
}
impl<T: StreamDecoder<T>> Drop for Subscription<T> {
fn drop(&mut self) {
debug!("dropping async subscription");
if self.snapshot_ended.load(Ordering::Relaxed) {
return;
}
if self.cancelled.load(Ordering::Relaxed) {
return;
}
self.cancelled.store(true, Ordering::Relaxed);
let id = self.request_id.or(self.order_id);
if let Ok(message) = T::cancel_message(self.context.server_version, id, Some(&self.context)) {
let message_bus = self.message_bus.clone();
tokio::spawn(async move {
if let Err(e) = message_bus.send_message(message).await {
log_cancel_error("subscription", &e);
}
});
}
}
}
#[must_use = "streams are lazy and do nothing unless polled"]
pub struct FilterDataStream<S> {
inner: S,
}
impl<S, T> Stream for FilterDataStream<S>
where
S: Stream<Item = Result<SubscriptionItem<T>, Error>> + Unpin,
{
type Item = Result<T, Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
loop {
match Pin::new(&mut self.inner).poll_next(cx) {
Poll::Ready(Some(item)) => {
if let Some(out) = filter_notice(item) {
return Poll::Ready(Some(out));
}
}
Poll::Ready(None) => return Poll::Ready(None),
Poll::Pending => return Poll::Pending,
}
}
}
}
pub trait SubscriptionItemStreamExt: Stream + Sized {
fn filter_data<T>(self) -> FilterDataStream<Self>
where
Self: Stream<Item = Result<SubscriptionItem<T>, Error>>,
{
FilterDataStream { inner: self }
}
}
impl<S: Stream + Sized> SubscriptionItemStreamExt for S {}
#[cfg(all(test, feature = "async"))]
#[path = "async_tests.rs"]
mod tests;