blob: 362db62f408cdfa36ad2c7ed66d76b07ed5ddd35 [file] [log] [blame] [edit]
// 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_->isolate_group()->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