f1aa58931d
The per Thread instance is sufficient. TEST=ci Change-Id: Iff2c4279937637089f7ae4194cb1097b5e6eef67 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452881 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Alexander Aprelev <aam@google.com>
866 lines
27 KiB
C++
866 lines
27 KiB
C++
// Copyright (c) 2021, 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 <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "platform/assert.h"
|
|
|
|
#include "vm/heap/safepoint.h"
|
|
#include "vm/isolate.h"
|
|
#include "vm/isolate_reload.h"
|
|
#include "vm/lockers.h"
|
|
#include "vm/message_handler.h"
|
|
#include "vm/message_snapshot.h"
|
|
#include "vm/random.h"
|
|
#include "vm/thread_pool.h"
|
|
#include "vm/unit_test.h"
|
|
|
|
namespace dart {
|
|
|
|
class StateMachineTask : public ThreadPool::Task {
|
|
public:
|
|
enum State {
|
|
kInitialized = 0,
|
|
kEntered,
|
|
kPleaseExit,
|
|
kExited,
|
|
kNext,
|
|
};
|
|
struct Data {
|
|
explicit Data(IsolateGroup* isolate_group)
|
|
: isolate_group_(isolate_group) {}
|
|
|
|
void WaitUntil(intptr_t target_state) {
|
|
MonitorLocker ml(&monitor_);
|
|
while (state != target_state) {
|
|
ml.Wait();
|
|
}
|
|
}
|
|
void MarkAndNotify(intptr_t target_state) {
|
|
MonitorLocker ml(&monitor_);
|
|
state = target_state;
|
|
ml.Notify();
|
|
}
|
|
void AssertIsIn(intptr_t expected_state) {
|
|
MonitorLocker ml(&monitor_);
|
|
EXPECT_EQ(expected_state, state);
|
|
}
|
|
void AssertIsNotIn(intptr_t expected_state) {
|
|
MonitorLocker ml(&monitor_);
|
|
EXPECT_NE(expected_state, state);
|
|
}
|
|
bool IsIn(intptr_t expected_state) {
|
|
MonitorLocker ml(&monitor_);
|
|
return expected_state == state;
|
|
}
|
|
|
|
intptr_t state = kInitialized;
|
|
IsolateGroup* isolate_group_;
|
|
|
|
private:
|
|
Monitor monitor_;
|
|
};
|
|
|
|
explicit StateMachineTask(std::shared_ptr<Data> data)
|
|
: data_(std::move(data)) {}
|
|
|
|
virtual void Run() {
|
|
const bool kBypassSafepoint = false;
|
|
Thread::EnterIsolateGroupAsHelper(data_->isolate_group_,
|
|
Thread::kUnknownTask, kBypassSafepoint);
|
|
thread_ = Thread::Current();
|
|
data_->MarkAndNotify(kEntered);
|
|
RunInternal();
|
|
data_->WaitUntil(kPleaseExit);
|
|
Thread::ExitIsolateGroupAsHelper(kBypassSafepoint);
|
|
thread_ = nullptr;
|
|
data_->MarkAndNotify(kExited);
|
|
}
|
|
|
|
protected:
|
|
virtual void RunInternal() = 0;
|
|
|
|
std::shared_ptr<Data> data_;
|
|
Thread* thread_ = nullptr;
|
|
};
|
|
|
|
class DeoptTask : public StateMachineTask {
|
|
public:
|
|
enum State {
|
|
kStartDeoptOperation = StateMachineTask::kNext,
|
|
kFinishedDeoptOperation,
|
|
};
|
|
|
|
explicit DeoptTask(std::shared_ptr<Data> data)
|
|
: StateMachineTask(std::move(data)) {}
|
|
|
|
protected:
|
|
virtual void RunInternal() {
|
|
data_->WaitUntil(kStartDeoptOperation);
|
|
{
|
|
DeoptSafepointOperationScope safepoint_operation(thread_);
|
|
}
|
|
data_->MarkAndNotify(kFinishedDeoptOperation);
|
|
}
|
|
};
|
|
|
|
class GcWithoutDeoptTask : public StateMachineTask {
|
|
public:
|
|
enum State {
|
|
kStartSafepointOperation = StateMachineTask::kNext,
|
|
kEndSafepointOperation,
|
|
kJoinDeoptOperation,
|
|
kDeoptOperationDone,
|
|
};
|
|
|
|
explicit GcWithoutDeoptTask(std::shared_ptr<Data> data)
|
|
: StateMachineTask(std::move(data)) {}
|
|
|
|
protected:
|
|
virtual void RunInternal() {
|
|
data_->WaitUntil(kStartSafepointOperation);
|
|
{
|
|
RuntimeCallDeoptScope no_deopt(thread_,
|
|
RuntimeCallDeoptAbility::kCannotLazyDeopt);
|
|
GcSafepointOperationScope safepoint_operation(thread_);
|
|
}
|
|
data_->MarkAndNotify(kEndSafepointOperation);
|
|
|
|
data_->WaitUntil(kJoinDeoptOperation);
|
|
EXPECT(thread_->IsSafepointRequested());
|
|
thread_->BlockForSafepoint();
|
|
data_->MarkAndNotify(kDeoptOperationDone);
|
|
}
|
|
};
|
|
|
|
// This test ensures that while a "deopt safepoint operation" is about to start
|
|
// but is still waiting for some threads to hit a "deopt safepoint" another
|
|
// safepoint operation can successfully start and finish.
|
|
ISOLATE_UNIT_TEST_CASE(
|
|
SafepointOperation_SafepointOpWhileDeoptSafepointOpBlocked) {
|
|
auto isolate_group = thread->isolate_group();
|
|
|
|
std::shared_ptr<DeoptTask::Data> deopt(new DeoptTask::Data(isolate_group));
|
|
std::shared_ptr<GcWithoutDeoptTask::Data> gc(
|
|
new GcWithoutDeoptTask::Data(isolate_group));
|
|
|
|
thread->EnterSafepoint();
|
|
{
|
|
// Will join outstanding threads on destruction.
|
|
ThreadPool pool;
|
|
|
|
pool.Run<DeoptTask>(deopt);
|
|
pool.Run<GcWithoutDeoptTask>(gc);
|
|
|
|
// Wait until both threads entered the isolate group.
|
|
deopt->WaitUntil(DeoptTask::kEntered);
|
|
gc->WaitUntil(GcWithoutDeoptTask::kEntered);
|
|
|
|
// Let deopt task start deopt operation scope (it will block in
|
|
// [SafepointOperationScope] constructor until all threads have checked-in).
|
|
deopt->MarkAndNotify(DeoptTask::kStartDeoptOperation);
|
|
OS::Sleep(200); // Give it time to actually start the deopt operation
|
|
|
|
// Now let the other thread do a full safepoint operation and wait until
|
|
// it's done: We want to ensure that we can do normal safepoint operations
|
|
// while a deopt operation is being started and is waiting for all mutators
|
|
// to reach an appropriate place where they can be deopted.
|
|
gc->MarkAndNotify(GcWithoutDeoptTask::kStartSafepointOperation);
|
|
gc->WaitUntil(GcWithoutDeoptTask::kEndSafepointOperation);
|
|
|
|
// We were successfully doing a safepoint operation, now let's ensure the
|
|
// first thread is still stuck in the starting of deopt operation.
|
|
deopt->AssertIsIn(DeoptTask::kStartDeoptOperation);
|
|
|
|
// Now we'll let the other thread check-in and ensure the deopt operation
|
|
// proceeded and finished.
|
|
gc->MarkAndNotify(GcWithoutDeoptTask::kJoinDeoptOperation);
|
|
gc->WaitUntil(GcWithoutDeoptTask::kDeoptOperationDone);
|
|
deopt->WaitUntil(DeoptTask::kFinishedDeoptOperation);
|
|
|
|
// Make both threads exit the isolate group.
|
|
deopt->MarkAndNotify(DeoptTask::kPleaseExit);
|
|
gc->MarkAndNotify(GcWithoutDeoptTask::kPleaseExit);
|
|
|
|
deopt->WaitUntil(DeoptTask::kExited);
|
|
gc->WaitUntil(GcWithoutDeoptTask::kExited);
|
|
}
|
|
thread->ExitSafepoint();
|
|
}
|
|
|
|
class LongDeoptTask : public StateMachineTask {
|
|
public:
|
|
enum State {
|
|
kStartDeoptOperation = StateMachineTask::kNext,
|
|
kInsideDeoptOperation,
|
|
kFinishDeoptOperation,
|
|
kFinishedDeoptOperation,
|
|
};
|
|
|
|
explicit LongDeoptTask(std::shared_ptr<Data> data)
|
|
: StateMachineTask(std::move(data)) {}
|
|
|
|
protected:
|
|
virtual void RunInternal() {
|
|
data_->WaitUntil(kStartDeoptOperation);
|
|
{
|
|
DeoptSafepointOperationScope safepoint_operation(thread_);
|
|
data_->MarkAndNotify(kInsideDeoptOperation);
|
|
data_->WaitUntil(kFinishDeoptOperation);
|
|
}
|
|
data_->MarkAndNotify(kFinishedDeoptOperation);
|
|
}
|
|
};
|
|
|
|
class WaiterTask : public StateMachineTask {
|
|
public:
|
|
enum State {
|
|
kEnterSafepoint = StateMachineTask::kNext,
|
|
kInsideSafepoint,
|
|
kPleaseExitSafepoint,
|
|
kExitedSafepoint,
|
|
};
|
|
|
|
explicit WaiterTask(std::shared_ptr<Data> data)
|
|
: StateMachineTask(std::move(data)) {}
|
|
|
|
protected:
|
|
virtual void RunInternal() {
|
|
data_->WaitUntil(kEnterSafepoint);
|
|
thread_->EnterSafepoint();
|
|
data_->MarkAndNotify(kInsideSafepoint);
|
|
data_->WaitUntil(kPleaseExitSafepoint);
|
|
thread_->ExitSafepoint();
|
|
data_->MarkAndNotify(kExitedSafepoint);
|
|
}
|
|
};
|
|
|
|
// This test ensures that while a "deopt safepoint operation" is in-progress
|
|
// other threads cannot perform a normal "safepoint operation".
|
|
ISOLATE_UNIT_TEST_CASE(
|
|
SafepointOperation_SafepointOpBlockedWhileDeoptSafepointOp) {
|
|
auto isolate_group = thread->isolate_group();
|
|
|
|
std::shared_ptr<LongDeoptTask::Data> deopt(
|
|
new LongDeoptTask::Data(isolate_group));
|
|
std::shared_ptr<WaiterTask::Data> gc(new WaiterTask::Data(isolate_group));
|
|
|
|
thread->EnterSafepoint();
|
|
{
|
|
// Will join outstanding threads on destruction.
|
|
ThreadPool pool;
|
|
|
|
pool.Run<LongDeoptTask>(deopt);
|
|
pool.Run<WaiterTask>(gc);
|
|
|
|
// Wait until both threads entered the isolate group.
|
|
deopt->WaitUntil(LongDeoptTask::kEntered);
|
|
gc->WaitUntil(WaiterTask::kEntered);
|
|
|
|
// Let gc task enter safepoint.
|
|
gc->MarkAndNotify(WaiterTask::kEnterSafepoint);
|
|
gc->WaitUntil(WaiterTask::kInsideSafepoint);
|
|
|
|
// Now let the "deopt operation" run and block.
|
|
deopt->MarkAndNotify(LongDeoptTask::kStartDeoptOperation);
|
|
deopt->WaitUntil(LongDeoptTask::kInsideDeoptOperation);
|
|
|
|
// Now let the gc task try to exit safepoint and do it's own safepoint
|
|
// operation: We expect it to block on exiting safepoint, since the deopt
|
|
// operation is still ongoing.
|
|
gc->MarkAndNotify(WaiterTask::kPleaseExitSafepoint);
|
|
OS::Sleep(200);
|
|
gc->AssertIsNotIn(WaiterTask::kExitedSafepoint);
|
|
|
|
// Now let's finish the deopt operation & ensure the waiter thread made
|
|
// progress.
|
|
deopt->MarkAndNotify(LongDeoptTask::kFinishDeoptOperation);
|
|
gc->WaitUntil(WaiterTask::kExitedSafepoint);
|
|
deopt->WaitUntil(LongDeoptTask::kFinishedDeoptOperation);
|
|
|
|
// Make both threads exit the isolate group.
|
|
deopt->MarkAndNotify(LongDeoptTask::kPleaseExit);
|
|
gc->MarkAndNotify(WaiterTask::kPleaseExit);
|
|
|
|
deopt->WaitUntil(LongDeoptTask::kExited);
|
|
gc->WaitUntil(WaiterTask::kExited);
|
|
}
|
|
thread->ExitSafepoint();
|
|
}
|
|
|
|
class CheckinTask : public StateMachineTask {
|
|
public:
|
|
enum State {
|
|
kStartLoop = StateMachineTask::kNext,
|
|
};
|
|
|
|
struct Data : public StateMachineTask::Data {
|
|
Data(IsolateGroup* isolate_group,
|
|
SafepointLevel level,
|
|
std::atomic<intptr_t>* gc_only_checkins,
|
|
std::atomic<intptr_t>* deopt_checkin,
|
|
std::atomic<intptr_t>* reload_checkin,
|
|
std::atomic<intptr_t>* timeout_checkin)
|
|
: StateMachineTask::Data(isolate_group),
|
|
level(level),
|
|
gc_only_checkins(gc_only_checkins),
|
|
deopt_checkin(deopt_checkin),
|
|
reload_checkin(reload_checkin),
|
|
timeout_checkin(timeout_checkin) {}
|
|
|
|
SafepointLevel level;
|
|
std::atomic<intptr_t>* gc_only_checkins;
|
|
std::atomic<intptr_t>* deopt_checkin;
|
|
std::atomic<intptr_t>* reload_checkin;
|
|
std::atomic<intptr_t>* timeout_checkin;
|
|
};
|
|
|
|
explicit CheckinTask(std::shared_ptr<Data> data) : StateMachineTask(data) {}
|
|
|
|
protected:
|
|
Data* data() { return reinterpret_cast<Data*>(data_.get()); }
|
|
|
|
virtual void RunInternal() {
|
|
data_->WaitUntil(kStartLoop);
|
|
|
|
uword last_sync = OS::GetCurrentMonotonicMicros();
|
|
while (!data()->IsIn(kPleaseExit)) {
|
|
OS::SleepMicros(100); // Make test avoid consuming 100% CPU x kTaskCount.
|
|
switch (data()->level) {
|
|
case SafepointLevel::kGC: {
|
|
// This thread should join only GC safepoint operations.
|
|
RuntimeCallDeoptScope no_deopt(
|
|
Thread::Current(), RuntimeCallDeoptAbility::kCannotLazyDeopt);
|
|
if (SafepointIfRequested(thread_, data()->gc_only_checkins)) {
|
|
last_sync = OS::GetCurrentMonotonicMicros();
|
|
}
|
|
break;
|
|
}
|
|
case SafepointLevel::kGCAndDeopt: {
|
|
// This thread should join only GC and Deopt safepoint operations.
|
|
if (SafepointIfRequested(thread_, data()->deopt_checkin)) {
|
|
last_sync = OS::GetCurrentMonotonicMicros();
|
|
}
|
|
break;
|
|
}
|
|
case SafepointLevel::kGCAndDeoptAndReload: {
|
|
// This thread should join any safepoint operations.
|
|
ReloadParticipationScope allow_reload(thread_);
|
|
if (SafepointIfRequested(thread_, data()->reload_checkin)) {
|
|
last_sync = OS::GetCurrentMonotonicMicros();
|
|
}
|
|
break;
|
|
}
|
|
case SafepointLevel::kNumLevels:
|
|
case SafepointLevel::kNoSafepoint:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
// If the main thread asks us to join a deopt safepoint but we are
|
|
// instructed to only really collaborate with GC safepoints we won't
|
|
// participate in the above cases (and therefore not register our
|
|
// check-in by increasing the checkin counts).
|
|
//
|
|
// After being quite sure to not have joined deopt safepoint if we only
|
|
// support GC safepoints, we will eventually comply here to make main
|
|
// thread continue.
|
|
const auto now = OS::GetCurrentMonotonicMicros();
|
|
if ((now - last_sync) > 1000000) {
|
|
ReloadParticipationScope allow_reload(thread_);
|
|
if (SafepointIfRequested(thread_, data()->timeout_checkin)) {
|
|
last_sync = now;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SafepointIfRequested(Thread* thread, std::atomic<intptr_t>* checkins) {
|
|
if (thread->IsSafepointRequested()) {
|
|
// Collaborates by checking into the safepoint.
|
|
thread->BlockForSafepoint();
|
|
(*checkins)++;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// Test that mutators will not check-in to "deopt safepoint operations" at
|
|
// at places where the mutator cannot depot (which is indicated by the
|
|
// [Thread::runtime_call_deopt_ability_] value).
|
|
#if !defined(PRODUCT)
|
|
ISOLATE_UNIT_TEST_CASE(SafepointOperation_SafepointPointTest) {
|
|
auto isolate_group = thread->isolate_group();
|
|
|
|
const intptr_t kTaskCount = 6;
|
|
std::atomic<intptr_t> gc_only_checkins[kTaskCount];
|
|
std::atomic<intptr_t> deopt_checkins[kTaskCount];
|
|
std::atomic<intptr_t> reload_checkins[kTaskCount];
|
|
std::atomic<intptr_t> timeout_checkins[kTaskCount];
|
|
for (intptr_t i = 0; i < kTaskCount; ++i) {
|
|
gc_only_checkins[i] = 0;
|
|
deopt_checkins[i] = 0;
|
|
reload_checkins[i] = 0;
|
|
timeout_checkins[i] = 0;
|
|
}
|
|
|
|
auto task_to_level = [](intptr_t task_id) -> SafepointLevel {
|
|
switch (task_id) {
|
|
case 0:
|
|
case 1:
|
|
return SafepointLevel::kGC;
|
|
case 2:
|
|
case 3:
|
|
return SafepointLevel::kGCAndDeopt;
|
|
case 4:
|
|
case 5:
|
|
return SafepointLevel::kGCAndDeoptAndReload;
|
|
default:
|
|
UNREACHABLE();
|
|
return SafepointLevel::kGC;
|
|
}
|
|
};
|
|
auto wait_for_sync = [&](intptr_t syncs) {
|
|
while (true) {
|
|
bool ready = true;
|
|
for (intptr_t i = 0; i < kTaskCount; ++i) {
|
|
const intptr_t all = gc_only_checkins[i] + deopt_checkins[i] +
|
|
reload_checkins[i] + timeout_checkins[i];
|
|
if (all != syncs) {
|
|
ready = false;
|
|
break;
|
|
}
|
|
}
|
|
if (ready) {
|
|
return;
|
|
}
|
|
OS::SleepMicros(1000);
|
|
}
|
|
};
|
|
|
|
std::vector<std::shared_ptr<CheckinTask::Data>> threads;
|
|
for (intptr_t i = 0; i < kTaskCount; ++i) {
|
|
const auto level = task_to_level(i);
|
|
std::unique_ptr<CheckinTask::Data> data(new CheckinTask::Data(
|
|
isolate_group, level, &gc_only_checkins[i], &deopt_checkins[i],
|
|
&reload_checkins[i], &timeout_checkins[i]));
|
|
threads.push_back(std::move(data));
|
|
}
|
|
|
|
{
|
|
// Will join outstanding threads on destruction.
|
|
ThreadPool pool;
|
|
|
|
for (intptr_t i = 0; i < kTaskCount; i++) {
|
|
pool.Run<CheckinTask>(threads[i]);
|
|
}
|
|
for (intptr_t i = 0; i < kTaskCount; i++) {
|
|
threads[i]->WaitUntil(CheckinTask::kEntered);
|
|
}
|
|
for (intptr_t i = 0; i < kTaskCount; i++) {
|
|
threads[i]->MarkAndNotify(CheckinTask::kStartLoop);
|
|
}
|
|
{
|
|
{
|
|
GcSafepointOperationScope safepoint_operation(thread);
|
|
}
|
|
wait_for_sync(1); // Wait for threads to exit safepoint
|
|
{
|
|
DeoptSafepointOperationScope safepoint_operation(thread);
|
|
}
|
|
wait_for_sync(2); // Wait for threads to exit safepoint
|
|
{
|
|
RELOAD_OPERATION_SCOPE(thread);
|
|
}
|
|
wait_for_sync(3); // Wait for threads to exit safepoint
|
|
{
|
|
GcSafepointOperationScope safepoint_operation(thread);
|
|
}
|
|
wait_for_sync(4); // Wait for threads to exit safepoint
|
|
{
|
|
DeoptSafepointOperationScope safepoint_operation(thread);
|
|
}
|
|
wait_for_sync(5); // Wait for threads to exit safepoint
|
|
{
|
|
RELOAD_OPERATION_SCOPE(thread);
|
|
}
|
|
}
|
|
for (intptr_t i = 0; i < kTaskCount; i++) {
|
|
threads[i]->MarkAndNotify(CheckinTask::kPleaseExit);
|
|
}
|
|
for (intptr_t i = 0; i < kTaskCount; i++) {
|
|
threads[i]->WaitUntil(CheckinTask::kExited);
|
|
}
|
|
// Unlucky scheduling may result in more timeout checkins than intended, but
|
|
// never checkins of otherwise the wrong type.
|
|
for (intptr_t i = 0; i < kTaskCount; ++i) {
|
|
switch (task_to_level(i)) {
|
|
case SafepointLevel::kGC:
|
|
EXPECT_LE(gc_only_checkins[i], 2);
|
|
EXPECT_EQ(deopt_checkins[i], 0);
|
|
EXPECT_EQ(reload_checkins[i], 0);
|
|
EXPECT_GE(timeout_checkins[i], 4);
|
|
break;
|
|
case SafepointLevel::kGCAndDeopt:
|
|
EXPECT_EQ(gc_only_checkins[i], 0);
|
|
EXPECT_LE(deopt_checkins[i], 4);
|
|
EXPECT_EQ(reload_checkins[i], 0);
|
|
EXPECT_GE(timeout_checkins[i], 2);
|
|
break;
|
|
case SafepointLevel::kGCAndDeoptAndReload:
|
|
EXPECT_EQ(gc_only_checkins[i], 0);
|
|
EXPECT_EQ(deopt_checkins[i], 0);
|
|
EXPECT_LE(reload_checkins[i], 6);
|
|
EXPECT_GE(timeout_checkins[i], 0);
|
|
break;
|
|
case SafepointLevel::kNumLevels:
|
|
case SafepointLevel::kNoSafepoint:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
|
|
class StressTask : public StateMachineTask {
|
|
public:
|
|
enum State {
|
|
kStart = StateMachineTask::kNext,
|
|
};
|
|
|
|
explicit StressTask(std::shared_ptr<Data> data) : StateMachineTask(data) {}
|
|
|
|
protected:
|
|
Data* data() { return reinterpret_cast<Data*>(data_.get()); }
|
|
|
|
virtual void RunInternal() {
|
|
data_->WaitUntil(kStart);
|
|
|
|
Random random(thread_->random()->NextUInt64());
|
|
while (!data()->IsIn(kPleaseExit)) {
|
|
const auto us = random.NextUInt32() % 3;
|
|
switch (random.NextUInt32() % 5) {
|
|
case 0: {
|
|
DeoptSafepointOperationScope safepoint_op(thread_);
|
|
OS::SleepMicros(us);
|
|
break;
|
|
}
|
|
case 1: {
|
|
GcSafepointOperationScope safepoint_op(thread_);
|
|
OS::SleepMicros(us);
|
|
break;
|
|
}
|
|
case 2: {
|
|
const bool kBypassSafepoint = false;
|
|
Thread::ExitIsolateGroupAsHelper(kBypassSafepoint);
|
|
OS::SleepMicros(us);
|
|
Thread::EnterIsolateGroupAsHelper(
|
|
data_->isolate_group_, Thread::kUnknownTask, kBypassSafepoint);
|
|
thread_ = Thread::Current();
|
|
break;
|
|
}
|
|
case 3: {
|
|
thread_->EnterSafepoint();
|
|
OS::SleepMicros(us);
|
|
thread_->ExitSafepoint();
|
|
break;
|
|
}
|
|
case 4: {
|
|
if (thread_->IsSafepointRequested()) {
|
|
thread_->BlockForSafepoint();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
ISOLATE_UNIT_TEST_CASE(SafepointOperation_StressTest) {
|
|
auto isolate_group = thread->isolate_group();
|
|
|
|
const intptr_t kTaskCount = 5;
|
|
|
|
std::vector<std::shared_ptr<StressTask::Data>> threads;
|
|
for (intptr_t i = 0; i < kTaskCount; ++i) {
|
|
std::unique_ptr<StressTask::Data> data(new StressTask::Data(isolate_group));
|
|
threads.push_back(std::move(data));
|
|
}
|
|
|
|
thread->EnterSafepoint();
|
|
{
|
|
// Will join outstanding threads on destruction.
|
|
ThreadPool pool;
|
|
|
|
for (intptr_t i = 0; i < kTaskCount; i++) {
|
|
pool.Run<StressTask>(threads[i]);
|
|
}
|
|
for (intptr_t i = 0; i < kTaskCount; i++) {
|
|
threads[i]->WaitUntil(StressTask::kEntered);
|
|
}
|
|
for (intptr_t i = 0; i < kTaskCount; i++) {
|
|
threads[i]->MarkAndNotify(StressTask::kStart);
|
|
}
|
|
OS::Sleep(3 * 1000);
|
|
for (intptr_t i = 0; i < kTaskCount; i++) {
|
|
threads[i]->MarkAndNotify(StressTask::kPleaseExit);
|
|
}
|
|
for (intptr_t i = 0; i < kTaskCount; i++) {
|
|
threads[i]->WaitUntil(StressTask::kExited);
|
|
}
|
|
}
|
|
thread->ExitSafepoint();
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(SafepointOperation_DeoptAndNonDeoptNesting) {
|
|
auto safepoint_handler = thread->isolate_group()->safepoint_handler();
|
|
{
|
|
DeoptSafepointOperationScope safepoint_scope(thread);
|
|
EXPECT(safepoint_handler->InnermostSafepointOperation(thread) ==
|
|
SafepointLevel::kGCAndDeopt);
|
|
DeoptSafepointOperationScope safepoint_scope2(thread);
|
|
EXPECT(safepoint_handler->InnermostSafepointOperation(thread) ==
|
|
SafepointLevel::kGCAndDeopt);
|
|
GcSafepointOperationScope safepoint_scope3(thread);
|
|
EXPECT(safepoint_handler->InnermostSafepointOperation(thread) ==
|
|
SafepointLevel::kGC);
|
|
GcSafepointOperationScope safepoint_scope4(thread);
|
|
EXPECT(safepoint_handler->InnermostSafepointOperation(thread) ==
|
|
SafepointLevel::kGC);
|
|
}
|
|
{
|
|
DeoptSafepointOperationScope safepoint_scope(thread);
|
|
EXPECT(safepoint_handler->InnermostSafepointOperation(thread) ==
|
|
SafepointLevel::kGCAndDeopt);
|
|
GcSafepointOperationScope safepoint_scope2(thread);
|
|
EXPECT(safepoint_handler->InnermostSafepointOperation(thread) ==
|
|
SafepointLevel::kGC);
|
|
}
|
|
{
|
|
GcSafepointOperationScope safepoint_scope1(thread);
|
|
EXPECT(safepoint_handler->InnermostSafepointOperation(thread) ==
|
|
SafepointLevel::kGC);
|
|
GcSafepointOperationScope safepoint_scope2(thread);
|
|
EXPECT(safepoint_handler->InnermostSafepointOperation(thread) ==
|
|
SafepointLevel::kGC);
|
|
}
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE_WITH_EXPECTATION(
|
|
SafepointOperation_NonDeoptAndDeoptNesting,
|
|
"Crash") {
|
|
GcSafepointOperationScope safepoint_scope(thread);
|
|
DeoptSafepointOperationScope safepoint_scope2(thread);
|
|
}
|
|
|
|
class IsolateExitScope {
|
|
public:
|
|
IsolateExitScope() : saved_isolate_(Dart_CurrentIsolate()) {
|
|
Dart_ExitIsolate();
|
|
}
|
|
~IsolateExitScope() { Dart_EnterIsolate(saved_isolate_); }
|
|
|
|
private:
|
|
Dart_Isolate saved_isolate_;
|
|
};
|
|
|
|
ISOLATE_UNIT_TEST_CASE(ReloadScopes_Test) {
|
|
// Unscheduling an isolate will enter a safepoint that is reloadable.
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
IsolateExitScope isolate_leave_scope;
|
|
#if !defined(PRODUCT)
|
|
EXPECT(thread->IsAtSafepoint(SafepointLevel::kGCAndDeoptAndReload));
|
|
#else
|
|
EXPECT(thread->IsAtSafepoint(SafepointLevel::kGCAndDeopt));
|
|
EXPECT(!thread->IsAtSafepoint(SafepointLevel::kGCAndDeoptAndReload));
|
|
#endif
|
|
}
|
|
|
|
// Unscheduling an isolate with active [NoReloadScope] will enter a safepoint
|
|
// that is not reloadable.
|
|
{
|
|
NoReloadScope no_reload_scope(thread);
|
|
TransitionVMToNative transition(thread);
|
|
IsolateExitScope isolate_leave_scope;
|
|
EXPECT(thread->IsAtSafepoint(SafepointLevel::kGCAndDeopt));
|
|
}
|
|
|
|
// Native/FFI calls are generally reloadable.
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
#if !defined(PRODUCT)
|
|
EXPECT(thread->IsAtSafepoint(SafepointLevel::kGCAndDeoptAndReload));
|
|
#else
|
|
EXPECT(thread->IsAtSafepoint(SafepointLevel::kGCAndDeopt));
|
|
EXPECT(!thread->IsAtSafepoint(SafepointLevel::kGCAndDeoptAndReload));
|
|
#endif
|
|
}
|
|
|
|
// But in some contexts like compiling should not be reloadable.
|
|
{
|
|
NoReloadScope no_reload_scope(thread);
|
|
TransitionVMToNative transition(thread);
|
|
EXPECT(thread->IsAtSafepoint(SafepointLevel::kGCAndDeopt));
|
|
EXPECT(!thread->IsAtSafepoint(SafepointLevel::kGCAndDeoptAndReload));
|
|
}
|
|
}
|
|
|
|
#if !defined(PRODUCT)
|
|
class ReloadTask : public StateMachineTask {
|
|
public:
|
|
using Data = StateMachineTask::Data;
|
|
|
|
explicit ReloadTask(std::shared_ptr<Data> data) : StateMachineTask(data) {}
|
|
|
|
protected:
|
|
virtual void RunInternal() { RELOAD_OPERATION_SCOPE(thread_); }
|
|
};
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Reload_AtReloadSafepoint) {
|
|
auto isolate = thread->isolate();
|
|
auto messages = isolate->message_handler();
|
|
|
|
ThreadPool pool;
|
|
|
|
{
|
|
ReloadParticipationScope allow_reload(thread);
|
|
|
|
// We are not at a safepoint.
|
|
ASSERT(!thread->IsAtSafepoint());
|
|
|
|
// Enter a reload safepoint.
|
|
thread->EnterSafepoint();
|
|
{
|
|
// The [ReloadTask] will trigger a reload safepoint operation, sees that
|
|
// we are at reload safepoint & finishes without sending OOB message.
|
|
std::shared_ptr<ReloadTask::Data> task(
|
|
new ReloadTask::Data(isolate->group()));
|
|
pool.Run<ReloadTask>(task);
|
|
task->WaitUntil(ReloadTask::kEntered);
|
|
task->MarkAndNotify(ReloadTask::kPleaseExit);
|
|
task->WaitUntil(ReloadTask::kExited);
|
|
}
|
|
thread->ExitSafepoint();
|
|
|
|
EXPECT(!messages->HasOOBMessages());
|
|
}
|
|
}
|
|
|
|
static void EnsureValidOOBMessage(Thread* thread,
|
|
Isolate* isolate,
|
|
std::unique_ptr<Message> message) {
|
|
EXPECT(message->IsOOB());
|
|
EXPECT(message->dest_port() == isolate->main_port());
|
|
|
|
const auto& msg = Object::Handle(ReadMessage(thread, message.get()));
|
|
EXPECT(msg.IsArray());
|
|
|
|
const auto& array = Array::Cast(msg);
|
|
EXPECT(array.Length() == 3);
|
|
EXPECT(Smi::Value(Smi::RawCast(array.At(0))) == Message::kIsolateLibOOBMsg);
|
|
EXPECT(Smi::Value(Smi::RawCast(array.At(1))) == Isolate::kCheckForReload);
|
|
// 3rd value is ignored.
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Reload_NotAtSafepoint) {
|
|
auto isolate = thread->isolate();
|
|
auto messages = isolate->message_handler();
|
|
|
|
ThreadPool pool;
|
|
|
|
std::shared_ptr<ReloadTask::Data> task(
|
|
new ReloadTask::Data(isolate->group()));
|
|
|
|
{
|
|
// Even if we are not running with an active isolate (e.g. due to being in
|
|
// GC / Compiler) the reload safepoint operation should still send us an OOB
|
|
// message (it should know this thread belongs to an isolate).
|
|
NoActiveIsolateScope no_active_isolate(thread);
|
|
|
|
pool.Run<ReloadTask>(task);
|
|
task->WaitUntil(ReloadTask::kEntered);
|
|
|
|
// We are not at a safepoint. The [ReloadTask] will trigger a reload
|
|
// safepoint operation, sees that we are not at reload safepoint and instead
|
|
// sends us an OOB.
|
|
ASSERT(!thread->IsAtSafepoint());
|
|
while (!messages->HasOOBMessages()) {
|
|
OS::Sleep(1000);
|
|
}
|
|
}
|
|
|
|
// Examine the OOB message for it's content.
|
|
std::unique_ptr<Message> message = messages->StealOOBMessage();
|
|
EnsureValidOOBMessage(thread, isolate, std::move(message));
|
|
|
|
// Finally participate in the reload safepoint and finish.
|
|
{
|
|
ReloadParticipationScope allow_reload(thread);
|
|
thread->BlockForSafepoint();
|
|
}
|
|
|
|
task->MarkAndNotify(ReloadTask::kPleaseExit);
|
|
task->WaitUntil(ReloadTask::kExited);
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Reload_AtNonReloadSafepoint) {
|
|
auto isolate = thread->isolate();
|
|
auto messages = isolate->message_handler();
|
|
|
|
ThreadPool pool;
|
|
|
|
// The [ReloadTask] will trigger a reload safepoint operation, sees that
|
|
// we are at not at reload safepoint & sends us an OOB and waits for us to
|
|
// check-in.
|
|
std::shared_ptr<ReloadTask::Data> task(
|
|
new ReloadTask::Data(isolate->group()));
|
|
pool.Run<ReloadTask>(task);
|
|
task->WaitUntil(ReloadTask::kEntered);
|
|
|
|
{
|
|
NoReloadScope no_reload(thread);
|
|
|
|
// We are not at a safepoint.
|
|
ASSERT(!thread->IsAtSafepoint());
|
|
|
|
// Enter a non-reload safepoint.
|
|
thread->EnterSafepoint();
|
|
{
|
|
// We are at a safepoint but not a reload safepoint. So we'll get an OOM.
|
|
ASSERT(thread->IsAtSafepoint());
|
|
while (!messages->HasOOBMessages()) {
|
|
OS::Sleep(1000);
|
|
}
|
|
// Ensure we got a valid OOM
|
|
std::unique_ptr<Message> message = messages->StealOOBMessage();
|
|
EnsureValidOOBMessage(thread, isolate, std::move(message));
|
|
}
|
|
thread->ExitSafepoint();
|
|
|
|
EXPECT(!messages->HasOOBMessages());
|
|
}
|
|
|
|
// We left the [NoReloadScope] which in it's destructor should detect
|
|
// that a reload safepoint operation is requested and re-send OOM message to
|
|
// current isolate.
|
|
EXPECT(messages->HasOOBMessages());
|
|
std::unique_ptr<Message> message = messages->StealOOBMessage();
|
|
EnsureValidOOBMessage(thread, isolate, std::move(message));
|
|
|
|
// Finally participate in the reload safepoint and finish.
|
|
{
|
|
ReloadParticipationScope allow_reload(thread);
|
|
thread->BlockForSafepoint();
|
|
}
|
|
|
|
task->MarkAndNotify(ReloadTask::kPleaseExit);
|
|
task->WaitUntil(ReloadTask::kExited);
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
|
|
} // namespace dart
|