use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use futures::{Stream, StreamExt};
use pulsar::consumer::{Consumer, DeadLetterPolicy};
use pulsar::proto::MessageIdData;
use pulsar::{SubType, TokioExecutor};
use ruststream::{AckError, Subscriber};
use tokio::sync::mpsc;
use crate::broker::Core;
use crate::error::{PulsarError, box_err};
use crate::message::{DriverCmd, PulsarMessage, PulsarPosition, SeekCmd, SettleKind, SettleSender};
use crate::subscription::{PulsarSubscription, SubscriptionType, Topics};
const CHANNEL_CAPACITY: usize = 16;
pub struct PulsarSubscriber {
topic: String,
rx: mpsc::Receiver<(u64, Result<PulsarMessage, PulsarError>)>,
cmd: SettleSender,
epoch: Arc<AtomicU64>,
}
impl std::fmt::Debug for PulsarSubscriber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PulsarSubscriber")
.field("topic", &self.topic)
.finish_non_exhaustive()
}
}
impl PulsarSubscriber {
#[must_use]
pub fn topic(&self) -> &str {
&self.topic
}
pub(crate) async fn open(
core: &Core,
descriptor: PulsarSubscription,
) -> Result<Self, PulsarError> {
let display = descriptor.display_topic();
let mut builder = core
.client
.consumer()
.with_subscription(&descriptor.subscription)
.with_subscription_type(match descriptor.sub_type {
SubscriptionType::Exclusive => SubType::Exclusive,
SubscriptionType::Shared => SubType::Shared,
SubscriptionType::Failover => SubType::Failover,
SubscriptionType::KeyShared => SubType::KeyShared,
});
match &descriptor.topics {
Topics::List(topics) => {
builder = builder.with_topics(topics);
}
Topics::Pattern(pattern) => {
let regex = regex::Regex::new(pattern)
.map_err(|e| PulsarError::Invalid(format!("invalid pattern: {e}")))?;
builder = builder.with_topic_regex(regex);
}
}
if let Some(dead_letter) = &descriptor.dead_letter {
builder = builder.with_dead_letter_policy(DeadLetterPolicy {
max_redeliver_count: dead_letter.max_deliveries,
dead_letter_topic: dead_letter.topic.clone(),
});
}
if descriptor.ack_timeout.is_some() {
builder = builder.with_unacked_message_resend_delay(descriptor.ack_timeout);
}
let consumer: Consumer<Vec<u8>, TokioExecutor> =
builder.build().await.map_err(|e| PulsarError::Subscribe {
topic: display.clone(),
source: box_err(e),
})?;
let (out_tx, out_rx) = mpsc::channel(CHANNEL_CAPACITY);
let (settle_tx, settle_rx) = mpsc::unbounded_channel();
let epoch = Arc::new(AtomicU64::new(0));
tokio::spawn(drive(
consumer,
core.client.clone(),
out_tx,
settle_tx.clone(),
settle_rx,
display.clone(),
Arc::clone(&epoch),
));
Ok(Self {
topic: display,
rx: out_rx,
cmd: settle_tx,
epoch,
})
}
}
#[derive(Clone)]
pub struct PulsarSeeker {
cmd: SettleSender,
}
impl std::fmt::Debug for PulsarSeeker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PulsarSeeker").finish_non_exhaustive()
}
}
impl ruststream::Seeker for PulsarSeeker {
type Position = PulsarPosition;
type Error = PulsarError;
async fn seek(&self, to: PulsarPosition) -> Result<(), PulsarError> {
let (done, wait) = tokio::sync::oneshot::channel();
self.cmd
.send(DriverCmd::Seek(SeekCmd { position: to, done }))
.map_err(|_| PulsarError::Receive {
topic: String::new(),
source: Box::from("the subscription's driver task has shut down"),
})?;
wait.await.map_err(|_| PulsarError::Receive {
topic: String::new(),
source: Box::from("the subscription's driver task has shut down"),
})?
}
}
impl ruststream::Seekable for PulsarSubscriber {
type Seeker = PulsarSeeker;
fn seeker(&self) -> PulsarSeeker {
PulsarSeeker {
cmd: self.cmd.clone(),
}
}
}
impl Subscriber for PulsarSubscriber {
type Message = PulsarMessage;
type Error = PulsarError;
fn stream(&mut self) -> impl Stream<Item = Result<PulsarMessage, PulsarError>> + Send + '_ {
futures::stream::poll_fn(move |cx| {
loop {
match self.rx.poll_recv(cx) {
std::task::Poll::Ready(Some((epoch, item))) => {
if epoch == self.epoch.load(Ordering::Acquire) {
return std::task::Poll::Ready(Some(item));
}
}
std::task::Poll::Ready(None) => return std::task::Poll::Ready(None),
std::task::Poll::Pending => return std::task::Poll::Pending,
}
}
})
}
}
async fn drive(
mut consumer: Consumer<Vec<u8>, TokioExecutor>,
client: pulsar::Pulsar<TokioExecutor>,
out: mpsc::Sender<(u64, Result<PulsarMessage, PulsarError>)>,
settle_tx: SettleSender,
mut settle_rx: mpsc::UnboundedReceiver<DriverCmd>,
topic: String,
epoch: Arc<AtomicU64>,
) {
let mut pending: Option<(u64, PulsarMessage)> = None;
loop {
if let Some((stamp, msg)) = pending.take() {
tokio::select! {
biased;
cmd = settle_rx.recv() => {
match cmd {
Some(DriverCmd::Seek(seek)) => {
epoch.fetch_add(1, Ordering::Release);
apply_seek(&mut consumer, &client, seek).await;
}
Some(cmd) => {
apply(&mut consumer, &client, cmd).await;
pending = Some((stamp, msg));
}
None => pending = Some((stamp, msg)),
}
}
permit = out.reserve() => match permit {
Ok(permit) => permit.send((stamp, Ok(msg))),
Err(_) => break, },
}
} else {
let current = epoch.load(Ordering::Acquire);
tokio::select! {
biased;
cmd = settle_rx.recv() => {
match cmd {
Some(DriverCmd::Seek(seek)) => {
epoch.fetch_add(1, Ordering::Release);
apply_seek(&mut consumer, &client, seek).await;
}
Some(cmd) => apply(&mut consumer, &client, cmd).await,
None => {}
}
}
() = out.closed() => break, next = consumer.next() => match next {
Some(Ok(message)) => {
pending = Some((current, PulsarMessage::new(&message, settle_tx.clone())));
}
Some(Err(err)) => {
if out
.send((
current,
Err(PulsarError::Receive {
topic: topic.clone(),
source: box_err(err),
}),
))
.await
.is_err()
{
break;
}
}
None => {
let _ = out
.send((
current,
Err(PulsarError::Receive {
topic: topic.clone(),
source: Box::from("the consumer stream ended"),
}),
))
.await;
break;
}
},
}
}
}
drop(settle_tx);
while let Some(cmd) = settle_rx.recv().await {
apply(&mut consumer, &client, cmd).await;
}
if let Err(err) = Box::pin(consumer.close()).await {
tracing::debug!(topic = %topic, error = %err, "pulsar consumer close failed");
}
}
async fn apply(
consumer: &mut Consumer<Vec<u8>, TokioExecutor>,
client: &pulsar::Pulsar<TokioExecutor>,
cmd: DriverCmd,
) {
match cmd {
DriverCmd::Settle(cmd) => {
let result = match cmd.kind {
SettleKind::Ack => consumer.ack_with_id(&cmd.topic, cmd.id).await,
SettleKind::Nack => consumer.nack_with_id(&cmd.topic, cmd.id).await,
};
let _ = cmd
.done
.send(result.map_err(|e| AckError::Broker(box_err(e))));
}
DriverCmd::Seek(seek) => apply_seek(consumer, client, seek).await,
}
}
const EARLIEST_MARK: u64 = u64::MAX;
const LATEST_MARK: u64 = i64::MAX.unsigned_abs();
fn end_of_log(mark: u64) -> MessageIdData {
MessageIdData {
ledger_id: mark,
entry_id: mark,
partition: Some(-1),
..MessageIdData::default()
}
}
async fn apply_seek(
consumer: &mut Consumer<Vec<u8>, TokioExecutor>,
client: &pulsar::Pulsar<TokioExecutor>,
SeekCmd { position, done }: SeekCmd,
) {
let (message_id, timestamp) = match position {
PulsarPosition::Earliest => (Some(end_of_log(EARLIEST_MARK)), None),
PulsarPosition::Latest => (Some(end_of_log(LATEST_MARK)), None),
PulsarPosition::MessageId(id) => (Some(id), None),
PulsarPosition::Timestamp(millis) => (None, Some(millis)),
};
let topics = consumer.topics();
let result =
Box::pin(consumer.seek(Some(topics.clone()), message_id, timestamp, client.clone()))
.await
.map_err(|e| PulsarError::Receive {
topic: topics.join(","),
source: box_err(e),
});
let _ = done.send(result);
}