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

  1. Never store or drop a `Runtime` inside async code — create runtimes only in sync/main context
  2. Keep the Runtime alive in main and pass `Handle`s (clones) into async code instead
  3. If you must shut down from within, use `runtime.shutdown_background()` or `shutdown_timeout(Duration)` which do not block the caller
  4. 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

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.