Skip to main content

mz_persist_client/internal/
watch.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Notifications for state changes.
11
12use 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    /// Fires on every strict seqno advance. Used by waiters that care about any
29    /// state change (e.g. lease/seqno machinery via [StateWatch::wait_for_seqno_ge]).
30    seqno_tx: broadcast::Sender<SeqNo>,
31    /// Holds the current shard upper (write frontier), advanced only when the upper
32    /// strictly moves forward. Used by [StateWatch::wait_for_upper_past] so upper
33    /// waiters (Listen/Subscribe/snapshot) don't wake on GC, rollups, or since-
34    /// downgrades that bump the seqno without moving the upper. The watch channel
35    /// coalesces: a waiter always reads the latest upper, never a stale intermediate.
36    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    /// Wake up any watchers of this state.
51    ///
52    /// `seqno` is the state's seqno after the modification, and `upper` is the
53    /// shard upper after the modification.
54    ///
55    /// This must be called while under the same lock that modified the state to
56    /// avoid any potential for out of order SeqNos in the seqno broadcast channel,
57    /// and so the watched upper never regresses.
58    ///
59    /// This restriction can be lifted (i.e. we could notify after releasing the
60    /// write lock), but we'd have to reason about out of order SeqNos in the
61    /// broadcast channel. In particular, if we see `RecvError::Lagged` then
62    /// it's possible we lost X+1 and got X, so if X isn't sufficient to return,
63    /// we'd need to grab the read lock and verify the real SeqNo.
64    pub(crate) fn notify(&self, seqno: SeqNo, upper: &Antichain<T>) {
65        match self.seqno_tx.send(seqno) {
66            // Someone got woken up.
67            Ok(_) => {
68                self.metrics.watch.notify_sent.inc();
69            }
70            // No one is listening, that's also fine.
71            Err(_) => {
72                self.metrics.watch.notify_noop.inc();
73            }
74        }
75        // Advance the watched upper only on a strict advance. send_if_modified does
76        // not require live receivers and never errors, so an advance is recorded
77        // even when no one waits; a later subscriber reads the up-to-date value.
78        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/// A reactive subscription to changes in [LockingTypedState].
93///
94/// This exposes two independent signals over the same state:
95/// - [Self::wait_for_seqno_ge] wakes on any strict seqno advance.
96/// - [Self::wait_for_upper_past] wakes only when the shard upper advances.
97///
98/// Invariants:
99/// - The `state.seqno` only advances (never regresses). This is guaranteed by
100///   LockingTypedState.
101/// - `seqno_high_water` is always <= `state.seqno`.
102/// - If `seqno_high_water` is < `state.seqno`, then we'll get a notification on
103///   `seqno_rx`. This is maintained by notifying new seqnos under the same lock
104///   which adds them.
105/// - `seqno_high_water` always holds the highest value received on `seqno_rx`.
106///   This is maintained by the wait method taking an exclusive reference to self.
107/// - `upper_rx` holds the shard upper, advanced under the same lock that advances
108///   the upper. It coalesces, so a waiter always observes the latest upper.
109#[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        // Important! We have to subscribe to the seqno broadcast channel _before_
121        // we grab the current seqno. Otherwise, we could race with a write to
122        // state and miss a notification. Tokio guarantees that "the returned
123        // Receiver will receive values sent after the call to subscribe", and
124        // the read_lock linearizes the subscribe to be _before_ whatever
125        // seqno we get here.
126        //
127        // The upper watch needs no such care: it retains the latest upper, and
128        // `subscribe` hands the receiver that value, so a waiter reads the current
129        // upper directly and parks only for the next advance.
130        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    /// Blocks until the State has a SeqNo >= the requested one.
143    ///
144    /// This method is cancel-safe.
145    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                    // This is just a hint that our buffer (of size 1) filled
164                    // up, which is totally fine. The broadcast channel
165                    // guarantees that the most recent N (again, =1 here) are
166                    // kept, so just loop around. This branch means we should be
167                    // able to read a new value immediately.
168                    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    /// Blocks until the shard upper has advanced past `frontier`.
182    ///
183    /// Unlike [Self::wait_for_seqno_ge], this only wakes on upper advances, not on
184    /// seqno bumps caused by GC, rollups, since-downgrades, or no-op CaAs.
185    ///
186    /// This method is cancel-safe.
187    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            // Bind the comparison so the borrow guard drops before the await.
199            let past = PartialOrder::less_than(frontier, &*self.upper_rx.borrow_and_update());
200            if past {
201                break;
202            }
203            // `changed` only errors once every sender has dropped. We hold the
204            // state Arc that owns the notifier's sender, so it outlives us.
205            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
221/// A concurrent state - one which allows reading, writing, and waiting for changes made by
222/// another concurrent writer.
223///
224/// This is morally similar to a mutex with a condvar, but allowing asynchronous waits and with
225/// access methods that make it a little trickier to accidentally hold a lock across a yield point.
226pub(crate) struct AwaitableState<T> {
227    state: Arc<RwLock<T>>,
228    /// NB: we can't wrap the [Notify] in the lock since the signature of [Notify::notified]
229    /// doesn't allow it, but this is only accessed while holding the lock.
230    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
248/// A wrapper around a mutable ref that tracks whether it's ever accessed mutably. See
249/// [AwaitableState::maybe_modify] for usage.
250pub 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    /// Conditionally modify the state. This method passes a guard to the provided function,
286    /// which only allows mutable access to the data via [ModifyGuard::get_mut]. If that method
287    /// is not called, waiters will not be woken up.
288    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        // Notify everyone while holding the guard. This guarantees that all waiters will observe
296        // the just-updated state, assuming the state was accessed mutably.
297        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                // Grab the notified future while holding the guard. This ensures that we will see any
317                // future modifications to this state, even if they happen before the first poll.
318                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        // A watch for 0 resolves immediately.
391        let mut w0 = StateWatch::new(Arc::clone(&state), Arc::clone(&metrics));
392        let _ = w0.wait_for_seqno_ge(SeqNo(0)).await;
393
394        // A watch for 1 does not yet resolve.
395        let w0s1 = w0.wait_for_seqno_ge(SeqNo(1)).map(|_| ()).shared();
396        assert_eq!(w0s1.clone().now_or_never(), None);
397
398        // After mutating state, the watch for 1 does resolve.
399        state.write_lock(&metrics.locks.applier_write, |state| {
400            state.seqno = state.seqno.next()
401        });
402        let () = w0s1.await;
403
404        // A watch for an old seqno immediately resolves.
405        let _ = w0.wait_for_seqno_ge(SeqNo(0)).await;
406
407        // We can create a new watch and it also behaves.
408        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)] // error: unsupported operation: integer-to-pointer casts and `ptr::from_exposed_addr` are not supported with `-Zmiri-strict-provenance`
416    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                    // We stared at 0, so N writes means N+1 seqnos.
451                    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    /// A [Waker] that counts how many times it has been woken.
484    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    /// A shard upper waiter must not be woken by seqno bumps that don't advance
496    /// the upper (GC, rollups, since-downgrades, other writers' no-op CaAs). It
497    /// must still be woken when the upper actually advances.
498    #[mz_persist_proc::test(tokio::test)]
499    #[cfg_attr(miri, ignore)] // unsupported operation: returning ready events from epoll_wait is not yet implemented
500    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        // Disable the listen poll so the only wakeups are watch notifications.
505        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        // A second writer so we can advance the upper while `write` is borrowed
520        // by the in-flight wait future.
521        let (mut writer2, _) = client.expect_open::<(), (), u64, i64>(shard_id).await;
522
523        // Move the upper to 10 so there's room to downgrade `since` underneath it.
524        write
525            .expect_compare_and_append(&[(((), ()), 0, 1)], 0, 10)
526            .await;
527
528        // Arm a waiter for an upper past 100. It stays pending.
529        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        // Bump the seqno WITHOUT advancing the upper (a since-downgrade). This is
538        // the regression: before the fix this woke the upper waiter.
539        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        // Advancing the upper must wake the waiter and let it resolve.
548        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)] // unsupported operation: returning ready events from epoll_wait is not yet implemented
560    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        // Override the listen poll so that it's useless.
567        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        // Grab a snapshot for 1, which doesn't resolve yet. Also grab a listen
582        // for 0, which resolves but doesn't yet resolve the next batch.
583        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        // Now update the frontier, which should allow the snapshot to resolve
595        // and the listen to resolve its next batch. Because we disabled the
596        // polling, the listen_next_batch future will block forever and timeout
597        // the test if the watch doesn't work.
598        write.expect_compare_and_append(&[], 0, 1).await;
599        let _ = listen_next_batch.await;
600
601        // For good measure, also resolve the snapshot, though we haven't broken
602        // the polling on this.
603        let _ = snapshot.await;
604    }
605
606    #[mz_ore::test(tokio::test(flavor = "multi_thread"))]
607    #[allow(clippy::disallowed_methods)] // For JoinSet.
608    #[cfg_attr(miri, ignore)]
609    async fn wait_on_awaitable_state() {
610        const TASKS: usize = 1000;
611        // Launch a bunch of tasks, have them all wait for a specific number, then increment it
612        // by one. Lost notifications would cause this test to time out.
613        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}