1use std::future::Future;
15use std::num::NonZeroUsize;
16use std::sync::Arc;
17use std::sync::atomic::{AtomicU64, Ordering};
18
19use futures::Stream;
20#[cfg(feature = "testing")]
21use futures::future::Either;
22use ruststream::{
23 AckError, BatchSubscriber, BufferedSubscriber, HeaderMap, IncomingMessage, Positioned,
24 Seekable, Seeker, Subscriber,
25};
26use sea_streamer_file::{FileConsumer, FileErr, MessageSource, SeekTarget, is_end_of_stream};
27use sea_streamer_types::{Consumer as _, ShardId, SharedMessage, StreamErr, StreamKey, Timestamp};
28use tokio::runtime::Handle;
29use tokio::sync::mpsc::error::TrySendError;
30use tokio::sync::{mpsc, oneshot};
31
32use crate::batching::BATCH_MAX_WAIT;
33use crate::error::{SeaFileError, box_err};
34#[cfg(feature = "testing")]
35use crate::in_process::{FileQueue, LogSeeker};
36use crate::message::{FilePosition, SeaMessage};
37
38const CHANNEL_CAPACITY: usize = 64;
40
41pub(crate) struct SeekCmd {
42 position: FilePosition,
43 done: oneshot::Sender<Result<(), SeaFileError>>,
44}
45
46pub(crate) struct Stamped {
47 epoch: u64,
48 item: Option<Result<SeaMessage, SeaFileError>>,
49}
50
51pub struct FileSubscriber {
95 stream: Arc<str>,
97 inner: BufferedSubscriber<Deliveries>,
100}
101
102impl std::fmt::Debug for FileSubscriber {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.debug_struct("FileSubscriber")
105 .field("stream", &self.stream)
106 .finish_non_exhaustive()
107 }
108}
109
110impl FileSubscriber {
111 #[must_use]
113 pub fn stream_key(&self) -> &str {
114 &self.stream
115 }
116
117 pub(crate) fn spawn(
121 runtime: &Handle,
122 stream: String,
123 reader: impl Source,
124 closed: impl Fn() -> bool + Send + Sync + 'static,
125 ) -> Self {
126 let (out_tx, out_rx) = mpsc::channel(CHANNEL_CAPACITY);
127 let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
128 let epoch = Arc::new(AtomicU64::new(0));
129 runtime.spawn(drive(
130 reader,
131 out_tx,
132 cmd_rx,
133 Arc::clone(&epoch),
134 stream.clone(),
135 closed,
136 ));
137 let stream: Arc<str> = Arc::from(stream);
138 Self {
139 stream: Arc::clone(&stream),
140 inner: BufferedSubscriber::new(Deliveries::Driver(Driven {
141 stream,
142 rx: out_rx,
143 cmd: cmd_tx,
144 epoch,
145 }))
146 .max_wait(BATCH_MAX_WAIT),
147 }
148 }
149
150 #[cfg(feature = "testing")]
153 pub(crate) fn in_process(queue: FileQueue) -> Self {
154 Self {
155 stream: Arc::clone(queue.stream()),
156 inner: BufferedSubscriber::new(Deliveries::InProcess(queue)).max_wait(BATCH_MAX_WAIT),
157 }
158 }
159}
160
161impl Subscriber for FileSubscriber {
162 type Message = FileMessage;
163 type Error = SeaFileError;
164
165 fn stream(&mut self) -> impl Stream<Item = Result<FileMessage, SeaFileError>> + Send + '_ {
166 self.inner.stream()
167 }
168}
169
170impl BatchSubscriber for FileSubscriber {
171 type Batch = Vec<FileMessage>;
172
173 fn batches(
174 &mut self,
175 size: NonZeroUsize,
176 ) -> impl Stream<Item = Result<Self::Batch, SeaFileError>> + Send + '_ {
177 self.inner.batches(size)
178 }
179}
180
181impl Seekable for FileSubscriber {
182 type Seeker = FileSeeker;
183
184 fn seeker(&self) -> FileSeeker {
185 self.inner.seeker()
188 }
189}
190
191enum Deliveries {
197 Driver(Driven),
199 #[cfg(feature = "testing")]
201 InProcess(FileQueue),
202}
203
204#[cfg(not(feature = "testing"))]
207const _: () = assert!(size_of::<Deliveries>() == size_of::<Driven>());
208
209impl Subscriber for Deliveries {
210 type Message = FileMessage;
211 type Error = SeaFileError;
212
213 #[cfg(not(feature = "testing"))]
214 fn stream(&mut self) -> impl Stream<Item = Result<FileMessage, SeaFileError>> + Send + '_ {
215 let Self::Driver(driven) = self;
216 driven.stream()
217 }
218
219 #[cfg(feature = "testing")]
220 fn stream(&mut self) -> impl Stream<Item = Result<FileMessage, SeaFileError>> + Send + '_ {
221 match self {
222 Self::Driver(driven) => Either::Left(driven.stream()),
223 Self::InProcess(queue) => Either::Right(in_process_stream(queue)),
224 }
225 }
226}
227
228impl Seekable for Deliveries {
229 type Seeker = FileSeeker;
230
231 fn seeker(&self) -> FileSeeker {
232 match self {
233 Self::Driver(driven) => driven.seeker(),
234 #[cfg(feature = "testing")]
235 Self::InProcess(queue) => {
236 FileSeeker::in_process(Arc::clone(queue.stream()), queue.seeker())
237 }
238 }
239 }
240}
241
242#[cfg(feature = "testing")]
244fn in_process_stream(
245 queue: &mut FileQueue,
246) -> impl Stream<Item = Result<FileMessage, SeaFileError>> + Send + '_ {
247 let seeker = Arc::new(FileSeeker::in_process(
248 Arc::clone(queue.stream()),
249 queue.seeker(),
250 ));
251 futures::stream::poll_fn(move |cx| {
252 queue.poll_next(cx).map(|next| {
253 next.map(|message| {
254 Ok(FileMessage {
255 message,
256 seeker: Arc::clone(&seeker),
257 })
258 })
259 })
260 })
261}
262
263struct Driven {
265 stream: Arc<str>,
266 rx: mpsc::Receiver<Stamped>,
267 cmd: mpsc::UnboundedSender<SeekCmd>,
268 epoch: Arc<AtomicU64>,
269}
270
271impl Driven {
272 fn stream(&mut self) -> impl Stream<Item = Result<FileMessage, SeaFileError>> + Send + '_ {
273 let seeker = Arc::new(self.seeker());
277 futures::stream::poll_fn(move |cx| {
282 loop {
283 match self.rx.poll_recv(cx) {
284 std::task::Poll::Ready(Some(stamped)) => {
285 if stamped.epoch == self.epoch.load(Ordering::Acquire) {
286 return std::task::Poll::Ready(stamped.item.map(|item| {
287 item.map(|message| FileMessage {
288 message,
289 seeker: Arc::clone(&seeker),
290 })
291 }));
292 }
293 }
294 std::task::Poll::Ready(None) => return std::task::Poll::Ready(None),
295 std::task::Poll::Pending => return std::task::Poll::Pending,
296 }
297 }
298 })
299 }
300
301 fn seeker(&self) -> FileSeeker {
302 FileSeeker {
303 stream: Arc::clone(&self.stream),
304 backend: SeekBackend::Driver {
305 cmd: self.cmd.clone(),
306 },
307 }
308 }
309}
310
311pub struct FileMessage {
319 message: SeaMessage,
320 seeker: Arc<FileSeeker>,
321}
322
323impl std::fmt::Debug for FileMessage {
324 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325 f.debug_struct("FileMessage")
326 .field("message", &self.message)
327 .finish_non_exhaustive()
328 }
329}
330
331impl FileMessage {
332 #[must_use]
334 pub fn stream(&self) -> &str {
335 self.message.stream()
336 }
337
338 pub(crate) fn seeker(&self) -> &FileSeeker {
340 &self.seeker
341 }
342}
343
344impl Positioned for FileMessage {
345 type Position = FilePosition;
346
347 fn position(&self) -> FilePosition {
348 self.message.position()
349 }
350}
351
352impl IncomingMessage for FileMessage {
353 fn payload(&self) -> &[u8] {
354 self.message.payload()
355 }
356
357 fn headers(&self) -> &HeaderMap {
358 self.message.headers()
359 }
360
361 fn ack(self) -> impl Future<Output = Result<(), AckError>> {
362 self.message.ack()
363 }
364
365 fn nack(self, requeue: bool) -> impl Future<Output = Result<(), AckError>> {
366 self.message.nack(requeue)
367 }
368}
369
370#[derive(Clone)]
378enum SeekBackend {
379 Driver { cmd: mpsc::UnboundedSender<SeekCmd> },
381 #[cfg(feature = "testing")]
383 Log(LogSeeker),
384}
385
386#[derive(Clone)]
392pub struct FileSeeker {
393 stream: Arc<str>,
396 backend: SeekBackend,
397}
398
399impl FileSeeker {
400 #[cfg(feature = "testing")]
402 pub(crate) fn in_process(stream: Arc<str>, log: LogSeeker) -> Self {
403 Self {
404 stream,
405 backend: SeekBackend::Log(log),
406 }
407 }
408
409 #[must_use]
411 pub fn stream_key(&self) -> &str {
412 &self.stream
413 }
414
415 fn dead(&self, why: &'static str) -> SeaFileError {
416 SeaFileError::Seek {
417 stream: self.stream.to_string(),
418 source: Box::from(why),
419 }
420 }
421}
422
423impl std::fmt::Debug for FileSeeker {
424 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
425 f.debug_struct("FileSeeker")
426 .field("stream", &self.stream)
427 .finish_non_exhaustive()
428 }
429}
430
431impl Seeker for FileSeeker {
432 type Position = FilePosition;
433 type Error = SeaFileError;
434
435 async fn seek(&self, to: FilePosition) -> Result<(), SeaFileError> {
436 match &self.backend {
437 SeekBackend::Driver { cmd } => {
438 let (done, wait) = oneshot::channel();
439 cmd.send(SeekCmd { position: to, done })
440 .map_err(|_| self.dead("the subscription's driver task has shut down"))?;
441 wait.await
442 .map_err(|_| self.dead("the subscription's driver task has shut down"))?
443 }
444 #[cfg(feature = "testing")]
448 SeekBackend::Log(log) => log.request(to),
449 }
450 }
451}
452
453const SHARD: ShardId = ShardId::new(0);
455
456pub(crate) enum Reader {
458 Tail(FileConsumer),
460 Replay {
467 source: Box<MessageSource>,
468 key: StreamKey,
469 },
470}
471
472pub(crate) trait Source: Send + 'static {
475 fn next(&mut self) -> impl Future<Output = Result<SharedMessage, StreamErr<FileErr>>> + Send;
477
478 fn reposition(
480 &mut self,
481 target: SeekTarget,
482 ) -> impl Future<Output = Result<(), FileErr>> + Send;
483
484 fn repositions_after_end(&self) -> bool;
486}
487
488impl Source for Reader {
489 async fn next(&mut self) -> Result<SharedMessage, StreamErr<FileErr>> {
490 match self {
491 Self::Tail(consumer) => consumer.next().await,
492 Self::Replay { source, key } => loop {
493 let message = source.next().await.map_err(StreamErr::Backend)?.message;
494 if is_end_of_stream(&message) {
495 return Err(StreamErr::Backend(FileErr::StreamEnded));
496 }
497 if message.header().stream_key() == key {
499 return Ok(message.to_shared());
500 }
501 },
502 }
503 }
504
505 async fn reposition(&mut self, target: SeekTarget) -> Result<(), FileErr> {
506 match self {
507 Self::Tail(consumer) => consumer.seek_to(target).await,
508 Self::Replay { source, key } => source.seek(key, &SHARD, target).await,
509 }
510 }
511
512 fn repositions_after_end(&self) -> bool {
513 matches!(self, Self::Replay { .. })
516 }
517}
518
519fn is_clean_end(err: &StreamErr<FileErr>) -> bool {
522 matches!(
523 err,
524 StreamErr::Backend(FileErr::StreamEnded | FileErr::NotEnoughBytes)
525 )
526}
527
528enum After {
530 Read,
532 Idle,
534 Stop,
536}
537
538fn delivery(
540 next: Result<SharedMessage, StreamErr<FileErr>>,
541 reader: &impl Source,
542 stream: &str,
543) -> (Option<Result<SeaMessage, SeaFileError>>, After) {
544 match next {
545 Ok(message) => (Some(Ok(SeaMessage::new(&message))), After::Read),
546 Err(err) if is_clean_end(&err) => {
550 let after = if reader.repositions_after_end() {
551 After::Idle
552 } else {
553 After::Stop
554 };
555 (None, after)
556 }
557 Err(err) => (
558 Some(Err(SeaFileError::Receive {
559 stream: stream.to_owned(),
560 source: box_err(err),
561 })),
562 After::Stop,
563 ),
564 }
565}
566
567async fn drive(
568 mut reader: impl Source,
569 out: mpsc::Sender<Stamped>,
570 mut cmd_rx: mpsc::UnboundedReceiver<SeekCmd>,
571 epoch: Arc<AtomicU64>,
572 stream: String,
573 closed: impl Fn() -> bool + Send + Sync,
574) {
575 let mut generation = 0;
578 let mut idle = false;
581 'read: loop {
582 let next = tokio::select! {
583 biased;
584 cmd = cmd_rx.recv() => {
585 let Some(cmd) = cmd else { break };
586 if serve(&mut reader, cmd, &mut generation, &epoch, &stream, &closed).await {
587 idle = false;
588 }
589 continue;
590 }
591 () = out.closed() => break,
592 next = reader.next(), if !idle => next,
593 };
594 let (item, after) = delivery(next, &reader, &stream);
595 let permit = match out.try_reserve() {
596 Ok(permit) => permit,
597 Err(TrySendError::Closed(())) => break,
598 Err(TrySendError::Full(())) => loop {
601 tokio::select! {
602 biased;
603 cmd = cmd_rx.recv() => {
604 let Some(cmd) = cmd else { break 'read };
605 if serve(&mut reader, cmd, &mut generation, &epoch, &stream, &closed).await {
606 idle = false;
608 continue 'read;
609 }
610 }
611 permit = out.reserve() => match permit {
612 Ok(permit) => break permit,
613 Err(_) => break 'read,
614 },
615 }
616 },
617 };
618 permit.send(Stamped {
619 epoch: generation,
620 item,
621 });
622 match after {
623 After::Read => {}
624 After::Idle => idle = true,
625 After::Stop => break,
626 }
627 }
628}
629
630async fn serve(
637 reader: &mut impl Source,
638 SeekCmd { position, done }: SeekCmd,
639 generation: &mut u64,
640 epoch: &AtomicU64,
641 stream: &str,
642 closed: impl Fn() -> bool + Send + Sync,
643) -> bool {
644 if closed() {
645 let _ = done.send(Err(SeaFileError::Seek {
646 stream: stream.to_owned(),
647 source: Box::from("the broker has shut down"),
648 }));
649 return false;
650 }
651 let mut skip_one = false;
653 let target = match position {
654 FilePosition::Beginning | FilePosition::Sequence(0 | 1) => SeekTarget::Beginning,
656 FilePosition::End => SeekTarget::End,
657 FilePosition::Sequence(sequence) => {
662 skip_one = true;
663 SeekTarget::SeqNo(sequence - 1)
664 }
665 FilePosition::Timestamp(millis) => {
666 let nanos = i128::from(millis) * 1_000_000;
667 match Timestamp::from_unix_timestamp_nanos(nanos) {
668 Ok(timestamp) => SeekTarget::Timestamp(timestamp),
669 Err(err) => {
670 let _ = done.send(Err(SeaFileError::Invalid(format!(
671 "'{millis}' is not a valid timestamp: {err}"
672 ))));
673 return false;
674 }
675 }
676 }
677 };
678 let mut result = reader.reposition(target).await.map_err(box_err);
681 if result.is_ok() && skip_one {
682 result = reader.next().await.map(drop).map_err(box_err);
683 }
684 let moved = result.is_ok();
685 if moved {
686 *generation += 1;
689 epoch.store(*generation, Ordering::Release);
690 }
691 let _ = done.send(result.map_err(|source| SeaFileError::Seek {
692 stream: stream.to_owned(),
693 source,
694 }));
695 moved
696}
697
698#[cfg(test)]
699mod tests {
700 use std::future::ready;
701 use std::time::Duration;
702
703 use futures::StreamExt;
704 use sea_streamer_file::SeekErr;
705 use sea_streamer_types::{MessageHeader, SeqNo};
706
707 use super::*;
708 use crate::wire;
709
710 const STUCK: Duration = Duration::from_mins(1);
713
714 struct Log {
718 messages: Vec<SharedMessage>,
719 next: usize,
720 }
721
722 impl Log {
723 fn of(count: u8) -> Self {
724 let key = StreamKey::new("orders").expect("the key is valid");
725 let messages = (1..=count)
726 .map(|body| {
727 let bytes = wire::encode(&HeaderMap::new(), &[body], false);
728 let length = bytes.len();
729 SharedMessage::new(
730 MessageHeader::new(
731 key.clone(),
732 SHARD,
733 SeqNo::from(body),
734 Timestamp::now_utc(),
735 ),
736 bytes,
737 0,
738 length,
739 )
740 })
741 .collect();
742 Self { messages, next: 0 }
743 }
744 }
745
746 impl Source for Log {
747 fn next(&mut self) -> impl Future<Output = Result<SharedMessage, StreamErr<FileErr>>> {
748 let message = self.messages.get(self.next).cloned();
749 if message.is_some() {
750 self.next += 1;
751 }
752 ready(message.ok_or(StreamErr::Backend(FileErr::NotEnoughBytes)))
753 }
754
755 fn reposition(&mut self, target: SeekTarget) -> impl Future<Output = Result<(), FileErr>> {
756 let next = match target {
757 SeekTarget::Beginning => 0,
758 SeekTarget::End => self.messages.len(),
759 SeekTarget::SeqNo(sequence) if sequence > self.messages.len() as u64 => {
761 return ready(Err(FileErr::SeekErr(SeekErr::OutOfBound)));
762 }
763 SeekTarget::SeqNo(sequence) => {
764 usize::try_from(sequence.saturating_sub(1)).expect("the sequence fits")
765 }
766 SeekTarget::Timestamp(_) => unreachable!("these tests seek by sequence"),
767 };
768 self.next = next;
769 ready(Ok(()))
770 }
771
772 fn repositions_after_end(&self) -> bool {
773 true
774 }
775 }
776
777 async fn next_body<S>(stream: &mut S) -> Option<u8>
779 where
780 S: Stream<Item = Result<FileMessage, SeaFileError>> + Unpin,
781 {
782 tokio::time::timeout(STUCK, stream.next())
783 .await
784 .expect("the subscription moves on")
785 .map(|next| next.expect("the delivery is ok").payload()[0])
786 }
787
788 #[tokio::test(start_paused = true)]
791 async fn a_seek_completes_while_the_delivery_channel_is_full() {
792 let backlog = u8::try_from(CHANNEL_CAPACITY * 2).expect("the backlog fits");
793 let mut subscriber = FileSubscriber::spawn(
794 &Handle::current(),
795 "orders".to_owned(),
796 Log::of(backlog),
797 || false,
798 );
799 let seeker = subscriber.seeker();
800 let mut stream = std::pin::pin!(subscriber.stream());
801 assert_eq!(next_body(&mut stream).await, Some(1));
802 tokio::task::yield_now().await;
804
805 tokio::time::timeout(STUCK, seeker.seek(FilePosition::sequence(100)))
806 .await
807 .expect("a seek completes while the delivery channel is full")
808 .expect("the seek succeeds");
809
810 for expected in 100..=backlog {
811 assert_eq!(next_body(&mut stream).await, Some(expected));
812 }
813 assert_eq!(next_body(&mut stream).await, None, "the replay ends");
814 }
815
816 #[tokio::test(start_paused = true)]
820 async fn a_seek_after_the_replay_reached_the_end_replays_again() {
821 let mut subscriber =
822 FileSubscriber::spawn(&Handle::current(), "orders".to_owned(), Log::of(3), || {
823 false
824 });
825 let seeker = subscriber.seeker();
826 let mut stream = std::pin::pin!(subscriber.stream());
827 for expected in 1..=3 {
828 assert_eq!(next_body(&mut stream).await, Some(expected));
829 }
830 tokio::task::yield_now().await;
832
833 tokio::time::timeout(STUCK, seeker.seek(FilePosition::beginning()))
834 .await
835 .expect("the seek completes")
836 .expect("a seek after the end of the file succeeds");
837
838 for expected in 1..=3 {
839 assert_eq!(next_body(&mut stream).await, Some(expected));
840 }
841 assert_eq!(next_body(&mut stream).await, None, "the replay ends again");
842 }
843
844 #[tokio::test(start_paused = true)]
847 async fn a_seek_after_the_stream_ended_reopens_it() {
848 let mut subscriber =
849 FileSubscriber::spawn(&Handle::current(), "orders".to_owned(), Log::of(2), || {
850 false
851 });
852 let seeker = subscriber.seeker();
853 let mut stream = std::pin::pin!(subscriber.stream());
854 assert_eq!(next_body(&mut stream).await, Some(1));
855 assert_eq!(next_body(&mut stream).await, Some(2));
856 assert_eq!(next_body(&mut stream).await, None);
857
858 tokio::time::timeout(STUCK, seeker.seek(FilePosition::sequence(2)))
859 .await
860 .expect("the seek completes")
861 .expect("a seek after the stream ended succeeds");
862
863 assert_eq!(next_body(&mut stream).await, Some(2));
864 assert_eq!(next_body(&mut stream).await, None);
865 }
866
867 #[tokio::test(start_paused = true)]
870 async fn a_seek_to_the_sequence_after_the_last_lands_at_the_tip() {
871 let mut subscriber =
872 FileSubscriber::spawn(&Handle::current(), "orders".to_owned(), Log::of(3), || {
873 false
874 });
875 let seeker = subscriber.seeker();
876 let mut stream = std::pin::pin!(subscriber.stream());
877 assert_eq!(next_body(&mut stream).await, Some(1));
878
879 tokio::time::timeout(STUCK, seeker.seek(FilePosition::sequence(4)))
880 .await
881 .expect("the seek completes")
882 .expect("the sequence the file writes next is a position");
883
884 assert_eq!(next_body(&mut stream).await, None);
885 }
886
887 #[tokio::test(start_paused = true)]
890 async fn a_seek_to_the_first_sequence_replays_from_the_start() {
891 let mut subscriber =
892 FileSubscriber::spawn(&Handle::current(), "orders".to_owned(), Log::of(3), || {
893 false
894 });
895 let seeker = subscriber.seeker();
896 let mut stream = std::pin::pin!(subscriber.stream());
897 assert_eq!(next_body(&mut stream).await, Some(1));
898 assert_eq!(next_body(&mut stream).await, Some(2));
899
900 tokio::time::timeout(STUCK, seeker.seek(FilePosition::sequence(1)))
901 .await
902 .expect("the seek completes")
903 .expect("the first sequence is a position");
904
905 for expected in 1..=3 {
906 assert_eq!(next_body(&mut stream).await, Some(expected));
907 }
908 assert_eq!(next_body(&mut stream).await, None);
909 }
910
911 #[tokio::test(start_paused = true)]
914 async fn a_refused_seek_keeps_what_was_queued() {
915 let mut subscriber =
916 FileSubscriber::spawn(&Handle::current(), "orders".to_owned(), Log::of(5), || {
917 false
918 });
919 let seeker = subscriber.seeker();
920 let mut stream = std::pin::pin!(subscriber.stream());
921 assert_eq!(next_body(&mut stream).await, Some(1));
922 tokio::task::yield_now().await;
924
925 tokio::time::timeout(STUCK, seeker.seek(FilePosition::sequence(9)))
926 .await
927 .expect("the seek completes")
928 .expect_err("no message carries that sequence");
929
930 for expected in 2..=5 {
931 assert_eq!(next_body(&mut stream).await, Some(expected));
932 }
933 assert_eq!(next_body(&mut stream).await, None);
934 }
935}