Skip to content

Surface::drop panics after device loss #9277

@GeneralGDA

Description

@GeneralGDA

Surface::drop panics "Trying to destroy a SwapchainAcquireSemaphore that is still in use by a SurfaceTexture" after device loss


After a GPU device loss, dropping a wgpu::Surface that had a frame in progress panics with:

thread 'main' panicked at wgpu-hal-29.0.0/src/vulkan/swapchain/native.rs:359:
Trying to destroy a SwapchainAcquireSemaphore that is still in use by a SurfaceTexture

Steps to Reproduce

  1. Call Surface::get_current_texture() successfully — this sets Presentation::acquired_texture = Some(...) inside wgpu-core and clones the Arc<SwapchainAcquireSemaphore> into the HAL SurfaceTexture (refcount goes 1 → 2).
  2. Submit GPU work with Queue::submit() — device loss is detected here; wgpu-core calls Device::lose(), marking the device invalid, and fires the set_device_lost_callback callback.
  3. Call SurfaceTexture::present() — this calls wgpu_core::Surface::present(), which calls device.check_is_valid(), gets Err(DeviceError::Lost), and returns early without taking acquired_texture. The field remains Some(...).
  4. Drop the wgpu::Surface (e.g. while recreating graphics resources after device loss) — Drop for wgpu_core::Surface runs:
// wgpu-core/src/instance.rs
impl Drop for Surface {
    fn drop(&mut self) {
        if let Some(present) = self.presentation.lock().take() {
            for (&backend, surface) in &self.surface_per_backend {
                if backend == present.device.backend() {
                    unsafe { surface.unconfigure(present.device.raw()) }; // ← called here
                }
            }
        } // ← `present` (with non-None acquired_texture) dropped HERE
    }
}

surface.unconfigure() is called while present is still in scope, so acquired_texture is still alive. It holds an Arc<SwapchainAcquireSemaphore> clone (refcount = 2). Inside unconfigure():

// wgpu-hal/src/vulkan/swapchain/native.rs
unsafe fn release_resources(&mut self, device: &super::Device) {
    // ...
    for semaphore in self.acquire_semaphores.drain(..) {
        let arc_removed = Arc::into_inner(semaphore).expect(
            "Trying to destroy a SwapchainAcquireSemaphore that is still in use by a SurfaceTexture",
        ); // ← PANIC: Arc refcount is 2, not 1
    }
}

Arc::into_inner() returns None because refcount = 2, causing the panic.

Root Cause

Drop for wgpu_core::Surface violates unconfigure()'s own documented contract ("no resources derived from the swapchain in use") because it does not drain acquired_texture before calling unconfigure().

The underlying cause is that wgpu_core::Surface::present() (and discard()) guard themselves with device.check_is_valid()? and return early on device loss, leaving acquired_texture populated. The Drop impl doesn't account for this case.

Expected Behavior

Dropping a wgpu::Surface should not panic, regardless of device state.

Environment

  • wgpu version: 29.0.0
  • wgpu-hal version: 29.0.0
  • wgpu-core version: 29.0.0
  • Backend: Vulkan
  • OS: Windows 11
  • GPU: GeForce 5090 mobile

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions