mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-28 00:00:11 +02:00
rt: add Runtime::shutdown_timeout (#2186)
Provides an API for forcing a runtime to shutdown even if there are still running tasks.
This commit is contained in:
@@ -10,13 +10,7 @@ pub(crate) struct ParkThread {
|
|||||||
inner: Arc<Inner>,
|
inner: Arc<Inner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error returned by `ParkThread`
|
pub(crate) type ParkError = ();
|
||||||
///
|
|
||||||
/// This currently is never returned, but might at some point in the future.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct ParkError {
|
|
||||||
_p: (),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unblocks a thread that was blocked by `ParkThread`.
|
/// Unblocks a thread that was blocked by `ParkThread`.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -240,7 +234,7 @@ cfg_blocking_impl! {
|
|||||||
F: FnOnce(&ParkThread) -> R,
|
F: FnOnce(&ParkThread) -> R,
|
||||||
{
|
{
|
||||||
CURRENT_PARKER.try_with(|inner| f(inner))
|
CURRENT_PARKER.try_with(|inner| f(inner))
|
||||||
.map_err(|_| ParkError { _p: () })
|
.map_err(|_| ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ cfg_blocking_impl! {
|
|||||||
|
|
||||||
cfg_not_blocking_impl! {
|
cfg_not_blocking_impl! {
|
||||||
use crate::runtime::Builder;
|
use crate::runtime::Builder;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct BlockingPool {}
|
pub(crate) struct BlockingPool {}
|
||||||
@@ -35,5 +36,8 @@ cfg_not_blocking_impl! {
|
|||||||
pub(crate) fn spawner(&self) -> &BlockingPool {
|
pub(crate) fn spawner(&self) -> &BlockingPool {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn shutdown(&mut self, _duration: Option<Duration>) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,19 +101,30 @@ impl BlockingPool {
|
|||||||
pub(crate) fn spawner(&self) -> &Spawner {
|
pub(crate) fn spawner(&self) -> &Spawner {
|
||||||
&self.spawner
|
&self.spawner
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for BlockingPool {
|
pub(crate) fn shutdown(&mut self, timeout: Option<Duration>) {
|
||||||
fn drop(&mut self) {
|
|
||||||
let mut shared = self.spawner.inner.shared.lock().unwrap();
|
let mut shared = self.spawner.inner.shared.lock().unwrap();
|
||||||
|
|
||||||
|
// The function can be called multiple times. First, by explicitly
|
||||||
|
// calling `shutdown` then by the drop handler calling `shutdown`. This
|
||||||
|
// prevents shutting down twice.
|
||||||
|
if shared.shutdown {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
shared.shutdown = true;
|
shared.shutdown = true;
|
||||||
shared.shutdown_tx = None;
|
shared.shutdown_tx = None;
|
||||||
self.spawner.inner.condvar.notify_all();
|
self.spawner.inner.condvar.notify_all();
|
||||||
|
|
||||||
drop(shared);
|
drop(shared);
|
||||||
|
|
||||||
self.shutdown_rx.wait();
|
self.shutdown_rx.wait(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for BlockingPool {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.shutdown(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
use crate::loom::sync::Arc;
|
use crate::loom::sync::Arc;
|
||||||
use crate::sync::oneshot;
|
use crate::sync::oneshot;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(super) struct Sender {
|
pub(super) struct Sender {
|
||||||
tx: Arc<oneshot::Sender<()>>,
|
tx: Arc<oneshot::Sender<()>>,
|
||||||
@@ -26,7 +28,11 @@ pub(super) fn channel() -> (Sender, Receiver) {
|
|||||||
|
|
||||||
impl Receiver {
|
impl Receiver {
|
||||||
/// Blocks the current thread until all `Sender` handles drop.
|
/// Blocks the current thread until all `Sender` handles drop.
|
||||||
pub(crate) fn wait(&mut self) {
|
///
|
||||||
|
/// If `timeout` is `Some`, the thread is blocked for **at most** `timeout`
|
||||||
|
/// duration. If `timeout` is `None`, then the thread is blocked until the
|
||||||
|
/// shutdown signal is received.
|
||||||
|
pub(crate) fn wait(&mut self, timeout: Option<Duration>) {
|
||||||
use crate::runtime::enter::{enter, try_enter};
|
use crate::runtime::enter::{enter, try_enter};
|
||||||
|
|
||||||
let mut e = if std::thread::panicking() {
|
let mut e = if std::thread::panicking() {
|
||||||
@@ -43,6 +49,10 @@ impl Receiver {
|
|||||||
// If blocking fails to wait, this indicates a problem parking the
|
// If blocking fails to wait, this indicates a problem parking the
|
||||||
// current thread (usually, shutting down a runtime stored in a
|
// current thread (usually, shutting down a runtime stored in a
|
||||||
// thread-local).
|
// thread-local).
|
||||||
let _ = e.block_on(&mut self.rx);
|
if let Some(timeout) = timeout {
|
||||||
|
let _ = e.block_on_timeout(&mut self.rx, timeout);
|
||||||
|
} else {
|
||||||
|
let _ = e.block_on(&mut self.rx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ pub(crate) fn exit<F: FnOnce() -> R, R>(f: F) -> R {
|
|||||||
|
|
||||||
cfg_blocking_impl! {
|
cfg_blocking_impl! {
|
||||||
use crate::park::ParkError;
|
use crate::park::ParkError;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
impl Enter {
|
impl Enter {
|
||||||
/// Blocks the thread on the specified future, returning the value with
|
/// Blocks the thread on the specified future, returning the value with
|
||||||
@@ -104,6 +105,44 @@ cfg_blocking_impl! {
|
|||||||
park.park()?;
|
park.park()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Blocks the thread on the specified future for **at most** `timeout`
|
||||||
|
///
|
||||||
|
/// If the future completes before `timeout`, the result is returned. If
|
||||||
|
/// `timeout` elapses, then `Err` is returned.
|
||||||
|
pub(crate) fn block_on_timeout<F>(&mut self, mut f: F, timeout: Duration) -> Result<F::Output, ParkError>
|
||||||
|
where
|
||||||
|
F: std::future::Future,
|
||||||
|
{
|
||||||
|
use crate::park::{CachedParkThread, Park};
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::Context;
|
||||||
|
use std::task::Poll::Ready;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
let mut park = CachedParkThread::new();
|
||||||
|
let waker = park.get_unpark()?.into_waker();
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
|
// `block_on` takes ownership of `f`. Once it is pinned here, the original `f` binding can
|
||||||
|
// no longer be accessed, making the pinning safe.
|
||||||
|
let mut f = unsafe { Pin::new_unchecked(&mut f) };
|
||||||
|
let when = Instant::now() + timeout;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Ready(v) = f.as_mut().poll(&mut cx) {
|
||||||
|
return Ok(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
if now >= when {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
park.park_timeout(when - now)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -235,6 +235,7 @@ cfg_rt_core! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
/// The Tokio runtime.
|
/// The Tokio runtime.
|
||||||
///
|
///
|
||||||
@@ -441,4 +442,46 @@ impl Runtime {
|
|||||||
pub fn handle(&self) -> &Handle {
|
pub fn handle(&self) -> &Handle {
|
||||||
&self.handle
|
&self.handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shutdown the runtime, waiting for at most `duration` for all spawned
|
||||||
|
/// task to shutdown.
|
||||||
|
///
|
||||||
|
/// Usually, dropping a `Runtime` handle is sufficient as tasks are able to
|
||||||
|
/// shutdown in a timely fashion. However, dropping a `Runtime` will wait
|
||||||
|
/// indefinitely for all tasks to terminate, and there are cases where a long
|
||||||
|
/// blocking task has been spawned which can block dropping `Runtime`.
|
||||||
|
///
|
||||||
|
/// In this case, calling `shutdown_timeout` with an explicit wait timeout
|
||||||
|
/// can work. The `shutdown_timeout` will signal all tasks to shutdown and
|
||||||
|
/// will wait for at most `duration` for all spawned tasks to terminate. If
|
||||||
|
/// `timeout` elapses before all tasks are dropped, the function returns and
|
||||||
|
/// outstanding tasks are potentially leaked.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tokio::runtime::Runtime;
|
||||||
|
/// use tokio::task;
|
||||||
|
///
|
||||||
|
/// use std::thread;
|
||||||
|
/// use std::time::Duration;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let mut runtime = Runtime::new().unwrap();
|
||||||
|
///
|
||||||
|
/// runtime.block_on(async move {
|
||||||
|
/// task::spawn_blocking(move || {
|
||||||
|
/// thread::sleep(Duration::from_secs(10_000));
|
||||||
|
/// });
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// runtime.shutdown_timeout(Duration::from_millis(100));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn shutdown_timeout(self, duration: Duration) {
|
||||||
|
let Runtime {
|
||||||
|
mut blocking_pool, ..
|
||||||
|
} = self;
|
||||||
|
blocking_pool.shutdown(Some(duration));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -709,6 +709,23 @@ rt_test! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shutdown_timeout() {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
let mut runtime = rt();
|
||||||
|
|
||||||
|
runtime.block_on(async move {
|
||||||
|
task::spawn_blocking(move || {
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
thread::sleep(Duration::from_secs(10_000));
|
||||||
|
});
|
||||||
|
|
||||||
|
rx.await.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
runtime.shutdown_timeout(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn runtime_in_thread_local() {
|
fn runtime_in_thread_local() {
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|||||||
Reference in New Issue
Block a user