Commit 4569ffae authored by Michael Lippautz's avatar Michael Lippautz Committed by Commit Bot

Migrate CrossThreadPersistent

Adds a cross-thread reference for strongly and weakly retaining
objects on a thread other than the thread that owns the object.

The intended use of the reference is by setting it up on the
originating thread, holding the object alive from another thread, and
ultimately accessing the object again on the originating thread.

The reference has known caveats:
- It's unsafe to use when the heap may terminate;
- It's unsafe to transitively reach through the graph because of
  compaction;

Change-Id: I84fbdde69a099eb54af5b93c34e2169915b17e64
Bug: chromium:1056170
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2436449
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarOmer Katz <omerkatz@chromium.org>
Reviewed-by: 's avatarAnton Bikineev <bikineev@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70428}
parent c97e79c0
......@@ -4422,6 +4422,7 @@ v8_source_set("cppgc_base") {
"src/heap/cppgc/prefinalizer-handler.cc",
"src/heap/cppgc/prefinalizer-handler.h",
"src/heap/cppgc/process-heap.cc",
"src/heap/cppgc/process-heap.h",
"src/heap/cppgc/raw-heap.cc",
"src/heap/cppgc/raw-heap.h",
"src/heap/cppgc/sanitizers.h",
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
#define INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
#include <atomic>
#include "cppgc/internal/persistent-node.h"
#include "cppgc/internal/pointer-policies.h"
#include "cppgc/persistent.h"
#include "cppgc/visitor.h"
namespace cppgc {
namespace internal {
template <typename T, typename WeaknessPolicy, typename LocationPolicy,
typename CheckingPolicy>
class BasicCrossThreadPersistent final : public PersistentBase,
public LocationPolicy,
private WeaknessPolicy,
private CheckingPolicy {
public:
using typename WeaknessPolicy::IsStrongPersistent;
using PointeeType = T;
~BasicCrossThreadPersistent() { Clear(); }
BasicCrossThreadPersistent( // NOLINT
const SourceLocation& loc = SourceLocation::Current())
: LocationPolicy(loc) {}
BasicCrossThreadPersistent( // NOLINT
std::nullptr_t, const SourceLocation& loc = SourceLocation::Current())
: LocationPolicy(loc) {}
BasicCrossThreadPersistent( // NOLINT
SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())
: PersistentBase(s), LocationPolicy(loc) {}
BasicCrossThreadPersistent( // NOLINT
T* raw, const SourceLocation& loc = SourceLocation::Current())
: PersistentBase(raw), LocationPolicy(loc) {
if (!IsValid(raw)) return;
PersistentRegionLock guard;
PersistentRegion& region = this->GetPersistentRegion(raw);
SetNode(region.AllocateNode(this, &Trace));
this->CheckPointer(raw);
}
BasicCrossThreadPersistent( // NOLINT
T& raw, const SourceLocation& loc = SourceLocation::Current())
: BasicCrossThreadPersistent(&raw, loc) {}
template <typename U, typename MemberBarrierPolicy,
typename MemberWeaknessTag, typename MemberCheckingPolicy,
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
BasicCrossThreadPersistent( // NOLINT
internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
MemberCheckingPolicy>
member,
const SourceLocation& loc = SourceLocation::Current())
: BasicCrossThreadPersistent(member.Get(), loc) {}
BasicCrossThreadPersistent(
const BasicCrossThreadPersistent& other,
const SourceLocation& loc = SourceLocation::Current())
: BasicCrossThreadPersistent(loc) {
// Invoke operator=.
*this = other;
}
// Heterogeneous ctor.
template <typename U, typename OtherWeaknessPolicy,
typename OtherLocationPolicy, typename OtherCheckingPolicy,
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
BasicCrossThreadPersistent( // NOLINT
const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
OtherLocationPolicy,
OtherCheckingPolicy>& other,
const SourceLocation& loc = SourceLocation::Current())
: BasicCrossThreadPersistent(loc) {
*this = other;
}
BasicCrossThreadPersistent(
BasicCrossThreadPersistent&& other,
const SourceLocation& loc = SourceLocation::Current()) noexcept {
// Invoke operator=.
*this = std::move(other);
}
BasicCrossThreadPersistent& operator=(
const BasicCrossThreadPersistent& other) {
PersistentRegionLock guard;
AssignUnsafe(other.Get());
return *this;
}
template <typename U, typename OtherWeaknessPolicy,
typename OtherLocationPolicy, typename OtherCheckingPolicy,
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
BasicCrossThreadPersistent& operator=(
const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
OtherLocationPolicy,
OtherCheckingPolicy>& other) {
PersistentRegionLock guard;
AssignUnsafe(other.Get());
return *this;
}
BasicCrossThreadPersistent& operator=(BasicCrossThreadPersistent&& other) {
if (this == &other) return *this;
Clear();
PersistentRegionLock guard;
PersistentBase::operator=(std::move(other));
LocationPolicy::operator=(std::move(other));
if (!IsValid(GetValue())) return *this;
GetNode()->UpdateOwner(this);
other.SetValue(nullptr);
other.SetNode(nullptr);
this->CheckPointer(GetValue());
return *this;
}
BasicCrossThreadPersistent& operator=(T* other) {
Assign(other);
return *this;
}
// Assignment from member.
template <typename U, typename MemberBarrierPolicy,
typename MemberWeaknessTag, typename MemberCheckingPolicy,
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
BasicCrossThreadPersistent& operator=(
internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
MemberCheckingPolicy>
member) {
return operator=(member.Get());
}
BasicCrossThreadPersistent& operator=(std::nullptr_t) {
Clear();
return *this;
}
BasicCrossThreadPersistent& operator=(SentinelPointer s) {
Assign(s);
return *this;
}
/**
* Returns a pointer to the stored object.
*
* Note: **Not thread-safe.**
*
* \returns a pointer to the stored object.
*/
// CFI cast exemption to allow passing SentinelPointer through T* and support
// heterogeneous assignments between different Member and Persistent handles
// based on their actual types.
V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const {
return static_cast<T*>(GetValue());
}
/**
* Clears the stored object.
*/
void Clear() { Assign(nullptr); }
/**
* Returns a pointer to the stored object and releases it.
*
* Note: **Not thread-safe.**
*
* \returns a pointer to the stored object.
*/
T* Release() {
T* result = Get();
Clear();
return result;
}
/**
* Conversio to boolean.
*
* Note: **Not thread-safe.**
*
* \returns true if an actual object has been stored and false otherwise.
*/
explicit operator bool() const { return Get(); }
/**
* Conversion to object of type T.
*
* Note: **Not thread-safe.**
*
* \returns the object.
*/
operator T*() const { return Get(); } // NOLINT
/**
* Dereferences the stored object.
*
* Note: **Not thread-safe.**
*/
T* operator->() const { return Get(); }
T& operator*() const { return *Get(); }
private:
static bool IsValid(void* ptr) { return ptr && ptr != kSentinelPointer; }
static void Trace(Visitor* v, const void* ptr) {
const auto* handle = static_cast<const BasicCrossThreadPersistent*>(ptr);
v->TraceRoot(*handle, handle->Location());
}
void Assign(T* ptr) {
void* old_value = GetValue();
if (IsValid(old_value)) {
PersistentRegionLock guard;
PersistentRegion& region = this->GetPersistentRegion(old_value);
if (IsValid(ptr) && (&region == &this->GetPersistentRegion(ptr))) {
SetValue(ptr);
this->CheckPointer(ptr);
return;
}
region.FreeNode(GetNode());
SetNode(nullptr);
}
SetValue(ptr);
if (!IsValid(ptr)) return;
PersistentRegionLock guard;
SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace));
this->CheckPointer(ptr);
}
void AssignUnsafe(T* ptr) {
void* old_value = GetValue();
if (IsValid(old_value)) {
PersistentRegion& region = this->GetPersistentRegion(old_value);
if (IsValid(ptr) && (&region == &this->GetPersistentRegion(ptr))) {
SetValue(ptr);
this->CheckPointer(ptr);
return;
}
region.FreeNode(GetNode());
SetNode(nullptr);
}
SetValue(ptr);
if (!IsValid(ptr)) return;
SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace));
this->CheckPointer(ptr);
}
void ClearFromGC() const {
if (IsValid(GetValue())) {
WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode());
PersistentBase::ClearFromGC();
}
}
friend class cppgc::Visitor;
};
template <typename T, typename LocationPolicy, typename CheckingPolicy>
struct IsWeak<
BasicCrossThreadPersistent<T, internal::WeakCrossThreadPersistentPolicy,
LocationPolicy, CheckingPolicy>>
: std::true_type {};
} // namespace internal
namespace subtle {
/**
* **DO NOT USE: Has known caveats, see below.**
*
* CrossThreadPersistent allows retaining objects from threads other than the
* thread the owning heap is operating on.
*
* Known caveats:
* - Does not protect the heap owning an object from terminating.
* - Reaching transitively through the graph is unsupported as objects may be
* moved concurrently on the thread owning the object.
*/
template <typename T>
using CrossThreadPersistent = internal::BasicCrossThreadPersistent<
T, internal::StrongCrossThreadPersistentPolicy>;
/**
* **DO NOT USE: Has known caveats, see below.**
*
* CrossThreadPersistent allows weakly retaining objects from threads other than
* the thread the owning heap is operating on.
*
* Known caveats:
* - Does not protect the heap owning an object from terminating.
* - Reaching transitively through the graph is unsupported as objects may be
* moved concurrently on the thread owning the object.
*/
template <typename T>
using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent<
T, internal::WeakCrossThreadPersistentPolicy>;
} // namespace subtle
} // namespace cppgc
#endif // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
......@@ -109,6 +109,14 @@ class V8_EXPORT PersistentRegion final {
PersistentNode* free_list_head_ = nullptr;
};
// CrossThreadPersistent uses PersistentRegion but protects it using this lock
// when needed.
class V8_EXPORT PersistentRegionLock final {
public:
PersistentRegionLock();
~PersistentRegionLock();
};
} // namespace internal
} // namespace cppgc
......
......@@ -62,6 +62,7 @@ class KeepLocationPolicy {
constexpr const SourceLocation& Location() const { return location_; }
protected:
constexpr KeepLocationPolicy() = default;
constexpr explicit KeepLocationPolicy(const SourceLocation& location)
: location_(location) {}
......@@ -82,6 +83,7 @@ class IgnoreLocationPolicy {
constexpr SourceLocation Location() const { return {}; }
protected:
constexpr IgnoreLocationPolicy() = default;
constexpr explicit IgnoreLocationPolicy(const SourceLocation&) {}
};
......@@ -93,17 +95,29 @@ using DefaultLocationPolicy = IgnoreLocationPolicy;
struct StrongPersistentPolicy {
using IsStrongPersistent = std::true_type;
static V8_EXPORT PersistentRegion& GetPersistentRegion(void* object);
};
struct WeakPersistentPolicy {
using IsStrongPersistent = std::false_type;
static V8_EXPORT PersistentRegion& GetPersistentRegion(void* object);
};
struct StrongCrossThreadPersistentPolicy {
using IsStrongPersistent = std::true_type;
static V8_EXPORT PersistentRegion& GetPersistentRegion(void* object);
};
struct WeakCrossThreadPersistentPolicy {
using IsStrongPersistent = std::false_type;
static V8_EXPORT PersistentRegion& GetPersistentRegion(void* object);
};
// Persistent/Member forward declarations.
// Forward declarations setting up the default policies.
template <typename T, typename WeaknessPolicy,
typename LocationPolicy = DefaultLocationPolicy,
typename CheckingPolicy = DisabledCheckingPolicy>
class BasicCrossThreadPersistent;
template <typename T, typename WeaknessPolicy,
typename LocationPolicy = DefaultLocationPolicy,
typename CheckingPolicy = DefaultCheckingPolicy>
......
......@@ -16,6 +16,9 @@
namespace cppgc {
namespace internal {
template <typename T, typename WeaknessPolicy, typename LocationPolicy,
typename CheckingPolicy>
class BasicCrossThreadPersistent;
template <typename T, typename WeaknessPolicy, typename LocationPolicy,
typename CheckingPolicy>
class BasicPersistent;
......@@ -200,6 +203,9 @@ class Visitor {
V8_EXPORT void CheckObjectNotInConstruction(const void* address);
#endif // V8_ENABLE_CHECKS
template <typename T, typename WeaknessPolicy, typename LocationPolicy,
typename CheckingPolicy>
friend class internal::BasicCrossThreadPersistent;
template <typename T, typename WeaknessPolicy, typename LocationPolicy,
typename CheckingPolicy>
friend class internal::BasicPersistent;
......
......@@ -113,6 +113,18 @@ class V8_EXPORT_PRIVATE HeapBase {
const PersistentRegion& GetWeakPersistentRegion() const {
return weak_persistent_region_;
}
PersistentRegion& GetStrongCrossThreadPersistentRegion() {
return strong_cross_thread_persistent_region_;
}
const PersistentRegion& GetStrongCrossThreadPersistentRegion() const {
return strong_cross_thread_persistent_region_;
}
PersistentRegion& GetWeakCrossThreadPersistentRegion() {
return weak_cross_thread_persistent_region_;
}
const PersistentRegion& GetWeakCrossThreadPersistentRegion() const {
return weak_cross_thread_persistent_region_;
}
#if defined(CPPGC_YOUNG_GENERATION)
std::set<void*>& remembered_slots() { return remembered_slots_; }
......@@ -149,6 +161,8 @@ class V8_EXPORT_PRIVATE HeapBase {
PersistentRegion strong_persistent_region_;
PersistentRegion weak_persistent_region_;
PersistentRegion strong_cross_thread_persistent_region_;
PersistentRegion weak_cross_thread_persistent_region_;
#if defined(CPPGC_YOUNG_GENERATION)
std::set<void*> remembered_slots_;
......
......@@ -15,6 +15,7 @@
#include "src/heap/cppgc/liveness-broker.h"
#include "src/heap/cppgc/marking-state.h"
#include "src/heap/cppgc/marking-visitor.h"
#include "src/heap/cppgc/process-heap.h"
#include "src/heap/cppgc/stats-collector.h"
#if defined(CPPGC_CAGED_HEAP)
......@@ -203,6 +204,12 @@ void MarkerBase::EnterAtomicPause(MarkingConfig::StackState stack_state) {
config_.stack_state = stack_state;
config_.marking_type = MarkingConfig::MarkingType::kAtomic;
// Lock guards against changes to {Weak}CrossThreadPersistent handles, that
// may conflict with marking. E.g., a WeakCrossThreadPersistent may be
// converted into a CrossThreadPersistent which requires that the handle
// is either cleared or the object is retained.
g_process_mutex.Pointer()->Lock();
// VisitRoots also resets the LABs.
VisitRoots(config_.stack_state);
if (config_.stack_state == MarkingConfig::StackState::kNoHeapPointers) {
......@@ -220,6 +227,7 @@ void MarkerBase::LeaveAtomicPause() {
schedule_.GetOverallMarkedBytes());
is_marking_started_ = false;
ProcessWeakness();
g_process_mutex.Pointer()->Unlock();
}
void MarkerBase::FinishMarking(MarkingConfig::StackState stack_state) {
......@@ -232,7 +240,11 @@ void MarkerBase::FinishMarking(MarkingConfig::StackState stack_state) {
}
void MarkerBase::ProcessWeakness() {
DCHECK_EQ(MarkingConfig::MarkingType::kAtomic, config_.marking_type);
heap().GetWeakPersistentRegion().Trace(&visitor());
// Processing cross-thread handles requires taking the process lock.
g_process_mutex.Get().AssertHeld();
heap().GetWeakCrossThreadPersistentRegion().Trace(&visitor());
// Call weak callbacks on objects that may now be pointing to dead objects.
MarkingWorklists::WeakCallbackItem item;
......@@ -252,6 +264,10 @@ void MarkerBase::VisitRoots(MarkingConfig::StackState stack_state) {
heap().object_allocator().ResetLinearAllocationBuffers();
heap().GetStrongPersistentRegion().Trace(&visitor());
if (config_.marking_type == MarkingConfig::MarkingType::kAtomic) {
g_process_mutex.Get().AssertHeld();
heap().GetStrongCrossThreadPersistentRegion().Trace(&visitor());
}
if (stack_state != MarkingConfig::StackState::kNoHeapPointers) {
heap().stack()->IteratePointers(&stack_visitor());
}
......
......@@ -8,6 +8,7 @@
#include <numeric>
#include "include/cppgc/persistent.h"
#include "src/heap/cppgc/process-heap.h"
namespace cppgc {
namespace internal {
......@@ -68,5 +69,13 @@ void PersistentRegion::Trace(Visitor* visitor) {
nodes_.end());
}
PersistentRegionLock::PersistentRegionLock() {
g_process_mutex.Pointer()->Lock();
}
PersistentRegionLock::~PersistentRegionLock() {
g_process_mutex.Pointer()->Unlock();
}
} // namespace internal
} // namespace cppgc
......@@ -31,5 +31,17 @@ PersistentRegion& WeakPersistentPolicy::GetPersistentRegion(void* object) {
return heap->GetWeakPersistentRegion();
}
PersistentRegion& StrongCrossThreadPersistentPolicy::GetPersistentRegion(
void* object) {
auto* heap = BasePage::FromPayload(object)->heap();
return heap->GetStrongCrossThreadPersistentRegion();
}
PersistentRegion& WeakCrossThreadPersistentPolicy::GetPersistentRegion(
void* object) {
auto* heap = BasePage::FromPayload(object)->heap();
return heap->GetWeakCrossThreadPersistentRegion();
}
} // namespace internal
} // namespace cppgc
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/heap/cppgc/process-heap.h"
#include "include/cppgc/internal/process-heap.h"
namespace cppgc {
......@@ -9,5 +11,7 @@ namespace internal {
AtomicEntryFlag ProcessHeap::concurrent_marking_flag_;
v8::base::LazyMutex g_process_mutex = LAZY_MUTEX_INITIALIZER;
} // namespace internal
} // namespace cppgc
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_HEAP_CPPGC_PROCESS_HEAP_H_
#define V8_HEAP_CPPGC_PROCESS_HEAP_H_
#include "src/base/platform/mutex.h"
namespace cppgc {
namespace internal {
extern v8::base::LazyMutex g_process_mutex;
} // namespace internal
} // namespace cppgc
#endif // V8_HEAP_CPPGC_PROCESS_HEAP_H_
......@@ -82,6 +82,7 @@ v8_source_set("cppgc_unittests_sources") {
sources = [
"heap/cppgc/concurrent-marking-unittest.cc",
"heap/cppgc/concurrent-sweeper-unittest.cc",
"heap/cppgc/cross-thread-persistent-unittest.cc",
"heap/cppgc/custom-spaces-unittest.cc",
"heap/cppgc/finalizer-trait-unittest.cc",
"heap/cppgc/free-list-unittest.cc",
......@@ -102,7 +103,7 @@ v8_source_set("cppgc_unittests_sources") {
"heap/cppgc/name-trait-unittest.cc",
"heap/cppgc/object-start-bitmap-unittest.cc",
"heap/cppgc/page-memory-unittest.cc",
"heap/cppgc/persistent-unittest.cc",
"heap/cppgc/persistent-family-unittest.cc",
"heap/cppgc/prefinalizer-unittest.cc",
"heap/cppgc/source-location-unittest.cc",
"heap/cppgc/stack-unittest.cc",
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/cppgc/cross-thread-persistent.h"
#include "include/cppgc/allocation.h"
#include "src/base/platform/condition-variable.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/platform.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
struct GCed final : GarbageCollected<GCed> {
static size_t destructor_call_count;
GCed() { destructor_call_count = 0; }
~GCed() { destructor_call_count++; }
virtual void Trace(cppgc::Visitor*) const {}
int a = 0;
};
size_t GCed::destructor_call_count = 0;
class Runner final : public v8::base::Thread {
public:
template <typename Callback>
explicit Runner(Callback callback)
: Thread(v8::base::Thread::Options("CrossThreadPersistent Thread")),
callback_(callback) {}
void Run() final { callback_(); }
private:
std::function<void()> callback_;
};
} // namespace
class CrossThreadPersistentTest : public testing::TestWithHeap {};
TEST_F(CrossThreadPersistentTest, RetainStronglyOnDifferentThread) {
subtle::CrossThreadPersistent<GCed> holder =
MakeGarbageCollected<GCed>(GetAllocationHandle());
{
Runner runner([obj = std::move(holder)]() {});
EXPECT_FALSE(holder);
EXPECT_EQ(0u, GCed::destructor_call_count);
PreciseGC();
EXPECT_EQ(0u, GCed::destructor_call_count);
runner.StartSynchronously();
runner.Join();
}
EXPECT_EQ(0u, GCed::destructor_call_count);
PreciseGC();
EXPECT_EQ(1u, GCed::destructor_call_count);
}
TEST_F(CrossThreadPersistentTest, RetainWeaklyOnDifferentThread) {
subtle::WeakCrossThreadPersistent<GCed> in =
MakeGarbageCollected<GCed>(GetAllocationHandle());
// Set up |out| with an object that is always retained to ensure that the
// different thread indeed moves back an empty handle.
Persistent<GCed> out_holder =
MakeGarbageCollected<GCed>(GetAllocationHandle());
subtle::WeakCrossThreadPersistent<GCed> out = *out_holder;
{
Persistent<GCed> temporary_holder = *in;
Runner runner([obj = std::move(in), &out]() { out = std::move(obj); });
EXPECT_FALSE(in);
EXPECT_TRUE(out);
EXPECT_EQ(0u, GCed::destructor_call_count);
PreciseGC();
EXPECT_EQ(0u, GCed::destructor_call_count);
temporary_holder.Clear();
PreciseGC();
EXPECT_EQ(1u, GCed::destructor_call_count);
runner.StartSynchronously();
runner.Join();
}
EXPECT_FALSE(out);
}
TEST_F(CrossThreadPersistentTest, DestroyRacingWithGC) {
// Destroy a handle on a different thread while at the same time invoking a
// garbage collection on the original thread.
subtle::CrossThreadPersistent<GCed> holder =
MakeGarbageCollected<GCed>(GetAllocationHandle());
Runner runner([&obj = holder]() { obj.Clear(); });
EXPECT_TRUE(holder);
runner.StartSynchronously();
PreciseGC();
runner.Join();
EXPECT_FALSE(holder);
}
} // namespace internal
} // namespace cppgc
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment