49ed2007e5
AllocateSuspendState was calling `Instance::SetField` but did not allow lazy deopt to occur. Possibility of lazy deopt from those
field stores is only theoretical, but it manifested as a
deadlock between background compiler and main thread: if main
thread calls `SetField` which tries to acquire write access to
the program lock, while background compiler has already
acquired write access to the program lock and is trying to
stop all mutators at a GC+Deopt safepoint then we will
deadlock as `AllocateSuspendState` does not allow Deopts
(safepoint level was lowered by 5bc107c29d).
We fix this problem by bypassing field guard and simply
writing affected fields directly in AllocateSuspendState.
We make sure to initialize guarded state for these fields
eagerly, so it never needs to change.
TEST=added assertion which validates that attempting to acquire program lock for write can only occur where GC+Deopt are permitted.
Change-Id: I6ee6b82f3296f49f799c0069e42850711c9320ac
Bug: b/355226004
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381240
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
228 lines
6.3 KiB
C++
228 lines
6.3 KiB
C++
// Copyright (c) 2016, 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 "vm/lockers.h"
|
|
#include "platform/assert.h"
|
|
#include "vm/heap/safepoint.h"
|
|
#include "vm/isolate.h"
|
|
|
|
namespace dart {
|
|
|
|
Monitor::WaitResult MonitorLocker::WaitWithSafepointCheck(Thread* thread,
|
|
int64_t millis) {
|
|
ASSERT(thread == Thread::Current());
|
|
ASSERT(thread->execution_state() == Thread::kThreadInVM);
|
|
#if defined(DEBUG)
|
|
if (no_safepoint_scope_) {
|
|
thread->DecrementNoSafepointScopeDepth();
|
|
}
|
|
#endif
|
|
thread->set_execution_state(Thread::kThreadInBlockedState);
|
|
thread->EnterSafepoint();
|
|
Monitor::WaitResult result = monitor_->Wait(millis);
|
|
// First try a fast update of the thread state to indicate it is not at a
|
|
// safepoint anymore.
|
|
if (!thread->TryExitSafepoint()) {
|
|
// Fast update failed which means we could potentially be in the middle
|
|
// of a safepoint operation and need to block for it.
|
|
monitor_->Exit();
|
|
thread->ExitSafepointUsingLock();
|
|
monitor_->Enter();
|
|
}
|
|
thread->set_execution_state(Thread::kThreadInVM);
|
|
#if defined(DEBUG)
|
|
if (no_safepoint_scope_) {
|
|
thread->IncrementNoSafepointScopeDepth();
|
|
}
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
SafepointMutexLocker::SafepointMutexLocker(ThreadState* thread, Mutex* mutex)
|
|
: StackResource(thread), mutex_(mutex) {
|
|
ASSERT(mutex != nullptr);
|
|
if (!mutex_->TryLock()) {
|
|
// We did not get the lock and could potentially block, so transition
|
|
// accordingly.
|
|
Thread* thread = Thread::Current();
|
|
if (thread != nullptr) {
|
|
TransitionVMToBlocked transition(thread);
|
|
mutex->Lock();
|
|
} else {
|
|
mutex->Lock();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SafepointMonitorLocker::AcquireLock() {
|
|
ASSERT(monitor_ != nullptr);
|
|
if (!monitor_->TryEnter()) {
|
|
// We did not get the lock and could potentially block, so transition
|
|
// accordingly.
|
|
Thread* thread = Thread::Current();
|
|
if (thread != nullptr) {
|
|
TransitionVMToBlocked transition(thread);
|
|
monitor_->Enter();
|
|
} else {
|
|
monitor_->Enter();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SafepointMonitorLocker::ReleaseLock() {
|
|
monitor_->Exit();
|
|
}
|
|
|
|
Monitor::WaitResult SafepointMonitorLocker::Wait(int64_t millis) {
|
|
Thread* thread = Thread::Current();
|
|
if (thread != nullptr) {
|
|
Monitor::WaitResult result;
|
|
{
|
|
TransitionVMToBlocked transition(thread);
|
|
result = monitor_->Wait(millis);
|
|
}
|
|
return result;
|
|
} else {
|
|
return monitor_->Wait(millis);
|
|
}
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
bool SafepointRwLock::IsCurrentThreadReader() {
|
|
ThreadId id = OSThread::GetCurrentThreadId();
|
|
if (IsCurrentThreadWriter()) {
|
|
return true;
|
|
}
|
|
MonitorLocker ml(&monitor_);
|
|
for (intptr_t i = readers_ids_.length() - 1; i >= 0; i--) {
|
|
if (readers_ids_.At(i) == id) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif // defined(DEBUG)
|
|
|
|
bool SafepointRwLock::EnterRead() {
|
|
// 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).
|
|
DEBUG_ASSERT(thread == nullptr || thread->CanAcquireSafepointLocks() ||
|
|
IsCurrentThreadReader());
|
|
|
|
const bool can_block_without_safepoint = thread == nullptr;
|
|
|
|
bool acquired_read_lock = false;
|
|
if (!TryEnterRead(can_block_without_safepoint, &acquired_read_lock)) {
|
|
// Important: must never hold monitor_ when blocking for safepoint.
|
|
TransitionVMToBlocked transition(thread);
|
|
const bool ok = TryEnterRead(/*can_block=*/true, &acquired_read_lock);
|
|
RELEASE_ASSERT(ok);
|
|
RELEASE_ASSERT(acquired_read_lock);
|
|
}
|
|
return acquired_read_lock;
|
|
}
|
|
|
|
bool SafepointRwLock::TryEnterRead(bool can_block, bool* acquired_read_lock) {
|
|
MonitorLocker ml(&monitor_);
|
|
if (IsCurrentThreadWriter()) {
|
|
*acquired_read_lock = false;
|
|
return true;
|
|
}
|
|
if (can_block) {
|
|
while (state_ < 0) {
|
|
ml.Wait();
|
|
}
|
|
}
|
|
if (state_ >= 0) {
|
|
++state_;
|
|
DEBUG_ONLY(readers_ids_.Add(OSThread::GetCurrentThreadId()));
|
|
*acquired_read_lock = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SafepointRwLock::LeaveRead() {
|
|
MonitorLocker ml(&monitor_);
|
|
ASSERT(state_ > 0);
|
|
#if defined(DEBUG)
|
|
{
|
|
intptr_t i = readers_ids_.length() - 1;
|
|
ThreadId id = OSThread::GetCurrentThreadId();
|
|
while (i >= 0) {
|
|
if (readers_ids_.At(i) == id) {
|
|
readers_ids_.RemoveAt(i);
|
|
break;
|
|
}
|
|
i--;
|
|
}
|
|
ASSERT(i >= 0);
|
|
}
|
|
#endif
|
|
if (--state_ == 0) {
|
|
ml.NotifyAll();
|
|
}
|
|
}
|
|
|
|
void SafepointRwLock::EnterWrite() {
|
|
// 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).
|
|
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.
|
|
TransitionVMToBlocked transition(thread);
|
|
const bool ok = TryEnterWrite(/*can_block=*/true);
|
|
RELEASE_ASSERT(ok);
|
|
}
|
|
}
|
|
|
|
bool SafepointRwLock::TryEnterWrite(bool can_block) {
|
|
MonitorLocker ml(&monitor_);
|
|
if (IsCurrentThreadWriter()) {
|
|
state_--;
|
|
return true;
|
|
}
|
|
if (can_block) {
|
|
while (state_ != 0) {
|
|
ml.Wait();
|
|
}
|
|
}
|
|
if (state_ == 0) {
|
|
writer_id_ = OSThread::GetCurrentThreadId();
|
|
state_ = -1;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SafepointRwLock::LeaveWrite() {
|
|
MonitorLocker ml(&monitor_);
|
|
ASSERT(state_ < 0);
|
|
if (++state_ < 0) {
|
|
return;
|
|
}
|
|
writer_id_ = OSThread::kInvalidThreadId;
|
|
ml.NotifyAll();
|
|
}
|
|
|
|
} // namespace dart
|