Skip to content

Instantly share code, notes, and snippets.

@fredemmott
Created January 3, 2026 16:13
Show Gist options
  • Select an option

  • Save fredemmott/e87d96d25318a4551c72c7b5df04b14a to your computer and use it in GitHub Desktop.

Select an option

Save fredemmott/e87d96d25318a4551c72c7b5df04b14a to your computer and use it in GitHub Desktop.
shared recursive mutex
// Copyright 2026 Fred Emmott <fred@fredemmott.com>
// SPDX-License-Identifier: MIT
#pragma once
#include <shared_mutex>
#include <thread>
#include <unordered_map>
#include "FredEmmott/GUI/assert.hpp"
namespace FredEmmott::utility {
class shared_recursive_mutex {
std::shared_mutex mSharedMutex;
std::atomic<std::thread::id> mWriteOwner {};
std::size_t mWriteDepth {};
static inline thread_local std::
unordered_map<const shared_recursive_mutex*, std::size_t>
mReaderDepths {};
[[nodiscard]]
bool try_lock_recursive() {
FUI_ASSERT(
!mReaderDepths.contains(this),
"READ to WRITE promotion deadlock detected");
if (
mWriteOwner.load(std::memory_order_relaxed)
== std::this_thread::get_id()) {
++mWriteDepth;
return true;
}
return false;
}
[[nodiscard]]
bool try_lock_shared_recursive() {
const auto it = mReaderDepths.find(this);
if (it == mReaderDepths.end()) {
return false;
}
auto& depth = it->second;
FUI_ASSERT(depth > 0);
++depth;
return true;
}
public:
[[nodiscard]]
bool try_lock() {
if (try_lock_recursive()) {
return true;
}
if (!mSharedMutex.try_lock()) {
return false;
}
mWriteOwner.store(std::this_thread::get_id(), std::memory_order_relaxed);
FUI_ASSERT(mWriteDepth == 0);
++mWriteDepth;
return true;
}
void lock() {
if (try_lock_recursive()) {
return;
}
mSharedMutex.lock();
mWriteOwner.store(std::this_thread::get_id(), std::memory_order_release);
FUI_ASSERT(mWriteDepth == 0);
++mWriteDepth;
}
void unlock() {
FUI_ASSERT(mWriteOwner == std::this_thread::get_id());
--mWriteDepth;
if (mWriteDepth > 0) {
return;
}
mWriteOwner.store({}, std::memory_order_release);
mSharedMutex.unlock();
}
[[nodiscard]]
bool try_lock_shared() {
if (try_lock_shared_recursive()) {
return true;
}
if (!mSharedMutex.try_lock_shared()) {
return false;
}
mReaderDepths.emplace(this, 1);
return true;
}
void lock_shared() {
if (try_lock_shared_recursive()) {
return;
}
mSharedMutex.lock_shared();
mReaderDepths.emplace(this, 1);
}
void unlock_shared() {
const auto it = mReaderDepths.find(this);
FUI_ASSERT(it != mReaderDepths.end());
auto& depth = it->second;
if (--depth > 0) {
return;
}
mReaderDepths.erase(it);
mSharedMutex.unlock_shared();
}
};
}// namespace FredEmmott::utility
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment