1dc4277e5b
TEST=ci Change-Id: Ic162deea4a39869a158726616e5c8dc0ff058817 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/475781 Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
443 lines
14 KiB
C++
443 lines
14 KiB
C++
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include "vm/heap/safepoint.h"
|
|
|
|
#include "vm/heap/heap.h"
|
|
#include "vm/thread.h"
|
|
#include "vm/thread_barrier.h"
|
|
#include "vm/thread_registry.h"
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_FLAG(bool, trace_safepoint, false, "Trace Safepoint logic.");
|
|
|
|
SafepointOperationScope::SafepointOperationScope(Thread* T,
|
|
SafepointLevel level)
|
|
: ThreadStackResource(T), level_(level) {
|
|
ASSERT(T != nullptr && T->isolate_group() != nullptr);
|
|
|
|
auto handler = T->isolate_group()->safepoint_handler();
|
|
handler->SafepointThreads(T, level_);
|
|
}
|
|
|
|
SafepointOperationScope::~SafepointOperationScope() {
|
|
Thread* T = thread();
|
|
ASSERT(T != nullptr && T->isolate_group() != nullptr);
|
|
|
|
auto handler = T->isolate_group()->safepoint_handler();
|
|
handler->ResumeThreads(T, level_);
|
|
}
|
|
|
|
SafepointHandler::SafepointHandler(IsolateGroup* isolate_group)
|
|
: isolate_group_(isolate_group), tasks_() {
|
|
handlers_[SafepointLevel::kGC] =
|
|
new LevelHandler(isolate_group, SafepointLevel::kGC);
|
|
handlers_[SafepointLevel::kGCAndDeopt] =
|
|
new LevelHandler(isolate_group, SafepointLevel::kGCAndDeopt);
|
|
handlers_[SafepointLevel::kGCAndDeoptAndReload] =
|
|
new LevelHandler(isolate_group, SafepointLevel::kGCAndDeoptAndReload);
|
|
}
|
|
|
|
SafepointHandler::~SafepointHandler() {
|
|
ASSERT(tasks_.IsEmpty());
|
|
for (intptr_t level = 0; level < SafepointLevel::kNumLevels; ++level) {
|
|
ASSERT(handlers_[level]->owner_ == nullptr);
|
|
delete handlers_[level];
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::SafepointThreads(Thread* T, SafepointLevel level) {
|
|
ASSERT(T->no_safepoint_scope_depth() == 0);
|
|
ASSERT(T->execution_state() == Thread::kThreadInVM);
|
|
ASSERT(T->current_safepoint_level() >= level);
|
|
|
|
MallocGrowableArray<Dart_Port> oob_isolates;
|
|
{
|
|
MonitorLocker tl(threads_lock());
|
|
|
|
// Allow recursive deopt safepoint operation.
|
|
if (handlers_[level]->owner_ == T) {
|
|
// If we own this safepoint level already we have to own the lower levels
|
|
// as well.
|
|
AssertWeOwnLowerLevelSafepoints(T, level);
|
|
|
|
for (intptr_t i = 0; i <= level; ++i) {
|
|
handlers_[i]->operation_count_++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// This level of nesting is not allowed (this thread cannot own lower levels
|
|
// and then later try acquire higher levels).
|
|
AssertWeDoNotOwnLowerLevelSafepoints(T, level);
|
|
|
|
// Mark this thread at safepoint and possibly notify waiting threads.
|
|
{
|
|
MonitorLocker tl(T->thread_lock());
|
|
// We only enter [level] here. That means a higher level that is waiting
|
|
// for us to check-in will not consider us as not parked. This is required
|
|
// since we are not actually parked (we can finish running this method and
|
|
// then caller continues).
|
|
EnterSafepointLocked(T, &tl, level);
|
|
}
|
|
|
|
// Wait until other safepoint operations are done & mark us as owning
|
|
// the safepoint - so no other thread can.
|
|
while (handlers_[level]->SafepointInProgress()) {
|
|
tl.Wait();
|
|
}
|
|
handlers_[level]->SetSafepointInProgress(T);
|
|
|
|
// Ensure a thread is at a safepoint or notify it to get to one.
|
|
handlers_[level]->NotifyThreadsToGetToSafepointLevel(T, &oob_isolates);
|
|
}
|
|
|
|
for (auto main_port : oob_isolates) {
|
|
Isolate::SendInternalLibMessage(main_port, Isolate::kCheckForReload,
|
|
/*capability=*/-1);
|
|
}
|
|
|
|
// Now wait for all threads that are not already at a safepoint to check-in.
|
|
handlers_[level]->WaitUntilThreadsReachedSafepointLevel();
|
|
|
|
// No other mutator is running at this point. We'll set ourselves as owners of
|
|
// all the lower levels as well - since higher levels provide even more
|
|
// guarantees that lower levels (e.g. others being stopped at places where
|
|
// one can deopt also implies one can gc)
|
|
AcquireLowerLevelSafepoints(T, level);
|
|
|
|
// The current thread owns the safepoint, but it will continue to run and as
|
|
// such is not at any "point" that can be considered safe.
|
|
{
|
|
MonitorLocker tl(T->thread_lock());
|
|
ExitSafepointLocked(T, &tl, level);
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::AssertWeOwnLowerLevelSafepoints(Thread* T,
|
|
SafepointLevel level) {
|
|
for (intptr_t lower_level = level - 1; lower_level >= 0; --lower_level) {
|
|
RELEASE_ASSERT(handlers_[lower_level]->owner_ == T);
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::AssertWeDoNotOwnLowerLevelSafepoints(
|
|
Thread* T,
|
|
SafepointLevel level) {
|
|
for (intptr_t lower_level = level - 1; lower_level >= 0; --lower_level) {
|
|
RELEASE_ASSERT(handlers_[lower_level]->owner_ != T);
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::LevelHandler::NotifyThreadsToGetToSafepointLevel(
|
|
Thread* T,
|
|
MallocGrowableArray<Dart_Port>* oob_isolates) {
|
|
ASSERT(num_threads_not_parked_ == 0);
|
|
for (auto current = isolate_group()->thread_registry()->active_list();
|
|
current != nullptr; current = current->next()) {
|
|
MonitorLocker tl(current->thread_lock());
|
|
if (!current->BypassSafepoints() && current != T) {
|
|
const uint32_t state = current->SetSafepointRequested(level_, true);
|
|
if (!Thread::IsAtSafepoint(level_, state)) {
|
|
if (level_ == SafepointLevel::kGCAndDeoptAndReload) {
|
|
// Interrupt the mutator by sending an reload OOB message. The
|
|
// mutator will only check-in once it's handling the reload OOB
|
|
// message.
|
|
//
|
|
// If there's no isolate, it may be a helper thread that has entered
|
|
// via `Thread::EnterIsolateGroupAsHelper()`. In that case we cannot
|
|
// send an OOB message. Instead we'll have to wait until that thread
|
|
// de-schedules itself.
|
|
auto isolate = current->scheduled_dart_mutator_isolate();
|
|
if (isolate != nullptr) {
|
|
oob_isolates->Add(isolate->main_port());
|
|
}
|
|
} else {
|
|
// Interrupt the mutator and ask it to block at any interruption
|
|
// point.
|
|
if (current->IsDartMutatorThread()) {
|
|
current->ScheduleInterrupts(Thread::kVMInterrupt);
|
|
}
|
|
}
|
|
MonitorLocker sl(&parked_lock_);
|
|
num_threads_not_parked_++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::ResumeThreads(Thread* T, SafepointLevel level) {
|
|
{
|
|
MonitorLocker sl(threads_lock());
|
|
|
|
ASSERT(handlers_[level]->SafepointInProgress());
|
|
ASSERT(handlers_[level]->owner_ == T);
|
|
AssertWeOwnLowerLevelSafepoints(T, level);
|
|
|
|
// We allow recursive safepoints.
|
|
if (handlers_[level]->operation_count_ > 1) {
|
|
for (intptr_t i = 0; i <= level; ++i) {
|
|
handlers_[i]->operation_count_--;
|
|
}
|
|
return;
|
|
}
|
|
|
|
ReleaseLowerLevelSafepoints(T, level);
|
|
handlers_[level]->ResetSafepointInProgress(T);
|
|
handlers_[level]->NotifyThreadsToContinue(T);
|
|
sl.NotifyAll();
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::LevelHandler::WaitUntilThreadsReachedSafepointLevel() {
|
|
MonitorLocker sl(&parked_lock_);
|
|
intptr_t num_attempts = 0;
|
|
while (num_threads_not_parked_ > 0) {
|
|
Monitor::WaitResult retval = sl.Wait(1000);
|
|
if (retval == Monitor::kTimedOut) {
|
|
num_attempts += 1;
|
|
if (FLAG_trace_safepoint && num_attempts > 10) {
|
|
for (auto current = isolate_group()->thread_registry()->active_list();
|
|
current != nullptr; current = current->next()) {
|
|
if (!current->IsAtSafepoint(level_)) {
|
|
OS::PrintErr("Attempt:%" Pd " waiting for thread %s to check in\n",
|
|
num_attempts, current->os_thread()->name());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::AcquireLowerLevelSafepoints(Thread* T,
|
|
SafepointLevel level) {
|
|
MonitorLocker tl(threads_lock());
|
|
ASSERT(handlers_[level]->owner_ == T);
|
|
for (intptr_t lower_level = level - 1; lower_level >= 0; --lower_level) {
|
|
ASSERT(!handlers_[lower_level]->SafepointInProgress());
|
|
handlers_[lower_level]->SetSafepointInProgress(T);
|
|
ASSERT(handlers_[lower_level]->owner_ == T);
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::ReleaseLowerLevelSafepoints(Thread* T,
|
|
SafepointLevel level) {
|
|
for (intptr_t lower_level = 0; lower_level < level; ++lower_level) {
|
|
handlers_[lower_level]->ResetSafepointInProgress(T);
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::LevelHandler::NotifyThreadsToContinue(Thread* T) {
|
|
for (auto current = isolate_group()->thread_registry()->active_list();
|
|
current != nullptr; current = current->next()) {
|
|
MonitorLocker tl(current->thread_lock());
|
|
if (!current->BypassSafepoints() && current != T) {
|
|
bool resume = false;
|
|
for (intptr_t lower_level = level_; lower_level >= 0; --lower_level) {
|
|
if (Thread::IsBlockedForSafepoint(current->SetSafepointRequested(
|
|
static_cast<SafepointLevel>(lower_level), false))) {
|
|
resume = true;
|
|
}
|
|
}
|
|
if (resume) {
|
|
tl.Notify();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::EnterSafepointUsingLock(Thread* T) {
|
|
MonitorLocker tl(T->thread_lock());
|
|
EnterSafepointLocked(T, &tl, T->current_safepoint_level());
|
|
}
|
|
|
|
void SafepointHandler::ExitSafepointUsingLock(Thread* T) {
|
|
MonitorLocker tl(T->thread_lock());
|
|
ASSERT(T->IsAtSafepoint());
|
|
ExitSafepointLocked(T, &tl, T->current_safepoint_level());
|
|
ASSERT(!T->IsSafepointRequestedLocked(T->current_safepoint_level()));
|
|
}
|
|
|
|
SafepointLevel SafepointHandler::InnermostSafepointOperation(
|
|
const Thread* current_thread) const {
|
|
// The [current_thread] may not own the active safepoint.
|
|
intptr_t last_count = -1;
|
|
SafepointLevel last_level = SafepointLevel::kNoSafepoint;
|
|
|
|
// Notice: We access SafepointLevel::{owner_,operation_count_} fields
|
|
// without lock. This is ok since:
|
|
//
|
|
// * If the current thread is the owner, then it will be the one that has
|
|
// last written `Thread::Current()` to the `owner_` field (as well as
|
|
// updated the `operation_count_`) - nobody else can update those fields
|
|
// in the meantime. Once the current thread exits we set it to `nullptr`.
|
|
//
|
|
// * If there's no owner or another thread is the owner the value cannot be
|
|
// `Thread::Current()`: only our thread can write that particular value.
|
|
//
|
|
// => Even if there's racy writes by another thread, the logic is still
|
|
// safe.
|
|
//
|
|
for (intptr_t level = 0; level < SafepointLevel::kNumLevels; ++level) {
|
|
if (handlers_[level]->owner_ == current_thread) {
|
|
const intptr_t count = handlers_[level]->operation_count_;
|
|
if (count < last_count) return last_level;
|
|
last_count = count;
|
|
last_level = static_cast<SafepointLevel>(level);
|
|
} else {
|
|
return last_level;
|
|
}
|
|
}
|
|
return last_level;
|
|
}
|
|
|
|
void SafepointHandler::BlockForSafepoint(Thread* T) {
|
|
ASSERT(!T->BypassSafepoints());
|
|
MonitorLocker tl(T->thread_lock());
|
|
// This takes into account the safepoint level the thread can participate in.
|
|
const SafepointLevel level = T->current_safepoint_level();
|
|
if (T->IsSafepointRequestedLocked(level)) {
|
|
EnterSafepointLocked(T, &tl, level);
|
|
ExitSafepointLocked(T, &tl, level);
|
|
ASSERT(!T->IsSafepointRequestedLocked(level));
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::EnterSafepointLocked(Thread* T,
|
|
MonitorLocker* tl,
|
|
SafepointLevel level) {
|
|
T->SetAtSafepoint(true, level);
|
|
// Several safepointing operations (at different) levels may happen at same
|
|
// time. Ensure we notify all of them that we are parked now.
|
|
for (intptr_t i = 0; i <= level; ++i) {
|
|
if (T->IsSafepointLevelRequestedLocked(static_cast<SafepointLevel>(i))) {
|
|
handlers_[i]->NotifyWeAreParked(T);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::LevelHandler::NotifyWeAreParked(Thread* T) {
|
|
ASSERT(owner_ != nullptr);
|
|
MonitorLocker sl(&parked_lock_);
|
|
ASSERT(num_threads_not_parked_ > 0);
|
|
num_threads_not_parked_ -= 1;
|
|
if (num_threads_not_parked_ == 0) {
|
|
sl.Notify();
|
|
}
|
|
}
|
|
|
|
void SafepointHandler::ExitSafepointLocked(Thread* T,
|
|
MonitorLocker* tl,
|
|
SafepointLevel level) {
|
|
ASSERT(T == Thread::Current());
|
|
while (T->IsSafepointRequestedLocked(level)) {
|
|
T->SetBlockedForSafepoint(true);
|
|
tl->Wait();
|
|
T->SetBlockedForSafepoint(false);
|
|
|
|
MonitorLeaveScope mls(tl);
|
|
SafepointTask* task = nullptr;
|
|
{
|
|
MonitorLocker ml(threads_lock());
|
|
if (!tasks_.IsEmpty()) {
|
|
task = tasks_.RemoveFirst();
|
|
}
|
|
}
|
|
if (task != nullptr) {
|
|
Thread::ExecutionState execution_state = T->execution_state();
|
|
T->set_execution_state(Thread::kThreadInVM);
|
|
task->RunBlockedAtSafepoint();
|
|
delete task;
|
|
T->set_execution_state(execution_state);
|
|
}
|
|
}
|
|
T->SetAtSafepoint(false, level);
|
|
}
|
|
|
|
void SafepointHandler::RunTasks(IntrusiveDList<SafepointTask>* tasks) {
|
|
ASSERT(Thread::Current()->OwnsSafepoint());
|
|
|
|
// Withold one task for the main thread.
|
|
ASSERT(!tasks->IsEmpty());
|
|
SafepointTask* main = tasks->RemoveFirst();
|
|
|
|
// First use threads blocked at this safepoint.
|
|
{
|
|
MonitorLocker tl(threads_lock());
|
|
ASSERT(tasks_.IsEmpty());
|
|
for (auto current = isolate_group()->thread_registry()->active_list();
|
|
current != nullptr; current = current->next()) {
|
|
if (tasks->IsEmpty()) break;
|
|
|
|
MonitorLocker tl(current->thread_lock());
|
|
if (current->IsBlockedForSafepoint()) {
|
|
tasks_.Append(tasks->RemoveFirst());
|
|
tl.Notify();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then use thread pool workers.
|
|
while (!tasks->IsEmpty()) {
|
|
bool result = Dart::thread_pool()->Run(tasks->RemoveFirst());
|
|
ASSERT(result);
|
|
}
|
|
|
|
// Run one task on the main thread.
|
|
main->RunMain();
|
|
delete main;
|
|
|
|
// Clean up any tasks that took too long to start.
|
|
{
|
|
MonitorLocker tl(threads_lock());
|
|
while (!tasks_.IsEmpty()) {
|
|
delete tasks_.RemoveFirst();
|
|
}
|
|
}
|
|
}
|
|
|
|
SafepointTask::~SafepointTask() {
|
|
barrier_->Release();
|
|
}
|
|
|
|
void SafepointTask::Run() {
|
|
if (!barrier_->TryEnter()) {
|
|
return;
|
|
}
|
|
|
|
Thread::EnterIsolateGroupAsHelper(isolate_group_, kind_,
|
|
/*bypass_safepoint=*/true);
|
|
|
|
RunEnteredIsolateGroup();
|
|
|
|
Thread::ExitIsolateGroupAsHelper(/*bypass_safepoint=*/true);
|
|
|
|
barrier_->Sync();
|
|
}
|
|
|
|
void SafepointTask::RunBlockedAtSafepoint() {
|
|
if (!barrier_->TryEnter()) {
|
|
return;
|
|
}
|
|
|
|
Thread* thread = Thread::Current();
|
|
Thread::TaskKind saved_task_kind = thread->task_kind();
|
|
thread->set_task_kind(kind_);
|
|
|
|
RunEnteredIsolateGroup();
|
|
|
|
thread->set_task_kind(saved_task_kind);
|
|
|
|
barrier_->Sync();
|
|
}
|
|
|
|
void SafepointTask::RunMain() {
|
|
RunEnteredIsolateGroup();
|
|
|
|
barrier_->Sync();
|
|
}
|
|
|
|
} // namespace dart
|