mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 23:49:45 +00:00
6226568fa5
Allows the scavenger to avoid pruning work, which can be significant if the marking stack becomes very deep. TEST=ci Bug: https://github.com/dart-lang/sdk/issues/55713 Change-Id: Icb6c15d07034bc79573af1bf30f31cbf30c59717 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/369123 Reviewed-by: Siva Annamalai <asiva@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
3822 lines
129 KiB
C++
3822 lines
129 KiB
C++
// Copyright (c) 2013, 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 <utility>
|
|
|
|
#include "vm/isolate.h"
|
|
|
|
#include "include/dart_api.h"
|
|
#include "include/dart_native_api.h"
|
|
#include "platform/assert.h"
|
|
#include "platform/atomic.h"
|
|
#include "platform/text_buffer.h"
|
|
#include "vm/canonical_tables.h"
|
|
#include "vm/class_finalizer.h"
|
|
#include "vm/code_observers.h"
|
|
#include "vm/compiler/jit/compiler.h"
|
|
#include "vm/dart_api_message.h"
|
|
#include "vm/dart_api_state.h"
|
|
#include "vm/dart_entry.h"
|
|
#include "vm/debugger.h"
|
|
#include "vm/deopt_instructions.h"
|
|
#include "vm/dispatch_table.h"
|
|
#include "vm/ffi_callback_metadata.h"
|
|
#include "vm/flags.h"
|
|
#include "vm/heap/heap.h"
|
|
#include "vm/heap/pointer_block.h"
|
|
#include "vm/heap/safepoint.h"
|
|
#include "vm/heap/verifier.h"
|
|
#include "vm/image_snapshot.h"
|
|
#include "vm/isolate_reload.h"
|
|
#include "vm/kernel_isolate.h"
|
|
#include "vm/lockers.h"
|
|
#include "vm/log.h"
|
|
#include "vm/message_handler.h"
|
|
#include "vm/message_snapshot.h"
|
|
#include "vm/object.h"
|
|
#include "vm/object_id_ring.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/os_thread.h"
|
|
#include "vm/port.h"
|
|
#include "vm/profiler.h"
|
|
#include "vm/reusable_handles.h"
|
|
#include "vm/reverse_pc_lookup_cache.h"
|
|
#include "vm/service.h"
|
|
#include "vm/service_event.h"
|
|
#include "vm/service_isolate.h"
|
|
#include "vm/simulator.h"
|
|
#include "vm/stack_frame.h"
|
|
#include "vm/stub_code.h"
|
|
#include "vm/symbols.h"
|
|
#include "vm/tags.h"
|
|
#include "vm/thread.h"
|
|
#include "vm/thread_interrupter.h"
|
|
#include "vm/thread_registry.h"
|
|
#include "vm/timeline.h"
|
|
#include "vm/visitor.h"
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
#include "vm/compiler/assembler/assembler.h"
|
|
#include "vm/compiler/stub_code_compiler.h"
|
|
#endif
|
|
|
|
namespace dart {
|
|
|
|
DECLARE_FLAG(bool, print_metrics);
|
|
DECLARE_FLAG(bool, trace_service);
|
|
DECLARE_FLAG(bool, trace_shutdown);
|
|
DECLARE_FLAG(bool, warn_on_pause_with_no_debugger);
|
|
DECLARE_FLAG(int, old_gen_growth_time_ratio);
|
|
|
|
// Reload flags.
|
|
DECLARE_FLAG(int, reload_every);
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
DECLARE_FLAG(bool, check_reloaded);
|
|
DECLARE_FLAG(bool, reload_every_back_off);
|
|
DECLARE_FLAG(bool, trace_reload);
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
static void DeterministicModeHandler(bool value) {
|
|
if (value) {
|
|
FLAG_background_compilation = false; // Timing dependent.
|
|
FLAG_concurrent_mark = false; // Timing dependent.
|
|
FLAG_concurrent_sweep = false; // Timing dependent.
|
|
FLAG_scavenger_tasks = 0; // Timing dependent.
|
|
FLAG_old_gen_growth_time_ratio = 0; // Timing dependent.
|
|
FLAG_random_seed = 0x44617274; // "Dart"
|
|
}
|
|
}
|
|
|
|
DEFINE_FLAG_HANDLER(DeterministicModeHandler,
|
|
deterministic,
|
|
"Enable deterministic mode.");
|
|
|
|
DEFINE_FLAG(bool,
|
|
disable_thread_pool_limit,
|
|
false,
|
|
"Disables the limit of the thread pool (simulates custom embedder "
|
|
"with custom message handler on unlimited number of threads).");
|
|
|
|
// Quick access to the locally defined thread() and isolate() methods.
|
|
#define T (thread())
|
|
#define I (isolate())
|
|
#define IG (isolate_group())
|
|
|
|
#if defined(DEBUG)
|
|
// Helper class to ensure that a live origin_id is never reused
|
|
// and assigned to an isolate.
|
|
class VerifyOriginId : public IsolateVisitor {
|
|
public:
|
|
explicit VerifyOriginId(Dart_Port id) : id_(id) {}
|
|
|
|
void VisitIsolate(Isolate* isolate) { ASSERT(isolate->origin_id() != id_); }
|
|
|
|
private:
|
|
Dart_Port id_;
|
|
DISALLOW_COPY_AND_ASSIGN(VerifyOriginId);
|
|
};
|
|
#endif
|
|
|
|
static std::unique_ptr<Message> SerializeMessage(Dart_Port dest_port,
|
|
const Instance& obj) {
|
|
return WriteMessage(/* same_group */ false, obj, dest_port,
|
|
Message::kNormalPriority);
|
|
}
|
|
|
|
static std::unique_ptr<Message> SerializeMessage(Zone* zone,
|
|
Dart_Port dest_port,
|
|
Dart_CObject* obj) {
|
|
return WriteApiMessage(zone, obj, dest_port, Message::kNormalPriority);
|
|
}
|
|
|
|
void IsolateGroupSource::add_loaded_blob(
|
|
Zone* zone,
|
|
const ExternalTypedData& external_typed_data) {
|
|
Array& loaded_blobs = Array::Handle();
|
|
bool saved_external_typed_data = false;
|
|
if (loaded_blobs_ != nullptr) {
|
|
loaded_blobs = loaded_blobs_;
|
|
|
|
// Walk the array, and (if stuff was removed) compact and reuse the space.
|
|
// Note that the space has to be compacted as the ordering is important.
|
|
WeakProperty& weak_property = WeakProperty::Handle();
|
|
WeakProperty& weak_property_tmp = WeakProperty::Handle();
|
|
ExternalTypedData& existing_entry = ExternalTypedData::Handle(zone);
|
|
intptr_t next_entry_index = 0;
|
|
for (intptr_t i = 0; i < loaded_blobs.Length(); i++) {
|
|
weak_property ^= loaded_blobs.At(i);
|
|
if (weak_property.key() != ExternalTypedData::null()) {
|
|
if (i != next_entry_index) {
|
|
existing_entry = ExternalTypedData::RawCast(weak_property.key());
|
|
weak_property_tmp ^= loaded_blobs.At(next_entry_index);
|
|
weak_property_tmp.set_key(existing_entry);
|
|
}
|
|
next_entry_index++;
|
|
}
|
|
}
|
|
if (next_entry_index < loaded_blobs.Length()) {
|
|
// There's now space to re-use.
|
|
weak_property ^= loaded_blobs.At(next_entry_index);
|
|
weak_property.set_key(external_typed_data);
|
|
next_entry_index++;
|
|
saved_external_typed_data = true;
|
|
}
|
|
if (next_entry_index < loaded_blobs.Length()) {
|
|
ExternalTypedData& nullExternalTypedData =
|
|
ExternalTypedData::Handle(zone);
|
|
while (next_entry_index < loaded_blobs.Length()) {
|
|
// Null out any extra spaces.
|
|
weak_property ^= loaded_blobs.At(next_entry_index);
|
|
weak_property.set_key(nullExternalTypedData);
|
|
next_entry_index++;
|
|
}
|
|
}
|
|
}
|
|
if (!saved_external_typed_data) {
|
|
const WeakProperty& weak_property =
|
|
WeakProperty::Handle(WeakProperty::New(Heap::kOld));
|
|
weak_property.set_key(external_typed_data);
|
|
|
|
intptr_t length = loaded_blobs.IsNull() ? 0 : loaded_blobs.Length();
|
|
Array& new_array =
|
|
Array::Handle(Array::Grow(loaded_blobs, length + 1, Heap::kOld));
|
|
new_array.SetAt(length, weak_property);
|
|
loaded_blobs_ = new_array.ptr();
|
|
}
|
|
num_blob_loads_++;
|
|
}
|
|
|
|
void IdleTimeHandler::InitializeWithHeap(Heap* heap) {
|
|
MutexLocker ml(&mutex_);
|
|
ASSERT(heap_ == nullptr && heap != nullptr);
|
|
heap_ = heap;
|
|
}
|
|
|
|
bool IdleTimeHandler::ShouldCheckForIdle() {
|
|
MutexLocker ml(&mutex_);
|
|
return idle_start_time_ > 0 && FLAG_idle_timeout_micros != 0 &&
|
|
disabled_counter_ == 0;
|
|
}
|
|
|
|
void IdleTimeHandler::UpdateStartIdleTime() {
|
|
MutexLocker ml(&mutex_);
|
|
if (disabled_counter_ == 0) {
|
|
idle_start_time_ = OS::GetCurrentMonotonicMicros();
|
|
}
|
|
}
|
|
|
|
bool IdleTimeHandler::ShouldNotifyIdle(int64_t* expiry) {
|
|
const int64_t now = OS::GetCurrentMonotonicMicros();
|
|
|
|
MutexLocker ml(&mutex_);
|
|
if (idle_start_time_ > 0 && disabled_counter_ == 0) {
|
|
const int64_t expiry_time = idle_start_time_ + FLAG_idle_timeout_micros;
|
|
if (expiry_time < now) {
|
|
idle_start_time_ = 0;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
*expiry = now + FLAG_idle_timeout_micros;
|
|
return false;
|
|
}
|
|
|
|
void IdleTimeHandler::NotifyIdle(int64_t deadline) {
|
|
{
|
|
MutexLocker ml(&mutex_);
|
|
disabled_counter_++;
|
|
}
|
|
if (heap_ != nullptr) {
|
|
heap_->NotifyIdle(deadline);
|
|
}
|
|
{
|
|
MutexLocker ml(&mutex_);
|
|
disabled_counter_--;
|
|
idle_start_time_ = 0;
|
|
}
|
|
}
|
|
|
|
void IdleTimeHandler::NotifyIdleUsingDefaultDeadline() {
|
|
const int64_t now = OS::GetCurrentMonotonicMicros();
|
|
NotifyIdle(now + FLAG_idle_duration_micros);
|
|
}
|
|
|
|
DisableIdleTimerScope::DisableIdleTimerScope(IdleTimeHandler* handler)
|
|
: handler_(handler) {
|
|
if (handler_ != nullptr) {
|
|
MutexLocker ml(&handler_->mutex_);
|
|
++handler_->disabled_counter_;
|
|
handler_->idle_start_time_ = 0;
|
|
}
|
|
}
|
|
|
|
DisableIdleTimerScope::~DisableIdleTimerScope() {
|
|
if (handler_ != nullptr) {
|
|
MutexLocker ml(&handler_->mutex_);
|
|
--handler_->disabled_counter_;
|
|
ASSERT(handler_->disabled_counter_ >= 0);
|
|
}
|
|
}
|
|
|
|
class FinalizeWeakPersistentHandlesVisitor : public HandleVisitor {
|
|
public:
|
|
explicit FinalizeWeakPersistentHandlesVisitor(IsolateGroup* isolate_group)
|
|
: HandleVisitor(Thread::Current()), isolate_group_(isolate_group) {}
|
|
|
|
void VisitHandle(uword addr) override {
|
|
auto handle = reinterpret_cast<FinalizablePersistentHandle*>(addr);
|
|
handle->UpdateUnreachable(isolate_group_);
|
|
}
|
|
|
|
private:
|
|
IsolateGroup* isolate_group_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(FinalizeWeakPersistentHandlesVisitor);
|
|
};
|
|
|
|
void MutatorThreadPool::OnEnterIdleLocked(MonitorLocker* ml) {
|
|
if (FLAG_idle_timeout_micros == 0) return;
|
|
|
|
// If the isolate has not started running application code yet, we ignore the
|
|
// idle time.
|
|
if (!isolate_group_->initial_spawn_successful()) return;
|
|
|
|
int64_t idle_expiry = 0;
|
|
// Obtain the idle time we should wait.
|
|
if (isolate_group_->idle_time_handler()->ShouldNotifyIdle(&idle_expiry)) {
|
|
MonitorLeaveScope mls(ml);
|
|
NotifyIdle();
|
|
return;
|
|
}
|
|
|
|
// Avoid shutdown having to wait for the timeout to expire.
|
|
if (ShuttingDownLocked()) return;
|
|
|
|
// Wait for the recommended idle timeout.
|
|
// We can be woken up because of a), b) or c)
|
|
const auto result =
|
|
ml->WaitMicros(idle_expiry - OS::GetCurrentMonotonicMicros());
|
|
|
|
// a) If there are new tasks we have to run them.
|
|
if (TasksWaitingToRunLocked()) return;
|
|
|
|
// b) If the thread pool is shutting down we're done.
|
|
if (ShuttingDownLocked()) return;
|
|
|
|
// c) We timed out and should run the idle notifier.
|
|
if (result == Monitor::kTimedOut &&
|
|
isolate_group_->idle_time_handler()->ShouldNotifyIdle(&idle_expiry)) {
|
|
MonitorLeaveScope mls(ml);
|
|
NotifyIdle();
|
|
return;
|
|
}
|
|
|
|
// There must've been another thread doing active work in the meantime.
|
|
// If that thread becomes idle and is the last idle thread it will run this
|
|
// code again.
|
|
}
|
|
|
|
void MutatorThreadPool::NotifyIdle() {
|
|
EnterIsolateGroupScope isolate_group_scope(isolate_group_);
|
|
isolate_group_->idle_time_handler()->NotifyIdleUsingDefaultDeadline();
|
|
}
|
|
|
|
IsolateGroup::IsolateGroup(std::shared_ptr<IsolateGroupSource> source,
|
|
void* embedder_data,
|
|
ObjectStore* object_store,
|
|
Dart_IsolateFlags api_flags,
|
|
bool is_vm_isolate)
|
|
: class_table_(nullptr),
|
|
cached_class_table_table_(nullptr),
|
|
object_store_(object_store),
|
|
class_table_allocator_(),
|
|
is_vm_isolate_(is_vm_isolate),
|
|
embedder_data_(embedder_data),
|
|
thread_pool_(),
|
|
isolates_lock_(new SafepointRwLock()),
|
|
isolates_(),
|
|
start_time_micros_(OS::GetCurrentMonotonicMicros()),
|
|
is_system_isolate_group_(source->flags.is_system_isolate),
|
|
random_(),
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
last_reload_timestamp_(OS::GetCurrentTimeMillis()),
|
|
reload_every_n_stack_overflow_checks_(FLAG_reload_every),
|
|
#endif
|
|
source_(std::move(source)),
|
|
api_state_(new ApiState()),
|
|
thread_registry_(new ThreadRegistry()),
|
|
safepoint_handler_(new SafepointHandler(this)),
|
|
store_buffer_(new StoreBuffer()),
|
|
heap_(nullptr),
|
|
saved_unlinked_calls_(Array::null()),
|
|
initial_field_table_(new FieldTable(/*isolate=*/nullptr)),
|
|
shared_field_table_(new FieldTable(/*isolate=*/nullptr, /*shared=*/true)),
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
background_compiler_(new BackgroundCompiler(this)),
|
|
#endif
|
|
symbols_mutex_(NOT_IN_PRODUCT("IsolateGroup::symbols_mutex_")),
|
|
type_canonicalization_mutex_(
|
|
NOT_IN_PRODUCT("IsolateGroup::type_canonicalization_mutex_")),
|
|
type_arguments_canonicalization_mutex_(NOT_IN_PRODUCT(
|
|
"IsolateGroup::type_arguments_canonicalization_mutex_")),
|
|
subtype_test_cache_mutex_(
|
|
NOT_IN_PRODUCT("IsolateGroup::subtype_test_cache_mutex_")),
|
|
megamorphic_table_mutex_(
|
|
NOT_IN_PRODUCT("IsolateGroup::megamorphic_table_mutex_")),
|
|
type_feedback_mutex_(
|
|
NOT_IN_PRODUCT("IsolateGroup::type_feedback_mutex_")),
|
|
patchable_call_mutex_(
|
|
NOT_IN_PRODUCT("IsolateGroup::patchable_call_mutex_")),
|
|
constant_canonicalization_mutex_(
|
|
NOT_IN_PRODUCT("IsolateGroup::constant_canonicalization_mutex_")),
|
|
kernel_data_lib_cache_mutex_(
|
|
NOT_IN_PRODUCT("IsolateGroup::kernel_data_lib_cache_mutex_")),
|
|
kernel_data_class_cache_mutex_(
|
|
NOT_IN_PRODUCT("IsolateGroup::kernel_data_class_cache_mutex_")),
|
|
kernel_constants_mutex_(
|
|
NOT_IN_PRODUCT("IsolateGroup::kernel_constants_mutex_")),
|
|
field_list_mutex_(NOT_IN_PRODUCT("Isolate::field_list_mutex_")),
|
|
boxed_field_list_(GrowableObjectArray::null()),
|
|
program_lock_(new SafepointRwLock()),
|
|
active_mutators_monitor_(new Monitor()),
|
|
max_active_mutators_(Scavenger::MaxMutatorThreadCount())
|
|
#if !defined(PRODUCT)
|
|
,
|
|
debugger_(new GroupDebugger(this))
|
|
#endif
|
|
{
|
|
FlagsCopyFrom(api_flags);
|
|
if (!is_vm_isolate) {
|
|
thread_pool_.reset(
|
|
new MutatorThreadPool(this, FLAG_disable_thread_pool_limit
|
|
? 0
|
|
: Scavenger::MaxMutatorThreadCount()));
|
|
}
|
|
{
|
|
WriteRwLocker wl(ThreadState::Current(), isolate_groups_rwlock_);
|
|
// Keep isolate IDs less than 2^53 so web clients of the service
|
|
// protocol can process it properly.
|
|
//
|
|
// See https://github.com/dart-lang/sdk/issues/53081.
|
|
id_ = isolate_group_random_->NextJSInt();
|
|
}
|
|
heap_walk_class_table_ = class_table_ =
|
|
new ClassTable(&class_table_allocator_);
|
|
cached_class_table_table_.store(class_table_->table());
|
|
memset(&native_assets_api_, 0, sizeof(NativeAssetsApi));
|
|
}
|
|
|
|
IsolateGroup::IsolateGroup(std::shared_ptr<IsolateGroupSource> source,
|
|
void* embedder_data,
|
|
Dart_IsolateFlags api_flags,
|
|
bool is_vm_isolate)
|
|
: IsolateGroup(source,
|
|
embedder_data,
|
|
new ObjectStore(),
|
|
api_flags,
|
|
is_vm_isolate) {
|
|
if (object_store() != nullptr) {
|
|
object_store()->InitStubs();
|
|
}
|
|
}
|
|
|
|
IsolateGroup::~IsolateGroup() {
|
|
// Ensure we destroy the heap before the other members.
|
|
heap_ = nullptr;
|
|
ASSERT(old_marking_stack_ == nullptr);
|
|
ASSERT(new_marking_stack_ == nullptr);
|
|
ASSERT(deferred_marking_stack_ == nullptr);
|
|
|
|
if (obfuscation_map_ != nullptr) {
|
|
for (intptr_t i = 0; obfuscation_map_[i] != nullptr; i++) {
|
|
delete[] obfuscation_map_[i];
|
|
}
|
|
delete[] obfuscation_map_;
|
|
}
|
|
|
|
class_table_allocator_.Free(class_table_);
|
|
if (heap_walk_class_table_ != class_table_) {
|
|
class_table_allocator_.Free(heap_walk_class_table_);
|
|
}
|
|
|
|
#if !defined(PRODUCT)
|
|
delete debugger_;
|
|
debugger_ = nullptr;
|
|
#endif
|
|
}
|
|
|
|
void IsolateGroup::RegisterIsolate(Isolate* isolate) {
|
|
SafepointWriteRwLocker ml(Thread::Current(), isolates_lock_.get());
|
|
ASSERT(isolates_lock_->IsCurrentThreadWriter());
|
|
isolates_.Append(isolate);
|
|
isolate_count_++;
|
|
}
|
|
|
|
bool IsolateGroup::ContainsOnlyOneIsolate() {
|
|
SafepointReadRwLocker ml(Thread::Current(), isolates_lock_.get());
|
|
// We do allow 0 here as well, because the background compiler might call
|
|
// this method while the mutator thread is in shutdown procedure and
|
|
// unregistered itself already.
|
|
return isolate_count_ == 0 || isolate_count_ == 1;
|
|
}
|
|
|
|
void IsolateGroup::RunWithLockedGroup(std::function<void()> fun) {
|
|
SafepointWriteRwLocker ml(Thread::Current(), isolates_lock_.get());
|
|
fun();
|
|
}
|
|
|
|
void IsolateGroup::UnregisterIsolate(Isolate* isolate) {
|
|
SafepointWriteRwLocker ml(Thread::Current(), isolates_lock_.get());
|
|
isolates_.Remove(isolate);
|
|
}
|
|
|
|
bool IsolateGroup::UnregisterIsolateDecrementCount() {
|
|
SafepointWriteRwLocker ml(Thread::Current(), isolates_lock_.get());
|
|
isolate_count_--;
|
|
return isolate_count_ == 0;
|
|
}
|
|
|
|
void IsolateGroup::CreateHeap(bool is_vm_isolate,
|
|
bool is_service_or_kernel_isolate) {
|
|
Heap::Init(this, is_vm_isolate,
|
|
is_vm_isolate
|
|
? 0 // New gen size 0; VM isolate should only allocate in old.
|
|
: FLAG_new_gen_semi_max_size * MBInWords,
|
|
(is_service_or_kernel_isolate ? kDefaultMaxOldGenHeapSize
|
|
: FLAG_old_gen_heap_size) *
|
|
MBInWords);
|
|
|
|
#define ISOLATE_GROUP_METRIC_CONSTRUCTORS(type, variable, name, unit) \
|
|
metric_##variable##_.InitInstance(this, name, nullptr, Metric::unit);
|
|
ISOLATE_GROUP_METRIC_LIST(ISOLATE_GROUP_METRIC_CONSTRUCTORS)
|
|
#undef ISOLATE_GROUP_METRIC_CONSTRUCTORS
|
|
}
|
|
|
|
void IsolateGroup::Shutdown() {
|
|
char* name = nullptr;
|
|
// We retrieve the flag value once to avoid the compiler complaining about the
|
|
// possibly uninitialized value of name, as the compiler is unaware that when
|
|
// the flag variable is non-const, it is set once during VM initialization and
|
|
// never changed after, and that modification never runs concurrently with
|
|
// this method.
|
|
const bool trace_shutdown = FLAG_trace_shutdown;
|
|
|
|
if (trace_shutdown) {
|
|
name = Utils::StrDup(source()->name);
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Shutdown starting for group %s\n",
|
|
Dart::UptimeMillis(), name);
|
|
}
|
|
// Ensure to join all threads before waiting for pending GC tasks (the thread
|
|
// pool can trigger idle notification, which can start new GC tasks).
|
|
//
|
|
// (The vm-isolate doesn't have a thread pool.)
|
|
if (!is_vm_isolate_) {
|
|
ASSERT(thread_pool_ != nullptr);
|
|
thread_pool_->Shutdown();
|
|
thread_pool_.reset();
|
|
}
|
|
|
|
// Needs to happen before starting to destroy the heap so helper tasks like
|
|
// the SampleBlockProcessor don't try to enter the group during this
|
|
// tear-down.
|
|
UnregisterIsolateGroup(this);
|
|
|
|
// Wait for any pending GC tasks.
|
|
if (heap_ != nullptr) {
|
|
// Wait for any concurrent GC tasks to finish before shutting down.
|
|
// TODO(rmacnak): Interrupt tasks for faster shutdown.
|
|
PageSpace* old_space = heap_->old_space();
|
|
MonitorLocker ml(old_space->tasks_lock());
|
|
while (old_space->tasks() > 0) {
|
|
ml.Wait();
|
|
}
|
|
// Needs to happen before ~PageSpace so TLS and the thread registry are
|
|
// still valid.
|
|
old_space->AbandonMarkingForShutdown();
|
|
}
|
|
|
|
// If the creation of the isolate group (or the first isolate within the
|
|
// isolate group) failed, we do not invoke the cleanup callback (the
|
|
// embedder is responsible for handling the creation error).
|
|
if (initial_spawn_successful_ && !is_vm_isolate_) {
|
|
auto group_shutdown_callback = Isolate::GroupCleanupCallback();
|
|
if (group_shutdown_callback != nullptr) {
|
|
group_shutdown_callback(embedder_data());
|
|
}
|
|
}
|
|
|
|
delete this;
|
|
|
|
// After this isolate group has died we might need to notify a pending
|
|
// `Dart_Cleanup()` call.
|
|
{
|
|
if (trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64
|
|
"ms] SHUTDOWN: Notifying "
|
|
"isolate group shutdown (%s)\n",
|
|
Dart::UptimeMillis(), name);
|
|
}
|
|
MonitorLocker ml(Isolate::isolate_creation_monitor_);
|
|
if (!Isolate::creation_enabled_ &&
|
|
!IsolateGroup::HasApplicationIsolateGroups()) {
|
|
ml.Notify();
|
|
}
|
|
if (trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64
|
|
"ms] SHUTDOWN: Done Notifying "
|
|
"isolate group shutdown (%s)\n",
|
|
Dart::UptimeMillis(), name);
|
|
}
|
|
}
|
|
if (trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Done shutdown for group %s\n",
|
|
Dart::UptimeMillis(), name);
|
|
free(name);
|
|
}
|
|
}
|
|
|
|
void IsolateGroup::set_heap(std::unique_ptr<Heap> heap) {
|
|
idle_time_handler_.InitializeWithHeap(heap.get());
|
|
heap_ = std::move(heap);
|
|
}
|
|
|
|
void IsolateGroup::set_saved_unlinked_calls(const Array& saved_unlinked_calls) {
|
|
saved_unlinked_calls_ = saved_unlinked_calls.ptr();
|
|
}
|
|
|
|
void IsolateGroup::IncreaseMutatorCount(Isolate* mutator,
|
|
bool is_nested_reenter) {
|
|
ASSERT(mutator->group() == this);
|
|
|
|
// If the mutator was temporarily blocked on a worker thread, we have to
|
|
// unblock the worker thread again.
|
|
if (is_nested_reenter) {
|
|
ASSERT(mutator->mutator_thread() != nullptr);
|
|
thread_pool()->MarkCurrentWorkerAsUnBlocked();
|
|
}
|
|
|
|
// Prevent too many mutators from entering the isolate group to avoid
|
|
// pathological behavior where many threads are fighting for obtaining TLABs.
|
|
{
|
|
// NOTE: This is performance critical code, we should avoid monitors and use
|
|
// std::atomics in the fast case (where active_mutators <
|
|
// max_active_mutators) and only use monitors in the uncommon case.
|
|
MonitorLocker ml(active_mutators_monitor_.get());
|
|
ASSERT(active_mutators_ <= max_active_mutators_);
|
|
while (active_mutators_ == max_active_mutators_) {
|
|
waiting_mutators_++;
|
|
ml.Wait();
|
|
waiting_mutators_--;
|
|
}
|
|
active_mutators_++;
|
|
}
|
|
}
|
|
|
|
void IsolateGroup::DecreaseMutatorCount(Isolate* mutator, bool is_nested_exit) {
|
|
ASSERT(mutator->group() == this);
|
|
|
|
// If the mutator thread has an active stack and runs on our thread pool we
|
|
// will mark the worker as blocked, thereby possibly spawning a new worker for
|
|
// pending tasks (if there are any).
|
|
if (is_nested_exit) {
|
|
ASSERT(mutator->mutator_thread() != nullptr);
|
|
thread_pool()->MarkCurrentWorkerAsBlocked();
|
|
}
|
|
|
|
{
|
|
// NOTE: This is performance critical code, we should avoid monitors and use
|
|
// std::atomics in the fast case (where active_mutators <
|
|
// max_active_mutators) and only use monitors in the uncommon case.
|
|
MonitorLocker ml(active_mutators_monitor_.get());
|
|
ASSERT(active_mutators_ <= max_active_mutators_);
|
|
active_mutators_--;
|
|
if (waiting_mutators_ > 0) {
|
|
ml.Notify();
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef PRODUCT
|
|
void IsolateGroup::PrintJSON(JSONStream* stream, bool ref) {
|
|
JSONObject jsobj(stream);
|
|
PrintToJSONObject(&jsobj, ref);
|
|
}
|
|
|
|
void IsolateGroup::PrintToJSONObject(JSONObject* jsobj, bool ref) {
|
|
jsobj->AddProperty("type", (ref ? "@IsolateGroup" : "IsolateGroup"));
|
|
jsobj->AddServiceId(ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, id());
|
|
|
|
jsobj->AddProperty("name", source()->script_uri);
|
|
jsobj->AddPropertyF("number", "%" Pu64 "", id());
|
|
jsobj->AddProperty("isSystemIsolateGroup", is_system_isolate_group());
|
|
if (ref) {
|
|
return;
|
|
}
|
|
|
|
{
|
|
JSONArray isolate_array(jsobj, "isolates");
|
|
for (auto it = isolates_.Begin(); it != isolates_.End(); ++it) {
|
|
Isolate* isolate = *it;
|
|
isolate_array.AddValue(isolate, /*ref=*/true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IsolateGroup::PrintMemoryUsageJSON(JSONStream* stream) {
|
|
int64_t used = heap()->TotalUsedInWords();
|
|
int64_t capacity = heap()->TotalCapacityInWords();
|
|
int64_t external_used = heap()->TotalExternalInWords();
|
|
|
|
JSONObject jsobj(stream);
|
|
// This is the same "MemoryUsage" that the isolate-specific "getMemoryUsage"
|
|
// rpc method returns.
|
|
jsobj.AddProperty("type", "MemoryUsage");
|
|
jsobj.AddProperty64("heapUsage", used * kWordSize);
|
|
jsobj.AddProperty64("heapCapacity", capacity * kWordSize);
|
|
jsobj.AddProperty64("externalUsage", external_used * kWordSize);
|
|
}
|
|
#endif
|
|
|
|
void IsolateGroup::ForEach(std::function<void(IsolateGroup*)> action) {
|
|
ReadRwLocker wl(Thread::Current(), isolate_groups_rwlock_);
|
|
for (auto isolate_group : *isolate_groups_) {
|
|
action(isolate_group);
|
|
}
|
|
}
|
|
|
|
void IsolateGroup::RunWithIsolateGroup(
|
|
uint64_t id,
|
|
std::function<void(IsolateGroup*)> action,
|
|
std::function<void()> not_found) {
|
|
ReadRwLocker wl(Thread::Current(), isolate_groups_rwlock_);
|
|
for (auto isolate_group : *isolate_groups_) {
|
|
if (isolate_group->id() == id) {
|
|
action(isolate_group);
|
|
return;
|
|
}
|
|
}
|
|
not_found();
|
|
}
|
|
|
|
void IsolateGroup::RegisterIsolateGroup(IsolateGroup* isolate_group) {
|
|
WriteRwLocker wl(ThreadState::Current(), isolate_groups_rwlock_);
|
|
isolate_groups_->Append(isolate_group);
|
|
}
|
|
|
|
void IsolateGroup::UnregisterIsolateGroup(IsolateGroup* isolate_group) {
|
|
WriteRwLocker wl(ThreadState::Current(), isolate_groups_rwlock_);
|
|
isolate_groups_->Remove(isolate_group);
|
|
}
|
|
|
|
bool IsolateGroup::HasApplicationIsolateGroups() {
|
|
ReadRwLocker wl(ThreadState::Current(), isolate_groups_rwlock_);
|
|
for (auto group : *isolate_groups_) {
|
|
if (!IsolateGroup::IsSystemIsolateGroup(group)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IsolateGroup::HasOnlyVMIsolateGroup() {
|
|
ReadRwLocker wl(ThreadState::Current(), isolate_groups_rwlock_);
|
|
for (auto group : *isolate_groups_) {
|
|
if (!group->is_vm_isolate()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void IsolateGroup::Init() {
|
|
ASSERT(isolate_groups_rwlock_ == nullptr);
|
|
isolate_groups_rwlock_ = new RwLock();
|
|
ASSERT(isolate_groups_ == nullptr);
|
|
isolate_groups_ = new IntrusiveDList<IsolateGroup>();
|
|
isolate_group_random_ = new Random();
|
|
}
|
|
|
|
void IsolateGroup::Cleanup() {
|
|
delete isolate_group_random_;
|
|
isolate_group_random_ = nullptr;
|
|
delete isolate_groups_rwlock_;
|
|
isolate_groups_rwlock_ = nullptr;
|
|
ASSERT(isolate_groups_->IsEmpty());
|
|
delete isolate_groups_;
|
|
isolate_groups_ = nullptr;
|
|
}
|
|
|
|
bool IsolateVisitor::IsSystemIsolate(Isolate* isolate) const {
|
|
return Isolate::IsSystemIsolate(isolate);
|
|
}
|
|
|
|
Bequest::~Bequest() {
|
|
if (handle_ == nullptr) {
|
|
return;
|
|
}
|
|
|
|
IsolateGroup* isolate_group = IsolateGroup::Current();
|
|
CHECK_ISOLATE_GROUP(isolate_group);
|
|
NoSafepointScope no_safepoint_scope;
|
|
ApiState* state = isolate_group->api_state();
|
|
ASSERT(state != nullptr);
|
|
state->FreePersistentHandle(handle_);
|
|
}
|
|
|
|
void IsolateGroup::RegisterClass(const Class& cls) {
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
if (IsReloading()) {
|
|
program_reload_context()->RegisterClass(cls);
|
|
return;
|
|
}
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
if (cls.IsTopLevel()) {
|
|
class_table()->RegisterTopLevel(cls);
|
|
} else {
|
|
class_table()->Register(cls);
|
|
}
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
void IsolateGroup::ValidateClassTable() {
|
|
class_table()->Validate();
|
|
}
|
|
#endif // DEBUG
|
|
|
|
void IsolateGroup::RegisterStaticField(const Field& field,
|
|
const Object& initial_value) {
|
|
ASSERT(program_lock()->IsCurrentThreadWriter());
|
|
|
|
ASSERT(field.is_static());
|
|
if (field.is_shared()) {
|
|
GcSafepointOperationScope scope(Thread::Current());
|
|
if (shared_field_table()->Register(field)) {
|
|
for (auto isolate : isolates_) {
|
|
isolate->mutator_thread()->shared_field_table_values_ =
|
|
shared_field_table()->table();
|
|
}
|
|
}
|
|
const intptr_t field_id = field.field_id();
|
|
shared_field_table()->SetAt(field_id, initial_value.ptr());
|
|
return;
|
|
}
|
|
const bool need_to_grow_backing_store =
|
|
initial_field_table()->Register(field);
|
|
const intptr_t field_id = field.field_id();
|
|
initial_field_table()->SetAt(field_id, initial_value.ptr());
|
|
|
|
SafepointReadRwLocker ml(Thread::Current(), isolates_lock_.get());
|
|
if (need_to_grow_backing_store) {
|
|
// We have to stop other isolates from accessing their field state, since
|
|
// we'll have to grow the backing store.
|
|
GcSafepointOperationScope scope(Thread::Current());
|
|
for (auto isolate : isolates_) {
|
|
auto field_table = isolate->field_table();
|
|
if (field_table->IsReadyToUse()) {
|
|
field_table->Register(field, field_id);
|
|
field_table->SetAt(field_id, initial_value.ptr());
|
|
}
|
|
}
|
|
} else {
|
|
for (auto isolate : isolates_) {
|
|
auto field_table = isolate->field_table();
|
|
if (field_table->IsReadyToUse()) {
|
|
field_table->Register(field, field_id);
|
|
field_table->SetAt(field_id, initial_value.ptr());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IsolateGroup::FreeStaticField(const Field& field) {
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
// This can only be called during hot-reload.
|
|
ASSERT(program_reload_context() != nullptr);
|
|
#endif
|
|
|
|
const intptr_t field_id = field.field_id();
|
|
if (field.is_shared()) {
|
|
shared_field_table()->Free(field_id);
|
|
} else {
|
|
initial_field_table()->Free(field_id);
|
|
ForEachIsolate([&](Isolate* isolate) {
|
|
auto field_table = isolate->field_table();
|
|
// The isolate might've just been created and is now participating in
|
|
// the reload request inside `IsolateGroup::RegisterIsolate()`.
|
|
// At that point it doesn't have the field table setup yet.
|
|
if (field_table->IsReadyToUse()) {
|
|
field_table->Free(field_id);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
Isolate* IsolateGroup::EnterTemporaryIsolate() {
|
|
Dart_IsolateFlags flags;
|
|
Isolate::FlagsInitialize(&flags);
|
|
Isolate* const isolate = Isolate::InitIsolate("temp", this, flags);
|
|
ASSERT(isolate != nullptr);
|
|
ASSERT(Isolate::Current() == isolate);
|
|
return isolate;
|
|
}
|
|
|
|
void IsolateGroup::ExitTemporaryIsolate() {
|
|
Thread* thread = Thread::Current();
|
|
ASSERT(thread != nullptr);
|
|
thread->set_execution_state(Thread::kThreadInVM);
|
|
Dart::ShutdownIsolate(thread);
|
|
}
|
|
|
|
void IsolateGroup::RehashConstants(Become* become) {
|
|
// Even though no individual constant contains a cycle, there can be "cycles"
|
|
// between the canonical tables if some const instances of A have fields that
|
|
// are const instance of B and vice versa. So set all the old tables to the
|
|
// side and clear all the tables attached to the classes before rehashing
|
|
// instead of resetting and rehash one class at a time.
|
|
|
|
Thread* thread = Thread::Current();
|
|
StackZone stack_zone(thread);
|
|
Zone* zone = stack_zone.GetZone();
|
|
|
|
intptr_t num_cids = class_table()->NumCids();
|
|
Array** old_constant_tables = zone->Alloc<Array*>(num_cids);
|
|
for (intptr_t i = 0; i < num_cids; i++) {
|
|
old_constant_tables[i] = nullptr;
|
|
}
|
|
|
|
Class& cls = Class::Handle(zone);
|
|
for (intptr_t cid = kInstanceCid; cid < num_cids; cid++) {
|
|
if (!class_table()->IsValidIndex(cid) ||
|
|
!class_table()->HasValidClassAt(cid)) {
|
|
continue;
|
|
}
|
|
if ((cid == kTypeArgumentsCid) || IsStringClassId(cid)) {
|
|
// TypeArguments and Symbols have special tables for canonical objects
|
|
// that aren't based on address.
|
|
continue;
|
|
}
|
|
if ((cid == kMintCid) || (cid == kDoubleCid)) {
|
|
// Constants stored as a plain list or in a hashset with a stable
|
|
// hashcode, which only depends on the actual value of the constant.
|
|
continue;
|
|
}
|
|
|
|
cls = class_table()->At(cid);
|
|
if (cls.constants() == Array::null()) continue;
|
|
old_constant_tables[cid] = &Array::Handle(zone, cls.constants());
|
|
cls.set_constants(Object::null_array());
|
|
}
|
|
|
|
// Clear invalid hashes.
|
|
heap()->ResetCanonicalHashTable();
|
|
|
|
Instance& constant = Instance::Handle(zone);
|
|
Field& field = Field::Handle(zone);
|
|
String& name = String::Handle(zone);
|
|
Array& new_values = Array::Handle(zone);
|
|
Instance& old_value = Instance::Handle(zone);
|
|
Instance& new_value = Instance::Handle(zone);
|
|
Instance& deleted = Instance::Handle(zone);
|
|
|
|
if (become != nullptr) {
|
|
for (intptr_t cid = kInstanceCid; cid < num_cids; cid++) {
|
|
Array* old_constants = old_constant_tables[cid];
|
|
if (old_constants == nullptr) continue;
|
|
|
|
cls = class_table()->At(cid);
|
|
CanonicalInstancesSet set(zone, old_constants->ptr());
|
|
CanonicalInstancesSet::Iterator it(&set);
|
|
while (it.MoveNext()) {
|
|
constant ^= set.GetKey(it.Current());
|
|
ASSERT(!constant.IsNull());
|
|
ASSERT(!constant.InVMIsolateHeap());
|
|
constant.ClearCanonical();
|
|
}
|
|
set.Release();
|
|
}
|
|
}
|
|
|
|
for (intptr_t cid = kInstanceCid; cid < num_cids; cid++) {
|
|
Array* old_constants = old_constant_tables[cid];
|
|
if (old_constants == nullptr) continue;
|
|
|
|
cls = class_table()->At(cid);
|
|
CanonicalInstancesSet set(zone, old_constants->ptr());
|
|
CanonicalInstancesSet::Iterator it(&set);
|
|
|
|
if (cls.is_enum_class() && (become != nullptr)) {
|
|
field = cls.LookupStaticField(Symbols::_DeletedEnumSentinel());
|
|
deleted ^= field.StaticConstFieldValue();
|
|
if (deleted.IsNull()) {
|
|
deleted = Instance::New(cls, Heap::kOld);
|
|
field = object_store()->enum_name_field();
|
|
name = cls.ScrubbedName();
|
|
name = Symbols::FromConcat(thread, Symbols::_DeletedEnumPrefix(), name);
|
|
deleted.SetField(field, name);
|
|
field = object_store()->enum_index_field();
|
|
new_value = Smi::New(-1);
|
|
deleted.SetField(field, new_value);
|
|
field = cls.LookupStaticField(Symbols::_DeletedEnumSentinel());
|
|
// The static const field contains `Object::null()` instead of
|
|
// `Object::sentinel()` - so it's not considered an initializing store.
|
|
field.SetStaticConstFieldValue(deleted,
|
|
/*assert_initializing_store*/ false);
|
|
}
|
|
|
|
field = cls.LookupField(Symbols::Values());
|
|
new_values ^= field.StaticConstFieldValue();
|
|
|
|
field = object_store()->enum_name_field();
|
|
while (it.MoveNext()) {
|
|
old_value ^= set.GetKey(it.Current());
|
|
ASSERT(old_value.GetClassId() == cid);
|
|
bool found = false;
|
|
for (intptr_t j = 0; j < new_values.Length(); j++) {
|
|
new_value ^= new_values.At(j);
|
|
ASSERT(new_value.GetClassId() == cid);
|
|
if (old_value.GetField(field) == new_value.GetField(field)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
new_value = deleted.ptr();
|
|
}
|
|
|
|
if (old_value.ptr() != new_value.ptr()) {
|
|
become->Add(old_value, new_value);
|
|
}
|
|
if (new_value.IsCanonical()) {
|
|
cls.InsertCanonicalConstant(zone, new_value);
|
|
}
|
|
}
|
|
} else {
|
|
while (it.MoveNext()) {
|
|
old_value ^= set.GetKey(it.Current());
|
|
ASSERT(!old_value.IsNull());
|
|
|
|
if (become == nullptr) {
|
|
ASSERT(old_value.IsCanonical());
|
|
cls.InsertCanonicalConstant(zone, old_value);
|
|
} else {
|
|
new_value = old_value.Canonicalize(thread);
|
|
if (old_value.ptr() != new_value.ptr()) {
|
|
become->Add(old_value, new_value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
set.Release();
|
|
}
|
|
|
|
// Save memory.
|
|
heap()->ResetCanonicalHashTable();
|
|
}
|
|
|
|
void Isolate::SendInternalLibMessage(LibMsgId msg_id, uint64_t capability) {
|
|
const bool ok = SendInternalLibMessage(main_port(), msg_id, capability);
|
|
if (!ok) UNREACHABLE();
|
|
}
|
|
|
|
bool Isolate::SendInternalLibMessage(Dart_Port main_port,
|
|
LibMsgId msg_id,
|
|
uint64_t capability) {
|
|
Dart_CObject array_entry_msg_kind;
|
|
array_entry_msg_kind.type = Dart_CObject_kInt64;
|
|
array_entry_msg_kind.value.as_int64 = Message::kIsolateLibOOBMsg;
|
|
|
|
Dart_CObject array_entry_msg_id;
|
|
array_entry_msg_id.type = Dart_CObject_kInt64;
|
|
array_entry_msg_id.value.as_int64 = msg_id;
|
|
|
|
Dart_CObject array_entry_capability;
|
|
array_entry_capability.type = Dart_CObject_kCapability;
|
|
array_entry_capability.value.as_capability.id = capability;
|
|
|
|
Dart_CObject* array_entries[3] = {
|
|
&array_entry_msg_kind,
|
|
&array_entry_msg_id,
|
|
&array_entry_capability,
|
|
};
|
|
|
|
Dart_CObject message;
|
|
message.type = Dart_CObject_kArray;
|
|
message.value.as_array.values = array_entries;
|
|
message.value.as_array.length = ARRAY_SIZE(array_entries);
|
|
|
|
AllocOnlyStackZone zone;
|
|
std::unique_ptr<Message> msg = WriteApiMessage(
|
|
zone.GetZone(), &message, main_port, Message::kOOBPriority);
|
|
if (msg == nullptr) UNREACHABLE();
|
|
|
|
return PortMap::PostMessage(std::move(msg));
|
|
}
|
|
|
|
void IsolateGroup::set_object_store(ObjectStore* object_store) {
|
|
object_store_.reset(object_store);
|
|
}
|
|
|
|
class IsolateMessageHandler : public MessageHandler {
|
|
public:
|
|
explicit IsolateMessageHandler(Isolate* isolate);
|
|
~IsolateMessageHandler();
|
|
|
|
const char* name() const;
|
|
void MessageNotify(Message::Priority priority);
|
|
MessageStatus HandleMessage(std::unique_ptr<Message> message);
|
|
#ifndef PRODUCT
|
|
void NotifyPauseOnStart();
|
|
void NotifyPauseOnExit();
|
|
#endif // !PRODUCT
|
|
|
|
#if defined(DEBUG)
|
|
// Check that it is safe to access this handler.
|
|
void CheckAccess() const;
|
|
#endif
|
|
bool IsCurrentIsolate() const;
|
|
virtual Isolate* isolate() const { return isolate_; }
|
|
virtual IsolateGroup* isolate_group() const { return isolate_->group(); }
|
|
|
|
virtual bool KeepAliveLocked() {
|
|
// If the message handler was asked to shutdown we shut down.
|
|
if (!MessageHandler::KeepAliveLocked()) return false;
|
|
// Otherwise we only stay alive as long as there's active receive ports, or
|
|
// there are FFI callbacks keeping the isolate alive.
|
|
return isolate_->HasLivePorts() || isolate_->HasOpenNativeCallables();
|
|
}
|
|
|
|
private:
|
|
// A result of false indicates that the isolate should terminate the
|
|
// processing of further events.
|
|
ErrorPtr HandleLibMessage(const Array& message);
|
|
|
|
MessageStatus ProcessUnhandledException(const Error& result);
|
|
Isolate* isolate_;
|
|
};
|
|
|
|
IsolateMessageHandler::IsolateMessageHandler(Isolate* isolate)
|
|
: isolate_(isolate) {}
|
|
|
|
IsolateMessageHandler::~IsolateMessageHandler() {}
|
|
|
|
const char* IsolateMessageHandler::name() const {
|
|
return isolate_->name();
|
|
}
|
|
|
|
// Isolate library OOB messages are fixed sized arrays which have the
|
|
// following format:
|
|
// [ OOB dispatch, Isolate library dispatch, <message specific data> ]
|
|
ErrorPtr IsolateMessageHandler::HandleLibMessage(const Array& message) {
|
|
if (message.Length() < 2) return Error::null();
|
|
Zone* zone = T->zone();
|
|
const Object& type = Object::Handle(zone, message.At(1));
|
|
if (!type.IsSmi()) return Error::null();
|
|
const intptr_t msg_type = Smi::Cast(type).Value();
|
|
switch (msg_type) {
|
|
case Isolate::kPauseMsg: {
|
|
// [ OOB, kPauseMsg, pause capability, resume capability ]
|
|
if (message.Length() != 4) return Error::null();
|
|
Object& obj = Object::Handle(zone, message.At(2));
|
|
if (!I->VerifyPauseCapability(obj)) return Error::null();
|
|
obj = message.At(3);
|
|
if (!obj.IsCapability()) return Error::null();
|
|
if (I->AddResumeCapability(Capability::Cast(obj))) {
|
|
increment_paused();
|
|
}
|
|
break;
|
|
}
|
|
case Isolate::kResumeMsg: {
|
|
// [ OOB, kResumeMsg, pause capability, resume capability ]
|
|
if (message.Length() != 4) return Error::null();
|
|
Object& obj = Object::Handle(zone, message.At(2));
|
|
if (!I->VerifyPauseCapability(obj)) return Error::null();
|
|
obj = message.At(3);
|
|
if (!obj.IsCapability()) return Error::null();
|
|
if (I->RemoveResumeCapability(Capability::Cast(obj))) {
|
|
decrement_paused();
|
|
}
|
|
break;
|
|
}
|
|
case Isolate::kPingMsg: {
|
|
// [ OOB, kPingMsg, responsePort, priority, response ]
|
|
if (message.Length() != 5) return Error::null();
|
|
const Object& obj2 = Object::Handle(zone, message.At(2));
|
|
if (!obj2.IsSendPort()) return Error::null();
|
|
const SendPort& send_port = SendPort::Cast(obj2);
|
|
const Object& obj3 = Object::Handle(zone, message.At(3));
|
|
if (!obj3.IsSmi()) return Error::null();
|
|
const intptr_t priority = Smi::Cast(obj3).Value();
|
|
const Object& obj4 = Object::Handle(zone, message.At(4));
|
|
if (!obj4.IsInstance() && !obj4.IsNull()) return Error::null();
|
|
const Instance& response =
|
|
obj4.IsNull() ? Instance::null_instance() : Instance::Cast(obj4);
|
|
if (priority == Isolate::kImmediateAction) {
|
|
PortMap::PostMessage(SerializeMessage(send_port.Id(), response));
|
|
} else {
|
|
ASSERT((priority == Isolate::kBeforeNextEventAction) ||
|
|
(priority == Isolate::kAsEventAction));
|
|
// Update the message so that it will be handled immediately when it
|
|
// is picked up from the message queue the next time.
|
|
message.SetAt(
|
|
0, Smi::Handle(zone, Smi::New(Message::kDelayedIsolateLibOOBMsg)));
|
|
message.SetAt(3,
|
|
Smi::Handle(zone, Smi::New(Isolate::kImmediateAction)));
|
|
this->PostMessage(
|
|
SerializeMessage(Message::kIllegalPort, message),
|
|
priority == Isolate::kBeforeNextEventAction /* at_head */);
|
|
}
|
|
break;
|
|
}
|
|
case Isolate::kKillMsg:
|
|
case Isolate::kInternalKillMsg: {
|
|
// [ OOB, kKillMsg, terminate capability, priority ]
|
|
if (message.Length() != 4) return Error::null();
|
|
Object& obj = Object::Handle(zone, message.At(3));
|
|
if (!obj.IsSmi()) return Error::null();
|
|
const intptr_t priority = Smi::Cast(obj).Value();
|
|
if (priority == Isolate::kImmediateAction) {
|
|
Thread::Current()->StartUnwindError();
|
|
obj = message.At(2);
|
|
if (I->VerifyTerminateCapability(obj)) {
|
|
// We will kill the current isolate by returning an UnwindError.
|
|
if (msg_type == Isolate::kKillMsg) {
|
|
const String& msg = String::Handle(
|
|
String::New("isolate terminated by Isolate.kill"));
|
|
const UnwindError& error =
|
|
UnwindError::Handle(UnwindError::New(msg));
|
|
error.set_is_user_initiated(true);
|
|
return error.ptr();
|
|
} else if (msg_type == Isolate::kInternalKillMsg) {
|
|
const String& msg =
|
|
String::Handle(String::New("isolate terminated by vm"));
|
|
return UnwindError::New(msg);
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
} else {
|
|
return Error::null();
|
|
}
|
|
} else {
|
|
ASSERT((priority == Isolate::kBeforeNextEventAction) ||
|
|
(priority == Isolate::kAsEventAction));
|
|
// Update the message so that it will be handled immediately when it
|
|
// is picked up from the message queue the next time.
|
|
message.SetAt(
|
|
0, Smi::Handle(zone, Smi::New(Message::kDelayedIsolateLibOOBMsg)));
|
|
message.SetAt(3,
|
|
Smi::Handle(zone, Smi::New(Isolate::kImmediateAction)));
|
|
this->PostMessage(
|
|
SerializeMessage(Message::kIllegalPort, message),
|
|
priority == Isolate::kBeforeNextEventAction /* at_head */);
|
|
}
|
|
break;
|
|
}
|
|
case Isolate::kInterruptMsg: {
|
|
// [ OOB, kInterruptMsg, pause capability ]
|
|
if (message.Length() != 3) return Error::null();
|
|
Object& obj = Object::Handle(zone, message.At(2));
|
|
if (!I->VerifyPauseCapability(obj)) return Error::null();
|
|
|
|
#if !defined(PRODUCT)
|
|
// If we are already paused, don't pause again.
|
|
if (I->debugger()->PauseEvent() == nullptr) {
|
|
return I->debugger()->PauseInterrupted();
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
case Isolate::kDrainServiceExtensionsMsg: {
|
|
#ifndef PRODUCT
|
|
Object& obj = Object::Handle(zone, message.At(2));
|
|
if (!obj.IsSmi()) return Error::null();
|
|
const intptr_t priority = Smi::Cast(obj).Value();
|
|
if (priority == Isolate::kImmediateAction) {
|
|
return I->InvokePendingServiceExtensionCalls();
|
|
} else {
|
|
ASSERT((priority == Isolate::kBeforeNextEventAction) ||
|
|
(priority == Isolate::kAsEventAction));
|
|
// Update the message so that it will be handled immediately when it
|
|
// is picked up from the message queue the next time.
|
|
message.SetAt(
|
|
0, Smi::Handle(zone, Smi::New(Message::kDelayedIsolateLibOOBMsg)));
|
|
message.SetAt(2,
|
|
Smi::Handle(zone, Smi::New(Isolate::kImmediateAction)));
|
|
this->PostMessage(
|
|
SerializeMessage(Message::kIllegalPort, message),
|
|
priority == Isolate::kBeforeNextEventAction /* at_head */);
|
|
}
|
|
#else
|
|
UNREACHABLE();
|
|
#endif // !PRODUCT
|
|
break;
|
|
}
|
|
|
|
case Isolate::kAddExitMsg:
|
|
case Isolate::kDelExitMsg:
|
|
case Isolate::kAddErrorMsg:
|
|
case Isolate::kDelErrorMsg: {
|
|
// [ OOB, msg, listener port ]
|
|
if (message.Length() < 3) return Error::null();
|
|
const Object& obj = Object::Handle(zone, message.At(2));
|
|
if (!obj.IsSendPort()) return Error::null();
|
|
const SendPort& listener = SendPort::Cast(obj);
|
|
switch (msg_type) {
|
|
case Isolate::kAddExitMsg: {
|
|
if (message.Length() != 4) return Error::null();
|
|
// [ OOB, msg, listener port, response object ]
|
|
const Object& response = Object::Handle(zone, message.At(3));
|
|
if (!response.IsInstance() && !response.IsNull()) {
|
|
return Error::null();
|
|
}
|
|
I->AddExitListener(listener, response.IsNull()
|
|
? Instance::null_instance()
|
|
: Instance::Cast(response));
|
|
break;
|
|
}
|
|
case Isolate::kDelExitMsg:
|
|
if (message.Length() != 3) return Error::null();
|
|
I->RemoveExitListener(listener);
|
|
break;
|
|
case Isolate::kAddErrorMsg:
|
|
if (message.Length() != 3) return Error::null();
|
|
I->AddErrorListener(listener);
|
|
break;
|
|
case Isolate::kDelErrorMsg:
|
|
if (message.Length() != 3) return Error::null();
|
|
I->RemoveErrorListener(listener);
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
break;
|
|
}
|
|
case Isolate::kErrorFatalMsg: {
|
|
// [ OOB, kErrorFatalMsg, terminate capability, val ]
|
|
if (message.Length() != 4) return Error::null();
|
|
// Check that the terminate capability has been passed correctly.
|
|
Object& obj = Object::Handle(zone, message.At(2));
|
|
if (!I->VerifyTerminateCapability(obj)) return Error::null();
|
|
// Get the value to be set.
|
|
obj = message.At(3);
|
|
if (!obj.IsBool()) return Error::null();
|
|
I->SetErrorsFatal(Bool::Cast(obj).value());
|
|
break;
|
|
}
|
|
case Isolate::kCheckForReload: {
|
|
// [ OOB, kCheckForReload, ignored ]
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
{
|
|
ReloadParticipationScope allow_reload(T);
|
|
T->CheckForSafepoint();
|
|
}
|
|
#else
|
|
UNREACHABLE();
|
|
#endif
|
|
break;
|
|
}
|
|
#if defined(DEBUG)
|
|
// Malformed OOB messages are silently ignored in release builds.
|
|
default:
|
|
FATAL("Unknown OOB message type: %" Pd "\n", msg_type);
|
|
break;
|
|
#endif // defined(DEBUG)
|
|
}
|
|
return Error::null();
|
|
}
|
|
|
|
void IsolateMessageHandler::MessageNotify(Message::Priority priority) {
|
|
if (priority >= Message::kOOBPriority) {
|
|
// Handle out of band messages even if the mutator thread is busy.
|
|
I->ScheduleInterrupts(Thread::kMessageInterrupt);
|
|
}
|
|
Dart_MessageNotifyCallback callback = I->message_notify_callback();
|
|
if (callback != nullptr) {
|
|
// Allow the embedder to handle message notification.
|
|
(*callback)(Api::CastIsolate(I));
|
|
}
|
|
}
|
|
|
|
bool Isolate::HasPendingMessages() {
|
|
return message_handler_->HasMessages() || message_handler_->HasOOBMessages();
|
|
}
|
|
|
|
MessageHandler::MessageStatus IsolateMessageHandler::HandleMessage(
|
|
std::unique_ptr<Message> message) {
|
|
ASSERT(IsCurrentIsolate());
|
|
Thread* thread = Thread::Current();
|
|
StackZone stack_zone(thread);
|
|
Zone* zone = stack_zone.GetZone();
|
|
HandleScope handle_scope(thread);
|
|
#if defined(SUPPORT_TIMELINE)
|
|
TimelineBeginEndScope tbes(
|
|
thread, Timeline::GetIsolateStream(),
|
|
message->IsOOB() ? "HandleOOBMessage" : "HandleMessage");
|
|
tbes.SetNumArguments(1);
|
|
tbes.CopyArgument(0, "isolateName", I->name());
|
|
#endif
|
|
|
|
// Parse the message.
|
|
Object& msg_obj = Object::Handle(zone, ReadMessage(thread, message.get()));
|
|
if (msg_obj.IsError()) {
|
|
// An error occurred while reading the message.
|
|
return ProcessUnhandledException(Error::Cast(msg_obj));
|
|
}
|
|
if (!msg_obj.IsNull() && !msg_obj.IsInstance()) {
|
|
// TODO(turnidge): We need to decide what an isolate does with
|
|
// malformed messages. If they (eventually) come from a remote
|
|
// machine, then it might make sense to drop the message entirely.
|
|
// In the case that the message originated locally, which is
|
|
// always true for now, then this should never occur.
|
|
UNREACHABLE();
|
|
}
|
|
Instance& msg = Instance::Handle(zone);
|
|
msg ^= msg_obj.ptr(); // Can't use Instance::Cast because may be null.
|
|
|
|
MessageStatus status = kOK;
|
|
if (message->IsOOB()) {
|
|
// OOB messages are expected to be fixed length arrays where the first
|
|
// element is a Smi describing the OOB destination. Messages that do not
|
|
// confirm to this layout are silently ignored.
|
|
if (msg.IsArray()) {
|
|
const Array& oob_msg = Array::Cast(msg);
|
|
if (oob_msg.Length() > 0) {
|
|
const Object& oob_tag = Object::Handle(zone, oob_msg.At(0));
|
|
if (oob_tag.IsSmi()) {
|
|
switch (Smi::Cast(oob_tag).Value()) {
|
|
case Message::kServiceOOBMsg: {
|
|
#ifndef PRODUCT
|
|
const Error& error =
|
|
Error::Handle(Service::HandleIsolateMessage(I, oob_msg));
|
|
if (!error.IsNull()) {
|
|
status = ProcessUnhandledException(error);
|
|
}
|
|
#else
|
|
UNREACHABLE();
|
|
#endif
|
|
break;
|
|
}
|
|
case Message::kIsolateLibOOBMsg: {
|
|
const Error& error = Error::Handle(HandleLibMessage(oob_msg));
|
|
if (!error.IsNull()) {
|
|
status = ProcessUnhandledException(error);
|
|
}
|
|
break;
|
|
}
|
|
#if defined(DEBUG)
|
|
// Malformed OOB messages are silently ignored in release builds.
|
|
default: {
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
#endif // defined(DEBUG)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (message->IsFinalizerInvocationRequest()) {
|
|
const Object& msg_handler = Object::Handle(
|
|
zone,
|
|
DartLibraryCalls::HandleFinalizerMessage(FinalizerBase::Cast(msg)));
|
|
if (msg_handler.IsError()) {
|
|
status = ProcessUnhandledException(Error::Cast(msg_handler));
|
|
} else {
|
|
// The handler closure which was used to successfully handle the message.
|
|
}
|
|
} else if (message->dest_port() == Message::kIllegalPort) {
|
|
// Check whether this is a delayed OOB message which needed handling as
|
|
// part of the regular message dispatch. All other messages are dropped on
|
|
// the floor.
|
|
if (msg.IsArray()) {
|
|
const Array& msg_arr = Array::Cast(msg);
|
|
if (msg_arr.Length() > 0) {
|
|
const Object& oob_tag = Object::Handle(zone, msg_arr.At(0));
|
|
if (oob_tag.IsSmi() &&
|
|
(Smi::Cast(oob_tag).Value() == Message::kDelayedIsolateLibOOBMsg)) {
|
|
const Error& error = Error::Handle(HandleLibMessage(msg_arr));
|
|
if (!error.IsNull()) {
|
|
status = ProcessUnhandledException(error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
const Object& msg_handler = Object::Handle(
|
|
zone, DartLibraryCalls::HandleMessage(message->dest_port(), msg));
|
|
if (msg_handler.IsError()) {
|
|
status = ProcessUnhandledException(Error::Cast(msg_handler));
|
|
} else if (msg_handler.IsNull()) {
|
|
// If the port has been closed then the message will be dropped at this
|
|
// point. Make sure to post to the delivery failure port in that case.
|
|
} else {
|
|
// The handler closure which was used to successfully handle the message.
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
#ifndef PRODUCT
|
|
void IsolateMessageHandler::NotifyPauseOnStart() {
|
|
if (Isolate::IsSystemIsolate(I)) {
|
|
return;
|
|
}
|
|
if (Service::debug_stream.enabled() || FLAG_warn_on_pause_with_no_debugger) {
|
|
StartIsolateScope start_isolate(I);
|
|
StackZone zone(T);
|
|
HandleScope handle_scope(T);
|
|
ServiceEvent pause_event(I, ServiceEvent::kPauseStart);
|
|
Service::HandleEvent(&pause_event);
|
|
} else if (FLAG_trace_service) {
|
|
OS::PrintErr("vm-service: Dropping event of type PauseStart (%s)\n",
|
|
I->name());
|
|
}
|
|
}
|
|
|
|
void IsolateMessageHandler::NotifyPauseOnExit() {
|
|
if (Isolate::IsSystemIsolate(I)) {
|
|
return;
|
|
}
|
|
if (Service::debug_stream.enabled() || FLAG_warn_on_pause_with_no_debugger) {
|
|
StartIsolateScope start_isolate(I);
|
|
StackZone zone(T);
|
|
HandleScope handle_scope(T);
|
|
ServiceEvent pause_event(I, ServiceEvent::kPauseExit);
|
|
Service::HandleEvent(&pause_event);
|
|
} else if (FLAG_trace_service) {
|
|
OS::PrintErr("vm-service: Dropping event of type PauseExit (%s)\n",
|
|
I->name());
|
|
}
|
|
}
|
|
#endif // !PRODUCT
|
|
|
|
#if defined(DEBUG)
|
|
void IsolateMessageHandler::CheckAccess() const {
|
|
ASSERT(IsCurrentIsolate());
|
|
}
|
|
#endif
|
|
|
|
bool IsolateMessageHandler::IsCurrentIsolate() const {
|
|
return (I == Isolate::Current());
|
|
}
|
|
|
|
static MessageHandler::MessageStatus StoreError(Thread* thread,
|
|
const Error& error) {
|
|
thread->set_sticky_error(error);
|
|
if (error.IsUnwindError()) {
|
|
const UnwindError& unwind = UnwindError::Cast(error);
|
|
if (!unwind.is_user_initiated()) {
|
|
return MessageHandler::kShutdown;
|
|
}
|
|
}
|
|
return MessageHandler::kError;
|
|
}
|
|
|
|
MessageHandler::MessageStatus IsolateMessageHandler::ProcessUnhandledException(
|
|
const Error& result) {
|
|
if (FLAG_trace_isolates) {
|
|
OS::PrintErr(
|
|
"[!] Unhandled exception in %s:\n"
|
|
" exception: %s\n",
|
|
T->isolate()->name(), result.ToErrorCString());
|
|
}
|
|
|
|
NoReloadScope no_reload(T);
|
|
// Generate the error and stacktrace strings for the error message.
|
|
const char* exception_cstr = nullptr;
|
|
const char* stacktrace_cstr = nullptr;
|
|
if (result.IsUnhandledException()) {
|
|
Zone* zone = T->zone();
|
|
const UnhandledException& uhe = UnhandledException::Cast(result);
|
|
const Instance& exception = Instance::Handle(zone, uhe.exception());
|
|
if (exception.ptr() == IG->object_store()->out_of_memory()) {
|
|
exception_cstr = "Out of Memory"; // Cf. OutOfMemoryError.toString().
|
|
} else if (exception.ptr() == IG->object_store()->stack_overflow()) {
|
|
exception_cstr = "Stack Overflow"; // Cf. StackOverflowError.toString().
|
|
} else {
|
|
const Object& exception_str =
|
|
Object::Handle(zone, DartLibraryCalls::ToString(exception));
|
|
if (!exception_str.IsString()) {
|
|
exception_cstr = exception.ToCString();
|
|
} else {
|
|
exception_cstr = exception_str.ToCString();
|
|
}
|
|
}
|
|
|
|
const Instance& stacktrace = Instance::Handle(zone, uhe.stacktrace());
|
|
stacktrace_cstr = stacktrace.ToCString();
|
|
} else {
|
|
exception_cstr = result.ToErrorCString();
|
|
}
|
|
if (result.IsUnwindError()) {
|
|
// When unwinding we don't notify error listeners and we ignore
|
|
// whether errors are fatal for the current isolate.
|
|
return StoreError(T, result);
|
|
} else {
|
|
bool has_listener =
|
|
I->NotifyErrorListeners(exception_cstr, stacktrace_cstr);
|
|
if (I->ErrorsFatal()) {
|
|
if (has_listener) {
|
|
T->ClearStickyError();
|
|
} else {
|
|
T->set_sticky_error(result);
|
|
}
|
|
#if !defined(PRODUCT)
|
|
// Notify the debugger about specific unhandled exceptions which are
|
|
// withheld when being thrown. Do this after setting the sticky error
|
|
// so the isolate has an error set when paused with the unhandled
|
|
// exception.
|
|
if (result.IsUnhandledException()) {
|
|
const UnhandledException& error = UnhandledException::Cast(result);
|
|
InstancePtr exception = error.exception();
|
|
if ((exception == IG->object_store()->out_of_memory()) ||
|
|
(exception == IG->object_store()->stack_overflow())) {
|
|
// We didn't notify the debugger when the stack was full. Do it now.
|
|
I->debugger()->PauseException(Instance::Handle(exception));
|
|
}
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
return kError;
|
|
}
|
|
}
|
|
return kOK;
|
|
}
|
|
|
|
void IsolateGroup::FlagsInitialize(Dart_IsolateFlags* api_flags) {
|
|
api_flags->version = DART_FLAGS_CURRENT_VERSION;
|
|
#define INIT_FROM_FLAG(when, name, bitname, isolate_flag, flag) \
|
|
api_flags->isolate_flag = flag;
|
|
BOOL_ISOLATE_GROUP_FLAG_LIST(INIT_FROM_FLAG)
|
|
#undef INIT_FROM_FLAG
|
|
api_flags->is_service_isolate = false;
|
|
api_flags->is_kernel_isolate = false;
|
|
api_flags->null_safety = true;
|
|
}
|
|
|
|
void IsolateGroup::FlagsCopyTo(Dart_IsolateFlags* api_flags) {
|
|
api_flags->version = DART_FLAGS_CURRENT_VERSION;
|
|
#define INIT_FROM_FIELD(when, name, bitname, isolate_flag, flag) \
|
|
api_flags->isolate_flag = name();
|
|
BOOL_ISOLATE_GROUP_FLAG_LIST(INIT_FROM_FIELD)
|
|
#undef INIT_FROM_FIELD
|
|
api_flags->is_service_isolate = false;
|
|
api_flags->is_kernel_isolate = false;
|
|
api_flags->null_safety = true;
|
|
}
|
|
|
|
void IsolateGroup::FlagsCopyFrom(const Dart_IsolateFlags& api_flags) {
|
|
#if defined(DART_PRECOMPILER)
|
|
#define FLAG_FOR_PRECOMPILER(action) action
|
|
#else
|
|
#define FLAG_FOR_PRECOMPILER(action)
|
|
#endif
|
|
|
|
#if !defined(PRODUCT)
|
|
#define FLAG_FOR_NONPRODUCT(action) action
|
|
#else
|
|
#define FLAG_FOR_NONPRODUCT(action)
|
|
#endif
|
|
|
|
#define FLAG_FOR_PRODUCT(action) action
|
|
|
|
#define SET_FROM_FLAG(when, name, bitname, isolate_flag, flag) \
|
|
FLAG_FOR_##when(isolate_group_flags_ = bitname##Bit::update( \
|
|
api_flags.isolate_flag, isolate_group_flags_));
|
|
|
|
BOOL_ISOLATE_GROUP_FLAG_LIST(SET_FROM_FLAG)
|
|
#undef FLAG_FOR_NONPRODUCT
|
|
#undef FLAG_FOR_PRECOMPILER
|
|
#undef FLAG_FOR_PRODUCT
|
|
#undef SET_FROM_FLAG
|
|
}
|
|
|
|
void Isolate::FlagsInitialize(Dart_IsolateFlags* api_flags) {
|
|
IsolateGroup::FlagsInitialize(api_flags);
|
|
|
|
api_flags->version = DART_FLAGS_CURRENT_VERSION;
|
|
#define INIT_FROM_FLAG(when, name, bitname, isolate_flag, flag) \
|
|
api_flags->isolate_flag = flag;
|
|
BOOL_ISOLATE_FLAG_LIST(INIT_FROM_FLAG)
|
|
#undef INIT_FROM_FLAG
|
|
api_flags->is_service_isolate = false;
|
|
api_flags->is_kernel_isolate = false;
|
|
api_flags->null_safety = true;
|
|
}
|
|
|
|
void Isolate::FlagsCopyTo(Dart_IsolateFlags* api_flags) const {
|
|
group()->FlagsCopyTo(api_flags);
|
|
|
|
api_flags->version = DART_FLAGS_CURRENT_VERSION;
|
|
#define INIT_FROM_FIELD(when, name, bitname, isolate_flag, flag) \
|
|
api_flags->isolate_flag = name();
|
|
BOOL_ISOLATE_FLAG_LIST(INIT_FROM_FIELD)
|
|
#undef INIT_FROM_FIELD
|
|
api_flags->is_service_isolate = false;
|
|
api_flags->is_kernel_isolate = false;
|
|
api_flags->null_safety = true;
|
|
}
|
|
|
|
void Isolate::FlagsCopyFrom(const Dart_IsolateFlags& api_flags) {
|
|
#if defined(DART_PRECOMPILER)
|
|
#define FLAG_FOR_PRECOMPILER(action) action
|
|
#else
|
|
#define FLAG_FOR_PRECOMPILER(action)
|
|
#endif
|
|
|
|
#if !defined(PRODUCT)
|
|
#define FLAG_FOR_NONPRODUCT(action) action
|
|
#else
|
|
#define FLAG_FOR_NONPRODUCT(action)
|
|
#endif
|
|
|
|
#define FLAG_FOR_PRODUCT(action) action
|
|
|
|
#define SET_FROM_FLAG(when, name, bitname, isolate_flag, flag) \
|
|
FLAG_FOR_##when(isolate_flags_ = bitname##Bit::update( \
|
|
api_flags.isolate_flag, isolate_flags_));
|
|
|
|
BOOL_ISOLATE_FLAG_LIST(SET_FROM_FLAG)
|
|
#undef FLAG_FOR_NONPRODUCT
|
|
#undef FLAG_FOR_PRECOMPILER
|
|
#undef FLAG_FOR_PRODUCT
|
|
#undef SET_FROM_FLAG
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
// static
|
|
void BaseIsolate::AssertCurrent(BaseIsolate* isolate) {
|
|
ASSERT(isolate == Isolate::Current());
|
|
}
|
|
#endif // defined(DEBUG)
|
|
|
|
#if defined(DEBUG)
|
|
#define REUSABLE_HANDLE_SCOPE_INIT(object) \
|
|
reusable_##object##_handle_scope_active_(false),
|
|
#else
|
|
#define REUSABLE_HANDLE_SCOPE_INIT(object)
|
|
#endif // defined(DEBUG)
|
|
|
|
#define REUSABLE_HANDLE_INITIALIZERS(object) object##_handle_(nullptr),
|
|
|
|
class LibraryPrefixMapTraits {
|
|
public:
|
|
static bool ReportStats() { return false; }
|
|
static const char* Name() { return "LibraryPrefixMapTraits"; }
|
|
|
|
static bool IsMatch(const Object& a, const Object& b) {
|
|
if (!a.IsLibraryPrefix() || !b.IsLibraryPrefix()) {
|
|
return false;
|
|
}
|
|
return a.ptr() == b.ptr();
|
|
}
|
|
|
|
static uword Hash(const Object& obj) {
|
|
auto& prefix = LibraryPrefix::Cast(obj);
|
|
return String::Hash(prefix.name());
|
|
}
|
|
};
|
|
|
|
// TODO(srdjan): Some Isolate monitors can be shared. Replace their usage with
|
|
// that shared monitor.
|
|
Isolate::Isolate(IsolateGroup* isolate_group,
|
|
const Dart_IsolateFlags& api_flags)
|
|
: BaseIsolate(),
|
|
current_tag_(UserTag::null()),
|
|
default_tag_(UserTag::null()),
|
|
field_table_(new FieldTable(/*isolate=*/this)),
|
|
finalizers_(GrowableObjectArray::null()),
|
|
isolate_group_(isolate_group),
|
|
isolate_object_store_(new IsolateObjectStore()),
|
|
isolate_flags_(0),
|
|
#if !defined(PRODUCT)
|
|
last_resume_timestamp_(OS::GetCurrentTimeMillis()),
|
|
vm_tag_counters_(),
|
|
pending_service_extension_calls_(GrowableObjectArray::null()),
|
|
registered_service_extension_handlers_(GrowableObjectArray::null()),
|
|
#define ISOLATE_METRIC_CONSTRUCTORS(type, variable, name, unit) \
|
|
metric_##variable##_(),
|
|
ISOLATE_METRIC_LIST(ISOLATE_METRIC_CONSTRUCTORS)
|
|
#undef ISOLATE_METRIC_CONSTRUCTORS
|
|
#endif // !defined(PRODUCT)
|
|
start_time_micros_(OS::GetCurrentMonotonicMicros()),
|
|
message_notify_callback_(nullptr),
|
|
on_shutdown_callback_(Isolate::ShutdownCallback()),
|
|
on_cleanup_callback_(Isolate::CleanupCallback()),
|
|
random_(),
|
|
mutex_(NOT_IN_PRODUCT("Isolate::mutex_")),
|
|
tag_table_(GrowableObjectArray::null()),
|
|
sticky_error_(Error::null()),
|
|
spawn_count_monitor_(),
|
|
handler_info_cache_(),
|
|
catch_entry_moves_cache_(),
|
|
wake_pause_event_handler_count_(0),
|
|
loaded_prefixes_set_storage_(nullptr) {
|
|
FlagsCopyFrom(api_flags);
|
|
SetErrorsFatal(true);
|
|
// TODO(asiva): A Thread is not available here, need to figure out
|
|
// how the vm_tag (kEmbedderTagId) can be set, these tags need to
|
|
// move to the OSThread structure.
|
|
set_user_tag(UserTags::kDefaultUserTag);
|
|
}
|
|
|
|
#undef REUSABLE_HANDLE_SCOPE_INIT
|
|
#undef REUSABLE_HANDLE_INITIALIZERS
|
|
|
|
Isolate::~Isolate() {
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
// TODO(32796): Re-enable assertion.
|
|
// RELEASE_ASSERT(program_reload_context_ == nullptr);
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
#if !defined(PRODUCT)
|
|
delete debugger_;
|
|
debugger_ = nullptr;
|
|
delete object_id_ring_;
|
|
object_id_ring_ = nullptr;
|
|
delete pause_loop_monitor_;
|
|
pause_loop_monitor_ = nullptr;
|
|
#endif // !defined(PRODUCT)
|
|
|
|
free(name_);
|
|
delete field_table_;
|
|
#if defined(USING_SIMULATOR)
|
|
delete simulator_;
|
|
#endif
|
|
delete message_handler_;
|
|
message_handler_ =
|
|
nullptr; // Fail fast if we send messages to a dead isolate.
|
|
ASSERT(deopt_context_ ==
|
|
nullptr); // No deopt in progress when isolate deleted.
|
|
ASSERT(spawn_count_ == 0);
|
|
|
|
// The [Thread] object should've been released on the last
|
|
// `Thread::ExitIsolate()` call.
|
|
ASSERT(scheduled_mutator_thread_ == nullptr);
|
|
ASSERT(mutator_thread_ == nullptr);
|
|
}
|
|
|
|
void Isolate::InitVM() {
|
|
create_group_callback_ = nullptr;
|
|
initialize_callback_ = nullptr;
|
|
shutdown_callback_ = nullptr;
|
|
cleanup_callback_ = nullptr;
|
|
cleanup_group_callback_ = nullptr;
|
|
register_kernel_blob_callback_ = nullptr;
|
|
unregister_kernel_blob_callback_ = nullptr;
|
|
if (isolate_creation_monitor_ == nullptr) {
|
|
isolate_creation_monitor_ = new Monitor();
|
|
}
|
|
ASSERT(isolate_creation_monitor_ != nullptr);
|
|
EnableIsolateCreation();
|
|
}
|
|
|
|
Isolate* Isolate::InitIsolate(const char* name_prefix,
|
|
IsolateGroup* isolate_group,
|
|
const Dart_IsolateFlags& api_flags,
|
|
bool is_vm_isolate) {
|
|
Isolate* result = new Isolate(isolate_group, api_flags);
|
|
result->set_is_vm_isolate(is_vm_isolate);
|
|
result->BuildName(name_prefix);
|
|
if (!is_vm_isolate) {
|
|
// vm isolate object store is initialized later, after null instance
|
|
// is created (in Dart::Init).
|
|
// Non-vm isolates need to have isolate object store initialized is that
|
|
// exit_listeners have to be null-initialized as they will be used if
|
|
// we fail to create isolate below, have to do low level shutdown.
|
|
ASSERT(result->group()->object_store() != nullptr);
|
|
result->isolate_object_store()->Init();
|
|
}
|
|
|
|
ASSERT(result != nullptr);
|
|
|
|
#if !defined(PRODUCT)
|
|
// Initialize metrics.
|
|
#define ISOLATE_METRIC_INIT(type, variable, name, unit) \
|
|
result->metric_##variable##_.InitInstance(result, name, nullptr, \
|
|
Metric::unit);
|
|
ISOLATE_METRIC_LIST(ISOLATE_METRIC_INIT);
|
|
#undef ISOLATE_METRIC_INIT
|
|
#endif // !defined(PRODUCT)
|
|
|
|
// First we ensure we enter the isolate. This will ensure we're participating
|
|
// in any safepointing requests from this point on. Other threads requesting a
|
|
// safepoint operation will therefore wait until we've stopped.
|
|
//
|
|
// Though the [result] isolate is still in a state where no memory has been
|
|
// allocated, which means it's safe to GC the isolate group until here.
|
|
Thread::EnterIsolate(result);
|
|
|
|
// Setup the isolate message handler.
|
|
result->message_handler_ = new IsolateMessageHandler(result);
|
|
|
|
result->set_main_port(PortMap::CreatePort(result->message_handler()));
|
|
#if defined(DEBUG)
|
|
// Verify that we are never reusing a live origin id.
|
|
VerifyOriginId id_verifier(result->main_port());
|
|
Isolate::VisitIsolates(&id_verifier);
|
|
#endif
|
|
result->set_origin_id(result->main_port());
|
|
|
|
// Keep capability IDs less than 2^53 so web clients of the service
|
|
// protocol can process it properly.
|
|
//
|
|
// See https://github.com/dart-lang/sdk/issues/53081.
|
|
result->set_pause_capability(result->random()->NextJSInt());
|
|
result->set_terminate_capability(result->random()->NextJSInt());
|
|
|
|
#if !defined(PRODUCT)
|
|
result->debugger_ = new Debugger(result);
|
|
#endif
|
|
|
|
// Now we register the isolate in the group. From this point on any GC would
|
|
// traverse the isolate roots (before this point, the roots are only pointing
|
|
// to vm-isolate objects, e.g. null)
|
|
isolate_group->RegisterIsolate(result);
|
|
|
|
if (api_flags.is_service_isolate) {
|
|
ASSERT(!ServiceIsolate::Exists());
|
|
ServiceIsolate::SetServiceIsolate(result);
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
} else if (api_flags.is_kernel_isolate) {
|
|
ASSERT(!KernelIsolate::Exists());
|
|
KernelIsolate::SetKernelIsolate(result);
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
}
|
|
|
|
if (FLAG_trace_isolates) {
|
|
if (name_prefix == nullptr || strcmp(name_prefix, "vm-isolate") != 0) {
|
|
OS::PrintErr(
|
|
"[+] Starting isolate:\n"
|
|
"\tisolate: %s\n",
|
|
result->name());
|
|
}
|
|
}
|
|
|
|
// Add to isolate list. Shutdown and delete the isolate on failure.
|
|
if (!TryMarkIsolateReady(result)) {
|
|
result->LowLevelShutdown();
|
|
Isolate::LowLevelCleanup(result);
|
|
return nullptr;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Thread* Isolate::mutator_thread() const {
|
|
ASSERT(thread_registry() != nullptr);
|
|
return mutator_thread_;
|
|
}
|
|
|
|
ObjectPtr IsolateGroup::CallTagHandler(Dart_LibraryTag tag,
|
|
const Object& arg1,
|
|
const Object& arg2) {
|
|
Thread* thread = Thread::Current();
|
|
Api::Scope api_scope(thread);
|
|
Dart_Handle api_arg1 = Api::NewHandle(thread, arg1.ptr());
|
|
Dart_Handle api_arg2 = Api::NewHandle(thread, arg2.ptr());
|
|
Dart_Handle api_result;
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
ASSERT(HasTagHandler());
|
|
api_result = library_tag_handler()(tag, api_arg1, api_arg2);
|
|
}
|
|
return Api::UnwrapHandle(api_result);
|
|
}
|
|
|
|
ObjectPtr Isolate::CallDeferredLoadHandler(intptr_t id) {
|
|
Thread* thread = Thread::Current();
|
|
Api::Scope api_scope(thread);
|
|
Dart_Handle api_result;
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
RELEASE_ASSERT(HasDeferredLoadHandler());
|
|
api_result = group()->deferred_load_handler()(id);
|
|
}
|
|
return Api::UnwrapHandle(api_result);
|
|
}
|
|
|
|
void IsolateGroup::SetupImagePage(const uint8_t* image_buffer,
|
|
bool is_executable) {
|
|
Image image(image_buffer);
|
|
heap()->SetupImagePage(image.object_start(), image.object_size(),
|
|
is_executable);
|
|
}
|
|
|
|
void IsolateGroup::ScheduleInterrupts(uword interrupt_bits) {
|
|
SafepointReadRwLocker ml(Thread::Current(), isolates_lock_.get());
|
|
for (Isolate* isolate : isolates_) {
|
|
isolate->ScheduleInterrupts(interrupt_bits);
|
|
}
|
|
}
|
|
|
|
void Isolate::ScheduleInterrupts(uword interrupt_bits) {
|
|
// We take the threads lock here to ensure that the mutator thread does not
|
|
// exit the isolate while we are trying to schedule interrupts on it.
|
|
MonitorLocker ml(group()->thread_registry()->threads_lock());
|
|
Thread* mthread = mutator_thread();
|
|
if (mthread != nullptr) {
|
|
mthread->ScheduleInterrupts(interrupt_bits);
|
|
}
|
|
}
|
|
|
|
void Isolate::set_name(const char* name) {
|
|
free(name_);
|
|
name_ = Utils::StrDup(name);
|
|
}
|
|
|
|
int64_t IsolateGroup::UptimeMicros() const {
|
|
return OS::GetCurrentMonotonicMicros() - start_time_micros_;
|
|
}
|
|
|
|
int64_t Isolate::UptimeMicros() const {
|
|
return OS::GetCurrentMonotonicMicros() - start_time_micros_;
|
|
}
|
|
|
|
Dart_Port Isolate::origin_id() {
|
|
MutexLocker ml(&origin_id_mutex_);
|
|
return origin_id_;
|
|
}
|
|
|
|
void Isolate::set_origin_id(Dart_Port id) {
|
|
MutexLocker ml(&origin_id_mutex_);
|
|
ASSERT((id == main_port_ && origin_id_ == 0) || (origin_id_ == main_port_));
|
|
origin_id_ = id;
|
|
}
|
|
|
|
void Isolate::set_finalizers(const GrowableObjectArray& value) {
|
|
finalizers_ = value.ptr();
|
|
}
|
|
|
|
bool Isolate::IsPaused() const {
|
|
#if defined(PRODUCT)
|
|
return false;
|
|
#else
|
|
return (debugger_ != nullptr) && (debugger_->PauseEvent() != nullptr);
|
|
#endif // !defined(PRODUCT)
|
|
}
|
|
|
|
ErrorPtr Isolate::PausePostRequest() {
|
|
#if !defined(PRODUCT)
|
|
if (debugger_ == nullptr) {
|
|
return Error::null();
|
|
}
|
|
ASSERT(!IsPaused());
|
|
const Error& error = Error::Handle(debugger_->PausePostRequest());
|
|
if (!error.IsNull()) {
|
|
if (Thread::Current()->top_exit_frame_info() == 0) {
|
|
return error.ptr();
|
|
} else {
|
|
Exceptions::PropagateError(error);
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
#endif
|
|
return Error::null();
|
|
}
|
|
|
|
void Isolate::BuildName(const char* name_prefix) {
|
|
ASSERT(name_ == nullptr);
|
|
if (name_prefix == nullptr) {
|
|
name_ = OS::SCreate(nullptr, "isolate-%" Pd64 "", main_port());
|
|
} else {
|
|
name_ = Utils::StrDup(name_prefix);
|
|
}
|
|
}
|
|
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
bool IsolateGroup::CanReload() {
|
|
// We only call this method on the mutator thread. Normally the caller is
|
|
// inside of the "reloadSources" service OOB message handler. Though
|
|
// we also use it in the slow path of StackOverflowCheck in the artificial
|
|
// --hot-reload-test-mode like flags.
|
|
//
|
|
// During reload itself we don't process OOB messages and don't execute Dart
|
|
// code, so the caller should implicitly have a guarantee we're not reloading
|
|
// already.
|
|
RELEASE_ASSERT(!Thread::Current()->OwnsReloadSafepoint());
|
|
|
|
// We only allow reload to take place from the point on where the first
|
|
// isolate within an isolate group has setup it's root library. From that
|
|
// point on it's safe to perform hot-reload.
|
|
auto thread = Thread::Current();
|
|
if (object_store()->root_library() == Library::null()) {
|
|
return false;
|
|
}
|
|
|
|
// We only care about the current thread's [NoReloadScope]. If we're inside
|
|
// one we cannot reload right now. Though if another isolate's mutator
|
|
// thread is inside such a scope, the multi-isolate reload will simply wait
|
|
// until it's out of that scope again.
|
|
if (thread->no_reload_scope_depth_ != 0) {
|
|
return false;
|
|
}
|
|
|
|
return !IsolateGroup::IsSystemIsolateGroup(this) &&
|
|
OSThread::Current()->HasStackHeadroom(64 * KB);
|
|
}
|
|
|
|
bool IsolateGroup::ReloadSources(JSONStream* js,
|
|
bool force_reload,
|
|
const char* root_script_url,
|
|
const char* packages_url,
|
|
bool dont_delete_reload_context) {
|
|
ASSERT(!IsReloading());
|
|
|
|
// Ensure all isolates inside the isolate group are paused at a place where we
|
|
// can safely do a reload.
|
|
RELOAD_OPERATION_SCOPE(Thread::Current());
|
|
|
|
auto class_table = IsolateGroup::Current()->class_table();
|
|
std::shared_ptr<IsolateGroupReloadContext> group_reload_context(
|
|
new IsolateGroupReloadContext(this, class_table, js));
|
|
group_reload_context_ = group_reload_context;
|
|
|
|
SetHasAttemptedReload(true);
|
|
program_reload_context_ =
|
|
new ProgramReloadContext(group_reload_context_, this);
|
|
const bool success =
|
|
group_reload_context_->Reload(force_reload, root_script_url, packages_url,
|
|
/*kernel_buffer=*/nullptr,
|
|
/*kernel_buffer_size=*/0);
|
|
if (!dont_delete_reload_context) {
|
|
DeleteReloadContext();
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool IsolateGroup::ReloadKernel(JSONStream* js,
|
|
bool force_reload,
|
|
const uint8_t* kernel_buffer,
|
|
intptr_t kernel_buffer_size,
|
|
bool dont_delete_reload_context) {
|
|
ASSERT(!IsReloading());
|
|
|
|
// Ensure all isolates inside the isolate group are paused at a place where we
|
|
// can safely do a reload.
|
|
RELOAD_OPERATION_SCOPE(Thread::Current());
|
|
|
|
auto class_table = IsolateGroup::Current()->class_table();
|
|
std::shared_ptr<IsolateGroupReloadContext> group_reload_context(
|
|
new IsolateGroupReloadContext(this, class_table, js));
|
|
group_reload_context_ = group_reload_context;
|
|
|
|
SetHasAttemptedReload(true);
|
|
program_reload_context_ =
|
|
new ProgramReloadContext(group_reload_context_, this);
|
|
const bool success = group_reload_context_->Reload(
|
|
force_reload,
|
|
/*root_script_url=*/nullptr,
|
|
/*packages_url=*/nullptr, kernel_buffer, kernel_buffer_size);
|
|
if (!dont_delete_reload_context) {
|
|
DeleteReloadContext();
|
|
}
|
|
return success;
|
|
}
|
|
|
|
void IsolateGroup::DeleteReloadContext() {
|
|
GcSafepointOperationScope safepoint_scope(Thread::Current());
|
|
group_reload_context_.reset();
|
|
|
|
delete program_reload_context_;
|
|
program_reload_context_ = nullptr;
|
|
}
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
const char* Isolate::MakeRunnable() {
|
|
MutexLocker ml(&mutex_);
|
|
// Check if we are in a valid state to make the isolate runnable.
|
|
if (is_runnable() == true) {
|
|
return "Isolate is already runnable";
|
|
}
|
|
if (group()->object_store()->root_library() == Library::null()) {
|
|
return "The embedder has to ensure there is a root library (e.g. by "
|
|
"calling Dart_LoadScriptFromKernel ).";
|
|
}
|
|
MakeRunnableLocked();
|
|
return nullptr;
|
|
}
|
|
|
|
void Isolate::MakeRunnableLocked() {
|
|
ASSERT(mutex_.IsOwnedByCurrentThread());
|
|
ASSERT(!is_runnable());
|
|
ASSERT(group()->object_store()->root_library() != Library::null());
|
|
|
|
// Set the isolate as runnable and if we are being spawned schedule
|
|
// isolate on thread pool for execution.
|
|
set_is_runnable(true);
|
|
#ifndef PRODUCT
|
|
if (!Isolate::IsSystemIsolate(this)) {
|
|
if (FLAG_pause_isolates_on_unhandled_exceptions) {
|
|
debugger()->SetExceptionPauseInfo(kPauseOnUnhandledExceptions);
|
|
}
|
|
}
|
|
#endif // !PRODUCT
|
|
#if defined(SUPPORT_TIMELINE)
|
|
TimelineStream* stream = Timeline::GetIsolateStream();
|
|
ASSERT(stream != nullptr);
|
|
TimelineEvent* event = stream->StartEvent();
|
|
if (event != nullptr) {
|
|
event->Instant("Runnable");
|
|
event->Complete();
|
|
}
|
|
#endif
|
|
#ifndef PRODUCT
|
|
if (!Isolate::IsSystemIsolate(this) && Service::isolate_stream.enabled()) {
|
|
ServiceEvent runnableEvent(this, ServiceEvent::kIsolateRunnable);
|
|
Service::HandleEvent(&runnableEvent, /* enter_safepoint */ false);
|
|
}
|
|
GetRunnableLatencyMetric()->set_value(UptimeMicros());
|
|
#endif // !PRODUCT
|
|
}
|
|
|
|
bool Isolate::VerifyPauseCapability(const Object& capability) const {
|
|
return !capability.IsNull() && capability.IsCapability() &&
|
|
(pause_capability() == Capability::Cast(capability).Id());
|
|
}
|
|
|
|
bool Isolate::VerifyTerminateCapability(const Object& capability) const {
|
|
return !capability.IsNull() && capability.IsCapability() &&
|
|
(terminate_capability() == Capability::Cast(capability).Id());
|
|
}
|
|
|
|
bool Isolate::AddResumeCapability(const Capability& capability) {
|
|
// Ensure a limit for the number of resume capabilities remembered.
|
|
const intptr_t kMaxResumeCapabilities =
|
|
compiler::target::kSmiMax / (6 * kWordSize);
|
|
|
|
const GrowableObjectArray& caps = GrowableObjectArray::Handle(
|
|
current_zone(), isolate_object_store()->resume_capabilities());
|
|
Capability& current = Capability::Handle(current_zone());
|
|
intptr_t insertion_index = -1;
|
|
for (intptr_t i = 0; i < caps.Length(); i++) {
|
|
current ^= caps.At(i);
|
|
if (current.IsNull()) {
|
|
if (insertion_index < 0) {
|
|
insertion_index = i;
|
|
}
|
|
} else if (current.Id() == capability.Id()) {
|
|
return false;
|
|
}
|
|
}
|
|
if (insertion_index < 0) {
|
|
if (caps.Length() >= kMaxResumeCapabilities) {
|
|
// Cannot grow the array of resume capabilities beyond its max. Additional
|
|
// pause requests are ignored. In practice will never happen as we will
|
|
// run out of memory beforehand.
|
|
return false;
|
|
}
|
|
caps.Add(capability);
|
|
} else {
|
|
caps.SetAt(insertion_index, capability);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Isolate::RemoveResumeCapability(const Capability& capability) {
|
|
const GrowableObjectArray& caps = GrowableObjectArray::Handle(
|
|
current_zone(), isolate_object_store()->resume_capabilities());
|
|
Capability& current = Capability::Handle(current_zone());
|
|
for (intptr_t i = 0; i < caps.Length(); i++) {
|
|
current ^= caps.At(i);
|
|
if (!current.IsNull() && (current.Id() == capability.Id())) {
|
|
// Remove the matching capability from the list.
|
|
current = Capability::null();
|
|
caps.SetAt(i, current);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// TODO(iposva): Remove duplicated code and start using some hash based
|
|
// structure instead of these linear lookups.
|
|
void Isolate::AddExitListener(const SendPort& listener,
|
|
const Instance& response) {
|
|
// Ensure a limit for the number of listeners remembered.
|
|
const intptr_t kMaxListeners = compiler::target::kSmiMax / (12 * kWordSize);
|
|
|
|
const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
|
|
current_zone(), isolate_object_store()->exit_listeners());
|
|
SendPort& current = SendPort::Handle(current_zone());
|
|
intptr_t insertion_index = -1;
|
|
for (intptr_t i = 0; i < listeners.Length(); i += 2) {
|
|
current ^= listeners.At(i);
|
|
if (current.IsNull()) {
|
|
if (insertion_index < 0) {
|
|
insertion_index = i;
|
|
}
|
|
} else if (current.Id() == listener.Id()) {
|
|
listeners.SetAt(i + 1, response);
|
|
return;
|
|
}
|
|
}
|
|
if (insertion_index < 0) {
|
|
if (listeners.Length() >= kMaxListeners) {
|
|
// Cannot grow the array of listeners beyond its max. Additional
|
|
// listeners are ignored. In practice will never happen as we will
|
|
// run out of memory beforehand.
|
|
return;
|
|
}
|
|
listeners.Add(listener);
|
|
listeners.Add(response);
|
|
} else {
|
|
listeners.SetAt(insertion_index, listener);
|
|
listeners.SetAt(insertion_index + 1, response);
|
|
}
|
|
}
|
|
|
|
void Isolate::RemoveExitListener(const SendPort& listener) {
|
|
const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
|
|
current_zone(), isolate_object_store()->exit_listeners());
|
|
SendPort& current = SendPort::Handle(current_zone());
|
|
for (intptr_t i = 0; i < listeners.Length(); i += 2) {
|
|
current ^= listeners.At(i);
|
|
if (!current.IsNull() && (current.Id() == listener.Id())) {
|
|
// Remove the matching listener from the list.
|
|
current = SendPort::null();
|
|
listeners.SetAt(i, current);
|
|
listeners.SetAt(i + 1, Object::null_instance());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Isolate::NotifyExitListeners() {
|
|
const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
|
|
current_zone(), isolate_object_store()->exit_listeners());
|
|
if (listeners.IsNull()) return;
|
|
|
|
SendPort& listener = SendPort::Handle(current_zone());
|
|
Instance& response = Instance::Handle(current_zone());
|
|
for (intptr_t i = 0; i < listeners.Length(); i += 2) {
|
|
listener ^= listeners.At(i);
|
|
if (!listener.IsNull()) {
|
|
Dart_Port port_id = listener.Id();
|
|
response ^= listeners.At(i + 1);
|
|
PortMap::PostMessage(SerializeMessage(port_id, response));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Isolate::AddErrorListener(const SendPort& listener) {
|
|
// Ensure a limit for the number of listeners remembered.
|
|
const intptr_t kMaxListeners = compiler::target::kSmiMax / (6 * kWordSize);
|
|
|
|
const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
|
|
current_zone(), isolate_object_store()->error_listeners());
|
|
SendPort& current = SendPort::Handle(current_zone());
|
|
intptr_t insertion_index = -1;
|
|
for (intptr_t i = 0; i < listeners.Length(); i++) {
|
|
current ^= listeners.At(i);
|
|
if (current.IsNull()) {
|
|
if (insertion_index < 0) {
|
|
insertion_index = i;
|
|
}
|
|
} else if (current.Id() == listener.Id()) {
|
|
return;
|
|
}
|
|
}
|
|
if (insertion_index < 0) {
|
|
if (listeners.Length() >= kMaxListeners) {
|
|
// Cannot grow the array of listeners beyond its max. Additional
|
|
// listeners are ignored. In practice will never happen as we will
|
|
// run out of memory beforehand.
|
|
return;
|
|
}
|
|
listeners.Add(listener);
|
|
} else {
|
|
listeners.SetAt(insertion_index, listener);
|
|
}
|
|
}
|
|
|
|
void Isolate::RemoveErrorListener(const SendPort& listener) {
|
|
const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
|
|
current_zone(), isolate_object_store()->error_listeners());
|
|
SendPort& current = SendPort::Handle(current_zone());
|
|
for (intptr_t i = 0; i < listeners.Length(); i++) {
|
|
current ^= listeners.At(i);
|
|
if (!current.IsNull() && (current.Id() == listener.Id())) {
|
|
// Remove the matching listener from the list.
|
|
current = SendPort::null();
|
|
listeners.SetAt(i, current);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Isolate::NotifyErrorListeners(const char* message,
|
|
const char* stacktrace) {
|
|
const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
|
|
current_zone(), isolate_object_store()->error_listeners());
|
|
if (listeners.IsNull()) return false;
|
|
|
|
Dart_CObject arr;
|
|
Dart_CObject* arr_values[2];
|
|
arr.type = Dart_CObject_kArray;
|
|
arr.value.as_array.length = 2;
|
|
arr.value.as_array.values = arr_values;
|
|
Dart_CObject msg;
|
|
msg.type = Dart_CObject_kString;
|
|
msg.value.as_string = const_cast<char*>(message);
|
|
arr_values[0] = &msg;
|
|
Dart_CObject stack;
|
|
if (stacktrace == nullptr) {
|
|
stack.type = Dart_CObject_kNull;
|
|
} else {
|
|
stack.type = Dart_CObject_kString;
|
|
stack.value.as_string = const_cast<char*>(stacktrace);
|
|
}
|
|
arr_values[1] = &stack;
|
|
|
|
SendPort& listener = SendPort::Handle(current_zone());
|
|
bool was_somebody_notified = false;
|
|
for (intptr_t i = 0; i < listeners.Length(); i++) {
|
|
listener ^= listeners.At(i);
|
|
if (!listener.IsNull()) {
|
|
Dart_Port port_id = listener.Id();
|
|
PortMap::PostMessage(SerializeMessage(current_zone(), port_id, &arr));
|
|
was_somebody_notified = true;
|
|
}
|
|
}
|
|
return was_somebody_notified;
|
|
}
|
|
|
|
static void ShutdownIsolate(uword parameter) {
|
|
Dart_EnterIsolate(reinterpret_cast<Dart_Isolate>(parameter));
|
|
Dart_ShutdownIsolate();
|
|
}
|
|
|
|
void Isolate::SetStickyError(ErrorPtr sticky_error) {
|
|
ASSERT(
|
|
((sticky_error_ == Error::null()) || (sticky_error == Error::null())) &&
|
|
(sticky_error != sticky_error_));
|
|
sticky_error_ = sticky_error;
|
|
}
|
|
|
|
void Isolate::Run() {
|
|
message_handler()->Run(group()->thread_pool(), nullptr, ShutdownIsolate,
|
|
reinterpret_cast<uword>(this));
|
|
}
|
|
|
|
MessageHandler* Isolate::message_handler() const {
|
|
return message_handler_;
|
|
}
|
|
|
|
void Isolate::RunAndCleanupFinalizersOnShutdown() {
|
|
if (finalizers_ == GrowableObjectArray::null()) return;
|
|
|
|
// Ensure we have a zone and handle scope so that we can call VM functions,
|
|
// but we no longer allocate new heap objects.
|
|
Thread* thread = Thread::Current();
|
|
StackZone stack_zone(thread);
|
|
HandleScope handle_scope(thread);
|
|
NoSafepointScope no_safepoint_scope;
|
|
|
|
// Set live finalizers isolate to null, before deleting the message handler.
|
|
const auto& finalizers =
|
|
GrowableObjectArray::Handle(stack_zone.GetZone(), finalizers_);
|
|
if (!finalizers.IsNull()) {
|
|
const intptr_t num_finalizers = finalizers.Length();
|
|
auto& weak_reference = WeakReference::Handle(stack_zone.GetZone());
|
|
auto& finalizer = FinalizerBase::Handle(stack_zone.GetZone());
|
|
auto& current_entry = FinalizerEntry::Handle(stack_zone.GetZone());
|
|
auto& all_entries = Set::Handle(stack_zone.GetZone());
|
|
for (int i = 0; i < num_finalizers; i++) {
|
|
weak_reference ^= finalizers.At(i);
|
|
finalizer ^= weak_reference.target();
|
|
if (!finalizer.IsNull()) {
|
|
if (finalizer.isolate() == this) {
|
|
if (FLAG_trace_finalizers) {
|
|
THR_Print("Isolate %p Setting finalizer %p isolate to null\n", this,
|
|
finalizer.ptr()->untag());
|
|
}
|
|
// Finalizer was not sent to another isolate with send and exit.
|
|
finalizer.set_isolate(nullptr);
|
|
} else {
|
|
// TODO(http://dartbug.com/47777): Send and exit support.
|
|
UNREACHABLE();
|
|
}
|
|
|
|
if (finalizer.IsNativeFinalizer()) {
|
|
// Immediately call native callback.
|
|
const auto& native_finalizer = NativeFinalizer::Cast(finalizer);
|
|
all_entries = finalizer.all_entries();
|
|
Set::Iterator iterator(all_entries);
|
|
while (iterator.MoveNext()) {
|
|
current_entry ^= iterator.CurrentKey();
|
|
native_finalizer.RunCallback(current_entry, "Isolate shutdown");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Isolate::LowLevelShutdown() {
|
|
// Ensure we have a zone and handle scope so that we can call VM functions,
|
|
// but we no longer allocate new heap objects.
|
|
Thread* thread = Thread::Current();
|
|
StackZone stack_zone(thread);
|
|
HandleScope handle_scope(thread);
|
|
NoSafepointScope no_safepoint_scope;
|
|
|
|
// Notify exit listeners that this isolate is shutting down.
|
|
if (group()->object_store() != nullptr) {
|
|
const Error& error = Error::Handle(thread->sticky_error());
|
|
if (error.IsNull() || !error.IsUnwindError() ||
|
|
UnwindError::Cast(error).is_user_initiated()) {
|
|
NotifyExitListeners();
|
|
}
|
|
}
|
|
|
|
// Close all the ports owned by this isolate.
|
|
PortMap::ClosePorts(message_handler());
|
|
|
|
// Fail fast if anybody tries to post any more messages to this isolate.
|
|
delete message_handler_;
|
|
message_handler_ = nullptr;
|
|
|
|
// Clean up any synchronous FFI callbacks registered with this isolate. Skip
|
|
// if this isolate never registered any.
|
|
if (ffi_callback_list_head_ != nullptr) {
|
|
FfiCallbackMetadata::Instance()->DeleteAllCallbacks(
|
|
&ffi_callback_list_head_);
|
|
}
|
|
|
|
#if !defined(PRODUCT)
|
|
if (FLAG_dump_megamorphic_stats) {
|
|
MegamorphicCacheTable::PrintSizes(thread);
|
|
}
|
|
if (FLAG_dump_symbol_stats) {
|
|
Symbols::DumpStats(group());
|
|
}
|
|
if (FLAG_trace_isolates) {
|
|
group()->heap()->PrintSizes();
|
|
OS::PrintErr(
|
|
"[-] Stopping isolate:\n"
|
|
"\tisolate: %s\n",
|
|
name());
|
|
}
|
|
if (FLAG_print_metrics) {
|
|
LogBlock lb;
|
|
OS::PrintErr("Printing metrics for %s\n", name());
|
|
#define ISOLATE_GROUP_METRIC_PRINT(type, variable, name, unit) \
|
|
OS::PrintErr("%s\n", isolate_group_->Get##variable##Metric()->ToString());
|
|
ISOLATE_GROUP_METRIC_LIST(ISOLATE_GROUP_METRIC_PRINT)
|
|
#undef ISOLATE_GROUP_METRIC_PRINT
|
|
#define ISOLATE_METRIC_PRINT(type, variable, name, unit) \
|
|
OS::PrintErr("%s\n", metric_##variable##_.ToString());
|
|
ISOLATE_METRIC_LIST(ISOLATE_METRIC_PRINT)
|
|
#undef ISOLATE_METRIC_PRINT
|
|
OS::PrintErr("\n");
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
}
|
|
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
void IsolateGroup::MaybeIncreaseReloadEveryNStackOverflowChecks() {
|
|
if (FLAG_reload_every_back_off) {
|
|
if (reload_every_n_stack_overflow_checks_ < 5000) {
|
|
reload_every_n_stack_overflow_checks_ += 99;
|
|
} else {
|
|
const auto old_value = reload_every_n_stack_overflow_checks_;
|
|
reload_every_n_stack_overflow_checks_ = old_value * old_value;
|
|
}
|
|
// Cap the value.
|
|
if (reload_every_n_stack_overflow_checks_ > 1000000) {
|
|
reload_every_n_stack_overflow_checks_ = 1000000;
|
|
}
|
|
}
|
|
}
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
void Isolate::set_forward_table_new(WeakTable* table) {
|
|
std::unique_ptr<WeakTable> value(table);
|
|
forward_table_new_ = std::move(value);
|
|
}
|
|
void Isolate::set_forward_table_old(WeakTable* table) {
|
|
std::unique_ptr<WeakTable> value(table);
|
|
forward_table_old_ = std::move(value);
|
|
}
|
|
|
|
void Isolate::Shutdown() {
|
|
Thread* thread = Thread::Current();
|
|
ASSERT(this == thread->isolate());
|
|
|
|
// Don't allow anymore dart code to execution on this isolate.
|
|
thread->ClearStackLimit();
|
|
|
|
{
|
|
StackZone zone(thread);
|
|
ServiceIsolate::SendIsolateShutdownMessage();
|
|
#if !defined(PRODUCT)
|
|
HandleScope handle_scope(thread);
|
|
debugger()->Shutdown();
|
|
Profiler::IsolateShutdown(thread);
|
|
#endif
|
|
}
|
|
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
if (FLAG_check_reloaded && is_runnable() && !Isolate::IsSystemIsolate(this)) {
|
|
if (!group()->HasAttemptedReload()) {
|
|
FATAL(
|
|
"Isolate did not reload before exiting and "
|
|
"--check-reloaded is enabled.\n");
|
|
}
|
|
}
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
// Then, proceed with low-level teardown.
|
|
Isolate::UnMarkIsolateReady(this);
|
|
|
|
// Ensure native finalizers are run before isolate has shutdown message is
|
|
// sent. This way users can rely on the exit message that an isolate will not
|
|
// run any Dart code anymore _and_ will not run any native finalizers anymore.
|
|
RunAndCleanupFinalizersOnShutdown();
|
|
|
|
// Post message before LowLevelShutdown that sends onExit message.
|
|
// This ensures that exit message comes last.
|
|
if (bequest_ != nullptr) {
|
|
auto beneficiary = bequest_->beneficiary();
|
|
auto handle = bequest_->TakeHandle();
|
|
PortMap::PostMessage(
|
|
Message::New(beneficiary, handle, Message::kNormalPriority));
|
|
bequest_.reset();
|
|
}
|
|
|
|
LowLevelShutdown();
|
|
|
|
// Now we can unregister from the thread, invoke cleanup callback, delete the
|
|
// isolate (and possibly the isolate group).
|
|
Isolate::LowLevelCleanup(this);
|
|
}
|
|
|
|
void Isolate::LowLevelCleanup(Isolate* isolate) {
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
if (isolate->is_kernel_isolate()) {
|
|
KernelIsolate::SetKernelIsolate(nullptr);
|
|
}
|
|
#endif
|
|
if (isolate->is_service_isolate()) {
|
|
ServiceIsolate::SetServiceIsolate(nullptr);
|
|
}
|
|
|
|
// Cache these two fields, since they are no longer available after the
|
|
// `delete isolate` further down.
|
|
IsolateGroup* isolate_group = isolate->isolate_group_;
|
|
Dart_IsolateCleanupCallback cleanup = isolate->on_cleanup_callback();
|
|
auto callback_data = isolate->init_callback_data_;
|
|
|
|
// From this point on the isolate is no longer visited by GC (which is ok,
|
|
// since we're just going to delete it anyway).
|
|
isolate_group->UnregisterIsolate(isolate);
|
|
|
|
// From this point on the isolate doesn't participate in safepointing
|
|
// requests anymore.
|
|
ASSERT(!Thread::Current()->HasActiveState());
|
|
Thread::ExitIsolate(/*isolate_shutdown=*/true);
|
|
|
|
// Now it's safe to delete the isolate.
|
|
delete isolate;
|
|
|
|
// Run isolate specific cleanup function for all non "vm-isolate's.
|
|
const bool is_vm_isolate = Dart::vm_isolate() == isolate;
|
|
if (!is_vm_isolate) {
|
|
if (cleanup != nullptr) {
|
|
cleanup(isolate_group->embedder_data(), callback_data);
|
|
}
|
|
}
|
|
|
|
const bool shutdown_group = isolate_group->UnregisterIsolateDecrementCount();
|
|
if (shutdown_group) {
|
|
KernelIsolate::NotifyAboutIsolateGroupShutdown(isolate_group);
|
|
|
|
if (!is_vm_isolate) {
|
|
Thread::EnterIsolateGroupAsHelper(isolate_group, Thread::kUnknownTask,
|
|
/*bypass_safepoint=*/false);
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
BackgroundCompiler::Stop(isolate_group);
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
// Finalize any weak persistent handles with a non-null referent with
|
|
// isolate group still being available.
|
|
FinalizeWeakPersistentHandlesVisitor visitor(isolate_group);
|
|
isolate_group->api_state()->VisitWeakHandlesUnlocked(&visitor);
|
|
|
|
Thread::ExitIsolateGroupAsHelper(/*bypass_safepoint=*/false);
|
|
}
|
|
|
|
// The "vm-isolate" does not have a thread pool.
|
|
ASSERT(is_vm_isolate == (isolate_group->thread_pool() == nullptr));
|
|
if (is_vm_isolate ||
|
|
!isolate_group->thread_pool()->CurrentThreadIsWorker()) {
|
|
isolate_group->Shutdown();
|
|
} else {
|
|
class ShutdownGroupTask : public ThreadPool::Task {
|
|
public:
|
|
explicit ShutdownGroupTask(IsolateGroup* isolate_group)
|
|
: isolate_group_(isolate_group) {}
|
|
|
|
virtual void Run() { isolate_group_->Shutdown(); }
|
|
|
|
private:
|
|
IsolateGroup* isolate_group_;
|
|
};
|
|
// The current thread is running on the isolate group's thread pool.
|
|
// So we cannot safely delete the isolate group (and it's pool).
|
|
// Instead we will destroy the isolate group on the VM-global pool.
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] : Scheduling shutdown on VM pool %s\n",
|
|
Dart::UptimeMillis(), isolate_group->source()->name);
|
|
}
|
|
Dart::thread_pool()->Run<ShutdownGroupTask>(isolate_group);
|
|
}
|
|
} else {
|
|
// TODO(dartbug.com/36097): An isolate just died. A significant amount of
|
|
// memory might have become unreachable. We should evaluate how to best
|
|
// inform the GC about this situation.
|
|
}
|
|
}
|
|
|
|
Dart_InitializeIsolateCallback Isolate::initialize_callback_ = nullptr;
|
|
Dart_IsolateGroupCreateCallback Isolate::create_group_callback_ = nullptr;
|
|
Dart_IsolateShutdownCallback Isolate::shutdown_callback_ = nullptr;
|
|
Dart_IsolateCleanupCallback Isolate::cleanup_callback_ = nullptr;
|
|
Dart_IsolateGroupCleanupCallback Isolate::cleanup_group_callback_ = nullptr;
|
|
Dart_RegisterKernelBlobCallback Isolate::register_kernel_blob_callback_ =
|
|
nullptr;
|
|
Dart_UnregisterKernelBlobCallback Isolate::unregister_kernel_blob_callback_ =
|
|
nullptr;
|
|
|
|
Random* IsolateGroup::isolate_group_random_ = nullptr;
|
|
Monitor* Isolate::isolate_creation_monitor_ = nullptr;
|
|
bool Isolate::creation_enabled_ = false;
|
|
|
|
RwLock* IsolateGroup::isolate_groups_rwlock_ = nullptr;
|
|
IntrusiveDList<IsolateGroup>* IsolateGroup::isolate_groups_ = nullptr;
|
|
|
|
void Isolate::VisitObjectPointers(ObjectPointerVisitor* visitor,
|
|
ValidationPolicy validate_frames) {
|
|
ASSERT(visitor != nullptr);
|
|
|
|
// Visit objects in the field table.
|
|
// N.B.: The heap snapshot writer requires visiting the field table first, so
|
|
// that the pointer visitation order aligns with order of field name metadata.
|
|
if (!visitor->trace_values_through_fields()) {
|
|
field_table()->VisitObjectPointers(visitor);
|
|
}
|
|
|
|
// Visit objects in the isolate object store.
|
|
if (isolate_object_store() != nullptr) {
|
|
isolate_object_store()->VisitObjectPointers(visitor);
|
|
}
|
|
|
|
visitor->clear_gc_root_type();
|
|
// Visit the objects directly referenced from the isolate structure.
|
|
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(¤t_tag_));
|
|
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&default_tag_));
|
|
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&tag_table_));
|
|
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&sticky_error_));
|
|
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&finalizers_));
|
|
#if !defined(PRODUCT)
|
|
visitor->VisitPointer(
|
|
reinterpret_cast<ObjectPtr*>(&pending_service_extension_calls_));
|
|
visitor->VisitPointer(
|
|
reinterpret_cast<ObjectPtr*>(®istered_service_extension_handlers_));
|
|
#endif // !defined(PRODUCT)
|
|
|
|
#if !defined(PRODUCT)
|
|
// Visit objects in the debugger.
|
|
if (debugger() != nullptr) {
|
|
debugger()->VisitObjectPointers(visitor);
|
|
}
|
|
if (is_service_isolate()) {
|
|
ServiceIsolate::VisitObjectPointers(visitor);
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
// Visit objects that are being used for deoptimization.
|
|
if (deopt_context() != nullptr) {
|
|
deopt_context()->VisitObjectPointers(visitor);
|
|
}
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
visitor->VisitPointer(
|
|
reinterpret_cast<ObjectPtr*>(&loaded_prefixes_set_storage_));
|
|
|
|
if (pointers_to_verify_at_exit_.length() != 0) {
|
|
visitor->VisitPointers(&pointers_to_verify_at_exit_[0],
|
|
pointers_to_verify_at_exit_.length());
|
|
}
|
|
}
|
|
|
|
void Isolate::VisitStackPointers(ObjectPointerVisitor* visitor,
|
|
ValidationPolicy validate_frames) {
|
|
if (mutator_thread_ != nullptr) {
|
|
mutator_thread_->VisitObjectPointers(visitor, validate_frames);
|
|
}
|
|
}
|
|
|
|
void IsolateGroup::ReleaseStoreBuffers() {
|
|
thread_registry()->ReleaseStoreBuffers();
|
|
}
|
|
|
|
void IsolateGroup::FlushMarkingStacks() {
|
|
thread_registry()->FlushMarkingStacks();
|
|
}
|
|
|
|
void Isolate::RememberLiveTemporaries() {
|
|
if (mutator_thread_ != nullptr) {
|
|
mutator_thread_->RememberLiveTemporaries();
|
|
}
|
|
}
|
|
|
|
void Isolate::DeferredMarkLiveTemporaries() {
|
|
if (mutator_thread_ != nullptr) {
|
|
mutator_thread_->DeferredMarkLiveTemporaries();
|
|
}
|
|
}
|
|
|
|
void Isolate::init_loaded_prefixes_set_storage() {
|
|
ASSERT(loaded_prefixes_set_storage_ == nullptr);
|
|
loaded_prefixes_set_storage_ =
|
|
HashTables::New<UnorderedHashSet<LibraryPrefixMapTraits> >(4);
|
|
}
|
|
|
|
bool Isolate::IsPrefixLoaded(const LibraryPrefix& prefix) const {
|
|
UnorderedHashSet<LibraryPrefixMapTraits> loaded_prefixes_set(
|
|
loaded_prefixes_set_storage_);
|
|
bool result = loaded_prefixes_set.GetOrNull(prefix) != Object::null();
|
|
loaded_prefixes_set.Release();
|
|
return result;
|
|
}
|
|
|
|
void Isolate::SetPrefixIsLoaded(const LibraryPrefix& prefix) {
|
|
UnorderedHashSet<LibraryPrefixMapTraits> loaded_prefixes_set(
|
|
loaded_prefixes_set_storage_);
|
|
loaded_prefixes_set.InsertOrGet(prefix);
|
|
loaded_prefixes_set_storage_ = loaded_prefixes_set.Release().ptr();
|
|
}
|
|
|
|
void IsolateGroup::EnableIncrementalBarrier(
|
|
MarkingStack* old_marking_stack,
|
|
MarkingStack* new_marking_stack,
|
|
MarkingStack* deferred_marking_stack) {
|
|
ASSERT(old_marking_stack_ == nullptr);
|
|
old_marking_stack_ = old_marking_stack;
|
|
ASSERT(new_marking_stack_ == nullptr);
|
|
new_marking_stack_ = new_marking_stack;
|
|
ASSERT(deferred_marking_stack_ == nullptr);
|
|
deferred_marking_stack_ = deferred_marking_stack;
|
|
thread_registry()->AcquireMarkingStacks();
|
|
ASSERT(Thread::Current()->is_marking());
|
|
}
|
|
|
|
void IsolateGroup::DisableIncrementalBarrier() {
|
|
thread_registry()->ReleaseMarkingStacks();
|
|
ASSERT(old_marking_stack_ != nullptr);
|
|
old_marking_stack_ = nullptr;
|
|
ASSERT(new_marking_stack_ != nullptr);
|
|
new_marking_stack_ = nullptr;
|
|
ASSERT(deferred_marking_stack_ != nullptr);
|
|
deferred_marking_stack_ = nullptr;
|
|
}
|
|
|
|
void IsolateGroup::ForEachIsolate(
|
|
std::function<void(Isolate* isolate)> function,
|
|
bool at_safepoint) {
|
|
auto thread = Thread::Current();
|
|
if (at_safepoint) {
|
|
ASSERT(thread->OwnsSafepoint() ||
|
|
(thread->task_kind() == Thread::kMutatorTask) ||
|
|
(thread->task_kind() == Thread::kMarkerTask) ||
|
|
(thread->task_kind() == Thread::kCompactorTask) ||
|
|
(thread->task_kind() == Thread::kScavengerTask) ||
|
|
(thread->task_kind() == Thread::kIncrementalCompactorTask));
|
|
for (Isolate* isolate : isolates_) {
|
|
function(isolate);
|
|
}
|
|
return;
|
|
}
|
|
if (thread != nullptr && thread->OwnsSafepoint()) {
|
|
for (Isolate* isolate : isolates_) {
|
|
function(isolate);
|
|
}
|
|
return;
|
|
}
|
|
SafepointReadRwLocker ml(thread, isolates_lock_.get());
|
|
for (Isolate* isolate : isolates_) {
|
|
function(isolate);
|
|
}
|
|
}
|
|
|
|
Isolate* IsolateGroup::FirstIsolate() const {
|
|
SafepointReadRwLocker ml(Thread::Current(), isolates_lock_.get());
|
|
return FirstIsolateLocked();
|
|
}
|
|
|
|
Isolate* IsolateGroup::FirstIsolateLocked() const {
|
|
return isolates_.IsEmpty() ? nullptr : isolates_.First();
|
|
}
|
|
|
|
void IsolateGroup::RunWithStoppedMutatorsCallable(
|
|
Callable* single_current_mutator,
|
|
Callable* otherwise,
|
|
bool use_force_growth_in_otherwise) {
|
|
auto thread = Thread::Current();
|
|
StoppedMutatorsScope stopped_mutators_scope(thread);
|
|
|
|
if (thread->OwnsSafepoint()) {
|
|
RELEASE_ASSERT(thread->OwnsSafepoint());
|
|
single_current_mutator->Call();
|
|
return;
|
|
}
|
|
|
|
{
|
|
SafepointReadRwLocker ml(thread, isolates_lock_.get());
|
|
if (thread->IsDartMutatorThread() && ContainsOnlyOneIsolate()) {
|
|
single_current_mutator->Call();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// We use the more strict safepoint operation scope here (which ensures that
|
|
// all other threads, including auxiliary threads are at a safepoint), even
|
|
// though we only need to ensure that the mutator threads are stopped.
|
|
if (use_force_growth_in_otherwise) {
|
|
ForceGrowthSafepointOperationScope safepoint_scope(
|
|
thread, SafepointLevel::kGCAndDeopt);
|
|
otherwise->Call();
|
|
} else {
|
|
DeoptSafepointOperationScope safepoint_scope(thread);
|
|
otherwise->Call();
|
|
}
|
|
}
|
|
|
|
void IsolateGroup::VisitObjectPointers(ObjectPointerVisitor* visitor,
|
|
ValidationPolicy validate_frames) {
|
|
VisitSharedPointers(visitor);
|
|
for (Isolate* isolate : isolates_) {
|
|
isolate->VisitObjectPointers(visitor, validate_frames);
|
|
}
|
|
VisitStackPointers(visitor, validate_frames);
|
|
}
|
|
|
|
void IsolateGroup::VisitSharedPointers(ObjectPointerVisitor* visitor) {
|
|
// Visit objects in the class table.
|
|
class_table()->VisitObjectPointers(visitor);
|
|
if (heap_walk_class_table() != class_table()) {
|
|
heap_walk_class_table()->VisitObjectPointers(visitor);
|
|
}
|
|
api_state()->VisitObjectPointersUnlocked(visitor);
|
|
// Visit objects in the object store.
|
|
if (object_store() != nullptr) {
|
|
object_store()->VisitObjectPointers(visitor);
|
|
}
|
|
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&saved_unlinked_calls_));
|
|
initial_field_table()->VisitObjectPointers(visitor);
|
|
shared_field_table()->VisitObjectPointers(visitor);
|
|
|
|
// Visit the boxed_field_list_.
|
|
// 'boxed_field_list_' access via mutator and background compilation threads
|
|
// is guarded with a monitor. This means that we can visit it only
|
|
// when at safepoint or the field_list_mutex_ lock has been taken.
|
|
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&boxed_field_list_));
|
|
|
|
NOT_IN_PRECOMPILED(background_compiler()->VisitPointers(visitor));
|
|
|
|
#if !defined(PRODUCT)
|
|
if (debugger() != nullptr) {
|
|
debugger()->VisitObjectPointers(visitor);
|
|
}
|
|
#endif
|
|
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
// Visit objects that are being used for isolate reload.
|
|
if (program_reload_context() != nullptr) {
|
|
program_reload_context()->VisitObjectPointers(visitor);
|
|
program_reload_context()->group_reload_context()->VisitObjectPointers(
|
|
visitor);
|
|
}
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
if (source()->loaded_blobs_ != nullptr) {
|
|
visitor->VisitPointer(
|
|
reinterpret_cast<ObjectPtr*>(&(source()->loaded_blobs_)));
|
|
}
|
|
|
|
if (become() != nullptr) {
|
|
become()->VisitObjectPointers(visitor);
|
|
}
|
|
}
|
|
|
|
void IsolateGroup::VisitStackPointers(ObjectPointerVisitor* visitor,
|
|
ValidationPolicy validate_frames) {
|
|
visitor->set_gc_root_type("stack");
|
|
|
|
// Visit objects in all threads (e.g. Dart stack, handles in zones), except
|
|
// for the mutator threads themselves.
|
|
thread_registry()->VisitObjectPointers(this, visitor, validate_frames);
|
|
|
|
for (Isolate* isolate : isolates_) {
|
|
// Visit mutator thread, even if the isolate isn't entered/scheduled
|
|
// (there might be live API handles to visit).
|
|
isolate->VisitStackPointers(visitor, validate_frames);
|
|
}
|
|
|
|
visitor->clear_gc_root_type();
|
|
}
|
|
|
|
void IsolateGroup::VisitObjectIdRingPointers(ObjectPointerVisitor* visitor) {
|
|
#if !defined(PRODUCT)
|
|
for (Isolate* isolate : isolates_) {
|
|
ObjectIdRing* ring = isolate->object_id_ring();
|
|
if (ring != nullptr) {
|
|
ring->VisitPointers(visitor);
|
|
}
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
}
|
|
|
|
void IsolateGroup::VisitWeakPersistentHandles(HandleVisitor* visitor) {
|
|
api_state()->VisitWeakHandlesUnlocked(visitor);
|
|
}
|
|
|
|
void IsolateGroup::DeferredMarkLiveTemporaries() {
|
|
ForEachIsolate(
|
|
[&](Isolate* isolate) { isolate->DeferredMarkLiveTemporaries(); },
|
|
/*at_safepoint=*/true);
|
|
}
|
|
|
|
void IsolateGroup::RememberLiveTemporaries() {
|
|
ForEachIsolate([&](Isolate* isolate) { isolate->RememberLiveTemporaries(); },
|
|
/*at_safepoint=*/true);
|
|
}
|
|
|
|
#if !defined(PRODUCT)
|
|
ObjectIdRing* Isolate::EnsureObjectIdRing() {
|
|
if (object_id_ring_ == nullptr) {
|
|
object_id_ring_ = new ObjectIdRing();
|
|
}
|
|
return object_id_ring_;
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
|
|
#ifndef PRODUCT
|
|
static const char* ExceptionPauseInfoToServiceEnum(Dart_ExceptionPauseInfo pi) {
|
|
switch (pi) {
|
|
case kPauseOnAllExceptions:
|
|
return "All";
|
|
case kNoPauseOnExceptions:
|
|
return "None";
|
|
case kPauseOnUnhandledExceptions:
|
|
return "Unhandled";
|
|
default:
|
|
UNIMPLEMENTED();
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static ServiceEvent IsolatePauseEvent(Isolate* isolate) {
|
|
if (!isolate->is_runnable()) {
|
|
// Isolate is not yet runnable.
|
|
ASSERT((isolate->debugger() == nullptr) ||
|
|
(isolate->debugger()->PauseEvent() == nullptr));
|
|
return ServiceEvent(isolate, ServiceEvent::kNone);
|
|
} else if (isolate->message_handler()->should_pause_on_start()) {
|
|
if (isolate->message_handler()->is_paused_on_start()) {
|
|
ASSERT((isolate->debugger() == nullptr) ||
|
|
(isolate->debugger()->PauseEvent() == nullptr));
|
|
return ServiceEvent(isolate, ServiceEvent::kPauseStart);
|
|
} else {
|
|
// Isolate is runnable but not paused on start.
|
|
// Some service clients get confused if they see:
|
|
// NotRunnable -> Runnable -> PausedAtStart
|
|
// Treat Runnable+ShouldPauseOnStart as NotRunnable so they see:
|
|
// NonRunnable -> PausedAtStart
|
|
// The should_pause_on_start flag is set to false after resume.
|
|
ASSERT((isolate->debugger() == nullptr) ||
|
|
(isolate->debugger()->PauseEvent() == nullptr));
|
|
return ServiceEvent(isolate, ServiceEvent::kNone);
|
|
}
|
|
} else if (isolate->message_handler()->is_paused_on_exit() &&
|
|
((isolate->debugger() == nullptr) ||
|
|
(isolate->debugger()->PauseEvent() == nullptr))) {
|
|
return ServiceEvent(isolate, ServiceEvent::kPauseExit);
|
|
} else if ((isolate->debugger() != nullptr) &&
|
|
(isolate->debugger()->PauseEvent() != nullptr) &&
|
|
!isolate->ResumeRequest()) {
|
|
return *(isolate->debugger()->PauseEvent());
|
|
} else {
|
|
ServiceEvent pause_event(isolate, ServiceEvent::kResume);
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
if (isolate->debugger() != nullptr) {
|
|
// TODO(turnidge): Don't compute a full stack trace.
|
|
DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
|
|
if (stack->Length() > 0) {
|
|
pause_event.set_top_frame(stack->FrameAt(0));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return pause_event;
|
|
}
|
|
}
|
|
|
|
void Isolate::PrintJSON(JSONStream* stream, bool ref) {
|
|
JSONObject jsobj(stream);
|
|
jsobj.AddProperty("type", (ref ? "@Isolate" : "Isolate"));
|
|
jsobj.AddServiceId(ISOLATE_SERVICE_ID_FORMAT_STRING,
|
|
static_cast<int64_t>(main_port()));
|
|
|
|
jsobj.AddProperty("name", name());
|
|
jsobj.AddPropertyF("number", "%" Pd64 "", static_cast<int64_t>(main_port()));
|
|
jsobj.AddProperty("isSystemIsolate", is_system_isolate());
|
|
jsobj.AddPropertyF("isolateGroupId", ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING,
|
|
group()->id());
|
|
if (ref) {
|
|
return;
|
|
}
|
|
jsobj.AddPropertyF("_originNumber", "%" Pd64 "",
|
|
static_cast<int64_t>(origin_id()));
|
|
int64_t uptime_millis = UptimeMicros() / kMicrosecondsPerMillisecond;
|
|
int64_t start_time = OS::GetCurrentTimeMillis() - uptime_millis;
|
|
jsobj.AddPropertyTimeMillis("startTime", start_time);
|
|
{
|
|
JSONObject jsheap(&jsobj, "_heaps");
|
|
group()->heap()->PrintToJSONObject(Heap::kNew, &jsheap);
|
|
group()->heap()->PrintToJSONObject(Heap::kOld, &jsheap);
|
|
}
|
|
|
|
{
|
|
// Stringification macros
|
|
// See https://gcc.gnu.org/onlinedocs/gcc-4.8.5/cpp/Stringification.html
|
|
#define TO_STRING(s) STR(s)
|
|
#define STR(s) #s
|
|
|
|
#define ADD_ISOLATE_FLAGS(when, name, bitname, isolate_flag_name, flag_name) \
|
|
{ \
|
|
JSONObject jsflag(&jsflags); \
|
|
jsflag.AddProperty("name", TO_STRING(name)); \
|
|
jsflag.AddProperty("valueAsString", name() ? "true" : "false"); \
|
|
}
|
|
JSONArray jsflags(&jsobj, "isolateFlags");
|
|
BOOL_ISOLATE_FLAG_LIST(ADD_ISOLATE_FLAGS)
|
|
#undef ADD_ISOLATE_FLAGS
|
|
#undef TO_STRING
|
|
#undef STR
|
|
}
|
|
|
|
jsobj.AddProperty("runnable", is_runnable());
|
|
jsobj.AddProperty("livePorts", open_ports_keepalive_);
|
|
jsobj.AddProperty("pauseOnExit", message_handler()->should_pause_on_exit());
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
jsobj.AddProperty("_isReloading", group()->IsReloading());
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
ServiceEvent pause_event = IsolatePauseEvent(this);
|
|
jsobj.AddProperty("pauseEvent", &pause_event);
|
|
|
|
const Library& lib = Library::Handle(group()->object_store()->root_library());
|
|
if (!lib.IsNull()) {
|
|
jsobj.AddProperty("rootLib", lib);
|
|
}
|
|
|
|
if (FLAG_profiler) {
|
|
JSONObject tagCounters(&jsobj, "_tagCounters");
|
|
vm_tag_counters()->PrintToJSONObject(&tagCounters);
|
|
}
|
|
if (Thread::Current()->sticky_error() != Object::null()) {
|
|
Error& error = Error::Handle(Thread::Current()->sticky_error());
|
|
ASSERT(!error.IsNull());
|
|
jsobj.AddProperty("error", error, false);
|
|
} else if (sticky_error() != Object::null()) {
|
|
Error& error = Error::Handle(sticky_error());
|
|
ASSERT(!error.IsNull());
|
|
jsobj.AddProperty("error", error, false);
|
|
}
|
|
|
|
{
|
|
const GrowableObjectArray& libs =
|
|
GrowableObjectArray::Handle(group()->object_store()->libraries());
|
|
intptr_t num_libs = libs.Length();
|
|
Library& lib = Library::Handle();
|
|
|
|
JSONArray lib_array(&jsobj, "libraries");
|
|
for (intptr_t i = 0; i < num_libs; i++) {
|
|
lib ^= libs.At(i);
|
|
ASSERT(!lib.IsNull());
|
|
lib_array.AddValue(lib);
|
|
}
|
|
}
|
|
|
|
{
|
|
JSONArray breakpoints(&jsobj, "breakpoints");
|
|
if (debugger() != nullptr) {
|
|
debugger()->PrintBreakpointsToJSONArray(&breakpoints);
|
|
}
|
|
}
|
|
|
|
Dart_ExceptionPauseInfo pause_info = (debugger() != nullptr)
|
|
? debugger()->GetExceptionPauseInfo()
|
|
: kNoPauseOnExceptions;
|
|
jsobj.AddProperty("exceptionPauseMode",
|
|
ExceptionPauseInfoToServiceEnum(pause_info));
|
|
|
|
if (debugger() != nullptr) {
|
|
JSONObject settings(&jsobj, "_debuggerSettings");
|
|
debugger()->PrintSettingsToJSONObject(&settings);
|
|
}
|
|
|
|
{
|
|
GrowableObjectArray& handlers =
|
|
GrowableObjectArray::Handle(registered_service_extension_handlers());
|
|
if (!handlers.IsNull()) {
|
|
JSONArray extensions(&jsobj, "extensionRPCs");
|
|
String& handler_name = String::Handle();
|
|
for (intptr_t i = 0; i < handlers.Length(); i += kRegisteredEntrySize) {
|
|
handler_name ^= handlers.At(i + kRegisteredNameIndex);
|
|
extensions.AddValue(handler_name.ToCString());
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
JSONObject isolate_group(&jsobj, "isolate_group");
|
|
group()->PrintToJSONObject(&isolate_group, /*ref=*/true);
|
|
}
|
|
}
|
|
|
|
void Isolate::PrintMemoryUsageJSON(JSONStream* stream) {
|
|
group()->heap()->PrintMemoryUsageJSON(stream);
|
|
}
|
|
|
|
void Isolate::PrintPauseEventJSON(JSONStream* stream) {
|
|
IsolatePauseEvent(this).PrintJSON(stream);
|
|
}
|
|
|
|
#endif
|
|
|
|
void Isolate::set_tag_table(const GrowableObjectArray& value) {
|
|
tag_table_ = value.ptr();
|
|
}
|
|
|
|
void Isolate::set_current_tag(const UserTag& tag) {
|
|
uword user_tag = tag.tag();
|
|
ASSERT(user_tag < kUwordMax);
|
|
set_user_tag(user_tag);
|
|
current_tag_ = tag.ptr();
|
|
}
|
|
|
|
void Isolate::set_default_tag(const UserTag& tag) {
|
|
default_tag_ = tag.ptr();
|
|
}
|
|
|
|
ErrorPtr Isolate::StealStickyError() {
|
|
NoSafepointScope no_safepoint;
|
|
ErrorPtr return_value = sticky_error_;
|
|
sticky_error_ = Error::null();
|
|
return return_value;
|
|
}
|
|
|
|
#if !defined(PRODUCT)
|
|
void Isolate::set_pending_service_extension_calls(
|
|
const GrowableObjectArray& value) {
|
|
pending_service_extension_calls_ = value.ptr();
|
|
}
|
|
|
|
void Isolate::set_registered_service_extension_handlers(
|
|
const GrowableObjectArray& value) {
|
|
registered_service_extension_handlers_ = value.ptr();
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
|
|
#ifndef PRODUCT
|
|
ErrorPtr Isolate::InvokePendingServiceExtensionCalls() {
|
|
GrowableObjectArray& calls =
|
|
GrowableObjectArray::Handle(GetAndClearPendingServiceExtensionCalls());
|
|
if (calls.IsNull()) {
|
|
return Error::null();
|
|
}
|
|
// Grab run function.
|
|
const Library& developer_lib = Library::Handle(Library::DeveloperLibrary());
|
|
ASSERT(!developer_lib.IsNull());
|
|
const Function& run_extension = Function::Handle(
|
|
developer_lib.LookupFunctionAllowPrivate(Symbols::_runExtension()));
|
|
ASSERT(!run_extension.IsNull());
|
|
|
|
const Array& arguments =
|
|
Array::Handle(Array::New(kPendingEntrySize + 1, Heap::kNew));
|
|
Object& result = Object::Handle();
|
|
String& method_name = String::Handle();
|
|
Instance& closure = Instance::Handle();
|
|
Array& parameter_keys = Array::Handle();
|
|
Array& parameter_values = Array::Handle();
|
|
Instance& reply_port = Instance::Handle();
|
|
Instance& id = Instance::Handle();
|
|
for (intptr_t i = 0; i < calls.Length(); i += kPendingEntrySize) {
|
|
// Grab arguments for call.
|
|
closure ^= calls.At(i + kPendingHandlerIndex);
|
|
ASSERT(!closure.IsNull());
|
|
arguments.SetAt(kPendingHandlerIndex, closure);
|
|
method_name ^= calls.At(i + kPendingMethodNameIndex);
|
|
ASSERT(!method_name.IsNull());
|
|
arguments.SetAt(kPendingMethodNameIndex, method_name);
|
|
parameter_keys ^= calls.At(i + kPendingKeysIndex);
|
|
ASSERT(!parameter_keys.IsNull());
|
|
arguments.SetAt(kPendingKeysIndex, parameter_keys);
|
|
parameter_values ^= calls.At(i + kPendingValuesIndex);
|
|
ASSERT(!parameter_values.IsNull());
|
|
arguments.SetAt(kPendingValuesIndex, parameter_values);
|
|
reply_port ^= calls.At(i + kPendingReplyPortIndex);
|
|
ASSERT(!reply_port.IsNull());
|
|
arguments.SetAt(kPendingReplyPortIndex, reply_port);
|
|
id ^= calls.At(i + kPendingIdIndex);
|
|
arguments.SetAt(kPendingIdIndex, id);
|
|
arguments.SetAt(kPendingEntrySize, Bool::Get(FLAG_trace_service));
|
|
|
|
if (FLAG_trace_service) {
|
|
OS::PrintErr("[+%" Pd64 "ms] Isolate %s invoking _runExtension for %s\n",
|
|
Dart::UptimeMillis(), name(), method_name.ToCString());
|
|
}
|
|
result = DartEntry::InvokeFunction(run_extension, arguments);
|
|
if (FLAG_trace_service) {
|
|
OS::PrintErr("[+%" Pd64 "ms] Isolate %s _runExtension complete for %s\n",
|
|
Dart::UptimeMillis(), name(), method_name.ToCString());
|
|
}
|
|
// Propagate the error.
|
|
if (result.IsError()) {
|
|
// Remaining service extension calls are dropped.
|
|
if (!result.IsUnwindError()) {
|
|
// Send error back over the protocol.
|
|
Service::PostError(method_name, parameter_keys, parameter_values,
|
|
reply_port, id, Error::Cast(result));
|
|
}
|
|
return Error::Cast(result).ptr();
|
|
}
|
|
// Drain the microtask queue.
|
|
result = DartLibraryCalls::DrainMicrotaskQueue();
|
|
// Propagate the error.
|
|
if (result.IsError()) {
|
|
// Remaining service extension calls are dropped.
|
|
return Error::Cast(result).ptr();
|
|
}
|
|
}
|
|
return Error::null();
|
|
}
|
|
|
|
GrowableObjectArrayPtr Isolate::GetAndClearPendingServiceExtensionCalls() {
|
|
GrowableObjectArrayPtr r = pending_service_extension_calls_;
|
|
pending_service_extension_calls_ = GrowableObjectArray::null();
|
|
return r;
|
|
}
|
|
|
|
void Isolate::AppendServiceExtensionCall(const Instance& closure,
|
|
const String& method_name,
|
|
const Array& parameter_keys,
|
|
const Array& parameter_values,
|
|
const Instance& reply_port,
|
|
const Instance& id) {
|
|
if (FLAG_trace_service) {
|
|
OS::PrintErr("[+%" Pd64
|
|
"ms] Isolate %s ENQUEUING request for extension %s\n",
|
|
Dart::UptimeMillis(), name(), method_name.ToCString());
|
|
}
|
|
GrowableObjectArray& calls =
|
|
GrowableObjectArray::Handle(pending_service_extension_calls());
|
|
bool schedule_drain = false;
|
|
if (calls.IsNull()) {
|
|
calls = GrowableObjectArray::New();
|
|
ASSERT(!calls.IsNull());
|
|
set_pending_service_extension_calls(calls);
|
|
schedule_drain = true;
|
|
}
|
|
ASSERT(kPendingHandlerIndex == 0);
|
|
calls.Add(closure);
|
|
ASSERT(kPendingMethodNameIndex == 1);
|
|
calls.Add(method_name);
|
|
ASSERT(kPendingKeysIndex == 2);
|
|
calls.Add(parameter_keys);
|
|
ASSERT(kPendingValuesIndex == 3);
|
|
calls.Add(parameter_values);
|
|
ASSERT(kPendingReplyPortIndex == 4);
|
|
calls.Add(reply_port);
|
|
ASSERT(kPendingIdIndex == 5);
|
|
calls.Add(id);
|
|
|
|
if (schedule_drain) {
|
|
const Array& msg = Array::Handle(Array::New(3));
|
|
Object& element = Object::Handle();
|
|
element = Smi::New(Message::kIsolateLibOOBMsg);
|
|
msg.SetAt(0, element);
|
|
element = Smi::New(Isolate::kDrainServiceExtensionsMsg);
|
|
msg.SetAt(1, element);
|
|
element = Smi::New(Isolate::kBeforeNextEventAction);
|
|
msg.SetAt(2, element);
|
|
std::unique_ptr<Message> message = WriteMessage(
|
|
/* same_group */ false, msg, main_port(), Message::kOOBPriority);
|
|
bool posted = PortMap::PostMessage(std::move(message));
|
|
ASSERT(posted);
|
|
}
|
|
}
|
|
|
|
// This function is written in C++ and not Dart because we must do this
|
|
// operation atomically in the face of random OOB messages. Do not port
|
|
// to Dart code unless you can ensure that the operations will can be
|
|
// done atomically.
|
|
void Isolate::RegisterServiceExtensionHandler(const String& name,
|
|
const Instance& closure) {
|
|
// Don't allow for service extensions to be registered for internal isolates.
|
|
if (Isolate::IsVMInternalIsolate(this)) {
|
|
return;
|
|
}
|
|
GrowableObjectArray& handlers =
|
|
GrowableObjectArray::Handle(registered_service_extension_handlers());
|
|
if (handlers.IsNull()) {
|
|
handlers = GrowableObjectArray::New(Heap::kOld);
|
|
set_registered_service_extension_handlers(handlers);
|
|
}
|
|
#if defined(DEBUG)
|
|
{
|
|
// Sanity check.
|
|
const Instance& existing_handler =
|
|
Instance::Handle(LookupServiceExtensionHandler(name));
|
|
ASSERT(existing_handler.IsNull());
|
|
}
|
|
#endif
|
|
ASSERT(kRegisteredNameIndex == 0);
|
|
handlers.Add(name, Heap::kOld);
|
|
ASSERT(kRegisteredHandlerIndex == 1);
|
|
handlers.Add(closure, Heap::kOld);
|
|
{
|
|
// Fire off an event.
|
|
ServiceEvent event(this, ServiceEvent::kServiceExtensionAdded);
|
|
event.set_extension_rpc(&name);
|
|
Service::HandleEvent(&event);
|
|
}
|
|
}
|
|
|
|
// This function is written in C++ and not Dart because we must do this
|
|
// operation atomically in the face of random OOB messages. Do not port
|
|
// to Dart code unless you can ensure that the operations will can be
|
|
// done atomically.
|
|
InstancePtr Isolate::LookupServiceExtensionHandler(const String& name) {
|
|
const GrowableObjectArray& handlers =
|
|
GrowableObjectArray::Handle(registered_service_extension_handlers());
|
|
if (handlers.IsNull()) {
|
|
return Instance::null();
|
|
}
|
|
String& handler_name = String::Handle();
|
|
for (intptr_t i = 0; i < handlers.Length(); i += kRegisteredEntrySize) {
|
|
handler_name ^= handlers.At(i + kRegisteredNameIndex);
|
|
ASSERT(!handler_name.IsNull());
|
|
if (handler_name.Equals(name)) {
|
|
return Instance::RawCast(handlers.At(i + kRegisteredHandlerIndex));
|
|
}
|
|
}
|
|
return Instance::null();
|
|
}
|
|
|
|
void Isolate::WakePauseEventHandler(Dart_Isolate isolate) {
|
|
Isolate* iso = reinterpret_cast<Isolate*>(isolate);
|
|
MonitorLocker ml(iso->pause_loop_monitor_);
|
|
ml.Notify();
|
|
|
|
Dart_MessageNotifyCallback current_notify_callback =
|
|
iso->message_notify_callback();
|
|
// It is possible that WakePauseEventHandler was replaced by original callback
|
|
// while waiting for pause_loop_monitor_. In that case PauseEventHandler
|
|
// is no longer running and the original callback needs to be invoked instead
|
|
// of incrementing wake_pause_event_handler_count_.
|
|
if (current_notify_callback != Isolate::WakePauseEventHandler) {
|
|
if (current_notify_callback != nullptr) {
|
|
current_notify_callback(isolate);
|
|
}
|
|
} else {
|
|
++iso->wake_pause_event_handler_count_;
|
|
}
|
|
}
|
|
|
|
void Isolate::PauseEventHandler() {
|
|
// We are stealing a pause event (like a breakpoint) from the
|
|
// embedder. We don't know what kind of thread we are on -- it
|
|
// could be from our thread pool or it could be a thread from the
|
|
// embedder. Sit on the current thread handling service events
|
|
// until we are told to resume.
|
|
if (pause_loop_monitor_ == nullptr) {
|
|
pause_loop_monitor_ = new Monitor();
|
|
}
|
|
Dart_EnterScope();
|
|
MonitorLocker ml(pause_loop_monitor_, false);
|
|
|
|
Dart_MessageNotifyCallback saved_notify_callback = message_notify_callback();
|
|
ASSERT(wake_pause_event_handler_count_ == 0);
|
|
set_message_notify_callback(Isolate::WakePauseEventHandler);
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
const bool had_program_reload_context =
|
|
group()->program_reload_context() != nullptr;
|
|
const int64_t start_time_micros = !had_program_reload_context
|
|
? 0
|
|
: group()
|
|
->program_reload_context()
|
|
->group_reload_context()
|
|
->start_time_micros();
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
bool resume = false;
|
|
while (true) {
|
|
// Handle all available vm service messages, up to a resume
|
|
// request.
|
|
while (!resume && Dart_HasServiceMessages()) {
|
|
ml.Exit();
|
|
resume = Dart_HandleServiceMessages();
|
|
ml.Enter();
|
|
}
|
|
if (resume) {
|
|
break;
|
|
}
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
if (had_program_reload_context &&
|
|
(group()->program_reload_context() == nullptr)) {
|
|
if (FLAG_trace_reload) {
|
|
const int64_t reload_time_micros =
|
|
OS::GetCurrentMonotonicMicros() - start_time_micros;
|
|
double reload_millis = MicrosecondsToMilliseconds(reload_time_micros);
|
|
OS::PrintErr("Reloading has finished! (%.2f ms)\n", reload_millis);
|
|
}
|
|
break;
|
|
}
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
// Wait for more service messages.
|
|
Monitor::WaitResult res = ml.Wait();
|
|
ASSERT(res == Monitor::kNotified);
|
|
}
|
|
// If any non-service messages came in, we need to notify the registered
|
|
// message notify callback to check for unhandled messages. Otherwise, events
|
|
// may be left unhandled until the next event comes in. See
|
|
// https://github.com/dart-lang/sdk/issues/37312.
|
|
if (saved_notify_callback != nullptr) {
|
|
while (wake_pause_event_handler_count_ > 0) {
|
|
saved_notify_callback(Api::CastIsolate(this));
|
|
--wake_pause_event_handler_count_;
|
|
}
|
|
} else {
|
|
wake_pause_event_handler_count_ = 0;
|
|
}
|
|
set_message_notify_callback(saved_notify_callback);
|
|
Dart_ExitScope();
|
|
}
|
|
#endif // !PRODUCT
|
|
|
|
void Isolate::VisitIsolates(IsolateVisitor* visitor) {
|
|
if (visitor == nullptr) {
|
|
return;
|
|
}
|
|
IsolateGroup::ForEach([&](IsolateGroup* group) {
|
|
group->ForEachIsolate(
|
|
[&](Isolate* isolate) { visitor->VisitIsolate(isolate); });
|
|
});
|
|
}
|
|
|
|
intptr_t Isolate::IsolateListLength() {
|
|
intptr_t count = 0;
|
|
IsolateGroup::ForEach([&](IsolateGroup* group) {
|
|
group->ForEachIsolate([&](Isolate* isolate) { count++; });
|
|
});
|
|
return count;
|
|
}
|
|
|
|
Isolate* Isolate::LookupIsolateByPort(Dart_Port port) {
|
|
Isolate* match = nullptr;
|
|
IsolateGroup::ForEach([&](IsolateGroup* group) {
|
|
group->ForEachIsolate([&](Isolate* isolate) {
|
|
if (isolate->main_port() == port) {
|
|
match = isolate;
|
|
}
|
|
});
|
|
});
|
|
return match;
|
|
}
|
|
|
|
std::unique_ptr<char[]> Isolate::LookupIsolateNameByPort(Dart_Port port) {
|
|
MonitorLocker ml(isolate_creation_monitor_);
|
|
std::unique_ptr<char[]> result;
|
|
IsolateGroup::ForEach([&](IsolateGroup* group) {
|
|
group->ForEachIsolate([&](Isolate* isolate) {
|
|
if (isolate->main_port() == port) {
|
|
const size_t len = strlen(isolate->name()) + 1;
|
|
result = std::unique_ptr<char[]>(new char[len]);
|
|
strncpy(result.get(), isolate->name(), len);
|
|
}
|
|
});
|
|
});
|
|
return result;
|
|
}
|
|
|
|
bool Isolate::TryMarkIsolateReady(Isolate* isolate) {
|
|
MonitorLocker ml(isolate_creation_monitor_);
|
|
if (!creation_enabled_) {
|
|
return false;
|
|
}
|
|
isolate->accepts_messages_ = true;
|
|
return true;
|
|
}
|
|
|
|
void Isolate::UnMarkIsolateReady(Isolate* isolate) {
|
|
MonitorLocker ml(isolate_creation_monitor_);
|
|
isolate->accepts_messages_ = false;
|
|
}
|
|
|
|
void Isolate::DisableIsolateCreation() {
|
|
MonitorLocker ml(isolate_creation_monitor_);
|
|
creation_enabled_ = false;
|
|
}
|
|
|
|
void Isolate::EnableIsolateCreation() {
|
|
MonitorLocker ml(isolate_creation_monitor_);
|
|
creation_enabled_ = true;
|
|
}
|
|
|
|
bool Isolate::IsolateCreationEnabled() {
|
|
MonitorLocker ml(isolate_creation_monitor_);
|
|
return creation_enabled_;
|
|
}
|
|
|
|
bool IsolateGroup::IsSystemIsolateGroup(const IsolateGroup* group) {
|
|
return group->source()->flags.is_system_isolate;
|
|
}
|
|
|
|
bool Isolate::IsVMInternalIsolate(const Isolate* isolate) {
|
|
return isolate->is_kernel_isolate() || isolate->is_service_isolate() ||
|
|
isolate->is_vm_isolate();
|
|
}
|
|
|
|
void Isolate::KillLocked(LibMsgId msg_id) {
|
|
Dart_CObject kill_msg;
|
|
Dart_CObject* list_values[4];
|
|
kill_msg.type = Dart_CObject_kArray;
|
|
kill_msg.value.as_array.length = 4;
|
|
kill_msg.value.as_array.values = list_values;
|
|
|
|
Dart_CObject oob;
|
|
oob.type = Dart_CObject_kInt32;
|
|
oob.value.as_int32 = Message::kIsolateLibOOBMsg;
|
|
list_values[0] = &oob;
|
|
|
|
Dart_CObject msg_type;
|
|
msg_type.type = Dart_CObject_kInt32;
|
|
msg_type.value.as_int32 = msg_id;
|
|
list_values[1] = &msg_type;
|
|
|
|
Dart_CObject cap;
|
|
cap.type = Dart_CObject_kCapability;
|
|
cap.value.as_capability.id = terminate_capability();
|
|
list_values[2] = ∩
|
|
|
|
Dart_CObject imm;
|
|
imm.type = Dart_CObject_kInt32;
|
|
imm.value.as_int32 = Isolate::kImmediateAction;
|
|
list_values[3] = &imm;
|
|
|
|
{
|
|
AllocOnlyStackZone zone;
|
|
std::unique_ptr<Message> message = WriteApiMessage(
|
|
zone.GetZone(), &kill_msg, main_port(), Message::kOOBPriority);
|
|
ASSERT(message != nullptr);
|
|
|
|
// Post the message at the given port.
|
|
bool success = PortMap::PostMessage(std::move(message));
|
|
ASSERT(success);
|
|
}
|
|
}
|
|
|
|
class IsolateKillerVisitor : public IsolateVisitor {
|
|
public:
|
|
IsolateKillerVisitor(Isolate::LibMsgId msg_id,
|
|
bool kill_system_isolates = false)
|
|
: target_(nullptr),
|
|
msg_id_(msg_id),
|
|
kill_system_isolates_(kill_system_isolates) {}
|
|
|
|
IsolateKillerVisitor(Isolate* isolate, Isolate::LibMsgId msg_id)
|
|
: target_(isolate), msg_id_(msg_id), kill_system_isolates_(false) {
|
|
ASSERT(isolate != Dart::vm_isolate());
|
|
}
|
|
|
|
virtual ~IsolateKillerVisitor() {}
|
|
|
|
void VisitIsolate(Isolate* isolate) {
|
|
MonitorLocker ml(Isolate::isolate_creation_monitor_);
|
|
ASSERT(isolate != nullptr);
|
|
if (ShouldKill(isolate)) {
|
|
if (isolate->AcceptsMessagesLocked()) {
|
|
isolate->KillLocked(msg_id_);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
bool ShouldKill(Isolate* isolate) {
|
|
if (kill_system_isolates_) {
|
|
ASSERT(target_ == nullptr);
|
|
// Don't kill the service isolate or vm isolate.
|
|
return IsSystemIsolate(isolate) && !Isolate::IsVMInternalIsolate(isolate);
|
|
}
|
|
// If a target_ is specified, then only kill the target_.
|
|
// Otherwise, don't kill the service isolate or vm isolate.
|
|
return (((target_ != nullptr) && (isolate == target_)) ||
|
|
((target_ == nullptr) && !IsSystemIsolate(isolate)));
|
|
}
|
|
|
|
Isolate* target_;
|
|
Isolate::LibMsgId msg_id_;
|
|
bool kill_system_isolates_;
|
|
};
|
|
|
|
void Isolate::KillAllIsolates(LibMsgId msg_id) {
|
|
IsolateKillerVisitor visitor(msg_id);
|
|
VisitIsolates(&visitor);
|
|
}
|
|
|
|
void Isolate::KillAllSystemIsolates(LibMsgId msg_id) {
|
|
IsolateKillerVisitor visitor(msg_id, /*kill_system_isolates=*/true);
|
|
VisitIsolates(&visitor);
|
|
}
|
|
|
|
void Isolate::KillIfExists(Isolate* isolate, LibMsgId msg_id) {
|
|
IsolateKillerVisitor visitor(isolate, msg_id);
|
|
VisitIsolates(&visitor);
|
|
}
|
|
|
|
void Isolate::IncrementSpawnCount() {
|
|
MonitorLocker ml(&spawn_count_monitor_);
|
|
spawn_count_++;
|
|
}
|
|
|
|
void Isolate::DecrementSpawnCount() {
|
|
MonitorLocker ml(&spawn_count_monitor_);
|
|
ASSERT(spawn_count_ > 0);
|
|
spawn_count_--;
|
|
ml.Notify();
|
|
}
|
|
|
|
void Isolate::WaitForOutstandingSpawns() {
|
|
Thread* thread = Thread::Current();
|
|
ASSERT(thread != nullptr);
|
|
MonitorLocker ml(&spawn_count_monitor_);
|
|
while (spawn_count_ > 0) {
|
|
ml.WaitWithSafepointCheck(thread);
|
|
}
|
|
}
|
|
|
|
FfiCallbackMetadata::Trampoline Isolate::CreateAsyncFfiCallback(
|
|
Zone* zone,
|
|
const Function& send_function,
|
|
Dart_Port send_port) {
|
|
return FfiCallbackMetadata::Instance()->CreateAsyncFfiCallback(
|
|
this, zone, send_function, send_port, &ffi_callback_list_head_);
|
|
}
|
|
|
|
FfiCallbackMetadata::Trampoline Isolate::CreateIsolateLocalFfiCallback(
|
|
Zone* zone,
|
|
const Function& trampoline,
|
|
const Closure& target,
|
|
bool keep_isolate_alive) {
|
|
if (keep_isolate_alive) {
|
|
UpdateNativeCallableKeepIsolateAliveCounter(1);
|
|
}
|
|
return FfiCallbackMetadata::Instance()->CreateIsolateLocalFfiCallback(
|
|
this, zone, trampoline, target, &ffi_callback_list_head_);
|
|
}
|
|
|
|
bool Isolate::HasLivePorts() {
|
|
ASSERT(0 <= open_ports_ && 0 <= open_ports_keepalive_ &&
|
|
open_ports_keepalive_ <= open_ports_);
|
|
return open_ports_keepalive_ > 0;
|
|
}
|
|
|
|
ReceivePortPtr Isolate::CreateReceivePort(const String& debug_name) {
|
|
Dart_Port port_id = PortMap::CreatePort(message_handler());
|
|
++open_ports_;
|
|
++open_ports_keepalive_;
|
|
return ReceivePort::New(port_id, debug_name);
|
|
}
|
|
|
|
void Isolate::SetReceivePortKeepAliveState(const ReceivePort& receive_port,
|
|
bool keep_isolate_alive) {
|
|
// Changing keep-isolate-alive state of a closed port is a NOP.
|
|
if (!receive_port.is_open()) return;
|
|
|
|
ASSERT(0 < open_ports_);
|
|
|
|
// If the state doesn't change it's a NOP.
|
|
if (receive_port.keep_isolate_alive() == keep_isolate_alive) return;
|
|
|
|
if (keep_isolate_alive) {
|
|
ASSERT(open_ports_keepalive_ < open_ports_);
|
|
++open_ports_keepalive_;
|
|
} else {
|
|
ASSERT(0 < open_ports_keepalive_);
|
|
--open_ports_keepalive_;
|
|
}
|
|
receive_port.set_keep_isolate_alive(keep_isolate_alive);
|
|
}
|
|
|
|
void Isolate::CloseReceivePort(const ReceivePort& receive_port) {
|
|
// Closing an already closed port is a NOP.
|
|
if (!receive_port.is_open()) return;
|
|
|
|
ASSERT(open_ports_ > 0);
|
|
const bool ok = PortMap::ClosePort(receive_port.Id());
|
|
RELEASE_ASSERT(ok);
|
|
|
|
if (receive_port.keep_isolate_alive()) {
|
|
--open_ports_keepalive_;
|
|
receive_port.set_keep_isolate_alive(false);
|
|
}
|
|
--open_ports_;
|
|
receive_port.set_is_open(false);
|
|
}
|
|
|
|
void Isolate::DeleteFfiCallback(FfiCallbackMetadata::Trampoline callback) {
|
|
FfiCallbackMetadata::Instance()->DeleteCallback(callback,
|
|
&ffi_callback_list_head_);
|
|
}
|
|
|
|
void Isolate::UpdateNativeCallableKeepIsolateAliveCounter(intptr_t delta) {
|
|
ffi_callback_keep_alive_counter_ += delta;
|
|
ASSERT(ffi_callback_keep_alive_counter_ >= 0);
|
|
}
|
|
|
|
bool Isolate::HasOpenNativeCallables() {
|
|
ASSERT(ffi_callback_keep_alive_counter_ >= 0);
|
|
return ffi_callback_keep_alive_counter_ > 0;
|
|
}
|
|
|
|
#if !defined(PRODUCT)
|
|
void IsolateGroup::CloneClassTableForReload() {
|
|
RELEASE_ASSERT(class_table_ == heap_walk_class_table_);
|
|
class_table_ = class_table_->Clone();
|
|
set_cached_class_table_table(nullptr);
|
|
}
|
|
|
|
void IsolateGroup::RestoreOriginalClassTable() {
|
|
RELEASE_ASSERT(class_table_ != heap_walk_class_table_);
|
|
class_table_allocator_.Free(class_table_);
|
|
class_table_ = heap_walk_class_table_;
|
|
set_cached_class_table_table(class_table_->table());
|
|
}
|
|
|
|
void IsolateGroup::DropOriginalClassTable() {
|
|
RELEASE_ASSERT(class_table_ != heap_walk_class_table_);
|
|
class_table_allocator_.Free(heap_walk_class_table_);
|
|
heap_walk_class_table_ = class_table_;
|
|
set_cached_class_table_table(class_table_->table());
|
|
}
|
|
#endif
|
|
|
|
} // namespace dart
|