diff --git a/runtime/vm/heap/safepoint.h b/runtime/vm/heap/safepoint.h index 91095b3579b..b8ae5ea5553 100644 --- a/runtime/vm/heap/safepoint.h +++ b/runtime/vm/heap/safepoint.h @@ -376,6 +376,30 @@ class TransitionVMToBlocked : public TransitionSafepointState { DISALLOW_COPY_AND_ASSIGN(TransitionVMToBlocked); }; +class TransitionVMToBlockedReloadableStealable + : public TransitionSafepointState { + public: + explicit TransitionVMToBlockedReloadableStealable(Thread* T) + : TransitionSafepointState(T) { + ASSERT(T->CanAcquireSafepointLocks()); + // A thread blocked on a monitor is considered to be at a safepoint. + ASSERT(T->execution_state() == Thread::kThreadInVM); + T->set_execution_state(Thread::kThreadInReloadableBlockedState); + T->EnterSafepointToNative(); + } + + ~TransitionVMToBlockedReloadableStealable() { + // We are returning to vm code and so we are not at a safepoint anymore. + ASSERT(thread()->execution_state() == + Thread::kThreadInReloadableBlockedState); + thread()->ExitSafepointFromNative(); + thread()->set_execution_state(Thread::kThreadInVM); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TransitionVMToBlockedReloadableStealable); +}; + // TransitionVMToNative is used to transition the safepoint state of a // thread from "running vm code" to "running native code" and ensures // that the state is reverted back to "running vm code" when diff --git a/runtime/vm/lockers.cc b/runtime/vm/lockers.cc index 4acdd891ccd..d6f2758cfd8 100644 --- a/runtime/vm/lockers.cc +++ b/runtime/vm/lockers.cc @@ -198,6 +198,32 @@ void SafepointRwLock::EnterWrite() { } } +void SafepointRwLock::EnterWriteReloadableStealable() { + // No need to safepoint if the current thread is not attached. + auto thread = Thread::Current(); + // Attempt to acquire a lock while owning a safepoint could lead to a deadlock + // (some other thread might be forced to a safepoint while holding this lock). + // + // Though if the lock was already acquired by this thread before entering a + // safepoint, we do allow the nested acquire (which is a NOP). + ASSERT(thread == nullptr || thread->execution_state() == Thread::kThreadInVM); + DEBUG_ASSERT(thread == nullptr || thread->CanAcquireSafepointLocks() || + IsCurrentThreadWriter()); + + const bool can_block_without_safepoint = thread == nullptr; + + RELEASE_ASSERT(can_block_without_safepoint || + thread->current_safepoint_level() >= + expected_safepoint_level_); + + if (!TryEnterWrite(can_block_without_safepoint)) { + // Important: must never hold monitor_ when blocking for safepoint. + TransitionVMToBlockedReloadableStealable transition(thread); + const bool ok = TryEnterWrite(/*can_block=*/true); + RELEASE_ASSERT(ok); + } +} + bool SafepointRwLock::TryEnterWrite(bool can_block) { MonitorLocker ml(&monitor_); if (IsCurrentThreadWriter()) { diff --git a/runtime/vm/lockers.h b/runtime/vm/lockers.h index fa6d0051f22..222a787c43a 100644 --- a/runtime/vm/lockers.h +++ b/runtime/vm/lockers.h @@ -379,6 +379,7 @@ class SafepointRwLock { private: friend class SafepointReadRwLocker; friend class SafepointWriteRwLocker; + friend class ReloadableStealableWriteRwLocker; // returns [true] if read lock was acquired, // returns [false] if the thread didn't have to acquire read lock due @@ -388,6 +389,7 @@ class SafepointRwLock { void LeaveRead(); void EnterWrite(); + void EnterWriteReloadableStealable(); bool TryEnterWrite(bool can_block); void LeaveWrite(); @@ -500,6 +502,23 @@ class SafepointWriteRwLocker : public StackResource { SafepointRwLock* rw_lock_; }; +/* + * Allows reload safepoint and active mutator slot stealing while blocked. + */ +class ReloadableStealableWriteRwLocker : public StackResource { + public: + ReloadableStealableWriteRwLocker(ThreadState* thread_state, + SafepointRwLock* rw_lock) + : StackResource(thread_state), rw_lock_(rw_lock) { + rw_lock_->EnterWriteReloadableStealable(); + } + + ~ReloadableStealableWriteRwLocker() { rw_lock_->LeaveWrite(); } + + private: + SafepointRwLock* rw_lock_; +}; + } // namespace dart #endif // RUNTIME_VM_LOCKERS_H_ diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc index e9e760474ba..2f8513c3373 100644 --- a/runtime/vm/runtime_entry.cc +++ b/runtime/vm/runtime_entry.cc @@ -5231,7 +5231,11 @@ DEFINE_LEAF_RUNTIME_ENTRY(PropagateError, DLRT_PropagateError); DEFINE_RUNTIME_ENTRY(InitializeSharedField, 1) { - SafepointWriteRwLocker locker( + // Running the initializer means running arbitrary Dart code that might yield + // to a reload safepoint or have its active mutator slot stolen. Make sure we + // likewise yield while waiting for the lock to avoid the holder of the lock + // being blocked on locker-waiters for reload or a mutator slot. + ReloadableStealableWriteRwLocker locker( thread, thread->isolate_group()->shared_field_initializer_rwlock()); const Field& field = Field::CheckedHandle(zone, arguments.ArgAt(0)); Object& result = Object::Handle(zone, field.StaticValue()); diff --git a/runtime/vm/thread.h b/runtime/vm/thread.h index 5222b37cbb3..288ceff4957 100644 --- a/runtime/vm/thread.h +++ b/runtime/vm/thread.h @@ -1108,7 +1108,8 @@ class Thread : public ThreadState, public IntrusiveDListEntry { kThreadInVM = 0, kThreadInGenerated, kThreadInNative, - kThreadInBlockedState + kThreadInBlockedState, + kThreadInReloadableBlockedState }; ExecutionState execution_state() const { @@ -1317,7 +1318,8 @@ class Thread : public ThreadState, public IntrusiveDListEntry { if (no_reload_scope_depth_ > 0) { return SafepointLevel::kGCAndDeopt; } - if (execution_state_ == kThreadInNative) { + if (execution_state_ == kThreadInNative || + execution_state_ == kThreadInReloadableBlockedState) { return SafepointLevel::kGCAndDeoptAndReload; } if (allow_reload_scope_depth_ <= 0) {