blob: ed816904844351d74e11b7e1b8446a067ddf7dfb [file] [log] [blame] [edit]
// 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