DawnNative: implement Instance::ProcessEvents

Since https://dawn-review.googlesource.com/c/dawn/+/120940, callbacks
will be deferred to be executed in next device.APITick() instead of
immediately.
However, if the device is already destroyed (last ref dropped),
user/wire_server has no chance to call device.APITick() anymore, leading
to the callbacks waiting in queue forever.

This is also possibly the cause of memory leaks in cluserfuzz tests.

This CL attempt to fix it by implementing Instance::ProcessEvents():
In this method, every created device will invoke APITick() even if it is
already lost/externally released.

bug: chromium:1422507
bug: dawn:752
Change-Id: Iec69ad3b547a7e88c6e1a2225b13ad060a501a4f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/123420
Commit-Queue: Quyen Le <lehoangquyen@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
Le Hoang Quyen 2023-03-14 19:03:10 +00:00 committed by Dawn LUCI CQ
parent 5b3aaf6636
commit 69e1c4b638
11 changed files with 91 additions and 29 deletions

View File

@ -1530,8 +1530,7 @@
] ]
}, },
{ {
"name": "process events", "name": "process events"
"tags": ["upstream", "emscripten"]
}, },
{ {
"name": "request adapter", "name": "request adapter",

View File

@ -187,6 +187,8 @@ class DAWN_NATIVE_EXPORT Instance {
uint64_t GetDeviceCountForTesting() const; uint64_t GetDeviceCountForTesting() const;
bool ProcessEvents();
// Returns the underlying WGPUInstance object. // Returns the underlying WGPUInstance object.
WGPUInstance Get() const; WGPUInstance Get() const;

View File

@ -133,6 +133,10 @@ int DawnWireServerFuzzer::Run(const uint8_t* data,
wireServer->InjectInstance(sInstance->Get(), 1, 0); wireServer->InjectInstance(sInstance->Get(), 1, 0);
wireServer->HandleCommands(reinterpret_cast<const char*>(data), size); wireServer->HandleCommands(reinterpret_cast<const char*>(data), size);
// Flush remaining callbacks to avoid memory leaks.
do {
} while (sInstance->ProcessEvents());
// Note: Deleting the server will release all created objects. // Note: Deleting the server will release all created objects.
// Deleted devices will wait for idle on destruction. // Deleted devices will wait for idle on destruction.
wireServer = nullptr; wireServer = nullptr;

View File

@ -251,6 +251,10 @@ uint64_t Instance::GetDeviceCountForTesting() const {
return mImpl->GetDeviceCountForTesting(); return mImpl->GetDeviceCountForTesting();
} }
bool Instance::ProcessEvents() {
return mImpl->APIProcessEvents();
}
WGPUInstance Instance::Get() const { WGPUInstance Instance::Get() const {
return ToAPI(mImpl); return ToAPI(mImpl);
} }

View File

@ -174,7 +174,6 @@ DeviceBase::DeviceBase(AdapterBase* adapter,
const DeviceDescriptor* descriptor, const DeviceDescriptor* descriptor,
const TogglesState& deviceToggles) const TogglesState& deviceToggles)
: mAdapter(adapter), mToggles(deviceToggles), mNextPipelineCompatibilityToken(1) { : mAdapter(adapter), mToggles(deviceToggles), mNextPipelineCompatibilityToken(1) {
mAdapter->GetInstance()->IncrementDeviceCountForTesting();
ASSERT(descriptor != nullptr); ASSERT(descriptor != nullptr);
AdapterProperties adapterProperties; AdapterProperties adapterProperties;
@ -218,8 +217,9 @@ DeviceBase::~DeviceBase() {
// Queue does not get destroyed after the Device. // Queue does not get destroyed after the Device.
mQueue = nullptr; mQueue = nullptr;
// mAdapter is not set for mock test devices. // mAdapter is not set for mock test devices.
// TODO(crbug.com/dawn/1702): using a mock adapter could avoid the null checking.
if (mAdapter != nullptr) { if (mAdapter != nullptr) {
mAdapter->GetInstance()->DecrementDeviceCountForTesting(); mAdapter->GetInstance()->RemoveDevice(this);
} }
} }
@ -282,6 +282,11 @@ MaybeError DeviceBase::Initialize(Ref<QueueBase> defaultQueue) {
CreateShaderModule(&descriptor)); CreateShaderModule(&descriptor));
} }
// TODO(crbug.com/dawn/1702): using a mock adapter could avoid the null checking.
if (mAdapter != nullptr) {
mAdapter->GetInstance()->AddDevice(this);
}
return {}; return {};
} }

View File

@ -21,6 +21,7 @@
#include "dawn/common/Log.h" #include "dawn/common/Log.h"
#include "dawn/common/SystemUtils.h" #include "dawn/common/SystemUtils.h"
#include "dawn/native/ChainUtils_autogen.h" #include "dawn/native/ChainUtils_autogen.h"
#include "dawn/native/Device.h"
#include "dawn/native/ErrorData.h" #include "dawn/native/ErrorData.h"
#include "dawn/native/Surface.h" #include "dawn/native/Surface.h"
#include "dawn/native/Toggles.h" #include "dawn/native/Toggles.h"
@ -504,15 +505,35 @@ BlobCache* InstanceBase::GetBlobCache(bool enabled) {
} }
uint64_t InstanceBase::GetDeviceCountForTesting() const { uint64_t InstanceBase::GetDeviceCountForTesting() const {
return mDeviceCountForTesting.load(); std::lock_guard<std::mutex> lg(mDevicesListMutex);
return mDevicesList.size();
} }
void InstanceBase::IncrementDeviceCountForTesting() { void InstanceBase::AddDevice(DeviceBase* device) {
mDeviceCountForTesting++; std::lock_guard<std::mutex> lg(mDevicesListMutex);
mDevicesList.insert(device);
} }
void InstanceBase::DecrementDeviceCountForTesting() { void InstanceBase::RemoveDevice(DeviceBase* device) {
mDeviceCountForTesting--; std::lock_guard<std::mutex> lg(mDevicesListMutex);
mDevicesList.erase(device);
}
bool InstanceBase::APIProcessEvents() {
std::vector<Ref<DeviceBase>> devices;
{
std::lock_guard<std::mutex> lg(mDevicesListMutex);
for (auto device : mDevicesList) {
devices.push_back(device);
}
}
bool hasMoreEvents = false;
for (auto device : devices) {
hasMoreEvents = device->APITick() || hasMoreEvents;
}
return hasMoreEvents;
} }
const std::vector<std::string>& InstanceBase::GetRuntimeSearchPaths() const { const std::vector<std::string>& InstanceBase::GetRuntimeSearchPaths() const {

View File

@ -17,6 +17,8 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <mutex>
#include <set>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@ -37,6 +39,7 @@ class Platform;
namespace dawn::native { namespace dawn::native {
class DeviceBase;
class Surface; class Surface;
class XlibXcbFunctions; class XlibXcbFunctions;
@ -103,8 +106,8 @@ class InstanceBase final : public RefCountedWithExternalCount {
BlobCache* GetBlobCache(bool enabled = true); BlobCache* GetBlobCache(bool enabled = true);
uint64_t GetDeviceCountForTesting() const; uint64_t GetDeviceCountForTesting() const;
void IncrementDeviceCountForTesting(); void AddDevice(DeviceBase* device);
void DecrementDeviceCountForTesting(); void RemoveDevice(DeviceBase* device);
const std::vector<std::string>& GetRuntimeSearchPaths() const; const std::vector<std::string>& GetRuntimeSearchPaths() const;
@ -113,6 +116,7 @@ class InstanceBase final : public RefCountedWithExternalCount {
// Dawn API // Dawn API
Surface* APICreateSurface(const SurfaceDescriptor* descriptor); Surface* APICreateSurface(const SurfaceDescriptor* descriptor);
bool APIProcessEvents();
private: private:
explicit InstanceBase(const TogglesState& instanceToggles); explicit InstanceBase(const TogglesState& instanceToggles);
@ -161,7 +165,8 @@ class InstanceBase final : public RefCountedWithExternalCount {
std::unique_ptr<XlibXcbFunctions> mXlibXcbFunctions; std::unique_ptr<XlibXcbFunctions> mXlibXcbFunctions;
#endif // defined(DAWN_USE_X11) #endif // defined(DAWN_USE_X11)
std::atomic_uint64_t mDeviceCountForTesting{0}; std::set<DeviceBase*> mDevicesList;
mutable std::mutex mDevicesListMutex;
}; };
} // namespace dawn::native } // namespace dawn::native

View File

@ -1439,12 +1439,12 @@ std::ostringstream& DawnTestBase::ExpectAttachmentDepthStencilTestData(
return EXPECT_TEXTURE_EQ(colorData.data(), colorTexture, {0, 0}, {width, height}); return EXPECT_TEXTURE_EQ(colorData.data(), colorTexture, {0, 0}, {width, height});
} }
void DawnTestBase::WaitABit(wgpu::Device targetDevice) { void DawnTestBase::WaitABit(wgpu::Instance targetInstance) {
if (targetDevice == nullptr) { if (targetInstance == nullptr) {
targetDevice = this->device; targetInstance = mInstance;
} }
if (targetDevice != nullptr) { if (targetInstance != nullptr) {
targetDevice.Tick(); targetInstance.ProcessEvents();
} }
FlushWire(); FlushWire();
@ -1498,8 +1498,6 @@ void DawnTestBase::MapSlotsSynchronously() {
// immediately. // immediately.
mNumPendingMapOperations = mReadbackSlots.size(); mNumPendingMapOperations = mReadbackSlots.size();
std::vector<wgpu::Device> pendingDevices;
// Map all readback slots // Map all readback slots
for (size_t i = 0; i < mReadbackSlots.size(); ++i) { for (size_t i = 0; i < mReadbackSlots.size(); ++i) {
MapReadUserdata* userdata = new MapReadUserdata{this, i}; MapReadUserdata* userdata = new MapReadUserdata{this, i};
@ -1507,15 +1505,11 @@ void DawnTestBase::MapSlotsSynchronously() {
const ReadbackSlot& slot = mReadbackSlots[i]; const ReadbackSlot& slot = mReadbackSlots[i];
slot.buffer.MapAsync(wgpu::MapMode::Read, 0, wgpu::kWholeMapSize, SlotMapCallback, slot.buffer.MapAsync(wgpu::MapMode::Read, 0, wgpu::kWholeMapSize, SlotMapCallback,
userdata); userdata);
pendingDevices.push_back(slot.device);
} }
// Busy wait until all map operations are done. // Busy wait until all map operations are done.
while (mNumPendingMapOperations != 0) { while (mNumPendingMapOperations != 0) {
for (const wgpu::Device& device : pendingDevices) { WaitABit();
WaitABit(device);
}
} }
} }

View File

@ -550,7 +550,7 @@ class DawnTestBase {
mipLevel, {}, &expectedStencil); mipLevel, {}, &expectedStencil);
} }
void WaitABit(wgpu::Device = nullptr); void WaitABit(wgpu::Instance = nullptr);
void FlushWire(); void FlushWire();
void WaitForAllOperations(); void WaitForAllOperations();

View File

@ -189,10 +189,7 @@ TEST_P(DeviceLifetimeTests, DroppedBeforeMappedAtCreationBuffer) {
// Test that the device can be dropped before a buffer created from it, then mapping the buffer // Test that the device can be dropped before a buffer created from it, then mapping the buffer
// fails. // fails.
// TODO(crbug.com/dawn/752): Re-enable this test once we implement Instance.ProcessEvents(). TEST_P(DeviceLifetimeTests, DroppedThenMapBuffer) {
// Currently the callbacks are called inside Device.Tick() only. However, since we drop the device,
// there is no way to call Device.Tick() anymore.
TEST_P(DeviceLifetimeTests, DISABLED_DroppedThenMapBuffer) {
wgpu::BufferDescriptor desc = {}; wgpu::BufferDescriptor desc = {};
desc.size = 4; desc.size = 4;
desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst; desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
@ -481,6 +478,36 @@ TEST_P(DeviceLifetimeTests, DroppedInsideCreatePipelineAsyncRaceCache) {
} }
} }
// Tests that dropping 2nd device inside 1st device's callback triggered by instance.ProcessEvents
// won't crash.
TEST_P(DeviceLifetimeTests, DropDevice2InProcessEvents) {
wgpu::Device device2 = CreateDevice();
struct UserData {
wgpu::Device device2;
bool done = false;
} userdata;
userdata.device2 = std::move(device2);
device.PushErrorScope(wgpu::ErrorFilter::Validation);
// The following callback will drop the 2nd device. It won't be triggered until
// instance.ProcessEvents() is called.
device.PopErrorScope(
[](WGPUErrorType type, const char*, void* userdataPtr) {
auto userdata = static_cast<UserData*>(userdataPtr);
userdata->device2 = nullptr;
userdata->done = true;
},
&userdata);
while (!userdata.done) {
WaitABit();
}
}
DAWN_INSTANTIATE_TEST(DeviceLifetimeTests, DAWN_INSTANTIATE_TEST(DeviceLifetimeTests,
D3D12Backend(), D3D12Backend(),
MetalBackend(), MetalBackend(),

View File

@ -150,5 +150,6 @@ void WireTest::DeleteClient() {
} }
void WireTest::SetupIgnoredCallExpectations() { void WireTest::SetupIgnoredCallExpectations() {
EXPECT_CALL(api, InstanceProcessEvents(_)).Times(AnyNumber());
EXPECT_CALL(api, DeviceTick(_)).Times(AnyNumber()); EXPECT_CALL(api, DeviceTick(_)).Times(AnyNumber());
} }