532c116cd2
This CL implements `NativeFinalizer` in the GC. `FinalizerEntry`s are extended to track `external_size` and in which `Heap::Space` the finalizable value is. On attaching a native finalizer, the external size is added to the relevant heap. When the finalizable value is promoted from new to old space, the external size is promoted as well. And when a native finalizer is run or is detached, the external size is removed from the relevant heap again. In contrast to Dart `Finalizer`s, `NativeFinalizer`s are run on isolate shutdown. When the `NativeFinalizer`s themselves are collected, the finalizers are not run. Users should stick the native finalizer in a global variable to ensure finalization. We will revisit this design when we add send and exit support, because there is a design space to explore what to do in that case. This current solution promises the least to users. In this implementation native finalizers have a Dart entry to clean up the entries from the `all_entries` field of the finalizer. We should consider using another data structure that avoids the need for this Dart entry. See the TODO left in the code. Bug: https://github.com/dart-lang/sdk/issues/47777 TEST=runtime/tests/vm/dart(_2)/isolates/fast_object_copy_test.dart TEST=runtime/vm/object_test.cc TEST=tests/ffi(_2)/vmspecific_native_finalizer_* Change-Id: I8f594c80c3c344ad83e1f2de10de028eb8456121 Cq-Include-Trybots: luci.dart.try:vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-reload-linux-debug-x64-try,vm-ffi-android-debug-arm64c-try,dart-sdk-mac-arm64-try,vm-kernel-mac-release-arm64-try,pkg-mac-release-arm64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try,vm-kernel-win-debug-x64c-try,vm-kernel-win-debug-x64-try,vm-kernel-precomp-win-debug-x64c-try,vm-kernel-nnbd-win-release-ia32-try,vm-ffi-android-debug-arm-try,vm-precomp-ffi-qemu-linux-release-arm-try,vm-kernel-mac-debug-x64-try,vm-kernel-nnbd-mac-debug-x64-try,vm-kernel-nnbd-linux-debug-ia32-try,benchmark-linux-try,flutter-frontend-try,pkg-linux-debug-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-gcc-linux-try,vm-kernel-optcounter-threshold-linux-release-x64-try,vm-kernel-precomp-linux-debug-simarm_x64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-debug-x64c-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/236320 Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com>
3826 lines
130 KiB
C++
3826 lines
130 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/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/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_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);
|
|
|
|
// 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_random_seed = 0x44617274; // "Dart"
|
|
}
|
|
}
|
|
|
|
DEFINE_FLAG_HANDLER(DeterministicModeHandler,
|
|
deterministic,
|
|
"Enable deterministic mode.");
|
|
|
|
int FLAG_sound_null_safety = kNullSafetyOptionUnspecified;
|
|
static void SoundNullSafetyHandler(bool value) {
|
|
FLAG_sound_null_safety =
|
|
value ? kNullSafetyOptionStrong : kNullSafetyOptionWeak;
|
|
}
|
|
|
|
DEFINE_FLAG_HANDLER(SoundNullSafetyHandler,
|
|
sound_null_safety,
|
|
"Respect the nullability of types at runtime.");
|
|
|
|
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(/* can_send_any_object */ false, /* 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_timeout_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) {
|
|
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;
|
|
}
|
|
|
|
// 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)
|
|
: shared_class_table_(new SharedClassTable()),
|
|
class_table_(new ClassTable(shared_class_table_.get())),
|
|
cached_class_table_table_(class_table_->table()),
|
|
object_store_(object_store),
|
|
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)),
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
reload_handler_(new ReloadHandler()),
|
|
#endif
|
|
store_buffer_(new StoreBuffer()),
|
|
heap_(nullptr),
|
|
saved_unlinked_calls_(Array::null()),
|
|
initial_field_table_(new FieldTable(/*isolate=*/nullptr)),
|
|
#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);
|
|
const bool is_vm_isolate = Dart::VmIsolateNameEquals(source_->name);
|
|
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_);
|
|
id_ = isolate_group_random_->NextUInt64();
|
|
}
|
|
}
|
|
|
|
IsolateGroup::IsolateGroup(std::shared_ptr<IsolateGroupSource> source,
|
|
void* embedder_data,
|
|
Dart_IsolateFlags api_flags)
|
|
: IsolateGroup(source, embedder_data, new ObjectStore(), api_flags) {
|
|
if (object_store() != nullptr) {
|
|
object_store()->InitStubs();
|
|
}
|
|
}
|
|
|
|
IsolateGroup::~IsolateGroup() {
|
|
// Ensure we destroy the heap before the other members.
|
|
heap_ = nullptr;
|
|
ASSERT(marking_stack_ == nullptr);
|
|
|
|
if (obfuscation_map_ != nullptr) {
|
|
for (intptr_t i = 0; obfuscation_map_[i] != nullptr; i++) {
|
|
delete[] obfuscation_map_[i];
|
|
}
|
|
delete[] obfuscation_map_;
|
|
}
|
|
|
|
#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_++;
|
|
}
|
|
NOT_IN_PRODUCT(NOT_IN_PRECOMPILED(reload_handler()->RegisterIsolate()));
|
|
}
|
|
|
|
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) {
|
|
NOT_IN_PRODUCT(NOT_IN_PRECOMPILED(reload_handler()->UnregisterIsolate()));
|
|
{
|
|
SafepointWriteRwLocker ml(Thread::Current(), isolates_lock_.get());
|
|
isolates_.Remove(isolate);
|
|
}
|
|
}
|
|
|
|
bool IsolateGroup::UnregisterIsolateDecrementCount(Isolate* isolate) {
|
|
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);
|
|
|
|
is_vm_isolate_heap_ = is_vm_isolate;
|
|
|
|
#define ISOLATE_METRIC_CONSTRUCTORS(type, variable, name, unit) \
|
|
metric_##variable##_.InitInstance(this, name, nullptr, Metric::unit);
|
|
ISOLATE_GROUP_METRIC_LIST(ISOLATE_METRIC_CONSTRUCTORS)
|
|
#undef ISOLATE_METRIC_CONSTRUCTORS
|
|
}
|
|
|
|
void IsolateGroup::Shutdown() {
|
|
char* name;
|
|
// 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 (!Dart::VmIsolateNameEquals(source()->name)) {
|
|
ASSERT(thread_pool_ != nullptr);
|
|
thread_pool_->Shutdown();
|
|
thread_pool_.reset();
|
|
}
|
|
|
|
// 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 registery are
|
|
// still valid.
|
|
old_space->AbandonMarkingForShutdown();
|
|
}
|
|
|
|
UnregisterIsolateGroup(this);
|
|
|
|
// 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_) {
|
|
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();
|
|
}
|
|
|
|
Thread* IsolateGroup::ScheduleThreadLocked(MonitorLocker* ml,
|
|
Thread* existing_mutator_thread,
|
|
bool is_vm_isolate,
|
|
bool is_mutator,
|
|
bool bypass_safepoint) {
|
|
ASSERT(threads_lock()->IsOwnedByCurrentThread());
|
|
|
|
// Schedule the thread into the isolate group by associating
|
|
// a 'Thread' structure with it (this is done while we are holding
|
|
// the thread registry lock).
|
|
Thread* thread = nullptr;
|
|
OSThread* os_thread = OSThread::Current();
|
|
if (os_thread != nullptr) {
|
|
// If a safepoint operation is in progress wait for it to finish before
|
|
// scheduling this thread.
|
|
while (!bypass_safepoint && safepoint_handler()->AnySafepointInProgress()) {
|
|
ml->Wait();
|
|
}
|
|
|
|
if (is_mutator) {
|
|
if (existing_mutator_thread == nullptr) {
|
|
// Allocate a new [Thread] structure for the mutator thread.
|
|
thread = thread_registry()->GetFreeThreadLocked(is_vm_isolate);
|
|
} else {
|
|
// Reuse the existing cached [Thread] structure for the mutator thread.,
|
|
// see comment in 'base_isolate.h'.
|
|
thread_registry()->AddToActiveListLocked(existing_mutator_thread);
|
|
thread = existing_mutator_thread;
|
|
}
|
|
} else {
|
|
thread = thread_registry()->GetFreeThreadLocked(is_vm_isolate);
|
|
}
|
|
|
|
// Now get a free Thread structure.
|
|
ASSERT(thread != nullptr);
|
|
|
|
// Set up other values and set the TLS value.
|
|
thread->isolate_ = nullptr;
|
|
thread->isolate_group_ = this;
|
|
thread->field_table_values_ = nullptr;
|
|
ASSERT(heap() != nullptr);
|
|
thread->heap_ = heap();
|
|
thread->set_os_thread(os_thread);
|
|
ASSERT(thread->execution_state() == Thread::kThreadInNative);
|
|
thread->set_execution_state(Thread::kThreadInVM);
|
|
thread->set_safepoint_state(
|
|
Thread::SetBypassSafepoints(bypass_safepoint, 0));
|
|
thread->set_vm_tag(VMTag::kVMTagId);
|
|
ASSERT(thread->no_safepoint_scope_depth() == 0);
|
|
os_thread->set_thread(thread);
|
|
Thread::SetCurrent(thread);
|
|
os_thread->EnableThreadInterrupts();
|
|
}
|
|
return thread;
|
|
}
|
|
|
|
void IsolateGroup::UnscheduleThreadLocked(MonitorLocker* ml,
|
|
Thread* thread,
|
|
bool is_mutator,
|
|
bool bypass_safepoint) {
|
|
thread->heap()->new_space()->AbandonRemainingTLAB(thread);
|
|
|
|
// Clear since GC will not visit the thread once it is unscheduled. Do this
|
|
// under the thread lock to prevent races with the GC visiting thread roots.
|
|
if (!is_mutator) {
|
|
thread->ClearReusableHandles();
|
|
}
|
|
|
|
// Disassociate the 'Thread' structure and unschedule the thread
|
|
// from this isolate group.
|
|
if (!is_mutator) {
|
|
ASSERT(thread->api_top_scope_ == nullptr);
|
|
ASSERT(thread->zone() == nullptr);
|
|
ASSERT(thread->sticky_error() == Error::null());
|
|
}
|
|
if (!bypass_safepoint) {
|
|
// Ensure that the thread reports itself as being at a safepoint.
|
|
thread->EnterSafepoint();
|
|
}
|
|
OSThread* os_thread = thread->os_thread();
|
|
ASSERT(os_thread != nullptr);
|
|
os_thread->DisableThreadInterrupts();
|
|
os_thread->set_thread(nullptr);
|
|
OSThread::SetCurrent(os_thread);
|
|
|
|
// Even if we unschedule the mutator thread, e.g. via calling
|
|
// `Dart_ExitIsolate()` inside a native, we might still have one or more Dart
|
|
// stacks active, which e.g. GC marker threads want to visit. So we don't
|
|
// clear out the isolate pointer if we are on the mutator thread.
|
|
//
|
|
// The [thread] structure for the mutator thread is kept alive in the thread
|
|
// registry even if the mutator thread is temporarily unscheduled.
|
|
//
|
|
// All other threads are not allowed to unschedule themselves and schedule
|
|
// again later on.
|
|
if (!is_mutator) {
|
|
ASSERT(thread->isolate_ == nullptr);
|
|
thread->isolate_group_ = nullptr;
|
|
}
|
|
thread->heap_ = nullptr;
|
|
thread->set_os_thread(nullptr);
|
|
thread->set_execution_state(Thread::kThreadInNative);
|
|
thread->set_safepoint_state(Thread::AtSafepointField::encode(true) |
|
|
Thread::AtDeoptSafepointField::encode(true));
|
|
ASSERT(thread->no_safepoint_scope_depth() == 0);
|
|
if (is_mutator) {
|
|
// The mutator thread structure stays alive and attached to the isolate as
|
|
// long as the isolate lives. So we simply remove the thread from the list
|
|
// of scheduled threads.
|
|
thread_registry()->RemoveFromActiveListLocked(thread);
|
|
} else {
|
|
// Return thread structure.
|
|
thread_registry()->ReturnThreadLocked(thread);
|
|
}
|
|
}
|
|
|
|
Thread* IsolateGroup::ScheduleThread(bool bypass_safepoint) {
|
|
// We are about to associate the thread with an isolate group and it would
|
|
// not be possible to correctly track no_safepoint_scope_depth for the
|
|
// thread in the constructor/destructor of MonitorLocker,
|
|
// so we create a MonitorLocker object which does not do any
|
|
// no_safepoint_scope_depth increments/decrements.
|
|
MonitorLocker ml(threads_lock(), false);
|
|
|
|
const bool is_vm_isolate = false;
|
|
|
|
// Schedule the thread into the isolate by associating
|
|
// a 'Thread' structure with it (this is done while we are holding
|
|
// the thread registry lock).
|
|
return ScheduleThreadLocked(&ml, /*existing_mutator_thread=*/nullptr,
|
|
is_vm_isolate, /*is_mutator=*/false,
|
|
bypass_safepoint);
|
|
}
|
|
|
|
void IsolateGroup::UnscheduleThread(Thread* thread,
|
|
bool is_mutator,
|
|
bool bypass_safepoint) {
|
|
// Disassociate the 'Thread' structure and unschedule the thread
|
|
// from this isolate group.
|
|
//
|
|
// We are disassociating the thread from an isolate and it would
|
|
// not be possible to correctly track no_safepoint_scope_depth for the
|
|
// thread in the constructor/destructor of MonitorLocker,
|
|
// so we create a MonitorLocker object which does not do any
|
|
// no_safepoint_scope_depth increments/decrements.
|
|
MonitorLocker ml(threads_lock(), false);
|
|
UnscheduleThreadLocked(&ml, thread, is_mutator, bypass_safepoint);
|
|
}
|
|
|
|
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 montiors 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).
|
|
ASSERT(mutator->mutator_thread() != nullptr);
|
|
if (is_nested_exit) {
|
|
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 montiors 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 (!Dart::VmIsolateNameEquals(group->source()->name)) {
|
|
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());
|
|
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();
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
void IsolateGroup::RehashConstants() {
|
|
Thread* thread = Thread::Current();
|
|
StackZone stack_zone(thread);
|
|
Zone* zone = stack_zone.GetZone();
|
|
|
|
thread->heap()->ResetCanonicalHashTable();
|
|
|
|
Class& cls = Class::Handle(zone);
|
|
intptr_t top = class_table()->NumCids();
|
|
for (intptr_t cid = kInstanceCid; cid < top; 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;
|
|
}
|
|
cls = class_table()->At(cid);
|
|
cls.RehashConstants(zone);
|
|
}
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
void IsolateGroup::ValidateConstants() {
|
|
if (FLAG_precompiled_mode) {
|
|
// TODO(27003)
|
|
return;
|
|
}
|
|
// Issue(https://dartbug.com/44862): Figure out why hot-reload causes
|
|
// existence of non-canonical constants.
|
|
if (HasAttemptedReload()) {
|
|
return;
|
|
}
|
|
|
|
// Verify that all canonical instances are correctly setup in the
|
|
// corresponding canonical tables.
|
|
NoBackgroundCompilerScope no_bg_compiler(Thread::Current());
|
|
heap()->CollectAllGarbage(GCReason::kDebugging);
|
|
Thread* thread = Thread::Current();
|
|
SafepointMutexLocker ml(
|
|
thread->isolate_group()->constant_canonicalization_mutex());
|
|
HeapIterationScope iteration(thread);
|
|
VerifyCanonicalVisitor check_canonical(thread);
|
|
iteration.IterateObjects(&check_canonical);
|
|
}
|
|
#endif // DEBUG
|
|
|
|
void Isolate::SendInternalLibMessage(LibMsgId msg_id, uint64_t capability) {
|
|
const Array& msg = Array::Handle(Array::New(3));
|
|
Object& element = Object::Handle();
|
|
|
|
element = Smi::New(Message::kIsolateLibOOBMsg);
|
|
msg.SetAt(0, element);
|
|
element = Smi::New(msg_id);
|
|
msg.SetAt(1, element);
|
|
element = Capability::New(capability);
|
|
msg.SetAt(2, element);
|
|
|
|
PortMap::PostMessage(WriteMessage(/* can_send_any_object */ false,
|
|
/* same_group */ false, msg, main_port(),
|
|
Message::kOOBPriority));
|
|
}
|
|
|
|
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();
|
|
#endif
|
|
bool IsCurrentIsolate() const;
|
|
virtual Isolate* isolate() const { return isolate_; }
|
|
virtual IsolateGroup* isolate_group() const { return isolate_->group(); }
|
|
|
|
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() == NULL) {
|
|
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)
|
|
IG->reload_handler()->CheckForReload();
|
|
#else
|
|
UNREACHABLE();
|
|
#endif
|
|
break;
|
|
}
|
|
#if defined(DEBUG)
|
|
// Malformed OOB messages are silently ignored in release builds.
|
|
default:
|
|
FATAL1("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() {
|
|
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->copy_parent_code = false;
|
|
}
|
|
|
|
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->copy_parent_code = false;
|
|
}
|
|
|
|
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)
|
|
// Needs to be called manually, otherwise we don't set the null_safety_set
|
|
// bit.
|
|
set_null_safety(api_flags.null_safety);
|
|
#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->copy_parent_code = false;
|
|
}
|
|
|
|
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->copy_parent_code = false;
|
|
}
|
|
|
|
void Isolate::FlagsCopyFrom(const Dart_IsolateFlags& api_flags) {
|
|
const bool copy_parent_code_ = copy_parent_code();
|
|
#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)
|
|
isolate_flags_ = CopyParentCodeBit::update(copy_parent_code_, isolate_flags_);
|
|
#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()),
|
|
ic_miss_code_(Code::null()),
|
|
field_table_(new FieldTable(/*isolate=*/this)),
|
|
finalizers_(GrowableObjectArray::null()),
|
|
isolate_group_(isolate_group),
|
|
isolate_object_store_(new IsolateObjectStore()),
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
native_callback_trampolines_(),
|
|
#endif
|
|
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_(),
|
|
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);
|
|
|
|
if (group()->obfuscate()) {
|
|
OS::PrintErr(
|
|
"Warning: This VM has been configured to obfuscate symbol information "
|
|
"which violates the Dart standard.\n"
|
|
" See dartbug.com/30524 for more information.\n");
|
|
}
|
|
}
|
|
|
|
#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_ == NULL);
|
|
#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);
|
|
|
|
// We have cached the mutator thread, delete it.
|
|
ASSERT(scheduled_mutator_thread_ == nullptr);
|
|
mutator_thread_->isolate_ = nullptr;
|
|
delete mutator_thread_;
|
|
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->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, NULL, 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.
|
|
if (!Thread::EnterIsolate(result)) {
|
|
delete result;
|
|
return nullptr;
|
|
}
|
|
|
|
// Setup the isolate message handler.
|
|
MessageHandler* handler = new IsolateMessageHandler(result);
|
|
ASSERT(handler != nullptr);
|
|
result->set_message_handler(handler);
|
|
|
|
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());
|
|
result->set_pause_capability(result->random()->NextUInt64());
|
|
result->set_terminate_capability(result->random()->NextUInt64());
|
|
|
|
#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 (ServiceIsolate::NameEquals(name_prefix)) {
|
|
ASSERT(!ServiceIsolate::Exists());
|
|
ServiceIsolate::SetServiceIsolate(result);
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
} else if (KernelIsolate::NameEquals(name_prefix)) {
|
|
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 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()->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(!IsReloading());
|
|
|
|
// 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) {
|
|
// Ensure all isolates inside the isolate group are paused at a place where we
|
|
// can safely do a reload.
|
|
ReloadOperationScope reload_operation(Thread::Current());
|
|
|
|
ASSERT(!IsReloading());
|
|
|
|
auto shared_class_table = IsolateGroup::Current()->shared_class_table();
|
|
std::shared_ptr<IsolateGroupReloadContext> group_reload_context(
|
|
new IsolateGroupReloadContext(this, shared_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) {
|
|
// Ensure all isolates inside the isolate group are paused at a place where we
|
|
// can safely do a reload.
|
|
ReloadOperationScope reload_operation(Thread::Current());
|
|
|
|
ASSERT(!IsReloading());
|
|
|
|
auto shared_class_table = IsolateGroup::Current()->shared_class_table();
|
|
std::shared_ptr<IsolateGroupReloadContext> group_reload_context(
|
|
new IsolateGroupReloadContext(this, shared_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() {
|
|
// Another thread may be in the middle of GetClassForHeapWalkAt.
|
|
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.
|
|
static 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.
|
|
static 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.
|
|
static 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 == NULL) {
|
|
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());
|
|
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));
|
|
}
|
|
}
|
|
return listeners.Length() > 0;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
#if !defined(PRODUCT)
|
|
void Isolate::set_current_sample_block(SampleBlock* current) {
|
|
ASSERT(current_sample_block_lock_.IsOwnedByCurrentThread());
|
|
if (current != nullptr) {
|
|
current->set_is_allocation_block(false);
|
|
current->set_owner(this);
|
|
}
|
|
current_sample_block_ = current;
|
|
}
|
|
|
|
void Isolate::set_current_allocation_sample_block(SampleBlock* current) {
|
|
if (current != nullptr) {
|
|
current->set_is_allocation_block(true);
|
|
current->set_owner(this);
|
|
}
|
|
current_allocation_sample_block_ = current;
|
|
}
|
|
|
|
void Isolate::FreeSampleBlock(SampleBlock* block) {
|
|
if (block == nullptr) {
|
|
return;
|
|
}
|
|
SampleBlock* head;
|
|
// We're pushing the freed sample block to the front of the free_block_list_,
|
|
// which means the last element of the list will be the oldest freed sample.
|
|
do {
|
|
head = free_block_list_.load(std::memory_order_acquire);
|
|
block->next_free_ = head;
|
|
} while (!free_block_list_.compare_exchange_weak(head, block,
|
|
std::memory_order_release));
|
|
}
|
|
|
|
class StreamableSampleFilter : public SampleFilter {
|
|
public:
|
|
explicit StreamableSampleFilter(Dart_Port port)
|
|
: SampleFilter(port, kNoTaskFilter, -1, -1) {}
|
|
|
|
bool FilterSample(Sample* sample) override {
|
|
const UserTag& tag =
|
|
UserTag::Handle(UserTag::FindTagById(sample->user_tag()));
|
|
return tag.streamable();
|
|
}
|
|
};
|
|
|
|
void Isolate::ProcessFreeSampleBlocks(Thread* thread) {
|
|
SampleBlock* head = free_block_list_.exchange(nullptr);
|
|
if (head == nullptr) {
|
|
// No sample blocks to process.
|
|
return;
|
|
}
|
|
// Reverse the list before processing so older blocks are streamed and reused
|
|
// first.
|
|
SampleBlock* reversed_head = nullptr;
|
|
do {
|
|
SampleBlock* next = head->next_free_;
|
|
if (reversed_head == nullptr) {
|
|
reversed_head = head;
|
|
reversed_head->next_free_ = nullptr;
|
|
} else {
|
|
head->next_free_ = reversed_head;
|
|
reversed_head = head;
|
|
}
|
|
head = next;
|
|
} while (head != nullptr);
|
|
head = reversed_head;
|
|
if (Service::profiler_stream.enabled() && !IsSystemIsolate(this)) {
|
|
StackZone zone(thread);
|
|
SampleBlockListProcessor buffer(head);
|
|
if (buffer.HasStreamableSamples(thread)) {
|
|
HandleScope handle_scope(thread);
|
|
StreamableSampleFilter filter(main_port());
|
|
Profile profile;
|
|
profile.Build(thread, &filter, &buffer);
|
|
ASSERT(profile.sample_count() > 0);
|
|
ServiceEvent event(this, ServiceEvent::kCpuSamples);
|
|
event.set_cpu_profile(&profile);
|
|
Service::HandleEvent(&event);
|
|
}
|
|
}
|
|
|
|
do {
|
|
SampleBlock* next = head->next_free_;
|
|
head->next_free_ = nullptr;
|
|
head->evictable_ = true;
|
|
Profiler::sample_block_buffer()->FreeBlock(head);
|
|
head = next;
|
|
thread->CheckForSafepoint();
|
|
} while (head != nullptr);
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
// 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 = LinkedHashSet::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();
|
|
LinkedHashSet::Iterator iterator(all_entries);
|
|
while (iterator.MoveNext()) {
|
|
current_entry ^= iterator.CurrentKey();
|
|
native_finalizer.RunCallback(current_entry, "Isolate shutdown");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
set_message_handler(nullptr);
|
|
|
|
#if !defined(PRODUCT)
|
|
if (FLAG_dump_megamorphic_stats) {
|
|
MegamorphicCacheTable::PrintSizes(this);
|
|
}
|
|
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);
|
|
HandleScope handle_scope(thread);
|
|
ServiceIsolate::SendIsolateShutdownMessage();
|
|
#if !defined(PRODUCT)
|
|
debugger()->Shutdown();
|
|
// Cleanup profiler state.
|
|
SampleBlock* cpu_block = current_sample_block();
|
|
if (cpu_block != nullptr) {
|
|
cpu_block->release_block();
|
|
}
|
|
SampleBlock* allocation_block = current_allocation_sample_block();
|
|
if (allocation_block != nullptr) {
|
|
allocation_block->release_block();
|
|
}
|
|
|
|
// Process the previously assigned sample blocks if we're using the
|
|
// profiler's sample buffer. Some tests create their own SampleBlockBuffer
|
|
// and handle block processing themselves.
|
|
if ((cpu_block != nullptr || allocation_block != nullptr) &&
|
|
Profiler::sample_block_buffer() != nullptr) {
|
|
StackZone zone(thread);
|
|
HandleScope handle_scope(thread);
|
|
Profiler::sample_block_buffer()->ProcessCompletedBlocks();
|
|
}
|
|
#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);
|
|
|
|
// Post message before LowLevelShutdown that sends onExit message.
|
|
// This ensures that exit message comes last.
|
|
if (bequest_.get() != 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 (KernelIsolate::IsKernelIsolate(isolate)) {
|
|
KernelIsolate::SetKernelIsolate(nullptr);
|
|
}
|
|
#endif
|
|
if (ServiceIsolate::IsServiceIsolate(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.
|
|
Thread::ExitIsolate();
|
|
|
|
// 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(isolate);
|
|
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.
|
|
}
|
|
} // namespace dart
|
|
|
|
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*>(&ic_miss_code_));
|
|
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 (ServiceIsolate::IsServiceIsolate(this)) {
|
|
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_));
|
|
}
|
|
|
|
void IsolateGroup::ReleaseStoreBuffers() {
|
|
thread_registry()->ReleaseStoreBuffers();
|
|
}
|
|
|
|
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* marking_stack,
|
|
MarkingStack* deferred_marking_stack) {
|
|
ASSERT(marking_stack_ == nullptr);
|
|
marking_stack_ = marking_stack;
|
|
deferred_marking_stack_ = deferred_marking_stack;
|
|
thread_registry()->AcquireMarkingStacks();
|
|
ASSERT(Thread::Current()->is_marking());
|
|
}
|
|
|
|
void IsolateGroup::DisableIncrementalBarrier() {
|
|
thread_registry()->ReleaseMarkingStacks();
|
|
ASSERT(marking_stack_ != nullptr);
|
|
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->IsAtSafepoint() ||
|
|
(thread->task_kind() == Thread::kMutatorTask) ||
|
|
(thread->task_kind() == Thread::kMarkerTask) ||
|
|
(thread->task_kind() == Thread::kCompactorTask) ||
|
|
(thread->task_kind() == Thread::kScavengerTask));
|
|
for (Isolate* isolate : isolates_) {
|
|
function(isolate);
|
|
}
|
|
return;
|
|
}
|
|
if (thread != nullptr && thread->IsAtSafepoint()) {
|
|
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->IsAtSafepoint()) {
|
|
RELEASE_ASSERT(safepoint_handler()->IsOwnedByTheThread(thread));
|
|
single_current_mutator->Call();
|
|
return;
|
|
}
|
|
|
|
{
|
|
SafepointReadRwLocker ml(thread, isolates_lock_.get());
|
|
if (thread->IsMutatorThread() && 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) {
|
|
// if class table is shared, it's stored on isolate group
|
|
if (class_table() != nullptr) {
|
|
// Visit objects in the class table.
|
|
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);
|
|
|
|
// 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).
|
|
if (isolate->mutator_thread_ != nullptr) {
|
|
isolate->mutator_thread_->VisitObjectPointers(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);
|
|
}
|
|
|
|
ClassPtr IsolateGroup::GetClassForHeapWalkAt(intptr_t cid) {
|
|
ClassPtr raw_class = nullptr;
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
if (IsReloading()) {
|
|
raw_class = program_reload_context()->GetClassForHeapWalkAt(cid);
|
|
} else {
|
|
raw_class = class_table()->At(cid);
|
|
}
|
|
#else
|
|
raw_class = class_table()->At(cid);
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
ASSERT(raw_class != nullptr);
|
|
ASSERT(remapping_cids() || raw_class->untag()->id_ == cid);
|
|
return raw_class;
|
|
}
|
|
|
|
intptr_t IsolateGroup::GetClassSizeForHeapWalkAt(intptr_t cid) {
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
if (IsReloading()) {
|
|
return group_reload_context_->GetClassSizeForHeapWalkAt(cid);
|
|
} else {
|
|
return shared_class_table()->SizeAt(cid);
|
|
}
|
|
#else
|
|
return shared_class_table()->SizeAt(cid);
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
}
|
|
|
|
#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;
|
|
}
|
|
}
|
|
|
|
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());
|
|
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", message_handler()->live_ports());
|
|
jsobj.AddProperty("pauseOnExit", message_handler()->should_pause_on_exit());
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
jsobj.AddProperty("_isReloading", group()->IsReloading());
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
if (!is_runnable()) {
|
|
// Isolate is not yet runnable.
|
|
ASSERT((debugger() == nullptr) || (debugger()->PauseEvent() == nullptr));
|
|
ServiceEvent pause_event(this, ServiceEvent::kNone);
|
|
jsobj.AddProperty("pauseEvent", &pause_event);
|
|
} else if (message_handler()->should_pause_on_start()) {
|
|
if (message_handler()->is_paused_on_start()) {
|
|
ASSERT((debugger() == nullptr) || (debugger()->PauseEvent() == nullptr));
|
|
ServiceEvent pause_event(this, ServiceEvent::kPauseStart);
|
|
jsobj.AddProperty("pauseEvent", &pause_event);
|
|
} 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((debugger() == nullptr) || (debugger()->PauseEvent() == nullptr));
|
|
ServiceEvent pause_event(this, ServiceEvent::kNone);
|
|
jsobj.AddProperty("pauseEvent", &pause_event);
|
|
}
|
|
} else if (message_handler()->is_paused_on_exit() &&
|
|
((debugger() == nullptr) ||
|
|
(debugger()->PauseEvent() == nullptr))) {
|
|
ServiceEvent pause_event(this, ServiceEvent::kPauseExit);
|
|
jsobj.AddProperty("pauseEvent", &pause_event);
|
|
} else if ((debugger() != nullptr) && (debugger()->PauseEvent() != nullptr) &&
|
|
!ResumeRequest()) {
|
|
jsobj.AddProperty("pauseEvent", debugger()->PauseEvent());
|
|
} else {
|
|
ServiceEvent pause_event(this, ServiceEvent::kResume);
|
|
|
|
if (debugger() != nullptr) {
|
|
// TODO(turnidge): Don't compute a full stack trace.
|
|
DebuggerStackTrace* stack = debugger()->StackTrace();
|
|
if (stack->Length() > 0) {
|
|
pause_event.set_top_frame(stack->FrameAt(0));
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|
|
#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();
|
|
}
|
|
|
|
void Isolate::set_ic_miss_code(const Code& code) {
|
|
ic_miss_code_ = code.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.LookupLocalFunction(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(
|
|
/* can_send_any_object */ false, /* 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();
|
|
}
|
|
|
|
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();
|
|
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;
|
|
bool handle_non_service_messages = 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;
|
|
} else {
|
|
handle_non_service_messages = true;
|
|
}
|
|
|
|
#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) && handle_non_service_messages) {
|
|
saved_notify_callback(Api::CastIsolate(this));
|
|
}
|
|
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() ||
|
|
(Dart::vm_isolate() == 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:
|
|
explicit IsolateKillerVisitor(Isolate::LibMsgId msg_id)
|
|
: target_(nullptr), msg_id_(msg_id) {}
|
|
|
|
IsolateKillerVisitor(Isolate* isolate, Isolate::LibMsgId msg_id)
|
|
: target_(isolate), msg_id_(msg_id) {
|
|
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 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_;
|
|
};
|
|
|
|
void Isolate::KillAllIsolates(LibMsgId msg_id) {
|
|
IsolateKillerVisitor visitor(msg_id);
|
|
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 != NULL);
|
|
MonitorLocker ml(&spawn_count_monitor_);
|
|
while (spawn_count_ > 0) {
|
|
ml.WaitWithSafepointCheck(thread);
|
|
}
|
|
}
|
|
|
|
Monitor* IsolateGroup::threads_lock() const {
|
|
return thread_registry_->threads_lock();
|
|
}
|
|
|
|
Thread* Isolate::ScheduleThread(bool is_mutator,
|
|
bool is_nested_reenter,
|
|
bool bypass_safepoint) {
|
|
if (is_mutator) {
|
|
group()->IncreaseMutatorCount(this, is_nested_reenter);
|
|
}
|
|
|
|
// We are about to associate the thread with an isolate group and it would
|
|
// not be possible to correctly track no_safepoint_scope_depth for the
|
|
// thread in the constructor/destructor of MonitorLocker,
|
|
// so we create a MonitorLocker object which does not do any
|
|
// no_safepoint_scope_depth increments/decrements.
|
|
MonitorLocker ml(group()->threads_lock(), false);
|
|
|
|
// Check to make sure we don't already have a mutator thread.
|
|
if (is_mutator && scheduled_mutator_thread_ != nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
// NOTE: We cannot just use `Dart::vm_isolate() == this` here, since during
|
|
// VM startup it might not have been set at this point.
|
|
const bool is_vm_isolate =
|
|
Dart::vm_isolate() == nullptr || Dart::vm_isolate() == this;
|
|
|
|
// We lazily create a [Thread] structure for the mutator thread, but we'll
|
|
// reuse it until the death of the isolate.
|
|
Thread* existing_mutator_thread = is_mutator ? mutator_thread_ : nullptr;
|
|
if (existing_mutator_thread != nullptr) {
|
|
ASSERT(existing_mutator_thread->is_mutator_thread_);
|
|
}
|
|
|
|
// Schedule the thread into the isolate by associating a 'Thread' structure
|
|
// with it (this is done while we are holding the thread registry lock).
|
|
Thread* thread =
|
|
group()->ScheduleThreadLocked(&ml, existing_mutator_thread, is_vm_isolate,
|
|
is_mutator, bypass_safepoint);
|
|
if (is_mutator) {
|
|
ASSERT(mutator_thread_ == nullptr || mutator_thread_ == thread);
|
|
mutator_thread_ = thread;
|
|
scheduled_mutator_thread_ = thread;
|
|
thread->is_mutator_thread_ = true;
|
|
}
|
|
thread->isolate_ = this;
|
|
thread->field_table_values_ = field_table_->table();
|
|
|
|
return thread;
|
|
}
|
|
|
|
void Isolate::UnscheduleThread(Thread* thread,
|
|
bool is_mutator,
|
|
bool is_nested_exit,
|
|
bool bypass_safepoint) {
|
|
{
|
|
// Disassociate the 'Thread' structure and unschedule the thread
|
|
// from this isolate.
|
|
// We are disassociating the thread from an isolate and it would
|
|
// not be possible to correctly track no_safepoint_scope_depth for the
|
|
// thread in the constructor/destructor of MonitorLocker,
|
|
// so we create a MonitorLocker object which does not do any
|
|
// no_safepoint_scope_depth increments/decrements.
|
|
MonitorLocker ml(group()->threads_lock(), false);
|
|
|
|
if (is_mutator) {
|
|
if (thread->sticky_error() != Error::null()) {
|
|
ASSERT(sticky_error_ == Error::null());
|
|
sticky_error_ = thread->StealStickyError();
|
|
}
|
|
ASSERT(mutator_thread_ == thread);
|
|
ASSERT(mutator_thread_ == scheduled_mutator_thread_);
|
|
scheduled_mutator_thread_ = nullptr;
|
|
} else {
|
|
// We only reset the isolate pointer for non-mutator threads, since
|
|
// mutator threads can still be visited during GC even if unscheduled.
|
|
// See also IsolateGroup::UnscheduleThreadLocked`
|
|
thread->isolate_ = nullptr;
|
|
}
|
|
thread->field_table_values_ = nullptr;
|
|
group()->UnscheduleThreadLocked(&ml, thread, is_mutator, bypass_safepoint);
|
|
}
|
|
if (is_mutator) {
|
|
group()->DecreaseMutatorCount(this, is_nested_exit);
|
|
}
|
|
}
|
|
|
|
} // namespace dart
|