1use crate::metrics::SubscriptionMetrics;
5use futures::{StreamExt, stream::FuturesUnordered};
6use std::sync::Arc;
7use std::sync::atomic::AtomicUsize;
8use std::sync::atomic::Ordering;
9use std::time::Duration;
10use sui_inverted_index::BitmapQuery;
11use sui_types::full_checkpoint_content::Checkpoint;
12use tokio::sync::broadcast;
13use tokio::sync::mpsc;
14use tokio::sync::oneshot;
15use tokio::time::Instant;
16use tokio::time::sleep;
17use tracing::info;
18use tracing::trace;
19use tracing::warn;
20
21mod matcher;
22
23const CHECKPOINT_MAILBOX_SIZE: usize = 1024;
24const ADMISSION_TURN_LIMIT: usize = 128;
27const SUBSCRIPTION_CHANNEL_SIZE: usize = 256;
28const DEFAULT_MAX_SUBSCRIBERS: usize = 1024;
29const SHARD_MAILBOX_SIZE: usize = 64;
32
33const DEFAULT_WATERMARK_INTERVAL: u32 = 25;
36
37fn default_shards() -> u32 {
40 std::thread::available_parallelism()
41 .map(|n| n.get() as u32)
42 .unwrap_or(1)
43}
44
45const INDEX_WAIT_POLL_INTERVAL: Duration = Duration::from_millis(10);
48const INDEX_WAIT_TIMEOUT: Duration = Duration::from_secs(10);
52
53pub type IndexedCheckpointFn = Arc<dyn Fn() -> Option<u64> + Send + Sync>;
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum SubscriptionKind {
64 Checkpoints,
65 Transactions,
66 Events,
67}
68
69impl SubscriptionKind {
70 pub(crate) fn metric_label(self) -> &'static str {
71 match self {
72 Self::Checkpoints => "checkpoint",
73 Self::Transactions => "transaction",
74 Self::Events => "event",
75 }
76 }
77}
78
79#[derive(Clone, Copy)]
80pub(crate) enum SubscriptionTerminationReason {
81 ClientClosed,
82 SlowConsumer,
83 SourceLag,
84 ServiceShutdown,
85}
86
87impl SubscriptionTerminationReason {
88 fn metric_label(self) -> &'static str {
89 match self {
90 Self::ClientClosed => "client_closed",
91 Self::SlowConsumer => "slow_consumer",
92 Self::SourceLag => "source_lag",
93 Self::ServiceShutdown => "service_shutdown",
94 }
95 }
96}
97
98pub(crate) struct SubscriptionLifecycleGuard {
99 kind: SubscriptionKind,
100 filtered: bool,
101 reservation: SubscriberReservation,
102 inflight_subscribers: prometheus::IntGauge,
103 terminations_total: prometheus::IntCounterVec,
104 termination_reason: SubscriptionTerminationReason,
105}
106
107impl SubscriptionLifecycleGuard {
108 pub(crate) fn new(
109 kind: SubscriptionKind,
110 filtered: bool,
111 reservation: SubscriberReservation,
112 metrics: &SubscriptionMetrics,
113 ) -> Self {
114 reservation.increment_resident_counts(kind, filtered);
115 let inflight_subscribers = metrics
116 .inflight_subscribers
117 .with_label_values(&[kind.metric_label(), if filtered { "true" } else { "false" }]);
118 inflight_subscribers.inc();
119
120 Self {
121 kind,
122 filtered,
123 reservation,
124 inflight_subscribers,
125 terminations_total: metrics.terminations_total.clone(),
126 termination_reason: SubscriptionTerminationReason::ServiceShutdown,
127 }
128 }
129
130 pub(crate) fn terminate(mut self, reason: SubscriptionTerminationReason) {
134 self.termination_reason = reason;
135 }
136}
137
138impl Drop for SubscriptionLifecycleGuard {
139 fn drop(&mut self) {
140 self.reservation
141 .decrement_resident_counts(self.kind, self.filtered);
142 self.inflight_subscribers.dec();
143 self.terminations_total
144 .with_label_values(&[
145 self.kind.metric_label(),
146 self.termination_reason.metric_label(),
147 ])
148 .inc();
149 }
150}
151
152pub struct SubscriptionSpec {
155 pub kind: SubscriptionKind,
156 pub query: Option<BitmapQuery>,
157}
158
159pub enum SubscriptionUpdate {
162 Matched(MatchedCheckpoint),
163 WatermarkTick {
169 checkpoint: u64,
170 tx_hi: u64,
171 },
172}
173
174pub struct MatchedCheckpoint {
175 pub checkpoint: Arc<Checkpoint>,
176 pub matches: SubscriptionMatches,
177}
178
179pub enum SubscriptionMatches {
181 Checkpoint,
184 Transactions(Vec<std::ops::Range<u32>>),
189 AllTransactions,
193 Events(Vec<(u32, Vec<u32>)>),
196 AllEvents,
200}
201
202impl SubscriptionMatches {
203 pub fn transaction_indices(
208 &self,
209 tx_count: u32,
210 ) -> Option<Box<dyn Iterator<Item = u32> + Send + '_>> {
211 match self {
212 Self::Transactions(ranges) => Some(Box::new(ranges.iter().flat_map(Clone::clone))),
213 Self::AllTransactions => Some(Box::new(0..tx_count)),
214 _ => None,
215 }
216 }
217
218 pub fn event_indices<'a>(
222 &'a self,
223 checkpoint: &'a Checkpoint,
224 ) -> Option<Box<dyn Iterator<Item = (u32, u32)> + Send + 'a>> {
225 match self {
226 Self::Events(txs) => Some(Box::new(
227 txs.iter()
228 .flat_map(|(tx, evs)| evs.iter().map(move |&ev| (*tx, ev))),
229 )),
230 Self::AllEvents => Some(Box::new(
231 checkpoint
232 .transactions
233 .iter()
234 .enumerate()
235 .flat_map(|(tx_idx, tx)| {
236 let event_count =
237 tx.events.as_ref().map(|e| e.data.len()).unwrap_or(0) as u32;
238 (0..event_count).map(move |ev| (tx_idx as u32, ev))
239 }),
240 )),
241 _ => None,
242 }
243 }
244}
245
246struct SubscriptionRequest {
247 spec: SubscriptionSpec,
248 response_sender: oneshot::Sender<mpsc::Receiver<SubscriptionUpdate>>,
249 reservation: SubscriberReservation,
250}
251
252#[allow(clippy::disallowed_methods)]
255fn subscription_admission_channel() -> (
256 mpsc::UnboundedSender<SubscriptionRequest>,
257 mpsc::UnboundedReceiver<SubscriptionRequest>,
258) {
259 mpsc::unbounded_channel()
260}
261
262enum AdmissionState {
263 Accepting,
264 WaitingForShard(SubscriptionRequest),
265 Closed,
266}
267
268enum SubscriptionServiceEvent {
269 Checkpoint(Result<Arc<Checkpoint>, broadcast::error::RecvError>),
270 AdmissionRequest(SubscriptionRequest),
271 AdmissionClosed,
272 ShardCapacity {
273 shard: usize,
274 permit: mpsc::OwnedPermit<ShardMsg>,
275 },
276 AdmissionCanceled,
277}
278
279#[derive(Clone)]
280pub struct SubscriptionServiceHandle {
281 admission_sender: mpsc::UnboundedSender<SubscriptionRequest>,
282 counters: Arc<SubscriberCounts>,
283 metrics: SubscriptionMetrics,
284}
285
286impl SubscriptionServiceHandle {
287 pub async fn register_subscription(
288 &self,
289 spec: SubscriptionSpec,
290 ) -> Option<mpsc::Receiver<SubscriptionUpdate>> {
291 let reservation = match self.counters.try_reserve() {
292 Some(reservation) => reservation,
293 None => {
294 trace!(
295 "failed to register new subscriber: hit maximum number of subscribers {}",
296 self.counters.limit
297 );
298 return None;
299 }
300 };
301
302 let (response_sender, response_receiver) = oneshot::channel();
303 let request = SubscriptionRequest {
304 spec,
305 response_sender,
306 reservation,
307 };
308 self.admission_sender.send(request).ok()?;
309
310 response_receiver.await.ok()
311 }
312
313 pub(crate) fn stream_metrics(
314 &self,
315 kind: SubscriptionKind,
316 ) -> crate::metrics::SubscriptionStreamMetrics {
317 self.metrics.stream_metrics(kind.metric_label())
318 }
319}
320
321pub(crate) struct SubscriberCounts {
327 limit: usize,
328 reserved: AtomicUsize,
329 total: AtomicUsize,
330 filtered_tx: AtomicUsize,
331 filtered_event: AtomicUsize,
332}
333
334impl SubscriberCounts {
335 fn new(limit: usize) -> Self {
336 Self {
337 limit,
338 reserved: AtomicUsize::new(0),
339 total: AtomicUsize::new(0),
340 filtered_tx: AtomicUsize::new(0),
341 filtered_event: AtomicUsize::new(0),
342 }
343 }
344
345 fn try_reserve(self: &Arc<Self>) -> Option<SubscriberReservation> {
346 self.reserved
347 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |reserved| {
348 (reserved < self.limit).then_some(reserved + 1)
349 })
350 .ok()?;
351 Some(SubscriberReservation {
352 counters: Arc::clone(self),
353 })
354 }
355
356 #[cfg(test)]
357 fn reserved(&self) -> usize {
358 self.reserved.load(Ordering::Relaxed)
359 }
360}
361
362pub(crate) struct SubscriberReservation {
363 counters: Arc<SubscriberCounts>,
364}
365
366impl SubscriberReservation {
367 fn increment_resident_counts(&self, kind: SubscriptionKind, filtered: bool) {
368 self.counters.total.fetch_add(1, Ordering::Relaxed);
369 if filtered {
370 match kind {
371 SubscriptionKind::Checkpoints | SubscriptionKind::Transactions => {
372 self.counters.filtered_tx.fetch_add(1, Ordering::Relaxed);
373 }
374 SubscriptionKind::Events => {
375 self.counters.filtered_event.fetch_add(1, Ordering::Relaxed);
376 }
377 }
378 }
379 }
380
381 fn decrement_resident_counts(&self, kind: SubscriptionKind, filtered: bool) {
382 self.counters.total.fetch_sub(1, Ordering::Relaxed);
383 if filtered {
384 match kind {
385 SubscriptionKind::Checkpoints | SubscriptionKind::Transactions => {
386 self.counters.filtered_tx.fetch_sub(1, Ordering::Relaxed);
387 }
388 SubscriptionKind::Events => {
389 self.counters.filtered_event.fetch_sub(1, Ordering::Relaxed);
390 }
391 }
392 }
393 }
394}
395
396impl Drop for SubscriberReservation {
397 fn drop(&mut self) {
398 let previous = self.counters.reserved.fetch_sub(1, Ordering::Relaxed);
399 debug_assert!(previous > 0, "subscriber reservation count underflow");
400 }
401}
402
403enum ShardMsg {
404 Checkpoint(Arc<Checkpoint>, Arc<matcher::CheckpointKeys>),
406 Register {
408 spec: SubscriptionSpec,
409 sender: mpsc::Sender<SubscriptionUpdate>,
410 guard: SubscriptionLifecycleGuard,
411 },
412 Clear(SubscriptionTerminationReason),
414}
415
416struct SubscriptionShard {
419 mailbox: mpsc::Receiver<ShardMsg>,
420 matcher: matcher::SubscriptionMatcher,
421 watermark_interval: u32,
424}
425
426impl SubscriptionShard {
427 async fn run(mut self) {
428 while let Some(msg) = self.mailbox.recv().await {
429 self.handle_msg(msg);
430 }
431 self.matcher
432 .clear(SubscriptionTerminationReason::ServiceShutdown);
433 }
434
435 fn handle_msg(&mut self, msg: ShardMsg) {
436 match msg {
437 ShardMsg::Register {
438 spec,
439 sender,
440 guard,
441 } => {
442 self.matcher.insert(spec, sender, guard);
443 }
444 ShardMsg::Checkpoint(checkpoint, keys) => {
445 self.matcher
446 .dispatch_with_keys(&checkpoint, &keys, self.watermark_interval);
447 }
448 ShardMsg::Clear(reason) => {
449 self.matcher.clear(reason);
450 }
451 }
452 }
453
454 #[cfg(test)]
458 fn drain(&mut self) {
459 while let Ok(msg) = self.mailbox.try_recv() {
460 self.handle_msg(msg);
461 }
462 }
463}
464
465pub struct SubscriptionService {
466 checkpoint_mailbox: broadcast::Receiver<Arc<Checkpoint>>,
472 admission_mailbox: mpsc::UnboundedReceiver<SubscriptionRequest>,
473 shards: Vec<mpsc::Sender<ShardMsg>>,
476 next_shard: usize,
478 counters: Arc<SubscriberCounts>,
481
482 indexed_checkpoint: Option<IndexedCheckpointFn>,
486
487 metrics: SubscriptionMetrics,
488}
489
490impl SubscriptionService {
491 pub fn build(
495 registry: &prometheus::Registry,
496 indexed_checkpoint: Option<IndexedCheckpointFn>,
497 watermark_interval: Option<u32>,
498 max_subscribers: Option<usize>,
499 shards: Option<u32>,
500 ) -> (
501 broadcast::Sender<Arc<Checkpoint>>,
502 SubscriptionServiceHandle,
503 ) {
504 let metrics = SubscriptionMetrics::new(registry);
505 let max_subscribers = max_subscribers.unwrap_or(DEFAULT_MAX_SUBSCRIBERS);
506 let (checkpoint_sender, checkpoint_mailbox) = broadcast::channel(CHECKPOINT_MAILBOX_SIZE);
507 let (admission_sender, admission_mailbox) = subscription_admission_channel();
508 let counters = Arc::new(SubscriberCounts::new(max_subscribers));
509 let handle = SubscriptionServiceHandle {
510 admission_sender,
511 counters: Arc::clone(&counters),
512 metrics: metrics.clone(),
513 };
514
515 let watermark_interval = watermark_interval
516 .unwrap_or(DEFAULT_WATERMARK_INTERVAL)
517 .max(1);
518 let shards = shards.unwrap_or_else(default_shards).max(1);
519 let mut shard_senders = Vec::with_capacity(shards as usize);
520 for _ in 0..shards {
521 let (sender, shard_mailbox) = mpsc::channel(SHARD_MAILBOX_SIZE);
522 tokio::spawn(
523 SubscriptionShard {
524 mailbox: shard_mailbox,
525 matcher: matcher::SubscriptionMatcher::default(),
526 watermark_interval,
527 }
528 .run(),
529 );
530 shard_senders.push(sender);
531 }
532
533 tokio::spawn(
534 Self {
535 checkpoint_mailbox,
536 admission_mailbox,
537 shards: shard_senders,
538 next_shard: 0,
539 counters,
540 indexed_checkpoint,
541 metrics,
542 }
543 .start(),
544 );
545
546 (checkpoint_sender, handle)
547 }
548
549 async fn start(mut self) {
550 let mut admission_state = AdmissionState::Accepting;
551 loop {
552 let event = match &mut admission_state {
553 AdmissionState::Accepting => {
554 tokio::select! {
555 biased;
556
557 result = self.checkpoint_mailbox.recv() => {
558 SubscriptionServiceEvent::Checkpoint(result)
559 },
560 request = self.admission_mailbox.recv() => {
561 match request {
562 Some(request) => {
563 SubscriptionServiceEvent::AdmissionRequest(request)
564 }
565 None => SubscriptionServiceEvent::AdmissionClosed,
566 }
567 },
568 }
569 }
570 AdmissionState::WaitingForShard(request) => {
571 let shards: Vec<_> = (0..self.shards.len())
572 .map(|offset| {
573 let shard = (self.next_shard + offset) % self.shards.len();
574 (shard, self.shards[shard].clone())
575 })
576 .collect();
577
578 tokio::select! {
579 biased;
580
581 result = self.checkpoint_mailbox.recv() => {
582 SubscriptionServiceEvent::Checkpoint(result)
583 },
584 _ = request.response_sender.closed() => {
585 SubscriptionServiceEvent::AdmissionCanceled
586 },
587 (shard, permit) = Self::reserve_first_available_shard(shards) => {
588 SubscriptionServiceEvent::ShardCapacity { shard, permit }
589 },
590 }
591 }
592 AdmissionState::Closed => {
593 SubscriptionServiceEvent::Checkpoint(self.checkpoint_mailbox.recv().await)
594 }
595 };
596
597 match event {
598 SubscriptionServiceEvent::Checkpoint(Ok(checkpoint)) => {
599 self.handle_checkpoint(checkpoint).await;
600 }
601 SubscriptionServiceEvent::Checkpoint(Err(broadcast::error::RecvError::Lagged(
602 skipped,
603 ))) => {
604 self.handle_lag(skipped).await;
605 }
606 SubscriptionServiceEvent::Checkpoint(Err(broadcast::error::RecvError::Closed)) => {
609 break;
610 }
611 SubscriptionServiceEvent::AdmissionRequest(request) => {
612 admission_state = self.admit_ready_requests(request);
613 }
614 SubscriptionServiceEvent::AdmissionClosed => {
615 admission_state = AdmissionState::Closed;
618 }
619 SubscriptionServiceEvent::ShardCapacity { shard, permit } => {
620 let AdmissionState::WaitingForShard(request) = admission_state else {
621 unreachable!("shard capacity requires a waiting admission");
622 };
623 self.complete_admission(shard, permit, request);
624 admission_state = AdmissionState::Accepting;
625 }
626 SubscriptionServiceEvent::AdmissionCanceled => {
627 let AdmissionState::WaitingForShard(_) = admission_state else {
628 unreachable!("admission cancellation requires a waiting admission");
629 };
630 admission_state = AdmissionState::Accepting;
631 }
632 }
633 }
634
635 for shard in &self.shards {
636 shard
637 .send(ShardMsg::Clear(
638 SubscriptionTerminationReason::ServiceShutdown,
639 ))
640 .await
641 .expect("subscription shard terminated unexpectedly");
642 }
643
644 info!("RPC Subscription Services ended");
645 }
646
647 async fn handle_checkpoint(&mut self, checkpoint: Arc<Checkpoint>) {
648 {
653 let last_sequence_number = self.metrics.last_recieved_checkpoint.get();
654 let sequence_number = *checkpoint.summary.sequence_number() as i64;
655
656 if last_sequence_number != 0 && (last_sequence_number + 1) != sequence_number {
657 panic!(
658 "recieved checkpoint out-of-order. expected checkpoint {}, recieved {}",
659 last_sequence_number + 1,
660 sequence_number
661 );
662 }
663
664 self.metrics.last_recieved_checkpoint.set(sequence_number);
666 }
667
668 self.wait_until_indexed(*checkpoint.summary.sequence_number())
672 .await;
673
674 if self.counters.total.load(Ordering::Relaxed) == 0 {
678 return;
679 }
680
681 let keys = Arc::new(matcher::extract_checkpoint_keys(
687 &checkpoint,
688 self.counters.filtered_tx.load(Ordering::Relaxed) > 0,
689 self.counters.filtered_event.load(Ordering::Relaxed) > 0,
690 ));
691 for shard in &self.shards {
692 shard
693 .send(ShardMsg::Checkpoint(
694 Arc::clone(&checkpoint),
695 Arc::clone(&keys),
696 ))
697 .await
698 .expect("subscription shard terminated unexpectedly");
699 }
700 }
701
702 async fn wait_until_indexed(&self, sequence_number: u64) {
709 let Some(indexed) = &self.indexed_checkpoint else {
710 return;
711 };
712
713 if indexed().is_some_and(|hi| hi >= sequence_number) {
714 return;
715 }
716
717 let wait_started = Instant::now();
718 let deadline = wait_started + INDEX_WAIT_TIMEOUT;
719 loop {
720 sleep(INDEX_WAIT_POLL_INTERVAL).await;
721 if indexed().is_some_and(|hi| hi >= sequence_number) {
722 self.metrics
723 .index_wait_seconds
724 .observe(wait_started.elapsed().as_secs_f64());
725 return;
726 }
727 if Instant::now() >= deadline {
728 self.metrics.index_wait_timeouts_total.inc();
729 self.metrics
730 .index_wait_seconds
731 .observe(wait_started.elapsed().as_secs_f64());
732 warn!(
733 checkpoint = sequence_number,
734 "index did not catch up within {INDEX_WAIT_TIMEOUT:?}; \
735 delivering checkpoint anyway"
736 );
737 return;
738 }
739 }
740 }
741
742 async fn handle_lag(&mut self, skipped: u64) {
748 warn!(
749 skipped,
750 "subscription service lagged behind the checkpoint stream; \
751 dropping all in-progress subscriptions"
752 );
753 for shard in &self.shards {
757 shard
758 .send(ShardMsg::Clear(SubscriptionTerminationReason::SourceLag))
759 .await
760 .expect("subscription shard terminated unexpectedly");
761 }
762 self.metrics.last_recieved_checkpoint.set(0);
765 }
766
767 fn try_reserve_least_backlogged_shard(&self) -> Option<(usize, mpsc::OwnedPermit<ShardMsg>)> {
768 let mut selected_shard = None;
769 let mut greatest_capacity = 0;
770
771 for offset in 0..self.shards.len() {
772 let shard = (self.next_shard + offset) % self.shards.len();
773 let sender = &self.shards[shard];
774 if sender.is_closed() {
775 panic!("subscription shard terminated unexpectedly");
776 }
777
778 let capacity = sender.capacity();
779 if selected_shard.is_none() || capacity > greatest_capacity {
780 selected_shard = Some(shard);
781 greatest_capacity = capacity;
782 }
783 }
784
785 let shard = selected_shard.expect("subscription service requires at least one shard");
786 match self.shards[shard].clone().try_reserve_owned() {
787 Ok(permit) => Some((shard, permit)),
788 Err(mpsc::error::TrySendError::Full(_)) => None,
789 Err(mpsc::error::TrySendError::Closed(_)) => {
790 panic!("subscription shard terminated unexpectedly")
791 }
792 }
793 }
794
795 async fn reserve_first_available_shard(
796 shards: Vec<(usize, mpsc::Sender<ShardMsg>)>,
797 ) -> (usize, mpsc::OwnedPermit<ShardMsg>) {
798 assert!(
799 !shards.is_empty(),
800 "subscription service requires at least one shard"
801 );
802 for (_, sender) in &shards {
803 if sender.is_closed() {
804 panic!("subscription shard terminated unexpectedly");
805 }
806 }
807
808 let mut reservations = FuturesUnordered::new();
809 for (shard, sender) in shards {
810 reservations.push(async move { (shard, sender.reserve_owned().await) });
811 }
812
813 match reservations
814 .next()
815 .await
816 .expect("subscription service requires at least one shard")
817 {
818 (shard, Ok(permit)) => (shard, permit),
819 (_, Err(_)) => panic!("subscription shard terminated unexpectedly"),
820 }
821 }
822
823 fn complete_admission(
824 &mut self,
825 shard: usize,
826 permit: mpsc::OwnedPermit<ShardMsg>,
827 request: SubscriptionRequest,
828 ) {
829 if request.response_sender.is_closed() {
830 trace!("failed to register new subscriber: request was cancelled");
831 return;
832 }
833
834 let (sender, receiver) = mpsc::channel(SUBSCRIPTION_CHANNEL_SIZE);
835 if request.response_sender.send(receiver).is_err() {
836 trace!("failed to register new subscriber: request was cancelled");
837 return;
838 }
839
840 trace!("successfully registered new subscriber");
841 let kind = request.spec.kind;
842 let filtered = request.spec.query.is_some();
843 let guard =
844 SubscriptionLifecycleGuard::new(kind, filtered, request.reservation, &self.metrics);
845 permit.send(ShardMsg::Register {
846 spec: request.spec,
847 sender,
848 guard,
849 });
850 self.next_shard = (shard + 1) % self.shards.len();
851 }
852
853 fn try_admit(&mut self, request: SubscriptionRequest) -> AdmissionState {
854 if request.response_sender.is_closed() {
855 trace!("failed to register new subscriber: request was cancelled");
856 return AdmissionState::Accepting;
857 }
858
859 let Some((shard, permit)) = self.try_reserve_least_backlogged_shard() else {
860 trace!("waiting for a subscription shard to have capacity");
861 return AdmissionState::WaitingForShard(request);
862 };
863
864 self.complete_admission(shard, permit, request);
865 AdmissionState::Accepting
866 }
867
868 fn admit_ready_requests(&mut self, first_request: SubscriptionRequest) -> AdmissionState {
869 let mut request = first_request;
870 let mut remaining_attempts = ADMISSION_TURN_LIMIT;
871
872 loop {
873 match self.try_admit(request) {
874 AdmissionState::Accepting => {}
875 state => return state,
876 }
877
878 remaining_attempts -= 1;
879 if remaining_attempts == 0 {
880 return AdmissionState::Accepting;
881 }
882
883 request = match self.admission_mailbox.try_recv() {
884 Ok(request) => request,
885 Err(mpsc::error::TryRecvError::Empty) => {
886 return AdmissionState::Accepting;
887 }
888 Err(mpsc::error::TryRecvError::Disconnected) => {
889 return AdmissionState::Closed;
890 }
891 };
892 }
893 }
894}
895
896#[cfg(test)]
897mod tests {
898 use std::sync::atomic::AtomicU64;
899
900 use move_core_types::account_address::AccountAddress;
901 use move_core_types::identifier::Identifier;
902 use move_core_types::language_storage::StructTag;
903 use sui_rpc::proto::sui::rpc::v2 as proto;
904 use sui_types::base_types::ObjectID;
905 use sui_types::base_types::SuiAddress;
906 use sui_types::event::Event;
907 use sui_types::test_checkpoint_data_builder::TestCheckpointBuilder;
908
909 use crate::ledger_history::filter::event_filter_to_query;
910 use crate::ledger_history::filter::transaction_filter_to_query;
911
912 use super::*;
913
914 fn test_service(shard_count: usize) -> (SubscriptionService, Vec<SubscriptionShard>) {
917 test_service_with(shard_count, 25, None)
918 }
919
920 fn test_service_with(
921 shard_count: usize,
922 watermark_interval: u32,
923 indexed_checkpoint: Option<IndexedCheckpointFn>,
924 ) -> (SubscriptionService, Vec<SubscriptionShard>) {
925 let (service, _checkpoint_sender, _request_sender, shards) = actor_service_with(
926 shard_count,
927 watermark_interval,
928 indexed_checkpoint,
929 16,
930 DEFAULT_MAX_SUBSCRIBERS,
931 );
932 (service, shards)
933 }
934
935 fn actor_service_with(
936 shard_count: usize,
937 watermark_interval: u32,
938 indexed_checkpoint: Option<IndexedCheckpointFn>,
939 checkpoint_capacity: usize,
940 max_subscribers: usize,
941 ) -> (
942 SubscriptionService,
943 broadcast::Sender<Arc<Checkpoint>>,
944 mpsc::UnboundedSender<SubscriptionRequest>,
945 Vec<SubscriptionShard>,
946 ) {
947 let (checkpoint_sender, checkpoint_mailbox) = broadcast::channel(checkpoint_capacity);
948 let (request_sender, admission_mailbox) = subscription_admission_channel();
949 let metrics = SubscriptionMetrics::new(&prometheus::Registry::new());
950 let counters = Arc::new(SubscriberCounts::new(max_subscribers));
951
952 let mut shard_senders = Vec::with_capacity(shard_count);
953 let mut shards = Vec::with_capacity(shard_count);
954 for _ in 0..shard_count {
955 let (sender, shard_mailbox) = mpsc::channel(SHARD_MAILBOX_SIZE);
956 shard_senders.push(sender);
957 shards.push(SubscriptionShard {
958 mailbox: shard_mailbox,
959 matcher: matcher::SubscriptionMatcher::default(),
960 watermark_interval,
961 });
962 }
963
964 let service = SubscriptionService {
965 checkpoint_mailbox,
966 admission_mailbox,
967 shards: shard_senders,
968 next_shard: 0,
969 counters,
970 indexed_checkpoint,
971 metrics,
972 };
973 (service, checkpoint_sender, request_sender, shards)
974 }
975
976 fn checkpoint(sequence_number: u64) -> Arc<Checkpoint> {
977 Arc::new(TestCheckpointBuilder::new(sequence_number).build_checkpoint())
978 }
979
980 fn checkpoint_with_senders(seq: u64, senders: &[u8]) -> Arc<Checkpoint> {
982 let mut builder = TestCheckpointBuilder::new(seq);
983 for &sender in senders {
984 builder = builder.start_transaction(sender).finish_transaction();
985 }
986 Arc::new(builder.build_checkpoint())
987 }
988
989 fn checkpoint_with_events(seq: u64) -> Arc<Checkpoint> {
991 let package = AccountAddress::random();
992 let event = |name: &str| Event {
993 package_id: ObjectID::from(package),
994 transaction_module: Identifier::new("emitter").unwrap(),
995 sender: addr(1),
996 type_: StructTag {
997 address: package,
998 module: Identifier::new("mod_t").unwrap(),
999 name: Identifier::new(name).unwrap(),
1000 type_params: vec![],
1001 },
1002 contents: vec![],
1003 };
1004 let mut builder = TestCheckpointBuilder::new(seq);
1005 builder = builder.start_transaction(0).finish_transaction();
1006 builder = builder
1007 .start_transaction(1)
1008 .with_events(vec![event("EventA"), event("EventB")])
1009 .finish_transaction();
1010 Arc::new(builder.build_checkpoint())
1011 }
1012
1013 fn addr(idx: u8) -> SuiAddress {
1014 TestCheckpointBuilder::derive_address(idx)
1015 }
1016
1017 fn sender_query(address: SuiAddress, negated: bool) -> BitmapQuery {
1018 let mut sender = proto::SenderFilter::default();
1019 sender.address = Some(address.to_string());
1020 let mut literal = proto::TransactionLiteral::default();
1021 literal.predicate = Some(proto::transaction_literal::Predicate::Sender(sender));
1022 literal.negated = negated;
1023 let mut term = proto::TransactionTerm::default();
1024 term.literals = vec![literal];
1025 let mut filter = proto::TransactionFilter::default();
1026 filter.terms = vec![term];
1027 transaction_filter_to_query(&filter, 16).unwrap()
1028 }
1029
1030 fn event_type_query(type_str: &str) -> BitmapQuery {
1031 let mut event_type = proto::EventTypeFilter::default();
1032 event_type.event_type = Some(type_str.to_owned());
1033 let mut literal = proto::EventLiteral::default();
1034 literal.predicate = Some(proto::event_literal::Predicate::EventType(event_type));
1035 let mut term = proto::EventTerm::default();
1036 term.literals = vec![literal];
1037 let mut filter = proto::EventFilter::default();
1038 filter.terms = vec![term];
1039 event_filter_to_query(&filter, 16).unwrap()
1040 }
1041
1042 fn unfiltered() -> SubscriptionSpec {
1043 SubscriptionSpec {
1044 kind: SubscriptionKind::Checkpoints,
1045 query: None,
1046 }
1047 }
1048
1049 fn subscription_request(
1050 counters: &Arc<SubscriberCounts>,
1051 spec: SubscriptionSpec,
1052 response_sender: oneshot::Sender<mpsc::Receiver<SubscriptionUpdate>>,
1053 ) -> SubscriptionRequest {
1054 let reservation = counters
1055 .try_reserve()
1056 .expect("test request requires subscriber capacity");
1057 SubscriptionRequest {
1058 spec,
1059 response_sender,
1060 reservation,
1061 }
1062 }
1063
1064 async fn register(
1069 service: &mut SubscriptionService,
1070 spec: SubscriptionSpec,
1071 ) -> Option<mpsc::Receiver<SubscriptionUpdate>> {
1072 let reservation = service.counters.try_reserve()?;
1073 let (response_sender, response_receiver) = oneshot::channel();
1074 assert!(matches!(
1075 service.try_admit(SubscriptionRequest {
1076 spec,
1077 response_sender,
1078 reservation,
1079 }),
1080 AdmissionState::Accepting
1081 ));
1082 response_receiver.await.ok()
1083 }
1084 fn inflight_subscribers(metrics: &SubscriptionMetrics) -> i64 {
1085 ["checkpoint", "transaction", "event"]
1086 .into_iter()
1087 .flat_map(|kind| {
1088 ["true", "false"].into_iter().map(move |filtered| {
1089 metrics
1090 .inflight_subscribers
1091 .with_label_values(&[kind, filtered])
1092 .get()
1093 })
1094 })
1095 .sum()
1096 }
1097
1098 fn terminations(
1099 metrics: &SubscriptionMetrics,
1100 kind: &'static str,
1101 reason: &'static str,
1102 ) -> u64 {
1103 metrics
1104 .terminations_total
1105 .with_label_values(&[kind, reason])
1106 .get()
1107 }
1108
1109 fn drain(shards: &mut [SubscriptionShard]) {
1111 for shard in shards {
1112 shard.drain();
1113 }
1114 }
1115
1116 fn matched_sequence_number(update: SubscriptionUpdate) -> u64 {
1117 match update {
1118 SubscriptionUpdate::Matched(matched) => {
1119 assert!(matches!(matched.matches, SubscriptionMatches::Checkpoint));
1120 *matched.checkpoint.summary.sequence_number()
1121 }
1122 SubscriptionUpdate::WatermarkTick { .. } => {
1123 panic!("expected a matched checkpoint, got a watermark tick")
1124 }
1125 }
1126 }
1127
1128 fn matched_transactions(update: SubscriptionUpdate) -> Vec<u32> {
1129 match update {
1130 SubscriptionUpdate::Matched(matched) => match matched.matches {
1131 SubscriptionMatches::Transactions(ranges) => ranges.into_iter().flatten().collect(),
1132 _ => panic!("expected transaction matches"),
1133 },
1134 SubscriptionUpdate::WatermarkTick { .. } => {
1135 panic!("expected a matched checkpoint, got a watermark tick")
1136 }
1137 }
1138 }
1139
1140 #[test]
1141 fn subscriber_reservations_are_bounded_and_released() {
1142 let counters = Arc::new(SubscriberCounts::new(8));
1143 let attempted = Arc::new(std::sync::Barrier::new(65));
1144 let release = Arc::new(std::sync::Barrier::new(65));
1145
1146 std::thread::scope(|scope| {
1147 let mut handles = Vec::with_capacity(64);
1148 for _ in 0..64 {
1149 let counters = Arc::clone(&counters);
1150 let attempted = Arc::clone(&attempted);
1151 let release = Arc::clone(&release);
1152 handles.push(scope.spawn(move || {
1153 let reservation = counters.try_reserve();
1154 attempted.wait();
1155 release.wait();
1156 reservation.is_some()
1157 }));
1158 }
1159
1160 attempted.wait();
1161 assert_eq!(counters.reserved(), 8);
1162 release.wait();
1163
1164 let successful_reservations = handles
1165 .into_iter()
1166 .map(|handle| handle.join().unwrap())
1167 .filter(|success| *success)
1168 .count();
1169 assert_eq!(successful_reservations, 8);
1170 });
1171
1172 assert_eq!(counters.reserved(), 0);
1173 }
1174
1175 #[tokio::test]
1176 async fn pending_request_reserves_final_subscriber_slot() {
1177 let counters = Arc::new(SubscriberCounts::new(3));
1178 let active_reservations = [
1179 counters.try_reserve().unwrap(),
1180 counters.try_reserve().unwrap(),
1181 ];
1182 let (admission_sender, mailbox) = subscription_admission_channel();
1183 let handle = SubscriptionServiceHandle {
1184 admission_sender,
1185 counters: Arc::clone(&counters),
1186 metrics: SubscriptionMetrics::new(&prometheus::Registry::new()),
1187 };
1188
1189 let pending_handle = handle.clone();
1190 let pending_registration =
1191 tokio::spawn(async move { pending_handle.register_subscription(unfiltered()).await });
1192 tokio::time::timeout(Duration::from_secs(1), async {
1193 while mailbox.len() != 1 {
1194 tokio::task::yield_now().await;
1195 }
1196 })
1197 .await
1198 .expect("pending registration did not enter admission");
1199
1200 assert_eq!(counters.reserved(), 3);
1201 let rejected = tokio::time::timeout(
1202 Duration::from_secs(1),
1203 handle.register_subscription(unfiltered()),
1204 )
1205 .await
1206 .expect("the final subscriber slot is already reserved");
1207 assert!(rejected.is_none());
1208 assert_eq!(mailbox.len(), 1);
1209
1210 drop(mailbox);
1211 assert!(pending_registration.await.unwrap().is_none());
1212 assert_eq!(counters.reserved(), 2);
1213
1214 drop(active_reservations);
1215 assert_eq!(counters.reserved(), 0);
1216 }
1217
1218 #[tokio::test]
1219 async fn zero_subscriber_limit_rejects_before_ingress() {
1220 let (_checkpoint_sender, handle) =
1221 SubscriptionService::build(&prometheus::Registry::new(), None, None, Some(0), Some(1));
1222 assert!(handle.counters.try_reserve().is_none());
1223 assert!(handle.register_subscription(unfiltered()).await.is_none());
1224 assert_eq!(handle.counters.reserved(), 0);
1225 }
1226
1227 #[tokio::test]
1228 async fn public_admission_rejects_before_queue_at_limit() {
1229 let counters = Arc::new(SubscriberCounts::new(1));
1230 let held_reservation = counters.try_reserve().unwrap();
1231 let (admission_sender, mut mailbox) = subscription_admission_channel();
1232 let handle = SubscriptionServiceHandle {
1233 admission_sender,
1234 counters: Arc::clone(&counters),
1235 metrics: SubscriptionMetrics::new(&prometheus::Registry::new()),
1236 };
1237
1238 let result = tokio::time::timeout(
1239 Duration::from_secs(1),
1240 handle.register_subscription(unfiltered()),
1241 )
1242 .await
1243 .expect("a saturated service must reject before queueing");
1244 assert!(result.is_none());
1245 assert!(matches!(
1246 mailbox.try_recv(),
1247 Err(mpsc::error::TryRecvError::Empty)
1248 ));
1249
1250 drop(held_reservation);
1251 assert_eq!(counters.reserved(), 0);
1252 }
1253
1254 #[tokio::test]
1255 async fn closed_public_admission_queue_releases_reservation() {
1256 let counters = Arc::new(SubscriberCounts::new(1));
1257 let (admission_sender, mailbox) = subscription_admission_channel();
1258 drop(mailbox);
1259 let handle = SubscriptionServiceHandle {
1260 admission_sender,
1261 counters: Arc::clone(&counters),
1262 metrics: SubscriptionMetrics::new(&prometheus::Registry::new()),
1263 };
1264
1265 let result = handle.register_subscription(unfiltered()).await;
1266 assert!(result.is_none());
1267 assert_eq!(counters.reserved(), 0);
1268 }
1269
1270 #[test]
1271 fn admission_turn_is_bounded_and_drains_ready_requests() {
1272 let (mut service, _checkpoint_sender, request_sender, _shards) =
1273 actor_service_with(3, 25, None, 4, DEFAULT_MAX_SUBSCRIBERS);
1274 let mut response_receivers = Vec::with_capacity(ADMISSION_TURN_LIMIT + 1);
1275
1276 for _ in 0..=ADMISSION_TURN_LIMIT {
1277 let (response_sender, response_receiver) = oneshot::channel();
1278 assert!(
1279 request_sender
1280 .send(subscription_request(
1281 &service.counters,
1282 unfiltered(),
1283 response_sender,
1284 ))
1285 .is_ok()
1286 );
1287 response_receivers.push(response_receiver);
1288 }
1289
1290 let first_request = service.admission_mailbox.try_recv().unwrap();
1291 assert!(matches!(
1292 service.admit_ready_requests(first_request),
1293 AdmissionState::Accepting
1294 ));
1295 assert_eq!(service.admission_mailbox.len(), 1);
1296
1297 for response_receiver in response_receivers.iter_mut().take(ADMISSION_TURN_LIMIT) {
1298 response_receiver
1299 .try_recv()
1300 .expect("request should be admitted");
1301 }
1302 assert!(matches!(
1303 response_receivers.last_mut().unwrap().try_recv(),
1304 Err(oneshot::error::TryRecvError::Empty)
1305 ));
1306
1307 let final_request = service.admission_mailbox.try_recv().unwrap();
1308 assert!(matches!(
1309 service.admit_ready_requests(final_request),
1310 AdmissionState::Accepting
1311 ));
1312 response_receivers
1313 .last_mut()
1314 .unwrap()
1315 .try_recv()
1316 .expect("final request should be admitted");
1317 }
1318
1319 #[test]
1320 fn canceled_ready_admission_releases_reservation() {
1321 let (mut service, _checkpoint_sender, _request_sender, _shards) =
1322 actor_service_with(1, 25, None, 4, 1);
1323 let (response_sender, response_receiver) = oneshot::channel();
1324 let request = subscription_request(&service.counters, unfiltered(), response_sender);
1325 assert_eq!(service.counters.reserved(), 1);
1326
1327 drop(response_receiver);
1328 assert!(matches!(
1329 service.try_admit(request),
1330 AdmissionState::Accepting
1331 ));
1332 assert_eq!(service.counters.reserved(), 0);
1333 }
1334
1335 #[tokio::test]
1336 async fn checkpoint_backlog_drains_before_registration() {
1337 let (service, checkpoint_sender, request_sender, mut shards) =
1340 actor_service_with(1, 25, None, 4, DEFAULT_MAX_SUBSCRIBERS);
1341 let metrics = service.metrics.clone();
1342 let (reply_sender, reply_receiver) = oneshot::channel();
1343 assert!(
1344 request_sender
1345 .send(subscription_request(
1346 &service.counters,
1347 unfiltered(),
1348 reply_sender,
1349 ))
1350 .is_ok()
1351 );
1352 assert_eq!(checkpoint_sender.send(checkpoint(1)).unwrap(), 1);
1353 assert_eq!(checkpoint_sender.send(checkpoint(2)).unwrap(), 1);
1354
1355 let actor = tokio::spawn(service.start());
1358 let mut receiver = tokio::time::timeout(Duration::from_secs(1), reply_receiver)
1359 .await
1360 .expect("admission remained blocked after the checkpoint backlog drained")
1361 .unwrap();
1362 assert_eq!(metrics.last_recieved_checkpoint.get(), 2);
1363
1364 assert_eq!(checkpoint_sender.send(checkpoint(3)).unwrap(), 1);
1367 drop(checkpoint_sender);
1368 actor.await.unwrap();
1369
1370 drain(&mut shards);
1371 assert_eq!(matched_sequence_number(receiver.recv().await.unwrap()), 3);
1372 assert!(receiver.recv().await.is_none());
1373 }
1374
1375 #[tokio::test]
1376 async fn queued_admission_releases_reservation_when_actor_shuts_down() {
1377 let (service, checkpoint_sender, request_sender, _shards) =
1378 actor_service_with(1, 25, None, 4, 1);
1379 let counters = Arc::clone(&service.counters);
1380 let (response_sender, response_receiver) = oneshot::channel();
1381 assert!(
1382 request_sender
1383 .send(subscription_request(
1384 &counters,
1385 unfiltered(),
1386 response_sender,
1387 ))
1388 .is_ok()
1389 );
1390 assert_eq!(counters.reserved(), 1);
1391
1392 drop(checkpoint_sender);
1393 service.start().await;
1394
1395 assert!(response_receiver.await.is_err());
1396 assert_eq!(counters.reserved(), 0);
1397 }
1398
1399 #[tokio::test]
1400 async fn admission_prefers_least_backlogged_shard() {
1401 let (mut service, mut shards) = test_service(3);
1404 for _ in 0..2 {
1405 assert!(
1406 service.shards[0]
1407 .try_send(ShardMsg::Clear(
1408 SubscriptionTerminationReason::ServiceShutdown
1409 ))
1410 .is_ok()
1411 );
1412 }
1413 assert!(
1414 service.shards[1]
1415 .try_send(ShardMsg::Clear(
1416 SubscriptionTerminationReason::ServiceShutdown
1417 ))
1418 .is_ok()
1419 );
1420
1421 let receiver = register(&mut service, unfiltered()).await.unwrap();
1424 let registration = shards[2].mailbox.try_recv().unwrap();
1425 assert!(matches!(registration, ShardMsg::Register { .. }));
1426 shards[2].handle_msg(registration);
1427 assert_eq!(service.counters.total.load(Ordering::Relaxed), 1);
1428 assert_eq!(inflight_subscribers(&service.metrics), 1);
1429 drop(receiver);
1430 }
1431
1432 #[tokio::test]
1433 async fn saturated_admission_waits_for_any_shard() {
1434 let (service, _checkpoint_sender, request_sender, mut shards) =
1437 actor_service_with(2, 25, None, 4, DEFAULT_MAX_SUBSCRIBERS);
1438 for shard in &service.shards {
1439 for _ in 0..SHARD_MAILBOX_SIZE {
1440 assert!(
1441 shard
1442 .try_send(ShardMsg::Clear(
1443 SubscriptionTerminationReason::ServiceShutdown
1444 ))
1445 .is_ok()
1446 );
1447 }
1448 }
1449
1450 let (reply_sender, reply_receiver) = oneshot::channel();
1453 assert!(
1454 request_sender
1455 .send(subscription_request(
1456 &service.counters,
1457 unfiltered(),
1458 reply_sender,
1459 ))
1460 .is_ok()
1461 );
1462 let mut actor = Box::pin(service.start());
1463 assert!(futures::poll!(&mut actor).is_pending());
1464 let actor = tokio::spawn(actor);
1465
1466 assert!(matches!(
1469 shards[1].mailbox.try_recv(),
1470 Ok(ShardMsg::Clear(_))
1471 ));
1472 let _receiver = tokio::time::timeout(Duration::from_secs(1), reply_receiver)
1473 .await
1474 .expect("admission did not wake when shard capacity became available")
1475 .unwrap();
1476
1477 for _ in 1..SHARD_MAILBOX_SIZE {
1480 assert!(matches!(
1481 shards[1].mailbox.try_recv(),
1482 Ok(ShardMsg::Clear(_))
1483 ));
1484 }
1485 assert!(matches!(
1486 shards[1].mailbox.try_recv(),
1487 Ok(ShardMsg::Register { .. })
1488 ));
1489 assert_eq!(shards[0].mailbox.len(), SHARD_MAILBOX_SIZE);
1490
1491 actor.abort();
1492 let _ = actor.await;
1493 }
1494
1495 #[tokio::test]
1496 async fn saturated_head_blocks_newer_request_until_canceled() {
1497 let (service, _checkpoint_sender, request_sender, mut shards) =
1499 actor_service_with(2, 25, None, 4, DEFAULT_MAX_SUBSCRIBERS);
1500 let counters = Arc::clone(&service.counters);
1501 for shard in &service.shards {
1502 for _ in 0..SHARD_MAILBOX_SIZE {
1503 assert!(
1504 shard
1505 .try_send(ShardMsg::Clear(
1506 SubscriptionTerminationReason::ServiceShutdown
1507 ))
1508 .is_ok()
1509 );
1510 }
1511 }
1512
1513 let (first_sender, first_reply) = oneshot::channel();
1514 let (second_sender, second_reply) = oneshot::channel();
1515 assert!(
1516 request_sender
1517 .send(subscription_request(
1518 &service.counters,
1519 unfiltered(),
1520 first_sender,
1521 ))
1522 .is_ok()
1523 );
1524 assert!(
1525 request_sender
1526 .send(subscription_request(
1527 &service.counters,
1528 unfiltered(),
1529 second_sender,
1530 ))
1531 .is_ok()
1532 );
1533 assert_eq!(counters.reserved(), 2);
1534
1535 let mut actor = Box::pin(service.start());
1538 assert!(futures::poll!(&mut actor).is_pending());
1539
1540 drop(first_reply);
1541 assert!(futures::poll!(&mut actor).is_pending());
1544 assert_eq!(counters.reserved(), 1);
1545 let actor = tokio::spawn(actor);
1546
1547 assert!(matches!(
1549 shards[0].mailbox.try_recv(),
1550 Ok(ShardMsg::Clear(_))
1551 ));
1552 let _receiver = tokio::time::timeout(Duration::from_secs(1), second_reply)
1553 .await
1554 .expect("newer admission remained blocked after canceling the waiting request")
1555 .unwrap();
1556 assert_eq!(counters.reserved(), 1);
1557
1558 actor.abort();
1559 let _ = actor.await;
1560 drop(shards);
1561 assert_eq!(counters.reserved(), 0);
1562 }
1563
1564 #[tokio::test]
1565 async fn run_loop_lag_clears_subscribers_and_resets_sequence_tracker() {
1566 let (mut service, checkpoint_sender, _request_sender, mut shards) =
1569 actor_service_with(1, 25, None, 1, DEFAULT_MAX_SUBSCRIBERS);
1570 let mut receiver = register(&mut service, unfiltered()).await.unwrap();
1571 let counters = Arc::clone(&service.counters);
1572 let metrics = service.metrics.clone();
1573 metrics.last_recieved_checkpoint.set(99);
1576
1577 assert_eq!(checkpoint_sender.send(checkpoint(1)).unwrap(), 1);
1578 assert_eq!(checkpoint_sender.send(checkpoint(2)).unwrap(), 1);
1579 let actor = tokio::spawn(service.start());
1580 drop(checkpoint_sender);
1581 actor.await.unwrap();
1582
1583 drain(&mut shards);
1586 assert!(receiver.recv().await.is_none());
1587 assert_eq!(counters.total.load(Ordering::Relaxed), 0);
1588 assert_eq!(inflight_subscribers(&metrics), 0);
1589 assert_eq!(terminations(&metrics, "checkpoint", "source_lag"), 1);
1590 assert_eq!(metrics.last_recieved_checkpoint.get(), 2);
1593 }
1594
1595 #[tokio::test]
1596 async fn labeled_inflight_gauge_covers_all_types_and_filter_states() {
1597 let (mut service, mut shards) = test_service(1);
1598 let kinds = [
1599 SubscriptionKind::Checkpoints,
1600 SubscriptionKind::Transactions,
1601 SubscriptionKind::Events,
1602 ];
1603 let mut receivers = Vec::new();
1604
1605 for kind in kinds {
1606 for filtered in [false, true] {
1607 let query = filtered.then(|| match kind {
1608 SubscriptionKind::Checkpoints | SubscriptionKind::Transactions => {
1609 sender_query(addr(0), false)
1610 }
1611 SubscriptionKind::Events => event_type_query(
1612 "0x0000000000000000000000000000000000000000000000000000000000000002::coin::CoinEvent",
1613 ),
1614 });
1615 receivers.push(
1616 register(&mut service, SubscriptionSpec { kind, query })
1617 .await
1618 .unwrap(),
1619 );
1620 }
1621 }
1622 drain(&mut shards);
1623
1624 for kind in kinds {
1625 for filtered in ["false", "true"] {
1626 assert_eq!(
1627 service
1628 .metrics
1629 .inflight_subscribers
1630 .with_label_values(&[kind.metric_label(), filtered])
1631 .get(),
1632 1
1633 );
1634 }
1635 }
1636
1637 drop(receivers);
1638 service.handle_checkpoint(checkpoint(1)).await;
1639 drain(&mut shards);
1640 assert_eq!(inflight_subscribers(&service.metrics), 0);
1641 assert_eq!(service.counters.total.load(Ordering::Relaxed), 0);
1642
1643 shards[0].handle_msg(ShardMsg::Clear(
1644 SubscriptionTerminationReason::ServiceShutdown,
1645 ));
1646 assert_eq!(inflight_subscribers(&service.metrics), 0);
1647 assert_eq!(service.counters.total.load(Ordering::Relaxed), 0);
1648 for kind in ["checkpoint", "transaction", "event"] {
1649 assert_eq!(terminations(&service.metrics, kind, "client_closed"), 2);
1650 }
1651 }
1652
1653 #[tokio::test]
1654 async fn handle_checkpoint_fans_out_in_order() {
1655 let (mut service, mut shards) = test_service(1);
1656 let mut receiver = register(&mut service, unfiltered()).await.unwrap();
1657
1658 service.handle_checkpoint(checkpoint(1)).await;
1659 service.handle_checkpoint(checkpoint(2)).await;
1660 drain(&mut shards);
1661
1662 assert_eq!(matched_sequence_number(receiver.recv().await.unwrap()), 1);
1663 assert_eq!(matched_sequence_number(receiver.recv().await.unwrap()), 2);
1664 assert_eq!(shards[0].matcher.len(), 1);
1665 }
1666
1667 #[tokio::test]
1668 async fn handle_checkpoint_drops_departed_subscriber() {
1669 let (mut service, mut shards) = test_service(1);
1670 let receiver = register(&mut service, unfiltered()).await.unwrap();
1671 drop(receiver);
1672
1673 service.handle_checkpoint(checkpoint(1)).await;
1674 drain(&mut shards);
1675
1676 assert!(shards[0].matcher.is_empty());
1677 assert_eq!(inflight_subscribers(&service.metrics), 0);
1678 assert_eq!(service.counters.total.load(Ordering::Relaxed), 0);
1679 assert_eq!(
1680 terminations(&service.metrics, "checkpoint", "client_closed"),
1681 1
1682 );
1683 }
1684
1685 #[tokio::test(start_paused = true)]
1686 async fn handle_checkpoint_waits_for_index_before_delivering() {
1687 let indexed = Arc::new(AtomicU64::new(4));
1689 let gate = indexed.clone();
1690 let (mut service, mut shards) = test_service_with(
1691 1,
1692 25,
1693 Some(Arc::new(move || Some(gate.load(Ordering::SeqCst)))),
1694 );
1695 let mut receiver = register(&mut service, unfiltered()).await.unwrap();
1696
1697 service.handle_checkpoint(checkpoint(4)).await;
1698 drain(&mut shards);
1699 assert_eq!(matched_sequence_number(receiver.recv().await.unwrap()), 4);
1700 assert_eq!(service.metrics.index_wait_seconds.get_sample_count(), 0);
1701 assert_eq!(service.metrics.index_wait_timeouts_total.get(), 0);
1702
1703 {
1705 let mut deliver = std::pin::pin!(service.handle_checkpoint(checkpoint(5)));
1706 assert!(
1707 futures::poll!(&mut deliver).is_pending(),
1708 "delivery should block while checkpoint 5 is unindexed"
1709 );
1710 assert!(receiver.try_recv().is_err());
1711
1712 indexed.store(5, Ordering::SeqCst);
1713 tokio::time::advance(INDEX_WAIT_POLL_INTERVAL).await;
1714 deliver.await;
1715 }
1716 drain(&mut shards);
1717
1718 assert_eq!(matched_sequence_number(receiver.recv().await.unwrap()), 5);
1719 assert_eq!(service.metrics.index_wait_seconds.get_sample_count(), 1);
1720 assert!(
1721 service.metrics.index_wait_seconds.get_sample_sum()
1722 >= INDEX_WAIT_POLL_INTERVAL.as_secs_f64()
1723 );
1724 assert_eq!(service.metrics.index_wait_timeouts_total.get(), 0);
1725 }
1726
1727 #[tokio::test(start_paused = true)]
1728 async fn index_wait_timeout_records_and_delivers_with_paused_time() {
1729 let indexed = Arc::new(AtomicU64::new(4));
1730 let gate = indexed.clone();
1731 let (mut service, mut shards) = test_service_with(
1732 1,
1733 25,
1734 Some(Arc::new(move || Some(gate.load(Ordering::SeqCst)))),
1735 );
1736 let mut receiver = register(&mut service, unfiltered()).await.unwrap();
1737
1738 {
1739 let mut deliver = std::pin::pin!(service.handle_checkpoint(checkpoint(5)));
1740 assert!(
1741 futures::poll!(&mut deliver).is_pending(),
1742 "delivery should block while checkpoint 5 is unindexed"
1743 );
1744 tokio::time::advance(INDEX_WAIT_TIMEOUT).await;
1745 deliver.await;
1746 }
1747 drain(&mut shards);
1748
1749 assert_eq!(matched_sequence_number(receiver.recv().await.unwrap()), 5);
1750 assert_eq!(service.metrics.index_wait_seconds.get_sample_count(), 1);
1751 assert!(
1752 service.metrics.index_wait_seconds.get_sample_sum() >= INDEX_WAIT_TIMEOUT.as_secs_f64()
1753 );
1754 assert_eq!(service.metrics.index_wait_timeouts_total.get(), 1);
1755 }
1756
1757 #[tokio::test]
1758 async fn handle_lag_drops_all_subscribers_and_resets_tracker() {
1759 let (mut service, mut shards) = test_service(2);
1760 let mut receiver_1 = register(&mut service, unfiltered()).await.unwrap();
1761 let mut receiver_2 = register(&mut service, unfiltered()).await.unwrap();
1762
1763 service.handle_checkpoint(checkpoint(5)).await;
1764 drain(&mut shards);
1765 assert_eq!(service.metrics.last_recieved_checkpoint.get(), 5);
1766
1767 service.handle_lag(10).await;
1768 drain(&mut shards);
1769
1770 assert!(shards[0].matcher.is_empty());
1772 assert!(shards[1].matcher.is_empty());
1773 assert_eq!(inflight_subscribers(&service.metrics), 0);
1774 assert_eq!(service.counters.total.load(Ordering::Relaxed), 0);
1775 assert!(receiver_1.recv().await.is_some()); assert!(receiver_1.recv().await.is_none());
1778 assert!(receiver_2.recv().await.is_some());
1779 assert!(receiver_2.recv().await.is_none());
1780 assert_eq!(service.metrics.last_recieved_checkpoint.get(), 0);
1783 assert_eq!(
1784 terminations(&service.metrics, "checkpoint", "source_lag"),
1785 2
1786 );
1787 service.handle_lag(1).await;
1788 drain(&mut shards);
1789 assert_eq!(
1790 terminations(&service.metrics, "checkpoint", "source_lag"),
1791 2
1792 );
1793
1794 let mut receiver_3 = register(&mut service, unfiltered()).await.unwrap();
1795 service.handle_checkpoint(checkpoint(100)).await;
1796 drain(&mut shards);
1797 assert_eq!(
1798 matched_sequence_number(receiver_3.recv().await.unwrap()),
1799 100
1800 );
1801 }
1802
1803 #[tokio::test]
1804 async fn service_shutdown_records_each_subscription_once() {
1805 let (mut service, mut shards) = test_service(2);
1806 let receiver_explicit = register(&mut service, unfiltered()).await.unwrap();
1807 let receiver_fallback = register(&mut service, unfiltered()).await.unwrap();
1808 drain(&mut shards);
1809 let metrics = service.metrics.clone();
1810 let counters = Arc::clone(&service.counters);
1811 assert_eq!(counters.reserved(), 2);
1812
1813 service.shards[0]
1814 .send(ShardMsg::Clear(
1815 SubscriptionTerminationReason::ServiceShutdown,
1816 ))
1817 .await
1818 .unwrap();
1819 shards[0].drain();
1820 assert_eq!(terminations(&metrics, "checkpoint", "service_shutdown"), 1);
1821 assert_eq!(counters.reserved(), 1);
1822
1823 let fallback_shard = shards.pop().unwrap();
1824 drop(service);
1825 fallback_shard.run().await;
1826 assert!(receiver_explicit.is_closed());
1827 assert!(receiver_fallback.is_closed());
1828 assert_eq!(inflight_subscribers(&metrics), 0);
1829 assert_eq!(counters.total.load(Ordering::Relaxed), 0);
1830 assert_eq!(counters.reserved(), 0);
1831 assert_eq!(counters.filtered_tx.load(Ordering::Relaxed), 0);
1832 assert_eq!(counters.filtered_event.load(Ordering::Relaxed), 0);
1833 assert_eq!(terminations(&metrics, "checkpoint", "service_shutdown"), 2);
1834
1835 drop(shards);
1836 assert_eq!(terminations(&metrics, "checkpoint", "service_shutdown"), 2);
1837 }
1838
1839 #[tokio::test]
1840 async fn subscribers_on_every_shard_receive_a_matched_checkpoint() {
1841 let (mut service, mut shards) = test_service(2);
1842 let mut receivers = Vec::new();
1843 for _ in 0..4 {
1844 receivers.push(register(&mut service, unfiltered()).await.unwrap());
1845 }
1846 drain(&mut shards);
1847 assert_eq!(shards[0].matcher.len(), 2);
1849 assert_eq!(shards[1].matcher.len(), 2);
1850
1851 service.handle_checkpoint(checkpoint(1)).await;
1852 drain(&mut shards);
1853 for receiver in &mut receivers {
1854 assert_eq!(matched_sequence_number(receiver.recv().await.unwrap()), 1);
1855 }
1856 }
1857
1858 #[tokio::test]
1859 async fn filtered_subscribers_match_via_dispatcher_extracted_keys() {
1860 let (mut service, mut shards) = test_service(2);
1861 let mut include_rx = register(
1863 &mut service,
1864 SubscriptionSpec {
1865 kind: SubscriptionKind::Transactions,
1866 query: Some(sender_query(addr(0), false)),
1867 },
1868 )
1869 .await
1870 .unwrap();
1871 let mut exclude_rx = register(
1875 &mut service,
1876 SubscriptionSpec {
1877 kind: SubscriptionKind::Transactions,
1878 query: Some(sender_query(addr(0), true)),
1879 },
1880 )
1881 .await
1882 .unwrap();
1883 assert_eq!(service.counters.filtered_tx.load(Ordering::Relaxed), 2);
1884
1885 let checkpoint = checkpoint_with_senders(1, &[0, 1]);
1886 let tx_lo = checkpoint.summary.data().network_total_transactions
1887 - checkpoint.transactions.len() as u64;
1888 service.handle_checkpoint(checkpoint).await;
1889 drain(&mut shards);
1890
1891 assert!(matches!(
1892 include_rx.recv().await.unwrap(),
1893 SubscriptionUpdate::WatermarkTick {
1894 checkpoint: 0,
1895 tx_hi
1896 } if tx_hi == tx_lo
1897 ));
1898 assert!(matches!(
1899 exclude_rx.recv().await.unwrap(),
1900 SubscriptionUpdate::WatermarkTick {
1901 checkpoint: 0,
1902 tx_hi
1903 } if tx_hi == tx_lo
1904 ));
1905
1906 assert_eq!(
1907 matched_transactions(include_rx.recv().await.unwrap()),
1908 vec![0]
1909 );
1910 assert_eq!(
1911 matched_transactions(exclude_rx.recv().await.unwrap()),
1912 vec![1]
1913 );
1914 }
1915
1916 #[tokio::test]
1917 async fn configured_cap_is_enforced_globally_across_shards() {
1918 let max_subscribers = 3;
1919 let (mut service, _, _, mut shards) = actor_service_with(2, 25, None, 16, max_subscribers);
1920 let mut receivers = Vec::with_capacity(max_subscribers);
1921 for _ in 0..max_subscribers {
1922 receivers.push(register(&mut service, unfiltered()).await.unwrap());
1923 }
1924 drain(&mut shards);
1925 assert_eq!(
1926 service.counters.total.load(Ordering::Relaxed),
1927 max_subscribers
1928 );
1929 assert_eq!(service.counters.reserved(), max_subscribers);
1930 assert_eq!(
1931 inflight_subscribers(&service.metrics),
1932 max_subscribers as i64
1933 );
1934 assert!(!shards[0].matcher.is_empty());
1935 assert!(!shards[1].matcher.is_empty());
1936
1937 assert!(register(&mut service, unfiltered()).await.is_none());
1940 assert_eq!(
1941 service.counters.total.load(Ordering::Relaxed),
1942 max_subscribers
1943 );
1944 assert_eq!(service.counters.reserved(), max_subscribers);
1945 assert_eq!(
1946 inflight_subscribers(&service.metrics),
1947 max_subscribers as i64
1948 );
1949 assert_eq!(
1950 terminations(&service.metrics, "checkpoint", "service_shutdown"),
1951 0
1952 );
1953
1954 drop(receivers);
1955 service.handle_checkpoint(checkpoint(1)).await;
1956 drain(&mut shards);
1957 assert_eq!(service.counters.total.load(Ordering::Relaxed), 0);
1958 assert_eq!(service.counters.reserved(), 0);
1959 assert_eq!(inflight_subscribers(&service.metrics), 0);
1960
1961 let replacement = register(&mut service, unfiltered()).await.unwrap();
1962 drain(&mut shards);
1963 assert_eq!(service.counters.total.load(Ordering::Relaxed), 1);
1964 assert_eq!(service.counters.reserved(), 1);
1965
1966 drop(replacement);
1967 service.handle_checkpoint(checkpoint(2)).await;
1968 drain(&mut shards);
1969 assert_eq!(service.counters.total.load(Ordering::Relaxed), 0);
1970 assert_eq!(service.counters.reserved(), 0);
1971 }
1972
1973 #[tokio::test]
1974 async fn unprocessed_register_finalizes_guard_when_shard_drops() {
1975 let (mut service, shards) = test_service(1);
1976 let _receiver = register(&mut service, unfiltered()).await.unwrap();
1977 assert_eq!(service.counters.total.load(Ordering::Relaxed), 1);
1978 assert_eq!(inflight_subscribers(&service.metrics), 1);
1979 assert_eq!(service.counters.reserved(), 1);
1980
1981 drop(shards);
1986 assert_eq!(service.counters.total.load(Ordering::Relaxed), 0);
1987 assert_eq!(service.counters.reserved(), 0);
1988 assert_eq!(inflight_subscribers(&service.metrics), 0);
1989 assert_eq!(
1990 terminations(&service.metrics, "checkpoint", "service_shutdown"),
1991 1
1992 );
1993 }
1994
1995 #[tokio::test]
1996 async fn registration_racing_dispatch_lands_at_the_next_checkpoint() {
1997 let (mut service, mut shards) = test_service(1);
1998 let mut receiver_a = register(&mut service, unfiltered()).await.unwrap();
1999 service.handle_checkpoint(checkpoint(1)).await;
2000 let mut receiver_b = register(&mut service, unfiltered()).await.unwrap();
2001 service.handle_checkpoint(checkpoint(2)).await;
2002 drain(&mut shards);
2003
2004 assert_eq!(matched_sequence_number(receiver_a.recv().await.unwrap()), 1);
2007 assert_eq!(matched_sequence_number(receiver_a.recv().await.unwrap()), 2);
2008 assert_eq!(matched_sequence_number(receiver_b.recv().await.unwrap()), 2);
2009 assert!(receiver_b.try_recv().is_err());
2010 }
2011
2012 #[tokio::test]
2013 async fn space_counters_track_filtered_subscribers() {
2014 let (mut service, mut shards) = test_service_with(2, 1, None);
2015 let tx_rx = register(
2016 &mut service,
2017 SubscriptionSpec {
2018 kind: SubscriptionKind::Transactions,
2019 query: Some(sender_query(addr(0), false)),
2020 },
2021 )
2022 .await
2023 .unwrap();
2024 assert_eq!(service.counters.filtered_tx.load(Ordering::Relaxed), 1);
2025 let event_rx = register(
2026 &mut service,
2027 SubscriptionSpec {
2028 kind: SubscriptionKind::Events,
2029 query: Some(event_type_query(
2030 "0x0000000000000000000000000000000000000000000000000000000000000002::coin::CoinEvent",
2031 )),
2032 },
2033 )
2034 .await
2035 .unwrap();
2036 assert_eq!(service.counters.filtered_event.load(Ordering::Relaxed), 1);
2037
2038 drop(tx_rx);
2041 drop(event_rx);
2042 service.handle_checkpoint(checkpoint(1)).await;
2043 drain(&mut shards);
2044
2045 assert_eq!(service.counters.filtered_tx.load(Ordering::Relaxed), 0);
2046 assert_eq!(service.counters.filtered_event.load(Ordering::Relaxed), 0);
2047 assert_eq!(inflight_subscribers(&service.metrics), 0);
2048 }
2049
2050 #[tokio::test]
2051 async fn unfiltered_subscribers_receive_all_matches() {
2052 let (mut service, mut shards) = test_service(1);
2053 let mut tx_rx = register(
2054 &mut service,
2055 SubscriptionSpec {
2056 kind: SubscriptionKind::Transactions,
2057 query: None,
2058 },
2059 )
2060 .await
2061 .unwrap();
2062 let mut ev_rx = register(
2063 &mut service,
2064 SubscriptionSpec {
2065 kind: SubscriptionKind::Events,
2066 query: None,
2067 },
2068 )
2069 .await
2070 .unwrap();
2071
2072 let event_checkpoint = checkpoint_with_events(1);
2073 let tx_lo = event_checkpoint.summary.data().network_total_transactions
2074 - event_checkpoint.transactions.len() as u64;
2075 service.handle_checkpoint(event_checkpoint).await;
2076 drain(&mut shards);
2077
2078 assert!(matches!(
2079 tx_rx.recv().await.unwrap(),
2080 SubscriptionUpdate::WatermarkTick {
2081 checkpoint: 0,
2082 tx_hi
2083 } if tx_hi == tx_lo
2084 ));
2085 assert!(matches!(
2086 ev_rx.recv().await.unwrap(),
2087 SubscriptionUpdate::WatermarkTick {
2088 checkpoint: 0,
2089 tx_hi
2090 } if tx_hi == tx_lo
2091 ));
2092
2093 let matched = match tx_rx.recv().await.unwrap() {
2096 SubscriptionUpdate::Matched(matched) => matched,
2097 SubscriptionUpdate::WatermarkTick { .. } => panic!("expected a match, got a tick"),
2098 };
2099 assert!(matches!(
2100 matched.matches,
2101 SubscriptionMatches::AllTransactions
2102 ));
2103 let tx_count = matched.checkpoint.transactions.len() as u32;
2104 assert_eq!(
2105 matched
2106 .matches
2107 .transaction_indices(tx_count)
2108 .unwrap()
2109 .collect::<Vec<_>>(),
2110 vec![0, 1]
2111 );
2112
2113 let matched = match ev_rx.recv().await.unwrap() {
2114 SubscriptionUpdate::Matched(matched) => matched,
2115 SubscriptionUpdate::WatermarkTick { .. } => panic!("expected a match, got a tick"),
2116 };
2117 assert!(matches!(matched.matches, SubscriptionMatches::AllEvents));
2118 assert_eq!(
2119 matched
2120 .matches
2121 .event_indices(&matched.checkpoint)
2122 .unwrap()
2123 .collect::<Vec<_>>(),
2124 vec![(1, 0), (1, 1)]
2125 );
2126
2127 service.handle_checkpoint(checkpoint(2)).await;
2130 drain(&mut shards);
2131 assert!(tx_rx.try_recv().is_err());
2132 assert!(ev_rx.try_recv().is_err());
2133 }
2134
2135 #[test]
2136 fn event_indices_flatten_sparse_matches_in_order() {
2137 let matches = SubscriptionMatches::Events(vec![(0, vec![1, 2]), (3, vec![0])]);
2138 let checkpoint = checkpoint(1);
2139 assert_eq!(
2140 matches
2141 .event_indices(&checkpoint)
2142 .unwrap()
2143 .collect::<Vec<_>>(),
2144 vec![(0, 1), (0, 2), (3, 0)]
2145 );
2146 }
2147}