1use differential_dataflow::lattice::Lattice;
13use mz_persist::location::SeqNo;
14use std::fmt::{Debug, Formatter};
15use std::ops::Deref;
16use std::sync::{Arc, RwLock};
17use timely::PartialOrder;
18use timely::progress::{Antichain, Timestamp};
19use tokio::sync::{Notify, broadcast, watch};
20use tracing::debug;
21
22use crate::cache::LockingTypedState;
23use crate::internal::metrics::Metrics;
24
25#[derive(Debug)]
26pub struct StateWatchNotifier<T> {
27 metrics: Arc<Metrics>,
28 seqno_tx: broadcast::Sender<SeqNo>,
31 upper_tx: watch::Sender<Antichain<T>>,
37}
38
39impl<T: Timestamp + Lattice> StateWatchNotifier<T> {
40 pub(crate) fn new(metrics: Arc<Metrics>, initial_upper: Antichain<T>) -> Self {
41 let (seqno_tx, _rx) = broadcast::channel(1);
42 let (upper_tx, _rx) = watch::channel(initial_upper);
43 StateWatchNotifier {
44 metrics,
45 seqno_tx,
46 upper_tx,
47 }
48 }
49
50 pub(crate) fn notify(&self, seqno: SeqNo, upper: &Antichain<T>) {
65 match self.seqno_tx.send(seqno) {
66 Ok(_) => {
68 self.metrics.watch.notify_sent.inc();
69 }
70 Err(_) => {
72 self.metrics.watch.notify_noop.inc();
73 }
74 }
75 let upper_advanced = self.upper_tx.send_if_modified(|current| {
79 if PartialOrder::less_than(current, upper) {
80 current.clone_from(upper);
81 true
82 } else {
83 false
84 }
85 });
86 if upper_advanced {
87 self.metrics.watch.notify_upper_sent.inc();
88 }
89 }
90}
91
92#[derive(Debug)]
110pub struct StateWatch<K, V, T, D> {
111 metrics: Arc<Metrics>,
112 state: Arc<LockingTypedState<K, V, T, D>>,
113 seqno_high_water: SeqNo,
114 seqno_rx: broadcast::Receiver<SeqNo>,
115 upper_rx: watch::Receiver<Antichain<T>>,
116}
117
118impl<K, V, T, D> StateWatch<K, V, T, D> {
119 pub(crate) fn new(state: Arc<LockingTypedState<K, V, T, D>>, metrics: Arc<Metrics>) -> Self {
120 let seqno_rx = state.notifier().seqno_tx.subscribe();
131 let upper_rx = state.notifier().upper_tx.subscribe();
132 let seqno_high_water = state.read_lock(&metrics.locks.watch, |x| x.seqno);
133 StateWatch {
134 metrics,
135 state,
136 seqno_high_water,
137 seqno_rx,
138 upper_rx,
139 }
140 }
141
142 pub async fn wait_for_seqno_ge(&mut self, requested: SeqNo) -> &mut Self {
146 self.metrics.watch.notify_wait_started.inc();
147 debug!("wait_for_seqno_ge {} {}", self.state.shard_id(), requested);
148 loop {
149 if self.seqno_high_water >= requested {
150 break;
151 }
152 match self.seqno_rx.recv().await {
153 Ok(x) => {
154 self.metrics.watch.notify_recv.inc();
155 assert!(x >= self.seqno_high_water);
156 self.seqno_high_water = x;
157 }
158 Err(broadcast::error::RecvError::Closed) => {
159 unreachable!("we're holding on to a reference to the sender")
160 }
161 Err(broadcast::error::RecvError::Lagged(_)) => {
162 self.metrics.watch.notify_lagged.inc();
163 continue;
169 }
170 }
171 }
172 self.metrics.watch.notify_wait_finished.inc();
173 debug!(
174 "wait_for_seqno_ge {} {} returning",
175 self.state.shard_id(),
176 requested
177 );
178 self
179 }
180
181 pub async fn wait_for_upper_past(&mut self, frontier: &Antichain<T>) -> &mut Self
188 where
189 T: Timestamp,
190 {
191 self.metrics.watch.notify_wait_started.inc();
192 debug!(
193 "wait_for_upper_past {} {:?}",
194 self.state.shard_id(),
195 frontier.elements()
196 );
197 loop {
198 let past = PartialOrder::less_than(frontier, &*self.upper_rx.borrow_and_update());
200 if past {
201 break;
202 }
203 self.upper_rx
206 .changed()
207 .await
208 .expect("notifier sender outlives the watch");
209 self.metrics.watch.notify_recv.inc();
210 }
211 self.metrics.watch.notify_wait_finished.inc();
212 debug!(
213 "wait_for_upper_past {} {:?} returning",
214 self.state.shard_id(),
215 frontier.elements()
216 );
217 self
218 }
219}
220
221pub(crate) struct AwaitableState<T> {
227 state: Arc<RwLock<T>>,
228 notify: Arc<Notify>,
231}
232
233impl<T: Debug> Debug for AwaitableState<T> {
234 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
235 self.state.read().fmt(f)
236 }
237}
238
239impl<T> Clone for AwaitableState<T> {
240 fn clone(&self) -> Self {
241 Self {
242 state: Arc::clone(&self.state),
243 notify: Arc::clone(&self.notify),
244 }
245 }
246}
247
248pub struct ModifyGuard<'a, T> {
251 mut_ref: &'a mut T,
252 modified: bool,
253}
254
255impl<'a, T> Deref for ModifyGuard<'a, T> {
256 type Target = T;
257
258 fn deref(&self) -> &Self::Target {
259 &*self.mut_ref
260 }
261}
262
263impl<'a, T> ModifyGuard<'a, T> {
264 pub fn get_mut(&mut self) -> &mut T {
265 self.modified = true;
266 &mut *self.mut_ref
267 }
268}
269
270impl<T> AwaitableState<T> {
271 pub fn new(value: T) -> Self {
272 Self {
273 state: Arc::new(RwLock::new(value)),
274 notify: Arc::new(Notify::new()),
275 }
276 }
277
278 #[allow(dead_code)]
279 pub fn read<A>(&self, read_fn: impl FnOnce(&T) -> A) -> A {
280 let guard = self.state.read().expect("not poisoned");
281 let state = &*guard;
282 read_fn(state)
283 }
284
285 pub fn maybe_modify<A>(&self, write_fn: impl FnOnce(&mut ModifyGuard<T>) -> A) -> A {
289 let mut guard = self.state.write().expect("not poisoned");
290 let mut state = ModifyGuard {
291 mut_ref: &mut *guard,
292 modified: false,
293 };
294 let result = write_fn(&mut state);
295 if state.modified {
298 self.notify.notify_waiters();
299 }
300 drop(guard);
301 result
302 }
303
304 pub fn modify<A>(&self, write_fn: impl FnOnce(&mut T) -> A) -> A {
305 self.maybe_modify(|guard| write_fn(guard.get_mut()))
306 }
307
308 pub async fn wait_for<A>(&self, mut wait_fn: impl FnMut(&T) -> Option<A>) -> A {
309 loop {
310 let notified = {
311 let guard = self.state.read().expect("not poisoned");
312 let state = &*guard;
313 if let Some(result) = wait_fn(state) {
314 return result;
315 }
316 let notified = self.notify.notified();
319 drop(guard);
320 notified
321 };
322
323 notified.await;
324 }
325 }
326
327 pub async fn wait_while(&self, mut wait_fn: impl FnMut(&T) -> bool) {
328 self.wait_for(|s| (!wait_fn(s)).then_some(())).await
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use std::future::Future;
335 use std::pin::Pin;
336 use std::sync::atomic::{AtomicUsize, Ordering};
337 use std::task::{Context, Poll, Wake, Waker};
338 use std::time::Duration;
339
340 use futures::FutureExt;
341 use futures_task::noop_waker;
342 use itertools::Itertools;
343 use mz_build_info::DUMMY_BUILD_INFO;
344 use mz_dyncfg::ConfigUpdates;
345 use mz_ore::assert_none;
346 use mz_ore::cast::CastFrom;
347 use mz_ore::metrics::MetricsRegistry;
348 use rand::prelude::SliceRandom;
349 use timely::progress::Antichain;
350 use tokio::task::JoinSet;
351
352 use crate::cache::StateCache;
353 use crate::cfg::PersistConfig;
354 use crate::internal::machine::{
355 NEXT_LISTEN_BATCH_RETRYER_CLAMP, NEXT_LISTEN_BATCH_RETRYER_INITIAL_BACKOFF,
356 NEXT_LISTEN_BATCH_RETRYER_MULTIPLIER,
357 };
358 use crate::internal::state::TypedState;
359 use crate::tests::new_test_client;
360 use crate::{Diagnostics, ShardId};
361
362 use super::*;
363
364 #[mz_ore::test(tokio::test)]
365 async fn state_watch() {
366 mz_ore::test::init_logging();
367 let metrics = Arc::new(Metrics::new(
368 &PersistConfig::new_for_tests(),
369 &MetricsRegistry::new(),
370 ));
371 let cache = StateCache::new_no_metrics();
372 let shard_id = ShardId::new();
373 let state = cache
374 .get::<(), (), u64, i64, _, _>(
375 shard_id,
376 || async {
377 Ok(TypedState::new(
378 DUMMY_BUILD_INFO.semver_version(),
379 shard_id,
380 "host".to_owned(),
381 0u64,
382 ))
383 },
384 &Diagnostics::for_tests(),
385 )
386 .await
387 .unwrap();
388 assert_eq!(state.read_lock(&metrics.locks.watch, |x| x.seqno), SeqNo(0));
389
390 let mut w0 = StateWatch::new(Arc::clone(&state), Arc::clone(&metrics));
392 let _ = w0.wait_for_seqno_ge(SeqNo(0)).await;
393
394 let w0s1 = w0.wait_for_seqno_ge(SeqNo(1)).map(|_| ()).shared();
396 assert_eq!(w0s1.clone().now_or_never(), None);
397
398 state.write_lock(&metrics.locks.applier_write, |state| {
400 state.seqno = state.seqno.next()
401 });
402 let () = w0s1.await;
403
404 let _ = w0.wait_for_seqno_ge(SeqNo(0)).await;
406
407 let mut w1 = StateWatch::new(Arc::clone(&state), Arc::clone(&metrics));
409 let _ = w1.wait_for_seqno_ge(SeqNo(0)).await;
410 let _ = w1.wait_for_seqno_ge(SeqNo(1)).await;
411 assert_none!(w1.wait_for_seqno_ge(SeqNo(2)).now_or_never());
412 }
413
414 #[mz_ore::test(tokio::test(flavor = "multi_thread"))]
415 #[cfg_attr(miri, ignore)] async fn state_watch_concurrency() {
417 mz_ore::test::init_logging();
418 let metrics = Arc::new(Metrics::new(
419 &PersistConfig::new_for_tests(),
420 &MetricsRegistry::new(),
421 ));
422 let cache = StateCache::new_no_metrics();
423 let shard_id = ShardId::new();
424 let state = cache
425 .get::<(), (), u64, i64, _, _>(
426 shard_id,
427 || async {
428 Ok(TypedState::new(
429 DUMMY_BUILD_INFO.semver_version(),
430 shard_id,
431 "host".to_owned(),
432 0u64,
433 ))
434 },
435 &Diagnostics::for_tests(),
436 )
437 .await
438 .unwrap();
439 assert_eq!(state.read_lock(&metrics.locks.watch, |x| x.seqno), SeqNo(0));
440
441 const NUM_WATCHES: usize = 100;
442 const NUM_WRITES: usize = 20;
443
444 let watches = (0..NUM_WATCHES)
445 .map(|idx| {
446 let state = Arc::clone(&state);
447 let metrics = Arc::clone(&metrics);
448 mz_ore::task::spawn(|| "watch", async move {
449 let mut watch = StateWatch::new(Arc::clone(&state), Arc::clone(&metrics));
450 let wait_seqno = SeqNo(u64::cast_from(idx % NUM_WRITES + 1));
452 let _ = watch.wait_for_seqno_ge(wait_seqno).await;
453 let observed_seqno =
454 state.read_lock(&metrics.locks.applier_read_noncacheable, |x| x.seqno);
455 assert!(
456 wait_seqno <= observed_seqno,
457 "{} vs {}",
458 wait_seqno,
459 observed_seqno
460 );
461 })
462 })
463 .collect::<Vec<_>>();
464 let writes = (0..NUM_WRITES)
465 .map(|_| {
466 let state = Arc::clone(&state);
467 let metrics = Arc::clone(&metrics);
468 mz_ore::task::spawn(|| "write", async move {
469 state.write_lock(&metrics.locks.applier_write, |x| {
470 x.seqno = x.seqno.next();
471 });
472 })
473 })
474 .collect::<Vec<_>>();
475 for watch in watches {
476 watch.await;
477 }
478 for write in writes {
479 write.await;
480 }
481 }
482
483 struct CountingWaker(AtomicUsize);
485
486 impl Wake for CountingWaker {
487 fn wake(self: Arc<Self>) {
488 self.0.fetch_add(1, Ordering::SeqCst);
489 }
490 fn wake_by_ref(self: &Arc<Self>) {
491 self.0.fetch_add(1, Ordering::SeqCst);
492 }
493 }
494
495 #[mz_persist_proc::test(tokio::test)]
499 #[cfg_attr(miri, ignore)] async fn wait_for_upper_past_ignores_non_upper_seqno_bumps(dyncfgs: ConfigUpdates) {
501 mz_ore::test::init_logging();
502
503 let client = new_test_client(&dyncfgs).await;
504 client.cfg.set_config(
506 &NEXT_LISTEN_BATCH_RETRYER_INITIAL_BACKOFF,
507 Duration::from_secs(1_000_000),
508 );
509 client
510 .cfg
511 .set_config(&NEXT_LISTEN_BATCH_RETRYER_MULTIPLIER, 1);
512 client.cfg.set_config(
513 &NEXT_LISTEN_BATCH_RETRYER_CLAMP,
514 Duration::from_secs(1_000_000),
515 );
516
517 let shard_id = ShardId::new();
518 let (mut write, mut read) = client.expect_open::<(), (), u64, i64>(shard_id).await;
519 let (mut writer2, _) = client.expect_open::<(), (), u64, i64>(shard_id).await;
522
523 write
525 .expect_compare_and_append(&[(((), ()), 0, 1)], 0, 10)
526 .await;
527
528 let counter = Arc::new(CountingWaker(AtomicUsize::new(0)));
530 let waker = Waker::from(Arc::clone(&counter));
531 let mut cx = Context::from_waker(&waker);
532 let frontier = Antichain::from_elem(100);
533 let mut fut = Box::pin(write.wait_for_upper_past(&frontier));
534 assert!(matches!(Pin::new(&mut fut).poll(&mut cx), Poll::Pending));
535 let wakes_after_arm = counter.0.load(Ordering::SeqCst);
536
537 read.downgrade_since(&Antichain::from_elem(5)).await;
540 assert!(matches!(Pin::new(&mut fut).poll(&mut cx), Poll::Pending));
541 assert_eq!(
542 counter.0.load(Ordering::SeqCst),
543 wakes_after_arm,
544 "a non-upper seqno bump must not wake an upper waiter"
545 );
546
547 writer2
549 .expect_compare_and_append(&[(((), ()), 10, 1)], 10, 200)
550 .await;
551 assert!(
552 counter.0.load(Ordering::SeqCst) > wakes_after_arm,
553 "an upper advance must wake the upper waiter"
554 );
555 fut.await;
556 }
557
558 #[mz_persist_proc::test(tokio::test)]
559 #[cfg_attr(miri, ignore)] async fn state_watch_listen_snapshot(dyncfgs: ConfigUpdates) {
561 mz_ore::test::init_logging();
562 let waker = noop_waker();
563 let mut cx = Context::from_waker(&waker);
564
565 let client = new_test_client(&dyncfgs).await;
566 client.cfg.set_config(
568 &NEXT_LISTEN_BATCH_RETRYER_INITIAL_BACKOFF,
569 Duration::from_secs(1_000_000),
570 );
571 client
572 .cfg
573 .set_config(&NEXT_LISTEN_BATCH_RETRYER_MULTIPLIER, 1);
574 client.cfg.set_config(
575 &NEXT_LISTEN_BATCH_RETRYER_CLAMP,
576 Duration::from_secs(1_000_000),
577 );
578
579 let (mut write, mut read) = client.expect_open::<(), (), u64, i64>(ShardId::new()).await;
580
581 let mut listen = read
584 .clone("test")
585 .await
586 .listen(Antichain::from_elem(0))
587 .await
588 .unwrap();
589 let mut snapshot = Box::pin(read.snapshot(Antichain::from_elem(0)));
590 assert!(Pin::new(&mut snapshot).poll(&mut cx).is_pending());
591 let mut listen_next_batch = Box::pin(listen.next(None));
592 assert!(Pin::new(&mut listen_next_batch).poll(&mut cx).is_pending());
593
594 write.expect_compare_and_append(&[], 0, 1).await;
599 let _ = listen_next_batch.await;
600
601 let _ = snapshot.await;
604 }
605
606 #[mz_ore::test(tokio::test(flavor = "multi_thread"))]
607 #[allow(clippy::disallowed_methods)] #[cfg_attr(miri, ignore)]
609 async fn wait_on_awaitable_state() {
610 const TASKS: usize = 1000;
611 let mut set = JoinSet::new();
614 let state = AwaitableState::new(0);
615 let mut tasks = (0..TASKS).collect_vec();
616 let mut rng = rand::rng();
617 tasks.shuffle(&mut rng);
618 for i in tasks {
619 set.spawn({
620 let state = state.clone();
621 async move {
622 state.wait_while(|v| *v != i).await;
623 state.modify(|v| *v += 1);
624 }
625 });
626 }
627 set.join_all().await;
628 assert_eq!(state.read(|i| *i), TASKS);
629 }
630}