Skip to content

Commit

Permalink
provide a way to drop a runtime in an async context
Browse files Browse the repository at this point in the history
Dropping a runtime normally involves waiting for any outstanding blocking tasks
to complete. When this drop happens in an asynchronous context, we previously
would issue a cryptic panic due to trying to block in an asynchronous context.

This change improves the panic message, and adds a `shutdown_now()` function
which can be used to shutdown a runtime without blocking at all, as an out for
cases where this really is necessary.
  • Loading branch information
Bryan Donlan committed Jul 7, 2020
1 parent 2aa8751 commit e7fc2bd
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 7 deletions.
24 changes: 17 additions & 7 deletions tokio/src/runtime/blocking/shutdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,25 @@ impl Receiver {
/// 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::try_enter;

let mut e = if std::thread::panicking() {
match try_enter(false) {
Some(enter) => enter,
_ => return,
if timeout == Some(Duration::from_nanos(0)) {
return;
}

let mut e = match try_enter(false) {
Some(enter) => enter,
_ => {
if std::thread::panicking() {
// Don't panic in a panic
return;
} else {
panic!(
"Cannot drop a runtime in a context where blocking is not allowed. \
This happens when a runtime is dropped from within an asynchronous context."
);
}
}
} else {
enter(false)
};

// The oneshot completes with an Err
Expand Down
30 changes: 30 additions & 0 deletions tokio/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,4 +548,34 @@ impl Runtime {
} = self;
blocking_pool.shutdown(Some(duration));
}

/// Shutdown the runtime, without waiting for any spawned tasks to shutdown.
///
/// This can be useful if you want to drop a runtime from within another runtime.
/// Normally, dropping a runtime will block indefinitely for spawned blocking tasks
/// to complete, which would normally not be permitted within an asynchronous context.
/// By calling `shutdown_now()`, you can drop the runtime from such a context.
///
/// Note however, that because we do not wait for any blocking tasks to complete, this
/// may result in a resource leak (in that any blocking tasks are still running until they
/// return.
///
/// This function is equivalent to calling `shutdown_timeout(Duration::of_nanos(0))`.
///
/// ```
/// use tokio::runtime::Runtime;
///
/// fn main() {
/// let mut runtime = Runtime::new().unwrap();
///
/// runtime.block_on(async move {
/// let inner_runtime = Runtime::new().unwrap();
/// // ...
/// inner_runtime.shutdown_now();
/// });
/// }
/// ```
pub fn shutdown_now(self) {
self.shutdown_timeout(Duration::from_nanos(0))
}
}
61 changes: 61 additions & 0 deletions tokio/tests/task_blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,64 @@ fn no_block_in_basic_block_on() {
task::block_in_place(|| {});
});
}

#[test]
fn useful_panic_message_when_dropping_rt_in_rt() {
use std::panic::{catch_unwind, AssertUnwindSafe};

let mut outer = tokio::runtime::Builder::new()
.threaded_scheduler()
.build()
.unwrap();

let result = catch_unwind(AssertUnwindSafe(|| {
outer.block_on(async {
let _ = tokio::runtime::Builder::new()
.basic_scheduler()
.build()
.unwrap();
});
}));

assert!(result.is_err());
let err = result.unwrap_err();
let err: &'static str = err.downcast_ref::<&'static str>().unwrap();

assert!(
err.find("Cannot drop a runtime").is_some(),
"Wrong panic message: {:?}",
err
);
}

#[test]
fn can_shutdown_with_zero_timeout_in_runtime() {
let mut outer = tokio::runtime::Builder::new()
.threaded_scheduler()
.build()
.unwrap();

outer.block_on(async {
let rt = tokio::runtime::Builder::new()
.basic_scheduler()
.build()
.unwrap();
rt.shutdown_timeout(Duration::from_nanos(0));
});
}

#[test]
fn can_shutdown_now_in_runtime() {
let mut outer = tokio::runtime::Builder::new()
.threaded_scheduler()
.build()
.unwrap();

outer.block_on(async {
let rt = tokio::runtime::Builder::new()
.basic_scheduler()
.build()
.unwrap();
rt.shutdown_now();
});
}

0 comments on commit e7fc2bd

Please sign in to comment.