Rework the thread utilities so that: We don't pay for a recursive mutex when we don't need the debugging capabilities, a race condition in the reader/writer locks is removed and the interfaces and (still inlined) implementations are separated for improved readability.

This commit is contained in:
Adam Reichold
2016-01-05 23:30:29 +01:00
parent b5aa2dcffc
commit 5883410354
4 changed files with 528 additions and 626 deletions

View File

@@ -21,7 +21,7 @@ set (BASESOURCEFILES
preferences.cc profilepanel.cc saveasdlg.cc preferences.cc profilepanel.cc saveasdlg.cc
saveformatpanel.cc soundman.cc splash.cc saveformatpanel.cc soundman.cc splash.cc
thumbnail.cc tonecurve.cc toolbar.cc thumbnail.cc tonecurve.cc toolbar.cc
guiutils.cc zoompanel.cc toolpanelcoord.cc guiutils.cc threadutils.cc zoompanel.cc toolpanelcoord.cc
thumbbrowserentrybase.cc batchqueueentry.cc thumbbrowserentrybase.cc batchqueueentry.cc
batchqueue.cc lwbutton.cc lwbuttonset.cc batchqueue.cc lwbutton.cc lwbuttonset.cc
batchqueuebuttonset.cc browserfilter.cc exiffiltersettings.cc batchqueuebuttonset.cc browserfilter.cc exiffiltersettings.cc

View File

@@ -29,11 +29,6 @@
using namespace std; using namespace std;
#if TRACE_MYRWMUTEX==1 && !defined NDEBUG
unsigned int MyReaderLock::readerLockCounter = 0;
unsigned int MyWriterLock::writerLockCounter = 0;
#endif
Glib::RefPtr<Gdk::Pixbuf> MyExpander::inconsistentPBuf; Glib::RefPtr<Gdk::Pixbuf> MyExpander::inconsistentPBuf;
Glib::RefPtr<Gdk::Pixbuf> MyExpander::enabledPBuf; Glib::RefPtr<Gdk::Pixbuf> MyExpander::enabledPBuf;
Glib::RefPtr<Gdk::Pixbuf> MyExpander::disabledPBuf; Glib::RefPtr<Gdk::Pixbuf> MyExpander::disabledPBuf;

292
rtgui/threadutils.cc Normal file
View File

@@ -0,0 +1,292 @@
/*
* This file is part of RawTherapee.
*
* Copyright (c) 2016 Adam Reichold <adam.reichold@t-online.de>
*
* RawTherapee 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.
*
* RawTherapee 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 RawTherapee. If not, see <http://www.gnu.org/licenses/>.
*/
#include "threadutils.h"
#include <csignal>
#include <iostream>
#ifdef WIN32
#include <windows.h>
#endif
#if STRICT_MUTEX && !NDEBUG
void MyMutex::checkLock ()
{
if (locked) {
std::cerr << "MyMutex already locked!" << std::endl;
#ifdef WIN32
DebugBreak ();
#else
raise (SIGTRAP);
#endif
}
locked = true;
}
void MyMutex::checkUnlock ()
{
if (!locked) {
std::cerr << "MyMutex already unlocked!" << std::endl;
#ifdef WIN32
DebugBreak ();
#else
raise (SIGTRAP);
#endif
}
locked = false;
}
#endif
#if !TRACE_MYRWMUTEX
void MyReaderLock::acquire ()
{
if (locked) {
return;
}
Glib::Threads::Mutex::Lock lock (mutex.mutex);
if (mutex.writerCount == 0) {
// There's no writer operating, we can increment the writer count which will lock writers.
++mutex.writerCount;
} else if (mutex.readerCount == 0) {
// The writer count is non null, but a reader can be the owner of the writer lock,
// which will be the case if the reader count is not zero too.
while (mutex.writerCount != 0) {
mutex.cond.wait(mutex.mutex);
}
// Then, we can increment the writer count.
++mutex.writerCount;
}
// Finally, we can increment the reader count as well.
++mutex.readerCount;
locked = true;
}
void MyReaderLock::release ()
{
if (!locked) {
return;
}
Glib::Threads::Mutex::Lock lock (mutex.mutex);
// decrement the writer number first...
--mutex.readerCount;
if (mutex.readerCount == 0) {
// ...if no more reader, we decrement the writer count as well...
--mutex.writerCount;
// ...and signal the next waiting reader/writer that it's free
mutex.cond.broadcast ();
}
locked = false;
}
void MyWriterLock::acquire ()
{
if (locked) {
return;
}
Glib::Threads::Mutex::Lock lock (mutex.mutex);
// The writer count is not zero, so we have to wait for it to be zero again...
while (mutex.writerCount != 0) {
mutex.cond.wait (mutex.mutex);
}
// ...then we can increment the writer count.
++mutex.writerCount;
locked = true;
}
void MyWriterLock::release ()
{
if (!locked) {
return;
}
Glib::Threads::Mutex::Lock lock (mutex.mutex);
// Decrement the writer number first...
if (--mutex.writerCount == 0) {
// ...and if the writer count is zero again, we can wake up the next writer or reader.
mutex.cond.broadcast ();
}
locked = false;
}
#else
namespace
{
std::ostream& trace (const char* file, int line)
{
const auto currentThread = Glib::Threads::Thread::self ();
return std::cout << currentThread << ":" << file << ":" << line << ": ";
}
}
void MyReaderLock::acquire (const char* file, int line)
{
if (locked) {
trace (file, line) << "MyReaderLock is already locked." << std::endl;
return;
}
trace (file, line) << "Acquiring MyReaderLock..." << std::endl;
Glib::Threads::Mutex::Lock lock (mutex.mutex);
if (mutex.writerCount == 0) {
// There's no writer operating, we can increment the writer count which will lock writers.
++mutex.writerCount;
} else if (mutex.readerCount == 0) {
// The writer count is non null, but a reader can be the owner of the writer lock,
// which will be the case if the reader count is not zero too.
while (mutex.writerCount != 0) {
trace (file, line) << "Waiting for current owner of MyWriterLock..." << std::endl
<< "\tOwner thread: " << mutex.ownerThread << std::endl
<< "\tLast writer file: " << mutex.lastWriterFile << std::endl
<< "\tLast writer line: " << mutex.lastWriterLine << std::endl;
mutex.cond.wait(mutex.mutex);
}
// Then, we can increment the writer count.
++mutex.writerCount;
mutex.ownerThread = Glib::Threads::Thread::self ();
mutex.lastWriterFile = file;
mutex.lastWriterLine = line;
}
// Finally, we can increment the reader count as well.
++mutex.readerCount;
trace (file, line) << "MyReaderLock is now locked, reader count is " << mutex.readerCount << ", writer count is " << mutex.writerCount << "." << std::endl;
locked = true;
}
void MyReaderLock::release (const char* file, int line)
{
if (!locked) {
trace (file, line) << "MyReaderLock is already unlocked." << std::endl;
return;
}
trace (file, line) << "Releasing MyReaderLock..." << std::endl;
Glib::Threads::Mutex::Lock lock (mutex.mutex);
// decrement the writer number first...
--mutex.readerCount;
if (mutex.readerCount == 0) {
// ...if no more reader, we decrement the writer count as well...
--mutex.writerCount;
// ...and signal the next waiting reader/writer that it's free
mutex.cond.broadcast ();
mutex.ownerThread = nullptr;
mutex.lastWriterFile = "";
mutex.lastWriterLine = 0;
}
trace (file, line) << "MyReaderLock is now unlocked, reader count is " << mutex.readerCount << ", writer count is " << mutex.writerCount << "." << std::endl;
locked = false;
}
void MyWriterLock::acquire (const char* file, int line)
{
if (locked) {
trace (file, line) << "MyWriterLock is already locked." << std::endl;
return;
}
trace (file, line) << "Acquiring MyWriterLock..." << std::endl;
Glib::Threads::Mutex::Lock lock (mutex.mutex);
// The writer count is not zero, so we have to wait for it to be zero again...
while (mutex.writerCount != 0) {
trace (file, line) << "Waiting for current owner of MyWriterLock..." << std::endl
<< "\tOwner thread: " << mutex.ownerThread << std::endl
<< "\tLast writer file: " << mutex.lastWriterFile << std::endl
<< "\tLast writer line: " << mutex.lastWriterLine << std::endl;
mutex.cond.wait (mutex.mutex);
}
// ...then we can increment the writer count.
++mutex.writerCount;
mutex.ownerThread = Glib::Threads::Thread::self ();
mutex.lastWriterFile = file;
mutex.lastWriterLine = line;
trace (file, line) << "MyWriterLock is now locked, reader count is " << mutex.readerCount << ", writer count is " << mutex.writerCount << "." << std::endl;
locked = true;
}
void MyWriterLock::release (const char* file, int line)
{
if (!locked) {
trace (file, line) << "MyWriterLock is already unlocked." << std::endl;
return;
}
trace (file, line) << "Releasing MyWriterLock..." << std::endl;
Glib::Threads::Mutex::Lock lock (mutex.mutex);
// Decrement the writer number first...
if (--mutex.writerCount == 0) {
// ...and if the writer count is zero again, we can wake up the next writer or reader.
mutex.cond.broadcast ();
mutex.ownerThread = nullptr;
mutex.lastWriterFile = "";
mutex.lastWriterLine = 0;
}
trace (file, line) << "MyWriterLock is now unlocked, reader count is " << mutex.readerCount << ", writer count is " << mutex.writerCount << "." << std::endl;
locked = false;
}
#endif

View File

@@ -16,30 +16,10 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with RawTherapee. If not, see <http://www.gnu.org/licenses/>. * along with RawTherapee. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef _THREADUTILS_ #ifndef _THREADUTILS_
#define _THREADUTILS_ #define _THREADUTILS_
#include <glibmm.h> // Uncomment this if you want to bypass the CMakeList options and force the values, but do not commit!
#include <csignal> // for raise()
#include <iostream>
#ifdef WIN32
#include <windows.h>
#endif
#ifdef NDEBUG
// We don't trace mutex
#undef TRACE_MYRWMUTEX
#define TRACE_MYRWMUTEX 0
#endif
// Uncomment this if you want to bypass the CMakeList options and force the values
// Of course, DO NOT COMMIT!
//#undef PROTECT_VECTORS //#undef PROTECT_VECTORS
//#define PROTECT_VECTORS 1 //#define PROTECT_VECTORS 1
//#undef TRACE_MYRWMUTEX //#undef TRACE_MYRWMUTEX
@@ -47,650 +27,285 @@
//#undef STRICT_MUTEX //#undef STRICT_MUTEX
//#define STRICT_MUTEX 1 //#define STRICT_MUTEX 1
/** #include <glibmm/threads.h>
* @brief Custom Mutex to replace Glib::Threads::Mutex, which behave differently on windows (recursive) and linux (non-recursive), by a recursive and "debugable" one
*
* This implementation will behave like a Glib::Threads::RecMutex (STRICT_MUTEX=0) or a Glib::Threads::Mutex (STRICT_MUTEX=1), but in this case, the application will
* crash instead of freezing.
*
* In Debug builds, a printf will let you know that the MyMutex was already locked
*
* The default and recommended mode is STRICT_MUTEX=1
*/
#ifdef WIN32 #if STRICT_MUTEX && NDEBUG
class MyMutex : public Glib::RecMutex using MyMutexBase = Glib::Threads::Mutex;
{
#else #else
class MyMutex : public Glib::Threads::RecMutex using MyMutexBase = Glib::Threads::RecMutex;
#endif
/**
* @brief Custom implementation to replace Glib::Threads::Mutex.
*
* Glib::Threads::Mutex shows different behaviour on Windows (recursive) and Linux (non-recursive).
* We therefore use a custom implementation that is optionally recursive and instrumented.
* It will behave like Glib::Threads::RecMutex (STRICT_MUTEX=0) or Glib::Threads::Mutex (STRICT_MUTEX=1).
* Debug builds with strict mutexes, will emit a message and crash immediately upon recursive locking.
*/
class MyMutex : private MyMutexBase
{ {
#endif
#if STRICT_MUTEX || !defined(NDEBUG)
private:
bool alreadyLocked;
#endif
public: public:
class MyLock; class MyLock;
#if STRICT_MUTEX || !defined(NDEBUG) MyMutex () = default;
MyMutex() : alreadyLocked(false) {} MyMutex (const MyMutex&) = delete;
#else MyMutex& operator= (const MyMutex&) = delete;
MyMutex() {}
#endif
void lock() void lock ();
bool trylock ();
void unlock ();
#if STRICT_MUTEX && !NDEBUG
private:
bool locked = false;
void checkLock ();
void checkUnlock ();
#endif
};
class MyMutex::MyLock
{ {
#ifdef WIN32 public:
Glib::RecMutex::lock(); explicit MyLock (MyMutex& mutex);
#else MyLock (MyMutex& mutex, Glib::Threads::NotLock);
Glib::Threads::RecMutex::lock(); MyLock (MyMutex& mutex, Glib::Threads::TryLock);
#endif
#if STRICT_MUTEX || !defined(NDEBUG)
if (alreadyLocked) { ~MyLock ();
#ifndef NDEBUG
std::cout << "Warning: MyMutex already locked!" << std::endl; // breakpoint MyLock (const MyLock&) = delete;
MyLock& operator= (const MyLock&) = delete;
void acquire ();
bool try_acquire ();
void release ();
private:
MyMutex& mutex;
bool locked;
};
/**
* @brief Custom implementation to replace Glib::Threads::RWLock
*/
class MyRWMutex
{
public:
MyRWMutex () = default;
MyRWMutex (const MyRWMutex&) = delete;
MyRWMutex& operator= (const MyRWMutex&) = delete;
friend class MyReaderLock;
friend class MyWriterLock;
private:
Glib::Threads::Mutex mutex;
Glib::Threads::Cond cond;
std::size_t writerCount = 0;
std::size_t readerCount = 0;
#if TRACE_MYRWMUTEX
Glib::Threads::Thread* ownerThread = nullptr;
const char* lastWriterFile = "";
int lastWriterLine = 0;
#endif #endif
#if STRICT_MUTEX };
#ifndef NDEBUG
#ifdef WIN32 /**
DebugBreak(); * @brief Custom implementation to replace Glib::Threads::RWLock::ReaderLock
*/
class MyReaderLock
{
public:
~MyReaderLock ();
MyReaderLock (const MyReaderLock&) = delete;
MyReaderLock& operator= (const MyReaderLock&) = delete;
#if !TRACE_MYRWMUTEX
explicit MyReaderLock (MyRWMutex& mutex);
void acquire ();
void release ();
#else #else
raise(SIGTRAP); explicit MyReaderLock (MyRWMutex& mutex, const char* file, int line);
void acquire (const char* file, int line);
void release (const char* file, int line);
#endif #endif
private:
MyRWMutex& mutex;
bool locked;
};
/**
* @brief Custom implementation to replace Glib::Threads::RWLock::WriterLock
*/
class MyWriterLock
{
public:
~MyWriterLock ();
MyWriterLock (const MyWriterLock&) = delete;
MyWriterLock& operator= (const MyWriterLock&) = delete;
#if !TRACE_MYRWMUTEX
explicit MyWriterLock (MyRWMutex& mutex);
void acquire ();
void release ();
#else #else
raise(SIGINT); MyWriterLock (MyRWMutex& mutex, const char* file, int line);
void acquire (const char* file, int line);
void release (const char* file, int line);
#endif #endif
private:
MyRWMutex& mutex;
bool locked;
};
inline void MyMutex::lock ()
{
MyMutexBase::lock ();
#if STRICT_MUTEX && !NDEBUG
checkLock ();
#endif #endif
} }
alreadyLocked = true; inline bool MyMutex::trylock ()
#endif
}
bool trylock()
{ {
#ifdef WIN32 if (MyMutexBase::trylock ()) {
#if STRICT_MUTEX && !NDEBUG
checkLock ();
#endif
if (Glib::RecMutex::trylock())
#else
if (Glib::Threads::RecMutex::trylock())
#endif
{
#if STRICT_MUTEX || !defined(NDEBUG)
if (alreadyLocked) {
#ifndef NDEBUG
std::cout << "Warning: MyMutex already locked!" << std::endl; // breakpoint
#endif
#if STRICT_MUTEX
#ifndef NDEBUG
#ifdef WIN32
DebugBreak();
#else
raise(SIGTRAP);
#endif
#else
raise(SIGINT);
#endif
#endif
}
alreadyLocked = true;
#endif
return true; return true;
} }
return false; return false;
} }
// Warning: the base class of MyMutex is RecMutex, but the mutex is said "unlocked" on first occurrence of "unlock", to avoid overhead. inline void MyMutex::unlock ()
void unlock()
{ {
#if STRICT_MUTEX || !defined(NDEBUG) #if STRICT_MUTEX && !NDEBUG
alreadyLocked = false; checkUnlock ();
#endif #endif
#ifdef WIN32
Glib::RecMutex::unlock();
#else
Glib::Threads::RecMutex::unlock();
#endif
}
};
MyMutexBase::unlock ();
// Class copied from the Glibmm source code, to provide a workaround of the behavior's difference between Linux and Windows
class MyMutex::MyLock
{
public:
explicit inline MyLock(MyMutex& mutex) : mutex_ (mutex), locked_ (true)
{
mutex_.lock();
} }
#ifdef WIN32
inline MyLock(MyMutex& mutex, Glib::NotLock) : mutex_ (mutex), locked_ (false) {} inline MyMutex::MyLock::MyLock (MyMutex& mutex)
inline MyLock(MyMutex& mutex, Glib::TryLock) : mutex_ (mutex), locked_ (mutex.trylock()) {} : mutex (mutex)
#else , locked (true)
inline MyLock(MyMutex& mutex, Glib::Threads::NotLock) : mutex_ (mutex), locked_ (false) {}
inline MyLock(MyMutex& mutex, Glib::Threads::TryLock) : mutex_ (mutex), locked_ (mutex.trylock()) {}
#endif
inline ~MyLock()
{ {
if(locked_) { mutex.lock();
mutex_.unlock(); }
inline MyMutex::MyLock::MyLock (MyMutex& mutex, Glib::Threads::NotLock)
: mutex (mutex)
, locked (false)
{
}
inline MyMutex::MyLock::MyLock (MyMutex& mutex, Glib::Threads::TryLock)
: mutex (mutex)
, locked (mutex.trylock ())
{
}
inline MyMutex::MyLock::~MyLock ()
{
if (locked) {
mutex.unlock ();
} }
} }
inline void acquire() inline void MyMutex::MyLock::acquire ()
{ {
mutex_.lock(); mutex.lock ();
locked_ = true;
}
inline bool try_acquire()
{
locked_ = mutex_.trylock();
return locked_;
}
inline void release()
{
mutex_.unlock();
locked_ = false;
}
inline bool locked() const
{
return locked_;
}
private:
MyMutex& mutex_;
bool locked_;
// noncopyable
MyLock(const MyMutex::Lock&);
MyMutex::Lock& operator=(const MyMutex::Lock&);
};
/**
* @brief Custom RWLock with debugging feature, to replace the buggy Glib::RWLock (can have negative reader_count value!)
*
* It may be slower, but thread safe!
*/
class MyRWMutex
{
public:
#ifdef WIN32
Glib::Mutex handlerMutex; // Having a recursive or non-recursive mutex is not important here, so we can use Glib::Mutex
Glib::Cond access;
#else
Glib::Threads::Mutex handlerMutex; // Having a recursive or non-recursive mutex is not important here, so we can use Glib::Threads::Mutex
Glib::Threads::Cond access;
#endif
size_t writerCount;
size_t readerCount;
#if TRACE_MYRWMUTEX
Glib::ustring lastWriterFile;
int lastWriterLine;
// Unfortunately, ownerThread may not be the culprit of a deadlock, it can be another concurrent Reader...
void* ownerThread;
MyRWMutex() : writerCount(0), readerCount(0), lastWriterLine(0), ownerThread(NULL) {}
#else
MyRWMutex() : writerCount(0), readerCount(0) {}
#endif
};
/**
* @brief Custom ReaderLock with debugging feature, to replace the buggy Glib::RWLock (can have negative reader_count value!)
*
*/
class MyReaderLock
{
MyRWMutex& rwMutex;
bool locked;
#if TRACE_MYRWMUTEX
static unsigned int readerLockCounter;
int locknumber;
public:
inline MyReaderLock(MyRWMutex& mutex, const char* name, const char* file, const int line) : rwMutex(mutex), locked(false), locknumber(0)
#else
public:
inline MyReaderLock(MyRWMutex & mutex) : rwMutex(mutex)
#endif
{
// to operate safely
rwMutex.handlerMutex.lock();
#if TRACE_MYRWMUTEX
locknumber = readerLockCounter++;
void* thread = Glib::Thread::self();
std::cout << thread << "/" << locknumber << ":" << name << " / " << file << " : " << line << " - locking - R";
#endif
if (!rwMutex.writerCount) {
// There's no writer operating, we can increment the writer count which will lock writers
++rwMutex.writerCount;
#if TRACE_MYRWMUTEX
std::cout << " ++ new owner";
#endif
} else {
// The writer count is non null, but we can be the owner of the writer lock
// It will be the case if the reader count is non null too.
if (!rwMutex.readerCount) {
// the mutex is in real write mode, we're waiting to see it null
#if TRACE_MYRWMUTEX
std::cout << " waiting..." << std::endl << "Current writer owner: " << rwMutex.lastWriterFile << " : " << rwMutex.lastWriterLine << std::endl;
#endif
while (rwMutex.writerCount) {
rwMutex.access.wait(rwMutex.handlerMutex);
}
++rwMutex.writerCount;
#if TRACE_MYRWMUTEX
rwMutex.lastWriterFile = file;
rwMutex.lastWriterLine = line;
rwMutex.ownerThread = thread;
std::cout << thread << "/" << locknumber << ":" << name << " / " << file << " : " << line << " - locking - R ++ new owner";
#endif
}
}
// then we can increment the reader count
++rwMutex.readerCount;
#if TRACE_MYRWMUTEX
std::cout << " - ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl;
#endif
rwMutex.handlerMutex.unlock();
locked = true; locked = true;
} }
#if TRACE_MYRWMUTEX inline bool MyMutex::MyLock::try_acquire ()
// locks the MyRWMutex with Read access if this MyReaderLock has not already locked it, otherwise return safely
inline void acquire(const char* file, const int line)
#else
// locks the MyRWMutex with Read access if this MyReaderLock has not already locked it, otherwise return safely
inline void acquire()
#endif
{ {
#if TRACE_MYRWMUTEX return locked = mutex.trylock ();
void* thread = Glib::Thread::self();
#endif
if (!locked) {
// to operate safely
rwMutex.handlerMutex.lock();
#if TRACE_MYRWMUTEX
std::cout << thread << "/" << locknumber << ":" << file << " : " << line << " - locking - R (lock)";
#endif
if (!rwMutex.writerCount) {
// There's no writer operating, we can increment the writer count which will lock writers
++rwMutex.writerCount;
#if TRACE_MYRWMUTEX
std::cout << " ++ new owner";
#endif
} else {
// The writer count is non null, but a reader can be the owner of the writer lock,
// it will be the case if the reader count is non null too.
if (!rwMutex.readerCount) {
// the mutex is in real write mode, we're waiting to see it null
#if TRACE_MYRWMUTEX
std::cout << " waiting..." << std::endl << "Current writer owner: " << rwMutex.lastWriterFile << " : " << rwMutex.lastWriterLine << std::endl;
#endif
while (rwMutex.writerCount) {
rwMutex.access.wait(rwMutex.handlerMutex);
} }
++rwMutex.writerCount; inline void MyMutex::MyLock::release ()
#if TRACE_MYRWMUTEX
rwMutex.lastWriterFile = file;
rwMutex.lastWriterLine = line;
rwMutex.ownerThread = thread;
std::cout << thread << "/" << locknumber << ":" << file << " : " << line << " - locking - R (lock) ++ new owner";
#endif
}
}
// then we can increment the reader count
++rwMutex.readerCount;
#if TRACE_MYRWMUTEX
std::cout << " - ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl;
#endif
rwMutex.handlerMutex.unlock();
locked = true;
}
#if TRACE_MYRWMUTEX
else {
std::cout << thread << "/" << locknumber << " / already locked by this object - R (lock)" << std::endl;
}
#endif
}
inline ~MyReaderLock()
{ {
#if TRACE_MYRWMUTEX mutex.unlock ();
void* thread = Glib::Thread::self();
#endif
if (locked) {
// to operate safely
rwMutex.handlerMutex.lock();
// decrement the writer number first
--rwMutex.readerCount;
#if TRACE_MYRWMUTEX
std::cout << thread << "/" << locknumber << " / unlocking - R - ReaderCount: " << rwMutex.readerCount;
#endif
if (!rwMutex.readerCount) {
// no more reader, so we decrement the writer count
--rwMutex.writerCount;
#if TRACE_MYRWMUTEX
rwMutex.lastWriterFile = "";
rwMutex.lastWriterLine = 0;
rwMutex.ownerThread = NULL;
std::cout << " -- new owner possible!" << " >>> ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount;
#endif
// and signal the next waiting reader/writer that it's free
rwMutex.access.broadcast();
}
#if TRACE_MYRWMUTEX
std::cout << std::endl;
#endif
rwMutex.handlerMutex.unlock();
}
#if TRACE_MYRWMUTEX
else {
std::cout << thread << "/" << locknumber << " / already unlocked by this object - R" << std::endl;
}
#endif
}
#if TRACE_MYRWMUTEX
// releases the MyRWMutex with Write access if this MyWriterLock has already locked it, otherwise return safely
inline void release(const char* file, const int line)
#else
// releases the MyRWMutex with Write access if this MyWriterLock has already locked it, otherwise return safely
inline void release()
#endif
{
#if TRACE_MYRWMUTEX
void* thread = Glib::Thread::self();
#endif
if (locked) {
// to operate safely
rwMutex.handlerMutex.lock();
// decrement the writer number first
--rwMutex.readerCount;
#if TRACE_MYRWMUTEX
std::cout << thread << "/" << locknumber << " / unlocking - R (release) - ReaderCount: " << rwMutex.readerCount;
#endif
if (!rwMutex.readerCount) {
// no more reader, so we decrement the writer count
--rwMutex.writerCount;
#if TRACE_MYRWMUTEX
rwMutex.lastWriterFile = "";
rwMutex.lastWriterLine = 0;
rwMutex.ownerThread = NULL;
std::cout << " -- new owner possible!" << " >>> ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount;
#endif
// and signal the next waiting reader/writer that it's free
rwMutex.access.broadcast();
}
#if TRACE_MYRWMUTEX
std::cout << std::endl;
#endif
rwMutex.handlerMutex.unlock();
locked = false; locked = false;
} }
#if TRACE_MYRWMUTEX #if !TRACE_MYRWMUTEX
else {
std::cout << thread << "/" << locknumber << " / already unlocked - R (release)" << std::endl;
}
#endif inline MyReaderLock::MyReaderLock (MyRWMutex& mutex)
} : mutex (mutex)
}; , locked (false)
/**
* @brief Custom WriterLock with debugging feature, to replace the buggy Glib::RWLock (can have negative reader_count value!)
*
*/
class MyWriterLock
{ {
acquire ();
}
MyRWMutex& rwMutex; inline MyWriterLock::MyWriterLock (MyRWMutex& mutex)
bool locked; : mutex (mutex)
, locked (false)
#if TRACE_MYRWMUTEX
static unsigned int writerLockCounter;
int locknumber;
public:
inline MyWriterLock(MyRWMutex& mutex, const char* name, const char* file, const int line) : rwMutex(mutex), locked(false), locknumber(0)
#else
public:
inline MyWriterLock(MyRWMutex & mutex) : rwMutex(mutex)
#endif
{ {
// to operate safely acquire ();
rwMutex.handlerMutex.lock();
#if TRACE_MYRWMUTEX
locknumber = writerLockCounter++;
void* thread = Glib::Thread::self();
std::cout << thread << "/" << locknumber << ":" << name << " / " << file << " : " << line << " - locking - W";
#endif
if (rwMutex.writerCount) {
// The writer count is non null, so we have to wait for it to be null again
#if TRACE_MYRWMUTEX
std::cout << " waiting..." << std::endl << "Current writer owner: " << rwMutex.lastWriterFile << " : " << rwMutex.lastWriterLine << std::endl;
#endif
while (rwMutex.writerCount) {
rwMutex.access.wait(rwMutex.handlerMutex);
} }
#if TRACE_MYRWMUTEX inline MyReaderLock::~MyReaderLock ()
std::cout << thread << "/" << locknumber << ":" << file << " : " << line << " - locking - W";
#endif
}
// then we can increment the writer count
++rwMutex.writerCount;
#if TRACE_MYRWMUTEX
rwMutex.lastWriterFile = file;
rwMutex.lastWriterLine = line;
rwMutex.ownerThread = thread;
std::cout << " ++ new owner <<< ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl;
#endif
rwMutex.handlerMutex.unlock();
locked = true;
}
#if TRACE_MYRWMUTEX
// locks the MyRWMutex with Read access if this MyReaderLock has not already locked it, otherwise return safely
inline void acquire(const char* file, const int line)
#else
// locks the MyRWMutex with Read access if this MyReaderLock has not already locked it, otherwise return safely
inline void acquire()
#endif
{ {
#if TRACE_MYRWMUTEX
void* thread = Glib::Thread::self();
#endif
if (!locked) {
// to operate safely
rwMutex.handlerMutex.lock();
#if TRACE_MYRWMUTEX
std::cout << thread << "/" << locknumber << ":" << file << " : " << line << " - locking - W (lock)";
#endif
if (rwMutex.writerCount) {
// The writer count is non null, so we have to wait for it to be null again
#if TRACE_MYRWMUTEX
std::cout << " waiting..." << std::endl << "Current writer owner: " << rwMutex.lastWriterFile << " : " << rwMutex.lastWriterLine << std::endl;
#endif
while (rwMutex.writerCount) {
rwMutex.access.wait(rwMutex.handlerMutex);
}
#if TRACE_MYRWMUTEX
std::cout << thread << "/" << locknumber << ":" << file << " : " << line << " - locking - W (lock)";
#endif
}
// then we can increment the reader count
++rwMutex.writerCount;
#if TRACE_MYRWMUTEX
rwMutex.lastWriterFile = file;
rwMutex.lastWriterLine = line;
rwMutex.ownerThread = thread;
std::cout << " ++ new owner <<< ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl;
#endif
rwMutex.handlerMutex.unlock();
locked = true;
}
#if TRACE_MYRWMUTEX
else {
std::cout << thread << "/" << locknumber << " / already locked by this object - W (lock)" << std::endl;
}
#endif
}
inline ~MyWriterLock()
{
#if TRACE_MYRWMUTEX
void* thread = Glib::Thread::self();
#endif
if (locked) { if (locked) {
// to operate safely release ();
rwMutex.handlerMutex.lock(); }
// decrement the writer number first
--rwMutex.writerCount;
#if TRACE_MYRWMUTEX
std::cout << thread << "/" << locknumber << " / unlocking - W";
#endif
if (!rwMutex.writerCount) {
#if TRACE_MYRWMUTEX
rwMutex.lastWriterFile = "";
rwMutex.lastWriterLine = 0;
rwMutex.ownerThread = NULL;
std::cout << " -- new owner possible!";
#endif
// The writer count is null again, so we can wake up the next writer or reader
rwMutex.access.broadcast();
} }
#if TRACE_MYRWMUTEX inline MyWriterLock::~MyWriterLock ()
std::cout << " <<< ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl;
#endif
rwMutex.handlerMutex.unlock();
}
#if TRACE_MYRWMUTEX
else {
std::cout << thread << "/" << locknumber << " / already unlocked by this object - W" << std::endl;
}
#endif
}
#if TRACE_MYRWMUTEX
// releases the MyRWMutex with Write access if this MyWriterLock has already locked it, otherwise return safely
inline void release(const char* file, const int line)
#else
// releases the MyRWMutex with Write access if this MyWriterLock has already locked it, otherwise return safely
inline void release()
#endif
{ {
#if TRACE_MYRWMUTEX
void* thread = Glib::Thread::self();
#endif
if (locked) { if (locked) {
// to operate safely release ();
rwMutex.handlerMutex.lock(); }
// decrement the writer number first
--rwMutex.writerCount;
#if TRACE_MYRWMUTEX
std::cout << thread << "/" << locknumber << " / unlocking - W (release)";
#endif
if (!rwMutex.writerCount) {
#if TRACE_MYRWMUTEX
rwMutex.lastWriterFile = "";
rwMutex.lastWriterLine = 0;
rwMutex.ownerThread = NULL;
std::cout << " -- new owner possible!";
#endif
// The writer count is null again, so we can wake up the next writer or reader
rwMutex.access.broadcast();
} }
#if TRACE_MYRWMUTEX #else
std::cout << " <<< ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl;
#endif
rwMutex.handlerMutex.unlock(); inline MyReaderLock::MyReaderLock (MyRWMutex& mutex, const char* file, int line)
: mutex (mutex)
locked = false; , locked (false)
{
acquire (file, line);
} }
#if TRACE_MYRWMUTEX inline MyWriterLock::MyWriterLock (MyRWMutex& mutex, const char* file, int line)
else { : mutex (mutex)
std::cout << thread << "/" << locknumber << " / already unlocked by this object - W (release)" << std::endl; , locked (false)
{
acquire (file, line);
}
inline MyReaderLock::~MyReaderLock ()
{
if (locked) {
release (__FILE__, __LINE__);
}
}
inline MyWriterLock::~MyWriterLock ()
{
if (locked) {
release (__FILE__, __LINE__);
}
} }
#endif #endif
}
};
#if TRACE_MYRWMUTEX #if TRACE_MYRWMUTEX
#define MYREADERLOCK(ln, e) MyReaderLock ln(e, #e, __FILE__, __LINE__); #define MYREADERLOCK(ln, e) MyReaderLock ln(e, __FILE__, __LINE__);
#define MYWRITERLOCK(ln, e) MyWriterLock ln(e, #e, __FILE__, __LINE__); #define MYWRITERLOCK(ln, e) MyWriterLock ln(e, __FILE__, __LINE__);
#define MYREADERLOCK_ACQUIRE(ln) ln.acquire(__FILE__, __LINE__); #define MYREADERLOCK_ACQUIRE(ln) ln.acquire(__FILE__, __LINE__);
#define MYWRITERLOCK_ACQUIRE(ln) ln.acquire(__FILE__, __LINE__); #define MYWRITERLOCK_ACQUIRE(ln) ln.acquire(__FILE__, __LINE__);
#define MYREADERLOCK_RELEASE(ln) ln.release(__FILE__, __LINE__); #define MYREADERLOCK_RELEASE(ln) ln.release(__FILE__, __LINE__);