From d801664c89360e68a7a4d1eacb1cc0617ec7958b Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 24 Oct 2023 10:09:07 -0700 Subject: [PATCH] docs: add countdown latch example to `Semaphore` ## Motivation Some users have requested that `tokio::sync` add a countdown latch synchronization primitive. This can be implemented using the existing `Semaphore` API, so rather than adding a countdown latch type, we should add documentation examples showing how the `Semaphore` can be used as a countdown latch. ## Solution This branch adds an example to the `tokio::sync::Semaphore` docs demonstrating its usage as a countdown latch. This example was extracted from https://github.com/tokio-rs/tokio/issues/6087#issuecomment-1771452743. Closes #6087 --- tokio/src/sync/semaphore.rs | 72 ++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/tokio/src/sync/semaphore.rs b/tokio/src/sync/semaphore.rs index 61896c9d5..027677e2e 100644 --- a/tokio/src/sync/semaphore.rs +++ b/tokio/src/sync/semaphore.rs @@ -322,7 +322,7 @@ use std::sync::Arc; /// # #[tokio::main(flavor = "current_thread", start_paused = true)] /// async fn main() { /// let capacity = 5; -/// let update_interval = Duration::from_secs_f32(1.0 / capacity as f32); +/// let update_interval = Duration::from_secs_f32(1.0 / capacity as f32); /// let bucket = TokenBucket::new(update_interval, capacity); /// /// for _ in 0..5 { @@ -333,6 +333,76 @@ use std::sync::Arc; /// } /// ``` /// +/// ## Countdown latch +/// +/// A `Semaphore` may be used to implement a countdown latch, a synchronization +/// primitive where one task waits until a set number of other tasks to have +/// completed before continuing. +/// +/// The task awaiting the countdown should call [`Semaphore::acquire_many`] to +/// acquire the a number of permits equal to the number of tasks which hold the +/// latch open. The tasks whose completion is awaited by the latch should call +/// [`Semaphore::add_permit`] with a single permit to increment the countdown +/// latch as they complete. For example: +/// +/// ```rust +/// use tokio::sync::Semaphore; +/// use std::{future::Future, sync::Arc}; +/// +/// /// A token that holds a countdown latch open until it is dropped. +/// #[derive(Clone)] +/// pub struct Countdown(Arc); +/// +/// impl Drop for Countdown { +/// fn drop(&mut self) { +/// self.0.add_permits(1); +/// } +/// } +/// +/// impl Countdown { +/// /// Returns a new countdown latch which completes when `n` tasks +/// /// have exited. +/// /// +/// /// This method returns a tuple of a `Countdown` token which +/// /// holds the latch open and incrememts the count of completed +/// /// tasks when it is dropped, and a `Future` which completes when +/// /// `n` clones of the `Countdown` token have been dropped. +/// pub fn latch(n: u32) -> (Self, impl Future + Send) { +/// let sem = Arc::new(Semaphore::new(0)); +/// let latch = Self(sem.clone()); +/// +/// let wait = async move { +/// let _ = sem.acquire_many(n).await; +/// }; +/// +/// (latch, wait) +/// } +/// } +/// +/// #[tokio::main] +/// async fn main() { +/// let (latch, wait) = Countdown::new(5); +/// for i in 1..=5 { +/// let latch = latch.clone(); +/// tokio::spawn(async move { +/// // move the latch into the task. +/// let _latch = latch; +/// +/// // do stuff... +/// println!("countdown task {i} running..."); +/// tokio::task::yield_now().await; +/// +/// // when the task completes, the latch is dropped. +/// println!("countdown task {i} done!"); +/// }); +/// } +/// +/// println!("waiting for tasks to complete..."); +/// wait.await; +/// println!("tasks completed!"); +/// } +/// ``` +/// /// [`PollSemaphore`]: https://docs.rs/tokio-util/latest/tokio_util/sync/struct.PollSemaphore.html /// [`Semaphore::acquire_owned`]: crate::sync::Semaphore::acquire_owned #[derive(Debug)]