use bytes::Bytes;
use futures::future::BoxFuture;
use std::{
sync::{Arc, Mutex},
task::Poll,
time::Duration,
};
use tokio::{sync::oneshot::error::TryRecvError, task::JoinHandle, time::Instant};
use serde::{Deserialize, Serialize};
use tracing::{debug, trace};
use crate::{
connection::State,
jetstream::{self, Context},
Error, StatusCode, Subscriber,
};
use super::{AckPolicy, Consumer, DeliverPolicy, FromConsumer, IntoConsumerConfig, ReplayPolicy};
use jetstream::consumer;
impl Consumer<Config> {
pub async fn messages(&self) -> Result<Stream, Error> {
Stream::stream(
BatchConfig {
batch: 200,
expires: Some(Duration::from_secs(30).as_nanos().try_into().unwrap()),
no_wait: false,
max_bytes: 0,
idle_heartbeat: Duration::from_secs(15),
},
self,
)
.await
}
pub fn stream(&self) -> StreamBuilder<'_> {
StreamBuilder::new(self)
}
pub(crate) async fn request_batch<I: Into<BatchConfig>>(
&self,
batch: I,
inbox: String,
) -> Result<(), Error> {
let subject = format!(
"{}.CONSUMER.MSG.NEXT.{}.{}",
self.context.prefix, self.info.stream_name, self.info.name
);
let payload = serde_json::to_vec(&batch.into())?;
self.context
.client
.publish_with_reply(subject, inbox, payload.into())
.await?;
Ok(())
}
pub fn fetch(&self) -> FetchBuilder {
FetchBuilder::new(self)
}
pub fn batch(&self) -> BatchBuilder {
BatchBuilder::new(self)
}
pub fn sequence(&self, batch: usize) -> Result<Sequence, Error> {
let context = self.context.clone();
let subject = format!(
"{}.CONSUMER.MSG.NEXT.{}.{}",
self.context.prefix, self.info.stream_name, self.info.name
);
let request = serde_json::to_vec(&BatchConfig {
batch,
..Default::default()
})
.map(Bytes::from)?;
Ok(Sequence {
context,
subject,
request,
pending_messages: batch,
next: None,
})
}
}
pub struct Batch {
pending_messages: usize,
subscriber: Subscriber,
context: Context,
}
impl<'a> Batch {
async fn batch(batch: BatchConfig, consumer: &Consumer<Config>) -> Result<Batch, Error> {
let inbox = consumer.context.client.new_inbox();
let subscription = consumer.context.client.subscribe(inbox.clone()).await?;
consumer.request_batch(batch, inbox.clone()).await?;
Ok(Batch {
pending_messages: batch.batch,
subscriber: subscription,
context: consumer.context.clone(),
})
}
}
impl futures::Stream for Batch {
type Item = Result<jetstream::Message, Error>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
if self.pending_messages == 0 {
return std::task::Poll::Ready(None);
}
match self.subscriber.receiver.poll_recv(cx) {
Poll::Ready(maybe_message) => match maybe_message {
Some(message) => match message.status.unwrap_or(StatusCode::OK) {
StatusCode::TIMEOUT => Poll::Ready(None),
StatusCode::IDLE_HEARTBEAT => Poll::Pending,
StatusCode::OK => {
self.pending_messages -= 1;
Poll::Ready(Some(Ok(jetstream::Message {
context: self.context.clone(),
message,
})))
}
status => Poll::Ready(Some(Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"error while processing messages from the stream: {}, {:?}",
status, message.description
),
))))),
},
None => Poll::Ready(None),
},
std::task::Poll::Pending => std::task::Poll::Pending,
}
}
}
pub struct Sequence<'a> {
context: Context,
subject: String,
request: Bytes,
pending_messages: usize,
next: Option<BoxFuture<'a, Result<Batch, Error>>>,
}
impl<'a> futures::Stream for Sequence<'a> {
type Item = Result<Batch, Error>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
match self.next.as_mut() {
None => {
let context = self.context.clone();
let subject = self.subject.clone();
let request = self.request.clone();
let pending_messages = self.pending_messages;
self.next = Some(Box::pin(async move {
let inbox = context.client.new_inbox();
let subscriber = context.client.subscribe(inbox.clone()).await?;
context
.client
.publish_with_reply(subject, inbox, request)
.await?;
Ok(Batch {
pending_messages,
subscriber,
context,
})
}));
match self.next.as_mut().unwrap().as_mut().poll(cx) {
Poll::Ready(result) => {
self.next = None;
Poll::Ready(Some(result))
}
Poll::Pending => Poll::Pending,
}
}
Some(next) => match next.as_mut().poll(cx) {
Poll::Ready(result) => {
self.next = None;
Poll::Ready(Some(result))
}
Poll::Pending => Poll::Pending,
},
}
}
}
pub struct Stream {
pending_messages: usize,
request_result_rx: tokio::sync::mpsc::Receiver<Result<bool, crate::Error>>,
request_tx: tokio::sync::watch::Sender<()>,
subscriber: Subscriber,
batch_config: BatchConfig,
context: Context,
pending_request: bool,
task_handle: JoinHandle<()>,
heartbeat_handle: Option<JoinHandle<()>>,
last_seen: Arc<Mutex<Instant>>,
heartbeats_missing: tokio::sync::oneshot::Receiver<()>,
}
impl Drop for Stream {
fn drop(&mut self) {
self.task_handle.abort();
if let Some(handle) = self.heartbeat_handle.take() {
handle.abort()
}
}
}
impl Stream {
async fn stream(
batch_config: BatchConfig,
consumer: &Consumer<Config>,
) -> Result<Stream, Error> {
let inbox = consumer.context.client.new_inbox();
let subscription = consumer.context.client.subscribe(inbox.clone()).await?;
let subject = format!(
"{}.CONSUMER.MSG.NEXT.{}.{}",
consumer.context.prefix, consumer.info.stream_name, consumer.info.name
);
let (request_result_tx, request_result_rx) = tokio::sync::mpsc::channel(1);
let (request_tx, mut request_rx) = tokio::sync::watch::channel(());
let task_handle = tokio::task::spawn({
let consumer = consumer.clone();
let batch = batch_config;
let mut context = consumer.context.clone();
let subject = subject;
let inbox = inbox.clone();
async move {
loop {
let prev_state = context.client.state.borrow().to_owned();
let mut pending_reset = false;
tokio::select! {
_ = context.client.state.changed() => {
let state = context.client.state.borrow().to_owned();
if !(state == crate::connection::State::Connected
&& prev_state != State::Connected) {
continue;
}
debug!("detected !Connected -> Connected state change");
match consumer.fetch_info().await {
Ok(info) => {
if info.num_waiting == 0 {
pending_reset = true;
}
}
Err(err) => request_result_tx.send(Err(err)).await.unwrap(),
}
},
_ = request_rx.changed() => debug!("task received request request"),
_ = tokio::time::sleep(Duration::from_nanos(batch.expires.unwrap() as u64)) => debug!("reached expires timer"),
}
let request = serde_json::to_vec(&batch).map(Bytes::from).unwrap();
let result = context
.client
.publish_with_reply(subject.clone(), inbox.clone(), request.clone())
.await;
if let Err(err) = consumer.context.client.flush().await {
debug!("flush failed: {}", err);
}
debug!("request published");
request_result_tx
.send(result.map(|_| pending_reset))
.await
.unwrap();
trace!("result send over tx");
}
}
});
let last_seen = Arc::new(Mutex::new(Instant::now()));
let (missed_heartbeat_tx, missed_heartbeat_rx) = tokio::sync::oneshot::channel();
let heartbeat_handle = if !batch_config.idle_heartbeat.is_zero() {
debug!("spawning heartbeat checker task");
Some(tokio::task::spawn({
let last_seen = last_seen.clone();
async move {
loop {
tokio::time::sleep(batch_config.idle_heartbeat).await;
debug!("checking for missed heartbeats");
if last_seen
.lock()
.unwrap()
.elapsed()
.ge(&batch_config.idle_heartbeat.saturating_mul(3))
{
debug!("missed heartbeat threshold met");
missed_heartbeat_tx.send(()).unwrap();
break;
}
}
}
}))
} else {
None
};
Ok(Stream {
task_handle,
heartbeat_handle,
request_result_rx,
request_tx,
batch_config,
pending_messages: 0,
subscriber: subscription,
context: consumer.context.clone(),
pending_request: false,
last_seen,
heartbeats_missing: missed_heartbeat_rx,
})
}
}
impl futures::Stream for Stream {
type Item = Result<jetstream::Message, Error>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
loop {
trace!("pending messages: {}", self.pending_messages);
if self.pending_messages <= std::cmp::min(self.batch_config.batch / 2, 100)
&& !self.pending_request
{
debug!("pending messages reached threshold to send new fetch request");
self.request_tx.send(()).unwrap();
self.pending_request = true;
}
match self.heartbeats_missing.try_recv() {
Ok(_) => {
return Poll::Ready(Some(Err(Box::new(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"did not receive idle heartbeat in time",
)))))
}
Err(TryRecvError::Empty) => (),
Err(TryRecvError::Closed) => {
return Poll::Ready(Some(Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"unexpected heartbeat error closure",
)))))
}
}
match self.request_result_rx.poll_recv(cx) {
Poll::Ready(resp) => match resp {
Some(resp) => match resp {
Ok(reset) => {
debug!("request successful, setting pending messages");
if reset {
self.pending_messages = self.batch_config.batch;
} else {
self.pending_messages += self.batch_config.batch;
}
self.pending_request = false;
continue;
}
Err(err) => return Poll::Ready(Some(Err(err))),
},
None => return Poll::Ready(None),
},
Poll::Pending => {
trace!("pending result");
}
}
trace!("polling subscriber");
match self.subscriber.receiver.poll_recv(cx) {
Poll::Ready(maybe_message) => match maybe_message {
Some(message) => match message.status.unwrap_or(StatusCode::OK) {
StatusCode::TIMEOUT => {
debug!("timeout reached, resetting pending messages");
self.pending_messages = self
.pending_messages
.saturating_sub(self.batch_config.batch);
continue;
}
StatusCode::IDLE_HEARTBEAT => {
if !self.batch_config.idle_heartbeat.is_zero() {
*self.last_seen.lock().unwrap() = Instant::now();
}
continue;
}
StatusCode::OK => {
if !self.batch_config.idle_heartbeat.is_zero() {
*self.last_seen.lock().unwrap() = Instant::now();
}
*self.last_seen.lock().unwrap() = Instant::now();
self.pending_messages = self.pending_messages.saturating_sub(1);
return Poll::Ready(Some(Ok(jetstream::Message {
context: self.context.clone(),
message,
})));
}
status => {
return Poll::Ready(Some(Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"eror while processing messages from the stream: {}, {:?}",
status, message.description
),
)))))
}
},
None => return Poll::Ready(None),
},
Poll::Pending => {
debug!("subscriber still pending");
return std::task::Poll::Pending;
}
}
}
}
}
pub struct StreamBuilder<'a> {
batch: usize,
max_bytes: usize,
heartbeat: Duration,
expires: usize,
consumer: &'a Consumer<Config>,
}
impl<'a> StreamBuilder<'a> {
pub fn new(consumer: &'a Consumer<Config>) -> Self {
StreamBuilder {
consumer,
batch: 200,
max_bytes: 0,
expires: Duration::from_secs(30).as_nanos().try_into().unwrap(),
heartbeat: Duration::default(),
}
}
pub fn max_bytes_per_batch(mut self, max_bytes: usize) -> Self {
self.max_bytes = max_bytes;
self
}
pub fn max_messages_per_batch(mut self, batch: usize) -> Self {
self.batch = batch;
self
}
pub fn heartbeat(mut self, heartbeat: Duration) -> Self {
self.heartbeat = heartbeat;
self
}
pub fn expires(mut self, expires: Duration) -> Self {
self.expires = expires.as_nanos().try_into().unwrap();
self
}
pub async fn messages(self) -> Result<Stream, Error> {
Stream::stream(
BatchConfig {
batch: self.batch,
expires: Some(self.expires),
no_wait: false,
max_bytes: self.max_bytes,
idle_heartbeat: self.heartbeat,
},
self.consumer,
)
.await
}
}
pub struct FetchBuilder<'a> {
batch: usize,
max_bytes: usize,
heartbeat: Duration,
expires: usize,
consumer: &'a Consumer<Config>,
}
impl<'a> FetchBuilder<'a> {
pub fn new(consumer: &'a Consumer<Config>) -> Self {
FetchBuilder {
consumer,
batch: 200,
max_bytes: 0,
expires: 0,
heartbeat: Duration::default(),
}
}
pub fn max_bytes(mut self, max_bytes: usize) -> Self {
self.max_bytes = max_bytes;
self
}
pub fn max_messages(mut self, batch: usize) -> Self {
self.batch = batch;
self
}
pub fn heartbeat(mut self, heartbeat: Duration) -> Self {
self.heartbeat = heartbeat;
self
}
pub fn expires(mut self, expires: Duration) -> Self {
self.expires = expires.as_nanos().try_into().unwrap();
self
}
pub async fn messages(self) -> Result<Batch, Error> {
Batch::batch(
BatchConfig {
batch: self.batch,
expires: Some(self.expires),
no_wait: true,
max_bytes: self.max_bytes,
idle_heartbeat: self.heartbeat,
},
self.consumer,
)
.await
}
}
pub struct BatchBuilder<'a> {
batch: usize,
max_bytes: usize,
heartbeat: Duration,
expires: usize,
consumer: &'a Consumer<Config>,
}
impl<'a> BatchBuilder<'a> {
pub fn new(consumer: &'a Consumer<Config>) -> Self {
BatchBuilder {
consumer,
batch: 200,
max_bytes: 0,
expires: 0,
heartbeat: Duration::default(),
}
}
pub fn max_bytes(mut self, max_bytes: usize) -> Self {
self.max_bytes = max_bytes;
self
}
pub fn max_messages(mut self, batch: usize) -> Self {
self.batch = batch;
self
}
pub fn heartbeat(mut self, heartbeat: Duration) -> Self {
self.heartbeat = heartbeat;
self
}
pub fn expires(mut self, expires: Duration) -> Self {
self.expires = expires.as_nanos().try_into().unwrap();
self
}
pub async fn messages(self) -> Result<Batch, Error> {
Batch::batch(
BatchConfig {
batch: self.batch,
expires: Some(self.expires),
no_wait: false,
max_bytes: self.max_bytes,
idle_heartbeat: self.heartbeat,
},
self.consumer,
)
.await
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub struct BatchConfig {
pub batch: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires: Option<usize>,
#[serde(default, skip_serializing_if = "is_default")]
pub no_wait: bool,
pub max_bytes: usize,
#[serde(default, with = "serde_nanos", skip_serializing_if = "is_default")]
pub idle_heartbeat: Duration,
}
fn is_default<T: Default + Eq>(t: &T) -> bool {
t == &T::default()
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Config {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub durable_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(flatten)]
pub deliver_policy: DeliverPolicy,
pub ack_policy: AckPolicy,
#[serde(default, with = "serde_nanos", skip_serializing_if = "is_default")]
pub ack_wait: Duration,
#[serde(default, skip_serializing_if = "is_default")]
pub max_deliver: i64,
#[serde(default, skip_serializing_if = "is_default")]
pub filter_subject: String,
pub replay_policy: ReplayPolicy,
#[serde(default, skip_serializing_if = "is_default")]
pub rate_limit: u64,
#[serde(default, skip_serializing_if = "is_default")]
pub sample_frequency: u8,
#[serde(default, skip_serializing_if = "is_default")]
pub max_waiting: i64,
#[serde(default, skip_serializing_if = "is_default")]
pub max_ack_pending: i64,
#[serde(default, skip_serializing_if = "is_default")]
pub headers_only: bool,
#[serde(default, skip_serializing_if = "is_default")]
pub max_batch: i64,
#[serde(default, with = "serde_nanos", skip_serializing_if = "is_default")]
pub max_expires: Duration,
#[serde(default, with = "serde_nanos", skip_serializing_if = "is_default")]
pub inactive_threshold: Duration,
#[serde(default, skip_serializing_if = "is_default")]
pub num_replicas: usize,
#[serde(default, skip_serializing_if = "is_default")]
pub memory_storage: bool,
}
impl IntoConsumerConfig for &Config {
fn into_consumer_config(self) -> consumer::Config {
self.clone().into_consumer_config()
}
}
impl IntoConsumerConfig for Config {
fn into_consumer_config(self) -> consumer::Config {
jetstream::consumer::Config {
deliver_subject: None,
name: self.name,
durable_name: self.durable_name,
description: self.description,
deliver_group: None,
deliver_policy: self.deliver_policy,
ack_policy: self.ack_policy,
ack_wait: self.ack_wait,
max_deliver: self.max_deliver,
filter_subject: self.filter_subject,
replay_policy: self.replay_policy,
rate_limit: self.rate_limit,
sample_frequency: self.sample_frequency,
max_waiting: self.max_waiting,
max_ack_pending: self.max_ack_pending,
headers_only: self.headers_only,
flow_control: false,
idle_heartbeat: Duration::default(),
max_batch: self.max_batch,
max_expires: self.max_expires,
inactive_threshold: self.inactive_threshold,
num_replicas: self.num_replicas,
memory_storage: self.memory_storage,
}
}
}
impl FromConsumer for Config {
fn try_from_consumer_config(config: consumer::Config) -> Result<Self, Error> {
if config.deliver_subject.is_some() {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"pull consumer cannot have delivery subject",
)));
}
Ok(Config {
durable_name: config.durable_name,
name: config.name,
description: config.description,
deliver_policy: config.deliver_policy,
ack_policy: config.ack_policy,
ack_wait: config.ack_wait,
max_deliver: config.max_deliver,
filter_subject: config.filter_subject,
replay_policy: config.replay_policy,
rate_limit: config.rate_limit,
sample_frequency: config.sample_frequency,
max_waiting: config.max_waiting,
max_ack_pending: config.max_ack_pending,
headers_only: config.headers_only,
max_batch: config.max_batch,
max_expires: config.max_expires,
inactive_threshold: config.inactive_threshold,
num_replicas: config.num_replicas,
memory_storage: config.memory_storage,
})
}
}