Revert "Reland "[ios blink] Implement futures using WaitListEvent""
This reverts commit a218ceac197ff55273d80942e114eea9d6dcc90c.
Reason for revert: Linux TSAN failures in WaitListEventTests:
https://ci.chromium.org/ui/p/chromium/builders/ci/Dawn%20Linux%20TSAN%20Release/60645/overview
Bug: 407801085
Original change's description:
> Reland "[ios blink] Implement futures using WaitListEvent"
>
> This is a reland of commit abc42736ef28ed8fcc11e66814907febff6ee930
>
> The only change over the original is the correct initialization of the
> `mSignaled` atomic_bool in WaitListEvent.
>
> Original change's description:
> > [ios blink] Implement futures using WaitListEvent
> >
> > Currently, most futures are implemented using SystemEvent. This includes
> > already completed and non-progressing events. This is problematic on iOS
> > where BrowserEngineKit child processes are not allowed to open fds, mach
> > ports, etc.
> >
> > This CL introduces WaitListEvent which mimics the base::WaitableEvent
> > implementation in Chromium for POSIX platforms. The event internally
> > maintains a list of waiters corresponding to a WaitAny call. In WaitAny,
> > we create a SyncWaiter that's signaled using a condition variable. The
> > waiter is added to each event that the WaitAny is waiting on. The events
> > also have a mutex to allow multiple threads to wait on them. We acquire
> > the event locks in a globally consistent order (sorted by address) to
> > prevent lock order inversion. WaitListEvents can also be waited on
> > asynchronously by returning a SystemEventReceiver which allows mixing
> > waits on SystemEvents and WaitListEvents.
> >
> > In addition, this CL changes how already signaled and non-progressing
> > TrackedEvents are represented. Already signaled events are backed by
> > WaitListEvents and non-progressing events become a flag on TrackedEvent.
> >
> > The code in EventManager is also cleaned up to aid readability and fix
> > an edge case of waiting on multiple queues with zero timeout - we could
> > end up ticking the same queue multiple times causing a subtle race
> > between MapAsync and OnSubmittedWorkDone futures completion.
> >
> > Bug: 407801085
> > Change-Id: I1c5deb8097339be5beb5e9021d753998a074bea3
> > Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/234277
> > Reviewed-by: Loko Kung <lokokung@google.com>
> > Auto-Submit: Sunny Sachanandani <sunnyps@chromium.org>
> > Commit-Queue: Sunny Sachanandani <sunnyps@chromium.org>
> > Reviewed-by: Kai Ninomiya <kainino@chromium.org>
> > Commit-Queue: Kai Ninomiya <kainino@chromium.org>
>
> Bug: 407801085
> Change-Id: I254398256851f2310ddfe906c79d389dae1d3d77
> Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/237719
> Commit-Queue: Corentin Wallez <cwallez@chromium.org>
> Reviewed-by: Kai Ninomiya <kainino@chromium.org>
> Commit-Queue: Sunny Sachanandani <sunnyps@chromium.org>
> Reviewed-by: Corentin Wallez <cwallez@chromium.org>
> Commit-Queue: Kai Ninomiya <kainino@chromium.org>
> Auto-Submit: Sunny Sachanandani <sunnyps@chromium.org>
TBR=cwallez@chromium.org,kainino@chromium.org,dawn-scoped@luci-project-accounts.iam.gserviceaccount.com,sunnyps@chromium.org,lokokung@google.com
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 407801085
Change-Id: I9966a09a2dd202579046c87b6609b53fff12bfac
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/237721
Commit-Queue: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
Auto-Submit: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index d3bdd62..fd055fc 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -398,8 +398,6 @@
"ValidationUtils.h",
"VisitableMembers.h",
"WaitAnySystemEvent.h",
- "WaitListEvent.cpp",
- "WaitListEvent.h",
"dawn_platform.h",
"stream/BlobSource.cpp",
"stream/BlobSource.h",
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp
index 46aebb1..f99f976 100644
--- a/src/dawn/native/Buffer.cpp
+++ b/src/dawn/native/Buffer.cpp
@@ -212,7 +212,7 @@
const std::string& message,
WGPUMapAsyncStatus status)
: TrackedEvent(static_cast<wgpu::CallbackMode>(callbackInfo.mode),
- TrackedEvent::Completed{}),
+ SystemEvent::CreateSignaled()),
mBufferOrError(BufferErrorData{status, message}),
mCallback(callbackInfo.callback),
mUserdata1(callbackInfo.userdata1),
@@ -224,7 +224,7 @@
~MapAsyncEvent() override { EnsureComplete(EventCompletionType::Shutdown); }
void Complete(EventCompletionType completionType) override {
- if (const auto* queueAndSerial = GetIfQueueAndSerial()) {
+ if (const auto* queueAndSerial = std::get_if<QueueAndSerial>(&GetCompletionData())) {
TRACE_EVENT_ASYNC_END0(queueAndSerial->queue->GetDevice()->GetPlatform(), General,
"Buffer::APIMapAsync",
uint64_t(queueAndSerial->completionSerial));
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index 2ef2e07..06d7845 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -153,7 +153,6 @@
"ValidationUtils.h"
"VisitableMembers.h"
"WaitAnySystemEvent.h"
- "WaitListEvent.h"
"webgpu_absl_format.h"
)
@@ -254,7 +253,6 @@
"Toggles.cpp"
"utils/WGPUHelpers.cpp"
"ValidationUtils.cpp"
- "WaitListEvent.cpp"
"webgpu_absl_format.cpp"
)
diff --git a/src/dawn/native/CreatePipelineAsyncEvent.cpp b/src/dawn/native/CreatePipelineAsyncEvent.cpp
index 3599b45..844d1d2 100644
--- a/src/dawn/native/CreatePipelineAsyncEvent.cpp
+++ b/src/dawn/native/CreatePipelineAsyncEvent.cpp
@@ -41,7 +41,7 @@
#include "dawn/native/EventManager.h"
#include "dawn/native/Instance.h"
#include "dawn/native/RenderPipeline.h"
-#include "dawn/native/WaitListEvent.h"
+#include "dawn/native/SystemEvent.h"
#include "dawn/native/dawn_platform_autogen.h"
#include "dawn/native/utils/WGPUHelpers.h"
#include "dawn/native/wgpu_structs_autogen.h"
@@ -96,8 +96,8 @@
DeviceBase* device,
const CreatePipelineAsyncCallbackInfo& callbackInfo,
Ref<PipelineType> pipeline,
- Ref<WaitListEvent> event)
- : TrackedEvent(static_cast<wgpu::CallbackMode>(callbackInfo.mode), std::move(event)),
+ Ref<SystemEvent> systemEvent)
+ : TrackedEvent(static_cast<wgpu::CallbackMode>(callbackInfo.mode), std::move(systemEvent)),
mCallback(callbackInfo.callback),
mUserdata1(callbackInfo.userdata1),
mUserdata2(callbackInfo.userdata2),
diff --git a/src/dawn/native/CreatePipelineAsyncEvent.h b/src/dawn/native/CreatePipelineAsyncEvent.h
index a6bd1e0..b2fefa6 100644
--- a/src/dawn/native/CreatePipelineAsyncEvent.h
+++ b/src/dawn/native/CreatePipelineAsyncEvent.h
@@ -56,12 +56,12 @@
public:
using CallbackType = decltype(std::declval<CreatePipelineAsyncCallbackInfo>().callback);
- // Create an event backed by the given wait list event (for async pipeline creation goes through
+ // Create an event backed by the given system event (for async pipeline creation goes through
// the backend).
CreatePipelineAsyncEvent(DeviceBase* device,
const CreatePipelineAsyncCallbackInfo& callbackInfo,
Ref<PipelineType> pipeline,
- Ref<WaitListEvent> event);
+ Ref<SystemEvent> systemEvent);
// Create an event that's ready at creation (for cached results)
CreatePipelineAsyncEvent(DeviceBase* device,
const CreatePipelineAsyncCallbackInfo& callbackInfo,
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index ce36dc6..e6f0c63 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -77,7 +77,6 @@
#include "dawn/native/SwapChain.h"
#include "dawn/native/Texture.h"
#include "dawn/native/ValidationUtils_autogen.h"
-#include "dawn/native/WaitListEvent.h"
#include "dawn/native/utils/WGPUHelpers.h"
#include "dawn/platform/DawnPlatform.h"
#include "dawn/platform/metrics/HistogramMacros.h"
@@ -155,7 +154,7 @@
DeviceBase::DeviceLostEvent::DeviceLostEvent(const WGPUDeviceLostCallbackInfo& callbackInfo)
: TrackedEvent(static_cast<wgpu::CallbackMode>(callbackInfo.mode),
- TrackedEvent::NonProgressing{}),
+ SystemEvent::CreateNonProgressingEvent()),
mCallback(callbackInfo.callback),
mUserdata1(callbackInfo.userdata1),
mUserdata2(callbackInfo.userdata2) {}
@@ -1283,7 +1282,7 @@
// New pipeline: create an event backed by system event that is really async.
Ref<CreateComputePipelineAsyncEvent> event = AcquireRef(new CreateComputePipelineAsyncEvent(
this, callbackInfo, std::move(uninitializedComputePipeline),
- AcquireRef(new WaitListEvent())));
+ AcquireRef(new SystemEvent())));
Future future = GetFuture(event);
InitializeComputePipelineAsyncImpl(std::move(event));
return future;
@@ -1351,8 +1350,7 @@
// New pipeline: create an event backed by system event that is really async.
Ref<CreateRenderPipelineAsyncEvent> event = AcquireRef(new CreateRenderPipelineAsyncEvent(
- this, callbackInfo, std::move(uninitializedRenderPipeline),
- AcquireRef(new WaitListEvent())));
+ this, callbackInfo, std::move(uninitializedRenderPipeline), AcquireRef(new SystemEvent())));
Future future = GetFuture(event);
InitializeRenderPipelineAsyncImpl(std::move(event));
return future;
diff --git a/src/dawn/native/EventManager.cpp b/src/dawn/native/EventManager.cpp
index 2780fe2..835f92c 100644
--- a/src/dawn/native/EventManager.cpp
+++ b/src/dawn/native/EventManager.cpp
@@ -29,7 +29,6 @@
#include <algorithm>
#include <functional>
-#include <type_traits>
#include <utility>
#include <vector>
@@ -59,186 +58,199 @@
bool ready;
};
-// Wrapper around an iterator to yield event specific objects and a pointer
-// to the ready bool. We pass this into helpers so that they can extract
-// the event specific objects and get pointers to the ready status - without
-// allocating duplicate storage to store the objects and ready bools.
-template <typename Traits>
-class WrappingIterator {
+// Wrapper around an iterator to yield system event receiver and a pointer
+// to the ready bool. We pass this into WaitAnySystemEvent so it can extract
+// the receivers and get pointers to the ready status - without allocating
+// duplicate storage to store the receivers and ready bools.
+class SystemEventAndReadyStateIterator {
public:
+ using WrappedIter = std::vector<TrackedFutureWaitInfo>::iterator;
+
// Specify required iterator traits.
- using value_type = typename Traits::value_type;
- using difference_type = typename Traits::WrappedIter::difference_type;
- using iterator_category = typename Traits::WrappedIter::iterator_category;
+ using value_type = std::pair<const SystemEventReceiver&, bool*>;
+ using difference_type = typename WrappedIter::difference_type;
+ using iterator_category = typename WrappedIter::iterator_category;
using pointer = value_type*;
using reference = value_type&;
- WrappingIterator() = default;
- WrappingIterator(const WrappingIterator&) = default;
- WrappingIterator& operator=(const WrappingIterator&) = default;
+ SystemEventAndReadyStateIterator() = default;
+ SystemEventAndReadyStateIterator(const SystemEventAndReadyStateIterator&) = default;
+ SystemEventAndReadyStateIterator& operator=(const SystemEventAndReadyStateIterator&) = default;
- explicit WrappingIterator(typename Traits::WrappedIter wrappedIt) : mWrappedIt(wrappedIt) {}
+ explicit SystemEventAndReadyStateIterator(WrappedIter wrappedIt) : mWrappedIt(wrappedIt) {}
- bool operator!=(const WrappingIterator& rhs) const { return rhs.mWrappedIt != mWrappedIt; }
- bool operator==(const WrappingIterator& rhs) const { return rhs.mWrappedIt == mWrappedIt; }
-
- difference_type operator-(const WrappingIterator& rhs) const {
+ bool operator!=(const SystemEventAndReadyStateIterator& rhs) const {
+ return rhs.mWrappedIt != mWrappedIt;
+ }
+ bool operator==(const SystemEventAndReadyStateIterator& rhs) const {
+ return rhs.mWrappedIt == mWrappedIt;
+ }
+ difference_type operator-(const SystemEventAndReadyStateIterator& rhs) const {
return mWrappedIt - rhs.mWrappedIt;
}
- WrappingIterator operator+(difference_type rhs) const {
- return WrappingIterator{mWrappedIt + rhs};
+ SystemEventAndReadyStateIterator operator+(difference_type rhs) const {
+ return SystemEventAndReadyStateIterator{mWrappedIt + rhs};
}
- WrappingIterator& operator++() {
+ SystemEventAndReadyStateIterator& operator++() {
++mWrappedIt;
return *this;
}
- value_type operator*() { return Traits::Deref(mWrappedIt); }
-
- private:
- typename Traits::WrappedIter mWrappedIt;
-};
-
-struct ExtractSystemEventAndReadyStateTraits {
- using WrappedIter = std::vector<TrackedFutureWaitInfo>::iterator;
- using value_type = std::pair<const SystemEventReceiver&, bool*>;
-
- static value_type Deref(const WrappedIter& wrappedIt) {
- if (auto event = wrappedIt->event->GetIfWaitListEvent()) {
- return {event->WaitAsync(), &wrappedIt->ready};
- }
- DAWN_ASSERT(wrappedIt->event->GetIfSystemEvent());
+ value_type operator*() {
return {
- wrappedIt->event->GetIfSystemEvent()->GetOrCreateSystemEventReceiver(),
- &wrappedIt->ready,
+ std::get<Ref<SystemEvent>>(mWrappedIt->event->GetCompletionData())
+ ->GetOrCreateSystemEventReceiver(),
+ &mWrappedIt->ready,
};
}
+
+ private:
+ WrappedIter mWrappedIt;
};
-using SystemEventAndReadyStateIterator = WrappingIterator<ExtractSystemEventAndReadyStateTraits>;
-
-struct ExtractWaitListEventAndReadyStateTraits {
- using WrappedIter = std::vector<TrackedFutureWaitInfo>::iterator;
- using value_type = std::pair<Ref<WaitListEvent>, bool*>;
-
- static value_type Deref(const WrappedIter& wrappedIt) {
- DAWN_ASSERT(wrappedIt->event->GetIfWaitListEvent());
- return {wrappedIt->event->GetIfWaitListEvent(), &wrappedIt->ready};
- }
-};
-
-using WaitListEventAndReadyStateIterator =
- WrappingIterator<ExtractWaitListEventAndReadyStateTraits>;
-
-// Returns true if at least one future is ready.
-bool PollFutures(std::vector<TrackedFutureWaitInfo>& futures) {
+// Wait/poll the queue for futures in range [begin, end). `waitSerial` should be
+// the serial after which at least one future should be complete. All futures must
+// have completion data of type QueueAndSerial.
+// Returns true if at least one future is ready. If no futures are ready or the wait
+// timed out, returns false.
+bool WaitQueueSerialsImpl(DeviceBase* device,
+ QueueBase* queue,
+ ExecutionSerial waitSerial,
+ std::vector<TrackedFutureWaitInfo>::iterator begin,
+ std::vector<TrackedFutureWaitInfo>::iterator end,
+ Nanoseconds timeout) {
bool success = false;
- for (auto& future : futures) {
- if (future.event->IsReadyToComplete()) {
- success = true;
- future.ready = true;
+ // TODO(dawn:1662): Make error handling thread-safe.
+ auto deviceLock(device->GetScopedLock());
+ if (device->ConsumedError([&]() -> MaybeError {
+ if (waitSerial > queue->GetLastSubmittedCommandSerial()) {
+ // Serial has not been submitted yet. Submit it now.
+ // TODO(dawn:1413): This doesn't need to be a full tick. It just needs to
+ // flush work up to `waitSerial`. This should be done after the
+ // ExecutionQueue / ExecutionContext refactor.
+ queue->ForceEventualFlushOfCommands();
+ DAWN_TRY(device->Tick());
+ }
+ // Check the completed serial.
+ ExecutionSerial completedSerial = queue->GetCompletedCommandSerial();
+ if (completedSerial < waitSerial) {
+ if (timeout > Nanoseconds(0)) {
+ // Wait on the serial if it hasn't passed yet.
+ DAWN_TRY_ASSIGN(success, queue->WaitForQueueSerial(waitSerial, timeout));
+ }
+ // Update completed serials.
+ DAWN_TRY(queue->CheckPassedSerials());
+ completedSerial = queue->GetCompletedCommandSerial();
+ }
+ // Poll futures for completion.
+ for (auto it = begin; it != end; ++it) {
+ ExecutionSerial serial =
+ std::get<QueueAndSerial>(it->event->GetCompletionData()).completionSerial;
+ if (serial <= completedSerial) {
+ success = true;
+ it->ready = true;
+ }
+ }
+ return {};
+ }())) {
+ // There was an error. Pending submit may have failed or waiting for fences
+ // may have lost the device. The device is lost inside ConsumedError.
+ // Mark all futures as ready.
+ for (auto it = begin; it != end; ++it) {
+ it->ready = true;
}
+ success = true;
}
return success;
}
-// Wait/poll queues with given `timeout`. `queueWaitSerials` should contain per queue, the serial up
-// to which we should flush the queue if needed.
-using QueueWaitSerialsMap = absl::flat_hash_map<QueueBase*, ExecutionSerial>;
-void WaitQueueSerials(const QueueWaitSerialsMap& queueWaitSerials, Nanoseconds timeout) {
- // TODO(dawn:1662): Make error handling thread-safe.
- // Poll/wait on queues up to the lowest wait serial, but do this once per queue instead of
- // per event so that events with same serial complete at the same time instead of racing.
- for (const auto& queueAndSerial : queueWaitSerials) {
- auto* queue = queueAndSerial.first;
- auto waitSerial = queueAndSerial.second;
-
- auto* device = queue->GetDevice();
- auto deviceLock(device->GetScopedLock());
-
- [[maybe_unused]] bool error = device->ConsumedError(
- [&]() -> MaybeError {
- if (waitSerial > queue->GetLastSubmittedCommandSerial()) {
- // Serial has not been submitted yet. Submit it now.
- DAWN_TRY(queue->EnsureCommandsFlushed(waitSerial));
- }
- // Check the completed serial.
- if (waitSerial > queue->GetCompletedCommandSerial()) {
- if (timeout > Nanoseconds(0)) {
- // Wait on the serial if it hasn't passed yet.
- [[maybe_unused]] bool waitResult = false;
- DAWN_TRY_ASSIGN(waitResult, queue->WaitForQueueSerial(waitSerial, timeout));
- }
- // Update completed serials.
- DAWN_TRY(queue->CheckPassedSerials());
- }
- return {};
- }(),
- "waiting for work in %s.", queue);
- }
-}
-
// We can replace the std::vector& when std::span is available via C++20.
wgpu::WaitStatus WaitImpl(const InstanceBase* instance,
std::vector<TrackedFutureWaitInfo>& futures,
Nanoseconds timeout) {
- bool foundSystemEvent = false;
- bool foundWaitListEvent = false;
- QueueWaitSerialsMap queueLowestWaitSerials;
+ auto begin = futures.begin();
+ const auto end = futures.end();
+ bool anySuccess = false;
+ // The following loop will partition [begin, end) based on the type of wait is required.
+ // After each partition, it will wait/poll on the first partition, then advance `begin`
+ // to the start of the next partition. Note that for timeout > 0 and unsupported mixed
+ // sources, we validate that there is a single partition. If there is only one, then the
+ // loop runs only once and the timeout does not stack.
+ while (begin != end) {
+ const auto& first = begin->event->GetCompletionData();
- for (const auto& future : futures) {
- if (future.event->GetIfSystemEvent()) {
- foundSystemEvent = true;
+ DeviceBase* waitDevice;
+ ExecutionSerial lowestWaitSerial;
+ if (std::holds_alternative<Ref<SystemEvent>>(first)) {
+ waitDevice = nullptr;
+ } else {
+ const auto& queueAndSerial = std::get<QueueAndSerial>(first);
+ waitDevice = queueAndSerial.queue->GetDevice();
+ lowestWaitSerial = queueAndSerial.completionSerial;
}
- if (future.event->GetIfWaitListEvent()) {
- foundWaitListEvent = true;
- }
- if (const auto* queueAndSerial = future.event->GetIfQueueAndSerial()) {
- auto [it, inserted] = queueLowestWaitSerials.insert(
- {queueAndSerial->queue.Get(), queueAndSerial->completionSerial});
- if (!inserted) {
- it->second = std::min(it->second, queueAndSerial->completionSerial);
+ // Partition the remaining futures based on whether they match the same completion
+ // data type as the first. Also keep track of the lowest wait serial.
+ const auto mid =
+ std::partition(std::next(begin), end, [&](const TrackedFutureWaitInfo& info) {
+ const auto& completionData = info.event->GetCompletionData();
+ if (std::holds_alternative<Ref<SystemEvent>>(completionData)) {
+ return waitDevice == nullptr;
+ } else {
+ const auto& queueAndSerial = std::get<QueueAndSerial>(completionData);
+ if (waitDevice == queueAndSerial.queue->GetDevice()) {
+ lowestWaitSerial =
+ std::min(lowestWaitSerial, queueAndSerial.completionSerial);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ });
+
+ // There's a mix of wait sources if partition yielded an iterator that is not at the end.
+ if (mid != end) {
+ if (timeout > Nanoseconds(0)) {
+ // Multi-source wait is unsupported.
+ // TODO(dawn:2062): Implement support for this when the device supports it.
+ // It should eventually gather the lowest serial from the queue(s), transform them
+ // into completion events, and wait on all of the events. Then for any queues that
+ // saw a completion, poll all futures related to that queue for completion.
+ instance->EmitLog(WGPULoggingType_Error,
+ "Mixed source waits with timeouts are not currently supported.");
+ return wgpu::WaitStatus::Error;
}
}
- }
- if (timeout == Nanoseconds(0)) {
- // This is a no-op if `queueLowestWaitSerials` is empty.
- WaitQueueSerials(queueLowestWaitSerials, timeout);
- return PollFutures(futures) ? wgpu::WaitStatus::Success : wgpu::WaitStatus::TimedOut;
- }
+ bool success;
+ if (waitDevice) {
+ success = WaitQueueSerialsImpl(waitDevice, std::get<QueueAndSerial>(first).queue.Get(),
+ lowestWaitSerial, begin, mid, timeout);
+ } else {
+ if (timeout > Nanoseconds(0)) {
+ success = WaitAnySystemEvent(SystemEventAndReadyStateIterator{begin},
+ SystemEventAndReadyStateIterator{mid}, timeout);
+ } else {
+ // Poll the completion events.
+ success = false;
+ for (auto it = begin; it != mid; ++it) {
+ if (std::get<Ref<SystemEvent>>(it->event->GetCompletionData())->IsSignaled()) {
+ it->ready = true;
+ success = true;
+ }
+ }
+ }
+ }
+ anySuccess |= success;
- // We can't have a mix of system/wait-list events and queue-serial events or queue-serial events
- // from multiple queues with a non-zero timeout.
- if (queueLowestWaitSerials.size() > 1 ||
- (!queueLowestWaitSerials.empty() && (foundWaitListEvent || foundSystemEvent))) {
- // Multi-source wait is unsupported.
- // TODO(dawn:2062): Implement support for this when the device supports it.
- // It should eventually gather the lowest serial from the queue(s), transform them
- // into completion events, and wait on all of the events. Then for any queues that
- // saw a completion, poll all futures related to that queue for completion.
- instance->EmitLog(WGPULoggingType_Error,
- "Mixed source waits with timeouts are not currently supported.");
- return wgpu::WaitStatus::Error;
+ // Advance the iterator to the next partition.
+ begin = mid;
}
-
- bool success = false;
- if (foundSystemEvent) {
- // Can upgrade wait list events to system events.
- success = WaitAnySystemEvent(SystemEventAndReadyStateIterator{futures.begin()},
- SystemEventAndReadyStateIterator{futures.end()}, timeout);
- } else if (foundWaitListEvent) {
- success =
- WaitListEvent::WaitAny(WaitListEventAndReadyStateIterator{futures.begin()},
- WaitListEventAndReadyStateIterator{futures.end()}, timeout);
- } else {
- // This is a no-op if `queueLowestWaitSerials` is empty.
- WaitQueueSerials(queueLowestWaitSerials, timeout);
- success = PollFutures(futures);
+ if (!anySuccess) {
+ return wgpu::WaitStatus::TimedOut;
}
- return success ? wgpu::WaitStatus::Success : wgpu::WaitStatus::TimedOut;
+ return wgpu::WaitStatus::Success;
}
// Reorder callbacks to enforce callback ordering required by the spec.
@@ -315,7 +327,17 @@
// Handle the event now if it's spontaneous and ready.
if (event->mCallbackMode == wgpu::CallbackMode::AllowSpontaneous) {
- if (event->IsReadyToComplete()) {
+ bool isReady = false;
+ auto completionData = event->GetCompletionData();
+ if (std::holds_alternative<Ref<SystemEvent>>(completionData)) {
+ isReady = std::get<Ref<SystemEvent>>(completionData)->IsSignaled();
+ }
+ if (std::holds_alternative<QueueAndSerial>(completionData)) {
+ auto& queueAndSerial = std::get<QueueAndSerial>(completionData);
+ isReady = queueAndSerial.completionSerial <=
+ queueAndSerial.queue->GetCompletedCommandSerial();
+ }
+ if (isReady) {
event->EnsureComplete(EventCompletionType::Ready);
return futureID;
}
@@ -338,6 +360,15 @@
}
void EventManager::SetFutureReady(TrackedEvent* event) {
+ auto completionData = event->GetCompletionData();
+ if (std::holds_alternative<Ref<SystemEvent>>(completionData)) {
+ std::get<Ref<SystemEvent>>(completionData)->Signal();
+ }
+ if (std::holds_alternative<QueueAndSerial>(completionData)) {
+ auto& queueAndSerial = std::get<QueueAndSerial>(completionData);
+ queueAndSerial.completionSerial = queueAndSerial.queue->GetCompletedCommandSerial();
+ }
+
// Sometimes, events might become ready before they are even tracked. This can happen because
// tracking is ordered to uphold callback ordering, but events may become ready in any order. If
// the event is spontaneous, it will be completed when it is tracked.
@@ -345,8 +376,6 @@
return;
}
- event->SetReadyToComplete();
-
// Handle spontaneous completion now.
if (event->mCallbackMode == wgpu::CallbackMode::AllowSpontaneous) {
mEvents.Use([&](auto events) {
@@ -377,7 +406,11 @@
// Figure out if there are any progressing events. If we only have non-progressing
// events, we need to return false to indicate that there isn't any polling work to
// be done.
- if (event->IsProgressing()) {
+ auto completionData = event->GetCompletionData();
+ if (std::holds_alternative<Ref<SystemEvent>>(completionData)) {
+ hasProgressingEvents |=
+ std::get<Ref<SystemEvent>>(completionData)->IsProgressing();
+ } else {
hasProgressingEvents = true;
}
@@ -505,10 +538,6 @@
// EventManager::TrackedEvent
EventManager::TrackedEvent::TrackedEvent(wgpu::CallbackMode callbackMode,
- Ref<WaitListEvent> completionEvent)
- : mCallbackMode(callbackMode), mCompletionData(std::move(completionEvent)) {}
-
-EventManager::TrackedEvent::TrackedEvent(wgpu::CallbackMode callbackMode,
Ref<SystemEvent> completionEvent)
: mCallbackMode(callbackMode), mCompletionData(std::move(completionEvent)) {}
@@ -518,14 +547,7 @@
: mCallbackMode(callbackMode), mCompletionData(QueueAndSerial{queue, completionSerial}) {}
EventManager::TrackedEvent::TrackedEvent(wgpu::CallbackMode callbackMode, Completed tag)
- : mCallbackMode(callbackMode), mCompletionData(AcquireRef(new WaitListEvent())) {
- GetIfWaitListEvent()->Signal();
-}
-
-EventManager::TrackedEvent::TrackedEvent(wgpu::CallbackMode callbackMode, NonProgressing tag)
- : mCallbackMode(callbackMode),
- mCompletionData(AcquireRef(new WaitListEvent())),
- mIsProgressing(false) {}
+ : TrackedEvent(callbackMode, SystemEvent::CreateSignaled()) {}
EventManager::TrackedEvent::~TrackedEvent() {
DAWN_ASSERT(mFutureID != kNullFutureID);
@@ -536,31 +558,9 @@
return {mFutureID};
}
-bool EventManager::TrackedEvent::IsReadyToComplete() const {
- bool isReady = false;
- if (auto event = GetIfSystemEvent()) {
- isReady = event->IsSignaled();
- }
- if (auto event = GetIfWaitListEvent()) {
- isReady = event->IsSignaled();
- }
- if (const auto* queueAndSerial = GetIfQueueAndSerial()) {
- isReady =
- queueAndSerial->completionSerial <= queueAndSerial->queue->GetCompletedCommandSerial();
- }
- return isReady;
-}
-
-void EventManager::TrackedEvent::SetReadyToComplete() {
- if (auto event = GetIfSystemEvent()) {
- event->Signal();
- }
- if (auto event = GetIfWaitListEvent()) {
- event->Signal();
- }
- if (auto* queueAndSerial = std::get_if<QueueAndSerial>(&mCompletionData)) {
- queueAndSerial->completionSerial = queueAndSerial->queue->GetCompletedCommandSerial();
- }
+const EventManager::TrackedEvent::CompletionData& EventManager::TrackedEvent::GetCompletionData()
+ const {
+ return mCompletionData;
}
void EventManager::TrackedEvent::EnsureComplete(EventCompletionType completionType) {
diff --git a/src/dawn/native/EventManager.h b/src/dawn/native/EventManager.h
index a6a4ec0..6ef872b 100644
--- a/src/dawn/native/EventManager.h
+++ b/src/dawn/native/EventManager.h
@@ -43,7 +43,6 @@
#include "dawn/native/Forward.h"
#include "dawn/native/IntegerTypes.h"
#include "dawn/native/SystemEvent.h"
-#include "dawn/native/WaitListEvent.h"
#include "partition_alloc/pointers/raw_ptr.h"
namespace dawn::native {
@@ -117,58 +116,7 @@
// to any TrackedEvents. Any which are not ref'd elsewhere (in order to be `Spontaneous`ly
// completed) will be cleaned up at that time.
class EventManager::TrackedEvent : public RefCounted {
- public:
- // Subclasses must implement this to complete the event (if not completed) with
- // EventCompletionType::Shutdown.
- ~TrackedEvent() override;
-
- Future GetFuture() const;
-
- bool IsProgressing() const { return mIsProgressing; }
-
- bool IsReadyToComplete() const;
-
- const QueueAndSerial* GetIfQueueAndSerial() const {
- return std::get_if<QueueAndSerial>(&mCompletionData);
- }
-
- Ref<SystemEvent> GetIfSystemEvent() const {
- if (auto* event = std::get_if<Ref<SystemEvent>>(&mCompletionData)) {
- return *event;
- }
- return nullptr;
- }
-
- Ref<WaitListEvent> GetIfWaitListEvent() const {
- if (auto* event = std::get_if<Ref<WaitListEvent>>(&mCompletionData)) {
- return *event;
- }
- return nullptr;
- }
-
- // Events may be one of three types:
- // - A queue and the ExecutionSerial after which the event will be completed.
- // Used for queue completion.
- // - A SystemEvent which will be signaled usually by the OS / GPU driver. It stores a boolean
- // that we can check instead of polling with the OS, or it can be transformed lazily into a
- // SystemEventReceiver.
- // - A WaitListEvent which will be signaled from our code, usually on a separate thread. It also
- // stores an atomic boolean that we can check instead of waiting synchronously, or it can be
- // transformed into a SystemEventReceiver for asynchronous waits.
- // The queue ref creates a temporary ref cycle
- // (Queue->Device->Instance->EventManager->TrackedEvent). This is OK because the instance will
- // clear out the EventManager on shutdown.
- // TODO(crbug.com/dawn/2067): This is a bit fragile. Is it possible to remove the ref cycle?
protected:
- friend class EventManager;
-
- using CompletionData = std::variant<QueueAndSerial, Ref<SystemEvent>, Ref<WaitListEvent>>;
-
- // Create an event from a WaitListEvent that can be signaled and waited-on in user-space only in
- // the current process. Note that events like RequestAdapter and RequestDevice complete
- // immediately in dawn native, and may use an already-completed event.
- TrackedEvent(wgpu::CallbackMode callbackMode, Ref<WaitListEvent> completionEvent);
-
// Create an event from a SystemEvent. Note that events like RequestAdapter and
// RequestDevice complete immediately in dawn native, and may use an already-completed event.
TrackedEvent(wgpu::CallbackMode callbackMode, Ref<SystemEvent> completionEvent);
@@ -178,17 +126,33 @@
QueueBase* queue,
ExecutionSerial completionSerial);
- // Create a TrackedEvent that is already completed.
struct Completed {};
+ // Create a TrackedEvent that is already completed.
TrackedEvent(wgpu::CallbackMode callbackMode, Completed tag);
- // Some SystemEvents may be non-progressing, i.e. DeviceLost. We tag these events so that we can
- // correctly return whether there is progressing work when users are polling.
- struct NonProgressing {};
- TrackedEvent(wgpu::CallbackMode callbackMode, NonProgressing tag);
+ public:
+ // Subclasses must implement this to complete the event (if not completed) with
+ // EventCompletionType::Shutdown.
+ ~TrackedEvent() override;
- void SetReadyToComplete();
+ Future GetFuture() const;
+ // Events may be one of two types:
+ // - A queue and the ExecutionSerial after which the event will be completed.
+ // Used for queue completion.
+ // - A SystemEvent which will be signaled from our code, usually on a separate thread.
+ // It stores a boolean that we can check instead of polling with the OS, or it can be
+ // transformed lazily into a SystemEventReceiver. Used for async pipeline creation, and Metal
+ // queue completion.
+ // The queue ref creates a temporary ref cycle
+ // (Queue->Device->Instance->EventManager->TrackedEvent). This is OK because the instance will
+ // clear out the EventManager on shutdown.
+ // TODO(crbug.com/dawn/2067): This is a bit fragile. Is it possible to remove the ref cycle?
+ using CompletionData = std::variant<QueueAndSerial, Ref<SystemEvent>>;
+
+ const CompletionData& GetCompletionData() const;
+
+ protected:
void EnsureComplete(EventCompletionType);
virtual void Complete(EventCompletionType) = 0;
@@ -200,8 +164,9 @@
#endif
private:
+ friend class EventManager;
+
CompletionData mCompletionData;
- const bool mIsProgressing = true;
// Callback has been called.
std::atomic<bool> mCompleted = false;
};
diff --git a/src/dawn/native/Queue.cpp b/src/dawn/native/Queue.cpp
index ef30378..c4f3532 100644
--- a/src/dawn/native/Queue.cpp
+++ b/src/dawn/native/Queue.cpp
@@ -55,6 +55,7 @@
#include "dawn/native/QuerySet.h"
#include "dawn/native/RenderPassEncoder.h"
#include "dawn/native/RenderPipeline.h"
+#include "dawn/native/SystemEvent.h"
#include "dawn/native/Texture.h"
#include "dawn/platform/DawnPlatform.h"
#include "dawn/platform/tracing/TraceEvent.h"
diff --git a/src/dawn/native/Queue.h b/src/dawn/native/Queue.h
index 1e166a8..bcc3777 100644
--- a/src/dawn/native/Queue.h
+++ b/src/dawn/native/Queue.h
@@ -38,6 +38,7 @@
#include "dawn/native/Forward.h"
#include "dawn/native/IntegerTypes.h"
#include "dawn/native/ObjectBase.h"
+#include "dawn/native/SystemEvent.h"
#include "partition_alloc/pointers/raw_ptr.h"
#include "dawn/native/DawnNative.h"
diff --git a/src/dawn/native/SystemEvent.cpp b/src/dawn/native/SystemEvent.cpp
index 2b8fe06..f8dd1c8 100644
--- a/src/dawn/native/SystemEvent.cpp
+++ b/src/dawn/native/SystemEvent.cpp
@@ -52,7 +52,6 @@
SystemEventReceiver::SystemEventReceiver(SystemHandle primitive)
: mPrimitive(std::move(primitive)) {}
-// static
SystemEventReceiver SystemEventReceiver::CreateAlreadySignaled() {
SystemEventPipeSender sender;
SystemEventReceiver receiver;
@@ -132,6 +131,21 @@
// SystemEvent
// static
+Ref<SystemEvent> SystemEvent::CreateSignaled() {
+ auto ev = AcquireRef(new SystemEvent());
+ ev->Signal();
+ return ev;
+}
+
+// static
+Ref<SystemEvent> SystemEvent::CreateNonProgressingEvent() {
+ return AcquireRef(new SystemEvent(kNonProgressingPayload));
+}
+
+bool SystemEvent::IsProgressing() const {
+ return GetRefCountPayload() != kNonProgressingPayload;
+}
+
bool SystemEvent::IsSignaled() const {
return mSignaled.load(std::memory_order_acquire);
}
diff --git a/src/dawn/native/SystemEvent.h b/src/dawn/native/SystemEvent.h
index 2a649dc..391d430 100644
--- a/src/dawn/native/SystemEvent.h
+++ b/src/dawn/native/SystemEvent.h
@@ -105,6 +105,12 @@
class SystemEvent : public RefCounted {
public:
+ using RefCounted::RefCounted;
+
+ static Ref<SystemEvent> CreateSignaled();
+ static Ref<SystemEvent> CreateNonProgressingEvent();
+
+ bool IsProgressing() const;
bool IsSignaled() const;
void Signal();
@@ -113,6 +119,10 @@
const SystemEventReceiver& GetOrCreateSystemEventReceiver();
private:
+ // Some SystemEvents may be non-progressing, i.e. DeviceLost. We tag these events so that we can
+ // correctly return whether there is progressing work when users are polling.
+ static constexpr uint64_t kNonProgressingPayload = 1;
+
// mSignaled indicates whether the event has already been signaled.
// It is stored outside the mPipe mutex so its status can quickly be checked without
// acquiring a lock.
diff --git a/src/dawn/native/WaitListEvent.cpp b/src/dawn/native/WaitListEvent.cpp
deleted file mode 100644
index 8a2f422..0000000
--- a/src/dawn/native/WaitListEvent.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2025 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-// list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include "dawn/native/WaitListEvent.h"
-
-namespace dawn::native {
-
-WaitListEvent::WaitListEvent() = default;
-WaitListEvent::~WaitListEvent() = default;
-
-bool WaitListEvent::IsSignaled() const {
- return mSignaled.load(std::memory_order_acquire);
-}
-
-void WaitListEvent::Signal() {
- std::lock_guard<std::mutex> lock(mMutex);
- DAWN_ASSERT(!mSignaled);
- mSignaled.store(true, std::memory_order_release);
- for (SyncWaiter* w : std::move(mSyncWaiters)) {
- {
- std::lock_guard<std::mutex> waiterLock(w->mutex);
- w->waitDone = true;
- }
- w->cv.notify_all();
- }
- for (auto& sender : std::move(mAsyncWaiters)) {
- std::move(sender).Signal();
- }
-}
-
-bool WaitListEvent::Wait(Nanoseconds timeout) {
- bool ready = false;
- std::array<std::pair<Ref<WaitListEvent>, bool*>, 1> events{{{this, &ready}}};
- return WaitListEvent::WaitAny(events.begin(), events.end(), timeout);
-}
-
-SystemEventReceiver WaitListEvent::WaitAsync() {
- std::lock_guard<std::mutex> lock(mMutex);
- if (IsSignaled()) {
- return SystemEventReceiver::CreateAlreadySignaled();
- }
- SystemEventPipeSender sender;
- SystemEventReceiver receiver;
- std::tie(sender, receiver) = CreateSystemEventPipe();
- mAsyncWaiters.push_back(std::move(sender));
- return receiver;
-}
-
-} // namespace dawn::native
diff --git a/src/dawn/native/WaitListEvent.h b/src/dawn/native/WaitListEvent.h
deleted file mode 100644
index 1addd70..0000000
--- a/src/dawn/native/WaitListEvent.h
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2025 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-// list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef SRC_DAWN_NATIVE_WAITLISTEVENT_H_
-#define SRC_DAWN_NATIVE_WAITLISTEVENT_H_
-
-#include <algorithm>
-#include <chrono>
-#include <condition_variable>
-#include <limits>
-#include <mutex>
-#include <utility>
-#include <vector>
-
-#include "dawn/common/RefCounted.h"
-#include "dawn/native/IntegerTypes.h"
-#include "dawn/native/SystemEvent.h"
-#include "partition_alloc/pointers/raw_ptr.h"
-
-namespace dawn::native {
-
-class WaitListEvent : public RefCounted {
- public:
- WaitListEvent();
-
- bool IsSignaled() const;
- void Signal();
- bool Wait(Nanoseconds timeout);
- SystemEventReceiver WaitAsync();
-
- template <typename It>
- static bool WaitAny(It eventAndReadyStateBegin, It eventAndReadyStateEnd, Nanoseconds timeout);
-
- private:
- ~WaitListEvent() override;
-
- struct SyncWaiter {
- std::condition_variable cv;
- std::mutex mutex;
- bool waitDone = false;
- };
-
- mutable std::mutex mMutex;
- std::atomic_bool mSignaled{false};
- std::vector<raw_ptr<SyncWaiter>> mSyncWaiters;
- std::vector<SystemEventPipeSender> mAsyncWaiters;
-};
-
-template <typename It>
-bool WaitListEvent::WaitAny(It eventAndReadyStateBegin,
- It eventAndReadyStateEnd,
- Nanoseconds timeout) {
- static_assert(std::is_base_of_v<std::random_access_iterator_tag,
- typename std::iterator_traits<It>::iterator_category>);
- static_assert(std::is_same_v<typename std::iterator_traits<It>::value_type,
- std::pair<Ref<WaitListEvent>, bool*>>);
-
- const size_t count = std::distance(eventAndReadyStateBegin, eventAndReadyStateEnd);
- if (count == 0) {
- return false;
- }
-
- struct EventState {
- WaitListEvent* event = nullptr;
- size_t origIndex;
- bool isReady = false;
- };
- std::vector<EventState> events(count);
-
- for (size_t i = 0; i < count; i++) {
- const auto& event = (*(eventAndReadyStateBegin + i)).first;
- events[i].event = event.Get();
- events[i].origIndex = i;
- }
- // Sort the events by address to get a globally consistent order.
- std::sort(events.begin(), events.end(),
- [](const auto& lhs, const auto& rhs) { return lhs.event < rhs.event; });
-
- // Acquire locks in order and enqueue our waiter.
- bool foundSignaled = false;
- for (size_t i = 0; i < count; i++) {
- WaitListEvent* event = events[i].event;
- // Skip over multiple waits on the same event, but ensure that we store the same ready state
- // for duplicates.
- if (i > 0 && event == events[i - 1].event) {
- events[i].isReady = events[i - 1].isReady;
- continue;
- }
- event->mMutex.lock();
- // Check `IsSignaled()` after acquiring the lock so that it doesn't become true immediately
- // before we acquire the lock - we assume that it is safe to enqueue our waiter after this
- // point if the event is not already signaled.
- if (event->IsSignaled()) {
- events[i].isReady = true;
- foundSignaled = true;
- }
- }
-
- // If any of the events were already signaled, early out after unlocking the events in reverse
- // order to prevent lock order inversion.
- if (foundSignaled) {
- for (size_t i = 0; i < count; i++) {
- WaitListEvent* event = events[count - 1 - i].event;
- // Use the cached value of `IsSignaled()` because we might have unlocked the event
- // already if it was a duplicate and checking `IsSignaled()` without the lock is racy
- // and can cause different values of isReady for multiple waits on the same event.
- if (events[count - 1 - i].isReady) {
- bool* isReady =
- (*(eventAndReadyStateBegin + events[count - 1 - i].origIndex)).second;
- *isReady = true;
- }
- // Skip over multiple waits on the same event.
- if (i > 0 && event == events[count - i].event) {
- continue;
- }
- event->mMutex.unlock();
- }
- return true;
- }
-
- // We have acquired locks for all the events we're going to wait on - enqueue our waiter now
- // after locking it since it could be woken up as soon as the event is unlocked and unlock the
- // events in reverse order now to prevent lock order inversion.
- SyncWaiter waiter;
- std::unique_lock<std::mutex> waiterLock(waiter.mutex);
- for (size_t i = 0; i < count; i++) {
- WaitListEvent* event = events[count - 1 - i].event;
- // Skip over multiple waits on the same event.
- if (i > 0 && event == events[count - i].event) {
- continue;
- }
- event->mSyncWaiters.push_back(&waiter);
- event->mMutex.unlock();
- }
-
- // Any values larger than those representatable by std::chrono::nanoseconds will be treated as
- // infinite waits - in particular this covers values greater than INT64_MAX.
- static constexpr uint64_t kMaxDurationNanos = std::chrono::nanoseconds::max().count();
- if (timeout > Nanoseconds(kMaxDurationNanos)) {
- waiter.cv.wait(waiterLock, [&waiter]() { return waiter.waitDone; });
- } else {
- waiter.cv.wait_for(waiterLock, std::chrono::nanoseconds(static_cast<uint64_t>(timeout)),
- [&waiter]() { return waiter.waitDone; });
- }
-
- // Remove our waiter from the events.
- for (size_t i = 0; i < count; i++) {
- WaitListEvent* event = events[i].event;
- // Skip over multiple waits on the same event, but ensure that we store the same ready state
- // for duplicates.
- if (i > 0 && event == events[i - 1].event) {
- events[i].isReady = events[i - 1].isReady;
- } else {
- // We could be woken by the condition variable before the atomic release store to
- // `mSignaled` is visible - locking the mutex ensures that the atomic acquire load in
- // `IsSignaled()` sees the correct value.
- std::lock_guard<std::mutex> eventLock(event->mMutex);
- if (event->IsSignaled()) {
- events[i].isReady = true;
- }
- event->mSyncWaiters.erase(
- std::remove(event->mSyncWaiters.begin(), event->mSyncWaiters.end(), &waiter),
- event->mSyncWaiters.end());
- }
- if (events[i].isReady) {
- bool* isReady = (*(eventAndReadyStateBegin + events[i].origIndex)).second;
- *isReady = true;
- foundSignaled = true;
- }
- }
-
- DAWN_ASSERT(!waiter.waitDone || foundSignaled);
- return foundSignaled;
-}
-
-} // namespace dawn::native
-
-#endif // SRC_DAWN_NATIVE_WAITLISTEVENT_H_
diff --git a/src/dawn/native/metal/QueueMTL.h b/src/dawn/native/metal/QueueMTL.h
index 1e03df3..67a11c2 100644
--- a/src/dawn/native/metal/QueueMTL.h
+++ b/src/dawn/native/metal/QueueMTL.h
@@ -35,7 +35,7 @@
#include "dawn/common/SerialMap.h"
#include "dawn/native/EventManager.h"
#include "dawn/native/Queue.h"
-#include "dawn/native/WaitListEvent.h"
+#include "dawn/native/SystemEvent.h"
#include "dawn/native/metal/CommandRecordingContext.h"
#include "dawn/native/metal/SharedFenceMTL.h"
@@ -53,7 +53,7 @@
id<MTLSharedEvent> GetMTLSharedEvent() const;
ResultOrError<Ref<SharedFence>> GetOrCreateSharedFence();
- Ref<WaitListEvent> CreateWorkDoneEvent(ExecutionSerial serial);
+ Ref<SystemEvent> CreateWorkDoneSystemEvent(ExecutionSerial serial);
ResultOrError<bool> WaitForQueueSerial(ExecutionSerial serial, Nanoseconds timeout) override;
private:
@@ -88,7 +88,7 @@
// TODO(crbug.com/dawn/2065): If we atomically knew a conservative lower bound on the
// mWaitingEvents serials, we could avoid taking this lock sometimes. Optimize if needed.
// See old draft code: https://dawn-review.googlesource.com/c/dawn/+/137502/29
- MutexProtected<SerialMap<ExecutionSerial, Ref<WaitListEvent>>> mWaitingEvents;
+ MutexProtected<SerialMap<ExecutionSerial, Ref<SystemEvent>>> mWaitingEvents;
// A shared event that can be exported for synchronization with other users of Metal.
// MTLSharedEvent is not available until macOS 10.14+ so use just `id`.
diff --git a/src/dawn/native/metal/QueueMTL.mm b/src/dawn/native/metal/QueueMTL.mm
index b51248a..f1d3f04f 100644
--- a/src/dawn/native/metal/QueueMTL.mm
+++ b/src/dawn/native/metal/QueueMTL.mm
@@ -33,6 +33,7 @@
#include "dawn/native/Commands.h"
#include "dawn/native/DynamicUploader.h"
#include "dawn/native/MetalBackend.h"
+#include "dawn/native/WaitAnySystemEvent.h"
#include "dawn/native/metal/CommandBufferMTL.h"
#include "dawn/native/metal/DeviceMTL.h"
#include "dawn/platform/DawnPlatform.h"
@@ -251,9 +252,10 @@
}
}
-Ref<WaitListEvent> Queue::CreateWorkDoneEvent(ExecutionSerial serial) {
- Ref<WaitListEvent> completionEvent = AcquireRef(new WaitListEvent());
+Ref<SystemEvent> Queue::CreateWorkDoneSystemEvent(ExecutionSerial serial) {
+ Ref<SystemEvent> completionEvent = AcquireRef(new SystemEvent());
mWaitingEvents.Use([&](auto events) {
+ SystemEventReceiver receiver;
// Now that we hold the lock, check against mCompletedSerial before inserting.
// This serial may have just completed. If it did, mark the event complete.
// Also check for device loss. Otherwise, we could enqueue the event
@@ -271,7 +273,11 @@
}
ResultOrError<bool> Queue::WaitForQueueSerial(ExecutionSerial serial, Nanoseconds timeout) {
- return CreateWorkDoneEvent(serial)->Wait(timeout);
+ Ref<SystemEvent> event = CreateWorkDoneSystemEvent(serial);
+ bool ready = false;
+ std::array<std::pair<const dawn::native::SystemEventReceiver&, bool*>, 1> events{
+ {{event->GetOrCreateSystemEventReceiver(), &ready}}};
+ return WaitAnySystemEvent(events.begin(), events.end(), timeout);
}
} // namespace dawn::native::metal
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 05ab8ec..cb9b140 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -780,7 +780,6 @@
"white_box/SharedBufferMemoryTests.h",
"white_box/SharedTextureMemoryTests.cpp",
"white_box/SharedTextureMemoryTests.h",
- "white_box/WaitListEventTests.cpp",
]
libs = []
diff --git a/src/dawn/tests/white_box/WaitListEventTests.cpp b/src/dawn/tests/white_box/WaitListEventTests.cpp
deleted file mode 100644
index b97c7c9..0000000
--- a/src/dawn/tests/white_box/WaitListEventTests.cpp
+++ /dev/null
@@ -1,351 +0,0 @@
-// Copyright 2025 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-// list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include <chrono>
-#include <thread>
-#include <utility>
-#include <vector>
-
-#include "dawn/common/Ref.h"
-#include "dawn/native/WaitAnySystemEvent.h"
-#include "dawn/native/WaitListEvent.h"
-#include "dawn/tests/DawnTest.h"
-
-namespace dawn::native {
-namespace {
-
-constexpr uint64_t kZeroDurationNs = 0;
-constexpr uint64_t kShortDurationNs = 1000000;
-constexpr uint64_t kMediumDurationNs = 50000000;
-
-// Helper to wait on a SystemEventReceiver with a timeout
-bool WaitOnReceiver(const SystemEventReceiver& receiver, Nanoseconds timeout) {
- bool ready = false;
- std::pair<const SystemEventReceiver&, bool*> event = {receiver, &ready};
- return WaitAnySystemEvent(&event, &event + 1, timeout);
-}
-
-class WaitListEventTests : public DawnTest {
- protected:
- void SetUp() override {
- DawnTest::SetUp();
- DAWN_TEST_UNSUPPORTED_IF(UsesWire());
- }
-};
-
-// Test basic signaling and state checking
-TEST(WaitListEventTests, SignalAndCheck) {
- Ref<WaitListEvent> event = AcquireRef(new WaitListEvent());
- EXPECT_FALSE(event->IsSignaled());
- event->Signal();
- EXPECT_TRUE(event->IsSignaled());
-}
-
-// Test waiting on an already signaled event
-TEST(WaitListEventTests, WaitAlreadySignaled) {
- Ref<WaitListEvent> event = AcquireRef(new WaitListEvent());
- event->Signal();
- EXPECT_TRUE(event->IsSignaled());
- // Wait with zero timeout should return true immediately
- EXPECT_TRUE(event->Wait(Nanoseconds(kZeroDurationNs)));
- // Wait with non-zero timeout should return true immediately
- EXPECT_TRUE(event->Wait(Nanoseconds(kShortDurationNs)));
-}
-
-// Test waiting on an event that gets signaled later
-TEST(WaitListEventTests, WaitThenSignal) {
- Ref<WaitListEvent> event = AcquireRef(new WaitListEvent());
- EXPECT_FALSE(event->IsSignaled());
-
- std::thread signaler([&]() {
- std::this_thread::sleep_for(std::chrono::nanoseconds(kShortDurationNs));
- event->Signal();
- });
-
- // Wait for longer than the signal delay
- EXPECT_TRUE(event->Wait(Nanoseconds(kMediumDurationNs)));
- EXPECT_TRUE(event->IsSignaled());
-
- signaler.join();
-}
-
-// Test waiting with a timeout that expires
-TEST(WaitListEventTests, WaitTimeout) {
- Ref<WaitListEvent> event = AcquireRef(new WaitListEvent());
- EXPECT_FALSE(event->IsSignaled());
-
- // Wait for a short duration, expect timeout
- EXPECT_FALSE(event->Wait(Nanoseconds(kShortDurationNs)));
- EXPECT_FALSE(event->IsSignaled());
-}
-
-// Test waiting with a zero timeout
-TEST(WaitListEventTests, WaitZeroTimeout) {
- Ref<WaitListEvent> event = AcquireRef(new WaitListEvent());
- EXPECT_FALSE(event->IsSignaled());
- // Wait with zero timeout should return false immediately
- EXPECT_FALSE(event->Wait(Nanoseconds(kZeroDurationNs)));
- EXPECT_FALSE(event->IsSignaled());
-
- event->Signal();
- EXPECT_TRUE(event->IsSignaled());
- // Wait with zero timeout should return true immediately
- EXPECT_TRUE(event->Wait(Nanoseconds(kZeroDurationNs)));
-}
-
-// Test WaitAsync on an already signaled event
-TEST(WaitListEventTests, WaitAsyncAlreadySignaled) {
- Ref<WaitListEvent> event = AcquireRef(new WaitListEvent());
- event->Signal();
- EXPECT_TRUE(event->IsSignaled());
-
- SystemEventReceiver receiver = event->WaitAsync();
- // The receiver should be immediately ready
- EXPECT_TRUE(WaitOnReceiver(receiver, Nanoseconds(kZeroDurationNs)));
-}
-
-// Test WaitAsync, signaling the event later
-TEST(WaitListEventTests, WaitAsyncThenSignal) {
- Ref<WaitListEvent> event = AcquireRef(new WaitListEvent());
- EXPECT_FALSE(event->IsSignaled());
-
- SystemEventReceiver receiver = event->WaitAsync();
-
- // Check it's not ready yet
- EXPECT_FALSE(WaitOnReceiver(receiver, Nanoseconds(kZeroDurationNs)));
-
- std::thread signaler([&]() {
- std::this_thread::sleep_for(std::chrono::nanoseconds(kShortDurationNs));
- event->Signal();
- });
-
- // Wait for the receiver to become signaled
- EXPECT_TRUE(WaitOnReceiver(receiver, Nanoseconds(kMediumDurationNs)));
- EXPECT_TRUE(event->IsSignaled());
-
- signaler.join();
-}
-
-// Test WaitAny with an empty list
-TEST(WaitListEventTests, WaitAnyEmpty) {
- std::array<std::pair<Ref<WaitListEvent>, bool*>, 0> events;
- EXPECT_FALSE(
- WaitListEvent::WaitAny(events.begin(), events.end(), Nanoseconds(kShortDurationNs)));
-}
-
-// Test WaitAny where one event is already signaled
-TEST(WaitListEventTests, WaitAnyOneAlreadySignaled) {
- Ref<WaitListEvent> event1 = AcquireRef(new WaitListEvent());
- Ref<WaitListEvent> event2 = AcquireRef(new WaitListEvent());
- event1->Signal();
-
- bool ready1 = false;
- bool ready2 = false;
- std::array<std::pair<Ref<WaitListEvent>, bool*>, 2> events = {
- {{event1, &ready1}, {event2, &ready2}}};
-
- EXPECT_TRUE(
- WaitListEvent::WaitAny(events.begin(), events.end(), Nanoseconds(kShortDurationNs)));
- EXPECT_TRUE(ready1);
- EXPECT_FALSE(ready2);
-}
-
-// Test WaitAny where one event is signaled while waiting
-TEST(WaitListEventTests, WaitAnySignalDuringWait) {
- Ref<WaitListEvent> event1 = AcquireRef(new WaitListEvent());
- Ref<WaitListEvent> event2 = AcquireRef(new WaitListEvent());
-
- bool ready1 = false;
- bool ready2 = false;
- std::array<std::pair<Ref<WaitListEvent>, bool*>, 2> events = {
- {{event1, &ready1}, {event2, &ready2}}};
-
- std::thread signaler([&]() {
- std::this_thread::sleep_for(std::chrono::nanoseconds(kShortDurationNs));
- event2->Signal(); // Signal the second event
- });
-
- EXPECT_TRUE(
- WaitListEvent::WaitAny(events.begin(), events.end(), Nanoseconds(kMediumDurationNs)));
- EXPECT_FALSE(ready1);
- EXPECT_TRUE(ready2); // Expect the second event to be ready
-
- signaler.join();
-}
-
-// Test WaitAny with a timeout
-TEST(WaitListEventTests, WaitAnyTimeout) {
- Ref<WaitListEvent> event1 = AcquireRef(new WaitListEvent());
- Ref<WaitListEvent> event2 = AcquireRef(new WaitListEvent());
-
- bool ready1 = false;
- bool ready2 = false;
- std::array<std::pair<Ref<WaitListEvent>, bool*>, 2> events = {
- {{event1, &ready1}, {event2, &ready2}}};
-
- EXPECT_FALSE(
- WaitListEvent::WaitAny(events.begin(), events.end(), Nanoseconds(kShortDurationNs)));
- EXPECT_FALSE(ready1);
- EXPECT_FALSE(ready2);
-}
-
-// Test WaitAny with zero timeout
-TEST(WaitListEventTests, WaitAnyZeroTimeout) {
- Ref<WaitListEvent> event1 = AcquireRef(new WaitListEvent());
- Ref<WaitListEvent> event2 = AcquireRef(new WaitListEvent());
-
- bool ready1 = false;
- bool ready2 = false;
- std::array<std::pair<Ref<WaitListEvent>, bool*>, 2> events = {
- {{event1, &ready1}, {event2, &ready2}}};
-
- // No events signaled
- EXPECT_FALSE(
- WaitListEvent::WaitAny(events.begin(), events.end(), Nanoseconds(kZeroDurationNs)));
- EXPECT_FALSE(ready1);
- EXPECT_FALSE(ready2);
-
- // Signal one event
- event1->Signal();
- EXPECT_TRUE(WaitListEvent::WaitAny(events.begin(), events.end(), Nanoseconds(kZeroDurationNs)));
- EXPECT_TRUE(ready1);
- EXPECT_FALSE(ready2);
-}
-
-// Test WaitAny with the same event multiple times
-TEST(WaitListEventTests, WaitAnyDuplicateEvents) {
- Ref<WaitListEvent> event = AcquireRef(new WaitListEvent());
-
- bool ready1 = false;
- bool ready2 = false;
- std::vector<std::pair<Ref<WaitListEvent>, bool*>> events = {
- {event, &ready1}, {event, &ready2} // Same event again
- };
-
- std::thread signaler([&]() {
- std::this_thread::sleep_for(std::chrono::nanoseconds(kShortDurationNs));
- event->Signal();
- });
-
- EXPECT_TRUE(
- WaitListEvent::WaitAny(events.begin(), events.end(), Nanoseconds(kMediumDurationNs)));
- // Both ready flags corresponding to the same event should be true
- EXPECT_TRUE(ready1);
- EXPECT_TRUE(ready2);
-
- signaler.join();
-}
-
-// Test WaitAny with the same event multiple times, already signaled
-TEST(WaitListEventTests, WaitAnyDuplicateEventsAlreadySignaled) {
- Ref<WaitListEvent> event = AcquireRef(new WaitListEvent());
-
- bool ready1 = false;
- bool ready2 = false;
- std::vector<std::pair<Ref<WaitListEvent>, bool*>> events = {
- {event, &ready1}, {event, &ready2} // Same event again
- };
-
- // Signal the event *before* waiting
- event->Signal();
- EXPECT_TRUE(event->IsSignaled());
-
- // WaitAny should return immediately since the event is already signaled
- EXPECT_TRUE(
- WaitListEvent::WaitAny(events.begin(), events.end(), Nanoseconds(kMediumDurationNs)));
-
- // Both ready flags corresponding to the same event should be true
- EXPECT_TRUE(ready1);
- EXPECT_TRUE(ready2);
-}
-
-// Test multiple threads waiting on the same event
-TEST(WaitListEventTests, WaitMultiThreadedSingleEvent) {
- Ref<WaitListEvent> event = AcquireRef(new WaitListEvent());
-
- constexpr size_t kNumWaiters = 5;
- std::array<std::thread, kNumWaiters> waiters;
- std::array<std::optional<bool>, kNumWaiters> results;
-
- for (size_t i = 0; i < kNumWaiters; ++i) {
- waiters[i] = std::thread(
- [&results, &event, i]() { results[i] = event->Wait(Nanoseconds(kMediumDurationNs)); });
- }
-
- // Give waiters time to start waiting
- std::this_thread::sleep_for(std::chrono::nanoseconds(kShortDurationNs));
- event->Signal();
-
- // Check all waiters returned true
- for (size_t i = 0; i < kNumWaiters; ++i) {
- waiters[i].join();
- EXPECT_TRUE(results[i].has_value());
- EXPECT_TRUE(results[i].value());
- }
- EXPECT_TRUE(event->IsSignaled());
-}
-
-// Test multiple threads waiting on different events via WaitAny
-TEST(WaitListEventTests, WaitAnyMultiThreaded) {
- Ref<WaitListEvent> event1 = AcquireRef(new WaitListEvent());
- Ref<WaitListEvent> event2 = AcquireRef(new WaitListEvent());
- Ref<WaitListEvent> event3 = AcquireRef(new WaitListEvent());
-
- bool ready1 = false;
- bool ready2 = false;
- bool ready3 = false;
- std::array<std::pair<Ref<WaitListEvent>, bool*>, 3> events = {
- {{event1, &ready1}, {event2, &ready2}, {event3, &ready3}}};
-
- // Start a thread that waits on any of the events
- bool waitResult = false;
- std::thread waiter([&]() {
- waitResult =
- WaitListEvent::WaitAny(events.begin(), events.end(), Nanoseconds(kMediumDurationNs));
- });
-
- // Start another thread that signals one of the events
- std::thread signaler([&]() {
- std::this_thread::sleep_for(std::chrono::nanoseconds(kShortDurationNs));
- event2->Signal(); // Signal the middle event
- });
-
- waiter.join();
-
- // Check that the waiting thread completes successfully
- EXPECT_TRUE(waitResult);
-
- // Check that the correct ready flag was set
- EXPECT_FALSE(ready1);
- EXPECT_TRUE(ready2);
- EXPECT_FALSE(ready3);
-
- signaler.join();
-}
-
-} // namespace
-} // namespace dawn::native