| //===-- ProcessRunLock.cpp ------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "lldb/Host/ProcessRunLock.h" | |
| #include "llvm/Support/ErrorHandling.h" | |
| #include "llvm/Support/Threading.h" | |
| #include <cassert> | |
| #include <cstdint> | |
| namespace lldb_private { | |
| #ifndef _WIN32 | |
| ProcessRunLock::ProcessRunLock() { | |
| int err = ::pthread_rwlock_init(&m_rwlock, nullptr); | |
| (void)err; | |
| } | |
| ProcessRunLock::~ProcessRunLock() { | |
| int err = ::pthread_rwlock_destroy(&m_rwlock); | |
| (void)err; | |
| } | |
| bool ProcessRunLock::ReadTryLock() { | |
| ::pthread_rwlock_rdlock(&m_rwlock); | |
| if (!m_running) { | |
| // coverity[missing_unlock] | |
| return true; | |
| } | |
| ::pthread_rwlock_unlock(&m_rwlock); | |
| return false; | |
| } | |
| bool ProcessRunLock::ReadUnlock() { | |
| return ::pthread_rwlock_unlock(&m_rwlock) == 0; | |
| } | |
| bool ProcessRunLock::SetRunning() { | |
| ::pthread_rwlock_wrlock(&m_rwlock); | |
| bool was_stopped = !m_running; | |
| m_running = true; | |
| ::pthread_rwlock_unlock(&m_rwlock); | |
| return was_stopped; | |
| } | |
| bool ProcessRunLock::SetStopped() { | |
| ::pthread_rwlock_wrlock(&m_rwlock); | |
| bool was_running = m_running; | |
| m_running = false; | |
| ::pthread_rwlock_unlock(&m_rwlock); | |
| return was_running; | |
| } | |
| #endif // !_WIN32 | |
| ProcessRunLock::ProcessRunLocker::ProcessRunLocker(ProcessRunLocker &&other) { | |
| if (other.m_lock && other.m_thread != llvm::get_threadid()) | |
| llvm::report_fatal_error( | |
| "ProcessRunLocker moved across threads while held"); | |
| assert(!m_lock && "move-construct into a held ProcessRunLocker"); | |
| m_lock = other.m_lock; | |
| m_thread = other.m_thread; | |
| other.m_lock = nullptr; | |
| } | |
| ProcessRunLock::ProcessRunLocker & | |
| ProcessRunLock::ProcessRunLocker::operator=(ProcessRunLocker &&other) { | |
| if (this != &other) { | |
| if (other.m_lock && other.m_thread != llvm::get_threadid()) | |
| llvm::report_fatal_error( | |
| "ProcessRunLocker move-assigned across threads while held"); | |
| Unlock(); | |
| m_lock = other.m_lock; | |
| m_thread = other.m_thread; | |
| other.m_lock = nullptr; | |
| } | |
| return *this; | |
| } | |
| bool ProcessRunLock::ProcessRunLocker::TryLock(ProcessRunLock *lock) { | |
| if (m_lock) { | |
| if (m_lock == lock) | |
| return true; | |
| Unlock(); | |
| } | |
| if (!lock) | |
| return false; | |
| const uint64_t this_thread = llvm::get_threadid(); | |
| // Fast path: already holding as a reader on this thread, bump the count. | |
| { | |
| std::lock_guard<std::mutex> guard(lock->m_recursion_mutex); | |
| auto it = lock->m_recursion.find(this_thread); | |
| if (it != lock->m_recursion.end()) { | |
| ++it->second; | |
| m_lock = lock; | |
| m_thread = this_thread; | |
| return true; | |
| } | |
| } | |
| // Acquire the rwlock with m_recursion_mutex released: ReadTryLock can | |
| // block waiting for a writer, and holding the recursion mutex through | |
| // that wait would stall fast-path bumps that only need the map. | |
| if (!lock->ReadTryLock()) | |
| return false; | |
| std::lock_guard<std::mutex> guard(lock->m_recursion_mutex); | |
| lock->m_recursion[this_thread] = 1; | |
| m_lock = lock; | |
| m_thread = this_thread; | |
| return true; | |
| } | |
| void ProcessRunLock::ProcessRunLocker::Unlock() { | |
| if (!m_lock) | |
| return; | |
| const uint64_t this_thread = llvm::get_threadid(); | |
| if (m_thread != this_thread) | |
| // pthread_rwlock_unlock from a different thread than the one that | |
| // called pthread_rwlock_rdlock is UB. The move ctor / operator= are | |
| // the only way a held locker can change object identity, and both | |
| // fatal on cross-thread transfer; this is the last-line check on | |
| // the destructor path in case a held locker escaped that trap. | |
| llvm::report_fatal_error( | |
| "ProcessRunLocker destroyed on a different thread while held"); | |
| bool release_rwlock = false; | |
| { | |
| std::lock_guard<std::mutex> guard(m_lock->m_recursion_mutex); | |
| auto it = m_lock->m_recursion.find(this_thread); | |
| assert( | |
| it != m_lock->m_recursion.end() && | |
| "ProcessRunLocker released without a matching TryLock on this thread"); | |
| if (it == m_lock->m_recursion.end()) { | |
| m_lock = nullptr; | |
| return; | |
| } | |
| if (--it->second == 0) { | |
| m_lock->m_recursion.erase(it); | |
| release_rwlock = true; | |
| } | |
| } | |
| if (release_rwlock) | |
| m_lock->ReadUnlock(); | |
| m_lock = nullptr; | |
| } | |
| } // namespace lldb_private |