// 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(); } else { regular_mutex = std::make_unique(); } } 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::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::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::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 guard(debug_mutex); stack[std::string(function)]++; } } void Mutex::removeDebugLock(const char* function) { if (function) { std::lock_guard 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 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; } }