Commit 64759d44 authored by Dominik Inführ's avatar Dominik Inführ Committed by Commit Bot

[heap] Introduce safepoint mechanism

Add safepoint mechanism to stop concurrent threads and bring them to a
safepoint. Threads are stopped before the safepoint and after e.g. the
GC resumed again. Each thread needs to be stopped in a safepoint, such
that all roots can be iterated safely.

Running threads need to be cooperative and are required to perform
regular safepoint polls.

The last version of this CL was reverted because safepoint_requested_
wasn't initialized (see https://crrev.com/c/2105634).

Bug: v8:10315
Change-Id: I6ef244c0fb31c178589b5e3d1c62687a8dd65768
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2105635Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Commit-Queue: Dominik Inführ <dinfuehr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66732}
parent 1b2e0ddf
...@@ -2402,6 +2402,8 @@ v8_source_set("v8_base_without_compiler") { ...@@ -2402,6 +2402,8 @@ v8_source_set("v8_base_without_compiler") {
"src/heap/read-only-heap.cc", "src/heap/read-only-heap.cc",
"src/heap/read-only-heap.h", "src/heap/read-only-heap.h",
"src/heap/remembered-set.h", "src/heap/remembered-set.h",
"src/heap/safepoint.cc",
"src/heap/safepoint.h",
"src/heap/scavenge-job.cc", "src/heap/scavenge-job.cc",
"src/heap/scavenge-job.h", "src/heap/scavenge-job.h",
"src/heap/scavenger-inl.h", "src/heap/scavenger-inl.h",
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include "src/heap/objects-visiting.h" #include "src/heap/objects-visiting.h"
#include "src/heap/read-only-heap.h" #include "src/heap/read-only-heap.h"
#include "src/heap/remembered-set.h" #include "src/heap/remembered-set.h"
#include "src/heap/safepoint.h"
#include "src/heap/scavenge-job.h" #include "src/heap/scavenge-job.h"
#include "src/heap/scavenger-inl.h" #include "src/heap/scavenger-inl.h"
#include "src/heap/stress-marking-observer.h" #include "src/heap/stress-marking-observer.h"
...@@ -202,6 +203,7 @@ Heap::Heap() ...@@ -202,6 +203,7 @@ Heap::Heap()
memory_pressure_level_(MemoryPressureLevel::kNone), memory_pressure_level_(MemoryPressureLevel::kNone),
global_pretenuring_feedback_(kInitialFeedbackCapacity), global_pretenuring_feedback_(kInitialFeedbackCapacity),
local_heaps_head_(nullptr), local_heaps_head_(nullptr),
safepoint_(new Safepoint(this)),
external_string_table_(this) { external_string_table_(this) {
// Ensure old_generation_size_ is a multiple of kPageSize. // Ensure old_generation_size_ is a multiple of kPageSize.
DCHECK_EQ(0, max_old_generation_size_ & (Page::kPageSize - 1)); DCHECK_EQ(0, max_old_generation_size_ & (Page::kPageSize - 1));
......
...@@ -83,6 +83,7 @@ class Page; ...@@ -83,6 +83,7 @@ class Page;
class PagedSpace; class PagedSpace;
class ReadOnlyHeap; class ReadOnlyHeap;
class RootVisitor; class RootVisitor;
class Safepoint;
class ScavengeJob; class ScavengeJob;
class Scavenger; class Scavenger;
class ScavengerCollector; class ScavengerCollector;
...@@ -624,6 +625,8 @@ class Heap { ...@@ -624,6 +625,8 @@ class Heap {
V8_EXPORT_PRIVATE bool ContainsLocalHeap(LocalHeap* local_heap); V8_EXPORT_PRIVATE bool ContainsLocalHeap(LocalHeap* local_heap);
V8_EXPORT_PRIVATE bool ContainsAnyLocalHeap(); V8_EXPORT_PRIVATE bool ContainsAnyLocalHeap();
Safepoint* safepoint() { return safepoint_.get(); }
V8_EXPORT_PRIVATE double MonotonicallyIncreasingTimeInMs(); V8_EXPORT_PRIVATE double MonotonicallyIncreasingTimeInMs();
void RecordStats(HeapStats* stats, bool take_snapshot = false); void RecordStats(HeapStats* stats, bool take_snapshot = false);
...@@ -2171,6 +2174,8 @@ class Heap { ...@@ -2171,6 +2174,8 @@ class Heap {
base::Mutex local_heaps_mutex_; base::Mutex local_heaps_mutex_;
LocalHeap* local_heaps_head_; LocalHeap* local_heaps_head_;
std::unique_ptr<Safepoint> safepoint_;
bool is_current_gc_forced_ = false; bool is_current_gc_forced_ = false;
ExternalStringTable external_string_table_; ExternalStringTable external_string_table_;
...@@ -2238,6 +2243,7 @@ class Heap { ...@@ -2238,6 +2243,7 @@ class Heap {
friend class Page; friend class Page;
friend class PagedSpace; friend class PagedSpace;
friend class ReadOnlyRoots; friend class ReadOnlyRoots;
friend class Safepoint;
friend class Scavenger; friend class Scavenger;
friend class ScavengerCollector; friend class ScavengerCollector;
friend class Space; friend class Space;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "src/heap/local-heap.h" #include "src/heap/local-heap.h"
#include "src/heap/heap.h" #include "src/heap/heap.h"
#include "src/heap/safepoint.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -11,17 +12,24 @@ namespace internal { ...@@ -11,17 +12,24 @@ namespace internal {
LocalHeap::LocalHeap(Heap* heap) LocalHeap::LocalHeap(Heap* heap)
: heap_(heap), : heap_(heap),
state_(ThreadState::Running), state_(ThreadState::Running),
safepoint_requested_(false),
prev_(nullptr), prev_(nullptr),
next_(nullptr) { next_(nullptr) {
heap_->AddLocalHeap(this); heap_->AddLocalHeap(this);
} }
LocalHeap::~LocalHeap() { heap_->RemoveLocalHeap(this); } LocalHeap::~LocalHeap() {
// Park thread since removing the local heap could block.
EnsureParkedBeforeDestruction();
heap_->RemoveLocalHeap(this);
}
void LocalHeap::Park() { void LocalHeap::Park() {
base::MutexGuard guard(&state_mutex_); base::MutexGuard guard(&state_mutex_);
CHECK(state_ == ThreadState::Running); CHECK(state_ == ThreadState::Running);
state_ = ThreadState::Parked; state_ = ThreadState::Parked;
state_change_.NotifyAll();
} }
void LocalHeap::Unpark() { void LocalHeap::Unpark() {
...@@ -30,6 +38,12 @@ void LocalHeap::Unpark() { ...@@ -30,6 +38,12 @@ void LocalHeap::Unpark() {
state_ = ThreadState::Running; state_ = ThreadState::Running;
} }
void LocalHeap::EnsureParkedBeforeDestruction() {
base::MutexGuard guard(&state_mutex_);
state_ = ThreadState::Parked;
state_change_.NotifyAll();
}
void LocalHeap::RequestSafepoint() { void LocalHeap::RequestSafepoint() {
safepoint_requested_.store(true, std::memory_order_relaxed); safepoint_requested_.store(true, std::memory_order_relaxed);
} }
...@@ -40,6 +54,7 @@ bool LocalHeap::IsSafepointRequested() { ...@@ -40,6 +54,7 @@ bool LocalHeap::IsSafepointRequested() {
void LocalHeap::Safepoint() { void LocalHeap::Safepoint() {
if (IsSafepointRequested()) { if (IsSafepointRequested()) {
ClearSafepointRequested();
EnterSafepoint(); EnterSafepoint();
} }
} }
...@@ -48,7 +63,7 @@ void LocalHeap::ClearSafepointRequested() { ...@@ -48,7 +63,7 @@ void LocalHeap::ClearSafepointRequested() {
safepoint_requested_.store(false, std::memory_order_relaxed); safepoint_requested_.store(false, std::memory_order_relaxed);
} }
void LocalHeap::EnterSafepoint() { UNIMPLEMENTED(); } void LocalHeap::EnterSafepoint() { heap_->safepoint()->EnterFromThread(this); }
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -14,6 +14,7 @@ namespace v8 { ...@@ -14,6 +14,7 @@ namespace v8 {
namespace internal { namespace internal {
class Heap; class Heap;
class Safepoint;
class LocalHeap { class LocalHeap {
public: public:
...@@ -26,7 +27,7 @@ class LocalHeap { ...@@ -26,7 +27,7 @@ class LocalHeap {
// Frequently invoked by local thread to check whether safepoint was requested // Frequently invoked by local thread to check whether safepoint was requested
// from the main thread. // from the main thread.
void Safepoint(); V8_EXPORT_PRIVATE void Safepoint();
private: private:
enum class ThreadState { enum class ThreadState {
...@@ -39,21 +40,41 @@ class LocalHeap { ...@@ -39,21 +40,41 @@ class LocalHeap {
Safepoint Safepoint
}; };
void Park(); V8_EXPORT_PRIVATE void Park();
void Unpark(); V8_EXPORT_PRIVATE void Unpark();
void EnsureParkedBeforeDestruction();
bool IsSafepointRequested(); bool IsSafepointRequested();
void ClearSafepointRequested(); void ClearSafepointRequested();
void EnterSafepoint(); void EnterSafepoint();
Heap* heap_; Heap* heap_;
base::Mutex state_mutex_; base::Mutex state_mutex_;
base::ConditionVariable state_condvar_; base::ConditionVariable state_change_;
ThreadState state_; ThreadState state_;
std::atomic<bool> safepoint_requested_; std::atomic<bool> safepoint_requested_;
LocalHeap* prev_; LocalHeap* prev_;
LocalHeap* next_; LocalHeap* next_;
friend class Heap; friend class Heap;
friend class Safepoint;
friend class ParkedScope;
};
class ParkedScope {
public:
explicit ParkedScope(LocalHeap* local_heap) : local_heap_(local_heap) {
local_heap_->Park();
}
~ParkedScope() { local_heap_->Unpark(); }
private:
LocalHeap* local_heap_;
}; };
} // namespace internal } // namespace internal
......
// 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 "src/heap/safepoint.h"
#include "src/heap/heap.h"
#include "src/heap/local-heap.h"
namespace v8 {
namespace internal {
Safepoint::Safepoint(Heap* heap) : heap_(heap) {}
void Safepoint::StopThreads() {
heap_->local_heaps_mutex_.Lock();
barrier_.Arm();
for (LocalHeap* current = heap_->local_heaps_head_; current;
current = current->next_) {
current->RequestSafepoint();
}
for (LocalHeap* current = heap_->local_heaps_head_; current;
current = current->next_) {
current->state_mutex_.Lock();
while (current->state_ == LocalHeap::ThreadState::Running) {
current->state_change_.Wait(&current->state_mutex_);
}
}
}
void Safepoint::ResumeThreads() {
for (LocalHeap* current = heap_->local_heaps_head_; current;
current = current->next_) {
current->state_mutex_.Unlock();
}
barrier_.Disarm();
heap_->local_heaps_mutex_.Unlock();
}
void Safepoint::EnterFromThread(LocalHeap* local_heap) {
{
base::MutexGuard guard(&local_heap->state_mutex_);
local_heap->state_ = LocalHeap::ThreadState::Safepoint;
local_heap->state_change_.NotifyAll();
}
barrier_.Wait();
{
base::MutexGuard guard(&local_heap->state_mutex_);
local_heap->state_ = LocalHeap::ThreadState::Running;
}
}
void Safepoint::Barrier::Arm() {
base::MutexGuard guard(&mutex_);
CHECK(!armed_);
armed_ = true;
}
void Safepoint::Barrier::Disarm() {
base::MutexGuard guard(&mutex_);
CHECK(armed_);
armed_ = false;
cond_.NotifyAll();
}
void Safepoint::Barrier::Wait() {
base::MutexGuard guard(&mutex_);
while (armed_) {
cond_.Wait(&mutex_);
}
}
SafepointScope::SafepointScope(Heap* heap) : safepoint_(heap->safepoint()) {
safepoint_->StopThreads();
}
SafepointScope::~SafepointScope() { safepoint_->ResumeThreads(); }
} // namespace internal
} // namespace v8
// 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_SAFEPOINT_H_
#define V8_HEAP_SAFEPOINT_H_
#include "src/base/platform/condition-variable.h"
#include "src/base/platform/mutex.h"
namespace v8 {
namespace internal {
class Heap;
class LocalHeap;
class Safepoint {
public:
explicit Safepoint(Heap* heap);
// Enter the safepoint from a thread
void EnterFromThread(LocalHeap* local_heap);
private:
class Barrier {
base::Mutex mutex_;
base::ConditionVariable cond_;
bool armed_;
public:
Barrier() : armed_(false) {}
void Arm();
void Disarm();
void Wait();
};
void StopThreads();
void ResumeThreads();
Barrier barrier_;
Heap* heap_;
friend class SafepointScope;
};
class SafepointScope {
public:
V8_EXPORT_PRIVATE explicit SafepointScope(Heap* heap);
V8_EXPORT_PRIVATE ~SafepointScope();
private:
Safepoint* safepoint_;
};
} // namespace internal
} // namespace v8
#endif // V8_HEAP_SAFEPOINT_H_
...@@ -176,6 +176,7 @@ v8_source_set("unittests_sources") { ...@@ -176,6 +176,7 @@ v8_source_set("unittests_sources") {
"heap/memory-reducer-unittest.cc", "heap/memory-reducer-unittest.cc",
"heap/object-stats-unittest.cc", "heap/object-stats-unittest.cc",
"heap/off-thread-factory-unittest.cc", "heap/off-thread-factory-unittest.cc",
"heap/safepoint-unittest.cc",
"heap/slot-set-unittest.cc", "heap/slot-set-unittest.cc",
"heap/spaces-unittest.cc", "heap/spaces-unittest.cc",
"heap/unmapper-unittest.cc", "heap/unmapper-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 "src/heap/safepoint.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/platform.h"
#include "src/heap/heap.h"
#include "src/heap/local-heap.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
using SafepoinTest = TestWithIsolate;
TEST_F(SafepoinTest, ReachSafepointWithoutLocalHeaps) {
Heap* heap = i_isolate()->heap();
bool run = false;
{
SafepointScope scope(heap);
run = true;
}
CHECK(run);
}
class ParkedThread final : public v8::base::Thread {
public:
ParkedThread(Heap* heap, base::Mutex* mutex)
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
heap_(heap),
mutex_(mutex) {}
void Run() override {
LocalHeap local_heap(heap_);
if (mutex_) {
ParkedScope scope(&local_heap);
base::MutexGuard guard(mutex_);
}
}
Heap* heap_;
base::Mutex* mutex_;
};
TEST_F(SafepoinTest, StopParkedThreads) {
Heap* heap = i_isolate()->heap();
int safepoints = 0;
const int kThreads = 10;
const int kRuns = 5;
for (int run = 0; run < kRuns; run++) {
base::Mutex mutex;
std::vector<ParkedThread*> threads;
mutex.Lock();
for (int i = 0; i < kThreads; i++) {
ParkedThread* thread =
new ParkedThread(heap, i % 2 == 0 ? &mutex : nullptr);
CHECK(thread->Start());
threads.push_back(thread);
}
{
SafepointScope scope(heap);
safepoints++;
}
mutex.Unlock();
for (ParkedThread* thread : threads) {
thread->Join();
delete thread;
}
}
CHECK_EQ(safepoints, kRuns);
}
static const int kRuns = 10000;
class RunningThread final : public v8::base::Thread {
public:
RunningThread(Heap* heap, std::atomic<int>* counter)
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
heap_(heap),
counter_(counter) {}
void Run() override {
LocalHeap local_heap(heap_);
for (int i = 0; i < kRuns; i++) {
counter_->fetch_add(1);
if (i % 100 == 0) local_heap.Safepoint();
}
}
Heap* heap_;
std::atomic<int>* counter_;
};
TEST_F(SafepoinTest, StopRunningThreads) {
Heap* heap = i_isolate()->heap();
const int kThreads = 10;
const int kRuns = 5;
const int kSafepoints = 3;
int safepoint_count = 0;
for (int run = 0; run < kRuns; run++) {
std::atomic<int> counter(0);
std::vector<RunningThread*> threads;
for (int i = 0; i < kThreads; i++) {
RunningThread* thread = new RunningThread(heap, &counter);
CHECK(thread->Start());
threads.push_back(thread);
}
for (int i = 0; i < kSafepoints; i++) {
SafepointScope scope(heap);
safepoint_count++;
}
for (RunningThread* thread : threads) {
thread->Join();
delete thread;
}
}
CHECK_EQ(safepoint_count, kRuns * kSafepoints);
}
} // namespace internal
} // namespace v8
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