/* **************************************************************************** * Copyright 2019 Open Systems Development BV * * * * 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 "sharedreaderlock.h" // #include "mlogic/common/invalidoperationexception.h" #include "lockguard.h" #include "mqttstream.h" // #include "mlogic/common/logger/loggerprovider.h" using namespace osdev::components::mqtt; // What happens if a read lock owner locks again // - another read lock : map the threads owning read locks and update a count. // - a write lock : upgrade the read lock to an exclusive write lock (including all recursive locks). // // The same for a write lock owner. // - another write lock : map the threads owning a write lock and update the count. // - a read lock : treat the lock as an exclusive write lock // // What happens if a thread that never locks does an unlock. // - when no locks are active : nothing happens // - during active read locks : nothing happens // - during active write lock : nothing happens // // What happens when multiple threads want a write lock. // - Only one writer can be active at any time. Other writers will have to wait until the active writer has unlocked. // namespace { /** * @param lockMap The map to check for active locks. * @return true when the lockMap contains an active lock, false otherwise. */ bool hasActiveLock(const std::map& lockMap) { for (const auto& lockPair : lockMap) { if (lockPair.second.active()) { return true; } } return false; } } // namespace SharedReaderLock::SharedReaderLock() : m_mutex() , m_readLockMap() , m_writeLockMap() , m_readersCV() , m_writersCV() { } SharedReaderLock::~SharedReaderLock() { OSDEV_COMPONENTS_LOCKGUARD(m_mutex); if (!m_readLockMap.empty() || !m_writeLockMap.empty()) { // toLogFile ("SharedReaderLock", "Cannot destroy this lock because threads are still registered. Readers : %1, Writers : %2", m_readLockMap, m_writeLockMap); // (InvalidOperationException, "Cannot destroy SharedReaderLock"); } } void SharedReaderLock::lockShared() { OSDEV_COMPONENTS_UNIQUELOCK_CREATE(m_mutex); if (m_writeLockMap.end() != m_writeLockMap.find(std::this_thread::get_id())) { // If the thread owns a write lock than update the write lock count m_writeLockMap[std::this_thread::get_id()].increase(); return; } auto resultPair = m_readLockMap.insert(std::make_pair(std::this_thread::get_id(), LockData{})); if (!resultPair.second) { // thread has a read lock already... resultPair.first->second.increase(); // add recursive reader lock return; // Already owner of the shared lock, so proceed. } m_readersCV.wait(OSDEV_COMPONENTS_UNIQUELOCK(m_mutex), [this] { return m_writeLockMap.empty(); }); // Reread the iterator because it might have been invalidated. auto it = m_readLockMap.find(std::this_thread::get_id()); if (m_readLockMap.end() == it) { // normally Throw(InvalidOperationException, "Thread id must be registered in the shared reader map at this point"); } it->second.increase(); // the reader is now registered and is a shared owner of the SharedReaderLock } void SharedReaderLock::lockExclusive() { OSDEV_COMPONENTS_UNIQUELOCK_CREATE(m_mutex); // First check if the thread already owns a read lock. auto readLockIter = m_readLockMap.find(std::this_thread::get_id()); if (m_readLockMap.end() != readLockIter) { auto resultPair = m_writeLockMap.insert(std::make_pair(readLockIter->first, LockData::initialize(readLockIter->second))); if (!resultPair.second) { // throw (InvalidOperationException, "Thread id cannot be registered in the write map at this point"); } // toLogFile ("SharedReaderLock", "Upgrade to exclusive lock"); m_readLockMap.erase(readLockIter); // This is an upgrade of the read lock to an exclusive write lock. // proceed to the wait call on m_writersCV. } else { auto resultPair = m_writeLockMap.insert(std::make_pair(std::this_thread::get_id(), LockData{})); if (!resultPair.second) { // thread has a write lock already... resultPair.first->second.increase(); // add recursive write lock return; // Already owner of the exclusive lock, so proceed. } } m_writersCV.wait(OSDEV_COMPONENTS_UNIQUELOCK(m_mutex), [this] { return !hasActiveLock(m_readLockMap) && !hasActiveLock(m_writeLockMap); }); // Reread the iterator because it might have been invalidated. auto it = m_writeLockMap.find(std::this_thread::get_id()); if (m_writeLockMap.end() == it) { // throw (InvalidOperationException, "Thread id must be registered in the writer map at this point"); } it->second.increase(); // the thread is now registered and is an exclusive owner of the SharedReaderLock } void SharedReaderLock::unlock() { OSDEV_COMPONENTS_UNIQUELOCK_CREATE(m_mutex); auto readLockIter = m_readLockMap.find(std::this_thread::get_id()); auto writeLockIter = m_writeLockMap.find(std::this_thread::get_id()); if (m_writeLockMap.end() != writeLockIter && m_readLockMap.end() != readLockIter) { return; // thread does not have a lock. Do nothing. } if (m_readLockMap.end() != readLockIter) { if (!readLockIter->second.decrease()) { // The lock is not active anymore so remove it m_readLockMap.erase(readLockIter); if (!hasActiveLock(m_readLockMap) && !m_writeLockMap.empty()) { // no active read lock anymore and a writer that waits for an exclusive lock. // toLogFileDebug("SharedReaderLock", "notify_one writersCV"); OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK(m_mutex); m_writersCV.notify_one(); } } return; } if (m_writeLockMap.end() != writeLockIter) { if (!writeLockIter->second.decrease()) { // remove the exclusive write lock m_writeLockMap.erase(writeLockIter); if (m_writeLockMap.empty()) { // toLogFile ("SharedReaderLock", "notify_all readersCV"); OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK(m_mutex); m_readersCV.notify_all(); } else { // toLogFile ("SharedReaderLock", "notify_one writersCV"); OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK(m_mutex); m_writersCV.notify_one(); } } return; } }