1use std::{
20 collections::{HashMap, VecDeque},
21 fmt, hint,
22 marker::PhantomData,
23 sync::{
24 Arc, Condvar, Mutex, MutexGuard,
25 atomic::{AtomicBool, AtomicU8, AtomicU64, AtomicUsize, Ordering, fence},
26 mpsc,
27 },
28 thread,
29 time::Duration,
30};
31
32use arc_swap::ArcSwap;
33use crossbeam_queue::ArrayQueue;
34use ractor::{Actor, ActorProcessingErr, ActorRef};
35use tokio::sync::Notify;
36
37use crate::{
38 StreamError, StreamResult,
39 actor::block_on_ractor_runtime,
40 stream::{BoxStream, NotUsed, Source, current_stream_cancelled},
41};
42
43const TOPIC_OPEN: u8 = 0;
44const TOPIC_CLOSING: u8 = 1;
45const TOPIC_CLOSED: u8 = 2;
46const SLOT_OPEN: u8 = 0;
47const SLOT_COMPLETE: u8 = 1;
48const SLOT_ERROR: u8 = 2;
49const SLOT_WAIT_BACKSTOP: Duration = Duration::from_millis(10);
50const TOPIC_DRAIN_BATCH: usize = 256;
51
52type Ack = mpsc::Sender<StreamResult<()>>;
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum TopicOverflow {
57 Backpressure,
65 Sliding,
72 Dropping,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum TopicPublishError<T> {
84 Closed(T),
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub enum TopicTryPublishError<T> {
91 Closed(T),
93 Full(T),
96 Busy(T),
99}
100
101pub struct Topic<T: Send + Sync + 'static> {
111 inner: Arc<TopicInner<T>>,
112}
113
114struct TopicInner<T: Send + Sync + 'static> {
115 actor: ActorRef<TopicMessage<T>>,
116 shared: Arc<TopicShared<T>>,
117 next_subscriber_id: Arc<AtomicU64>,
118}
119
120struct TopicShared<T: Send + Sync + 'static> {
121 subscribers: Arc<ArcSwap<TopicSlotTable<T>>>,
122 capacity: usize,
123 overflow: TopicOverflow,
124 lifecycle: AtomicU8,
125 active_publishers: AtomicUsize,
126 next_sequence: AtomicU64,
127 delivered_sequence: AtomicU64,
128 space_waiters: AtomicUsize,
129 space_available: Notify,
130 closed_notified: Notify,
131}
132
133struct TopicSlotTable<T: Send + Sync + 'static> {
134 slots: Vec<Arc<TopicSlot<T>>>,
135}
136
137struct TopicSlot<T: Send + Sync + 'static> {
138 id: u64,
139 actor: ActorRef<TopicMessage<T>>,
140 buffer: ArrayQueue<Arc<T>>,
141 active: AtomicBool,
142 parked: AtomicBool,
143 terminal_state: AtomicU8,
144 terminal: Mutex<Option<TopicSlotTerminal>>,
145 available_lock: Mutex<()>,
146 available: Condvar,
147 async_available: Notify,
148}
149
150#[derive(Clone)]
151enum TopicSlotTerminal {
152 Complete,
153 Error(StreamError),
154}
155
156impl<T: Send + Sync + 'static> Clone for Topic<T> {
157 fn clone(&self) -> Self {
158 Self {
159 inner: Arc::clone(&self.inner),
160 }
161 }
162}
163
164impl<T: Send + Sync + 'static> Topic<T> {
165 pub fn new(capacity: usize, overflow: TopicOverflow) -> StreamResult<Self> {
169 assert!(capacity > 0, "topic capacity must be greater than zero");
170 let shared = Arc::new(TopicShared {
171 subscribers: Arc::new(ArcSwap::from_pointee(TopicSlotTable { slots: Vec::new() })),
172 capacity,
173 overflow,
174 lifecycle: AtomicU8::new(TOPIC_OPEN),
175 active_publishers: AtomicUsize::new(0),
176 next_sequence: AtomicU64::new(0),
177 delivered_sequence: AtomicU64::new(0),
178 space_waiters: AtomicUsize::new(0),
179 space_available: Notify::new(),
180 closed_notified: Notify::new(),
181 });
182 let state = TopicActorState {
183 shared: Arc::clone(&shared),
184 subscribers: HashMap::new(),
185 closed: false,
186 };
187 let (actor, _handle) =
188 block_on_ractor_runtime(Actor::spawn(None, TopicActor::<T>::default(), state))?
189 .map_err(|error| {
190 StreamError::Failed(format!("topic actor failed to spawn: {error}"))
191 })?;
192 Ok(Self {
193 inner: Arc::new(TopicInner {
194 actor,
195 shared,
196 next_subscriber_id: Arc::new(AtomicU64::new(1)),
197 }),
198 })
199 }
200
201 pub async fn publish(&self, value: T) -> Result<(), TopicPublishError<T>> {
209 let Ok(_permit) = self.inner.shared.begin_publish() else {
210 return Err(TopicPublishError::Closed(value));
211 };
212 let sequence = self.inner.shared.claim_sequence();
213 self.inner.shared.wait_publish_turn(sequence);
214
215 let table = self.inner.shared.subscribers.load_full();
216 if self.inner.shared.overflow == TopicOverflow::Backpressure {
217 self.inner.shared.wait_for_capacity(&table).await;
218 }
219
220 self.inner
221 .shared
222 .publish_to_snapshot(&table, Arc::new(value));
223 self.inner.shared.finish_publish(sequence);
224 Ok(())
225 }
226
227 pub fn try_publish(&self, value: T) -> Result<(), TopicTryPublishError<T>> {
236 let Ok(_permit) = self.inner.shared.begin_publish() else {
237 return Err(TopicTryPublishError::Closed(value));
238 };
239 let Some(sequence) = self.inner.shared.try_claim_sequence() else {
240 return Err(TopicTryPublishError::Busy(value));
241 };
242
243 let table = self.inner.shared.subscribers.load_full();
244 if self.inner.shared.overflow == TopicOverflow::Backpressure
245 && !self.inner.shared.snapshot_has_capacity(&table)
246 {
247 self.inner.shared.finish_publish(sequence);
248 return Err(TopicTryPublishError::Full(value));
249 }
250
251 self.inner
252 .shared
253 .publish_to_snapshot(&table, Arc::new(value));
254 self.inner.shared.finish_publish(sequence);
255 Ok(())
256 }
257
258 #[must_use]
264 pub fn subscribe(&self) -> Source<T>
265 where
266 T: Clone,
267 {
268 let topic = self.clone();
269 let actor = self.inner.actor.clone();
270 let next_subscriber_id = Arc::clone(&self.inner.next_subscriber_id);
271 Source::from_materialized_factory(move |_materializer| {
272 let id = next_subscriber_id.fetch_add(1, Ordering::Relaxed);
273 let capacity = topic.registered_capacity();
274 let slot = TopicSlot::new(id, actor.clone(), capacity);
275 topic.register_slot(Arc::clone(&slot), id)?;
276 let stream: BoxStream<T> = Box::new(TopicStream {
277 shared: Arc::clone(&topic.inner.shared),
278 slot,
279 pending: VecDeque::new(),
280 terminated: false,
281 });
282 Ok((stream, NotUsed))
283 })
284 }
285
286 #[must_use]
288 pub fn subscriber_count(&self) -> usize {
289 self.inner.shared.subscriber_count()
290 }
291
292 pub fn close(&self) -> StreamResult<()> {
298 let (reply, receiver) = mpsc::channel();
299 self.inner
300 .actor
301 .send_message(TopicMessage::Close { reply })
302 .map_err(|error| StreamError::ActorAskSendFailed {
303 reason: error.to_string(),
304 })?;
305 receiver.recv().unwrap_or(Err(StreamError::ActorTerminated))
306 }
307
308 #[must_use]
310 pub fn is_closed(&self) -> bool {
311 self.inner.shared.is_closed()
312 }
313
314 pub async fn closed(&self) {
316 loop {
317 if self.is_closed() {
318 return;
319 }
320 let notified = self.inner.shared.closed_notified.notified();
321 let mut notified = std::pin::pin!(notified);
322 notified.as_mut().enable();
323 if self.is_closed() {
324 return;
325 }
326 notified.as_mut().await;
327 }
328 }
329
330 fn registered_capacity(&self) -> usize {
331 self.inner.shared.capacity
332 }
333
334 fn register_slot(&self, slot: Arc<TopicSlot<T>>, id: u64) -> StreamResult<()> {
335 let (reply, receiver) = mpsc::channel();
336 self.inner
337 .actor
338 .send_message(TopicMessage::Subscribe { id, slot, reply })
339 .map_err(|error| StreamError::ActorAskSendFailed {
340 reason: error.to_string(),
341 })?;
342 receiver.recv().unwrap_or(Err(StreamError::ActorTerminated))
343 }
344}
345
346impl<T: Clone + Send + Sync + 'static> Topic<T> {
347 #[doc(hidden)]
348 pub fn __benchmark_subscribe(&self) -> StreamResult<TopicBenchmarkStream<T>> {
349 let id = self
350 .inner
351 .next_subscriber_id
352 .fetch_add(1, Ordering::Relaxed);
353 let capacity = self.registered_capacity();
354 let slot = TopicSlot::new(id, self.inner.actor.clone(), capacity);
355 self.register_slot(Arc::clone(&slot), id)?;
356 Ok(TopicBenchmarkStream {
357 shared: Arc::clone(&self.inner.shared),
358 slot,
359 pending: VecDeque::new(),
360 terminated: false,
361 })
362 }
363}
364
365impl<T: Send + Sync + 'static> TopicShared<T> {
366 fn begin_publish(&self) -> StreamResult<PublishPermit<'_>> {
367 if self.lifecycle.load(Ordering::Acquire) != TOPIC_OPEN {
368 return Err(closed_error());
369 }
370 self.active_publishers.fetch_add(1, Ordering::AcqRel);
371 if self.lifecycle.load(Ordering::Acquire) == TOPIC_OPEN {
372 Ok(PublishPermit {
373 active_publishers: &self.active_publishers,
374 })
375 } else {
376 self.active_publishers.fetch_sub(1, Ordering::AcqRel);
377 Err(closed_error())
378 }
379 }
380
381 fn claim_sequence(&self) -> u64 {
382 self.next_sequence.fetch_add(1, Ordering::AcqRel) + 1
383 }
384
385 fn try_claim_sequence(&self) -> Option<u64> {
386 let delivered = self.delivered_sequence.load(Ordering::Acquire);
387 self.next_sequence
388 .compare_exchange(
389 delivered,
390 delivered + 1,
391 Ordering::AcqRel,
392 Ordering::Acquire,
393 )
394 .ok()
395 .map(|_| delivered + 1)
396 }
397
398 fn wait_publish_turn(&self, sequence: u64) {
399 let mut spins = 0_u32;
400 while self.delivered_sequence.load(Ordering::Acquire) + 1 != sequence {
401 spins = spins.wrapping_add(1);
402 if spins < 64 {
403 hint::spin_loop();
404 } else {
405 thread::yield_now();
406 }
407 }
408 }
409
410 fn finish_publish(&self, sequence: u64) {
411 self.delivered_sequence.store(sequence, Ordering::Release);
412 }
413
414 fn publish_to_snapshot(&self, table: &TopicSlotTable<T>, value: Arc<T>) {
415 match self.overflow {
416 TopicOverflow::Backpressure => {
417 for slot in &table.slots {
418 slot.enqueue_backpressured(Arc::clone(&value));
419 }
420 }
421 TopicOverflow::Sliding => {
422 for slot in &table.slots {
423 slot.enqueue_sliding(Arc::clone(&value));
424 }
425 }
426 TopicOverflow::Dropping => {
427 for slot in &table.slots {
428 slot.enqueue_dropping(Arc::clone(&value));
429 }
430 }
431 }
432 }
433
434 async fn wait_for_capacity(&self, table: &TopicSlotTable<T>) {
435 loop {
436 if self.snapshot_has_capacity(table) {
437 return;
438 }
439
440 let notified = self.space_available.notified();
441 let mut notified = std::pin::pin!(notified);
442 notified.as_mut().enable();
443 self.space_waiters.fetch_add(1, Ordering::AcqRel);
444 if self.snapshot_has_capacity(table) {
445 self.space_waiters.fetch_sub(1, Ordering::AcqRel);
446 return;
447 }
448 notified.as_mut().await;
449 self.space_waiters.fetch_sub(1, Ordering::AcqRel);
450 }
451 }
452
453 fn snapshot_has_capacity(&self, table: &TopicSlotTable<T>) -> bool {
454 table.slots.iter().all(|slot| slot.has_capacity())
455 }
456
457 fn notify_space(&self) {
458 if self.space_waiters.load(Ordering::Acquire) != 0 {
459 self.space_available.notify_waiters();
460 }
461 }
462
463 fn subscriber_count(&self) -> usize {
464 let table = self.subscribers.load();
465 table.slots.iter().filter(|slot| slot.is_active()).count()
466 }
467
468 fn is_closed(&self) -> bool {
469 self.lifecycle.load(Ordering::Acquire) == TOPIC_CLOSED
470 }
471
472 fn wait_for_publishers_to_drain(&self) {
473 while self.active_publishers.load(Ordering::Acquire) != 0 {
474 thread::yield_now();
475 }
476 }
477
478 fn mark_actor_terminated(&self) {
479 self.lifecycle.store(TOPIC_CLOSED, Ordering::Release);
480 self.closed_notified.notify_waiters();
481 self.notify_space();
482 }
483}
484
485struct PublishPermit<'a> {
486 active_publishers: &'a AtomicUsize,
487}
488
489impl Drop for PublishPermit<'_> {
490 fn drop(&mut self) {
491 self.active_publishers.fetch_sub(1, Ordering::AcqRel);
492 }
493}
494
495impl<T: Send + Sync + 'static> TopicSlot<T> {
496 fn new(id: u64, actor: ActorRef<TopicMessage<T>>, capacity: usize) -> Arc<Self> {
497 Arc::new(Self {
498 id,
499 actor,
500 buffer: ArrayQueue::new(capacity),
501 active: AtomicBool::new(true),
502 parked: AtomicBool::new(false),
503 terminal_state: AtomicU8::new(SLOT_OPEN),
504 terminal: Mutex::new(None),
505 available_lock: Mutex::new(()),
506 available: Condvar::new(),
507 async_available: Notify::new(),
508 })
509 }
510
511 fn terminal_lock(&self) -> MutexGuard<'_, Option<TopicSlotTerminal>> {
512 self.terminal
513 .lock()
514 .unwrap_or_else(|poison| poison.into_inner())
515 }
516
517 fn is_active(&self) -> bool {
518 self.active.load(Ordering::Acquire)
519 }
520
521 fn has_capacity(&self) -> bool {
522 !self.is_active() || !self.buffer.is_full()
523 }
524
525 fn enqueue_backpressured(&self, value: Arc<T>) {
526 if !self.is_active() {
527 return;
528 }
529 let was_empty = self.buffer.is_empty();
530 if self.buffer.push(value).is_ok() && was_empty {
531 self.wake();
532 }
533 }
534
535 fn enqueue_sliding(&self, value: Arc<T>) {
536 if !self.is_active() {
537 return;
538 }
539 while self.buffer.is_full() {
540 if self.buffer.pop().is_none() {
541 break;
542 }
543 }
544 let was_empty = self.buffer.is_empty();
545 if self.buffer.push(value).is_ok() && was_empty {
546 self.wake();
547 }
548 }
549
550 fn enqueue_dropping(&self, value: Arc<T>) {
551 if !self.is_active() || self.buffer.is_full() {
552 return;
553 }
554 let was_empty = self.buffer.is_empty();
555 if self.buffer.push(value).is_ok() && was_empty {
556 self.wake();
557 }
558 }
559
560 fn pop(&self) -> Option<Arc<T>> {
561 self.buffer.pop()
562 }
563
564 fn park(&self) {
565 self.parked.store(true, Ordering::Release);
566 }
567
568 fn unpark(&self) {
569 self.parked.store(false, Ordering::Release);
570 }
571
572 fn wake(&self) {
573 if self.parked.swap(false, Ordering::AcqRel) {
574 let _guard = self
575 .available_lock
576 .lock()
577 .unwrap_or_else(|poison| poison.into_inner());
578 self.available.notify_one();
579 self.async_available.notify_waiters();
580 }
581 }
582
583 fn complete(&self) {
584 if self
585 .terminal_state
586 .compare_exchange(
587 SLOT_OPEN,
588 SLOT_COMPLETE,
589 Ordering::AcqRel,
590 Ordering::Acquire,
591 )
592 .is_err()
593 {
594 return;
595 }
596 self.active.store(false, Ordering::Release);
597 *self.terminal_lock() = Some(TopicSlotTerminal::Complete);
598 self.wake();
599 }
600
601 fn fail(&self, error: StreamError) {
602 if self
603 .terminal_state
604 .compare_exchange(SLOT_OPEN, SLOT_ERROR, Ordering::AcqRel, Ordering::Acquire)
605 .is_err()
606 {
607 return;
608 }
609 self.active.store(false, Ordering::Release);
610 *self.terminal_lock() = Some(TopicSlotTerminal::Error(error));
611 self.wake();
612 }
613
614 fn terminal(&self) -> Option<TopicSlotTerminal> {
615 if self.terminal_state.load(Ordering::Acquire) == SLOT_OPEN {
616 return None;
617 }
618 self.terminal_lock().clone()
619 }
620
621 fn deactivate(&self) {
622 self.active.store(false, Ordering::Release);
623 while self.buffer.pop().is_some() {}
624 self.wake();
625 }
626
627 fn unsubscribe(&self) {
628 self.deactivate();
629 let _ = self
630 .actor
631 .send_message(TopicMessage::Unsubscribe { id: self.id });
632 }
633}
634
635impl<T: Send + Sync + 'static> Drop for TopicInner<T> {
636 fn drop(&mut self) {
637 self.actor.stop(None);
638 }
639}
640
641enum TopicMessage<T: Send + Sync + 'static> {
642 Close {
643 reply: Ack,
644 },
645 Subscribe {
646 id: u64,
647 slot: Arc<TopicSlot<T>>,
648 reply: Ack,
649 },
650 Unsubscribe {
651 id: u64,
652 },
653}
654
655struct TopicActor<T> {
656 _marker: PhantomData<fn() -> T>,
657}
658
659impl<T> Default for TopicActor<T> {
660 fn default() -> Self {
661 Self {
662 _marker: PhantomData,
663 }
664 }
665}
666
667struct TopicActorState<T: Send + Sync + 'static> {
668 shared: Arc<TopicShared<T>>,
669 subscribers: HashMap<u64, Arc<TopicSlot<T>>>,
670 closed: bool,
671}
672
673impl<T: Send + Sync + 'static> Actor for TopicActor<T> {
674 type Msg = TopicMessage<T>;
675 type State = TopicActorState<T>;
676 type Arguments = TopicActorState<T>;
677
678 async fn pre_start(
679 &self,
680 _myself: ActorRef<Self::Msg>,
681 args: Self::Arguments,
682 ) -> Result<Self::State, ActorProcessingErr> {
683 Ok(args)
684 }
685
686 async fn handle(
687 &self,
688 _myself: ActorRef<Self::Msg>,
689 message: Self::Msg,
690 state: &mut Self::State,
691 ) -> Result<(), ActorProcessingErr> {
692 match message {
693 TopicMessage::Close { reply } => {
694 close_topic(state);
695 let _ = reply.send(Ok(()));
696 }
697 TopicMessage::Subscribe { id, slot, reply } => {
698 if state.closed || state.shared.lifecycle.load(Ordering::Acquire) == TOPIC_CLOSED {
699 slot.complete();
700 } else {
701 state.subscribers.insert(id, Arc::clone(&slot));
702 publish_topic_slot_table(state);
703 }
704 let _ = reply.send(Ok(()));
705 }
706 TopicMessage::Unsubscribe { id } => {
707 state.subscribers.remove(&id);
708 publish_topic_slot_table(state);
709 state.shared.notify_space();
710 }
711 }
712 Ok(())
713 }
714
715 async fn post_stop(
716 &self,
717 _myself: ActorRef<Self::Msg>,
718 state: &mut Self::State,
719 ) -> Result<(), ActorProcessingErr> {
720 if !state.closed {
721 for slot in state.subscribers.values() {
722 slot.fail(StreamError::ActorTerminated);
723 }
724 state.subscribers.clear();
725 publish_topic_slot_table(state);
726 state.shared.mark_actor_terminated();
727 }
728 Ok(())
729 }
730}
731
732fn close_topic<T: Send + Sync + 'static>(state: &mut TopicActorState<T>) {
733 if state.closed {
734 return;
735 }
736 match state.shared.lifecycle.compare_exchange(
737 TOPIC_OPEN,
738 TOPIC_CLOSING,
739 Ordering::AcqRel,
740 Ordering::Acquire,
741 ) {
742 Ok(_) => {}
743 Err(TOPIC_CLOSED) => {
744 state.closed = true;
745 return;
746 }
747 Err(_) => {}
748 }
749 state.shared.wait_for_publishers_to_drain();
750 state
751 .shared
752 .lifecycle
753 .store(TOPIC_CLOSED, Ordering::Release);
754
755 for slot in state.subscribers.values() {
756 slot.complete();
757 }
758 state.subscribers.clear();
759 publish_topic_slot_table(state);
760 state.shared.notify_space();
761 state.shared.closed_notified.notify_waiters();
762 state.closed = true;
763}
764
765fn publish_topic_slot_table<T: Send + Sync + 'static>(state: &TopicActorState<T>) {
766 let slots = state.subscribers.values().cloned().collect::<Vec<_>>();
767 state
768 .shared
769 .subscribers
770 .store(Arc::new(TopicSlotTable { slots }));
771}
772
773fn closed_error() -> StreamError {
774 StreamError::Failed("topic is closed".into())
775}
776
777struct TopicStream<T: Clone + Send + Sync + 'static> {
778 shared: Arc<TopicShared<T>>,
779 slot: Arc<TopicSlot<T>>,
780 pending: VecDeque<Arc<T>>,
781 terminated: bool,
782}
783
784#[doc(hidden)]
785pub struct TopicBenchmarkStream<T: Clone + Send + Sync + 'static> {
786 shared: Arc<TopicShared<T>>,
787 slot: Arc<TopicSlot<T>>,
788 pending: VecDeque<Arc<T>>,
789 terminated: bool,
790}
791
792impl<T: Clone + Send + Sync + 'static> Iterator for TopicStream<T> {
793 type Item = StreamResult<T>;
794
795 fn next(&mut self) -> Option<Self::Item> {
796 if self.terminated {
797 return None;
798 }
799
800 loop {
801 if let Some(value) = self.pending.pop_front() {
802 return Some(Ok(value.as_ref().clone()));
803 }
804
805 if let Some(value) = self.drain_batch() {
806 return Some(Ok(value.as_ref().clone()));
807 }
808
809 if let Some(terminal) = self.slot.terminal() {
810 self.terminated = true;
811 return match terminal {
812 TopicSlotTerminal::Complete => None,
813 TopicSlotTerminal::Error(error) => Some(Err(error)),
814 };
815 }
816
817 if current_stream_cancelled()
818 .as_ref()
819 .is_some_and(|cancelled| cancelled.load(Ordering::SeqCst))
820 {
821 self.terminated = true;
822 return Some(Err(StreamError::Cancelled));
823 }
824
825 self.wait_for_wake();
826 }
827 }
828}
829
830impl<T: Clone + Send + Sync + 'static> TopicStream<T> {
831 fn drain_batch(&mut self) -> Option<Arc<T>> {
832 let first = self.slot.pop()?;
833 let mut drained = 1_usize;
834 while drained < TOPIC_DRAIN_BATCH {
835 let Some(value) = self.slot.pop() else {
836 break;
837 };
838 self.pending.push_back(value);
839 drained += 1;
840 }
841 self.shared.notify_space();
842 Some(first)
843 }
844
845 fn wait_for_wake(&self) {
846 let guard = self
847 .slot
848 .available_lock
849 .lock()
850 .unwrap_or_else(|poison| poison.into_inner());
851 self.slot.park();
852 fence(Ordering::SeqCst);
853 if !self.slot.buffer.is_empty() || self.slot.terminal().is_some() {
854 self.slot.unpark();
855 return;
856 }
857 let _guard = self
858 .slot
859 .available
860 .wait_timeout(guard, SLOT_WAIT_BACKSTOP)
861 .unwrap_or_else(|poison| poison.into_inner())
862 .0;
863 self.slot.unpark();
864 }
865}
866
867impl<T: Clone + Send + Sync + 'static> Drop for TopicStream<T> {
868 fn drop(&mut self) {
869 self.slot.unsubscribe();
870 self.shared.notify_space();
871 }
872}
873
874impl<T: Clone + Send + Sync + 'static> TopicBenchmarkStream<T> {
875 #[doc(hidden)]
876 pub async fn next(&mut self) -> Option<StreamResult<T>> {
877 if self.terminated {
878 return None;
879 }
880
881 loop {
882 if let Some(value) = self.pending.pop_front() {
883 return Some(Ok(value.as_ref().clone()));
884 }
885
886 if let Some(value) = self.drain_batch() {
887 return Some(Ok(value.as_ref().clone()));
888 }
889
890 if let Some(terminal) = self.slot.terminal() {
891 self.terminated = true;
892 return match terminal {
893 TopicSlotTerminal::Complete => None,
894 TopicSlotTerminal::Error(error) => Some(Err(error)),
895 };
896 }
897
898 self.wait_for_wake().await;
899 }
900 }
901
902 #[doc(hidden)]
903 pub async fn count_items(&mut self, target: u64) -> StreamResult<u64> {
904 let mut count = 0_u64;
905 while count < target {
906 if self.terminated {
907 return Err(StreamError::Failed(
908 "topic stream ended before requested count".into(),
909 ));
910 }
911
912 if !self.pending.is_empty() {
913 let drained = self.pending.len().min((target - count) as usize);
914 self.pending.drain(..drained);
915 count += drained as u64;
916 continue;
917 }
918
919 if let Some(drained) = self.drain_available_count((target - count) as usize) {
920 count += drained as u64;
921 continue;
922 }
923
924 if let Some(terminal) = self.slot.terminal() {
925 self.terminated = true;
926 return match terminal {
927 TopicSlotTerminal::Complete => Err(StreamError::Failed(
928 "topic stream completed before requested count".into(),
929 )),
930 TopicSlotTerminal::Error(error) => Err(error),
931 };
932 }
933
934 self.wait_for_wake().await;
935 }
936 Ok(count)
937 }
938
939 fn drain_batch(&mut self) -> Option<Arc<T>> {
940 let first = self.slot.pop()?;
941 let mut drained = 1_usize;
942 while drained < TOPIC_DRAIN_BATCH {
943 let Some(value) = self.slot.pop() else {
944 break;
945 };
946 self.pending.push_back(value);
947 drained += 1;
948 }
949 self.shared.notify_space();
950 Some(first)
951 }
952
953 fn drain_available_count(&mut self, limit: usize) -> Option<usize> {
954 let mut drained = 0_usize;
955 let limit = limit.min(TOPIC_DRAIN_BATCH);
956 while drained < limit {
957 let Some(_value) = self.slot.pop() else {
958 break;
959 };
960 drained += 1;
961 }
962 if drained == 0 {
963 return None;
964 }
965 self.shared.notify_space();
966 Some(drained)
967 }
968
969 async fn wait_for_wake(&self) {
970 let notified = self.slot.async_available.notified();
971 tokio::pin!(notified);
972 notified.as_mut().enable();
973
974 self.slot.park();
975 fence(Ordering::SeqCst);
976 if !self.slot.buffer.is_empty() || self.slot.terminal().is_some() {
977 self.slot.unpark();
978 return;
979 }
980
981 notified.as_mut().await;
982 self.slot.unpark();
983 }
984}
985
986impl<T: Clone + Send + Sync + 'static> Drop for TopicBenchmarkStream<T> {
987 fn drop(&mut self) {
988 self.slot.unsubscribe();
989 self.shared.notify_space();
990 }
991}
992
993impl<T: Send + Sync + 'static> fmt::Debug for Topic<T> {
994 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
995 f.debug_struct("Topic")
996 .field("closed", &self.is_closed())
997 .field("subscribers", &self.subscriber_count())
998 .finish_non_exhaustive()
999 }
1000}
1001
1002impl<T> fmt::Display for TopicPublishError<T> {
1003 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1004 match self {
1005 TopicPublishError::Closed(_) => f.write_str("topic is closed"),
1006 }
1007 }
1008}
1009
1010impl<T> fmt::Display for TopicTryPublishError<T> {
1011 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1012 match self {
1013 TopicTryPublishError::Closed(_) => f.write_str("topic is closed"),
1014 TopicTryPublishError::Full(_) => f.write_str("topic subscriber buffer is full"),
1015 TopicTryPublishError::Busy(_) => f.write_str("topic publish turn is busy"),
1016 }
1017 }
1018}
1019
1020#[cfg(test)]
1021mod tests {
1022 use super::*;
1023 use crate::{Sink, stream::Materializer};
1024 use futures::executor::block_on;
1025 use std::{
1026 collections::HashSet,
1027 sync::{
1028 Arc,
1029 atomic::{AtomicBool, AtomicUsize},
1030 },
1031 thread,
1032 time::{Duration, Instant},
1033 };
1034
1035 fn wait<T>(completion: crate::StreamCompletion<T>) -> T {
1036 completion.wait().unwrap()
1037 }
1038
1039 fn wait_until<F>(timeout: Duration, mut condition: F) -> bool
1040 where
1041 F: FnMut() -> bool,
1042 {
1043 let deadline = Instant::now() + timeout;
1044 while Instant::now() < deadline {
1045 if condition() {
1046 return true;
1047 }
1048 thread::yield_now();
1049 thread::sleep(Duration::from_millis(1));
1050 }
1051 condition()
1052 }
1053
1054 fn materialize_topic<T: Clone + Send + Sync + 'static>(topic: &Topic<T>) -> BoxStream<T> {
1055 let materializer = Materializer::new();
1056 let (stream, _) = topic.subscribe().factory.create(&materializer).unwrap();
1057 stream
1058 }
1059
1060 #[test]
1061 fn every_subscriber_sees_every_post_subscription_element() {
1062 const SUBSCRIBERS: usize = 4;
1063 const PUBLISHERS: usize = 4;
1064 const PER_PUBLISHER: usize = 128;
1065 let topic = Topic::new(1_024, TopicOverflow::Backpressure).unwrap();
1066 let completions = (0..SUBSCRIBERS)
1067 .map(|_| topic.subscribe().run_with(Sink::collect()).unwrap())
1068 .collect::<Vec<_>>();
1069 assert!(wait_until(Duration::from_secs(1), || topic
1070 .subscriber_count()
1071 == SUBSCRIBERS));
1072
1073 let mut handles = Vec::new();
1074 for publisher in 0..PUBLISHERS {
1075 let topic = topic.clone();
1076 handles.push(thread::spawn(move || {
1077 for seq in 0..PER_PUBLISHER {
1078 let value = ((publisher as u64) << 32) | seq as u64;
1079 block_on(topic.publish(value)).unwrap();
1080 }
1081 }));
1082 }
1083 for handle in handles {
1084 handle.join().unwrap();
1085 }
1086 topic.close().unwrap();
1087
1088 let observed = completions.into_iter().map(wait).collect::<Vec<_>>();
1089 let first = observed.first().unwrap();
1090 assert_eq!(first.len(), PUBLISHERS * PER_PUBLISHER);
1091 let unique = first.iter().copied().collect::<HashSet<_>>();
1092 assert_eq!(unique.len(), PUBLISHERS * PER_PUBLISHER);
1093 for values in &observed[1..] {
1094 assert_eq!(values, first, "subscribers disagreed on global order");
1095 }
1096 }
1097
1098 #[test]
1099 fn late_subscriber_sees_nothing_prior_and_zero_subscriber_publish_drops() {
1100 let topic = Topic::new(8, TopicOverflow::Backpressure).unwrap();
1101 assert_eq!(topic.subscriber_count(), 0);
1102 block_on(topic.publish(1_u64)).unwrap();
1103 topic.try_publish(2).unwrap();
1104
1105 let completion = topic.subscribe().run_with(Sink::collect()).unwrap();
1106 assert!(wait_until(Duration::from_secs(1), || topic
1107 .subscriber_count()
1108 == 1));
1109 block_on(topic.publish(3)).unwrap();
1110 topic.close().unwrap();
1111 assert_eq!(wait(completion), vec![3]);
1112 }
1113
1114 #[test]
1115 fn sliding_overflow_drops_oldest_for_slow_subscriber() {
1116 let topic = Topic::new(2, TopicOverflow::Sliding).unwrap();
1117 let mut stream = materialize_topic(&topic);
1118 topic.try_publish(1_u64).unwrap();
1119 topic.try_publish(2).unwrap();
1120 topic.try_publish(3).unwrap();
1121 topic.try_publish(4).unwrap();
1122 topic.close().unwrap();
1123
1124 assert_eq!(stream.next(), Some(Ok(3)));
1125 assert_eq!(stream.next(), Some(Ok(4)));
1126 assert_eq!(stream.next(), None);
1127 }
1128
1129 #[test]
1130 fn dropping_overflow_drops_newest_for_slow_subscriber() {
1131 let topic = Topic::new(2, TopicOverflow::Dropping).unwrap();
1132 let mut stream = materialize_topic(&topic);
1133 topic.try_publish(1_u64).unwrap();
1134 topic.try_publish(2).unwrap();
1135 topic.try_publish(3).unwrap();
1136 topic.try_publish(4).unwrap();
1137 topic.close().unwrap();
1138
1139 assert_eq!(stream.next(), Some(Ok(1)));
1140 assert_eq!(stream.next(), Some(Ok(2)));
1141 assert_eq!(stream.next(), None);
1142 }
1143
1144 #[test]
1145 fn backpressure_stalls_publisher_until_slow_subscriber_drains() {
1146 let topic = Topic::new(1, TopicOverflow::Backpressure).unwrap();
1147 let mut stream = materialize_topic(&topic);
1148 block_on(topic.publish(1_u64)).unwrap();
1149
1150 let completed = Arc::new(AtomicBool::new(false));
1151 let publisher_completed = Arc::clone(&completed);
1152 let publisher = {
1153 let topic = topic.clone();
1154 thread::spawn(move || {
1155 block_on(topic.publish(2)).unwrap();
1156 publisher_completed.store(true, Ordering::SeqCst);
1157 })
1158 };
1159
1160 assert!(!wait_until(Duration::from_millis(25), || completed
1161 .load(Ordering::SeqCst)));
1162 assert_eq!(stream.next(), Some(Ok(1)));
1163 assert!(wait_until(Duration::from_secs(1), || completed.load(Ordering::SeqCst)));
1164 publisher.join().unwrap();
1165 topic.close().unwrap();
1166 assert_eq!(stream.next(), Some(Ok(2)));
1167 assert_eq!(stream.next(), None);
1168 }
1169
1170 #[test]
1171 fn dropping_source_unsubscribes_and_frees_backpressured_slot() {
1172 let topic = Topic::new(1, TopicOverflow::Backpressure).unwrap();
1173 let stream = materialize_topic(&topic);
1174 block_on(topic.publish(1_u64)).unwrap();
1175 assert_eq!(topic.subscriber_count(), 1);
1176 drop(stream);
1177 assert!(wait_until(Duration::from_secs(1), || topic
1178 .subscriber_count()
1179 == 0));
1180 block_on(topic.publish(2)).unwrap();
1181 topic.close().unwrap();
1182 }
1183
1184 #[test]
1185 fn close_drains_then_completes_and_rejects_late_publishes() {
1186 let topic = Topic::new(4, TopicOverflow::Backpressure).unwrap();
1187 let mut stream = materialize_topic(&topic);
1188 block_on(topic.publish(1_u64)).unwrap();
1189 block_on(topic.publish(2)).unwrap();
1190 topic.close().unwrap();
1191
1192 assert_eq!(stream.next(), Some(Ok(1)));
1193 assert_eq!(stream.next(), Some(Ok(2)));
1194 assert_eq!(stream.next(), None);
1195 assert_eq!(topic.try_publish(3), Err(TopicTryPublishError::Closed(3)));
1196 assert_eq!(
1197 block_on(topic.publish(4)),
1198 Err(TopicPublishError::Closed(4))
1199 );
1200 }
1201
1202 #[test]
1203 fn closed_future_wakes_on_close() {
1204 let topic = Topic::<u64>::new(1, TopicOverflow::Backpressure).unwrap();
1205 let waiting = Arc::new(AtomicBool::new(false));
1206 let waiter_started = Arc::clone(&waiting);
1207 let waiter = {
1208 let topic = topic.clone();
1209 thread::spawn(move || {
1210 waiter_started.store(true, Ordering::SeqCst);
1211 block_on(topic.closed());
1212 })
1213 };
1214
1215 assert!(wait_until(Duration::from_secs(1), || waiting.load(Ordering::SeqCst)));
1216 topic.close().unwrap();
1217 waiter.join().unwrap();
1218 assert!(topic.is_closed());
1219 }
1220
1221 #[test]
1222 fn publisher_subscriber_churn_hammer() {
1223 const ROUNDS: usize = 200;
1224 const PUBLISHERS: usize = 4;
1225 let topic = Topic::new(8, TopicOverflow::Sliding).unwrap();
1226 let published = Arc::new(AtomicUsize::new(0));
1227
1228 let mut publisher_handles = Vec::new();
1229 for publisher in 0..PUBLISHERS {
1230 let topic = topic.clone();
1231 let published = Arc::clone(&published);
1232 publisher_handles.push(thread::spawn(move || {
1233 for seq in 0..ROUNDS {
1234 let value = ((publisher as u64) << 32) | seq as u64;
1235 block_on(topic.publish(value)).unwrap();
1236 published.fetch_add(1, Ordering::Relaxed);
1237 }
1238 }));
1239 }
1240
1241 let churn_topic = topic.clone();
1242 let churn = thread::spawn(move || {
1243 for _ in 0..ROUNDS {
1244 let stream = materialize_topic(&churn_topic);
1245 drop(stream);
1246 }
1247 });
1248
1249 for handle in publisher_handles {
1250 handle.join().unwrap();
1251 }
1252 churn.join().unwrap();
1253 topic.close().unwrap();
1254 assert_eq!(published.load(Ordering::Relaxed), PUBLISHERS * ROUNDS);
1255 }
1256
1257 #[test]
1258 fn post_close_subscribe_completes_empty() {
1259 let topic = Topic::<u64>::new(8, TopicOverflow::Backpressure).unwrap();
1260 topic.close().unwrap();
1261 assert_eq!(topic.subscribe().run_collect().unwrap(), Vec::<u64>::new());
1262 }
1263}