cppgc: Fix CTP destruction
Double-checked locking pattern for destruction was missing the acquire barrier for the initial load. TSAN complained with a data race where: T1: ClearAllUsedNodes(), clearing out the node T2: a. if(GetNodeSafe()) { Lock; ... } T2: b. operator delete Since GetNodeSafe() was a relaxed load, operator delete was allowed to be reordered which raced with ClearAllUsedNodes(). Bug: chromium:1239081, chromium:1242795 Change-Id: I3906555b13cc51538a1a54b7ca481a96d81fd84e Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3132264 Commit-Queue: Michael Lippautz <mlippautz@chromium.org> Reviewed-by: Omer Katz <omerkatz@chromium.org> Reviewed-by: Anton Bikineev <bikineev@chromium.org> Cr-Commit-Position: refs/heads/main@{#76599}
This commit is contained in:
parent
0ffc1ba5fc
commit
760682da3e
@ -37,10 +37,11 @@ class CrossThreadPersistentBase : public PersistentBase {
|
|||||||
SetNodeSafe(nullptr);
|
SetNodeSafe(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNodeSafe() can be used for a thread-safe IsValid() check.
|
// GetNodeSafe() can be used for a thread-safe IsValid() check in a
|
||||||
|
// double-checked locking pattern. See ~BasicCrossThreadPersistent.
|
||||||
PersistentNode* GetNodeSafe() const {
|
PersistentNode* GetNodeSafe() const {
|
||||||
return reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->load(
|
return reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->load(
|
||||||
std::memory_order_relaxed);
|
std::memory_order_acquire);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The GC writes using SetNodeSafe() while holding the lock.
|
// The GC writes using SetNodeSafe() while holding the lock.
|
||||||
@ -53,12 +54,12 @@ class CrossThreadPersistentBase : public PersistentBase {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef V8_IS_ASAN
|
#ifdef V8_IS_ASAN
|
||||||
__atomic_store(&node_, &value, __ATOMIC_RELAXED);
|
__atomic_store(&node_, &value, __ATOMIC_RELEASE);
|
||||||
#else // !V8_IS_ASAN
|
#else // !V8_IS_ASAN
|
||||||
// Non-ASAN builds can use atomics. This also covers MSVC which does not
|
// Non-ASAN builds can use atomics. This also covers MSVC which does not
|
||||||
// have the __atomic_store intrinsic.
|
// have the __atomic_store intrinsic.
|
||||||
reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->store(
|
reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->store(
|
||||||
value, std::memory_order_relaxed);
|
value, std::memory_order_release);
|
||||||
#endif // !V8_IS_ASAN
|
#endif // !V8_IS_ASAN
|
||||||
|
|
||||||
#undef V8_IS_ASAN
|
#undef V8_IS_ASAN
|
||||||
@ -76,10 +77,11 @@ class BasicCrossThreadPersistent final : public CrossThreadPersistentBase,
|
|||||||
using PointeeType = T;
|
using PointeeType = T;
|
||||||
|
|
||||||
~BasicCrossThreadPersistent() {
|
~BasicCrossThreadPersistent() {
|
||||||
|
// This implements fast path for destroying empty/sentinel.
|
||||||
|
//
|
||||||
// Simplified version of `AssignUnsafe()` to allow calling without a
|
// Simplified version of `AssignUnsafe()` to allow calling without a
|
||||||
// complete type `T`. Also performs a thread-safe check for a handle that is
|
// complete type `T`. Uses double-checked locking with a simple thread-safe
|
||||||
// not valid. This implements fast path for destroying empty/sentinel
|
// check for a valid handle based on a node.
|
||||||
// handles.
|
|
||||||
if (GetNodeSafe()) {
|
if (GetNodeSafe()) {
|
||||||
PersistentRegionLock guard;
|
PersistentRegionLock guard;
|
||||||
const void* old_value = GetValue();
|
const void* old_value = GetValue();
|
||||||
|
Loading…
Reference in New Issue
Block a user