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

Add a countdown latch example to Semaphore #6087

Open
mcches opened this issue Oct 19, 2023 · 5 comments · May be fixed by #6105
Open

Add a countdown latch example to Semaphore #6087

mcches opened this issue Oct 19, 2023 · 5 comments · May be fixed by #6105
Labels
A-tokio Area: The main tokio crate C-feature-request Category: A feature request. M-sync Module: tokio/sync T-docs Topic: documentation

Comments

@mcches
Copy link

mcches commented Oct 19, 2023

Is your feature request related to a problem? Please describe.
No

Describe the solution you'd like
A tokio::sync primitive that supports notifying a single task when N tasks have completed some work. On creation a clone-able guard is vended out as needed. Tasks hold the guard until they completed some work. The owner of the primitive waits until the guard count is 0, then wakes and proceeds.

Describe alternatives you've considered
Nothing in the mod exists today.

Additional context
N/A

@mcches mcches added A-tokio Area: The main tokio crate C-feature-request Category: A feature request. labels Oct 19, 2023
@mcches
Copy link
Author

mcches commented Oct 19, 2023

A naive approach using an mpsc.

fn latch(mut n: usize) -> (Countdown, impl Future) {
    let (tx, mut rx) = mpsc::channel(n);

    let wait = async move {
        loop {
            rx.recv().await.unwrap();
            n -= 1;

            if n == 0 {
                break;
            }
        }
    };

    (Countdown { tx }, wait)
}

#[derive(Clone)]
struct Countdown {
    tx: mpsc::Sender<()>,
}

impl Drop for Countdown {
    fn drop(&mut self) {
        _ = self.tx.try_send(());
    }
}

@hawkw
Copy link
Member

hawkw commented Oct 19, 2023

Rather than a mpsc channel, I would recommend using a tokio::sync::Semaphore. The task awaiting the countdown should call Semaphore::acquire_many with the initial value of the countdown latch, while other tasks call Semaphore::add_permit with a single permit to increment the countdown latch. For example:

use tokio::sync::Semaphore; // 1.33.0
use std::{future::Future, sync::Arc};

#[derive(Clone)]
pub struct Countdown(Arc<Semaphore>);

impl Drop for Countdown {
    fn drop(&mut self) {
        self.0.add_permits(1);
    }
}

impl Countdown {
    pub fn new(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!");
}

(playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9e2641bf1cd38b78f7389f51b439533e)

We may want to consider adding this to the Semaphore examples...

@mcches
Copy link
Author

mcches commented Oct 24, 2023

That looks great. Thanks. Having that as an example on the doc with something "count down" that is searchable would probably be super helpful.

@Darksonn Darksonn added the M-sync Module: tokio/sync label Oct 24, 2023
@Darksonn Darksonn changed the title Add a countdown latch primitive to tokio::sync Add a countdown latch example to Semaphore Oct 24, 2023
@Darksonn
Copy link
Contributor

We will probably not add a separate utility for this, but I would be happy to see that example added. I updated the title of this issue to add that example.

@Darksonn Darksonn added the T-docs Topic: documentation label Oct 24, 2023
@hawkw
Copy link
Member

hawkw commented Oct 24, 2023

I can open a PR to add the example to the docs.

hawkw added a commit that referenced this issue Oct 24, 2023
## 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 #6087 (comment).

Closes #6087
@hawkw hawkw linked a pull request Oct 24, 2023 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-tokio Area: The main tokio crate C-feature-request Category: A feature request. M-sync Module: tokio/sync T-docs Topic: documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants