Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add countdown latch example to Semaphore #6105

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
72 changes: 71 additions & 1 deletion tokio/src/sync/semaphore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like an accidental change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoops, yeah, i'll undo that

/// let bucket = TokenBucket::new(update_interval, capacity);
///
/// for _ in 0..5 {
Expand All @@ -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
hawkw marked this conversation as resolved.
Show resolved Hide resolved
/// latch open. The tasks whose completion is awaited by the latch should call
/// [`Semaphore::add_permit`] with a single permit to increment the countdown
hawkw marked this conversation as resolved.
Show resolved Hide resolved
/// latch as they complete. For example:
///
/// ```rust
hawkw marked this conversation as resolved.
Show resolved Hide resolved
/// use tokio::sync::Semaphore;
/// use std::{future::Future, sync::Arc};
hawkw marked this conversation as resolved.
Show resolved Hide resolved
///
/// /// A token that holds a countdown latch open until it is dropped.
/// #[derive(Clone)]
/// pub struct Countdown(Arc<Semaphore>);
Comment on lines +353 to +355
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This derive(Clone) seems a bit weird to me. It makes it difficult to make sure you have the right number of tokens. In your example, you also create one token more than you need, but the extra one in main is not dropped until after the wait completes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that's a good point. this could maybe have some kind of fn try_clone -> Option<Self> that only allows creating as many guards as there are permits, or something? i'm not sure if the additional complexity is worth including in the example or not...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just return a vector of them?

///
/// 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)]
Expand Down