reland: Use mutex with DeviceVk deallocation queue

DeviceVk::mDescriptorAllocatorsPendingDeallocation can be accessed from
an arbitrary thread without holding the device mutex. BindGroup
destruction ends up appending values to the queue in
Device::EnqueueDeferredDeallocation(). Add a mutex to protect
mDescriptorAllocatorsPendingDeallocation against data races.

The first time this CL landed introduced a lock-order-inversion when the
DescriptorSetAllocator lock and mDescriptorAllocatorsPendingDeallocation
locks were held at the time but acquired in different orders. Release
DescriptorSetAllocator lock before calling into
EnqueueDeferredDeallocation() to avoid this.

Bug: 372651189
Change-Id: Ie8df192528776e538eb7f4b1c5d28dd8e3553778
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/216114
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Kyle Charbonneau <kylechar@google.com>
Reviewed-by: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/native/vulkan/DescriptorSetAllocator.cpp b/src/dawn/native/vulkan/DescriptorSetAllocator.cpp
index 123718d..6b70b4f 100644
--- a/src/dawn/native/vulkan/DescriptorSetAllocator.cpp
+++ b/src/dawn/native/vulkan/DescriptorSetAllocator.cpp
@@ -118,25 +118,36 @@
 }
 
 void DescriptorSetAllocator::Deallocate(DescriptorSetAllocation* allocationInfo) {
-    Mutex::AutoLock lock(&mMutex);
-
-    DAWN_ASSERT(allocationInfo != nullptr);
-    DAWN_ASSERT(allocationInfo->set != VK_NULL_HANDLE);
-
-    // We can't reuse the descriptor set right away because the Vulkan spec says in the
-    // documentation for vkCmdBindDescriptorSets that the set may be consumed any time between
-    // host execution of the command and the end of the draw/dispatch.
+    bool enqueueDeferredDeallocation = false;
     Device* device = ToBackend(GetDevice());
-    const ExecutionSerial serial = device->GetQueue()->GetPendingCommandSerial();
-    mPendingDeallocations.Enqueue({allocationInfo->poolIndex, allocationInfo->setIndex}, serial);
 
-    if (mLastDeallocationSerial != serial) {
-        device->EnqueueDeferredDeallocation(this);
-        mLastDeallocationSerial = serial;
+    {
+        Mutex::AutoLock lock(&mMutex);
+
+        DAWN_ASSERT(allocationInfo != nullptr);
+        DAWN_ASSERT(allocationInfo->set != VK_NULL_HANDLE);
+
+        // We can't reuse the descriptor set right away because the Vulkan spec says in the
+        // documentation for vkCmdBindDescriptorSets that the set may be consumed any time between
+        // host execution of the command and the end of the draw/dispatch.
+        const ExecutionSerial serial = device->GetQueue()->GetPendingCommandSerial();
+        mPendingDeallocations.Enqueue({allocationInfo->poolIndex, allocationInfo->setIndex},
+                                      serial);
+
+        if (mLastDeallocationSerial != serial) {
+            enqueueDeferredDeallocation = true;
+            mLastDeallocationSerial = serial;
+        }
+
+        // Clear the content of allocation so that use after frees are more visible.
+        *allocationInfo = {};
     }
 
-    // Clear the content of allocation so that use after frees are more visible.
-    *allocationInfo = {};
+    if (enqueueDeferredDeallocation) {
+        // Release lock before calling EnqueueDeferredDeallocation() to avoid lock acquisition
+        // order inversion with lock used there.
+        device->EnqueueDeferredDeallocation(this);
+    }
 }
 
 void DescriptorSetAllocator::FinishDeallocation(ExecutionSerial completedSerial) {
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index 22c080c..7d0e740 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -332,11 +332,12 @@
     ExecutionSerial completedSerial = queue->GetCompletedCommandSerial();
     queue->RecycleCompletedCommands(completedSerial);
 
-    for (Ref<DescriptorSetAllocator>& allocator :
-         mDescriptorAllocatorsPendingDeallocation.IterateUpTo(completedSerial)) {
-        allocator->FinishDeallocation(completedSerial);
-    }
-    mDescriptorAllocatorsPendingDeallocation.ClearUpTo(completedSerial);
+    mDescriptorAllocatorsPendingDeallocation.Use([&](auto pending) {
+        for (Ref<DescriptorSetAllocator>& allocator : pending->IterateUpTo(completedSerial)) {
+            allocator->FinishDeallocation(completedSerial);
+        }
+        pending->ClearUpTo(completedSerial);
+    });
 
     GetResourceMemoryAllocator()->Tick(completedSerial);
     GetFencedDeleter()->Tick(completedSerial);
@@ -383,8 +384,8 @@
 }
 
 void Device::EnqueueDeferredDeallocation(DescriptorSetAllocator* allocator) {
-    mDescriptorAllocatorsPendingDeallocation.Enqueue(allocator,
-                                                     GetQueue()->GetPendingCommandSerial());
+    mDescriptorAllocatorsPendingDeallocation->Enqueue(allocator,
+                                                      GetQueue()->GetPendingCommandSerial());
 }
 
 ResultOrError<VulkanDeviceKnobs> Device::CreateDevice(VkPhysicalDevice vkPhysicalDevice) {
@@ -898,11 +899,12 @@
 
     ToBackend(GetPhysicalDevice())->GetVulkanInstance()->StopListeningForDeviceMessages(this);
 
-    for (Ref<DescriptorSetAllocator>& allocator :
-         mDescriptorAllocatorsPendingDeallocation.IterateUpTo(kMaxExecutionSerial)) {
-        allocator->FinishDeallocation(kMaxExecutionSerial);
-    }
-    mDescriptorAllocatorsPendingDeallocation.ClearUpTo(kMaxExecutionSerial);
+    mDescriptorAllocatorsPendingDeallocation.Use([&](auto pending) {
+        for (Ref<DescriptorSetAllocator>& allocator : pending->IterateUpTo(kMaxExecutionSerial)) {
+            allocator->FinishDeallocation(kMaxExecutionSerial);
+        }
+        pending->ClearUpTo(kMaxExecutionSerial);
+    });
 
     // Releasing the uploader enqueues buffers to be released.
     // Call Tick() again to clear them before releasing the deleter.
diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h
index 75df610..355a704 100644
--- a/src/dawn/native/vulkan/DeviceVk.h
+++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -187,7 +187,8 @@
     VkDevice mVkDevice = VK_NULL_HANDLE;
     uint32_t mMainQueueFamily = 0;
 
-    SerialQueue<ExecutionSerial, Ref<DescriptorSetAllocator>>
+    // Entries can be appended without holding the device mutex.
+    MutexProtected<SerialQueue<ExecutionSerial, Ref<DescriptorSetAllocator>>>
         mDescriptorAllocatorsPendingDeallocation;
     std::unique_ptr<MutexProtected<FencedDeleter>> mDeleter;
     std::unique_ptr<MutexProtected<ResourceMemoryAllocator>> mResourceMemoryAllocator;