[vm] Fix deadlocks in shared field initialization.

TEST=ci
Bug: https://github.com/dart-lang/sdk/issues/62392
Change-Id: Id34b7f47c8f03c4cd0fb6555912279ee03303f23
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/474565
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
Ryan Macnak
2026-01-21 10:59:51 -08:00
committed by Commit Queue
parent 0eae14f76a
commit b7a36e1c0f
5 changed files with 78 additions and 3 deletions
+24
View File
@@ -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
+26
View File
@@ -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()) {
+19
View File
@@ -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_
+5 -1
View File
@@ -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());
+4 -2
View File
@@ -1108,7 +1108,8 @@ class Thread : public ThreadState, public IntrusiveDListEntry<Thread> {
kThreadInVM = 0,
kThreadInGenerated,
kThreadInNative,
kThreadInBlockedState
kThreadInBlockedState,
kThreadInReloadableBlockedState
};
ExecutionState execution_state() const {
@@ -1317,7 +1318,8 @@ class Thread : public ThreadState, public IntrusiveDListEntry<Thread> {
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) {