sharedreaderlock.cpp 8.26 KB
/* ****************************************************************************
 * 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<std::thread::id, LockData>& 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;
    }
}