1use std::num::NonZeroUsize;
14use std::sync::Arc;
15use std::sync::atomic::{AtomicU64, Ordering};
16use std::task::{Context, Poll};
17use std::time::Duration;
18
19use futures::{Stream, StreamExt};
20
21use pulsar::consumer::{Consumer, DeadLetterPolicy};
22use pulsar::proto::MessageIdData;
23use pulsar::{Pulsar, SubType, TokioExecutor};
24use ruststream::{AckError, BatchSubscriber, BufferedSubscriber, Seekable, Subscriber};
25use tokio::sync::{mpsc, oneshot};
26use tokio::time::sleep;
27use tracing::warn;
28
29use crate::broker::Core;
30use crate::error::{PulsarError, box_err};
31#[cfg(feature = "testing")]
32use crate::in_process::{LogSeeker, Queued};
33use crate::message::{
34 DriverCmd, NackAfterCmd, PulsarMessage, PulsarPosition, SeekCmd, SettleKind, SettleSender,
35 send_settle,
36};
37use crate::pattern::TopicPattern;
38use crate::subscription::{PulsarSubscription, SubscriptionType, Topics};
39
40const CHANNEL_CAPACITY: usize = 16;
43
44pub struct PulsarSubscriber {
49 topic: String,
50 inner: BufferedSubscriber<Deliveries>,
54}
55
56#[derive(Debug)]
62enum Deliveries {
63 Consumer(ConsumerDeliveries),
64 #[cfg(feature = "testing")]
65 InProcess(Queued),
66}
67
68#[cfg(not(feature = "testing"))]
71const _: () = assert!(size_of::<Deliveries>() == size_of::<ConsumerDeliveries>());
72
73#[derive(Debug)]
75struct ConsumerDeliveries {
76 rx: mpsc::Receiver<(u64, Result<PulsarMessage, PulsarError>)>,
77 cmd: SettleSender,
78 epoch: Arc<AtomicU64>,
81}
82
83impl ConsumerDeliveries {
84 fn poll_next(
87 &mut self,
88 cx: &mut Context<'_>,
89 ) -> Poll<Option<Result<PulsarMessage, PulsarError>>> {
90 loop {
91 match self.rx.poll_recv(cx) {
92 Poll::Ready(Some((epoch, item))) => {
93 if epoch == self.epoch.load(Ordering::Acquire) {
94 return Poll::Ready(Some(item));
95 }
96 }
97 Poll::Ready(None) => return Poll::Ready(None),
98 Poll::Pending => return Poll::Pending,
99 }
100 }
101 }
102}
103
104impl std::fmt::Debug for PulsarSubscriber {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 f.debug_struct("PulsarSubscriber")
107 .field("topic", &self.topic)
108 .finish_non_exhaustive()
109 }
110}
111
112impl PulsarSubscriber {
113 #[must_use]
115 pub fn topic(&self) -> &str {
116 &self.topic
117 }
118
119 pub(crate) async fn open(
122 client: &Pulsar<TokioExecutor>,
123 descriptor: PulsarSubscription,
124 core: &Arc<Core>,
125 ) -> Result<Self, PulsarError> {
126 let display = descriptor.display_topic();
127 let batch_wait = descriptor.batch_wait;
128 let mut builder = client
129 .consumer()
130 .with_subscription(&descriptor.subscription)
131 .with_subscription_type(match descriptor.sub_type {
132 SubscriptionType::Exclusive => SubType::Exclusive,
133 SubscriptionType::Shared => SubType::Shared,
134 SubscriptionType::Failover => SubType::Failover,
135 SubscriptionType::KeyShared => SubType::KeyShared,
136 });
137 match &descriptor.topics {
138 Topics::List(topics) => {
139 builder = builder.with_topics(topics);
140 }
141 Topics::Pattern(pattern) => {
142 let pattern = TopicPattern::parse(pattern)?;
143 builder = builder
144 .with_lookup_namespace(pattern.lookup_namespace())
145 .with_topic_regex(pattern.regex().clone());
146 }
147 }
148 if let Some(dead_letter) = descriptor.dead_letter_policy()? {
149 builder = builder.with_dead_letter_policy(DeadLetterPolicy {
152 max_redeliver_count: usize::try_from(dead_letter.max_deliveries)
153 .unwrap_or(usize::MAX),
154 dead_letter_topic: dead_letter.topic,
155 });
156 }
157 if descriptor.ack_timeout.is_some() {
158 builder = builder.with_unacked_message_resend_delay(descriptor.ack_timeout);
159 }
160
161 let consumer: Consumer<Vec<u8>, TokioExecutor> =
162 builder.build().await.map_err(|e| PulsarError::Subscribe {
163 topic: display.clone(),
164 source: box_err(e),
165 })?;
166
167 let (out_tx, out_rx) = mpsc::channel(CHANNEL_CAPACITY);
168 let (settle_tx, settle_rx) = mpsc::unbounded_channel();
169 let epoch = Arc::new(AtomicU64::new(0));
170 core.runtime.spawn(drive(
171 consumer,
172 client.clone(),
173 Arc::clone(core),
174 out_tx,
175 settle_tx.clone(),
176 settle_rx,
177 display.clone(),
178 Arc::clone(&epoch),
179 descriptor.ack_timeout,
180 ));
181
182 Ok(Self::batching(
183 display,
184 Deliveries::Consumer(ConsumerDeliveries {
185 rx: out_rx,
186 cmd: settle_tx,
187 epoch,
188 }),
189 batch_wait,
190 ))
191 }
192
193 #[cfg(feature = "testing")]
195 pub(crate) fn in_process(topic: String, queued: Queued, batch_wait: Duration) -> Self {
196 Self::batching(topic, Deliveries::InProcess(queued), batch_wait)
197 }
198
199 fn batching(topic: String, wire: Deliveries, batch_wait: Duration) -> Self {
205 Self {
206 topic,
207 inner: BufferedSubscriber::new(wire).max_wait(batch_wait),
208 }
209 }
210}
211
212#[derive(Clone)]
222pub struct PulsarSeeker {
223 inner: SeekerKind,
224}
225
226#[derive(Clone)]
229enum SeekerKind {
230 Consumer(SettleSender),
232 #[cfg(feature = "testing")]
234 InProcess(LogSeeker),
235}
236
237impl std::fmt::Debug for PulsarSeeker {
238 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239 f.debug_struct("PulsarSeeker").finish_non_exhaustive()
240 }
241}
242
243impl PulsarSeeker {
244 pub(crate) fn new(cmd: SettleSender) -> Self {
247 Self {
248 inner: SeekerKind::Consumer(cmd),
249 }
250 }
251
252 #[cfg(feature = "testing")]
254 pub(crate) const fn in_process(seeker: LogSeeker) -> Self {
255 Self {
256 inner: SeekerKind::InProcess(seeker),
257 }
258 }
259}
260
261impl ruststream::Seeker for PulsarSeeker {
262 type Position = PulsarPosition;
263 type Error = PulsarError;
264
265 async fn seek(&self, to: PulsarPosition) -> Result<(), PulsarError> {
266 match &self.inner {
267 SeekerKind::Consumer(cmd) => {
268 let (done, wait) = oneshot::channel();
269 cmd.send(DriverCmd::Seek(SeekCmd { position: to, done }))
272 .map_err(|_| PulsarError::NotConnected)?;
273 wait.await.map_err(|_| PulsarError::NotConnected)?
274 }
275 #[cfg(feature = "testing")]
276 SeekerKind::InProcess(seeker) => seeker.seek(&to),
277 }
278 }
279}
280
281impl Seekable for Deliveries {
282 type Seeker = PulsarSeeker;
283
284 fn seeker(&self) -> PulsarSeeker {
285 match self {
286 Self::Consumer(consumer) => PulsarSeeker::new(consumer.cmd.clone()),
287 #[cfg(feature = "testing")]
288 Self::InProcess(queued) => queued.seeker(),
289 }
290 }
291}
292
293impl Subscriber for Deliveries {
294 type Message = PulsarMessage;
295 type Error = PulsarError;
296
297 fn stream(&mut self) -> impl Stream<Item = Result<PulsarMessage, PulsarError>> + Send + '_ {
298 futures::stream::poll_fn(move |cx| match self {
302 Self::Consumer(consumer) => consumer.poll_next(cx),
303 #[cfg(feature = "testing")]
304 Self::InProcess(queued) => queued.poll_next(cx),
305 })
306 }
307}
308
309impl Seekable for PulsarSubscriber {
312 type Seeker = PulsarSeeker;
313
314 fn seeker(&self) -> PulsarSeeker {
315 self.inner.seeker()
316 }
317}
318
319impl Subscriber for PulsarSubscriber {
320 type Message = PulsarMessage;
321 type Error = PulsarError;
322
323 fn stream(&mut self) -> impl Stream<Item = Result<PulsarMessage, PulsarError>> + Send + '_ {
324 self.inner.stream()
325 }
326}
327
328impl BatchSubscriber for PulsarSubscriber {
335 type Batch = Vec<PulsarMessage>;
336
337 fn batches(
338 &mut self,
339 size: NonZeroUsize,
340 ) -> impl Stream<Item = Result<Self::Batch, PulsarError>> + Send + '_ {
341 self.inner.batches(size)
342 }
343}
344
345#[allow(clippy::too_many_arguments)]
346async fn drive(
347 mut consumer: Consumer<Vec<u8>, TokioExecutor>,
348 client: Pulsar<TokioExecutor>,
349 core: Arc<Core>,
351 out: mpsc::Sender<(u64, Result<PulsarMessage, PulsarError>)>,
352 settle_tx: SettleSender,
353 mut settle_rx: mpsc::UnboundedReceiver<DriverCmd>,
354 topic: String,
355 epoch: Arc<AtomicU64>,
356 ack_timeout: Option<Duration>,
359) {
360 let mut pending: Option<(u64, PulsarMessage)> = None;
364 loop {
365 if let Some((stamp, msg)) = pending.take() {
366 tokio::select! {
369 biased;
370 cmd = settle_rx.recv() => {
371 match cmd {
372 Some(DriverCmd::Seek(seek)) => {
373 epoch.fetch_add(1, Ordering::Release);
375 apply_seek(&mut consumer, &client, &core, seek).await;
376 }
377 Some(cmd) => {
378 apply(&mut consumer, &client, &core, cmd).await;
379 pending = Some((stamp, msg));
380 }
381 None => pending = Some((stamp, msg)),
382 }
383 }
384 permit = out.reserve() => match permit {
385 Ok(permit) => permit.send((stamp, Ok(msg))),
386 Err(_) => break, },
388 }
389 } else {
390 let current = epoch.load(Ordering::Acquire);
391 tokio::select! {
392 biased;
393 cmd = settle_rx.recv() => {
394 match cmd {
395 Some(DriverCmd::Seek(seek)) => {
396 epoch.fetch_add(1, Ordering::Release);
397 apply_seek(&mut consumer, &client, &core, seek).await;
398 }
399 Some(cmd) => apply(&mut consumer, &client, &core, cmd).await,
400 None => {}
401 }
402 }
403 () = out.closed() => break, next = consumer.next() => match next {
405 Some(Ok(message)) => {
406 pending = Some((
407 current,
408 PulsarMessage::new(&message, settle_tx.clone(), ack_timeout),
409 ));
410 }
411 Some(Err(err)) => {
412 if out
415 .send((
416 current,
417 Err(PulsarError::Receive {
418 topic: topic.clone(),
419 source: box_err(err),
420 }),
421 ))
422 .await
423 .is_err()
424 {
425 break;
426 }
427 }
428 None => {
429 let _ = out
431 .send((
432 current,
433 Err(PulsarError::Receive {
434 topic: topic.clone(),
435 source: Box::from("the consumer stream ended"),
436 }),
437 ))
438 .await;
439 break;
440 }
441 },
442 }
443 }
444 }
445
446 drop(settle_tx);
449 while let Some(cmd) = settle_rx.recv().await {
450 apply(&mut consumer, &client, &core, cmd).await;
451 }
452 if let Err(err) = Box::pin(consumer.close()).await {
453 tracing::debug!(topic = %topic, error = %err, "pulsar consumer close failed");
454 }
455}
456
457async fn apply(
458 consumer: &mut Consumer<Vec<u8>, TokioExecutor>,
459 client: &Pulsar<TokioExecutor>,
460 core: &Core,
461 cmd: DriverCmd,
462) {
463 match cmd {
464 DriverCmd::NackAfter(cmd) => wait_then_nack(cmd),
465 DriverCmd::Settle(cmd) => {
466 let result = match cmd.kind {
467 SettleKind::Ack => consumer.ack_with_id(&cmd.topic, cmd.id).await,
468 SettleKind::Nack => consumer.nack_with_id(&cmd.topic, cmd.id).await,
469 };
470 let _ = cmd
471 .done
472 .send(result.map_err(|e| AckError::Broker(box_err(e))));
473 }
474 DriverCmd::Seek(seek) => apply_seek(consumer, client, core, seek).await,
475 }
476}
477
478fn wait_then_nack(
483 NackAfterCmd {
484 topic,
485 id,
486 delay,
487 back,
488 }: NackAfterCmd,
489) {
490 tokio::spawn(async move {
491 sleep(delay).await;
492 let named = topic.clone();
493 if let Err(err) = send_settle(back, topic, id, SettleKind::Nack).await {
494 warn!(
495 target: "ruststream_pulsar::subscriber",
496 topic = %named,
497 delay = ?delay,
498 error = %err,
499 "a delayed retry could not be negatively acknowledged; the broker redelivers it \
500 once the consumer's ack timeout elapses",
501 );
502 }
503 });
504}
505
506const EARLIEST_MARK: u64 = u64::MAX;
510const LATEST_MARK: u64 = i64::MAX.unsigned_abs();
511
512fn end_of_log(mark: u64) -> MessageIdData {
513 MessageIdData {
514 ledger_id: mark,
515 entry_id: mark,
516 partition: Some(-1),
518 ..MessageIdData::default()
519 }
520}
521
522async fn apply_seek(
526 consumer: &mut Consumer<Vec<u8>, TokioExecutor>,
527 client: &Pulsar<TokioExecutor>,
528 core: &Core,
529 SeekCmd { position, done }: SeekCmd,
530) {
531 if let Err(closed) = core.ensure_open() {
532 let _ = done.send(Err(closed));
533 return;
534 }
535 let (message_id, timestamp) = match position {
536 PulsarPosition::Earliest => (Some(end_of_log(EARLIEST_MARK)), None),
537 PulsarPosition::Latest => (Some(end_of_log(LATEST_MARK)), None),
538 PulsarPosition::MessageId(id) => (Some(id), None),
539 PulsarPosition::Timestamp(millis) => (None, Some(millis)),
540 };
541 let topics = consumer.topics();
545 let result =
546 Box::pin(consumer.seek(Some(topics.clone()), message_id, timestamp, client.clone()))
547 .await
548 .map_err(|e| PulsarError::Receive {
549 topic: topics.join(","),
550 source: box_err(e),
551 });
552 let _ = done.send(result);
553}
554
555#[cfg(test)]
556mod tests {
557 use ruststream::Seeker;
558
559 use super::*;
560
561 #[tokio::test]
564 async fn a_seek_after_the_driver_is_gone_reports_not_connected() {
565 let (cmd, driver) = mpsc::unbounded_channel();
566 drop(driver);
567
568 let refused = PulsarSeeker::new(cmd)
569 .seek(PulsarPosition::earliest())
570 .await
571 .expect_err("no driver is left to seek");
572
573 assert!(matches!(refused, PulsarError::NotConnected), "{refused:?}");
574 }
575
576 #[tokio::test]
579 async fn a_seek_the_driver_drops_unanswered_reports_not_connected() {
580 let (cmd, mut driver) = mpsc::unbounded_channel();
581 let seeker = PulsarSeeker::new(cmd);
582
583 let (refused, ()) = tokio::join!(seeker.seek(PulsarPosition::earliest()), async {
584 drop(driver.recv().await);
585 });
586
587 let refused = refused.expect_err("the driver went without answering");
588 assert!(matches!(refused, PulsarError::NotConnected), "{refused:?}");
589 }
590}