// 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); }; /* * 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 != NULL); #if defined(DEBUG) if (no_safepoint_scope_) { Thread* thread = Thread::Current(); if (thread != NULL) { 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() {} 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; } void LeaveWrite() { MonitorLocker ml(&monitor_); ASSERT(state_ == -1); state_ = 0; 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; }; class SafepointRwLock { public: SafepointRwLock() {} ~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(); // 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 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_