dawn_node: Track promises

These should always be resolved or rejected.
The Fatal() call, when a promise is not resolved or rejected, is currently disabled due to https://github.com/gpuweb/cts/issues/784.

Bug: dawn:1123
Change-Id: Ie0e8ac187ad70be0fea41cd66956d0bfd9c53212
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/66821
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
Ben Clayton 2021-10-19 19:18:42 +00:00 committed by Dawn LUCI CQ
parent 0e3d4fcbd4
commit e9cbd4896a
7 changed files with 119 additions and 79 deletions

View File

@ -55,8 +55,8 @@ namespace wgpu { namespace binding {
interop::Promise<std::optional<interop::Interface<interop::GPUAdapter>>> GPU::requestAdapter( interop::Promise<std::optional<interop::Interface<interop::GPUAdapter>>> GPU::requestAdapter(
Napi::Env env, Napi::Env env,
interop::GPURequestAdapterOptions options) { interop::GPURequestAdapterOptions options) {
auto promise = auto promise = interop::Promise<std::optional<interop::Interface<interop::GPUAdapter>>>(
interop::Promise<std::optional<interop::Interface<interop::GPUAdapter>>>(env); env, PROMISE_INFO);
if (options.forceFallbackAdapter) { if (options.forceFallbackAdapter) {
// Software adapters are not currently supported. // Software adapters are not currently supported.

View File

@ -139,7 +139,7 @@ namespace wgpu { namespace binding {
Napi::Env env, Napi::Env env,
interop::GPUDeviceDescriptor descriptor) { interop::GPUDeviceDescriptor descriptor) {
dawn_native::DeviceDescriptor desc{}; // TODO(crbug.com/dawn/1133): Fill in. dawn_native::DeviceDescriptor desc{}; // TODO(crbug.com/dawn/1133): Fill in.
interop::Promise<interop::Interface<interop::GPUDevice>> promise(env); interop::Promise<interop::Interface<interop::GPUDevice>> promise(env, PROMISE_INFO);
// See src/dawn_native/Features.cpp for enum <-> string mappings. // See src/dawn_native/Features.cpp for enum <-> string mappings.
for (auto required : descriptor.requiredFeatures) { for (auto required : descriptor.requiredFeatures) {

View File

@ -47,11 +47,13 @@ namespace wgpu { namespace binding {
wgpu::MapMode md{}; wgpu::MapMode md{};
Converter conv(env); Converter conv(env);
if (!conv(md, mode)) { if (!conv(md, mode)) {
return {env}; interop::Promise<void> promise(env, PROMISE_INFO);
promise.Reject(Errors::OperationError(env));
return promise;
} }
if (state_ != State::Unmapped) { if (state_ != State::Unmapped) {
interop::Promise<void> promise(env); interop::Promise<void> promise(env, PROMISE_INFO);
promise.Reject(Errors::OperationError(env)); promise.Reject(Errors::OperationError(env));
device_.InjectError(wgpu::ErrorType::Validation, device_.InjectError(wgpu::ErrorType::Validation,
"mapAsync called on buffer that is not in the unmapped state"); "mapAsync called on buffer that is not in the unmapped state");
@ -64,7 +66,7 @@ namespace wgpu { namespace binding {
AsyncTask task; AsyncTask task;
State& state; State& state;
}; };
auto ctx = new Context{env, interop::Promise<void>(env), async_, state_}; auto ctx = new Context{env, interop::Promise<void>(env, PROMISE_INFO), async_, state_};
auto promise = ctx->promise; auto promise = ctx->promise;
uint64_t s = size.has_value() ? size.value() : (desc_.size - offset); uint64_t s = size.has_value() ? size.value() : (desc_.size - offset);

View File

@ -138,7 +138,12 @@ namespace wgpu { namespace binding {
return interop::GPUQueue::Create<GPUQueue>(env, device_.GetQueue(), async_); return interop::GPUQueue::Create<GPUQueue>(env, device_.GetQueue(), async_);
} }
void GPUDevice::destroy(Napi::Env) { void GPUDevice::destroy(Napi::Env env) {
for (auto promise : lost_promises_) {
promise.Resolve(interop::GPUDeviceLostInfo::Create<DeviceLostInfo>(
env_, interop::GPUDeviceLostReason::kDestroyed, "device was destroyed"));
}
lost_promises_.clear();
device_.Release(); device_.Release();
} }
@ -293,21 +298,23 @@ namespace wgpu { namespace binding {
interop::Promise<interop::Interface<interop::GPUComputePipeline>> interop::Promise<interop::Interface<interop::GPUComputePipeline>>
GPUDevice::createComputePipelineAsync(Napi::Env env, GPUDevice::createComputePipelineAsync(Napi::Env env,
interop::GPUComputePipelineDescriptor descriptor) { interop::GPUComputePipelineDescriptor descriptor) {
using Promise = interop::Promise<interop::Interface<interop::GPUComputePipeline>>;
Converter conv(env); Converter conv(env);
wgpu::ComputePipelineDescriptor desc{}; wgpu::ComputePipelineDescriptor desc{};
if (!conv(desc, descriptor)) { if (!conv(desc, descriptor)) {
return {env}; Promise promise(env, PROMISE_INFO);
promise.Reject(Errors::OperationError(env));
return promise;
} }
using Promise = interop::Promise<interop::Interface<interop::GPUComputePipeline>>;
struct Context { struct Context {
Napi::Env env; Napi::Env env;
Promise promise; Promise promise;
AsyncTask task; AsyncTask task;
}; };
auto ctx = new Context{env, env, async_}; auto ctx = new Context{env, Promise(env, PROMISE_INFO), async_};
auto promise = ctx->promise; auto promise = ctx->promise;
device_.CreateComputePipelineAsync( device_.CreateComputePipelineAsync(
@ -334,21 +341,23 @@ namespace wgpu { namespace binding {
interop::Promise<interop::Interface<interop::GPURenderPipeline>> interop::Promise<interop::Interface<interop::GPURenderPipeline>>
GPUDevice::createRenderPipelineAsync(Napi::Env env, GPUDevice::createRenderPipelineAsync(Napi::Env env,
interop::GPURenderPipelineDescriptor descriptor) { interop::GPURenderPipelineDescriptor descriptor) {
using Promise = interop::Promise<interop::Interface<interop::GPURenderPipeline>>;
Converter conv(env); Converter conv(env);
wgpu::RenderPipelineDescriptor desc{}; wgpu::RenderPipelineDescriptor desc{};
if (!conv(desc, descriptor)) { if (!conv(desc, descriptor)) {
return {env}; Promise promise(env, PROMISE_INFO);
promise.Reject(Errors::OperationError(env));
return promise;
} }
using Promise = interop::Promise<interop::Interface<interop::GPURenderPipeline>>;
struct Context { struct Context {
Napi::Env env; Napi::Env env;
Promise promise; Promise promise;
AsyncTask task; AsyncTask task;
}; };
auto ctx = new Context{env, env, async_}; auto ctx = new Context{env, Promise(env, PROMISE_INFO), async_};
auto promise = ctx->promise; auto promise = ctx->promise;
device_.CreateRenderPipelineAsync( device_.CreateRenderPipelineAsync(
@ -415,7 +424,8 @@ namespace wgpu { namespace binding {
interop::Promise<interop::Interface<interop::GPUDeviceLostInfo>> GPUDevice::getLost( interop::Promise<interop::Interface<interop::GPUDeviceLostInfo>> GPUDevice::getLost(
Napi::Env env) { Napi::Env env) {
auto promise = interop::Promise<interop::Interface<interop::GPUDeviceLostInfo>>(env); auto promise =
interop::Promise<interop::Interface<interop::GPUDeviceLostInfo>>(env, PROMISE_INFO);
lost_promises_.emplace_back(promise); lost_promises_.emplace_back(promise);
return promise; return promise;
} }
@ -444,7 +454,7 @@ namespace wgpu { namespace binding {
Promise promise; Promise promise;
AsyncTask task; AsyncTask task;
}; };
auto* ctx = new Context{env, env, async_}; auto* ctx = new Context{env, Promise(env, PROMISE_INFO), async_};
auto promise = ctx->promise; auto promise = ctx->promise;
bool ok = device_.PopErrorScope( bool ok = device_.PopErrorScope(
@ -476,10 +486,8 @@ namespace wgpu { namespace binding {
} }
delete ctx; delete ctx;
Promise p(env); promise.Reject(Errors::OperationError(env));
p.Resolve( return promise;
interop::GPUValidationError::Create<ValidationError>(env, "failed to pop error scope"));
return p;
} }
std::optional<std::string> GPUDevice::getLabel(Napi::Env) { std::optional<std::string> GPUDevice::getLabel(Napi::Env) {

View File

@ -51,7 +51,7 @@ namespace wgpu { namespace binding {
interop::Promise<void> promise; interop::Promise<void> promise;
AsyncTask task; AsyncTask task;
}; };
auto ctx = new Context{env, interop::Promise<void>(env), async_}; auto ctx = new Context{env, interop::Promise<void>(env, PROMISE_INFO), async_};
auto promise = ctx->promise; auto promise = ctx->promise;
queue_.OnSubmittedWorkDone( queue_.OnSubmittedWorkDone(

View File

@ -91,7 +91,7 @@ namespace wgpu { namespace binding {
Promise promise; Promise promise;
AsyncTask task; AsyncTask task;
}; };
auto ctx = new Context{env, env, async_}; auto ctx = new Context{env, Promise(env, PROMISE_INFO), async_};
auto promise = ctx->promise; auto promise = ctx->promise;
shader_.GetCompilationInfo( shader_.GetCompilationInfo(

View File

@ -38,6 +38,13 @@
# define INTEROP_LOG(...) # define INTEROP_LOG(...)
#endif #endif
// A helper macro for constructing a PromiseInfo with the current file, function and line.
// See PromiseInfo
#define PROMISE_INFO \
::wgpu::interop::PromiseInfo { \
__FILE__, __FUNCTION__, __LINE__ \
}
namespace wgpu { namespace interop { namespace wgpu { namespace interop {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -147,83 +154,106 @@ namespace wgpu { namespace interop {
// Promise<T> // Promise<T>
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Info holds details about where the promise was constructed.
// Used for printing debug messages when a promise is finalized without being resolved
// or rejected.
// Use the PROMISE_INFO macro to populate this structure.
struct PromiseInfo {
const char* file = nullptr;
const char* function = nullptr;
int line = 0;
};
namespace detail {
// Base class for Promise<T> specializations.
class PromiseBase {
public:
// Implicit conversion operators to Napi promises.
inline operator napi_value() const {
return state->deferred.Promise();
}
inline operator Napi::Value() const {
return state->deferred.Promise();
}
inline operator Napi::Promise() const {
return state->deferred.Promise();
}
// Reject() rejects the promise with the given failure value.
void Reject(Napi::Value value) const {
state->deferred.Reject(value);
state->resolved_or_rejected = true;
}
void Reject(Napi::Error err) const {
Reject(err.Value());
}
void Reject(std::string err) const {
Reject(Napi::Error::New(state->deferred.Env(), err));
}
protected:
void Resolve(Napi::Value value) const {
state->deferred.Resolve(value);
state->resolved_or_rejected = true;
}
struct State {
Napi::Promise::Deferred deferred;
PromiseInfo info;
bool resolved_or_rejected = false;
};
PromiseBase(Napi::Env env, const PromiseInfo& info)
: state(new State{Napi::Promise::Deferred::New(env), info}) {
state->deferred.Promise().AddFinalizer(
[](Napi::Env, State* state) {
// TODO(https://github.com/gpuweb/cts/issues/784):
// Devices are never destroyed, so we always end up
// leaking the Device.lost promise. Enable this once
// fixed.
if ((false)) {
if (!state->resolved_or_rejected) {
::wgpu::utils::Fatal("Promise not resolved or rejected",
state->info.file, state->info.line,
state->info.function);
}
}
delete state;
},
state);
}
State* const state;
};
} // namespace detail
// Promise<T> is a templated wrapper around a JavaScript promise, which can // Promise<T> is a templated wrapper around a JavaScript promise, which can
// resolve to the template type T. // resolve to the template type T.
template <typename T> template <typename T>
class Promise { class Promise : public detail::PromiseBase {
public: public:
// Constructor // Constructor
Promise(Napi::Env env) : deferred(Napi::Promise::Deferred::New(env)) { Promise(Napi::Env env, const PromiseInfo& info) : PromiseBase(env, info) {
}
// Implicit conversion operators to Napi promises.
inline operator napi_value() const {
return deferred.Promise();
}
inline operator Napi::Value() const {
return deferred.Promise();
}
inline operator Napi::Promise() const {
return deferred.Promise();
} }
// Resolve() fulfills the promise with the given value. // Resolve() fulfills the promise with the given value.
void Resolve(T&& value) const { void Resolve(T&& value) const {
deferred.Resolve(ToJS(deferred.Env(), std::forward<T>(value))); PromiseBase::Resolve(ToJS(state->deferred.Env(), std::forward<T>(value)));
} }
// Reject() rejects the promise with the given failure value.
void Reject(Napi::Object obj) const {
deferred.Reject(obj);
}
void Reject(Napi::Error err) const {
deferred.Reject(err.Value());
}
void Reject(std::string err) const {
Reject(Napi::Error::New(deferred.Env(), err));
}
private:
Napi::Promise::Deferred deferred;
}; };
// Specialization for Promises that resolve with no value // Specialization for Promises that resolve with no value
template <> template <>
class Promise<void> { class Promise<void> : public detail::PromiseBase {
public: public:
// Constructor // Constructor
Promise(Napi::Env env) : deferred(Napi::Promise::Deferred::New(env)) { Promise(Napi::Env env, const PromiseInfo& info) : PromiseBase(env, info) {
}
// Implicit conversion operators to Napi promises.
inline operator napi_value() const {
return deferred.Promise();
}
inline operator Napi::Value() const {
return deferred.Promise();
}
inline operator Napi::Promise() const {
return deferred.Promise();
} }
// Resolve() fulfills the promise. // Resolve() fulfills the promise.
void Resolve() const { void Resolve() const {
deferred.Resolve(deferred.Env().Undefined()); PromiseBase::Resolve(state->deferred.Env().Undefined());
} }
// Reject() rejects the promise with the given failure value.
void Reject(Napi::Object obj) const {
deferred.Reject(obj);
}
void Reject(Napi::Error err) const {
deferred.Reject(err.Value());
}
void Reject(std::string err) const {
Reject(Napi::Error::New(deferred.Env(), err));
}
private:
Napi::Promise::Deferred deferred;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////