Fix crash from async callback calling Queue::Submit
An async callback which calls Queue::Submit will cause reentrance in QueueBase::Tick and CreatePipelineAsyncTracker::Tick, which invalidates the task queue being used by the original call, and leads to a crash from an invalid pointer. The Tick functions should remove the tasks from the queues before the callbacks are called, so invalidation doesn't cause a crash. Bug: dawn:729 Change-Id: I0d952d51040a3d1a475767400de3333a8b9b0821 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/45900 Commit-Queue: Corentin Wallez <cwallez@chromium.org> Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
parent
98028718e1
commit
7f8c91cdde
|
@ -98,10 +98,20 @@ namespace dawn_native {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatePipelineAsyncTracker::Tick(ExecutionSerial finishedSerial) {
|
void CreatePipelineAsyncTracker::Tick(ExecutionSerial finishedSerial) {
|
||||||
|
// If a user calls Queue::Submit inside Create*PipelineAsync, then the device will be
|
||||||
|
// ticked, which in turns ticks the tracker, causing reentrance here. To prevent the
|
||||||
|
// reentrant call from invalidating mCreatePipelineAsyncTasksInFlight while in use by the
|
||||||
|
// first call, we remove the tasks to finish from the queue, update
|
||||||
|
// mCreatePipelineAsyncTasksInFlight, then run the callbacks.
|
||||||
|
std::vector<std::unique_ptr<CreatePipelineAsyncTaskBase>> tasks;
|
||||||
for (auto& task : mCreatePipelineAsyncTasksInFlight.IterateUpTo(finishedSerial)) {
|
for (auto& task : mCreatePipelineAsyncTasksInFlight.IterateUpTo(finishedSerial)) {
|
||||||
task->Finish(WGPUCreatePipelineAsyncStatus_Success);
|
tasks.push_back(std::move(task));
|
||||||
}
|
}
|
||||||
mCreatePipelineAsyncTasksInFlight.ClearUpTo(finishedSerial);
|
mCreatePipelineAsyncTasksInFlight.ClearUpTo(finishedSerial);
|
||||||
|
|
||||||
|
for (auto& task : tasks) {
|
||||||
|
task->Finish(WGPUCreatePipelineAsyncStatus_Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatePipelineAsyncTracker::ClearForShutDown() {
|
void CreatePipelineAsyncTracker::ClearForShutDown() {
|
||||||
|
|
|
@ -223,10 +223,20 @@ namespace dawn_native {
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueueBase::Tick(ExecutionSerial finishedSerial) {
|
void QueueBase::Tick(ExecutionSerial finishedSerial) {
|
||||||
|
// If a user calls Queue::Submit inside a task, for example in a Buffer::MapAsync callback,
|
||||||
|
// then the device will be ticked, which in turns ticks the queue, causing reentrance here.
|
||||||
|
// To prevent the reentrant call from invalidating mTasksInFlight while in use by the first
|
||||||
|
// call, we remove the tasks to finish from the queue, update mTasksInFlight, then run the
|
||||||
|
// callbacks.
|
||||||
|
std::vector<std::unique_ptr<TaskInFlight>> tasks;
|
||||||
for (auto& task : mTasksInFlight.IterateUpTo(finishedSerial)) {
|
for (auto& task : mTasksInFlight.IterateUpTo(finishedSerial)) {
|
||||||
task->Finish();
|
tasks.push_back(std::move(task));
|
||||||
}
|
}
|
||||||
mTasksInFlight.ClearUpTo(finishedSerial);
|
mTasksInFlight.ClearUpTo(finishedSerial);
|
||||||
|
|
||||||
|
for (auto& task : tasks) {
|
||||||
|
task->Finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueueBase::HandleDeviceLoss() {
|
void QueueBase::HandleDeviceLoss() {
|
||||||
|
|
|
@ -334,7 +334,13 @@ namespace dawn_native { namespace null {
|
||||||
}
|
}
|
||||||
|
|
||||||
MaybeError Queue::SubmitImpl(uint32_t, CommandBufferBase* const*) {
|
MaybeError Queue::SubmitImpl(uint32_t, CommandBufferBase* const*) {
|
||||||
ToBackend(GetDevice())->SubmitPendingOperations();
|
Device* device = ToBackend(GetDevice());
|
||||||
|
|
||||||
|
// The Vulkan, D3D12 and Metal implementation all tick the device here,
|
||||||
|
// for testing purposes we should also tick in the null implementation.
|
||||||
|
DAWN_TRY(device->Tick());
|
||||||
|
|
||||||
|
device->SubmitPendingOperations();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
#include "tests/unittests/validation/ValidationTest.h"
|
#include "tests/unittests/validation/ValidationTest.h"
|
||||||
|
|
||||||
|
#include "utils/ComboRenderPipelineDescriptor.h"
|
||||||
#include "utils/WGPUHelpers.h"
|
#include "utils/WGPUHelpers.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -129,4 +130,95 @@ namespace {
|
||||||
ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
|
ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that submitting in a buffer mapping callback doesn't cause re-entrance problems.
|
||||||
|
TEST_F(QueueSubmitValidationTest, SubmitInBufferMapCallback) {
|
||||||
|
// Create a buffer for mapping, to run our callback.
|
||||||
|
wgpu::BufferDescriptor descriptor;
|
||||||
|
descriptor.size = 4;
|
||||||
|
descriptor.usage = wgpu::BufferUsage::MapWrite;
|
||||||
|
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
|
||||||
|
|
||||||
|
struct CallbackData {
|
||||||
|
wgpu::Device device;
|
||||||
|
wgpu::Buffer buffer;
|
||||||
|
} callbackData = {device, buffer};
|
||||||
|
|
||||||
|
const auto callback = [](WGPUBufferMapAsyncStatus status, void* userdata) {
|
||||||
|
CallbackData* data = reinterpret_cast<CallbackData*>(userdata);
|
||||||
|
|
||||||
|
data->buffer.Unmap();
|
||||||
|
|
||||||
|
wgpu::Queue queue = data->device.GetQueue();
|
||||||
|
queue.Submit(0, nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
buffer.MapAsync(wgpu::MapMode::Write, 0, descriptor.size, callback, &callbackData);
|
||||||
|
|
||||||
|
WaitForAllOperations(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that submitting in a render pipeline creation callback doesn't cause re-entrance
|
||||||
|
// problems.
|
||||||
|
TEST_F(QueueSubmitValidationTest, SubmitInCreateRenderPipelineAsyncCallback) {
|
||||||
|
struct CallbackData {
|
||||||
|
wgpu::Device device;
|
||||||
|
} callbackData = {device};
|
||||||
|
|
||||||
|
const auto callback = [](WGPUCreatePipelineAsyncStatus status, WGPURenderPipeline pipeline,
|
||||||
|
char const* message, void* userdata) {
|
||||||
|
CallbackData* data = reinterpret_cast<CallbackData*>(userdata);
|
||||||
|
|
||||||
|
wgpuRenderPipelineRelease(pipeline);
|
||||||
|
|
||||||
|
wgpu::Queue queue = data->device.GetQueue();
|
||||||
|
queue.Submit(0, nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
|
||||||
|
[[builtin(position)]] var<out> Position : vec4<f32>;
|
||||||
|
[[stage(vertex)]] fn main() -> void {
|
||||||
|
Position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||||
|
})");
|
||||||
|
|
||||||
|
wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
|
||||||
|
[[location(0)]] var<out> fragColor : vec4<f32>;
|
||||||
|
[[stage(fragment)]] fn main() -> void {
|
||||||
|
fragColor = vec4<f32>(0.0, 1.0, 0.0, 1.0);
|
||||||
|
})");
|
||||||
|
|
||||||
|
utils::ComboRenderPipelineDescriptor2 descriptor;
|
||||||
|
descriptor.vertex.module = vsModule;
|
||||||
|
descriptor.cFragment.module = fsModule;
|
||||||
|
device.CreateRenderPipelineAsync(&descriptor, callback, &callbackData);
|
||||||
|
|
||||||
|
WaitForAllOperations(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that submitting in a compute pipeline creation callback doesn't cause re-entrance
|
||||||
|
// problems.
|
||||||
|
TEST_F(QueueSubmitValidationTest, SubmitInCreateComputePipelineAsyncCallback) {
|
||||||
|
struct CallbackData {
|
||||||
|
wgpu::Device device;
|
||||||
|
} callbackData = {device};
|
||||||
|
|
||||||
|
const auto callback = [](WGPUCreatePipelineAsyncStatus status, WGPUComputePipeline pipeline,
|
||||||
|
char const* message, void* userdata) {
|
||||||
|
CallbackData* data = reinterpret_cast<CallbackData*>(userdata);
|
||||||
|
|
||||||
|
wgpuComputePipelineRelease(pipeline);
|
||||||
|
|
||||||
|
wgpu::Queue queue = data->device.GetQueue();
|
||||||
|
queue.Submit(0, nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
wgpu::ComputePipelineDescriptor descriptor;
|
||||||
|
descriptor.computeStage.module = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(compute)]] fn main() -> void {
|
||||||
|
})");
|
||||||
|
descriptor.computeStage.entryPoint = "main";
|
||||||
|
device.CreateComputePipelineAsync(&descriptor, callback, &callbackData);
|
||||||
|
|
||||||
|
WaitForAllOperations(device);
|
||||||
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
Loading…
Reference in New Issue