gfx-rs/wgpu · critical

Mismatched pop_error_scope call: no error scope for this thr

Error message

Mismatched pop_error_scope call: no error scope for this thread. Error scopes are thread-local.

What it means

pop_error_scope_checked validates that the calling thread has a registered error-scope stack. If no scope was ever pushed on this thread, wgpu panics with this message. Error scopes in wgpu are strictly thread-local, so a scope pushed on one thread cannot be popped on another.

Source

Thrown at wgpu-core/src/error.rs:395

        }

        #[cfg(not(feature = "std"))]
        fn is_panicking() -> bool {
            false
        }

        let mut error_sink = self.error_sink.0.lock();

        // We go out of our way to avoid panicking while unwinding, because that would abort the process,
        // and we are supposed to just drop the error scope on the floor.
        let is_panicking = is_panicking();
        let thread_id = thread_id::ThreadId::current();
        let err = "Mismatched pop_error_scope call: no error scope for this thread. Error scopes are thread-local.";
        let scopes = match error_sink.scopes.get_mut(&thread_id) {
            Some(s) => s,
            None => {
                if !is_panicking {
                    panic!("{err}");
                } else {
                    return None;
                }
            }
        };
        if scopes.is_empty() && !is_panicking {
            panic!("{err}");
        }
        if index as usize != scopes.len() - 1 && !is_panicking {
            panic!(
                "Mismatched pop_error_scope call: error scopes must be popped in reverse order."
            );
        }

        // It would be more correct in this case to use `remove` here so that when unwinding is occurring
        // we would remove the correct error scope, but we don't have such a primitive on the web
        // and having consistent behavior here is more important. If you are unwinding and it unwinds
        // the guards in the wrong order, it's totally reasonable to have incorrect behavior.

View on GitHub (pinned to 3e11ff59bf)

Solutions

  1. Ensure push_error_scope and pop_error_scope are called on the same thread, paired one-to-one.
  2. Refactor to keep the entire scope push/pop lifecycle in one function/task (use async blocks that run on a single executor thread).
  3. Guard the pop behind a flag set by the corresponding push on the same thread.
  4. If this fires during process shutdown/panic unwinding it is expected to return None instead; only fix code paths that call it normally.

Example fix

// before
let handle = std::thread::spawn(move || device.push_error_scope(knd));
// later, on main thread:
device.pop_error_scope().await; // panics
// after
let handle = std::thread::spawn(move || {
    device.push_error_scope(knd);
    // ... work ...
    device.pop_error_scope() // same thread
});
Defensive patterns

Strategy: validation

Validate before calling

fn thread_can_pop(pushed_on: std::thread::ThreadId) -> bool {
    pushed_on == std::thread::current().id()
}

Type guard

struct ScopedErrorScope<'a> { device: &'a Device }
impl Device {
    fn with_error_scope<F: FnOnce()>(&self, k: ErrorFilter, f: F) {
        self.push_error_scope(k);
        f();
    }
}

Try / catch

// panics are not catchable idioms here; instead ensure same-thread pairing
std::thread::scope(|s| {
    s.spawn(|| {
        device.push_error_scope(ErrorFilter::Validation);
        device.pop_error_scope(); // same thread
    });
});

Prevention

When it happens

Trigger: Calling device.pop_error_scope() (or the internal equivalent) on a thread that never called push_error_scope; calling pop more times than push on that thread; popping a scope from a different thread than the one that pushed it.

Common situations: Multithreaded code where push happens on a worker thread but pop is awaited elsewhere; an unbalanced push/pop pair after an early-return; porting code that assumed error scopes were device-global like in some other APIs.

Related errors


AI-assisted analysis of gfx-rs/wgpu@3e11ff59bf (2026-09-03). Data as JSON: /api/errors/87d063e3d714cade. Report an issue: GitHub.