/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see .
*/
#include "../common/Log.h"
#include "../common/debug.h"
#include "../common/Mutex.h"
Mutex::Mutex() {
readers = 0;
mlocked = false;
writing = false;
name = "";
#ifdef DEBUG
stack.clear();
#endif
//CSLock is a pointer so we can use a different attribute type on create
CSLock = new CriticalSection(MUTEX_ATTRIBUTE_RECURSIVE);
}
Mutex::~Mutex() {
safe_delete(CSLock);
#ifdef DEBUG
stack.clear();
#endif
}
void Mutex::SetName(string in_name) {
#ifdef DEBUG
name = in_name;
#endif
}
void Mutex::lock() {
#ifdef DEBUG
int i = 0;
#endif
if (name.length() > 0) {
while (mlocked) {
#ifdef DEBUG
if (i > MUTEX_TIMEOUT_MILLISECONDS) {
LogWrite(MUTEX__ERROR, 0, "Mutex", "Possible deadlock attempt by '%s'!", name.c_str());
return;
}
i++;
#endif
Sleep(1);
}
}
mlocked = true;
CSLock->lock();
}
bool Mutex::trylock() {
return CSLock->trylock();
}
void Mutex::unlock() {
CSLock->unlock();
mlocked = false;
}
void Mutex::readlock(const char* function, int32 line) {
#ifdef DEBUG
int32 i = 0;
#endif
while (true) {
//Loop until there isn't a writer, then we can read!
CSRead.lock();
if (!writing) {
readers++;
CSRead.unlock();
#ifdef DEBUG
CSStack.lock();
if (function)
stack[(string)function]++;
CSStack.unlock();
#endif
return;
}
CSRead.unlock();
#ifdef DEBUG
if (i > MUTEX_TIMEOUT_MILLISECONDS) {
LogWrite(MUTEX__ERROR, 0, "Mutex", "The mutex %s called from %s at line %u timed out waiting for a readlock!", name.c_str(), function ? function : "name_not_provided", line);
LogWrite(MUTEX__ERROR, 0, "Mutex", "The following functions had locks:");
map::iterator itr;
CSStack.lock();
for (itr = stack.begin(); itr != stack.end(); itr++) {
if (itr->second > 0 && itr->first.length() > 0)
LogWrite(MUTEX__ERROR, 0, "Mutex", "%s, number of locks = %u", itr->first.c_str(), itr->second);
}
CSStack.unlock();
return;
}
i++;
#endif
Sleep(1);
}
}
void Mutex::releasereadlock(const char* function, int32 line) {
//Wait for the readcount lock
CSRead.lock();
//Lower the readcount by one, when readcount is 0 writers may start writing
readers--;
CSRead.unlock();
#ifdef DEBUG
CSStack.lock();
if (function) {
map::iterator itr = stack.find((string)function);
if (itr != stack.end()) {
if (--(itr->second) == 0) {
stack.erase(itr);
}
}
}
CSStack.unlock();
#endif
}
bool Mutex::tryreadlock(const char* function) {
//This returns true if able to instantly obtain a readlock, false if not
CSRead.lock();
if (!writing) {
readers++;
CSRead.unlock();
}
else {
CSRead.unlock();
return false;
}
#ifdef DEBUG
CSStack.lock();
if (function)
stack[(string)function]++;
CSStack.unlock();
#endif
return true;
}
void Mutex::writelock(const char* function, int32 line) {
//Wait until the writer lock becomes available, then we can be the only writer!
#ifdef DEBUG
int32 i = 0;
#endif
while (!CSWrite.trylock()) {
#ifdef DEBUG
if (i > MUTEX_TIMEOUT_MILLISECONDS) {
LogWrite(MUTEX__ERROR, 0, "Mutex", "The mutex %s called from %s at line %u timed out waiting on another writelock!", name.c_str(), function ? function : "name_not_provided", line);
LogWrite(MUTEX__ERROR, 0, "Mutex", "The following functions had locks:");
map::iterator itr;
CSStack.lock();
for (itr = stack.begin(); itr != stack.end(); itr++) {
if (itr->second > 0 && itr->first.length() > 0)
LogWrite(MUTEX__ERROR, 0, "Mutex", "%s, number of locks = %u", itr->first.c_str(), itr->second);
}
CSStack.unlock();
i = 0;
continue;
}
i++;
#endif
Sleep(1);
}
waitReaders(function, line);
#ifdef DEBUG
CSStack.lock();
if (function)
stack[(string)function]++;
CSStack.unlock();
#endif
}
void Mutex::releasewritelock(const char* function, int32 line) {
//Wait for the readcount lock
CSRead.lock();
//Readers are aloud again
writing = false;
CSRead.unlock();
//Allow other writers to write
CSWrite.unlock();
#ifdef DEBUG
CSStack.lock();
if (function) {
map::iterator itr = stack.find((string)function);
if (itr != stack.end()) {
if (--(itr->second) == 0) {
stack.erase(itr);
}
}
}
CSStack.unlock();
#endif
}
bool Mutex::trywritelock(const char* function) {
//This returns true if able to instantly obtain a writelock, false if not
if (CSWrite.trylock()) {
CSRead.lock();
if (readers == 0)
writing = true;
CSRead.unlock();
if (!writing) {
CSWrite.unlock();
return false;
}
}
else
return false;
#ifdef DEBUG
CSStack.lock();
if (function)
stack[(string)function]++;
CSStack.unlock();
#endif
return true;
}
void Mutex::waitReaders(const char* function, int32 line)
{
//Wait for all current readers to stop, then we can write!
#ifdef DEBUG
int32 i = 0;
#endif
while (true)
{
CSRead.lock();
if (readers == 0)
{
writing = true;
CSRead.unlock();
break;
}
CSRead.unlock();
#ifdef DEBUG
if (i > MUTEX_TIMEOUT_MILLISECONDS) {
LogWrite(MUTEX__ERROR, 0, "Mutex", "The mutex %s called from %s at line %u timed out while waiting on readers!", name.c_str(), function ? function : "name_not_provided", line);
LogWrite(MUTEX__ERROR, 0, "Mutex", "The following functions had locks:");
map::iterator itr;
CSStack.lock();
for (itr = stack.begin(); itr != stack.end(); itr++) {
if (itr->second > 0 && itr->first.length() > 0)
LogWrite(MUTEX__ERROR, 0, "Mutex", "%s, number of locks = %u", itr->first.c_str(), itr->second);
}
CSStack.unlock();
i = 0;
continue;
}
i++;
#endif
Sleep(1);
}
}
LockMutex::LockMutex(Mutex* in_mut, bool iLock) {
mut = in_mut;
locked = iLock;
if (locked) {
mut->lock();
}
}
LockMutex::~LockMutex() {
if (locked) {
mut->unlock();
}
}
void LockMutex::unlock() {
if (locked)
mut->unlock();
locked = false;
}
void LockMutex::lock() {
if (!locked)
mut->lock();
locked = true;
}
CriticalSection::CriticalSection(int attribute) {
#ifdef WIN32
InitializeCriticalSection(&CSMutex);
#else
pthread_mutexattr_init(&type_attribute);
switch (attribute)
{
case MUTEX_ATTRIBUTE_FAST:
pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_FAST_NP);
break;
case MUTEX_ATTRIBUTE_RECURSIVE:
pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_RECURSIVE_NP);
break;
case MUTEX_ATTRIBUTE_ERRORCHK:
pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_ERRORCHECK_NP);
break;
default:
LogWrite(MUTEX__DEBUG, 0, "Critical Section", "Invalid mutex attribute type! Using PTHREAD_MUTEX_FAST_NP");
pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_FAST_NP);
break;
}
pthread_mutex_init(&CSMutex, &type_attribute);
#endif
}
CriticalSection::~CriticalSection() {
#ifdef WIN32
DeleteCriticalSection(&CSMutex);
#else
pthread_mutex_destroy(&CSMutex);
pthread_mutexattr_destroy(&type_attribute);
#endif
}
void CriticalSection::lock() {
//Waits for a lock on this critical section
#ifdef WIN32
EnterCriticalSection(&CSMutex);
#else
pthread_mutex_lock(&CSMutex);
#endif
}
void CriticalSection::unlock() {
//Gets rid of one of the current thread's locks on this critical section
#ifdef WIN32
LeaveCriticalSection(&CSMutex);
#else
pthread_mutex_unlock(&CSMutex);
#endif
}
bool CriticalSection::trylock() {
//Returns true if able to instantly get a lock on this critical section, false if not
#ifdef WIN32
return TryEnterCriticalSection(&CSMutex);
#else
return (pthread_mutex_trylock(&CSMutex) == 0);
#endif
}