Emu/source/common/mutex.cpp
2025-09-06 21:43:05 -05:00

239 lines
5.1 KiB
C++

// Copyright (C) 2007-2025 EQ2EMulator
// Licensed under GPL v3
#include "mutex.h"
// CriticalSection implementation
CriticalSection::CriticalSection(int attribute) : mutex_type(attribute)
{
if (mutex_type == MUTEX_ATTRIBUTE_RECURSIVE) {
recursive_mutex = std::make_unique<std::recursive_mutex>();
} else {
regular_mutex = std::make_unique<std::mutex>();
}
}
void CriticalSection::lock()
{
if (recursive_mutex) {
recursive_mutex->lock();
} else {
regular_mutex->lock();
}
}
void CriticalSection::unlock()
{
if (recursive_mutex) {
recursive_mutex->unlock();
} else {
regular_mutex->unlock();
}
}
bool CriticalSection::trylock()
{
if (recursive_mutex) {
return recursive_mutex->try_lock();
} else {
return regular_mutex->try_lock();
}
}
// Mutex implementation
Mutex::Mutex() : name("")
{
#ifdef DEBUG
stack.clear();
#endif
}
void Mutex::SetName(std::string in_name)
{
name = in_name;
}
void Mutex::lock()
{
#ifdef DEBUG
if (!name.empty()) {
auto start = std::chrono::steady_clock::now();
while (!basic_mutex.try_lock()) {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
if (elapsed > MUTEX_TIMEOUT_MILLISECONDS) {
LogWrite(MUTEX__ERROR, 0, "Mutex", "Possible deadlock attempt by '%s'!", name.c_str());
return;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
} else {
basic_mutex.lock();
}
#else
basic_mutex.lock();
#endif
}
bool Mutex::trylock()
{
return basic_mutex.try_lock();
}
void Mutex::unlock()
{
basic_mutex.unlock();
}
void Mutex::readlock([[maybe_unused]] const char* function, [[maybe_unused]] std::int32_t line)
{
#ifdef DEBUG
auto start = std::chrono::steady_clock::now();
while (!rw_mutex.try_lock_shared()) {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
if (elapsed > MUTEX_TIMEOUT_MILLISECONDS) {
logDeadlock(function, line, "readlock");
start = std::chrono::steady_clock::now(); // Reset timer and continue
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
addDebugLock(function);
#else
rw_mutex.lock_shared();
#endif
}
void Mutex::releasereadlock([[maybe_unused]] const char* function, [[maybe_unused]] std::int32_t line)
{
rw_mutex.unlock_shared();
#ifdef DEBUG
removeDebugLock(function);
#endif
}
bool Mutex::tryreadlock([[maybe_unused]] const char* function)
{
bool result = rw_mutex.try_lock_shared();
#ifdef DEBUG
if (result && function) {
addDebugLock(function);
}
#endif
return result;
}
void Mutex::writelock([[maybe_unused]] const char* function, [[maybe_unused]] std::int32_t line)
{
#ifdef DEBUG
auto start = std::chrono::steady_clock::now();
while (!rw_mutex.try_lock()) {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
if (elapsed > MUTEX_TIMEOUT_MILLISECONDS) {
logDeadlock(function, line, "writelock");
start = std::chrono::steady_clock::now(); // Reset timer and continue
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
addDebugLock(function);
#else
rw_mutex.lock();
#endif
}
void Mutex::releasewritelock([[maybe_unused]] const char* function, [[maybe_unused]] std::int32_t line)
{
rw_mutex.unlock();
#ifdef DEBUG
removeDebugLock(function);
#endif
}
bool Mutex::trywritelock([[maybe_unused]] const char* function)
{
bool result = rw_mutex.try_lock();
#ifdef DEBUG
if (result && function) {
addDebugLock(function);
}
#endif
return result;
}
void Mutex::waitReaders(const char* function, std::int32_t line)
{
// Wait until we can get a write lock (which means no readers)
writelock(function, line);
releasewritelock(function, line);
}
#ifdef DEBUG
void Mutex::addDebugLock(const char* function)
{
if (function) {
std::lock_guard<std::mutex> guard(debug_mutex);
stack[std::string(function)]++;
}
}
void Mutex::removeDebugLock(const char* function)
{
if (function) {
std::lock_guard<std::mutex> guard(debug_mutex);
auto itr = stack.find(std::string(function));
if (itr != stack.end()) {
if (--(itr->second) == 0) {
stack.erase(itr);
}
}
}
}
void Mutex::logDeadlock(const char* function, std::int32_t line, const char* lock_type)
{
LogWrite(MUTEX__ERROR, 0, "Mutex", "The mutex %s called from %s at line %u timed out waiting for a %s!",
name.c_str(), function ? function : "name_not_provided", line, lock_type);
LogWrite(MUTEX__ERROR, 0, "Mutex", "The following functions had locks:");
std::lock_guard<std::mutex> guard(debug_mutex);
for (const auto& [func_name, count] : stack) {
if (count > 0 && !func_name.empty()) {
LogWrite(MUTEX__ERROR, 0, "Mutex", "%s, number of locks = %u", func_name.c_str(), count);
}
}
}
#endif
// LockMutex implementation
LockMutex::LockMutex(Mutex* in_mut, bool iLock)
: locked(false), mut(in_mut)
{
if (mut && iLock) {
lock();
}
}
LockMutex::~LockMutex()
{
if (locked) {
unlock();
}
}
void LockMutex::lock()
{
if (mut && !locked) {
mut->lock();
locked = true;
}
}
void LockMutex::unlock()
{
if (mut && locked) {
mut->unlock();
locked = false;
}
}