gfx-rs/wgpu · critical
Could not lock adapter context. This is most-likely a deadlo
Error message
Could not lock adapter context. This is most-likely a deadlock.
What it means
`AdapterContext::lock` on the WGL (Windows GL) backend tries to acquire the adapter's shared GL context mutex with a 1-second timeout (`try_lock_for`). If the lock cannot be acquired within `CONTEXT_LOCK_TIMEOUT_SECS`, it panics with this message to surface a suspected deadlock instead of hanging forever. The adapter context is shared between all devices/surfaces of one adapter, so any thread holding it longer than ~1 second triggers this in another thread.
Source
Thrown at wgpu-hal/src/gles/wgl.rs:70
}
pub fn raw_context(&self) -> *mut c_void {
match self.inner.lock().context {
Some(ref wgl) => wgl.context.0,
None => ptr::null_mut(),
}
}
/// Obtain a lock to the WGL context and get handle to the [`glow::Context`] that can be used to
/// do rendering.
#[track_caller]
pub fn lock(&self) -> AdapterContextLock<'_> {
let inner = self
.inner
// Don't lock forever. If it takes longer than 1 second to get the lock we've got a
// deadlock and should panic to show where we got stuck
.try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
.expect("Could not lock adapter context. This is most-likely a deadlock.");
if let Some(wgl) = &inner.context {
wgl.make_current(inner.device.dc).unwrap()
};
AdapterContextLock { inner }
}
/// Obtain a lock to the WGL context and get handle to the [`glow::Context`] that can be used to
/// do rendering.
///
/// Unlike [`lock`](Self::lock), this accepts a device to pass to `make_current` and exposes the error
/// when `make_current` fails.
#[track_caller]
fn lock_with_dc(&self, device: Gdi::HDC) -> windows::core::Result<AdapterContextLock<'_>> {
let inner = self
.inner
.try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))View on GitHub (pinned to 3e11ff59bf)
Solutions
- Audit for recursive locking: never call another wgpu method that takes the context while you already hold `AdapterContextLock` (e.g. inside `lock()`-based accessors like `GlTexture::raw`).
- Ensure every `AdapterContextLock` is dropped promptly; never hold it across `.await` or long-running GL calls.
- Route all GL/wgpu calls for that adapter through a single thread or serialize them with your own synchronization so contention stays below the 1s timeout.
- If contention is legitimately heavy, restructure to drop-and-reacquire frequently, or file an issue to raise `CONTEXT_LOCK_TIMEOUT_SECS` via a patched build.
Example fix
// before: nested lock deadlocks (same thread, non-reentrant)
let tex = unsafe { texture.lock() };
unsafe { queue.submit(...) }; // panics: submit locks the same context
// after
let tex_raw = unsafe { texture.lock() }.raw().clone();
drop(tex_raw_owner); // ensure lock released before further wgpu calls
unsafe { queue.submit(...) }; Defensive patterns
Strategy: validation
Validate before calling
// Rust: detect re-entrant lock attempts before they deadlock
// wgpu's AdapterContext uses a timed try_lock; emulate the guard discipline:
fn assert_not_holding<T>(guard_slot: &Option<std::sync::MutexGuard<'_, T>>) {
assert!(guard_slot.is_none(), "wgpu GL context lock already held on this thread: recursive call will deadlock");
} Type guard
// Rust: structural guard - refuse to run wgpu calls while a lock guard exists
struct GlGuardScope<'a>(Option<wgpu_hal::gles::AdapterContextLock<'a>>);
impl<'a> GlGuardScope<'a> {
fn enter(&mut self, ctx: &'a wgpu_hal::gles::AdapterContext) {
assert!(self.0.is_none(), "cannot nest AdapterContext locks on one thread");
self.0 = Some(ctx.lock());
}
} Try / catch
// panic must be caught at the FFI/task boundary, not around every call
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
device_queue_do_work();
}));
match result {
Ok(v) => v,
Err(p) => {
let msg = p.downcast_ref::<String>().map(String::as_str)
.or_else(|| p.downcast_ref::<&str>().copied()).unwrap_or("");
if msg.contains("Could not lock adapter context") {
restart_gl_thread(); // rebuild device after suspected deadlock
} else { std::panic::resume_unwind(p); }
}
} Prevention
- Never call wgpu methods that lock the AdapterContext while you already hold an AdapterContextLock / device.lock() guard on the same thread.
- Never hold the GL context guard across an .await point; drop it before yielding.
- Keep all GL work for one adapter on a dedicated thread so context acquisition is naturally serialized.
- Review code that clones raw GL handles (GlTexture::raw etc.) and make sure the guard is scoped as tightly as possible.
When it happens
Trigger: Any public API that needs the GL context (buffer/texture/queue operations, instance->adapter enumeration) while another thread holds `AdapterContextLock` for more than 1 second, or recursively locking the adapter context on the same thread (std sync RwLock/Mutex non-reentrant), or a lock leaked (not dropped) on another thread.
Common situations: Calling wgpu GL APIs from the same thread that already holds a lock (re-entrant call e.g. inside a winit callback that itself calls queue.submit); holding the lock across an await point; doing long GL work inside a closure that keeps `AdapterContextLock` alive; window teardown racing with rendering on another thread.
Related errors
- wgpu error: {err}
- Mismatched pop_error_scope call: no error scope for this thr
- Mismatched pop_error_scope call: error scopes must be popped
- Feature `MESH_SHADING` not enabled
- {:?} is not enabled for this backend
AI-assisted analysis of gfx-rs/wgpu@3e11ff59bf (2026-09-03).
Data as JSON: /api/errors/8a3c73c98683f4b1.
Report an issue: GitHub.