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

sync: consider exposing Semaphore::close in public API #3061

Closed
hawkw opened this issue Oct 27, 2020 · 2 comments
Closed

sync: consider exposing Semaphore::close in public API #3061

hawkw opened this issue Oct 27, 2020 · 2 comments
Labels
A-tokio Area: The main tokio crate C-feature-request Category: A feature request. E-easy Call for participation: Experience needed to fix: Easy / not much M-sync Module: tokio/sync
Milestone

Comments

@hawkw
Copy link
Member

hawkw commented Oct 27, 2020

Is your feature request related to a problem? Please describe.
When updating tower::buffer to Tokio 0.3, it was necessary to replace the use of tokio's bounded MPSC channel with a re-implementation using the unbounded MPSC and Tokio's semaphore. This is because the bounded MPSC no longer exposes poll_ready, and the ready method borrows the sender, so it can't be easily boxed for an unbounded lifetime. Using Sender::poll_ready used to be sufficient to ensure that tasks waiting for the buffer service to become ready would be notified when the buffer worker terminates, because the senders would be woken on close.

This doesn't work with the new approach using the semaphore, because the Semaphore::acquire_owned method requires the Semaphore itself to be Arced. The Arc is necessary to uphold the intrusive waitlist's safety invariants; as the semaphore cannot be dropped while there are still tasks in its waitlist. However, this means we can't drop it to notify the waiting tasks. Instead, we use the somewhat jankier approach of just adding a big pile of permits to the semaphore and hoping that callers don't continue trying to acquire permits from it. This isn't great, for that reason and because it's theoretically (although probably not practically) possible to leak tasks if the semaphore is close to the maximum number of permits.

See tower-rs/tower#480 for details.

Describe the solution you'd like

The internal semaphore implementation has a close method, which does exactly what we'd want it to do:

/// Closes the semaphore. This prevents the semaphore from issuing new
/// permits and notifies all pending waiters.
// This will be used once the bounded MPSC is updated to use the new
// semaphore implementation.
pub(crate) fn close(&self) {
let mut waiters = self.waiters.lock();
// If the semaphore's permits counter has enough permits for an
// unqueued waiter to acquire all the permits it needs immediately,
// it won't touch the wait list. Therefore, we have to set a bit on
// the permit counter as well. However, we must do this while
// holding the lock --- otherwise, if we set the bit and then wait
// to acquire the lock we'll enter an inconsistent state where the
// permit counter is closed, but the wait list is not.
self.permits.fetch_or(Self::CLOSED, Release);
waiters.closed = true;
while let Some(mut waiter) = waiters.queue.pop_back() {
let waker = unsafe { waiter.as_mut().waker.with_mut(|waker| (*waker).take()) };
if let Some(waker) = waker {
waker.wake();
}
}
}

However, the public API semaphore type doesn't expose this for users to call. It would be nice if it did, for use cases like this.

Describe alternatives you've considered

  • I also considered using a Notify alongside the semaphore to signal that the buffer has closed. However, Notify::notify_all is also not public, so that's not a great option.
  • We could continue just adding a bunch of permits. This is probably fine but it feels kinda messy.

I can't think of any arguments against adding close to the API except that it's good to stabilize as minimal an API surface as possible. It doesn't feel like a potential footgun (as long as the behavior is documented)...

@hawkw hawkw added E-easy Call for participation: Experience needed to fix: Easy / not much A-tokio Area: The main tokio crate M-sync Module: tokio/sync C-feature-request Category: A feature request. labels Oct 27, 2020
zaharidichev added a commit to zaharidichev/tokio that referenced this issue Oct 28, 2020
Fixes: tokio-rs#3061

Signed-off-by: Zahari Dichev <zaharidichev@gmail.com>
@carllerche
Copy link
Member

This will require changing acquire() to return Result, making it a breaking change. I am tagging this for 1.0.

@carllerche carllerche added this to the v1.0 milestone Oct 28, 2020
zaharidichev added a commit to zaharidichev/tokio that referenced this issue Dec 14, 2020
Fixes: tokio-rs#3061

Signed-off-by: Zahari Dichev <zaharidichev@gmail.com>
hawkw pushed a commit that referenced this issue Dec 15, 2020
## Motivation
The need to expose `Semaphore::close` as explained in #3061. 

## Solution
Expose `Semaphore::close`

Signed-off-by: Zahari Dichev <zaharidichev@gmail.com>
@carllerche
Copy link
Member

Closed by #3065

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. E-easy Call for participation: Experience needed to fix: Easy / not much M-sync Module: tokio/sync
Projects
None yet
Development

No branches or pull requests

2 participants