{"record":{"id":"8495c20c49964150","repo":"dbt-labs/dbt-core","slug":"enterguard-values-dropped-out-of-order-guards-r","errorCode":null,"errorMessage":"`EnterGuard` values dropped out of order. Guards returned by `Handle::enter()` must be dropped in the reverse order as they were acquired.","messagePattern":"`EnterGuard` values dropped out of order\\. Guards returned by `Handle::enter\\(\\)` must be dropped in the reverse order as they were acquired\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/dbt-runtime/src/context/current.rs","lineNumber":80,"sourceCode":"}\n\nimpl HandleCell {\n    pub(super) const fn new() -> HandleCell {\n        HandleCell {\n            handle: RefCell::new(None),\n            depth: Cell::new(0),\n        }\n    }\n}\n\nimpl Drop for SetCurrentGuard {\n    fn drop(&mut self) {\n        CONTEXT.with(|ctx| {\n            let depth = ctx.current.depth.get();\n\n            if depth != self.depth {\n                if !std::thread::panicking() {\n                    panic!(\n                        \"`EnterGuard` values dropped out of order. Guards returned by \\\n                         `Handle::enter()` must be dropped in the reverse order as they \\\n                         were acquired.\"\n                    );\n                } else {\n                    // Just return... this will leave handles in a wonky state though...\n                    return;\n                }\n            }\n\n            *ctx.current.handle.borrow_mut() = self.prev.take();\n            ctx.current.depth.set(depth - 1);\n        });\n    }\n}\n","sourceCodeStart":62,"sourceCodeEnd":96,"githubUrl":"https://github.com/dbt-labs/dbt-core/blob/0267ce9170576975b76b64ce856b2e5848e96617/crates/dbt-runtime/src/context/current.rs#L62-L96","documentation":"This EnterGuard tracks the depth of a thread-local task context stack (like tokio's Handle::enter). Guards must be dropped in LIFO order; if a guard's depth doesn't match the current stack depth at drop time, the nesting invariant is violated and the code panics (unless already panicking, in which case it stays silent).","triggerScenarios":"Dropping an EnterGuard out of acquisition order — e.g. storing guards in a collection and dropping them in FIFO order, leaking one guard while dropping an outer one, mem::forget on a guard, or holding a guard across an early return/panic path that unwinds past an inner guard differently.","commonSituations":"Manually calling Handle::enter() in tests or FFI boundaries and juggling multiple guards; wrapping enter() in a non-RAII pattern; catching a panic inside an entered scope and then dropping guards in the wrong sequence.","solutions":["Scope each enter() guard tightly (let _guard = handle.enter(); inside a block) so RAII guarantees reverse-order drop.","Never store EnterGuard values in collections or fields; only keep them as local bindings.","Check for mem::forget, ManuallyDrop, or early returns that skip guard drops out of order.","If guards must nest, ensure the innermost guard is dropped before the outer one."],"exampleFix":"// before\nlet g1 = handle.enter();\nlet g2 = handle.enter();\ndrop(g1); // wrong order\ndrop(g2);\n\n// after\nlet g1 = handle.enter();\n{\n    let g2 = handle.enter();\n    // ... inner work ...\n} // g2 dropped first automatically\ndrop(g1);","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"fn guard_depth_ok(ctx: &Ctx, guard: &EnterGuard) -> bool {\n    ctx.current.depth.get() == guard.depth + 1\n}","tryCatchPattern":"std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    let _g = handle.enter();\n    run_task();\n})).unwrap_or_else(|_| eprintln!(\"enter scope panicked; guards unwound in order\"));","preventionTips":["Keep EnterGuard values as local RAII bindings; never store them in structs, Vecs, or fields.","Scope nested enters in inner blocks so inner guards drop first automatically.","Avoid mem::forget/ManuallyDrop on guards and early returns that juggle multiple guards manually.","Prefer passing the Handle to code that needs it instead of re-entering repeatedly."],"tags":["rust","runtime","panic","raii"],"backgroundTag":"internal-invariant-violation","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"}