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