DataMutex.h   [plain text]


/*
 * Copyright (C) 2019 Igalia, S.L.
 * Copyright (C) 2019 Metrological Group B.V.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * aint with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#pragma once

#include <wtf/Lock.h>
#include <wtf/Threading.h>

namespace WTF {

// By default invalid access checks are only done in Debug builds.
#if !defined(ENABLE_DATA_MUTEX_CHECKS)
#if defined(NDEBUG)
#define ENABLE_DATA_MUTEX_CHECKS 0
#else
#define ENABLE_DATA_MUTEX_CHECKS 1
#endif
#endif

#if ENABLE_DATA_MUTEX_CHECKS
#define DATA_MUTEX_CHECK(expr) RELEASE_ASSERT(expr)
#else
#define DATA_MUTEX_CHECK(expr)
#endif

template<typename LockType>
class OwnerAwareLockAdapter {
public:
    void lock()
    {
        DATA_MUTEX_CHECK(m_owner != &Thread::current()); // Thread attempted recursive lock (unsupported).
        m_lock.lock();
#if ENABLE_DATA_MUTEX_CHECKS
        ASSERT(!m_owner);
        m_owner = &Thread::current();
#endif
    }

    void unlock()
    {
#if ENABLE_DATA_MUTEX_CHECKS
        m_owner = nullptr;
#endif
        m_lock.unlock();
    }

    bool tryLock()
    {
        DATA_MUTEX_CHECK(m_owner != &Thread::current()); // Thread attempted recursive lock (unsupported).
        if (!m_lock.tryLock())
            return false;

#if ENABLE_DATA_MUTEX_CHECKS
        ASSERT(!m_owner);
        m_owner = &Thread::current();
#endif
        return true;
    }

    bool isLocked() const
    {
        return m_lock.isLocked();
    }

private:
#if ENABLE_DATA_MUTEX_CHECKS
    Thread* m_owner { nullptr }; // Use Thread* instead of RefPtr<Thread> since m_owner thread is always alive while m_owner is set.
#endif
    LockType m_lock;
};

using OwnerAwareLock = OwnerAwareLockAdapter<Lock>;

template<typename T, typename LockType = OwnerAwareLock>
class DataMutex {
    WTF_MAKE_FAST_ALLOCATED;
    WTF_MAKE_NONCOPYABLE(DataMutex);
public:
    template<typename ...Args>
    explicit DataMutex(Args&&... args)
        : m_data(std::forward<Args>(args)...)
    { }

    class LockedWrapper {
    public:
        explicit LockedWrapper(DataMutex& dataMutex)
            : m_mutex(dataMutex.m_mutex)
            , m_lockHolder(dataMutex.m_mutex)
            , m_data(dataMutex.m_data)
        { }

        T* operator->()
        {
            DATA_MUTEX_CHECK(m_mutex.isLocked());
            return &m_data;
        }

        T& operator*()
        {
            DATA_MUTEX_CHECK(m_mutex.isLocked());
            return m_data;
        }

        LockType& mutex()
        {
            return m_mutex;
        }

        Locker<LockType>& lockHolder()
        {
            return m_lockHolder;
        }

        // Used to avoid excessive brace scoping when only small parts of the code need to be run unlocked.
        // Please be mindful that accessing the wrapped data from the callback is unsafe and will fail on assertions.
        // It's helpful to use a minimal lambda capture to be conscious of what data you're having access to in these sections.
        void runUnlocked(WTF::Function<void()> callback)
        {
            m_mutex.unlock();
            callback();
            m_mutex.lock();
        }

    private:
        LockType& m_mutex;
        Locker<LockType> m_lockHolder;
        T& m_data;
    };

private:
    LockType m_mutex;
    T m_data;
};

} // namespace WTF