sharedreaderlock.cpp 7.38 KB
/* 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<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;
    }
}