{"record":{"id":"804e6687ef828fa6","repo":"dbt-labs/dbt-core","slug":"cannot-drop-a-runtime-in-a-context-where-blocking","errorCode":null,"errorMessage":"Cannot drop a runtime in a context where blocking is not allowed. This happens when a runtime is dropped from within an asynchronous context.","messagePattern":"Cannot drop a runtime in a context where blocking is not allowed\\. This happens when a runtime is dropped from within an asynchronous context\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/dbt-runtime/src/shutdown.rs","lineNumber":51,"sourceCode":"    /// duration. If `timeout` is `None`, then the thread is blocked until the\n    /// shutdown signal is received.\n    ///\n    /// If the timeout has elapsed, it returns `false`, otherwise it returns `true`.\n    pub(crate) fn wait(&mut self, timeout: Option<Duration>) -> bool {\n        use crate::context::blocking::try_enter_blocking_region;\n\n        if timeout == Some(Duration::from_nanos(0)) {\n            return false;\n        }\n\n        let mut e = match try_enter_blocking_region() {\n            Some(enter) => enter,\n            _ => {\n                if std::thread::panicking() {\n                    // Don't panic in a panic\n                    return false;\n                } else {\n                    panic!(\n                        \"Cannot drop a runtime in a context where blocking is not allowed. \\\n                        This happens when a runtime is dropped from within an asynchronous context.\"\n                    );\n                }\n            }\n        };\n\n        // The oneshot completes with an Err\n        //\n        // If blocking fails to wait, this indicates a problem parking the\n        // current thread (usually, shutting down a runtime stored in a\n        // thread-local).\n        if let Some(timeout) = timeout {\n            e.block_on_timeout(&mut self.rx, timeout).is_ok()\n        } else {\n            let _ = e.block_on(&mut self.rx);\n            true\n        }","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/dbt-labs/dbt-core/blob/0267ce9170576975b76b64ce856b2e5848e96617/crates/dbt-runtime/src/shutdown.rs#L33-L69","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nasync fn run() {\n    let rt = tokio::runtime::Runtime::new().unwrap();\n    rt.block_on(do_work());\n} // Runtime dropped inside async ctx -> panic\n\n// after\nfn main() {\n    let rt = tokio::runtime::Runtime::new().unwrap();\n    rt.block_on(do_work());\n} // or: let handle = rt.handle().clone(); rt.shutdown_background();","handlingStrategy":"type-guard","validationCode":"// Before dropping a runtime, ensure you are NOT inside an async context\n// e.g. assert you are on the main thread (sync world):\nfn can_drop_runtime() -> bool {\n    std::thread::current().name() == Some(\"main\")\n}","typeGuard":"fn is_inside_async_ctx() -> bool {\n    // tokio::task::try_current() is Ok only inside a runtime context\n    tokio::task::try_current().is_ok()\n}\n// guard: assert!(!is_inside_async_ctx(), \"drop the runtime outside async code\");","tryCatchPattern":"let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(runtime)));\nif res.is_err() { eprintln!(\"runtime dropped inside async ctx; use shutdown_background()\"); }","preventionTips":["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"],"tags":["rust","async","runtime-lifecycle","panic"],"backgroundTag":"invalid-state-transition","analyzedSha":"0267ce9170576975b76b64ce856b2e5848e96617","analyzedAt":"2026-09-07T21:53:39.732Z","contentChangedAt":"2026-09-07T21:53:39.732Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}