Fix TSAN errors in WaitAny

Bug: 345541614
Change-Id: I10af9e8fc307be2c12dbab35a8816463105c90b7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/192721
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Reviewed-by: Quyen Le <lehoangquyen@chromium.org>
diff --git a/src/dawn/native/ExecutionQueue.cpp b/src/dawn/native/ExecutionQueue.cpp
index adfeed6..75e9822 100644
--- a/src/dawn/native/ExecutionQueue.cpp
+++ b/src/dawn/native/ExecutionQueue.cpp
@@ -27,6 +27,8 @@
 
 #include "dawn/native/ExecutionQueue.h"
 
+#include <atomic>
+
 namespace dawn::native {
 
 ExecutionSerial ExecutionQueueBase::GetPendingCommandSerial() const {
@@ -38,7 +40,7 @@
 }
 
 ExecutionSerial ExecutionQueueBase::GetCompletedCommandSerial() const {
-    return mCompletedSerial;
+    return ExecutionSerial(mCompletedSerial.load(std::memory_order_acquire));
 }
 
 MaybeError ExecutionQueueBase::CheckPassedSerials() {
@@ -47,14 +49,13 @@
 
     DAWN_ASSERT(completedSerial <=
                 ExecutionSerial(mLastSubmittedSerial.load(std::memory_order_acquire)));
-    // completedSerial should not be less than mCompletedSerial unless it is 0.
-    // It can be 0 when there's no fences to check.
-    DAWN_ASSERT(completedSerial >= mCompletedSerial || completedSerial == ExecutionSerial(0));
 
-    if (completedSerial > mCompletedSerial) {
-        mCompletedSerial = completedSerial;
+    // Atomically set mCompletedSerial to completedSerial if completedSerial is larger.
+    uint64_t current = mCompletedSerial.load(std::memory_order_acquire);
+    while (uint64_t(completedSerial) > current &&
+           !mCompletedSerial.compare_exchange_weak(current, uint64_t(completedSerial),
+                                                   std::memory_order_acq_rel)) {
     }
-
     return {};
 }
 
@@ -70,8 +71,10 @@
 
 void ExecutionQueueBase::AssumeCommandsComplete() {
     // Bump serials so any pending callbacks can be fired.
+    // TODO(crbug.com/dawn/831): This is called during device destroy, which is not
+    // thread-safe yet. Two threads calling destroy would race setting these serials.
     uint64_t prev = mLastSubmittedSerial.fetch_add(1u, std::memory_order_release);
-    mCompletedSerial = ExecutionSerial(prev + 1);
+    mCompletedSerial.store(prev + 1u, std::memory_order_release);
 }
 
 void ExecutionQueueBase::IncrementLastSubmittedCommandSerial() {
@@ -79,8 +82,8 @@
 }
 
 bool ExecutionQueueBase::HasScheduledCommands() const {
-    return ExecutionSerial(mLastSubmittedSerial.load(std::memory_order_acquire)) >
-               mCompletedSerial ||
+    return mLastSubmittedSerial.load(std::memory_order_acquire) >
+               mCompletedSerial.load(std::memory_order_acquire) ||
            HasPendingCommands();
 }
 
diff --git a/src/dawn/native/ExecutionQueue.h b/src/dawn/native/ExecutionQueue.h
index 7901f82..5bd86ff 100644
--- a/src/dawn/native/ExecutionQueue.h
+++ b/src/dawn/native/ExecutionQueue.h
@@ -94,7 +94,7 @@
     // mLastSubmittedSerial tracks the last submitted command serial.
     // During device removal, the serials could be artificially incremented
     // to make it appear as if commands have been compeleted.
-    ExecutionSerial mCompletedSerial = kBeginningOfGPUTime;
+    std::atomic<uint64_t> mCompletedSerial = static_cast<uint64_t>(kBeginningOfGPUTime);
     std::atomic<uint64_t> mLastSubmittedSerial = static_cast<uint64_t>(kBeginningOfGPUTime);
 
     // Indicates whether the backend has pending commands to be submitted as soon as possible.
diff --git a/src/dawn/native/vulkan/QueueVk.cpp b/src/dawn/native/vulkan/QueueVk.cpp
index 8923720..298a9d9 100644
--- a/src/dawn/native/vulkan/QueueVk.cpp
+++ b/src/dawn/native/vulkan/QueueVk.cpp
@@ -27,6 +27,9 @@
 
 #include "dawn/native/vulkan/QueueVk.h"
 
+#include <optional>
+#include <utility>
+
 #include "dawn/common/Math.h"
 #include "dawn/native/Buffer.h"
 #include "dawn/native/CommandValidation.h"
@@ -161,7 +164,7 @@
             // Update fenceSerial since fence is ready.
             fenceSerial = tentativeSerial;
 
-            mUnusedFences.push_back(fence);
+            mUnusedFences->push_back(fence);
 
             DAWN_ASSERT(fenceSerial > GetCompletedCommandSerial());
             fencesInFlight->pop_front();
@@ -421,7 +424,7 @@
             // If submitting to the queue fails, move the fence back into the unused fence
             // list, as if it were never acquired. Not doing so would leak the fence since
             // it would be neither in the unused list nor in the in-flight list.
-            mUnusedFences.push_back(fence);
+            mUnusedFences->push_back(fence);
         });
     TRACE_EVENT_END0(device->GetPlatform(), Recording, "vkQueueSubmit");
 
@@ -464,12 +467,21 @@
     Device* device = ToBackend(GetDevice());
     VkDevice vkDevice = device->GetVkDevice();
 
-    if (!mUnusedFences.empty()) {
-        VkFence fence = mUnusedFences.back();
-        DAWN_TRY(CheckVkSuccess(device->fn.ResetFences(vkDevice, 1, &*fence), "vkResetFences"));
+    auto result =
+        mUnusedFences.Use([&](auto unusedFences) -> std::optional<ResultOrError<VkFence>> {
+            if (!unusedFences->empty()) {
+                VkFence fence = unusedFences->back();
+                DAWN_ASSERT(fence != VK_NULL_HANDLE);
+                DAWN_TRY(
+                    CheckVkSuccess(device->fn.ResetFences(vkDevice, 1, &*fence), "vkResetFences"));
 
-        mUnusedFences.pop_back();
-        return fence;
+                unusedFences->pop_back();
+                return fence;
+            }
+            return std::nullopt;
+        });
+    if (result) {
+        return std::move(*result);
     }
 
     VkFenceCreateInfo createInfo;
@@ -520,10 +532,12 @@
         }
     });
 
-    for (VkFence fence : mUnusedFences) {
-        device->fn.DestroyFence(vkDevice, fence, nullptr);
-    }
-    mUnusedFences.clear();
+    mUnusedFences.Use([&](auto unusedFences) {
+        for (VkFence fence : *unusedFences) {
+            device->fn.DestroyFence(vkDevice, fence, nullptr);
+        }
+        unusedFences->clear();
+    });
 
     QueueBase::DestroyImpl();
 }
diff --git a/src/dawn/native/vulkan/QueueVk.h b/src/dawn/native/vulkan/QueueVk.h
index 2e4b823..cec9cb6 100644
--- a/src/dawn/native/vulkan/QueueVk.h
+++ b/src/dawn/native/vulkan/QueueVk.h
@@ -83,7 +83,7 @@
     // have finished.
     MutexProtected<std::deque<std::pair<VkFence, ExecutionSerial>>> mFencesInFlight;
     // Fences in the unused list aren't reset yet.
-    std::vector<VkFence> mUnusedFences;
+    MutexProtected<std::vector<VkFence>> mUnusedFences;
 
     MaybeError PrepareRecordingContext();
     ResultOrError<CommandPoolAndBuffer> BeginVkCommandBuffer();
diff --git a/src/dawn/tests/end2end/CreatePipelineAsyncTests.cpp b/src/dawn/tests/end2end/CreatePipelineAsyncTests.cpp
index fee0053..317220c 100644
--- a/src/dawn/tests/end2end/CreatePipelineAsyncTests.cpp
+++ b/src/dawn/tests/end2end/CreatePipelineAsyncTests.cpp
@@ -172,6 +172,9 @@
 TEST_P(CreatePipelineAsyncTest, CreateComputePipelineAsyncStress) {
     DAWN_TEST_UNSUPPORTED_IF(UsesWire());
 
+    // TODO(crbug.com/dawn/1766): TSAN reported race conditions in NVIDIA's vk driver.
+    DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia() && IsTsan());
+
     for (size_t i = 0; i < 100; i++) {
         wgpu::ComputePipelineDescriptor csDesc;
         std::string shader = R"(
@@ -204,6 +207,9 @@
     // eglMakeCurrent: Context can only be current on one thread
     DAWN_TEST_UNSUPPORTED_IF(IsOpenGL() || IsOpenGLES());
 
+    // TODO(crbug.com/dawn/1766): TSAN reported race conditions in NVIDIA's vk driver.
+    DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia() && IsTsan());
+
     auto f = [&](size_t t) {
         wgpu::ComputePipelineDescriptor csDesc;
         std::string shader = R"(
@@ -335,6 +341,9 @@
 TEST_P(CreatePipelineAsyncTest, CreateRenderPipelineAsyncStress) {
     DAWN_TEST_UNSUPPORTED_IF(UsesWire());
 
+    // TODO(crbug.com/dawn/1766): TSAN reported race conditions in NVIDIA's vk driver.
+    DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia() && IsTsan());
+
     for (size_t i = 0; i < 100; i++) {
         utils::ComboRenderPipelineDescriptor desc;
         std::string shader = R"(
@@ -363,6 +372,9 @@
     // eglMakeCurrent: Context can only be current on one thread
     DAWN_TEST_UNSUPPORTED_IF(IsOpenGL() || IsOpenGLES());
 
+    // TODO(crbug.com/dawn/1766): TSAN reported race conditions in NVIDIA's vk driver.
+    DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia() && IsTsan());
+
     auto f = [&](size_t t) {
         utils::ComboRenderPipelineDescriptor desc;
         std::string shader = R"(