Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: 95c3cfc258782ea42c3f0a8b570e6bb317c9a256
      
https://github.com/WebKit/WebKit/commit/95c3cfc258782ea42c3f0a8b570e6bb317c9a256
  Author: Ruthvik Konda <[email protected]>
  Date:   2026-09-02 (Wed, 02 Sep 2026)

  Changed paths:
    A 
LayoutTests/ipc/webgpu-command-encoder-cross-thread-destruction-race-expected.txt
    A LayoutTests/ipc/webgpu-command-encoder-cross-thread-destruction-race.html
    A LayoutTests/ipc/webgpu-instance-cross-thread-destruction-race-expected.txt
    A LayoutTests/ipc/webgpu-instance-cross-thread-destruction-race.html
    M Source/WebGPU/WebGPU/CommandBuffer.mm
    M Source/WebGPU/WebGPU/Instance.h
    M Source/WebGPU/WebGPU/Instance.mm

  Log Message:
  -----------
  [WebGPU] ~CommandBuffer can run on Metal completion thread, racing non-atomic 
CommandEncoder refcount and Device::m_commandEncoderMap
https://bugs.webkit.org/show_bug.cgi?id=315794
rdar://176483048

Reviewed by Mike Wyrzykowski.

CommandBuffer::makeInvalidDueToCommit registers an addCompletedHandler block 
whose body
bounces cleanup work to the WebGPU work queue via Queue::scheduleWork. The 
inner lambda
captures a strong Ref<CommandBuffer>. During RemoteGPU teardown, 
StreamConnectionWorkQueue::dispatch
silently drops the lambda when m_shouldQuit is set; the dropped lambda's 
Ref<CommandBuffer>
is destroyed on com.Metal.CompletionQueueDispatch. If that is the last ref, 
~CommandBuffer
runs on the Metal thread and (a) non-atomically derefs m_commandEncoder 
(CommandEncoder is
RefCountedAndCanMakeWeakPtr), racing the work-queue thread's deref via 
ObjectHeap::clear()
-> torn refcount -> UAF; and (b) ~CommandEncoder mutates the unlocked 
Device::m_commandEncoderMap,
racing the work-queue thread's concurrent removeCommandEncoder.

Additionally, every WebGPU addCompletedHandler/addScheduledHandler that calls
Queue::scheduleWork creates a temporary RefPtr<Instance> via m_instance.get() 
on the Metal
thread; if that temp outlives the work-queue's wgpuInstanceRelease deref, 
~Instance runs on
the Metal thread and destroys whatever Ref<Device>/Ref<CommandBuffer> Instance 
owns there.

Fix:

1. Anchor each committed CommandBuffer in 
Instance::m_retainedCommandBufferInstances so the
   Metal-thread block's deref is never the last. The anchor is keyed by a
   WeakObjCPtr<id<MTLCommandBuffer>> and lazily pruned when that pointer goes 
nil; nil
   implies MTLCommandBuffer dealloc, which only happens after Metal's
   didCompleteWithStartTime: has Block_release()d every completion handler, so 
the outer
   block's Ref<CommandBuffer> is already gone by the time the anchor is dropped.

2. Drain all retained MTLCommandBuffers (waitUntilCompleted) in 
wgpuInstanceRelease before
   deref(), so the C-API ref is alive while completion handlers run -> no 
Metal-thread
   RefPtr<Instance> temporary can be the last -> ~Instance and the anchors are 
released on
   the work-queue thread.

3. Change the inner lambda to capture ThreadSafeWeakPtr<CommandBuffer>. This is 
load-bearing,
   not just defense-in-depth: Instance::scheduleWork wraps the inner lambda in 
an ObjC block
   via makeBlockPtr().get(), and that wrapper is autoreleased on the Metal 
thread. Its pool
   drains at _dispatch_last_resort_autorelease_pool_pop, which is after 
waitUntilCompleted
   returns (after _completedCallbacksDone). With a strong inner capture, the 
autoreleased
   block copy holds a Ref<CommandBuffer> past the anchor's lifetime, and 
~CommandBuffer runs
   on the Metal thread when the pool drains. With a weak capture, the 
autoreleased copy
   holds nothing that destructs cross-thread.

retainCommandBuffer, retainDevice, and waitForCommandBufferCompletions all run 
on the
single per-Instance StreamConnectionWorkQueue thread (Instance is 
per-RemoteGPU, not
per-process), so the m_lock acquire around the retention containers is 
unnecessary; drop it
along with the WTF_GUARDED_BY_LOCK annotations. m_lock continues to guard 
m_pendingWork for
the unused defaultScheduleWork/processEvents fallback.

Regressed in 288538@main.

Tests: ipc/webgpu-command-encoder-cross-thread-destruction-race.html
       ipc/webgpu-instance-cross-thread-destruction-race.html

* 
LayoutTests/ipc/webgpu-command-encoder-cross-thread-destruction-race-expected.txt:
 Added.
* LayoutTests/ipc/webgpu-command-encoder-cross-thread-destruction-race.html: 
Added.
* LayoutTests/ipc/webgpu-instance-cross-thread-destruction-race-expected.txt: 
Added.
* LayoutTests/ipc/webgpu-instance-cross-thread-destruction-race.html: Added.
* Source/WebGPU/WebGPU/CommandBuffer.mm:
(WebGPU::CommandBuffer::makeInvalidDueToCommit):
* Source/WebGPU/WebGPU/Instance.h:
* Source/WebGPU/WebGPU/Instance.mm:
(WebGPU::Instance::waitForCommandBufferCompletions):
(WebGPU::Instance::retainDevice):
(WebGPU::Instance::retainCommandBuffer):
(wgpuInstanceRelease):

Originally-landed-as: [email protected] (c1b3074a6202). 
rdar://185367024
Canonical link: https://commits.webkit.org/320361@main



To unsubscribe from these emails, change your notification settings at 
https://github.com/WebKit/WebKit/settings/notifications

Reply via email to