1use std::collections::BTreeMap;
13use std::collections::btree_map::Entry;
14use std::fmt::{Debug, Formatter};
15use std::net::SocketAddr;
16use std::pin::Pin;
17use std::str::FromStr;
18use std::sync::atomic::{AtomicUsize, Ordering};
19use std::sync::{Arc, Mutex, RwLock, Weak};
20use std::time::{Duration, Instant, SystemTime};
21
22use anyhow::{Error, anyhow};
23use async_trait::async_trait;
24use bytes::Bytes;
25use futures::Stream;
26use futures_util::StreamExt;
27use mz_dyncfg::{Config, ParameterScope};
28use mz_ore::cast::CastFrom;
29use mz_ore::collections::{HashMap, HashSet};
30use mz_ore::metrics::MetricsRegistry;
31use mz_ore::retry::RetryResult;
32use mz_ore::task::JoinHandle;
33use mz_persist::location::VersionedData;
34use mz_proto::{ProtoType, RustType};
35use prost::Message;
36use tokio::sync::mpsc::Sender;
37use tokio::sync::mpsc::error::TrySendError;
38use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
39use tokio_stream::wrappers::{BroadcastStream, ReceiverStream};
40use tonic::metadata::{AsciiMetadataKey, AsciiMetadataValue, MetadataMap};
41use tonic::transport::Endpoint;
42use tonic::{Extensions, Request, Response, Status, Streaming};
43use tracing::{Instrument, debug, error, info, info_span, warn};
44
45use crate::ShardId;
46use crate::cache::{DynState, StateCache};
47use crate::cfg::PersistConfig;
48use crate::internal::metrics::{PubSubClientCallMetrics, PubSubServerMetrics};
49use crate::internal::service::proto_persist_pub_sub_client::ProtoPersistPubSubClient;
50use crate::internal::service::proto_persist_pub_sub_server::ProtoPersistPubSubServer;
51use crate::internal::service::{
52 ProtoPubSubMessage, ProtoPushDiff, ProtoSubscribe, ProtoUnsubscribe,
53 proto_persist_pub_sub_server, proto_pub_sub_message,
54};
55use crate::metrics::Metrics;
56
57pub(crate) const PUBSUB_CLIENT_ENABLED: Config<bool> = Config::new(
59 "persist_pubsub_client_enabled",
60 true,
61 "Whether to connect to the Persist PubSub service.",
62 ParameterScope::Environment,
63);
64
65pub(crate) const PUBSUB_PUSH_DIFF_ENABLED: Config<bool> = Config::new(
69 "persist_pubsub_push_diff_enabled",
70 true,
71 "Whether to push state diffs to Persist PubSub.",
72 ParameterScope::Environment,
73);
74
75pub(crate) const PUBSUB_SAME_PROCESS_DELEGATE_ENABLED: Config<bool> = Config::new(
79 "persist_pubsub_same_process_delegate_enabled",
80 true,
81 "Whether to push state diffs to Persist PubSub on the same process.",
82 ParameterScope::Environment,
83);
84
85pub(crate) const PUBSUB_CONNECT_ATTEMPT_TIMEOUT: Config<Duration> = Config::new(
87 "persist_pubsub_connect_attempt_timeout",
88 Duration::from_secs(5),
89 "Timeout per connection attempt to Persist PubSub service.",
90 ParameterScope::Environment,
91);
92
93pub(crate) const PUBSUB_REQUEST_TIMEOUT: Config<Duration> = Config::new(
95 "persist_pubsub_request_timeout",
96 Duration::from_secs(5),
97 "Timeout per request attempt to Persist PubSub service.",
98 ParameterScope::Environment,
99);
100
101pub(crate) const PUBSUB_CONNECT_MAX_BACKOFF: Config<Duration> = Config::new(
103 "persist_pubsub_connect_max_backoff",
104 Duration::from_secs(60),
105 "Maximum backoff when retrying connection establishment to Persist PubSub service.",
106 ParameterScope::Environment,
107);
108
109pub(crate) const PUBSUB_CLIENT_SENDER_CHANNEL_SIZE: Config<usize> = Config::new(
111 "persist_pubsub_client_sender_channel_size",
112 25,
113 "Size of channel used to buffer send messages to PubSub service.",
114 ParameterScope::Environment,
115);
116
117pub(crate) const PUBSUB_CLIENT_RECEIVER_CHANNEL_SIZE: Config<usize> = Config::new(
119 "persist_pubsub_client_receiver_channel_size",
120 25,
121 "Size of channel used to buffer received messages from PubSub service.",
122 ParameterScope::Environment,
123);
124
125pub(crate) const PUBSUB_SERVER_CONNECTION_CHANNEL_SIZE: Config<usize> = Config::new(
127 "persist_pubsub_server_connection_channel_size",
128 25,
129 "Size of channel used per connection to buffer broadcasted messages from PubSub server.",
130 ParameterScope::Environment,
131);
132
133pub(crate) const PUBSUB_STATE_CACHE_SHARD_REF_CHANNEL_SIZE: Config<usize> = Config::new(
135 "persist_pubsub_state_cache_shard_ref_channel_size",
136 25,
137 "Size of channel used by the state cache to broadcast shard state references.",
138 ParameterScope::Environment,
139);
140
141pub(crate) const PUBSUB_RECONNECT_BACKOFF: Config<Duration> = Config::new(
143 "persist_pubsub_reconnect_backoff",
144 Duration::from_secs(5),
145 "Backoff after an established connection to Persist PubSub service fails.",
146 ParameterScope::Environment,
147);
148
149const MAX_GRPC_MESSAGE_SIZE: usize = usize::MAX;
154
155pub trait PersistPubSubClient {
160 fn connect(
162 pubsub_config: PersistPubSubClientConfig,
163 metrics: Arc<Metrics>,
164 ) -> PubSubClientConnection;
165}
166
167#[derive(Debug)]
169pub struct PubSubClientConnection {
170 pub sender: Arc<dyn PubSubSender>,
172 pub receiver: Box<dyn PubSubReceiver>,
174}
175
176impl PubSubClientConnection {
177 pub fn new(sender: Arc<dyn PubSubSender>, receiver: Box<dyn PubSubReceiver>) -> Self {
179 Self { sender, receiver }
180 }
181
182 pub fn noop() -> Self {
184 Self {
185 sender: Arc::new(NoopPubSubSender),
186 receiver: Box::new(futures::stream::empty()),
187 }
188 }
189}
190
191pub trait PubSubSender: std::fmt::Debug + Send + Sync {
193 fn push_diff(&self, shard_id: &ShardId, diff: &VersionedData);
195
196 fn subscribe(self: Arc<Self>, shard_id: &ShardId) -> Arc<ShardSubscriptionToken>;
204}
205
206trait PubSubSenderInternal: Debug + Send + Sync {
211 fn push_diff(&self, shard_id: &ShardId, diff: &VersionedData);
213
214 fn subscribe(&self, shard_id: &ShardId);
218
219 fn unsubscribe(&self, shard_id: &ShardId);
223}
224
225pub trait PubSubReceiver:
230 Stream<Item = ProtoPubSubMessage> + Send + Unpin + std::fmt::Debug
231{
232}
233
234impl<T> PubSubReceiver for T where
235 T: Stream<Item = ProtoPubSubMessage> + Send + Unpin + std::fmt::Debug
236{
237}
238
239pub struct ShardSubscriptionToken {
244 pub(crate) shard_id: ShardId,
245 sender: Arc<dyn PubSubSenderInternal>,
246}
247
248impl Debug for ShardSubscriptionToken {
249 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
250 let ShardSubscriptionToken {
251 shard_id,
252 sender: _sender,
253 } = self;
254 write!(f, "ShardSubscriptionToken({})", shard_id)
255 }
256}
257
258impl Drop for ShardSubscriptionToken {
259 fn drop(&mut self) {
260 self.sender.unsubscribe(&self.shard_id);
261 }
262}
263
264pub const PERSIST_PUBSUB_CALLER_KEY: &str = "persist-pubsub-caller-id";
266
267#[derive(Debug)]
269pub struct PersistPubSubClientConfig {
270 pub url: String,
272 pub caller_id: String,
274 pub persist_cfg: PersistConfig,
276}
277
278#[derive(Debug)]
284pub struct GrpcPubSubClient;
285
286impl GrpcPubSubClient {
287 async fn reconnect_to_server_forever(
288 send_requests: tokio::sync::broadcast::Sender<ProtoPubSubMessage>,
289 receiver_input: &tokio::sync::mpsc::Sender<ProtoPubSubMessage>,
290 sender: Arc<SubscriptionTrackingSender>,
291 metadata: MetadataMap,
292 config: PersistPubSubClientConfig,
293 metrics: Arc<Metrics>,
294 ) {
295 config.persist_cfg.configs_synced_once().await;
300
301 let mut is_first_connection_attempt = true;
302 loop {
303 let sender = Arc::clone(&sender);
304 metrics.pubsub_client.grpc_connection.connected.set(0);
305
306 if !PUBSUB_CLIENT_ENABLED.get(&config.persist_cfg) {
307 tokio::time::sleep(Duration::from_secs(5)).await;
308 continue;
309 }
310
311 if is_first_connection_attempt {
313 is_first_connection_attempt = false;
314 } else {
315 tokio::time::sleep(PUBSUB_RECONNECT_BACKOFF.get(&config.persist_cfg)).await;
316 }
317
318 info!("Connecting to Persist PubSub: {}", config.url);
319 let client = mz_ore::retry::Retry::default()
320 .clamp_backoff(PUBSUB_CONNECT_MAX_BACKOFF.get(&config.persist_cfg))
321 .retry_async(|_| async {
322 metrics
323 .pubsub_client
324 .grpc_connection
325 .connect_call_attempt_count
326 .inc();
327 let endpoint = match Endpoint::from_str(&config.url) {
328 Ok(endpoint) => endpoint,
329 Err(err) => return RetryResult::FatalErr(err),
330 };
331 ProtoPersistPubSubClient::connect(
332 endpoint
333 .connect_timeout(
334 PUBSUB_CONNECT_ATTEMPT_TIMEOUT.get(&config.persist_cfg),
335 )
336 .timeout(PUBSUB_REQUEST_TIMEOUT.get(&config.persist_cfg)),
337 )
338 .await
339 .into()
340 })
341 .await;
342
343 let mut client = match client {
344 Ok(client) => client.max_decoding_message_size(MAX_GRPC_MESSAGE_SIZE),
345 Err(err) => {
346 error!("fatal error connecting to persist pubsub: {:?}", err);
347 return;
348 }
349 };
350
351 metrics
352 .pubsub_client
353 .grpc_connection
354 .connection_established_count
355 .inc();
356 metrics.pubsub_client.grpc_connection.connected.set(1);
357
358 info!("Connected to Persist PubSub: {}", config.url);
359
360 let mut broadcast = BroadcastStream::new(send_requests.subscribe());
361 let broadcast_errors = metrics
362 .pubsub_client
363 .grpc_connection
364 .broadcast_recv_lagged_count
365 .clone();
366
367 let (cancel_tx, cancel_rx) = tokio::sync::oneshot::channel::<()>();
373
374 let broadcast_messages = async_stream::stream! {
378 let mut cancel_rx = std::pin::pin!(cancel_rx);
379 'reconnect: loop {
380 for id in sender.subscriptions() {
382 debug!("re-subscribing to shard: {id}");
383 let msg = proto_pub_sub_message::Message::Subscribe(
384 ProtoSubscribe {
385 shard_id: id.into_proto(),
386 },
387 );
388 yield create_request(msg);
389 }
390
391 loop {
393 tokio::select! {
394 message = broadcast.next() => {
395 debug!("sending pubsub message: {:?}", message);
396 match message {
397 Some(Ok(message)) => yield message,
398 Some(Err(BroadcastStreamRecvError::Lagged(i))) => {
399 broadcast_errors.inc_by(i);
400 continue 'reconnect;
401 }
402 None => {
403 debug!("exhausted pubsub broadcast stream; shutting down");
404 return;
405 }
406 }
407 }
408 _ = &mut cancel_rx => {
409 debug!("pubsub broadcast stream cancelled; shutting down");
410 return;
411 }
412 }
413 }
414 }
415 };
416 let pubsub_request =
417 Request::from_parts(metadata.clone(), Extensions::default(), broadcast_messages);
418
419 let responses = match client.pub_sub(pubsub_request).await {
420 Ok(response) => response.into_inner(),
421 Err(err) => {
422 warn!("pub_sub rpc error: {:?}", err);
423 continue;
424 }
425 };
426
427 let stream_completed = GrpcPubSubClient::consume_grpc_stream(
428 responses,
429 receiver_input,
430 &config,
431 metrics.as_ref(),
432 )
433 .await;
434
435 drop(cancel_tx);
436
437 match stream_completed {
438 Ok(_) => continue,
440 Err(err) => {
443 warn!("shutting down connection loop to Persist PubSub: {}", err);
444 return;
445 }
446 }
447 }
448 }
449
450 async fn consume_grpc_stream(
451 mut responses: Streaming<ProtoPubSubMessage>,
452 receiver_input: &Sender<ProtoPubSubMessage>,
453 config: &PersistPubSubClientConfig,
454 metrics: &Metrics,
455 ) -> Result<(), Error> {
456 loop {
457 if !PUBSUB_CLIENT_ENABLED.get(&config.persist_cfg) {
458 return Ok(());
459 }
460
461 debug!("awaiting next pubsub response");
462 match responses.next().await {
463 Some(Ok(message)) => {
464 debug!("received pubsub message: {:?}", message);
465 match receiver_input.send(message).await {
466 Ok(_) => {}
467 Err(err) => {
470 return Err(anyhow!("closing pubsub grpc client connection: {}", err));
471 }
472 }
473 }
474 Some(Err(err)) => {
475 metrics.pubsub_client.grpc_connection.grpc_error_count.inc();
476 warn!("pubsub client error: {:?}", err);
477 return Ok(());
478 }
479 None => return Ok(()),
480 }
481 }
482 }
483}
484
485impl PersistPubSubClient for GrpcPubSubClient {
486 fn connect(config: PersistPubSubClientConfig, metrics: Arc<Metrics>) -> PubSubClientConnection {
487 let (send_requests, _) = tokio::sync::broadcast::channel(
492 PUBSUB_CLIENT_SENDER_CHANNEL_SIZE.get(&config.persist_cfg),
493 );
494 let (receiver_input, receiver_output) = tokio::sync::mpsc::channel(
498 PUBSUB_CLIENT_RECEIVER_CHANNEL_SIZE.get(&config.persist_cfg),
499 );
500
501 let sender = Arc::new(SubscriptionTrackingSender::new(Arc::new(
502 GrpcPubSubSender {
503 metrics: Arc::clone(&metrics),
504 requests: send_requests.clone(),
505 },
506 )));
507 let pubsub_sender = Arc::clone(&sender);
508 mz_ore::task::spawn(
509 || "persist::rpc::client::connection".to_string(),
510 async move {
511 let mut metadata = MetadataMap::new();
512 metadata.insert(
513 AsciiMetadataKey::from_static(PERSIST_PUBSUB_CALLER_KEY),
514 AsciiMetadataValue::try_from(&config.caller_id)
515 .unwrap_or_else(|_| AsciiMetadataValue::from_static("unknown")),
516 );
517
518 GrpcPubSubClient::reconnect_to_server_forever(
519 send_requests,
520 &receiver_input,
521 pubsub_sender,
522 metadata,
523 config,
524 metrics,
525 )
526 .await;
527 },
528 );
529
530 PubSubClientConnection {
531 sender,
532 receiver: Box::new(ReceiverStream::new(receiver_output)),
533 }
534 }
535}
536
537struct GrpcPubSubSender {
539 metrics: Arc<Metrics>,
540 requests: tokio::sync::broadcast::Sender<ProtoPubSubMessage>,
541}
542
543impl Debug for GrpcPubSubSender {
544 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
545 let GrpcPubSubSender {
546 metrics: _metrics,
547 requests: _requests,
548 } = self;
549
550 write!(f, "GrpcPubSubSender")
551 }
552}
553
554fn create_request(message: proto_pub_sub_message::Message) -> ProtoPubSubMessage {
555 let now = SystemTime::now()
556 .duration_since(SystemTime::UNIX_EPOCH)
557 .expect("failed to get millis since epoch");
558
559 ProtoPubSubMessage {
560 timestamp: Some(now.into_proto()),
561 message: Some(message),
562 }
563}
564
565impl GrpcPubSubSender {
566 fn send(&self, message: proto_pub_sub_message::Message, metrics: &PubSubClientCallMetrics) {
567 let size = message.encoded_len();
568
569 match self.requests.send(create_request(message)) {
570 Ok(_) => {
571 metrics.succeeded.inc();
572 metrics.bytes_sent.inc_by(u64::cast_from(size));
573 }
574 Err(err) => {
575 metrics.failed.inc();
576 debug!("error sending client message: {}", err);
577 }
578 }
579 }
580}
581
582impl PubSubSenderInternal for GrpcPubSubSender {
583 fn push_diff(&self, shard_id: &ShardId, diff: &VersionedData) {
584 self.send(
585 proto_pub_sub_message::Message::PushDiff(ProtoPushDiff {
586 shard_id: shard_id.into_proto(),
587 seqno: diff.seqno.into_proto(),
588 diff: diff.data.clone(),
589 }),
590 &self.metrics.pubsub_client.sender.push,
591 )
592 }
593
594 fn subscribe(&self, shard_id: &ShardId) {
595 self.send(
596 proto_pub_sub_message::Message::Subscribe(ProtoSubscribe {
597 shard_id: shard_id.into_proto(),
598 }),
599 &self.metrics.pubsub_client.sender.subscribe,
600 )
601 }
602
603 fn unsubscribe(&self, shard_id: &ShardId) {
604 self.send(
605 proto_pub_sub_message::Message::Unsubscribe(ProtoUnsubscribe {
606 shard_id: shard_id.into_proto(),
607 }),
608 &self.metrics.pubsub_client.sender.unsubscribe,
609 )
610 }
611}
612
613#[derive(Debug)]
616struct SubscriptionTrackingSender {
617 delegate: Arc<dyn PubSubSenderInternal>,
618 subscribes: Arc<Mutex<BTreeMap<ShardId, Weak<ShardSubscriptionToken>>>>,
619}
620
621impl SubscriptionTrackingSender {
622 fn new(sender: Arc<dyn PubSubSenderInternal>) -> Self {
623 Self {
624 delegate: sender,
625 subscribes: Default::default(),
626 }
627 }
628
629 fn subscriptions(&self) -> Vec<ShardId> {
630 let mut subscribes = self.subscribes.lock().expect("lock");
631 let mut out = Vec::with_capacity(subscribes.len());
632 subscribes.retain(|shard_id, token| {
633 if token.upgrade().is_none() {
634 false
635 } else {
636 debug!("reconnecting to: {}", shard_id);
637 out.push(*shard_id);
638 true
639 }
640 });
641 out
642 }
643}
644
645impl PubSubSender for SubscriptionTrackingSender {
646 fn push_diff(&self, shard_id: &ShardId, diff: &VersionedData) {
647 self.delegate.push_diff(shard_id, diff)
648 }
649
650 fn subscribe(self: Arc<Self>, shard_id: &ShardId) -> Arc<ShardSubscriptionToken> {
651 let mut subscribes = self.subscribes.lock().expect("lock");
652 if let Some(token) = subscribes.get(shard_id) {
653 match token.upgrade() {
654 None => assert!(subscribes.remove(shard_id).is_some()),
655 Some(token) => {
656 return Arc::clone(&token);
657 }
658 }
659 }
660
661 let pubsub_sender = Arc::clone(&self.delegate);
662 let token = Arc::new(ShardSubscriptionToken {
663 shard_id: *shard_id,
664 sender: pubsub_sender,
665 });
666
667 assert!(
668 subscribes
669 .insert(*shard_id, Arc::downgrade(&token))
670 .is_none()
671 );
672
673 self.delegate.subscribe(shard_id);
674
675 token
676 }
677}
678
679#[derive(Debug)]
683pub struct MetricsSameProcessPubSubSender {
684 delegate_subscribe: bool,
685 metrics: Arc<Metrics>,
686 delegate: Arc<dyn PubSubSender>,
687}
688
689impl MetricsSameProcessPubSubSender {
690 pub fn new(
693 cfg: &PersistConfig,
694 pubsub_sender: Arc<dyn PubSubSender>,
695 metrics: Arc<Metrics>,
696 ) -> Self {
697 Self {
698 delegate_subscribe: PUBSUB_SAME_PROCESS_DELEGATE_ENABLED.get(cfg),
699 delegate: pubsub_sender,
700 metrics,
701 }
702 }
703}
704
705impl PubSubSender for MetricsSameProcessPubSubSender {
706 fn push_diff(&self, shard_id: &ShardId, diff: &VersionedData) {
707 self.delegate.push_diff(shard_id, diff);
708 self.metrics.pubsub_client.sender.push.succeeded.inc();
709 }
710
711 fn subscribe(self: Arc<Self>, shard_id: &ShardId) -> Arc<ShardSubscriptionToken> {
712 if self.delegate_subscribe {
713 let delegate = Arc::clone(&self.delegate);
714 delegate.subscribe(shard_id)
715 } else {
716 Arc::new(ShardSubscriptionToken {
722 shard_id: *shard_id,
723 sender: Arc::new(NoopPubSubSender),
724 })
725 }
726 }
727}
728
729#[derive(Debug)]
730pub(crate) struct NoopPubSubSender;
731
732impl PubSubSenderInternal for NoopPubSubSender {
733 fn push_diff(&self, _shard_id: &ShardId, _diff: &VersionedData) {}
734 fn subscribe(&self, _shard_id: &ShardId) {}
735 fn unsubscribe(&self, _shard_id: &ShardId) {}
736}
737
738impl PubSubSender for NoopPubSubSender {
739 fn push_diff(&self, _shard_id: &ShardId, _diff: &VersionedData) {}
740
741 fn subscribe(self: Arc<Self>, shard_id: &ShardId) -> Arc<ShardSubscriptionToken> {
742 Arc::new(ShardSubscriptionToken {
743 shard_id: *shard_id,
744 sender: self,
745 })
746 }
747}
748
749pub(crate) fn subscribe_state_cache_to_pubsub(
751 cache: Arc<StateCache>,
752 mut pubsub_receiver: Box<dyn PubSubReceiver>,
753) -> JoinHandle<()> {
754 let mut state_refs: HashMap<ShardId, Weak<dyn DynState>> = HashMap::new();
755 let receiver_metrics = cache.metrics.pubsub_client.receiver.clone();
756
757 mz_ore::task::spawn(
758 || "persist::rpc::client::state_cache_diff_apply",
759 async move {
760 while let Some(msg) = pubsub_receiver.next().await {
761 match msg.message {
762 Some(proto_pub_sub_message::Message::PushDiff(diff)) => {
763 receiver_metrics.push_received.inc();
764 let shard_id = diff.shard_id.into_rust().expect("valid shard id");
765 let diff = VersionedData {
766 seqno: diff.seqno.into_rust().expect("valid SeqNo"),
767 data: diff.diff,
768 };
769 debug!(
770 "applying pubsub diff {} {} {}",
771 shard_id,
772 diff.seqno,
773 diff.data.len()
774 );
775
776 let mut pushed_diff = false;
777 if let Some(state_ref) = state_refs.get(&shard_id) {
778 if let Some(state) = state_ref.upgrade() {
781 state.push_diff(diff.clone());
782 pushed_diff = true;
783 receiver_metrics.state_pushed_diff_fast_path.inc();
784 }
785 }
786
787 if !pushed_diff {
788 let state_ref = cache.get_state_weak(&shard_id);
793 match state_ref {
794 None => {
795 state_refs.remove(&shard_id);
796 }
797 Some(state_ref) => {
798 if let Some(state) = state_ref.upgrade() {
799 state.push_diff(diff);
800 pushed_diff = true;
801 state_refs.insert(shard_id, state_ref);
802 } else {
803 state_refs.remove(&shard_id);
804 }
805 }
806 }
807
808 if pushed_diff {
809 receiver_metrics.state_pushed_diff_slow_path_succeeded.inc();
810 } else {
811 receiver_metrics.state_pushed_diff_slow_path_failed.inc();
812 }
813 }
814
815 if let Some(send_timestamp) = msg.timestamp {
816 let send_timestamp =
817 send_timestamp.into_rust().expect("valid timestamp");
818 let now = SystemTime::now()
819 .duration_since(SystemTime::UNIX_EPOCH)
820 .expect("failed to get millis since epoch");
821 receiver_metrics
822 .approx_diff_latency_seconds
823 .observe((now.saturating_sub(send_timestamp)).as_secs_f64());
824 }
825 }
826 ref msg @ None | ref msg @ Some(_) => {
827 warn!("pubsub client received unexpected message: {:?}", msg);
828 receiver_metrics.unknown_message_received.inc();
829 }
830 }
831 }
832 },
833 )
834}
835
836#[derive(Debug)]
838pub(crate) struct PubSubState {
839 connection_id_counter: AtomicUsize,
841 shard_subscribers:
843 Arc<RwLock<BTreeMap<ShardId, BTreeMap<usize, Sender<Result<ProtoPubSubMessage, Status>>>>>>,
844 connections: Arc<RwLock<HashSet<usize>>>,
846 metrics: Arc<PubSubServerMetrics>,
848}
849
850impl PubSubState {
851 fn new_connection(
852 self: Arc<Self>,
853 notifier: Sender<Result<ProtoPubSubMessage, Status>>,
854 ) -> PubSubConnection {
855 let connection_id = self.connection_id_counter.fetch_add(1, Ordering::SeqCst);
856 {
857 debug!("inserting connid: {}", connection_id);
858 let mut connections = self.connections.write().expect("lock");
859 assert!(connections.insert(connection_id));
860 }
861
862 self.metrics.active_connections.inc();
863 PubSubConnection {
864 connection_id,
865 notifier,
866 state: self,
867 }
868 }
869
870 fn remove_connection(&self, connection_id: usize) {
871 let now = Instant::now();
872
873 {
874 debug!("removing connid: {}", connection_id);
875 let mut connections = self.connections.write().expect("lock");
876 assert!(
877 connections.remove(&connection_id),
878 "unknown connection id: {}",
879 connection_id
880 );
881 }
882
883 {
884 let mut subscribers = self.shard_subscribers.write().expect("lock poisoned");
885 subscribers.retain(|_shard, connections_for_shard| {
886 connections_for_shard.remove(&connection_id);
887 !connections_for_shard.is_empty()
888 });
889 }
890
891 self.metrics
892 .connection_cleanup_seconds
893 .inc_by(now.elapsed().as_secs_f64());
894 self.metrics.active_connections.dec();
895 }
896
897 fn push_diff(&self, connection_id: usize, shard_id: &ShardId, data: &VersionedData) {
898 let now = Instant::now();
899 self.metrics.push_call_count.inc();
900
901 assert!(
902 self.connections
903 .read()
904 .expect("lock")
905 .contains(&connection_id),
906 "unknown connection id: {}",
907 connection_id
908 );
909
910 let subscribers = self.shard_subscribers.read().expect("lock poisoned");
911 if let Some(subscribed_connections) = subscribers.get(shard_id) {
912 let mut num_sent = 0;
913 let mut data_size = 0;
914
915 for (subscribed_conn_id, tx) in subscribed_connections {
916 if *subscribed_conn_id == connection_id {
918 continue;
919 }
920 debug!(
921 "server forwarding req to conn {}: {} {} {}",
922 subscribed_conn_id,
923 &shard_id,
924 data.seqno,
925 data.data.len()
926 );
927 let req = create_request(proto_pub_sub_message::Message::PushDiff(ProtoPushDiff {
928 seqno: data.seqno.into_proto(),
929 shard_id: shard_id.to_string(),
930 diff: Bytes::clone(&data.data),
931 }));
932 data_size = req.encoded_len();
933 match tx.try_send(Ok(req)) {
934 Ok(_) => {
935 num_sent += 1;
936 }
937 Err(TrySendError::Full(_)) => {
938 self.metrics.broadcasted_diff_dropped_channel_full.inc();
939 }
940 Err(TrySendError::Closed(_)) => {}
941 };
942 }
943
944 self.metrics.broadcasted_diff_count.inc_by(num_sent);
945 self.metrics
946 .broadcasted_diff_bytes
947 .inc_by(num_sent * u64::cast_from(data_size));
948 }
949
950 self.metrics
951 .push_seconds
952 .inc_by(now.elapsed().as_secs_f64());
953 }
954
955 fn subscribe(
956 &self,
957 connection_id: usize,
958 notifier: Sender<Result<ProtoPubSubMessage, Status>>,
959 shard_id: &ShardId,
960 ) {
961 let now = Instant::now();
962 self.metrics.subscribe_call_count.inc();
963
964 assert!(
965 self.connections
966 .read()
967 .expect("lock")
968 .contains(&connection_id),
969 "unknown connection id: {}",
970 connection_id
971 );
972
973 {
974 let mut subscribed_shards = self.shard_subscribers.write().expect("lock poisoned");
975 subscribed_shards
976 .entry(*shard_id)
977 .or_default()
978 .insert(connection_id, notifier);
979 }
980
981 self.metrics
982 .subscribe_seconds
983 .inc_by(now.elapsed().as_secs_f64());
984 }
985
986 fn unsubscribe(&self, connection_id: usize, shard_id: &ShardId) {
987 let now = Instant::now();
988 self.metrics.unsubscribe_call_count.inc();
989
990 assert!(
991 self.connections
992 .read()
993 .expect("lock")
994 .contains(&connection_id),
995 "unknown connection id: {}",
996 connection_id
997 );
998
999 {
1000 let mut subscribed_shards = self.shard_subscribers.write().expect("lock poisoned");
1001 if let Entry::Occupied(mut entry) = subscribed_shards.entry(*shard_id) {
1002 let subscribed_connections = entry.get_mut();
1003 subscribed_connections.remove(&connection_id);
1004
1005 if subscribed_connections.is_empty() {
1006 entry.remove_entry();
1007 }
1008 }
1009 }
1010
1011 self.metrics
1012 .unsubscribe_seconds
1013 .inc_by(now.elapsed().as_secs_f64());
1014 }
1015
1016 #[cfg(test)]
1017 fn new_for_test() -> Self {
1018 Self {
1019 connection_id_counter: AtomicUsize::new(0),
1020 shard_subscribers: Default::default(),
1021 connections: Default::default(),
1022 metrics: Arc::new(PubSubServerMetrics::new(&MetricsRegistry::new())),
1023 }
1024 }
1025
1026 #[cfg(test)]
1027 fn active_connections(&self) -> HashSet<usize> {
1028 self.connections.read().expect("lock").clone()
1029 }
1030
1031 #[cfg(test)]
1032 fn subscriptions(&self, connection_id: usize) -> HashSet<ShardId> {
1033 let mut shards = HashSet::new();
1034
1035 let subscribers = self.shard_subscribers.read().expect("lock");
1036 for (shard, subscribed_connections) in subscribers.iter() {
1037 if subscribed_connections.contains_key(&connection_id) {
1038 shards.insert(*shard);
1039 }
1040 }
1041
1042 shards
1043 }
1044
1045 #[cfg(test)]
1046 fn shard_subscription_counts(&self) -> mz_ore::collections::HashMap<ShardId, usize> {
1047 let mut shards = mz_ore::collections::HashMap::new();
1048
1049 let subscribers = self.shard_subscribers.read().expect("lock");
1050 for (shard, subscribed_connections) in subscribers.iter() {
1051 shards.insert(*shard, subscribed_connections.len());
1052 }
1053
1054 shards
1055 }
1056}
1057
1058#[derive(Debug)]
1060pub struct PersistGrpcPubSubServer {
1061 cfg: PersistConfig,
1062 state: Arc<PubSubState>,
1063}
1064
1065impl PersistGrpcPubSubServer {
1066 pub fn new(cfg: &PersistConfig, metrics_registry: &MetricsRegistry) -> Self {
1068 let metrics = PubSubServerMetrics::new(metrics_registry);
1069 let state = Arc::new(PubSubState {
1070 connection_id_counter: AtomicUsize::new(0),
1071 shard_subscribers: Default::default(),
1072 connections: Default::default(),
1073 metrics: Arc::new(metrics),
1074 });
1075
1076 PersistGrpcPubSubServer {
1077 cfg: cfg.clone(),
1078 state,
1079 }
1080 }
1081
1082 pub fn new_same_process_connection(&self) -> PubSubClientConnection {
1086 let (tx, rx) =
1087 tokio::sync::mpsc::channel(PUBSUB_CLIENT_RECEIVER_CHANNEL_SIZE.get(&self.cfg));
1088 let sender: Arc<dyn PubSubSender> = Arc::new(SubscriptionTrackingSender::new(Arc::new(
1089 Arc::clone(&self.state).new_connection(tx),
1090 )));
1091
1092 PubSubClientConnection {
1093 sender,
1094 receiver: Box::new(
1095 ReceiverStream::new(rx).map(|x| x.expect("cannot receive grpc errors locally")),
1096 ),
1097 }
1098 }
1099
1100 pub async fn serve(self, listen_addr: SocketAddr) -> Result<(), anyhow::Error> {
1102 tonic::transport::Server::builder()
1104 .add_service(
1105 ProtoPersistPubSubServer::new(self)
1106 .max_decoding_message_size(MAX_GRPC_MESSAGE_SIZE),
1107 )
1108 .serve(listen_addr)
1109 .await?;
1110 Ok(())
1111 }
1112
1113 pub async fn serve_with_stream(
1116 self,
1117 listener: tokio_stream::wrappers::TcpListenerStream,
1118 ) -> Result<(), anyhow::Error> {
1119 tonic::transport::Server::builder()
1120 .add_service(
1121 ProtoPersistPubSubServer::new(self)
1122 .max_decoding_message_size(MAX_GRPC_MESSAGE_SIZE),
1123 )
1124 .serve_with_incoming(listener)
1125 .await?;
1126 Ok(())
1127 }
1128}
1129
1130#[async_trait]
1131impl proto_persist_pub_sub_server::ProtoPersistPubSub for PersistGrpcPubSubServer {
1132 type PubSubStream = Pin<Box<dyn Stream<Item = Result<ProtoPubSubMessage, Status>> + Send>>;
1133
1134 #[mz_ore::instrument(name = "persist::rpc::server", level = "info")]
1135 async fn pub_sub(
1136 &self,
1137 request: Request<Streaming<ProtoPubSubMessage>>,
1138 ) -> Result<Response<Self::PubSubStream>, Status> {
1139 let caller_id = request
1140 .metadata()
1141 .get(AsciiMetadataKey::from_static(PERSIST_PUBSUB_CALLER_KEY))
1142 .map(|key| key.to_str().ok())
1143 .flatten()
1144 .map(|key| key.to_string())
1145 .unwrap_or_else(|| "unknown".to_string());
1146 info!("Received Persist PubSub connection from: {:?}", caller_id);
1147
1148 let mut in_stream = request.into_inner();
1149 let (tx, rx) =
1150 tokio::sync::mpsc::channel(PUBSUB_SERVER_CONNECTION_CHANNEL_SIZE.get(&self.cfg));
1151
1152 let caller = caller_id.clone();
1153 let cfg = Arc::clone(&self.cfg.configs);
1154 let server_state = Arc::clone(&self.state);
1155 let connection_span = info_span!("connection", caller_id);
1159 mz_ore::task::spawn(
1160 || format!("persist_pubsub_connection({})", caller),
1161 async move {
1162 let connection = server_state.new_connection(tx);
1163 while let Some(result) = in_stream.next().await {
1164 let req = match result {
1165 Ok(req) => req,
1166 Err(err) => {
1167 warn!("pubsub connection err: {}", err);
1168 break;
1169 }
1170 };
1171
1172 match req.message {
1173 None => {
1174 warn!("received empty message from: {}", caller_id);
1175 }
1176 Some(proto_pub_sub_message::Message::PushDiff(req)) => {
1177 let shard_id = req.shard_id.parse().expect("valid shard id");
1178 let diff = VersionedData {
1179 seqno: req.seqno.into_rust().expect("valid seqno"),
1180 data: req.diff.clone(),
1181 };
1182 if PUBSUB_PUSH_DIFF_ENABLED.get(&cfg) {
1183 connection.push_diff(&shard_id, &diff);
1184 }
1185 }
1186 Some(proto_pub_sub_message::Message::Subscribe(diff)) => {
1187 let shard_id = diff.shard_id.parse().expect("valid shard id");
1188 connection.subscribe(&shard_id);
1189 }
1190 Some(proto_pub_sub_message::Message::Unsubscribe(diff)) => {
1191 let shard_id = diff.shard_id.parse().expect("valid shard id");
1192 connection.unsubscribe(&shard_id);
1193 }
1194 }
1195 }
1196
1197 info!("Persist PubSub connection ended: {:?}", caller_id);
1198 }
1199 .instrument(connection_span),
1200 );
1201
1202 let out_stream: Self::PubSubStream = Box::pin(ReceiverStream::new(rx));
1203 Ok(Response::new(out_stream))
1204 }
1205}
1206
1207#[derive(Debug)]
1211pub(crate) struct PubSubConnection {
1212 connection_id: usize,
1213 notifier: Sender<Result<ProtoPubSubMessage, Status>>,
1214 state: Arc<PubSubState>,
1215}
1216
1217impl PubSubSenderInternal for PubSubConnection {
1218 fn push_diff(&self, shard_id: &ShardId, diff: &VersionedData) {
1219 self.state.push_diff(self.connection_id, shard_id, diff)
1220 }
1221
1222 fn subscribe(&self, shard_id: &ShardId) {
1223 self.state
1224 .subscribe(self.connection_id, self.notifier.clone(), shard_id)
1225 }
1226
1227 fn unsubscribe(&self, shard_id: &ShardId) {
1228 self.state.unsubscribe(self.connection_id, shard_id)
1229 }
1230}
1231
1232impl Drop for PubSubConnection {
1233 fn drop(&mut self) {
1234 self.state.remove_connection(self.connection_id)
1235 }
1236}
1237
1238#[cfg(test)]
1239mod pubsub_state {
1240 use std::str::FromStr;
1241 use std::sync::Arc;
1242 use std::sync::LazyLock;
1243
1244 use bytes::Bytes;
1245 use mz_ore::collections::HashSet;
1246 use mz_persist::location::{SeqNo, VersionedData};
1247 use mz_proto::RustType;
1248 use tokio::sync::mpsc::Receiver;
1249 use tokio::sync::mpsc::error::TryRecvError;
1250 use tonic::Status;
1251
1252 use crate::ShardId;
1253 use crate::internal::service::ProtoPubSubMessage;
1254 use crate::internal::service::proto_pub_sub_message::Message;
1255 use crate::rpc::{PubSubSenderInternal, PubSubState};
1256
1257 static SHARD_ID_0: LazyLock<ShardId> =
1258 LazyLock::new(|| ShardId::from_str("s00000000-0000-0000-0000-000000000000").unwrap());
1259 static SHARD_ID_1: LazyLock<ShardId> =
1260 LazyLock::new(|| ShardId::from_str("s11111111-1111-1111-1111-111111111111").unwrap());
1261
1262 const VERSIONED_DATA_0: VersionedData = VersionedData {
1263 seqno: SeqNo(0),
1264 data: Bytes::from_static(&[0, 1, 2, 3]),
1265 };
1266
1267 const VERSIONED_DATA_1: VersionedData = VersionedData {
1268 seqno: SeqNo(1),
1269 data: Bytes::from_static(&[4, 5, 6, 7]),
1270 };
1271
1272 #[mz_ore::test]
1273 #[should_panic(expected = "unknown connection id: 100")]
1274 fn test_zero_connections_push_diff() {
1275 let state = Arc::new(PubSubState::new_for_test());
1276 state.push_diff(100, &SHARD_ID_0, &VERSIONED_DATA_0);
1277 }
1278
1279 #[mz_ore::test]
1280 #[should_panic(expected = "unknown connection id: 100")]
1281 fn test_zero_connections_subscribe() {
1282 let state = Arc::new(PubSubState::new_for_test());
1283 let (tx, _) = tokio::sync::mpsc::channel(100);
1284 state.subscribe(100, tx, &SHARD_ID_0);
1285 }
1286
1287 #[mz_ore::test]
1288 #[should_panic(expected = "unknown connection id: 100")]
1289 fn test_zero_connections_unsubscribe() {
1290 let state = Arc::new(PubSubState::new_for_test());
1291 state.unsubscribe(100, &SHARD_ID_0);
1292 }
1293
1294 #[mz_ore::test]
1295 #[should_panic(expected = "unknown connection id: 100")]
1296 fn test_zero_connections_remove() {
1297 let state = Arc::new(PubSubState::new_for_test());
1298 state.remove_connection(100)
1299 }
1300
1301 #[mz_ore::test]
1302 fn test_single_connection() {
1303 let state = Arc::new(PubSubState::new_for_test());
1304
1305 let (tx, mut rx) = tokio::sync::mpsc::channel(100);
1306 let connection = Arc::clone(&state).new_connection(tx);
1307
1308 assert_eq!(
1309 state.active_connections(),
1310 HashSet::from([connection.connection_id])
1311 );
1312
1313 assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
1315
1316 connection.push_diff(
1317 &SHARD_ID_0,
1318 &VersionedData {
1319 seqno: SeqNo::minimum(),
1320 data: Bytes::new(),
1321 },
1322 );
1323
1324 assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
1326
1327 connection.subscribe(&SHARD_ID_0);
1329 assert_eq!(
1330 state.subscriptions(connection.connection_id),
1331 HashSet::from([SHARD_ID_0.clone()])
1332 );
1333
1334 connection.unsubscribe(&SHARD_ID_0);
1336 assert!(state.subscriptions(connection.connection_id).is_empty());
1337
1338 connection.subscribe(&SHARD_ID_0);
1340 connection.subscribe(&SHARD_ID_1);
1341 assert_eq!(
1342 state.subscriptions(connection.connection_id),
1343 HashSet::from([*SHARD_ID_0, *SHARD_ID_1])
1344 );
1345
1346 connection.subscribe(&SHARD_ID_0);
1348 connection.subscribe(&SHARD_ID_0);
1349 assert_eq!(
1350 state.subscriptions(connection.connection_id),
1351 HashSet::from([*SHARD_ID_0, *SHARD_ID_1])
1352 );
1353
1354 let connection_id = connection.connection_id;
1356 drop(connection);
1357 assert!(state.subscriptions(connection_id).is_empty());
1358 assert!(state.active_connections().is_empty());
1359 }
1360
1361 #[mz_ore::test]
1362 fn test_many_connection() {
1363 let state = Arc::new(PubSubState::new_for_test());
1364
1365 let (tx1, mut rx1) = tokio::sync::mpsc::channel(100);
1366 let conn1 = Arc::clone(&state).new_connection(tx1);
1367
1368 let (tx2, mut rx2) = tokio::sync::mpsc::channel(100);
1369 let conn2 = Arc::clone(&state).new_connection(tx2);
1370
1371 let (tx3, mut rx3) = tokio::sync::mpsc::channel(100);
1372 let conn3 = Arc::clone(&state).new_connection(tx3);
1373
1374 conn1.subscribe(&SHARD_ID_0);
1375 conn2.subscribe(&SHARD_ID_0);
1376 conn2.subscribe(&SHARD_ID_1);
1377
1378 assert_eq!(
1379 state.active_connections(),
1380 HashSet::from([
1381 conn1.connection_id,
1382 conn2.connection_id,
1383 conn3.connection_id
1384 ])
1385 );
1386
1387 conn3.push_diff(&SHARD_ID_0, &VERSIONED_DATA_0);
1389 assert_push(&mut rx1, &SHARD_ID_0, &VERSIONED_DATA_0);
1390 assert_push(&mut rx2, &SHARD_ID_0, &VERSIONED_DATA_0);
1391 assert!(matches!(rx3.try_recv(), Err(TryRecvError::Empty)));
1392
1393 conn1.push_diff(&SHARD_ID_0, &VERSIONED_DATA_0);
1395 assert!(matches!(rx1.try_recv(), Err(TryRecvError::Empty)));
1396 assert_push(&mut rx2, &SHARD_ID_0, &VERSIONED_DATA_0);
1397 assert!(matches!(rx3.try_recv(), Err(TryRecvError::Empty)));
1398
1399 conn3.push_diff(&SHARD_ID_1, &VERSIONED_DATA_1);
1401 assert!(matches!(rx1.try_recv(), Err(TryRecvError::Empty)));
1402 assert_push(&mut rx2, &SHARD_ID_1, &VERSIONED_DATA_1);
1403 assert!(matches!(rx3.try_recv(), Err(TryRecvError::Empty)));
1404
1405 conn2.unsubscribe(&SHARD_ID_1);
1407 conn3.push_diff(&SHARD_ID_1, &VERSIONED_DATA_1);
1408 assert!(matches!(rx1.try_recv(), Err(TryRecvError::Empty)));
1409 assert!(matches!(rx2.try_recv(), Err(TryRecvError::Empty)));
1410 assert!(matches!(rx3.try_recv(), Err(TryRecvError::Empty)));
1411
1412 let conn1_id = conn1.connection_id;
1414 drop(conn1);
1415 conn3.push_diff(&SHARD_ID_0, &VERSIONED_DATA_0);
1416 assert!(matches!(rx1.try_recv(), Err(TryRecvError::Disconnected)));
1417 assert_push(&mut rx2, &SHARD_ID_0, &VERSIONED_DATA_0);
1418 assert!(matches!(rx3.try_recv(), Err(TryRecvError::Empty)));
1419
1420 assert!(state.subscriptions(conn1_id).is_empty());
1421 assert_eq!(
1422 state.subscriptions(conn2.connection_id),
1423 HashSet::from([*SHARD_ID_0])
1424 );
1425 assert_eq!(state.subscriptions(conn3.connection_id), HashSet::new());
1426 assert_eq!(
1427 state.active_connections(),
1428 HashSet::from([conn2.connection_id, conn3.connection_id])
1429 );
1430 }
1431
1432 fn assert_push(
1433 rx: &mut Receiver<Result<ProtoPubSubMessage, Status>>,
1434 shard: &ShardId,
1435 data: &VersionedData,
1436 ) {
1437 let message = rx
1438 .try_recv()
1439 .expect("message in channel")
1440 .expect("pubsub")
1441 .message
1442 .expect("proto contains message");
1443 match message {
1444 Message::PushDiff(x) => {
1445 assert_eq!(x.shard_id, shard.into_proto());
1446 assert_eq!(x.seqno, data.seqno.into_proto());
1447 assert_eq!(x.diff, data.data);
1448 }
1449 Message::Subscribe(_) | Message::Unsubscribe(_) => panic!("unexpected message type"),
1450 };
1451 }
1452}
1453
1454#[cfg(test)]
1455mod grpc {
1456 use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
1457 use std::str::FromStr;
1458 use std::sync::Arc;
1459 use std::time::{Duration, Instant};
1460
1461 use bytes::Bytes;
1462 use futures_util::FutureExt;
1463 use mz_dyncfg::ConfigUpdates;
1464 use mz_ore::assert_none;
1465 use mz_ore::collections::HashMap;
1466 use mz_ore::metrics::MetricsRegistry;
1467 use mz_persist::location::{SeqNo, VersionedData};
1468 use mz_proto::RustType;
1469 use std::sync::LazyLock;
1470 use tokio::net::TcpListener;
1471 use tokio_stream::StreamExt;
1472 use tokio_stream::wrappers::TcpListenerStream;
1473
1474 use crate::ShardId;
1475 use crate::cfg::PersistConfig;
1476 use crate::internal::service::ProtoPubSubMessage;
1477 use crate::internal::service::proto_pub_sub_message::Message;
1478 use crate::metrics::Metrics;
1479 use crate::rpc::{
1480 GrpcPubSubClient, PUBSUB_CLIENT_ENABLED, PUBSUB_RECONNECT_BACKOFF, PersistGrpcPubSubServer,
1481 PersistPubSubClient, PersistPubSubClientConfig, PubSubState,
1482 };
1483
1484 static SHARD_ID_0: LazyLock<ShardId> =
1485 LazyLock::new(|| ShardId::from_str("s00000000-0000-0000-0000-000000000000").unwrap());
1486 static SHARD_ID_1: LazyLock<ShardId> =
1487 LazyLock::new(|| ShardId::from_str("s11111111-1111-1111-1111-111111111111").unwrap());
1488 const VERSIONED_DATA_0: VersionedData = VersionedData {
1489 seqno: SeqNo(0),
1490 data: Bytes::from_static(&[0, 1, 2, 3]),
1491 };
1492 const VERSIONED_DATA_1: VersionedData = VersionedData {
1493 seqno: SeqNo(1),
1494 data: Bytes::from_static(&[4, 5, 6, 7]),
1495 };
1496
1497 const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
1498 const SUBSCRIPTIONS_TIMEOUT: Duration = Duration::from_secs(3);
1499 const SERVER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(2);
1500
1501 #[mz_ore::test]
1506 #[cfg_attr(miri, ignore)] fn grpc_server() {
1508 let metrics = Arc::new(Metrics::new(
1509 &test_persist_config(),
1510 &MetricsRegistry::new(),
1511 ));
1512 let server_runtime = tokio::runtime::Runtime::new().expect("server runtime");
1513 let client_runtime = tokio::runtime::Runtime::new().expect("client runtime");
1514
1515 let (addr, tcp_listener_stream) = server_runtime.block_on(new_tcp_listener());
1517 let server_state = server_runtime.block_on(spawn_server(tcp_listener_stream));
1518
1519 {
1521 let _guard = client_runtime.enter();
1522 mz_ore::task::spawn(|| "client".to_string(), async move {
1523 let client = GrpcPubSubClient::connect(
1524 PersistPubSubClientConfig {
1525 url: format!("http://{}", addr),
1526 caller_id: "client".to_string(),
1527 persist_cfg: test_persist_config(),
1528 },
1529 metrics,
1530 );
1531 let _token = client.sender.subscribe(&SHARD_ID_0);
1532 tokio::time::sleep(Duration::MAX).await;
1533 });
1534 }
1535
1536 server_runtime.block_on(async {
1538 poll_until_true(CONNECT_TIMEOUT, || {
1539 server_state.active_connections().len() == 1
1540 })
1541 .await;
1542 poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1543 server_state.shard_subscription_counts() == HashMap::from([(*SHARD_ID_0, 1)])
1544 })
1545 .await
1546 });
1547
1548 client_runtime.shutdown_timeout(SERVER_SHUTDOWN_TIMEOUT);
1550
1551 server_runtime.block_on(async {
1553 poll_until_true(CONNECT_TIMEOUT, || {
1554 server_state.active_connections().is_empty()
1555 })
1556 .await;
1557 poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1558 server_state.shard_subscription_counts() == HashMap::new()
1559 })
1560 .await
1561 });
1562 }
1563
1564 #[mz_ore::test]
1565 #[cfg_attr(miri, ignore)] fn grpc_client_sender_reconnects() {
1567 let metrics = Arc::new(Metrics::new(
1568 &test_persist_config(),
1569 &MetricsRegistry::new(),
1570 ));
1571 let server_runtime = tokio::runtime::Runtime::new().expect("server runtime");
1572 let client_runtime = tokio::runtime::Runtime::new().expect("client runtime");
1573 let (addr, tcp_listener_stream) = server_runtime.block_on(new_tcp_listener());
1574
1575 let client = client_runtime.block_on(async {
1577 GrpcPubSubClient::connect(
1578 PersistPubSubClientConfig {
1579 url: format!("http://{}", addr),
1580 caller_id: "client".to_string(),
1581 persist_cfg: test_persist_config(),
1582 },
1583 metrics,
1584 )
1585 });
1586
1587 let _token = Arc::clone(&client.sender).subscribe(&SHARD_ID_0);
1589 let _token_2 = Arc::clone(&client.sender).subscribe(&SHARD_ID_1);
1591 drop(_token_2);
1592
1593 let server_state = server_runtime.block_on(spawn_server(tcp_listener_stream));
1595
1596 server_runtime.block_on(async {
1597 poll_until_true(CONNECT_TIMEOUT, || {
1599 server_state.active_connections().len() == 1
1600 })
1601 .await;
1602
1603 poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1606 server_state.shard_subscription_counts() == HashMap::from([(*SHARD_ID_0, 1)])
1607 })
1608 .await;
1609 });
1610
1611 server_runtime.shutdown_timeout(SERVER_SHUTDOWN_TIMEOUT);
1613
1614 let _token_2 = Arc::clone(&client.sender).subscribe(&SHARD_ID_1);
1616
1617 let server_runtime = tokio::runtime::Runtime::new().expect("server runtime");
1619 let tcp_listener_stream = server_runtime.block_on(async {
1620 TcpListenerStream::new(
1621 TcpListener::bind(addr)
1622 .await
1623 .expect("can bind to previous addr"),
1624 )
1625 });
1626 let server_state = server_runtime.block_on(spawn_server(tcp_listener_stream));
1627
1628 server_runtime.block_on(async {
1629 poll_until_true(CONNECT_TIMEOUT, || {
1631 server_state.active_connections().len() == 1
1632 })
1633 .await;
1634
1635 poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1638 server_state.shard_subscription_counts()
1639 == HashMap::from([(*SHARD_ID_0, 1), (*SHARD_ID_1, 1)])
1640 })
1641 .await;
1642 });
1643 }
1644
1645 #[mz_ore::test(tokio::test(flavor = "multi_thread"))]
1646 #[cfg_attr(miri, ignore)] async fn grpc_client_sender_subscription_tokens() {
1648 let metrics = Arc::new(Metrics::new(
1649 &test_persist_config(),
1650 &MetricsRegistry::new(),
1651 ));
1652
1653 let (addr, tcp_listener_stream) = new_tcp_listener().await;
1654 let server_state = spawn_server(tcp_listener_stream).await;
1655
1656 let client = GrpcPubSubClient::connect(
1657 PersistPubSubClientConfig {
1658 url: format!("http://{}", addr),
1659 caller_id: "client".to_string(),
1660 persist_cfg: test_persist_config(),
1661 },
1662 metrics,
1663 );
1664
1665 poll_until_true(CONNECT_TIMEOUT, || {
1667 server_state.active_connections().len() == 1
1668 })
1669 .await;
1670
1671 let token = Arc::clone(&client.sender).subscribe(&SHARD_ID_0);
1673 poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1674 server_state.shard_subscription_counts() == HashMap::from([(*SHARD_ID_0, 1)])
1675 })
1676 .await;
1677
1678 drop(token);
1680 poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1681 server_state.shard_subscription_counts() == HashMap::new()
1682 })
1683 .await;
1684
1685 let token = Arc::clone(&client.sender).subscribe(&SHARD_ID_0);
1687 poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1688 server_state.shard_subscription_counts() == HashMap::from([(*SHARD_ID_0, 1)])
1689 })
1690 .await;
1691
1692 let token2 = Arc::clone(&client.sender).subscribe(&SHARD_ID_0);
1694 let token3 = Arc::clone(&client.sender).subscribe(&SHARD_ID_0);
1695 assert_eq!(Arc::strong_count(&token), 3);
1696 poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1697 server_state.shard_subscription_counts() == HashMap::from([(*SHARD_ID_0, 1)])
1698 })
1699 .await;
1700
1701 drop(token);
1703 drop(token2);
1704 drop(token3);
1705 poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1706 server_state.shard_subscription_counts() == HashMap::new()
1707 })
1708 .await;
1709
1710 let _token0 = Arc::clone(&client.sender).subscribe(&SHARD_ID_0);
1712 let _token1 = Arc::clone(&client.sender).subscribe(&SHARD_ID_1);
1713 poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1714 server_state.shard_subscription_counts()
1715 == HashMap::from([(*SHARD_ID_0, 1), (*SHARD_ID_1, 1)])
1716 })
1717 .await;
1718 }
1719
1720 #[mz_ore::test]
1721 #[cfg_attr(miri, ignore)] fn grpc_client_receiver() {
1723 let metrics = Arc::new(Metrics::new(
1724 &PersistConfig::new_for_tests(),
1725 &MetricsRegistry::new(),
1726 ));
1727 let server_runtime = tokio::runtime::Runtime::new().expect("server runtime");
1728 let client_runtime = tokio::runtime::Runtime::new().expect("client runtime");
1729 let (addr, tcp_listener_stream) = server_runtime.block_on(new_tcp_listener());
1730
1731 let mut client_1 = client_runtime.block_on(async {
1733 GrpcPubSubClient::connect(
1734 PersistPubSubClientConfig {
1735 url: format!("http://{}", addr),
1736 caller_id: "client_1".to_string(),
1737 persist_cfg: test_persist_config(),
1738 },
1739 Arc::clone(&metrics),
1740 )
1741 });
1742 let mut client_2 = client_runtime.block_on(async {
1743 GrpcPubSubClient::connect(
1744 PersistPubSubClientConfig {
1745 url: format!("http://{}", addr),
1746 caller_id: "client_2".to_string(),
1747 persist_cfg: test_persist_config(),
1748 },
1749 metrics,
1750 )
1751 });
1752
1753 assert_none!(client_1.receiver.next().now_or_never());
1758 assert_none!(client_2.receiver.next().now_or_never());
1759
1760 let server_state = server_runtime.block_on(spawn_server(tcp_listener_stream));
1762
1763 server_runtime.block_on(poll_until_true(CONNECT_TIMEOUT, || {
1765 server_state.active_connections().len() == 2
1766 }));
1767
1768 assert_none!(client_1.receiver.next().now_or_never());
1770 assert_none!(client_2.receiver.next().now_or_never());
1771
1772 let _token_client_1 = Arc::clone(&client_1.sender).subscribe(&SHARD_ID_0);
1774 let _token_client_2 = Arc::clone(&client_2.sender).subscribe(&SHARD_ID_0);
1775 server_runtime.block_on(poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1776 server_state.shard_subscription_counts() == HashMap::from([(*SHARD_ID_0, 2)])
1777 }));
1778
1779 client_1.sender.push_diff(&SHARD_ID_0, &VERSIONED_DATA_1);
1781 assert_none!(client_1.receiver.next().now_or_never());
1782 client_runtime.block_on(async {
1783 assert_push(
1784 client_2.receiver.next().await.expect("has diff"),
1785 &SHARD_ID_0,
1786 &VERSIONED_DATA_1,
1787 )
1788 });
1789
1790 server_runtime.shutdown_timeout(SERVER_SHUTDOWN_TIMEOUT);
1792
1793 assert_none!(client_1.receiver.next().now_or_never());
1795 assert_none!(client_2.receiver.next().now_or_never());
1796
1797 let server_runtime = tokio::runtime::Runtime::new().expect("server runtime");
1799 let tcp_listener_stream = server_runtime.block_on(async {
1800 TcpListenerStream::new(
1801 TcpListener::bind(addr)
1802 .await
1803 .expect("can bind to previous addr"),
1804 )
1805 });
1806 let server_state = server_runtime.block_on(spawn_server(tcp_listener_stream));
1807
1808 server_runtime.block_on(async {
1810 poll_until_true(CONNECT_TIMEOUT, || {
1811 server_state.active_connections().len() == 2
1812 })
1813 .await;
1814 poll_until_true(SUBSCRIPTIONS_TIMEOUT, || {
1815 server_state.shard_subscription_counts() == HashMap::from([(*SHARD_ID_0, 2)])
1816 })
1817 .await;
1818 });
1819
1820 client_2.sender.push_diff(&SHARD_ID_0, &VERSIONED_DATA_0);
1823 client_runtime.block_on(async {
1824 assert_push(
1825 client_1.receiver.next().await.expect("has diff"),
1826 &SHARD_ID_0,
1827 &VERSIONED_DATA_0,
1828 )
1829 });
1830 assert_none!(client_2.receiver.next().now_or_never());
1831 }
1832
1833 async fn new_tcp_listener() -> (SocketAddr, TcpListenerStream) {
1834 let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0));
1835 let tcp_listener = TcpListener::bind(addr).await.expect("tcp listener");
1836
1837 (
1838 tcp_listener.local_addr().expect("bound to local address"),
1839 TcpListenerStream::new(tcp_listener),
1840 )
1841 }
1842
1843 #[allow(clippy::unused_async)]
1844 async fn spawn_server(tcp_listener_stream: TcpListenerStream) -> Arc<PubSubState> {
1845 let server = PersistGrpcPubSubServer::new(&test_persist_config(), &MetricsRegistry::new());
1846 let server_state = Arc::clone(&server.state);
1847
1848 let _server_task = mz_ore::task::spawn(|| "server".to_string(), async move {
1849 server.serve_with_stream(tcp_listener_stream).await
1850 });
1851 server_state
1852 }
1853
1854 async fn poll_until_true<F>(timeout: Duration, f: F)
1855 where
1856 F: Fn() -> bool,
1857 {
1858 let now = Instant::now();
1859 loop {
1860 if f() {
1861 return;
1862 }
1863
1864 if now.elapsed() > timeout {
1865 panic!("timed out");
1866 }
1867
1868 tokio::time::sleep(Duration::from_millis(1)).await;
1869 }
1870 }
1871
1872 fn assert_push(message: ProtoPubSubMessage, shard: &ShardId, data: &VersionedData) {
1873 let message = message.message.expect("proto contains message");
1874 match message {
1875 Message::PushDiff(x) => {
1876 assert_eq!(x.shard_id, shard.into_proto());
1877 assert_eq!(x.seqno, data.seqno.into_proto());
1878 assert_eq!(x.diff, data.data);
1879 }
1880 Message::Subscribe(_) | Message::Unsubscribe(_) => panic!("unexpected message type"),
1881 };
1882 }
1883
1884 fn test_persist_config() -> PersistConfig {
1885 let cfg = PersistConfig::new_for_tests();
1886
1887 let mut updates = ConfigUpdates::default();
1888 updates.add(&PUBSUB_CLIENT_ENABLED, true);
1889 updates.add(&PUBSUB_RECONNECT_BACKOFF, Duration::ZERO);
1890 cfg.apply_from(&updates);
1891
1892 cfg
1893 }
1894}