9302a7427b
Attempts to acquire safepoint lock while in SafepointOperationScope could result in deadlocks if somebody else was holding that lock when they were forced to a safepoint. Clean up few places where locks were acquired in SafepointOperationScope. Introduce StoppedMutatorsScope and GroupDebugger::RunUnderReadLockIfNeeded that acquires a lock only if it runs outside of StoppedMutatorsScope - to prevent such deadlocks. Also fix tsan warning about data race around message_notify_callback by making it atomic. TEST=tsan runs of debugger CI tests Fixes https://github.com/dart-lang/sdk/issues/45527 Fixes https://github.com/dart-lang/sdk/issues/45549 Issue https://github.com/dart-lang/sdk/issues/36097 Change-Id: Ibb53d9ce760d869e044e17075aeebf20fc0016a5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/193582 Commit-Queue: Alexander Aprelev <aam@google.com> Reviewed-by: Ben Konyi <bkonyi@google.com>
215 lines
5.7 KiB
C++
215 lines
5.7 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();
|
|
SafepointHandler* handler = thread->isolate_group()->safepoint_handler();
|
|
handler->ExitSafepointUsingLock(thread);
|
|
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 != NULL);
|
|
if (!mutex_->TryLock()) {
|
|
// We did not get the lock and could potentially block, so transition
|
|
// accordingly.
|
|
Thread* thread = Thread::Current();
|
|
if (thread != NULL) {
|
|
TransitionVMToBlocked transition(thread);
|
|
mutex->Lock();
|
|
} else {
|
|
mutex->Lock();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SafepointMonitorLocker::AcquireLock() {
|
|
ASSERT(monitor_ != NULL);
|
|
if (!monitor_->TryEnter()) {
|
|
// We did not get the lock and could potentially block, so transition
|
|
// accordingly.
|
|
Thread* thread = Thread::Current();
|
|
if (thread != NULL) {
|
|
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 != NULL) {
|
|
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).
|
|
ASSERT(thread == nullptr ||
|
|
!thread->isolate_group()->safepoint_handler()->IsOwnedByTheThread(
|
|
thread));
|
|
|
|
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();
|
|
const bool can_block_without_safepoint = thread == nullptr;
|
|
|
|
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
|