dbt-labs/dbt-core · error
Cannot drop a runtime in a context where blocking is not all
Error message
Cannot drop a runtime in a context where blocking is not allowed. This happens when a runtime is dropped from within an asynchronous context.
What it means
Dropping a dbt-runtime shuts it down synchronously: wait() blocks until all worker threads finish. If that drop happens inside an async context where blocking is not allowed (the runtime's enter guard detects a runtime thread cannot block), the library panics with this message rather than deadlocking. It deliberately skips the panic if the thread is already panicking.
Source
Thrown at crates/dbt-runtime/src/shutdown.rs:51
/// duration. If `timeout` is `None`, then the thread is blocked until the
/// shutdown signal is received.
///
/// If the timeout has elapsed, it returns `false`, otherwise it returns `true`.
pub(crate) fn wait(&mut self, timeout: Option<Duration>) -> bool {
use crate::context::blocking::try_enter_blocking_region;
if timeout == Some(Duration::from_nanos(0)) {
return false;
}
let mut e = match try_enter_blocking_region() {
Some(enter) => enter,
_ => {
if std::thread::panicking() {
// Don't panic in a panic
return false;
} 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."
);
}
}
};
// The oneshot completes with an Err
//
// If blocking fails to wait, this indicates a problem parking the
// current thread (usually, shutting down a runtime stored in a
// thread-local).
if let Some(timeout) = timeout {
e.block_on_timeout(&mut self.rx, timeout).is_ok()
} else {
let _ = e.block_on(&mut self.rx);
true
}View on GitHub (pinned to 0267ce9170)
Solutions
- Never store or drop a `Runtime` inside async code — create runtimes only in sync/main context
- Keep the Runtime alive in main and pass `Handle`s (clones) into async code instead
- If you must shut down from within, use `runtime.shutdown_background()` or `shutdown_timeout(Duration)` which do not block the caller
- Wrap drop sites: move runtime ownership to a dedicated thread or use Handle::block_on from a plain thread
Example fix
// before
async fn run() {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(do_work());
} // Runtime dropped inside async ctx -> panic
// after
fn main() {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(do_work());
} // or: let handle = rt.handle().clone(); rt.shutdown_background(); Defensive patterns
Strategy: type-guard
Validate before calling
// Before dropping a runtime, ensure you are NOT inside an async context
// e.g. assert you are on the main thread (sync world):
fn can_drop_runtime() -> bool {
std::thread::current().name() == Some("main")
} Type guard
fn is_inside_async_ctx() -> bool {
// tokio::task::try_current() is Ok only inside a runtime context
tokio::task::try_current().is_ok()
}
// guard: assert!(!is_inside_async_ctx(), "drop the runtime outside async code"); Try / catch
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(runtime)));
if res.is_err() { eprintln!("runtime dropped inside async ctx; use shutdown_background()"); } Prevention
- Create runtimes only in sync code (main, tests); pass Handle clones into async code
- Use shutdown_background() or shutdown_timeout() instead of relying on Drop inside async
- Never put a Runtime in a struct held across await points
- Prefer #[tokio::main] or an explicit main-scoped runtime over ad-hoc Runtime::new inside tasks
When it happens
Trigger: Dropping a Runtime handle (or a value holding one) from within async code executed on a runtime thread — e.g. letting a `Runtime` (not a `Handle`) go out of scope inside an async fn, storing a Runtime in a struct dropped by a task, or calling std::mem::drop(runtime) inside a block_on body executed on the runtime.
Common situations: Accidentally putting a `Runtime` field in a struct that lives across await points or in a struct stored in task-local/TLS state; creating a new Runtime inside async code and dropping it at scope end; confusing Runtime::block_on with Handle::block_on.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/804e6687ef828fa6.
Report an issue: GitHub.