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::{
collect_step, drain_outcome, filter_notice, is_undeclared, notice_item, Bound, CollectStop, DecoderContext, Drained, RoutedItem, SubscriptionItem,
};
use super::{log_cancel_error, StreamDecoder};
use crate::transport::{AsyncInternalSubscription, AsyncMessageBus, SharedTicket};
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>,
shared: Option<SharedTicket>,
context: DecoderContext,
cancelled: Arc<AtomicBool>,
stopped: Arc<AtomicBool>,
snapshot_ended: Arc<AtomicBool>,
ended_natively: 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,
shared: self.shared,
context: self.context.clone(),
cancelled: self.cancelled.clone(),
stopped: self.stopped.clone(),
snapshot_ended: self.snapshot_ended.clone(),
ended_natively: self.ended_natively.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);
let shared = internal.shared_ticket();
Self {
subscription: internal,
request_id,
order_id,
shared,
context,
cancelled: Arc::new(AtomicBool::new(false)),
stopped: Arc::new(AtomicBool::new(false)),
snapshot_ended: Arc::new(AtomicBool::new(false)),
ended_natively: 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, stop: impl FnMut(&[T]) -> bool) -> Vec<T> {
let (rows, stopped) = self.collect_core(Bound::At(tokio::time::Instant::now() + timeout), stop).await;
if let CollectStop::Error(e) = stopped {
warn!("subscription error during collect: {e}");
}
rows
}
pub async fn collect_to_end_within(&mut self, timeout: Duration) -> Result<Vec<T>, Error> {
let (rows, stopped) = self.collect_core(Bound::At(tokio::time::Instant::now() + timeout), |_| false).await;
stopped.into_result(rows)
}
pub async fn next_batch(&mut 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))
.await;
stopped.into_batch(rows)
}
pub(crate) async fn collect_to_end(&mut self) -> Result<Vec<T>, Error> {
let (rows, stopped) = self.collect_core(Bound::None, |_| false).await;
stopped.into_result(rows)
}
async fn collect_core(&mut self, bound: Bound<tokio::time::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().await,
Some(deadline) if tokio::time::Instant::now() >= deadline => return (rows, CollectStop::Deadline),
Some(deadline) => match tokio::time::timeout_at(deadline, self.next()).await {
Ok(item) => item,
Err(_elapsed) => return (rows, CollectStop::Deadline),
},
};
let Some(item) = item else {
let stopped = if self.ended_natively() {
CollectStop::EndMarker
} 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(tokio::time::Instant::now() + quiet);
}
}
}
}
pub async fn cancel_and_drain(mut self, deadline: tokio::time::Instant) -> Result<Drained, Error> {
if self.ended_natively() || self.snapshot_ended.load(Ordering::Relaxed) {
return Ok(Drained::Ended);
}
if self.stream_ended.load(Ordering::Relaxed) {
return Ok(Drained::Unconfirmed);
}
if self.request_id.is_none() {
self.cancel().await;
return Ok(Drained::Unconfirmed);
}
if !self.cancelled.swap(true, Ordering::Relaxed) {
if let Some(message) = self.pending_cancel(self.request_id) {
let message_bus = self.message_bus.clone();
let write = self.message_bus.runtime_handle().spawn(async move {
if let Err(e) = message_bus.send_message(message).await {
log_cancel_error("subscription", &e);
}
});
let _ = write.await;
}
}
loop {
match tokio::time::timeout_at(deadline, self.next()).await {
Err(_elapsed) => return Ok(Drained::Unconfirmed),
Ok(Some(item)) => {
if let Some(outcome) = drain_outcome(item) {
return outcome;
}
}
Ok(None) if self.ended_natively() => return Ok(Drained::Ended),
Ok(None) => return Ok(Drained::Unconfirmed),
}
}
}
}
#[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);
}
if this.stopped.load(Ordering::Relaxed) {
this.stream_ended.store(true, Ordering::Relaxed);
return Poll::Ready(Some(Err(Error::Cancelled)));
}
let Subscription {
subscription,
context,
stream_ended,
snapshot_ended,
ended_natively,
..
} = 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);
ended_natively.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(notice_item(notice)))),
RoutedItem::Error(Error::Notice(notice)) if T::is_nonterminal_notice(¬ice) => {
return Poll::Ready(Some(Ok(SubscriptionItem::Notice(notice))));
}
RoutedItem::Error(Error::EndOfStream) => {
stream_ended.store(true, Ordering::Relaxed);
ended_natively.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) {
self.stopped.store(true, Ordering::Relaxed);
if self.cancelled.swap(true, Ordering::Relaxed) {
return;
}
let id = self.request_id.or(self.order_id);
let message = self.pending_cancel(id);
if let Err(e) = send_cancel(&self.message_bus, id, self.shared, message).await {
log_cancel_error("subscription", &e);
}
}
}
#[allow(private_bounds)]
impl<T: StreamDecoder<T>> Subscription<T> {
fn pending_cancel(&self, id: Option<i32>) -> Option<Vec<u8>> {
if self.request_id.is_some() && self.ended_natively.load(Ordering::Relaxed) || self.snapshot_ended.load(Ordering::Relaxed) {
return None;
}
T::cancel_message(self.context.server_version, id, Some(&self.context)).ok()
}
pub(crate) fn ended_natively(&self) -> bool {
self.ended_natively.load(Ordering::Relaxed)
}
}
async fn send_cancel(
message_bus: &Arc<dyn AsyncMessageBus>,
id: Option<i32>,
shared: Option<SharedTicket>,
message: Option<Vec<u8>>,
) -> Result<(), Error> {
match (id, shared, message) {
(None, Some(ticket), message) => message_bus.cancel_shared_subscription(ticket, message).await,
(_, _, Some(message)) => message_bus.send_message(message).await,
(_, _, None) => Ok(()),
}
}
impl<T: StreamDecoder<T>> Drop for Subscription<T> {
fn drop(&mut self) {
debug!("dropping async subscription");
if self.cancelled.swap(true, Ordering::Relaxed) {
return;
}
let id = self.request_id.or(self.order_id);
let shared = self.shared;
let message = self.pending_cancel(id);
if message.is_none() && shared.is_none() {
return;
}
let message_bus = self.message_bus.clone();
self.message_bus.runtime_handle().spawn(async move {
if let Err(e) = send_cancel(&message_bus, id, shared, 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;