-
Notifications
You must be signed in to change notification settings - Fork 342
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
Graceful exit for background tasks like in tokio #832
Comments
Hi @cecton, thanks for opening this. This issue is not unlike #830 which also had questions about the
This behavior has been a deliberate choice in I don't think this is a change we can make at this time. Instead I would recommend trying to call |
And there is no way to do a graceful shutdown without the joinhandle? Or at least gracefully exit when the process exit? I think it is weird to allow the program to go into an inconsistent state where destructors are ignored. |
Rust doesn't guarantee for destructors to ever run; on a power outage or process reap there is no way these can be run anyway so it's a case to always account for.
Good question: Boats wrote a post on async drop which would allow for "graceful exit on drop". But that wouldn't apply for Tasks since the background behavior is the way it is by design. Instead we may look in a different direction, and aim to make joining multiple tasks easy. I did some work on this through |
I just tested with the for_each_concurrent approach and it works pretty neat. Thanks a lot! I think I will use that for now. fn wait_completion()
-> (mpsc::UnboundedSender<Pin<Box<dyn Future<Output = ()> + Send>>>, Pin<Box<dyn Future<Output = ()> + Send>>)
{
let (tx, rx) = mpsc::unbounded::<Pin<Box<dyn Future<Output = ()> + Send>>>();
let monitor = rx.for_each_concurrent(None, |x| async move { x.await; } );
(tx, Box::pin(monitor))
}
...
let (mut tx, monitor) = wait_completion();
let completion = async_std::task::spawn(monitor);
...
tx.send(async_std::task::spawn(some_task())).unwrap();
...
// graceful shutdown
tx.close_channel();
completion.await; (This code doesn't include the exit signal handling, only the clean shutdown) |
Issue
When using async-std's
block_on
, the background tasks spawned are neither stopped neither awaited properly. This means that destructors are not called as they should.I talk about this with @yoshuawuyts and there seems to be a way to do it with task cancellation but I think async-std should actually stop the tasks gracefully.
Current behavior
To help understand the issue I made this repository to demonstrate: https://github.com/cecton/asyncstd-test-drops
In this example a task is ran in background for async-std and then I use
block_on
on a sleep. The sleep finishes before the task actually completes and we exit theblock_on
despite things are still running in background.Right after in the same example I do the exact same thing with tokio and you can see that in tokio the
block_on
only exit when the task and its objects are dropped properly.Here is the output of the program if I comment the tokio example and keep only the async-std example. Note that the destructor is never called:
The program finished in 2 seconds because the timeout is of 2 seconds. All the blocking tasks in background and objects are just not dropped.
Here is the output of the program if I comment the async-std example and keep only the tokio example. Note that the task is dropped before reaching the marker "2":
The program finished in 7 seconds. This is the addition of the 2 blocking codes (
std::thread::sleep
). It blocks the thread and prevents tokio to finish.Now another interesting thing is this: if I keep both examples enabled, this is the output I get:
The async-std task was still running in background and it finished while the tokio test was running. Here the
Test(1)
object created by the async-std task is beginning the drop but it couldn't finish because the tokio drop finished before. I first thought that the background tasks were abruptly killed by async-std but this proves this is not the case. The background tasks are still running, they're just not managed anymore.Expected behavior
I have worked a lot with asyncio on Python and recently a bit with tokio. I expect that the async runtime will drop things gracefully. You could argue that I could implement a shutdown mechanism (which is true) but that doesn't allow the runtime to simply forget about my objects and not call their destructors.
The best behavior is to wait for the background tasks to get into an awaiting state (waiting for io, waiting for a sleep, etc...) and cancel the task, deleting it and its objects. When everything has been deleted, the
block_on
should finish and the program can safely exit.The text was updated successfully, but these errors were encountered: