1use std::any::Any;
13use std::collections::BTreeMap;
14use std::collections::btree_map::Entry;
15use std::fmt::Debug;
16use std::future::Future;
17use std::sync::{Arc, RwLock, TryLockError, Weak};
18use std::time::{Duration, Instant};
19
20use differential_dataflow::difference::Monoid;
21use differential_dataflow::lattice::Lattice;
22use mz_dyncfg::Config;
23use mz_ore::instrument;
24use mz_ore::metrics::MetricsRegistry;
25use mz_ore::task::{AbortOnDropHandle, JoinHandle};
26use mz_ore::url::SensitiveUrl;
27use mz_persist::cfg::{BlobConfig, ConsensusConfig};
28use mz_persist::location::{
29 BLOB_GET_LIVENESS_KEY, Blob, CONSENSUS_HEAD_LIVENESS_KEY, Consensus, ExternalError, Tasked,
30 VersionedData,
31};
32use mz_persist_types::{Codec, Codec64};
33use timely::progress::Timestamp;
34use tokio::sync::{Mutex, OnceCell};
35use tracing::debug;
36
37use crate::async_runtime::IsolatedRuntime;
38use crate::error::{CodecConcreteType, CodecMismatch};
39use crate::internal::cache::BlobMemCache;
40use crate::internal::machine::retry_external;
41use crate::internal::metrics::{LockMetrics, Metrics, MetricsBlob, MetricsConsensus, ShardMetrics};
42use crate::internal::state::TypedState;
43use crate::internal::watch::{AwaitableState, StateWatchNotifier};
44use crate::rpc::{PubSubClientConnection, PubSubSender, ShardSubscriptionToken};
45use crate::schema::SchemaCacheMaps;
46use crate::{Diagnostics, PersistClient, PersistConfig, PersistLocation, ShardId};
47
48#[derive(Debug)]
57pub struct PersistClientCache {
58 pub cfg: PersistConfig,
60 pub(crate) metrics: Arc<Metrics>,
61 blob_by_uri: Mutex<BTreeMap<SensitiveUrl, (RttLatencyTask, Arc<dyn Blob>)>>,
62 consensus_by_uri: Mutex<BTreeMap<SensitiveUrl, (RttLatencyTask, Arc<dyn Consensus>)>>,
63 isolated_runtime: Arc<IsolatedRuntime>,
64 pub(crate) state_cache: Arc<StateCache>,
65 pubsub_sender: Arc<dyn PubSubSender>,
66 _pubsub_receiver_task: JoinHandle<()>,
67}
68
69#[derive(Debug)]
70struct RttLatencyTask(#[allow(dead_code)] AbortOnDropHandle<()>);
71
72impl PersistClientCache {
73 pub fn new<F>(cfg: PersistConfig, registry: &MetricsRegistry, pubsub: F) -> Self
75 where
76 F: FnOnce(&PersistConfig, Arc<Metrics>) -> PubSubClientConnection,
77 {
78 let metrics = Arc::new(Metrics::new(&cfg, registry));
79 let pubsub_client = pubsub(&cfg, Arc::clone(&metrics));
80
81 let state_cache = Arc::new(StateCache::new(
82 &cfg,
83 Arc::clone(&metrics),
84 Arc::clone(&pubsub_client.sender),
85 ));
86 let _pubsub_receiver_task = crate::rpc::subscribe_state_cache_to_pubsub(
87 Arc::clone(&state_cache),
88 pubsub_client.receiver,
89 );
90 let isolated_runtime =
91 IsolatedRuntime::new(registry, Some(cfg.isolated_runtime_worker_threads));
92
93 PersistClientCache {
94 cfg,
95 metrics,
96 blob_by_uri: Mutex::new(BTreeMap::new()),
97 consensus_by_uri: Mutex::new(BTreeMap::new()),
98 isolated_runtime: Arc::new(isolated_runtime),
99 state_cache,
100 pubsub_sender: pubsub_client.sender,
101 _pubsub_receiver_task,
102 }
103 }
104
105 pub fn new_no_metrics() -> Self {
108 Self::new(
109 PersistConfig::new_for_tests(),
110 &MetricsRegistry::new(),
111 |_, _| PubSubClientConnection::noop(),
112 )
113 }
114
115 #[cfg(feature = "turmoil")]
116 pub fn new_for_turmoil() -> Self {
121 use crate::rpc::NoopPubSubSender;
122
123 let cfg = PersistConfig::new_for_tests();
124 let metrics = Arc::new(Metrics::new(&cfg, &MetricsRegistry::new()));
125
126 let pubsub_sender: Arc<dyn PubSubSender> = Arc::new(NoopPubSubSender);
127 let _pubsub_receiver_task = mz_ore::task::spawn(|| "noop", async {});
128
129 let state_cache = Arc::new(StateCache::new(
130 &cfg,
131 Arc::clone(&metrics),
132 Arc::clone(&pubsub_sender),
133 ));
134 let isolated_runtime = IsolatedRuntime::new_disabled();
135
136 PersistClientCache {
137 cfg,
138 metrics,
139 blob_by_uri: Mutex::new(BTreeMap::new()),
140 consensus_by_uri: Mutex::new(BTreeMap::new()),
141 isolated_runtime: Arc::new(isolated_runtime),
142 state_cache,
143 pubsub_sender,
144 _pubsub_receiver_task,
145 }
146 }
147
148 pub fn cfg(&self) -> &PersistConfig {
150 &self.cfg
151 }
152
153 pub fn metrics(&self) -> &Arc<Metrics> {
155 &self.metrics
156 }
157
158 pub fn shard_metrics(&self, shard_id: &ShardId, name: &str) -> Arc<ShardMetrics> {
160 self.metrics.shards.shard(shard_id, name)
161 }
162
163 pub fn clear_state_cache(&mut self) {
167 self.state_cache = Arc::new(StateCache::new(
168 &self.cfg,
169 Arc::clone(&self.metrics),
170 Arc::clone(&self.pubsub_sender),
171 ))
172 }
173
174 #[instrument(level = "debug")]
179 pub async fn open(&self, location: PersistLocation) -> Result<PersistClient, ExternalError> {
180 let blob = self.open_blob(location.blob_uri).await?;
181 let consensus = self.open_consensus(location.consensus_uri).await?;
182 PersistClient::new(
183 self.cfg.clone(),
184 blob,
185 consensus,
186 Arc::clone(&self.metrics),
187 Arc::clone(&self.isolated_runtime),
188 Arc::clone(&self.state_cache),
189 Arc::clone(&self.pubsub_sender),
190 )
191 }
192
193 const PROMETHEUS_SCRAPE_INTERVAL: Duration = Duration::from_secs(60);
195
196 async fn open_consensus(
197 &self,
198 consensus_uri: SensitiveUrl,
199 ) -> Result<Arc<dyn Consensus>, ExternalError> {
200 let mut consensus_by_uri = self.consensus_by_uri.lock().await;
201 let consensus = match consensus_by_uri.entry(consensus_uri) {
202 Entry::Occupied(x) => Arc::clone(&x.get().1),
203 Entry::Vacant(x) => {
204 let consensus = ConsensusConfig::try_from(
207 x.key(),
208 Box::new(self.cfg.clone()),
209 self.metrics.postgres_consensus.clone(),
210 Arc::clone(&self.cfg().configs),
211 )?;
212 let consensus =
213 retry_external(&self.metrics.retries.external.consensus_open, || {
214 consensus.clone().open()
215 })
216 .await;
217 let consensus =
218 Arc::new(MetricsConsensus::new(consensus, Arc::clone(&self.metrics)));
219 let consensus = Arc::new(Tasked(consensus));
220 let task = consensus_rtt_latency_task(
221 Arc::clone(&consensus),
222 Arc::clone(&self.metrics),
223 Self::PROMETHEUS_SCRAPE_INTERVAL,
224 )
225 .await;
226 Arc::clone(
227 &x.insert((RttLatencyTask(task.abort_on_drop()), consensus))
228 .1,
229 )
230 }
231 };
232 Ok(consensus)
233 }
234
235 async fn open_blob(&self, blob_uri: SensitiveUrl) -> Result<Arc<dyn Blob>, ExternalError> {
236 let mut blob_by_uri = self.blob_by_uri.lock().await;
237 let blob = match blob_by_uri.entry(blob_uri) {
238 Entry::Occupied(x) => Arc::clone(&x.get().1),
239 Entry::Vacant(x) => {
240 let blob = BlobConfig::try_from(
243 x.key(),
244 Box::new(self.cfg.clone()),
245 self.metrics.s3_blob.clone(),
246 )
247 .await?;
248 let blob = retry_external(&self.metrics.retries.external.blob_open, || {
249 blob.clone().open()
250 })
251 .await;
252 let blob = Arc::new(MetricsBlob::new(blob, Arc::clone(&self.metrics)));
253 let blob = Arc::new(Tasked(blob));
254 let task = blob_rtt_latency_task(
255 Arc::clone(&blob),
256 Arc::clone(&self.metrics),
257 Self::PROMETHEUS_SCRAPE_INTERVAL,
258 )
259 .await;
260 let blob = BlobMemCache::new(&self.cfg, Arc::clone(&self.metrics), blob);
263 Arc::clone(&x.insert((RttLatencyTask(task.abort_on_drop()), blob)).1)
264 }
265 };
266 Ok(blob)
267 }
268}
269
270#[allow(clippy::unused_async)]
284async fn blob_rtt_latency_task(
285 blob: Arc<Tasked<MetricsBlob>>,
286 metrics: Arc<Metrics>,
287 measurement_interval: Duration,
288) -> JoinHandle<()> {
289 mz_ore::task::spawn(|| "persist::blob_rtt_latency", async move {
290 let mut next_measurement = tokio::time::Instant::now();
293 loop {
294 tokio::time::sleep_until(next_measurement).await;
295 let start = Instant::now();
296 match blob.get(BLOB_GET_LIVENESS_KEY).await {
297 Ok(_) => {
298 metrics.blob.rtt_latency.set(start.elapsed().as_secs_f64());
299 }
300 Err(_) => {
301 }
305 }
306 next_measurement = tokio::time::Instant::now() + measurement_interval;
307 }
308 })
309}
310
311#[allow(clippy::unused_async)]
325async fn consensus_rtt_latency_task(
326 consensus: Arc<Tasked<MetricsConsensus>>,
327 metrics: Arc<Metrics>,
328 measurement_interval: Duration,
329) -> JoinHandle<()> {
330 mz_ore::task::spawn(|| "persist::consensus_rtt_latency", async move {
331 let mut next_measurement = tokio::time::Instant::now();
334 loop {
335 tokio::time::sleep_until(next_measurement).await;
336 let start = Instant::now();
337 match consensus.head(CONSENSUS_HEAD_LIVENESS_KEY).await {
338 Ok(_) => {
339 metrics
340 .consensus
341 .rtt_latency
342 .set(start.elapsed().as_secs_f64());
343 }
344 Err(_) => {
345 }
349 }
350 next_measurement = tokio::time::Instant::now() + measurement_interval;
351 }
352 })
353}
354
355pub(crate) trait DynState: Debug + Send + Sync {
356 fn codecs(&self) -> (String, String, String, String, Option<CodecConcreteType>);
357 fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
358 fn push_diff(&self, diff: VersionedData);
359}
360
361impl<K, V, T, D> DynState for LockingTypedState<K, V, T, D>
362where
363 K: Codec,
364 V: Codec,
365 T: Timestamp + Lattice + Codec64 + Sync,
366 D: Codec64,
367{
368 fn codecs(&self) -> (String, String, String, String, Option<CodecConcreteType>) {
369 (
370 K::codec_name(),
371 V::codec_name(),
372 T::codec_name(),
373 D::codec_name(),
374 Some(CodecConcreteType(std::any::type_name::<(K, V, T, D)>())),
375 )
376 }
377
378 fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
379 self
380 }
381
382 fn push_diff(&self, diff: VersionedData) {
383 self.write_lock(&self.metrics.locks.applier_write, |state| {
384 let seqno_before = state.seqno;
385 state.apply_encoded_diffs(&self.cfg, &self.metrics, std::iter::once(&diff));
386 let seqno_after = state.seqno;
387 assert!(seqno_after >= seqno_before);
388
389 if seqno_before != seqno_after {
390 debug!(
391 "applied pushed diff {}. seqno {} -> {}.",
392 state.shard_id, seqno_before, state.seqno
393 );
394 self.shard_metrics.pubsub_push_diff_applied.inc();
395 } else {
396 debug!(
397 "failed to apply pushed diff {}. seqno {} vs diff {}",
398 state.shard_id, seqno_before, diff.seqno
399 );
400 if diff.seqno <= seqno_before {
401 self.shard_metrics.pubsub_push_diff_not_applied_stale.inc();
402 } else {
403 self.shard_metrics
404 .pubsub_push_diff_not_applied_out_of_order
405 .inc();
406 }
407 }
408 })
409 }
410}
411
412#[derive(Debug)]
423pub struct StateCache {
424 cfg: Arc<PersistConfig>,
425 pub(crate) metrics: Arc<Metrics>,
426 states: Arc<std::sync::Mutex<BTreeMap<ShardId, Arc<OnceCell<Weak<dyn DynState>>>>>>,
427 pubsub_sender: Arc<dyn PubSubSender>,
428}
429
430#[derive(Debug)]
431enum StateCacheInit {
432 Init(Arc<dyn DynState>),
433 NeedInit(Arc<OnceCell<Weak<dyn DynState>>>),
434}
435
436impl StateCache {
437 pub fn new(
439 cfg: &PersistConfig,
440 metrics: Arc<Metrics>,
441 pubsub_sender: Arc<dyn PubSubSender>,
442 ) -> Self {
443 StateCache {
444 cfg: Arc::new(cfg.clone()),
445 metrics,
446 states: Default::default(),
447 pubsub_sender,
448 }
449 }
450
451 #[cfg(test)]
452 pub(crate) fn new_no_metrics() -> Self {
453 Self::new(
454 &PersistConfig::new_for_tests(),
455 Arc::new(Metrics::new(
456 &PersistConfig::new_for_tests(),
457 &MetricsRegistry::new(),
458 )),
459 Arc::new(crate::rpc::NoopPubSubSender),
460 )
461 }
462
463 pub(crate) async fn get<K, V, T, D, F, InitFn>(
464 &self,
465 shard_id: ShardId,
466 mut init_fn: InitFn,
467 diagnostics: &Diagnostics,
468 ) -> Result<Arc<LockingTypedState<K, V, T, D>>, Box<CodecMismatch>>
469 where
470 K: Debug + Codec,
471 V: Debug + Codec,
472 T: Timestamp + Lattice + Codec64 + Sync,
473 D: Monoid + Codec64,
474 F: Future<Output = Result<TypedState<K, V, T, D>, Box<CodecMismatch>>>,
475 InitFn: FnMut() -> F,
476 {
477 loop {
478 let init = {
479 let mut states = self.states.lock().expect("lock poisoned");
480 let state = states.entry(shard_id).or_default();
481 match state.get() {
482 Some(once_val) => match once_val.upgrade() {
483 Some(x) => StateCacheInit::Init(x),
484 None => {
485 *state = Arc::new(OnceCell::new());
489 StateCacheInit::NeedInit(Arc::clone(state))
490 }
491 },
492 None => StateCacheInit::NeedInit(Arc::clone(state)),
493 }
494 };
495
496 let state = match init {
497 StateCacheInit::Init(x) => x,
498 StateCacheInit::NeedInit(init_once) => {
499 let mut did_init: Option<Arc<LockingTypedState<K, V, T, D>>> = None;
500 let state = init_once
501 .get_or_try_init::<Box<CodecMismatch>, _, _>(|| async {
502 let init_res = init_fn().await;
503 let state = Arc::new(LockingTypedState::new(
504 shard_id,
505 init_res?,
506 Arc::clone(&self.metrics),
507 Arc::clone(&self.cfg),
508 Arc::clone(&self.pubsub_sender).subscribe(&shard_id),
509 diagnostics,
510 ));
511 let ret = Arc::downgrade(&state);
512 did_init = Some(state);
513 let ret: Weak<dyn DynState> = ret;
514 Ok(ret)
515 })
516 .await?;
517 if let Some(x) = did_init {
518 return Ok(x);
522 }
523 let Some(state) = state.upgrade() else {
524 continue;
531 };
532 state
533 }
534 };
535
536 match Arc::clone(&state)
537 .as_any()
538 .downcast::<LockingTypedState<K, V, T, D>>()
539 {
540 Ok(x) => return Ok(x),
541 Err(_) => {
542 return Err(Box::new(CodecMismatch {
543 requested: (
544 K::codec_name(),
545 V::codec_name(),
546 T::codec_name(),
547 D::codec_name(),
548 Some(CodecConcreteType(std::any::type_name::<(K, V, T, D)>())),
549 ),
550 actual: state.codecs(),
551 }));
552 }
553 }
554 }
555 }
556
557 pub(crate) fn get_state_weak(&self, shard_id: &ShardId) -> Option<Weak<dyn DynState>> {
558 self.states
559 .lock()
560 .expect("lock")
561 .get(shard_id)
562 .and_then(|x| x.get())
563 .map(Weak::clone)
564 }
565
566 #[cfg(test)]
567 fn get_cached(&self, shard_id: &ShardId) -> Option<Arc<dyn DynState>> {
568 self.states
569 .lock()
570 .expect("lock")
571 .get(shard_id)
572 .and_then(|x| x.get())
573 .and_then(|x| x.upgrade())
574 }
575
576 #[cfg(test)]
577 fn initialized_count(&self) -> usize {
578 self.states
579 .lock()
580 .expect("lock")
581 .values()
582 .filter(|x| x.initialized())
583 .count()
584 }
585
586 #[cfg(test)]
587 fn strong_count(&self) -> usize {
588 self.states
589 .lock()
590 .expect("lock")
591 .values()
592 .filter(|x| x.get().map_or(false, |x| x.upgrade().is_some()))
593 .count()
594 }
595}
596
597pub(crate) struct LockingTypedState<K, V, T, D> {
601 shard_id: ShardId,
602 state: RwLock<TypedState<K, V, T, D>>,
603 notifier: StateWatchNotifier<T>,
604 cfg: Arc<PersistConfig>,
605 metrics: Arc<Metrics>,
606 shard_metrics: Arc<ShardMetrics>,
607 update_semaphore: AwaitableState<Option<tokio::time::Instant>>,
608 schema_cache: Arc<dyn Any + Send + Sync>,
611 _subscription_token: Arc<ShardSubscriptionToken>,
612}
613
614impl<K, V, T: Debug, D> Debug for LockingTypedState<K, V, T, D> {
615 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
616 let LockingTypedState {
617 shard_id,
618 state,
619 notifier,
620 cfg: _cfg,
621 metrics: _metrics,
622 shard_metrics: _shard_metrics,
623 update_semaphore: _,
624 schema_cache: _schema_cache,
625 _subscription_token,
626 } = self;
627 f.debug_struct("LockingTypedState")
628 .field("shard_id", shard_id)
629 .field("state", state)
630 .field("notifier", notifier)
631 .finish()
632 }
633}
634
635impl<K: Codec, V: Codec, T, D> LockingTypedState<K, V, T, D> {
636 fn new(
637 shard_id: ShardId,
638 initial_state: TypedState<K, V, T, D>,
639 metrics: Arc<Metrics>,
640 cfg: Arc<PersistConfig>,
641 subscription_token: Arc<ShardSubscriptionToken>,
642 diagnostics: &Diagnostics,
643 ) -> Self
644 where
645 T: Timestamp + Lattice + Codec64,
647 D: Codec64,
648 {
649 let notifier = StateWatchNotifier::new(Arc::clone(&metrics), initial_state.upper().clone());
650 Self {
651 shard_id,
652 notifier,
653 state: RwLock::new(initial_state),
654 cfg: Arc::clone(&cfg),
655 shard_metrics: metrics.shards.shard(&shard_id, &diagnostics.shard_name),
656 update_semaphore: AwaitableState::new(None),
657 schema_cache: Arc::new(SchemaCacheMaps::<K, V>::new(&metrics.schema)),
658 metrics,
659 _subscription_token: subscription_token,
660 }
661 }
662
663 pub(crate) fn schema_cache(&self) -> Arc<SchemaCacheMaps<K, V>> {
664 Arc::clone(&self.schema_cache)
665 .downcast::<SchemaCacheMaps<K, V>>()
666 .expect("K and V match")
667 }
668}
669
670pub(crate) const STATE_UPDATE_LEASE_TIMEOUT: Config<Duration> = Config::new(
671 "persist_state_update_lease_timeout",
672 Duration::from_secs(1),
673 "The amount of time for a command to wait for a previous command to finish before executing. \
674 (If zero, commands will not wait for others to complete.) Higher values reduce database contention \
675 at the cost of higher worst-case latencies for individual requests.",
676);
677
678impl<K, V, T, D> LockingTypedState<K, V, T, D> {
679 pub(crate) fn shard_id(&self) -> &ShardId {
680 &self.shard_id
681 }
682
683 pub(crate) fn read_lock<R, F: FnMut(&TypedState<K, V, T, D>) -> R>(
684 &self,
685 metrics: &LockMetrics,
686 mut f: F,
687 ) -> R {
688 metrics.acquire_count.inc();
689 let state = match self.state.try_read() {
690 Ok(x) => x,
691 Err(TryLockError::WouldBlock) => {
692 metrics.blocking_acquire_count.inc();
693 let start = Instant::now();
694 let state = self.state.read().expect("lock poisoned");
695 metrics
696 .blocking_seconds
697 .inc_by(start.elapsed().as_secs_f64());
698 state
699 }
700 Err(TryLockError::Poisoned(err)) => panic!("state read lock poisoned: {}", err),
701 };
702 f(&state)
703 }
704
705 pub(crate) fn write_lock<R, F>(&self, metrics: &LockMetrics, f: F) -> R
706 where
707 F: FnOnce(&mut TypedState<K, V, T, D>) -> R,
708 K: Codec,
711 V: Codec,
712 T: Timestamp + Lattice + Codec64,
713 D: Codec64,
714 {
715 metrics.acquire_count.inc();
716 let mut state = match self.state.try_write() {
717 Ok(x) => x,
718 Err(TryLockError::WouldBlock) => {
719 metrics.blocking_acquire_count.inc();
720 let start = Instant::now();
721 let state = self.state.write().expect("lock poisoned");
722 metrics
723 .blocking_seconds
724 .inc_by(start.elapsed().as_secs_f64());
725 state
726 }
727 Err(TryLockError::Poisoned(err)) => panic!("state read lock poisoned: {}", err),
728 };
729 let seqno_before = state.seqno;
730 let ret = f(&mut state);
731 let seqno_after = state.seqno;
732 debug_assert!(seqno_after >= seqno_before);
733 if seqno_after > seqno_before {
734 self.notifier.notify(seqno_after, state.upper());
739 }
740 drop(state);
743 ret
744 }
745
746 pub(crate) async fn lease_for_update(&self) -> impl Drop {
754 use tokio::time::Instant;
755
756 let timeout = STATE_UPDATE_LEASE_TIMEOUT.get(&self.cfg);
757
758 struct DropLease(Option<(AwaitableState<Option<Instant>>, Instant)>);
759
760 impl Drop for DropLease {
761 fn drop(&mut self) {
762 if let Some((state, time)) = self.0.take() {
763 state.maybe_modify(|s| {
765 if s.is_some_and(|t| t == time) {
766 *s.get_mut() = None;
767 }
768 })
769 }
770 }
771 }
772
773 if timeout.is_zero() {
775 return DropLease(None);
776 }
777
778 let timeout_state = self.update_semaphore.clone();
779 loop {
780 let now = tokio::time::Instant::now();
781 let expires_at = now + timeout;
782 let maybe_leased = timeout_state.maybe_modify(|state| {
784 if let Some(other_expires_at) = **state
785 && other_expires_at > now
786 {
787 Err(other_expires_at)
789 } else {
790 *state.get_mut() = Some(expires_at);
791 Ok(())
792 }
793 });
794
795 match maybe_leased {
796 Ok(()) => {
797 break DropLease(Some((timeout_state, expires_at)));
798 }
799 Err(other_expires_at) => {
800 let _ = tokio::time::timeout_at(
805 other_expires_at,
806 timeout_state.wait_while(|s| s.is_some()),
807 )
808 .await;
809 }
810 }
811 }
812 }
813
814 pub(crate) fn notifier(&self) -> &StateWatchNotifier<T> {
815 &self.notifier
816 }
817}
818
819#[cfg(test)]
820mod tests {
821 use std::ops::Deref;
822 use std::pin::pin;
823 use std::str::FromStr;
824 use std::sync::atomic::{AtomicBool, Ordering};
825
826 use super::*;
827 use crate::rpc::NoopPubSubSender;
828 use futures::stream::{FuturesUnordered, StreamExt};
829 use mz_build_info::DUMMY_BUILD_INFO;
830 use mz_ore::task::spawn;
831 use mz_ore::{assert_err, assert_none};
832 use tokio::sync::oneshot;
833
834 #[mz_ore::test(tokio::test)]
835 #[cfg_attr(miri, ignore)] async fn client_cache() {
837 let cache = PersistClientCache::new(
838 PersistConfig::new_for_tests(),
839 &MetricsRegistry::new(),
840 |_, _| PubSubClientConnection::noop(),
841 );
842 assert_eq!(cache.blob_by_uri.lock().await.len(), 0);
843 assert_eq!(cache.consensus_by_uri.lock().await.len(), 0);
844
845 let _ = cache
847 .open(PersistLocation {
848 blob_uri: SensitiveUrl::from_str("mem://blob_zero").expect("invalid URL"),
849 consensus_uri: SensitiveUrl::from_str("mem://consensus_zero").expect("invalid URL"),
850 })
851 .await
852 .expect("failed to open location");
853 assert_eq!(cache.blob_by_uri.lock().await.len(), 1);
854 assert_eq!(cache.consensus_by_uri.lock().await.len(), 1);
855
856 let _ = cache
859 .open(PersistLocation {
860 blob_uri: SensitiveUrl::from_str("mem://blob_one").expect("invalid URL"),
861 consensus_uri: SensitiveUrl::from_str("mem://consensus_zero").expect("invalid URL"),
862 })
863 .await
864 .expect("failed to open location");
865 assert_eq!(cache.blob_by_uri.lock().await.len(), 2);
866 assert_eq!(cache.consensus_by_uri.lock().await.len(), 1);
867
868 let _ = cache
870 .open(PersistLocation {
871 blob_uri: SensitiveUrl::from_str("mem://blob_one").expect("invalid URL"),
872 consensus_uri: SensitiveUrl::from_str("mem://consensus_one").expect("invalid URL"),
873 })
874 .await
875 .expect("failed to open location");
876 assert_eq!(cache.blob_by_uri.lock().await.len(), 2);
877 assert_eq!(cache.consensus_by_uri.lock().await.len(), 2);
878
879 let _ = cache
881 .open(PersistLocation {
882 blob_uri: SensitiveUrl::from_str("mem://blob_one?foo").expect("invalid URL"),
883 consensus_uri: SensitiveUrl::from_str("mem://consensus_one/bar")
884 .expect("invalid URL"),
885 })
886 .await
887 .expect("failed to open location");
888 assert_eq!(cache.blob_by_uri.lock().await.len(), 3);
889 assert_eq!(cache.consensus_by_uri.lock().await.len(), 3);
890
891 let _ = cache
893 .open(PersistLocation {
894 blob_uri: SensitiveUrl::from_str("mem://user@blob_one").expect("invalid URL"),
895 consensus_uri: SensitiveUrl::from_str("mem://@consensus_one:123")
896 .expect("invalid URL"),
897 })
898 .await
899 .expect("failed to open location");
900 assert_eq!(cache.blob_by_uri.lock().await.len(), 4);
901 assert_eq!(cache.consensus_by_uri.lock().await.len(), 4);
902 }
903
904 #[mz_ore::test(tokio::test)]
905 #[cfg_attr(miri, ignore)] async fn state_cache() {
907 mz_ore::test::init_logging();
908 fn new_state<K, V, T, D>(shard_id: ShardId) -> TypedState<K, V, T, D>
909 where
910 K: Codec,
911 V: Codec,
912 T: Timestamp + Lattice + Codec64,
913 D: Codec64,
914 {
915 TypedState::new(
916 DUMMY_BUILD_INFO.semver_version(),
917 shard_id,
918 "host".into(),
919 0,
920 )
921 }
922 fn assert_same<K, V, T, D>(
923 state1: &LockingTypedState<K, V, T, D>,
924 state2: &LockingTypedState<K, V, T, D>,
925 ) {
926 let pointer1 = format!("{:p}", state1.state.read().expect("lock").deref());
927 let pointer2 = format!("{:p}", state2.state.read().expect("lock").deref());
928 assert_eq!(pointer1, pointer2);
929 }
930
931 let s1 = ShardId::new();
932 let states = Arc::new(StateCache::new_no_metrics());
933
934 assert_eq!(states.states.lock().expect("lock").len(), 0);
936
937 let s = Arc::clone(&states);
939 let res = spawn(|| "test", async move {
940 s.get::<(), (), u64, i64, _, _>(
941 s1,
942 || async { panic!("forced panic") },
943 &Diagnostics::for_tests(),
944 )
945 .await
946 })
947 .into_tokio_handle()
948 .await;
949 assert_err!(res);
950 assert_eq!(states.initialized_count(), 0);
951
952 let res = states
954 .get::<(), (), u64, i64, _, _>(
955 s1,
956 || async {
957 Err(Box::new(CodecMismatch {
958 requested: ("".into(), "".into(), "".into(), "".into(), None),
959 actual: ("".into(), "".into(), "".into(), "".into(), None),
960 }))
961 },
962 &Diagnostics::for_tests(),
963 )
964 .await;
965 assert_err!(res);
966 assert_eq!(states.initialized_count(), 0);
967
968 let did_work = Arc::new(AtomicBool::new(false));
970 let s1_state1 = states
971 .get::<(), (), u64, i64, _, _>(
972 s1,
973 || {
974 let did_work = Arc::clone(&did_work);
975 async move {
976 did_work.store(true, Ordering::SeqCst);
977 Ok(new_state(s1))
978 }
979 },
980 &Diagnostics::for_tests(),
981 )
982 .await
983 .expect("should successfully initialize");
984 assert_eq!(did_work.load(Ordering::SeqCst), true);
985 assert_eq!(states.initialized_count(), 1);
986 assert_eq!(states.strong_count(), 1);
987
988 let did_work = Arc::new(AtomicBool::new(false));
990 let s1_state2 = states
991 .get::<(), (), u64, i64, _, _>(
992 s1,
993 || {
994 let did_work = Arc::clone(&did_work);
995 async move {
996 did_work.store(true, Ordering::SeqCst);
997 did_work.store(true, Ordering::SeqCst);
998 Ok(new_state(s1))
999 }
1000 },
1001 &Diagnostics::for_tests(),
1002 )
1003 .await
1004 .expect("should successfully initialize");
1005 assert_eq!(did_work.load(Ordering::SeqCst), false);
1006 assert_eq!(states.initialized_count(), 1);
1007 assert_eq!(states.strong_count(), 1);
1008 assert_same(&s1_state1, &s1_state2);
1009
1010 let did_work = Arc::new(AtomicBool::new(false));
1012 let res = states
1013 .get::<String, (), u64, i64, _, _>(
1014 s1,
1015 || {
1016 let did_work = Arc::clone(&did_work);
1017 async move {
1018 did_work.store(true, Ordering::SeqCst);
1019 Ok(new_state(s1))
1020 }
1021 },
1022 &Diagnostics::for_tests(),
1023 )
1024 .await;
1025 assert_eq!(did_work.load(Ordering::SeqCst), false);
1026 assert_eq!(
1027 format!("{}", res.expect_err("types shouldn't match")),
1028 "requested codecs (\"String\", \"()\", \"u64\", \"i64\", Some(CodecConcreteType(\"(alloc::string::String, (), u64, i64)\"))) did not match ones in durable storage (\"()\", \"()\", \"u64\", \"i64\", Some(CodecConcreteType(\"((), (), u64, i64)\")))"
1029 );
1030 assert_eq!(states.initialized_count(), 1);
1031 assert_eq!(states.strong_count(), 1);
1032
1033 let s2 = ShardId::new();
1035 let s2_state1 = states
1036 .get::<String, (), u64, i64, _, _>(
1037 s2,
1038 || async { Ok(new_state(s2)) },
1039 &Diagnostics::for_tests(),
1040 )
1041 .await
1042 .expect("should successfully initialize");
1043 assert_eq!(states.initialized_count(), 2);
1044 assert_eq!(states.strong_count(), 2);
1045 let s2_state2 = states
1046 .get::<String, (), u64, i64, _, _>(
1047 s2,
1048 || async { Ok(new_state(s2)) },
1049 &Diagnostics::for_tests(),
1050 )
1051 .await
1052 .expect("should successfully initialize");
1053 assert_same(&s2_state1, &s2_state2);
1054
1055 drop(s1_state1);
1058 assert_eq!(states.strong_count(), 2);
1059 drop(s1_state2);
1060 assert_eq!(states.strong_count(), 1);
1061 assert_eq!(states.initialized_count(), 2);
1062 assert_none!(states.get_cached(&s1));
1063
1064 let s1_state1 = states
1066 .get::<(), (), u64, i64, _, _>(
1067 s1,
1068 || async { Ok(new_state(s1)) },
1069 &Diagnostics::for_tests(),
1070 )
1071 .await
1072 .expect("should successfully initialize");
1073 assert_eq!(states.initialized_count(), 2);
1074 assert_eq!(states.strong_count(), 2);
1075 drop(s1_state1);
1076 assert_eq!(states.strong_count(), 1);
1077 }
1078
1079 #[mz_ore::test(tokio::test(flavor = "multi_thread"))]
1080 #[cfg_attr(miri, ignore)] async fn state_cache_concurrency() {
1082 mz_ore::test::init_logging();
1083
1084 const COUNT: usize = 1000;
1085 let id = ShardId::new();
1086 let cache = StateCache::new_no_metrics();
1087 let diagnostics = Diagnostics::for_tests();
1088
1089 let mut futures = (0..COUNT)
1090 .map(|_| {
1091 cache.get::<(), (), u64, i64, _, _>(
1092 id,
1093 || async {
1094 Ok(TypedState::new(
1095 DUMMY_BUILD_INFO.semver_version(),
1096 id,
1097 "host".into(),
1098 0,
1099 ))
1100 },
1101 &diagnostics,
1102 )
1103 })
1104 .collect::<FuturesUnordered<_>>();
1105
1106 for _ in 0..COUNT {
1107 let _ = futures.next().await.unwrap();
1108 }
1109 }
1110
1111 #[mz_ore::test(tokio::test)]
1112 #[cfg_attr(miri, ignore)] async fn update_semaphore() {
1114 mz_ore::test::init_logging();
1117
1118 let shard_id = ShardId::new();
1119 let persist_config = Arc::new(PersistConfig::new_for_tests());
1120 let pubsub = Arc::new(NoopPubSubSender);
1121 let state: LockingTypedState<String, (), u64, i64> = LockingTypedState::new(
1122 shard_id,
1123 TypedState::new(
1124 DUMMY_BUILD_INFO.semver_version(),
1125 shard_id,
1126 "host".into(),
1127 0,
1128 ),
1129 Arc::new(Metrics::new(&*persist_config, &MetricsRegistry::new())),
1130 persist_config,
1131 pubsub.subscribe(&shard_id),
1132 &Diagnostics::for_tests(),
1133 );
1134
1135 let mk_future = || {
1138 let (tx, rx) = oneshot::channel();
1139 let future = async {
1140 let lease = state.lease_for_update().await;
1141 let () = rx.await.unwrap();
1142 drop(lease);
1143 };
1144 (future, tx)
1145 };
1146
1147 let (one, _one_tx) = mk_future();
1148 let (two, _two_tx) = mk_future();
1149 let (three, three_tx) = mk_future();
1150 let mut one = pin!(one);
1151 let mut two = pin!(two);
1152 let mut three = pin!(three);
1153
1154 tokio::select! { biased;
1156 _ = &mut one => { unreachable!() }
1157 _ = &mut two => { unreachable!() }
1158 _ = &mut three => { unreachable!() }
1159 _ = async {} => {}
1160 }
1161
1162 three_tx.send(()).unwrap();
1164
1165 tokio::select! { biased;
1168 _ = &mut one => { unreachable!() }
1169 _ = &mut three => { }
1170 }
1171 }
1172
1173 #[mz_ore::test(tokio::test(flavor = "multi_thread"))]
1174 #[cfg_attr(miri, ignore)] async fn update_semaphore_stress() {
1176 mz_ore::test::init_logging();
1179
1180 const TIMEOUT: Duration = Duration::from_millis(100);
1181 const COUNT: u64 = 100;
1182
1183 let shard_id = ShardId::new();
1184 let persist_config = Arc::new(PersistConfig::new_for_tests());
1185 persist_config.set_config(&STATE_UPDATE_LEASE_TIMEOUT, TIMEOUT);
1186 let pubsub = Arc::new(NoopPubSubSender);
1187 let state: LockingTypedState<String, (), u64, i64> = LockingTypedState::new(
1188 shard_id,
1189 TypedState::new(
1190 DUMMY_BUILD_INFO.semver_version(),
1191 shard_id,
1192 "host".into(),
1193 0,
1194 ),
1195 Arc::new(Metrics::new(&*persist_config, &MetricsRegistry::new())),
1196 persist_config,
1197 pubsub.subscribe(&shard_id),
1198 &Diagnostics::for_tests(),
1199 );
1200
1201 let mut futures = (0..(COUNT * 3))
1202 .map(async |i| {
1203 state.lease_for_update().await;
1204 match i % 3 {
1206 0 => {
1207 let () = std::future::pending().await;
1208 }
1209 1 => {
1210 tokio::time::sleep(Duration::from_millis(i)).await;
1211 }
1212 _ => {
1213 tokio::time::sleep(Duration::from_millis(i) + TIMEOUT).await;
1214 }
1215 }
1216 })
1217 .collect::<FuturesUnordered<_>>();
1218
1219 for _ in 0..(COUNT * 2) {
1221 futures.next().await.unwrap();
1222 }
1223 }
1224}