-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Waiting for things to shut down #5585
Comments
How would such a data structure behave? Basically the same as the cancelation token, just in reverse? Meaning you have a tree where you can |
My idea was just to have two types, a sender and receiver (and some better naming) and have the receiver wake up when all senders are dropped. |
What would be the advantage over mpsc? Because mpsc already has this functionality. Performance? |
The advantage is that it would be less confusing. |
Would it make sense to integrate this functionality into |
It's an interesting idea. The API needs some sort of way to distinguish between senders and receivers, so it's not completely clear how to do that. |
Why that? I thought of something like |
The assumption here is that the cancelation tokens and the joiner objects are always used in conjunction anyway, and most likely in the same tree structure, if you want to allow for partial cancelation. Which would make sense, because the CancellationTokens also allow partial cancelation. So it would make sense to combine the two and enable a cancelation token to detect when all of its children are dropped, then we would only need one object to perform both tasks. |
Yes, I guess that would work. |
@uds5501 No, I'm quite busy at the moment. Have fun. |
Hey @Darksonn , I got a chance to go through the internals of Cancellation Token recently. It’s a well-written module! I was going through currently suggested methods for graceful shutdown -
This led to a thought, why do we need a separate API with senders and receivers? For the receiving end, we can continue listening to the cancelled event from the base token (the very first token created) // Borrowed from documentation
let check_for_shutdown = tokio::spawn(async move {
tokio::select! {
_ = base_token.cancelled() => {
// The token was cancelled, task can shut down
}
}
}); Now, we can pass a clone of the base token to some other function / thread which can trigger a cancellation. Jotted down the following example to illustrate - use tokio_util::sync::CancellationToken;
use tokio;
fn some_task(token: CancellationToken) {
// some computation above
token.cancel();
}
async fn base_task() {
let base_token = CancellationToken::new();
let check_for_shutdown = tokio::spawn(async move {
tokio::select! {
_ = base_token.cancelled() => {
// handle shutdown
}
}
});
let token_to_pass = base_token.clone();
someTask(token_to_pass);
check_for_shutdown.await.unwrap();
}
fn main() {
println!("Hello, world!");
base_task();
} This kind of feels like how we use |
Im not sure what you are trying to say here :) would you mind explaining how what you are saying is connected to the issue? I might just be a little dense... |
The point is that it's not always feasible to await the child task, or if spawned, its joiner. So we would like to have another mechanism to propagate the information that everything that needs to shut down is now finished. The original proposal was to introduce a new data structure, and my counter proposal was to add that capability to shutdown token itself. Heads up: I think it's cleaner to add it to the shutdown token, but that will also be a lot more work. It took quite a while to arrive at the current state the code of the token is in. |
The biggest problem we had in the original implementation is to avoid recursion, and that only works because we rip the tree apart on cancelation. So if we want to keep the tree structure during cancelation so that we can then later check whether all children got dropped, I currently don't see how we could then propagate the cancelation without recursion and keeping locks for a long duration. The current implementation is really elegant and I'm afraid adding the functionality to wait for children to be dropped will make the implementation a lot more ugly |
@Finomnis thanks for taking the time to elaborate on the previous discussion. I wanted to clarify a couple of things regarding how async/await works.
Did I understand this bit right? Thank you for your patience :D |
@Finomnis When we rip the tree apart, we are ensuring that the children are getting cancelled and dropped right? Why would we want to keep them attached to the parent? |
@uds5501 I'm afraid you are missing the point here :/
No, this is not true. If a child triggers a cancellation, it will be a partial cancellation that only stops the siblings and all the grand-children. The parent will never be notified - in fact, we don't want the parent to be notified in the case of a partial cancellation.
No, propagating the cancellation is incredibly fast, almost instantaneous. What we are talking about here is the tasks that a child might have to perform after cancellation, like shutting down a socket or similar. We then want the parent to be notified when the child has finished those.
We use
No, cancellation tokens do not cancel a child or drop it or anything. They simply propagate the intent of a graceful cancellation to the child. How the child deals with that is its own decision. The reason why keeping the tokens attached might be useful is because they exist inside of the child's scope, so their |
Be warned though: Implementing this inside of |
Apologies for the miscommunication, by child process triggering the cancellation, I meant child process canceling the parent token.
Makes sense
Again, makes sense.
I am a bit confused here, I was looking at this implementation of cancel in the treenode where cancelling a token also ensures that we are canceling the child token and dropping the child token as well. Am I misunderstanding this code snippet? tokio/tokio-util/src/sync/cancellation_token/tree_node.rs Lines 295 to 343 in 61b68a8
I acknowledge that this will be my first contribution to this library so learning curve will indeed be steep. Will probably move on to another issue if this discussion doesn't come to any conclusion. Thanks again for your patience :) |
@uds5501 I think we are miscommunication the word "child". I'm the context, I meant child-task, and you meant child-token. Yes, it cancels the child token, but we want to know when the child task is done. |
Feel free to persue it :) I just wanted to make you aware of the difficilty. Didn't mean to stop you :) |
I remembered this issue and "independently" came to the same conclusion as @Finomnis in #5585 (comment), basically that it would be great if CancellationToken were able to wait for it's children to be dropped. As for the original idea by @Darksonn of just having these mspc wrapping types, I've created Naive `tokio::sync::mpsc::{Sender<()>,Receiver<()>}` wrappers#[derive(Clone)]
struct Completion(tokio::sync::mpsc::Sender<()>);
#[derive(Clone)]
struct Barrier(Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<()>>>);
impl Barrier {
/// Awaits until all associated Completion clones have been dropped.
async fn wait(self) {
self.0.lock().await.recv().await;
}
}
fn channel() -> (Completion, Barrier) {
let (tx, rx) = tokio::sync::mpsc::channel(1);
let tx = Completion(tx);
let rx = Arc::new(tokio::sync::Mutex::new(rx));
(tx, rx)
} There is probably a lot to optimize over the
Having
This is a good point, however it would be great to have the CancellationToken thing provide the waitability. Perhaps I'll experiment this outside this repository first, if I have time. Hierarchical waitability is definitely more difficult to implement, and might be less required. |
This sounds a lot like what the recent crate tokio-graceful is doing. The code looks clean and not that complex. Might be worth a look? |
Currently, the shutdown page of the Tokio tutorial suggests using an mpsc channel to wait for shutdown. This is a bit of a hack. We should provide a dedicated alternative similar to
CancellationToken
.The text was updated successfully, but these errors were encountered: