From 3d6e08156d9ceff0f76d878f0f900a78c36685b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?kleines=20Filmr=C3=B6llchen?= Date: Sun, 2 Jan 2022 14:49:53 +0100 Subject: [PATCH] LibThreading: Introduce MutexProtected generic synchronization primitive MutexProtected mirrors the identically-named Kernel primitive and can be used to synchronize access to any object that might not be thread safe on its own. Synchronization is done with a simple mutex, so access to a MutexProtected object is potentially blocking. Mutex now has an internal nesting variable which is there to harden it against lock-unlock ordering issues (e.g. double unlocking). --- Userland/Libraries/LibThreading/Mutex.h | 21 ++++++- .../Libraries/LibThreading/MutexProtected.h | 56 +++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 Userland/Libraries/LibThreading/MutexProtected.h diff --git a/Userland/Libraries/LibThreading/Mutex.h b/Userland/Libraries/LibThreading/Mutex.h index 44921eab614..9ffb0613062 100644 --- a/Userland/Libraries/LibThreading/Mutex.h +++ b/Userland/Libraries/LibThreading/Mutex.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2021, Andreas Kling + * Copyright (c) 2021, kleines Filmröllchen * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -20,6 +22,7 @@ class Mutex { public: Mutex() + : m_lock_count(0) { #ifndef __serenity__ pthread_mutexattr_t attr; @@ -28,7 +31,11 @@ public: pthread_mutex_init(&m_mutex, &attr); #endif } - ~Mutex() = default; + ~Mutex() + { + VERIFY(m_lock_count == 0); + // FIXME: pthread_mutex_destroy() is not implemented. + } void lock(); void unlock(); @@ -39,6 +46,7 @@ private: #else pthread_mutex_t m_mutex; #endif + unsigned m_lock_count { 0 }; }; class MutexLocker { @@ -51,7 +59,10 @@ public: { lock(); } - ALWAYS_INLINE ~MutexLocker() { unlock(); } + ALWAYS_INLINE ~MutexLocker() + { + unlock(); + } ALWAYS_INLINE void unlock() { m_mutex.unlock(); } ALWAYS_INLINE void lock() { m_mutex.lock(); } @@ -62,10 +73,16 @@ private: ALWAYS_INLINE void Mutex::lock() { pthread_mutex_lock(&m_mutex); + m_lock_count++; } ALWAYS_INLINE void Mutex::unlock() { + VERIFY(m_lock_count > 0); + // FIXME: We need to protect the lock count with the mutex itself. + // This may be bad because we're not *technically* unlocked yet, + // but we're not handling any errors from pthread_mutex_unlock anyways. + m_lock_count--; pthread_mutex_unlock(&m_mutex); } diff --git a/Userland/Libraries/LibThreading/MutexProtected.h b/Userland/Libraries/LibThreading/MutexProtected.h new file mode 100644 index 00000000000..f12d2fea418 --- /dev/null +++ b/Userland/Libraries/LibThreading/MutexProtected.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022, kleines Filmröllchen . + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Threading { + +template +class MutexProtected { + AK_MAKE_NONCOPYABLE(MutexProtected); + AK_MAKE_NONMOVABLE(MutexProtected); + using ProtectedType = T; + +public: + ALWAYS_INLINE MutexProtected() = default; + ALWAYS_INLINE MutexProtected(T&& value) + : m_value(move(value)) + { + } + ALWAYS_INLINE explicit MutexProtected(T& value) + : m_value(value) + { + } + + template + decltype(auto) with_locked(Callback callback) + { + auto lock = this->lock(); + // This allows users to get a copy, but if we don't allow references through &m_value, it's even more complex. + return callback(m_value); + } + + template Callback> + void for_each_locked(Callback callback) + { + with_locked([&](auto& value) { + for (auto& item : value) + callback(item); + }); + } + +private: + [[nodiscard]] ALWAYS_INLINE MutexLocker lock() { return MutexLocker(m_lock); } + + T m_value; + Mutex m_lock {}; +}; + +}