mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-27 00:00:12 +02:00
threadpool: drop incomplete tasks on shutdown (#722)
## Motivation When the thread pool shuts down, futures that have been polled at least once but not completed yet are simply leaked. We should drop them instead. ## Solution Multiple changes are introduced: * Tasks are assigned a home worker the first time they are polled. * Each worker contains a set of tasks (`Arc<Task>`) it is home to. When a task is assigned a home worker, it is registered in that worker's set of tasks. When the task is completed, it is unregistered from the set. * When the thread pool shuts down and after all worker threads stop, the remaining tasks in workers' sets are aborted, i.e. they are switched to the `Aborted` state and their `Future`s are dropped. * The thread pool shutdown process is refactored to make it more robust. We don't track the number of active threads manually anymore. Instead, there's `Arc<ShutdownTrigger>` that aborts remaining tasks and completes the `Shutdown` future once it gets destroyed (when all `Worker`s and `ThreadPool` get dropped because they're the only ones to contain strong references to the `ShutdownTrigger`). Closes #424 Closes #428
This commit is contained in:
@@ -3,7 +3,10 @@ extern crate tokio_executor;
|
||||
extern crate futures;
|
||||
extern crate env_logger;
|
||||
|
||||
use tokio_executor::park::{Park, Unpark};
|
||||
use tokio_threadpool::*;
|
||||
use tokio_threadpool::park::{DefaultPark, DefaultUnpark};
|
||||
|
||||
use futures::{Poll, Sink, Stream, Async, Future};
|
||||
use futures::future::lazy;
|
||||
|
||||
@@ -420,3 +423,113 @@ fn multi_threadpool() {
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eagerly_drops_futures() {
|
||||
use futures::future::{Future, lazy, empty};
|
||||
use futures::task;
|
||||
use std::sync::mpsc;
|
||||
|
||||
struct NotifyOnDrop(mpsc::Sender<()>);
|
||||
|
||||
impl Drop for NotifyOnDrop {
|
||||
fn drop(&mut self) {
|
||||
self.0.send(()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
struct MyPark {
|
||||
inner: DefaultPark,
|
||||
#[allow(dead_code)]
|
||||
park_tx: mpsc::SyncSender<()>,
|
||||
unpark_tx: mpsc::SyncSender<()>,
|
||||
}
|
||||
|
||||
impl Park for MyPark {
|
||||
type Unpark = MyUnpark;
|
||||
type Error = <DefaultPark as Park>::Error;
|
||||
|
||||
fn unpark(&self) -> Self::Unpark {
|
||||
MyUnpark {
|
||||
inner: self.inner.unpark(),
|
||||
unpark_tx: self.unpark_tx.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn park(&mut self) -> Result<(), Self::Error> {
|
||||
self.inner.park()
|
||||
}
|
||||
|
||||
fn park_timeout(&mut self, duration: Duration) -> Result<(), Self::Error> {
|
||||
self.inner.park_timeout(duration)
|
||||
}
|
||||
}
|
||||
|
||||
struct MyUnpark {
|
||||
inner: DefaultUnpark,
|
||||
#[allow(dead_code)]
|
||||
unpark_tx: mpsc::SyncSender<()>,
|
||||
}
|
||||
|
||||
impl Unpark for MyUnpark {
|
||||
fn unpark(&self) {
|
||||
self.inner.unpark()
|
||||
}
|
||||
}
|
||||
|
||||
let (task_tx, task_rx) = mpsc::channel();
|
||||
let (drop_tx, drop_rx) = mpsc::channel();
|
||||
let (park_tx, park_rx) = mpsc::sync_channel(0);
|
||||
let (unpark_tx, unpark_rx) = mpsc::sync_channel(0);
|
||||
|
||||
// Get the signal that the handler dropped.
|
||||
let notify_on_drop = NotifyOnDrop(drop_tx);
|
||||
|
||||
let pool = tokio_threadpool::Builder::new()
|
||||
.custom_park(move |_| {
|
||||
MyPark {
|
||||
inner: DefaultPark::new(),
|
||||
park_tx: park_tx.clone(),
|
||||
unpark_tx: unpark_tx.clone(),
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
pool.spawn(lazy(move || {
|
||||
// Get a handle to the current task.
|
||||
let task = task::current();
|
||||
|
||||
// Send it to the main thread to hold on to.
|
||||
task_tx.send(task).unwrap();
|
||||
|
||||
// This future will never resolve, it is only used to hold on to thee
|
||||
// `notify_on_drop` handle.
|
||||
empty::<(), ()>().then(move |_| {
|
||||
// This code path should never be reached.
|
||||
if true { panic!() }
|
||||
|
||||
// Explicitly drop `notify_on_drop` here, this is mostly to ensure
|
||||
// that the `notify_on_drop` handle gets moved into the task. It
|
||||
// will actually get dropped when the runtime is dropped.
|
||||
drop(notify_on_drop);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}));
|
||||
|
||||
// Wait until we get the task handle.
|
||||
let task = task_rx.recv().unwrap();
|
||||
|
||||
// Drop the pool, this should result in futures being forcefully dropped.
|
||||
drop(pool);
|
||||
|
||||
// Make sure `MyPark` and `MyUnpark` were dropped during shutdown.
|
||||
assert_eq!(park_rx.try_recv(), Err(mpsc::TryRecvError::Disconnected));
|
||||
assert_eq!(unpark_rx.try_recv(), Err(mpsc::TryRecvError::Disconnected));
|
||||
|
||||
// If the future is forcefully dropped, then we will get a signal here.
|
||||
drop_rx.recv().unwrap();
|
||||
|
||||
// Ensure `task` lives until after the test completes.
|
||||
drop(task);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user