gfx-rs/wgpu · error

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 panics when the thread-local error scope count is 0 (no matching push) or when scopes are popped out of order. Error scopes in the web backend are tracked per thread, so popping from a different thread than the push, or an extra/mismatched pop, is a programming error rather than a GPU error.

Source

Thrown at wgpu/src/backend/webgpu.rs:2780

        let index = self.error_scope_count.get();
        self.error_scope_count.set(
            index
                .checked_add(1)
                .expect("Greater than 2^32 nested error scopes"),
        );
        self.inner.push_error_scope(match filter {
            crate::ErrorFilter::OutOfMemory => webgpu_sys::GpuErrorFilter::OutOfMemory,
            crate::ErrorFilter::Validation => webgpu_sys::GpuErrorFilter::Validation,
            crate::ErrorFilter::Internal => webgpu_sys::GpuErrorFilter::Internal,
        });
        index
    }

    fn pop_error_scope(&self, index: u32) -> Pin<Box<dyn dispatch::PopErrorScopeFuture>> {
        let current_scope_count = self.error_scope_count.get();
        let is_panicking = crate::util::is_panicking();
        if current_scope_count == 0 && !is_panicking {
            panic!("Mismatched pop_error_scope call: no error scope for this thread. Error scopes are thread-local.");
        }
        if index + 1 != current_scope_count && !is_panicking {
            panic!(
                "Mismatched pop_error_scope call: error scopes must be popped in reverse order."
            );
        }
        // Decrement the error scope count. We've asserted that the current
        // size is `index + 1` above.
        self.error_scope_count.set(index);

        let error_promise = self.inner.pop_error_scope();
        Box::pin(MakeSendFuture::new(
            wasm_bindgen_futures::JsFuture::from(error_promise),
            future_pop_error_scope,
        ))
    }

    unsafe fn start_graphics_debugger_capture(&self) {

View on GitHub (pinned to 3e11ff59bf)

Solutions

  1. Ensure every pop_error_scope is called on the same thread as its matching push_error_scope; in async code, await the returned future rather than hopping threads.
  2. Balance push/pop pairs in RAII-style helpers or guaranteed control flow so counts never go negative or out of order.
  3. Pop scopes in reverse order (LIFO) when nesting error scopes.

Example fix

// before
let handle = device.push_error_scope(wgpu::ErrorFilter::Validation);
spawn(async move { let err = handle.pop().await; }); // different thread: panics
// after
let handle = device.push_error_scope(wgpu::ErrorFilter::Validation);
let err = handle.pop().await; // same thread, awaited in order
Defensive patterns

Strategy: try-catch

Validate before calling

// before popping, ensure the current scope exists (app-level counter mirrored from push calls)
assert!(active_error_scopes.get() > 0, "no error scope to pop on this thread");

Try / catch

// wgpu's pop_error_scope returns a future resolving to Result, but this panic is synchronous;
// prevent it structurally with an RAII guard:
struct ErrorScope<'a> { device: &'a wgpu::Device }
impl<'a> ErrorScope<'a> {
    async fn pop(self) -> Option<wgpu::Error> { self.device.pop_error_scope().await }
}
impl Drop for ErrorScope<'_> { /* ensure balanced push/pop per thread */ }

Prevention

When it happens

Trigger: Calling device.pop_error_scope without a preceding push_error_scope on the same thread; pushing on the main thread and popping inside an async callback/worker or wasm task that runs on another thread context; popping two scopes in the wrong order.

Common situations: Awaiting pop_error_scope across an async runtime boundary (spawned task) instead of the pushing thread; forgetting a push after early-return code paths; over-popping in nested scope helpers.

Related errors


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