1use std::sync::Arc;
9use std::sync::atomic::{AtomicU64, Ordering};
10
11use futures::{Stream, StreamExt};
12
13use pulsar::consumer::{Consumer, DeadLetterPolicy};
14use pulsar::proto::MessageIdData;
15use pulsar::{SubType, TokioExecutor};
16use ruststream::{AckError, Subscriber};
17use tokio::sync::mpsc;
18
19use crate::broker::Core;
20use crate::error::{PulsarError, box_err};
21use crate::message::{DriverCmd, PulsarMessage, PulsarPosition, SeekCmd, SettleKind, SettleSender};
22use crate::subscription::{PulsarSubscription, SubscriptionType, Topics};
23
24const CHANNEL_CAPACITY: usize = 16;
27
28pub struct PulsarSubscriber {
32 topic: String,
33 rx: mpsc::Receiver<(u64, Result<PulsarMessage, PulsarError>)>,
34 cmd: SettleSender,
35 epoch: Arc<AtomicU64>,
38}
39
40impl std::fmt::Debug for PulsarSubscriber {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("PulsarSubscriber")
43 .field("topic", &self.topic)
44 .finish_non_exhaustive()
45 }
46}
47
48impl PulsarSubscriber {
49 #[must_use]
51 pub fn topic(&self) -> &str {
52 &self.topic
53 }
54
55 pub(crate) async fn open(
56 core: &Core,
57 descriptor: PulsarSubscription,
58 ) -> Result<Self, PulsarError> {
59 let display = descriptor.display_topic();
60 let mut builder = core
61 .client
62 .consumer()
63 .with_subscription(&descriptor.subscription)
64 .with_subscription_type(match descriptor.sub_type {
65 SubscriptionType::Exclusive => SubType::Exclusive,
66 SubscriptionType::Shared => SubType::Shared,
67 SubscriptionType::Failover => SubType::Failover,
68 SubscriptionType::KeyShared => SubType::KeyShared,
69 });
70 match &descriptor.topics {
71 Topics::List(topics) => {
72 builder = builder.with_topics(topics);
73 }
74 Topics::Pattern(pattern) => {
75 let regex = regex::Regex::new(pattern)
76 .map_err(|e| PulsarError::Invalid(format!("invalid pattern: {e}")))?;
77 builder = builder.with_topic_regex(regex);
78 }
79 }
80 if let Some(dead_letter) = &descriptor.dead_letter {
81 builder = builder.with_dead_letter_policy(DeadLetterPolicy {
82 max_redeliver_count: dead_letter.max_deliveries,
83 dead_letter_topic: dead_letter.topic.clone(),
84 });
85 }
86 if descriptor.ack_timeout.is_some() {
87 builder = builder.with_unacked_message_resend_delay(descriptor.ack_timeout);
88 }
89
90 let consumer: Consumer<Vec<u8>, TokioExecutor> =
91 builder.build().await.map_err(|e| PulsarError::Subscribe {
92 topic: display.clone(),
93 source: box_err(e),
94 })?;
95
96 let (out_tx, out_rx) = mpsc::channel(CHANNEL_CAPACITY);
97 let (settle_tx, settle_rx) = mpsc::unbounded_channel();
98 let epoch = Arc::new(AtomicU64::new(0));
99 tokio::spawn(drive(
100 consumer,
101 core.client.clone(),
102 out_tx,
103 settle_tx.clone(),
104 settle_rx,
105 display.clone(),
106 Arc::clone(&epoch),
107 ));
108
109 Ok(Self {
110 topic: display,
111 rx: out_rx,
112 cmd: settle_tx,
113 epoch,
114 })
115 }
116}
117
118#[derive(Clone)]
124pub struct PulsarSeeker {
125 cmd: SettleSender,
126}
127
128impl std::fmt::Debug for PulsarSeeker {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 f.debug_struct("PulsarSeeker").finish_non_exhaustive()
131 }
132}
133
134impl ruststream::Seeker for PulsarSeeker {
135 type Position = PulsarPosition;
136 type Error = PulsarError;
137
138 async fn seek(&self, to: PulsarPosition) -> Result<(), PulsarError> {
139 let (done, wait) = tokio::sync::oneshot::channel();
140 self.cmd
141 .send(DriverCmd::Seek(SeekCmd { position: to, done }))
142 .map_err(|_| PulsarError::Receive {
143 topic: String::new(),
144 source: Box::from("the subscription's driver task has shut down"),
145 })?;
146 wait.await.map_err(|_| PulsarError::Receive {
147 topic: String::new(),
148 source: Box::from("the subscription's driver task has shut down"),
149 })?
150 }
151}
152
153impl ruststream::Seekable for PulsarSubscriber {
154 type Seeker = PulsarSeeker;
155
156 fn seeker(&self) -> PulsarSeeker {
157 PulsarSeeker {
158 cmd: self.cmd.clone(),
159 }
160 }
161}
162
163impl Subscriber for PulsarSubscriber {
164 type Message = PulsarMessage;
165 type Error = PulsarError;
166
167 fn stream(&mut self) -> impl Stream<Item = Result<PulsarMessage, PulsarError>> + Send + '_ {
168 futures::stream::poll_fn(move |cx| {
173 loop {
174 match self.rx.poll_recv(cx) {
175 std::task::Poll::Ready(Some((epoch, item))) => {
176 if epoch == self.epoch.load(Ordering::Acquire) {
177 return std::task::Poll::Ready(Some(item));
178 }
179 }
180 std::task::Poll::Ready(None) => return std::task::Poll::Ready(None),
181 std::task::Poll::Pending => return std::task::Poll::Pending,
182 }
183 }
184 })
185 }
186}
187
188async fn drive(
189 mut consumer: Consumer<Vec<u8>, TokioExecutor>,
190 client: pulsar::Pulsar<TokioExecutor>,
191 out: mpsc::Sender<(u64, Result<PulsarMessage, PulsarError>)>,
192 settle_tx: SettleSender,
193 mut settle_rx: mpsc::UnboundedReceiver<DriverCmd>,
194 topic: String,
195 epoch: Arc<AtomicU64>,
196) {
197 let mut pending: Option<(u64, PulsarMessage)> = None;
201 loop {
202 if let Some((stamp, msg)) = pending.take() {
203 tokio::select! {
206 biased;
207 cmd = settle_rx.recv() => {
208 match cmd {
209 Some(DriverCmd::Seek(seek)) => {
210 epoch.fetch_add(1, Ordering::Release);
212 apply_seek(&mut consumer, &client, seek).await;
213 }
214 Some(cmd) => {
215 apply(&mut consumer, &client, cmd).await;
216 pending = Some((stamp, msg));
217 }
218 None => pending = Some((stamp, msg)),
219 }
220 }
221 permit = out.reserve() => match permit {
222 Ok(permit) => permit.send((stamp, Ok(msg))),
223 Err(_) => break, },
225 }
226 } else {
227 let current = epoch.load(Ordering::Acquire);
228 tokio::select! {
229 biased;
230 cmd = settle_rx.recv() => {
231 match cmd {
232 Some(DriverCmd::Seek(seek)) => {
233 epoch.fetch_add(1, Ordering::Release);
234 apply_seek(&mut consumer, &client, seek).await;
235 }
236 Some(cmd) => apply(&mut consumer, &client, cmd).await,
237 None => {}
238 }
239 }
240 () = out.closed() => break, next = consumer.next() => match next {
242 Some(Ok(message)) => {
243 pending = Some((current, PulsarMessage::new(&message, settle_tx.clone())));
244 }
245 Some(Err(err)) => {
246 if out
249 .send((
250 current,
251 Err(PulsarError::Receive {
252 topic: topic.clone(),
253 source: box_err(err),
254 }),
255 ))
256 .await
257 .is_err()
258 {
259 break;
260 }
261 }
262 None => {
263 let _ = out
265 .send((
266 current,
267 Err(PulsarError::Receive {
268 topic: topic.clone(),
269 source: Box::from("the consumer stream ended"),
270 }),
271 ))
272 .await;
273 break;
274 }
275 },
276 }
277 }
278 }
279
280 drop(settle_tx);
283 while let Some(cmd) = settle_rx.recv().await {
284 apply(&mut consumer, &client, cmd).await;
285 }
286 if let Err(err) = Box::pin(consumer.close()).await {
287 tracing::debug!(topic = %topic, error = %err, "pulsar consumer close failed");
288 }
289}
290
291async fn apply(
292 consumer: &mut Consumer<Vec<u8>, TokioExecutor>,
293 client: &pulsar::Pulsar<TokioExecutor>,
294 cmd: DriverCmd,
295) {
296 match cmd {
297 DriverCmd::Settle(cmd) => {
298 let result = match cmd.kind {
299 SettleKind::Ack => consumer.ack_with_id(&cmd.topic, cmd.id).await,
300 SettleKind::Nack => consumer.nack_with_id(&cmd.topic, cmd.id).await,
301 };
302 let _ = cmd
303 .done
304 .send(result.map_err(|e| AckError::Broker(box_err(e))));
305 }
306 DriverCmd::Seek(seek) => apply_seek(consumer, client, seek).await,
307 }
308}
309
310const EARLIEST_MARK: u64 = u64::MAX;
314const LATEST_MARK: u64 = i64::MAX.unsigned_abs();
315
316fn end_of_log(mark: u64) -> MessageIdData {
317 MessageIdData {
318 ledger_id: mark,
319 entry_id: mark,
320 partition: Some(-1),
322 ..MessageIdData::default()
323 }
324}
325
326async fn apply_seek(
327 consumer: &mut Consumer<Vec<u8>, TokioExecutor>,
328 client: &pulsar::Pulsar<TokioExecutor>,
329 SeekCmd { position, done }: SeekCmd,
330) {
331 let (message_id, timestamp) = match position {
332 PulsarPosition::Earliest => (Some(end_of_log(EARLIEST_MARK)), None),
333 PulsarPosition::Latest => (Some(end_of_log(LATEST_MARK)), None),
334 PulsarPosition::MessageId(id) => (Some(id), None),
335 PulsarPosition::Timestamp(millis) => (None, Some(millis)),
336 };
337 let topics = consumer.topics();
341 let result =
342 Box::pin(consumer.seek(Some(topics.clone()), message_id, timestamp, client.clone()))
343 .await
344 .map_err(|e| PulsarError::Receive {
345 topic: topics.join(","),
346 source: box_err(e),
347 });
348 let _ = done.send(result);
349}