/* * ReaderWriterMutex.cpp * * Copyright (C) 2022 by Posit Software, PBC * * Unless you have received this program directly from Posit Software pursuant to the terms of a commercial license agreement * with Posit, then this program is licensed to you under the following terms: * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ #include #include #include namespace rstudio { namespace core { namespace thread { typedef boost::unique_lock Lock; // ReaderWriterMutex =================================================================================================== struct ReaderWriterMutex::Impl { bool IsWriting; unsigned int ReaderCount; boost::recursive_mutex Mutex; // This mutex is used to allow re-entrant lock behaviour on write. On read it's basically already re-entrant. boost::recursive_mutex WriteMutex; boost::condition_variable_any Condition; }; PRIVATE_IMPL_DELETER_IMPL(ReaderWriterMutex); ReaderWriterMutex::ReaderWriterMutex() : m_impl(new Impl()) { m_impl->IsWriting = false; m_impl->ReaderCount = 0; } ReaderWriterMutex::ReaderWriterMutex(ReaderWriterMutex&& in_other) noexcept : m_impl(std::move(in_other.m_impl)) { } void ReaderWriterMutex::lockRead() { // Acquire the lock. Lock lock(m_impl->Mutex); // Wait until writers are finished, unless we can acquire the write lock, because that means the writer is in this // thread. bool haveWriteLock = false; if (m_impl->IsWriting) { haveWriteLock = m_impl->WriteMutex.try_lock(); while (!haveWriteLock && m_impl->IsWriting) { m_impl->Condition.wait(lock); haveWriteLock = m_impl->WriteMutex.try_lock(); } } // Increment the number of readers. ++m_impl->ReaderCount; // Safely read. Also unlock the write lock if we locked it before. if (haveWriteLock) m_impl->WriteMutex.unlock(); } // This implementation is write-preferring because no more readers can start reading while there is a thread that // wishes to obtain the lock for write. Such an implementation is desirable when there would be many read operations and // few write operations. In such a case, a read-preferring implementation would result in write operations getting // effectively locked out. void ReaderWriterMutex::lockWrite() { // Acquire the lock. Lock lock(m_impl->Mutex); // Wait until we can get access to the write lock. while (!m_impl->WriteMutex.try_lock()) m_impl->Condition.wait(lock); // Notify other threads that you're waiting to write. m_impl->IsWriting = true; // Wait until current readers are finished. while (m_impl->ReaderCount > 0) m_impl->Condition.wait(lock); // Safely write. } void ReaderWriterMutex::unlockRead() { // Acquire the lock. Lock lock(m_impl->Mutex); // If this wasn't erroneously called, decrement the reader count. if (m_impl->ReaderCount > 0) { --m_impl->ReaderCount; // If there are no more readers, notify any waiters. if (m_impl->ReaderCount == 0) m_impl->Condition.notify_all(); } } void ReaderWriterMutex::unlockWrite() { Lock lock(m_impl->Mutex); // If this wasn't erroneously called, reset IsWriting and notify any waiters. if (m_impl->IsWriting) { m_impl->WriteMutex.unlock(); m_impl->IsWriting = false; m_impl->Condition.notify_all(); } } // ReaderLock ========================================================================================================== ReaderLock::ReaderLock(ReaderWriterMutex& in_mutex) : m_mutex(in_mutex) { m_mutex.lockRead(); } ReaderLock::~ReaderLock() { m_mutex.unlockRead(); } // WriterLock ========================================================================================================== WriterLock::WriterLock(ReaderWriterMutex& in_mutex) : m_mutex(in_mutex) { m_mutex.lockWrite(); } WriterLock::~WriterLock() { m_mutex.unlockWrite(); } } // namespace thread } // namespace rstudio } // namespace core