Make error scope shutdown iterative instead of recursive.
This avoids a stack overflow when many error scopes are pushed on device shutdown. It also changes the error scopes to return a Unknown error type on shutdown instead of NoError. A regression test is added. Bug: chromium:1078438 Bug: chromium:1081063 Change-Id: Ibfab8dd19480414c1854ec2bd4928939663ba698 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/21440 Commit-Queue: Corentin Wallez <cwallez@chromium.org> Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
parent
c2542cde3e
commit
a6cf7b5b1d
|
@ -152,6 +152,7 @@ namespace dawn_native {
|
||||||
mState = State::Disconnected;
|
mState = State::Disconnected;
|
||||||
|
|
||||||
mErrorScopeTracker = nullptr;
|
mErrorScopeTracker = nullptr;
|
||||||
|
mCurrentErrorScope->UnlinkForShutdown();
|
||||||
mFenceSignalTracker = nullptr;
|
mFenceSignalTracker = nullptr;
|
||||||
mDynamicUploader = nullptr;
|
mDynamicUploader = nullptr;
|
||||||
|
|
||||||
|
|
|
@ -18,18 +18,18 @@
|
||||||
|
|
||||||
namespace dawn_native {
|
namespace dawn_native {
|
||||||
|
|
||||||
ErrorScope::ErrorScope() = default;
|
ErrorScope::ErrorScope() : mIsRoot(true) {
|
||||||
|
}
|
||||||
|
|
||||||
ErrorScope::ErrorScope(wgpu::ErrorFilter errorFilter, ErrorScope* parent)
|
ErrorScope::ErrorScope(wgpu::ErrorFilter errorFilter, ErrorScope* parent)
|
||||||
: RefCounted(), mErrorFilter(errorFilter), mParent(parent) {
|
: RefCounted(), mErrorFilter(errorFilter), mParent(parent), mIsRoot(false) {
|
||||||
ASSERT(mParent.Get() != nullptr);
|
ASSERT(mParent.Get() != nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorScope::~ErrorScope() {
|
ErrorScope::~ErrorScope() {
|
||||||
if (mCallback == nullptr || IsRoot()) {
|
if (!IsRoot()) {
|
||||||
return;
|
RunNonRootCallback();
|
||||||
}
|
}
|
||||||
mCallback(static_cast<WGPUErrorType>(mErrorType), mErrorMessage.c_str(), mUserdata);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ErrorScope::SetCallback(wgpu::ErrorCallback callback, void* userdata) {
|
void ErrorScope::SetCallback(wgpu::ErrorCallback callback, void* userdata) {
|
||||||
|
@ -42,13 +42,27 @@ namespace dawn_native {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ErrorScope::IsRoot() const {
|
bool ErrorScope::IsRoot() const {
|
||||||
return mParent.Get() == nullptr;
|
return mIsRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ErrorScope::RunNonRootCallback() {
|
||||||
|
ASSERT(!IsRoot());
|
||||||
|
|
||||||
|
if (mCallback != nullptr) {
|
||||||
|
// For non-root error scopes, the callback can run at most once.
|
||||||
|
mCallback(static_cast<WGPUErrorType>(mErrorType), mErrorMessage.c_str(), mUserdata);
|
||||||
|
mCallback = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ErrorScope::HandleError(wgpu::ErrorType type, const char* message) {
|
void ErrorScope::HandleError(wgpu::ErrorType type, const char* message) {
|
||||||
HandleErrorImpl(this, type, message);
|
HandleErrorImpl(this, type, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ErrorScope::UnlinkForShutdown() {
|
||||||
|
UnlinkForShutdownImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void ErrorScope::HandleErrorImpl(ErrorScope* scope, wgpu::ErrorType type, const char* message) {
|
void ErrorScope::HandleErrorImpl(ErrorScope* scope, wgpu::ErrorType type, const char* message) {
|
||||||
ErrorScope* currentScope = scope;
|
ErrorScope* currentScope = scope;
|
||||||
|
@ -105,10 +119,24 @@ namespace dawn_native {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ErrorScope::Destroy() {
|
// static
|
||||||
if (!IsRoot()) {
|
void ErrorScope::UnlinkForShutdownImpl(ErrorScope* scope) {
|
||||||
mErrorType = wgpu::ErrorType::Unknown;
|
Ref<ErrorScope> currentScope = scope;
|
||||||
mErrorMessage = "Error scope destroyed";
|
Ref<ErrorScope> parentScope = nullptr;
|
||||||
|
for (; !currentScope->IsRoot(); currentScope = parentScope.Get()) {
|
||||||
|
ASSERT(!currentScope->IsRoot());
|
||||||
|
ASSERT(currentScope.Get() != nullptr);
|
||||||
|
parentScope = std::move(currentScope->mParent);
|
||||||
|
ASSERT(parentScope.Get() != nullptr);
|
||||||
|
|
||||||
|
// On shutdown, error scopes that have yet to have a status get Unknown.
|
||||||
|
if (currentScope->mErrorType == wgpu::ErrorType::NoError) {
|
||||||
|
currentScope->mErrorType = wgpu::ErrorType::Unknown;
|
||||||
|
currentScope->mErrorMessage = "Error scope destroyed";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the callback if it hasn't run already.
|
||||||
|
currentScope->RunNonRootCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,16 +44,19 @@ namespace dawn_native {
|
||||||
ErrorScope* GetParent();
|
ErrorScope* GetParent();
|
||||||
|
|
||||||
void HandleError(wgpu::ErrorType type, const char* message);
|
void HandleError(wgpu::ErrorType type, const char* message);
|
||||||
|
void UnlinkForShutdown();
|
||||||
void Destroy();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~ErrorScope() override;
|
~ErrorScope() override;
|
||||||
bool IsRoot() const;
|
bool IsRoot() const;
|
||||||
|
void RunNonRootCallback();
|
||||||
|
|
||||||
static void HandleErrorImpl(ErrorScope* scope, wgpu::ErrorType type, const char* message);
|
static void HandleErrorImpl(ErrorScope* scope, wgpu::ErrorType type, const char* message);
|
||||||
|
static void UnlinkForShutdownImpl(ErrorScope* scope);
|
||||||
|
|
||||||
wgpu::ErrorFilter mErrorFilter = wgpu::ErrorFilter::None;
|
wgpu::ErrorFilter mErrorFilter = wgpu::ErrorFilter::None;
|
||||||
Ref<ErrorScope> mParent = nullptr;
|
Ref<ErrorScope> mParent = nullptr;
|
||||||
|
bool mIsRoot;
|
||||||
|
|
||||||
wgpu::ErrorCallback mCallback = nullptr;
|
wgpu::ErrorCallback mCallback = nullptr;
|
||||||
void* mUserdata = nullptr;
|
void* mUserdata = nullptr;
|
||||||
|
|
|
@ -30,7 +30,7 @@ namespace dawn_native {
|
||||||
// with UNKNOWN.
|
// with UNKNOWN.
|
||||||
constexpr Serial maxSerial = std::numeric_limits<Serial>::max();
|
constexpr Serial maxSerial = std::numeric_limits<Serial>::max();
|
||||||
for (Ref<ErrorScope>& scope : mScopesInFlight.IterateUpTo(maxSerial)) {
|
for (Ref<ErrorScope>& scope : mScopesInFlight.IterateUpTo(maxSerial)) {
|
||||||
scope->Destroy();
|
scope->UnlinkForShutdown();
|
||||||
}
|
}
|
||||||
Tick(maxSerial);
|
Tick(maxSerial);
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,3 +196,11 @@ TEST_F(ErrorScopeValidationTest, DeviceDestroyedBeforeCallback) {
|
||||||
EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, this)).Times(1);
|
EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, this)).Times(1);
|
||||||
device = nullptr;
|
device = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression test that on device shutdown, we don't get a recursion in O(pushed error scope) that
|
||||||
|
// would lead to a stack overflow
|
||||||
|
TEST_F(ErrorScopeValidationTest, ShutdownStackOverflow) {
|
||||||
|
for (size_t i = 0; i < 1'000'000; i++) {
|
||||||
|
device.PushErrorScope(wgpu::ErrorFilter::Validation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue