/* Copyright (C) 2019 * * This file is part of the osdev components suite * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #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; } }