Refcount check in Buffer map async in Wire
If buffer is released and the external refcount reaches 0 while
buffer map state is pending map in Wire, the map async callback
is fired with destroyed-before-callback status from the buffer
destructor.
It is possible to call another MapAsync in the callback. At that
time the pointer is still valid because the internal refcount is
not 0 yet.
The behavior of MapAsync should be undefined if external refcount
is 0. This commit adds an assert to check whether external
refcount is 0 in Buffer::MapAsync() in Wire. Ending up with
assertion error may be reasonable.
This commit also adds protected GetRefcount() method to
ObjectBase to allow derived classes to check the refcount.
bug: dawn:1624
Change-Id: I95411a7be2093ba7bb2bb45b466f17f1ebac0ca9
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/119961
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
index dd37ade..e4bf75d 100644
--- a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
@@ -15,6 +15,7 @@
#include <limits>
#include <memory>
+#include "dawn/common/Assert.h"
#include "dawn/tests/unittests/wire/WireTest.h"
#include "dawn/wire/WireClient.h"
@@ -818,6 +819,37 @@
}
}
+#if defined(DAWN_ENABLE_ASSERTS)
+static void ToMockBufferMapCallbackWithAssertErrorRequest(WGPUBufferMapAsyncStatus status,
+ void* userdata) {
+ WGPUBuffer* buffer = reinterpret_cast<WGPUBuffer*>(userdata);
+
+ mockBufferMapCallback->Call(status, buffer);
+ ASSERT_DEATH(
+ {
+ // This map async should cause assertion error because of
+ // refcount == 0.
+ wgpuBufferMapAsync(*buffer, WGPUMapMode_Read, 0, sizeof(uint32_t),
+ ToMockBufferMapCallback, nullptr);
+ },
+ "");
+}
+
+// Test that request inside user callbacks after object destruction is called
+TEST_F(WireBufferMappingTests, MapInsideCallbackAfterDestruction) {
+ SetupBuffer(WGPUMapMode_Read);
+
+ wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize,
+ ToMockBufferMapCallbackWithAssertErrorRequest, &buffer);
+
+ // By releasing the buffer the refcount reaches zero and pending map async
+ // should fail with destroyed before callback status.
+ EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _))
+ .Times(1);
+ wgpuBufferRelease(buffer);
+}
+#endif // defined(DAWN_ENABLE_ASSERTS)
+
// Hack to pass in test context into user callback
struct TestData {
WireBufferMappingTests* pTest;
@@ -864,6 +896,7 @@
// Test that requests inside user callbacks before object destruction are called
TEST_F(WireBufferMappingWriteTests, MapInsideCallbackBeforeDestruction) {
+ SetupBuffer(WGPUMapMode_Write);
TestData testData = {this, &buffer, 10};
wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize,
ToMockBufferMapCallbackWithNewRequests, &testData);
@@ -876,12 +909,22 @@
FlushClient();
- // Maybe this should be assert errors, see dawn:1624
+ // The first map async call should succeed
+ EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, this)).Times(1);
+
+ // The second or later map async calls in the map async callback
+ // should immediately fail because of pending map
EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Error, this))
.Times(testData.numRequests - 1);
+
+ // The first map async call in the map async callback should fail
+ // with destroyed before callback status due to buffer release below
EXPECT_CALL(*mockBufferMapCallback,
Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, this))
.Times(1);
+
+ FlushServer();
+
wgpuBufferRelease(buffer);
}
diff --git a/src/dawn/wire/client/Buffer.cpp b/src/dawn/wire/client/Buffer.cpp
index b9093e4..32e9ef0 100644
--- a/src/dawn/wire/client/Buffer.cpp
+++ b/src/dawn/wire/client/Buffer.cpp
@@ -183,6 +183,8 @@
size_t size,
WGPUBufferMapCallback callback,
void* userdata) {
+ ASSERT(GetRefcount() != 0);
+
if (mPendingMap) {
return callback(WGPUBufferMapAsyncStatus_Error, userdata);
}
diff --git a/src/dawn/wire/client/ObjectBase.h b/src/dawn/wire/client/ObjectBase.h
index cf5f8ee..feb0086 100644
--- a/src/dawn/wire/client/ObjectBase.h
+++ b/src/dawn/wire/client/ObjectBase.h
@@ -51,6 +51,9 @@
// object.
[[nodiscard]] bool Release();
+ protected:
+ uint32_t GetRefcount() const { return mRefcount; }
+
private:
Client* const mClient;
const ObjectHandle mHandle;