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>
503 lines
13 KiB
C++
503 lines
13 KiB
C++
// Copyright (c) 2012, 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.
|
|
|
|
#ifndef RUNTIME_VM_LOCKERS_H_
|
|
#define RUNTIME_VM_LOCKERS_H_
|
|
|
|
#include "platform/assert.h"
|
|
#include "platform/growable_array.h"
|
|
#include "vm/allocation.h"
|
|
#include "vm/globals.h"
|
|
#include "vm/os_thread.h"
|
|
#include "vm/thread.h"
|
|
|
|
namespace dart {
|
|
|
|
const bool kNoSafepointScope = true;
|
|
const bool kDontAssertNoSafepointScope = false;
|
|
|
|
/*
|
|
* Normal mutex locker :
|
|
* This locker abstraction should only be used when the enclosing code can
|
|
* not trigger a safepoint. In debug mode this class increments the
|
|
* no_safepoint_scope_depth variable for the current thread when the lock is
|
|
* taken and decrements it when the lock is released. NOTE: please do not use
|
|
* the passed in mutex object independent of the locker class, For example the
|
|
* code below will not assert correctly:
|
|
* {
|
|
* MutexLocker ml(m);
|
|
* ....
|
|
* m->Exit();
|
|
* ....
|
|
* m->Enter();
|
|
* ...
|
|
* }
|
|
* Always use the locker object even when the lock needs to be released
|
|
* temporarily, e.g:
|
|
* {
|
|
* MutexLocker ml(m);
|
|
* ....
|
|
* ml.Exit();
|
|
* ....
|
|
* ml.Enter();
|
|
* ...
|
|
* }
|
|
*/
|
|
class MutexLocker : public ValueObject {
|
|
public:
|
|
explicit MutexLocker(Mutex* mutex)
|
|
:
|
|
#if defined(DEBUG)
|
|
no_safepoint_scope_(true),
|
|
#endif
|
|
mutex_(mutex) {
|
|
ASSERT(mutex != nullptr);
|
|
#if defined(DEBUG)
|
|
Thread* thread = Thread::Current();
|
|
if ((thread != nullptr) &&
|
|
(thread->execution_state() != Thread::kThreadInNative)) {
|
|
thread->IncrementNoSafepointScopeDepth();
|
|
} else {
|
|
no_safepoint_scope_ = false;
|
|
}
|
|
#endif
|
|
mutex_->Lock();
|
|
}
|
|
|
|
virtual ~MutexLocker() {
|
|
mutex_->Unlock();
|
|
#if defined(DEBUG)
|
|
if (no_safepoint_scope_) {
|
|
Thread::Current()->DecrementNoSafepointScopeDepth();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Lock() const {
|
|
#if defined(DEBUG)
|
|
if (no_safepoint_scope_) {
|
|
Thread::Current()->IncrementNoSafepointScopeDepth();
|
|
}
|
|
#endif
|
|
mutex_->Lock();
|
|
}
|
|
void Unlock() const {
|
|
mutex_->Unlock();
|
|
#if defined(DEBUG)
|
|
if (no_safepoint_scope_) {
|
|
Thread::Current()->DecrementNoSafepointScopeDepth();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
DEBUG_ONLY(bool no_safepoint_scope_;)
|
|
Mutex* const mutex_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MutexLocker);
|
|
};
|
|
|
|
// Unlocks the given mutex during the scope of the object.
|
|
class MutexUnlocker : public ValueObject {
|
|
public:
|
|
explicit MutexUnlocker(MutexLocker* mutex_locker)
|
|
: mutex_locker_(mutex_locker) {
|
|
mutex_locker_->Unlock();
|
|
}
|
|
|
|
virtual ~MutexUnlocker() { mutex_locker_->Lock(); }
|
|
|
|
private:
|
|
MutexLocker* const mutex_locker_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MutexUnlocker);
|
|
};
|
|
|
|
/*
|
|
* Normal monitor locker :
|
|
* This locker abstraction should only be used when the enclosed code can
|
|
* not trigger a safepoint. In debug mode this class increments the
|
|
* no_safepoint_scope_depth variable for the current thread when the lock is
|
|
* taken and decrements it when the lock is released. NOTE: please do not use
|
|
* the passed in mutex object independent of the locker class, For example the
|
|
* code below will not assert correctly:
|
|
* {
|
|
* MonitorLocker ml(m);
|
|
* ....
|
|
* m->Exit();
|
|
* ....
|
|
* m->Enter();
|
|
* ...
|
|
* }
|
|
* Always use the locker object even when the lock needs to be released
|
|
* temporarily, e.g:
|
|
* {
|
|
* MonitorLocker ml(m);
|
|
* ....
|
|
* ml.Exit();
|
|
* ....
|
|
* ml.Enter();
|
|
* ...
|
|
* }
|
|
*/
|
|
class MonitorLocker : public ValueObject {
|
|
public:
|
|
explicit MonitorLocker(Monitor* monitor, bool no_safepoint_scope = true)
|
|
: monitor_(monitor), no_safepoint_scope_(no_safepoint_scope) {
|
|
ASSERT(monitor != nullptr);
|
|
#if defined(DEBUG)
|
|
if (no_safepoint_scope_) {
|
|
Thread* thread = Thread::Current();
|
|
if (thread != nullptr) {
|
|
thread->IncrementNoSafepointScopeDepth();
|
|
} else {
|
|
no_safepoint_scope_ = false;
|
|
}
|
|
}
|
|
#endif
|
|
monitor_->Enter();
|
|
}
|
|
|
|
virtual ~MonitorLocker() {
|
|
monitor_->Exit();
|
|
#if defined(DEBUG)
|
|
if (no_safepoint_scope_) {
|
|
Thread::Current()->DecrementNoSafepointScopeDepth();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Enter() const {
|
|
#if defined(DEBUG)
|
|
if (no_safepoint_scope_) {
|
|
Thread::Current()->IncrementNoSafepointScopeDepth();
|
|
}
|
|
#endif
|
|
monitor_->Enter();
|
|
}
|
|
void Exit() const {
|
|
monitor_->Exit();
|
|
#if defined(DEBUG)
|
|
if (no_safepoint_scope_) {
|
|
Thread::Current()->DecrementNoSafepointScopeDepth();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Monitor::WaitResult Wait(int64_t millis = Monitor::kNoTimeout) {
|
|
return monitor_->Wait(millis);
|
|
}
|
|
|
|
Monitor::WaitResult WaitWithSafepointCheck(
|
|
Thread* thread,
|
|
int64_t millis = Monitor::kNoTimeout);
|
|
|
|
Monitor::WaitResult WaitMicros(int64_t micros = Monitor::kNoTimeout) {
|
|
return monitor_->WaitMicros(micros);
|
|
}
|
|
|
|
void Notify() { monitor_->Notify(); }
|
|
|
|
void NotifyAll() { monitor_->NotifyAll(); }
|
|
|
|
private:
|
|
Monitor* const monitor_;
|
|
bool no_safepoint_scope_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MonitorLocker);
|
|
};
|
|
|
|
// Leaves the given monitor during the scope of the object.
|
|
class MonitorLeaveScope : public ValueObject {
|
|
public:
|
|
explicit MonitorLeaveScope(MonitorLocker* monitor)
|
|
: monitor_locker_(monitor) {
|
|
monitor_locker_->Exit();
|
|
}
|
|
|
|
virtual ~MonitorLeaveScope() { monitor_locker_->Enter(); }
|
|
|
|
private:
|
|
MonitorLocker* const monitor_locker_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MonitorLeaveScope);
|
|
};
|
|
|
|
/*
|
|
* Safepoint mutex locker :
|
|
* This locker abstraction should be used when the enclosing code could
|
|
* potentially trigger a safepoint.
|
|
* This locker ensures that other threads that try to acquire the same lock
|
|
* will be marked as being at a safepoint if they get blocked trying to
|
|
* acquire the lock.
|
|
* NOTE: please do not use the passed in mutex object independent of the locker
|
|
* class, For example the code below will not work correctly:
|
|
* {
|
|
* SafepointMutexLocker ml(m);
|
|
* ....
|
|
* m->Exit();
|
|
* ....
|
|
* m->Enter();
|
|
* ...
|
|
* }
|
|
*/
|
|
class SafepointMutexLocker : public StackResource {
|
|
public:
|
|
explicit SafepointMutexLocker(Mutex* mutex)
|
|
: SafepointMutexLocker(ThreadState::Current(), mutex) {}
|
|
SafepointMutexLocker(ThreadState* thread, Mutex* mutex);
|
|
virtual ~SafepointMutexLocker() { mutex_->Unlock(); }
|
|
|
|
private:
|
|
Mutex* const mutex_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(SafepointMutexLocker);
|
|
};
|
|
|
|
/*
|
|
* Safepoint monitor locker :
|
|
* This locker abstraction should be used when the enclosing code could
|
|
* potentially trigger a safepoint.
|
|
* This locker ensures that other threads that try to acquire the same lock
|
|
* will be marked as being at a safepoint if they get blocked trying to
|
|
* acquire the lock.
|
|
* NOTE: please do not use the passed in monitor object independent of the
|
|
* locker class, For example the code below will not work correctly:
|
|
* {
|
|
* SafepointMonitorLocker ml(m);
|
|
* ....
|
|
* m->Exit();
|
|
* ....
|
|
* m->Enter();
|
|
* ...
|
|
* }
|
|
*/
|
|
class SafepointMonitorLocker : public ValueObject {
|
|
public:
|
|
explicit SafepointMonitorLocker(Monitor* monitor) : monitor_(monitor) {
|
|
AcquireLock();
|
|
}
|
|
virtual ~SafepointMonitorLocker() { ReleaseLock(); }
|
|
|
|
Monitor::WaitResult Wait(int64_t millis = Monitor::kNoTimeout);
|
|
|
|
void NotifyAll() { monitor_->NotifyAll(); }
|
|
|
|
private:
|
|
friend class SafepointMonitorUnlockScope;
|
|
|
|
void AcquireLock();
|
|
void ReleaseLock();
|
|
|
|
Monitor* const monitor_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(SafepointMonitorLocker);
|
|
};
|
|
|
|
class SafepointMonitorUnlockScope : public ValueObject {
|
|
public:
|
|
explicit SafepointMonitorUnlockScope(SafepointMonitorLocker* locker)
|
|
: locker_(locker) {
|
|
locker_->ReleaseLock();
|
|
}
|
|
~SafepointMonitorUnlockScope() { locker_->AcquireLock(); }
|
|
|
|
private:
|
|
SafepointMonitorLocker* locker_;
|
|
};
|
|
|
|
class RwLock {
|
|
public:
|
|
RwLock() {}
|
|
~RwLock() {}
|
|
|
|
bool IsCurrentThreadWriter() {
|
|
return writer_id_ == OSThread::GetCurrentThreadId();
|
|
}
|
|
|
|
private:
|
|
friend class ReadRwLocker;
|
|
friend class WriteRwLocker;
|
|
|
|
void EnterRead() {
|
|
MonitorLocker ml(&monitor_);
|
|
while (state_ == -1) {
|
|
ml.Wait();
|
|
}
|
|
++state_;
|
|
}
|
|
void LeaveRead() {
|
|
MonitorLocker ml(&monitor_);
|
|
ASSERT(state_ > 0);
|
|
if (--state_ == 0) {
|
|
ml.NotifyAll();
|
|
}
|
|
}
|
|
|
|
void EnterWrite() {
|
|
MonitorLocker ml(&monitor_);
|
|
while (state_ != 0) {
|
|
ml.Wait();
|
|
}
|
|
state_ = -1;
|
|
writer_id_ = OSThread::GetCurrentThreadId();
|
|
}
|
|
|
|
void LeaveWrite() {
|
|
MonitorLocker ml(&monitor_);
|
|
ASSERT(state_ == -1);
|
|
state_ = 0;
|
|
writer_id_ = OSThread::kInvalidThreadId;
|
|
ml.NotifyAll();
|
|
}
|
|
|
|
Monitor monitor_;
|
|
// [state_] > 0 : The lock is held by multiple readers.
|
|
// [state_] == 0 : The lock is free (no readers/writers).
|
|
// [state_] == -1: The lock is held by a single writer.
|
|
intptr_t state_ = 0;
|
|
ThreadId writer_id_ = OSThread::kInvalidThreadId;
|
|
};
|
|
|
|
class SafepointRwLock {
|
|
public:
|
|
explicit SafepointRwLock(
|
|
SafepointLevel expected_safepoint_level = SafepointLevel::kGC)
|
|
: expected_safepoint_level_(expected_safepoint_level) {}
|
|
~SafepointRwLock() {}
|
|
|
|
DEBUG_ONLY(bool IsCurrentThreadReader());
|
|
|
|
bool IsCurrentThreadWriter() {
|
|
return writer_id_ == OSThread::GetCurrentThreadId();
|
|
}
|
|
|
|
private:
|
|
friend class SafepointReadRwLocker;
|
|
friend class SafepointWriteRwLocker;
|
|
|
|
// returns [true] if read lock was acquired,
|
|
// returns [false] if the thread didn't have to acquire read lock due
|
|
// to the thread already holding write lock
|
|
bool EnterRead();
|
|
bool TryEnterRead(bool can_block, bool* acquired_read_lock);
|
|
void LeaveRead();
|
|
|
|
void EnterWrite();
|
|
bool TryEnterWrite(bool can_block);
|
|
void LeaveWrite();
|
|
|
|
const SafepointLevel expected_safepoint_level_;
|
|
|
|
// We maintain an invariant that this monitor is never locked for long periods
|
|
// of time: Any thread that acquired this monitor must always be able to do
|
|
// it's work and release it (or wait on the monitor which will also release
|
|
// it).
|
|
//
|
|
// In particular we must ensure the monitor is never held and then a potential
|
|
// safepoint operation is triggered, since another thread could try to acquire
|
|
// the lock and it would deadlock.
|
|
Monitor monitor_;
|
|
|
|
// [state_] > 0 : The lock is held by multiple readers.
|
|
// [state_] == 0 : The lock is free (no readers/writers).
|
|
// [state_] < 0 : The lock is held by a single writer (possibly nested).
|
|
intptr_t state_ = 0;
|
|
|
|
DEBUG_ONLY(MallocGrowableArray<ThreadId> readers_ids_);
|
|
ThreadId writer_id_ = OSThread::kInvalidThreadId;
|
|
};
|
|
|
|
/*
|
|
* Locks a given [RwLock] for reading purposes.
|
|
*
|
|
* It will block while the lock is held by a writer.
|
|
*
|
|
* If this locker is long'jmped over (e.g. on a background compiler thread) the
|
|
* lock will be freed.
|
|
*
|
|
* NOTE: If the locking operation blocks (due to a writer) it will not check
|
|
* for a pending safepoint operation.
|
|
*/
|
|
class ReadRwLocker : public StackResource {
|
|
public:
|
|
ReadRwLocker(ThreadState* thread_state, RwLock* rw_lock)
|
|
: StackResource(thread_state), rw_lock_(rw_lock) {
|
|
rw_lock_->EnterRead();
|
|
}
|
|
~ReadRwLocker() { rw_lock_->LeaveRead(); }
|
|
|
|
private:
|
|
RwLock* rw_lock_;
|
|
};
|
|
|
|
/*
|
|
* In addition to what [ReadRwLocker] does, this implementation also gets into a
|
|
* safepoint if necessary.
|
|
*/
|
|
class SafepointReadRwLocker : public StackResource {
|
|
public:
|
|
SafepointReadRwLocker(ThreadState* thread_state, SafepointRwLock* rw_lock)
|
|
: StackResource(thread_state), rw_lock_(rw_lock) {
|
|
ASSERT(rw_lock_ != nullptr);
|
|
if (!rw_lock_->EnterRead()) {
|
|
// if lock didn't have to be acquired, it doesn't have to be released.
|
|
rw_lock_ = nullptr;
|
|
}
|
|
}
|
|
~SafepointReadRwLocker() {
|
|
if (rw_lock_ != nullptr) {
|
|
rw_lock_->LeaveRead();
|
|
}
|
|
}
|
|
|
|
private:
|
|
SafepointRwLock* rw_lock_;
|
|
};
|
|
|
|
/*
|
|
* Locks a given [RwLock] for writing purposes.
|
|
*
|
|
* It will block while the lock is held by one or more readers.
|
|
*
|
|
* If this locker is long'jmped over (e.g. on a background compiler thread) the
|
|
* lock will be freed.
|
|
*
|
|
* NOTE: If the locking operation blocks (due to a writer) it will not check
|
|
* for a pending safepoint operation.
|
|
*/
|
|
class WriteRwLocker : public StackResource {
|
|
public:
|
|
WriteRwLocker(ThreadState* thread_state, RwLock* rw_lock)
|
|
: StackResource(thread_state), rw_lock_(rw_lock) {
|
|
rw_lock_->EnterWrite();
|
|
}
|
|
|
|
~WriteRwLocker() { rw_lock_->LeaveWrite(); }
|
|
|
|
private:
|
|
RwLock* rw_lock_;
|
|
};
|
|
|
|
/*
|
|
* In addition to what [WriteRwLocker] does, this implementation also gets into a
|
|
* safepoint if necessary.
|
|
*/
|
|
class SafepointWriteRwLocker : public StackResource {
|
|
public:
|
|
SafepointWriteRwLocker(ThreadState* thread_state, SafepointRwLock* rw_lock)
|
|
: StackResource(thread_state), rw_lock_(rw_lock) {
|
|
rw_lock_->EnterWrite();
|
|
}
|
|
|
|
~SafepointWriteRwLocker() { rw_lock_->LeaveWrite(); }
|
|
|
|
private:
|
|
SafepointRwLock* rw_lock_;
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_VM_LOCKERS_H_
|