/*
* 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