239 lines
5.1 KiB
C++
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;
|
|
}
|
|
}
|