panda3d/panda/src/pipeline/mutexDebug.cxx

276 lines
8.7 KiB
C++
Executable File

// Filename: mutexDebug.cxx
// Created by: drose (13Feb06)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license. You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////
#include "mutexDebug.h"
#include "thread.h"
#include "config_pipeline.h"
#ifdef DEBUG_THREADS
MutexDebug::VoidFunc *MutexDebug::_pstats_wait_start;
MutexDebug::VoidFunc *MutexDebug::_pstats_wait_stop;
MutexImpl MutexDebug::_global_mutex;
////////////////////////////////////////////////////////////////////
// Function: MutexDebug::Constructor
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
MutexDebug::
MutexDebug(const string &name, bool allow_recursion) :
_name(name),
_allow_recursion(allow_recursion),
_locking_thread(NULL),
_lock_count(0),
_cvar(_global_mutex)
{
}
////////////////////////////////////////////////////////////////////
// Function: MutexDebug::Destructor
// Access: Protected, Virtual
// Description:
////////////////////////////////////////////////////////////////////
MutexDebug::
~MutexDebug() {
nassertv(_locking_thread == NULL && _lock_count == 0);
// Put a distinctive, bogus lock count in upon destruction, so we'll
// be more likely to notice a floating pointer.
_lock_count = -100;
}
////////////////////////////////////////////////////////////////////
// Function: MutexDebug::output
// Access: Public, Virtual
// Description: This method is declared virtual in MutexDebug, but
// non-virtual in MutexDirect.
////////////////////////////////////////////////////////////////////
void MutexDebug::
output(ostream &out) const {
if (_allow_recursion) {
out << "ReMutex " << _name << " " << (void *)this;
} else {
out << "Mutex " << _name << " " << (void *)this;
}
}
////////////////////////////////////////////////////////////////////
// Function: MutexDebug::set_pstats_callbacks
// Access: Public, Static
// Description: This special function exists to provide hooks into
// the PStatClient system, so we can time the amount of
// time we spend waiting for a mutex lock (if the user
// configures this on). We have to do this nutty void
// function callback thing, because PStats is defined in
// a later module (it depends on this module, because it
// needs to use mutex locks, of course).
////////////////////////////////////////////////////////////////////
void MutexDebug::
set_pstats_callbacks(MutexDebug::VoidFunc *wait_start,
MutexDebug::VoidFunc *wait_stop) {
_pstats_wait_start = wait_start;
_pstats_wait_stop = wait_stop;
}
////////////////////////////////////////////////////////////////////
// Function: MutexDebug::do_lock
// Access: Private
// Description: The private implementation of lock() assumes that
// _global_mutex is held.
////////////////////////////////////////////////////////////////////
void MutexDebug::
do_lock() {
// If this assertion is triggered, you tried to lock a
// recently-destructed mutex.
nassertv(_lock_count != -100);
Thread *this_thread = Thread::get_current_thread();
if (_locking_thread == (Thread *)NULL) {
// The mutex is not already locked by anyone. Lock it.
_locking_thread = this_thread;
++_lock_count;
nassertv(_lock_count == 1);
} else if (_locking_thread == this_thread) {
// The mutex is already locked by this thread. Increment the lock
// count.
nassertv(_lock_count >= 0);
if (!_allow_recursion && _lock_count == 0) {
ostringstream ostr;
ostr << *_locking_thread << " attempted to re-lock non-reentrant "
<< *this;
nassert_raise(ostr.str());
return;
}
++_lock_count;
} else {
// The mutex is locked by some other thread.
#ifdef DO_PSTATS
if (_pstats_wait_start != NULL) {
(*_pstats_wait_start)();
}
#endif // DO_PSTATS
// Check for deadlock.
MutexDebug *next_mutex = this;
while (next_mutex != NULL) {
if (next_mutex->_locking_thread == this_thread) {
// Whoops, the thread is blocked on me! Deadlock!
report_deadlock(this_thread);
nassert_raise("Deadlock");
#ifdef DO_PSTATS
if (_pstats_wait_stop != NULL) {
(*_pstats_wait_stop)();
}
#endif // DO_PSTATS
_global_mutex.release();
return;
}
Thread *next_thread = next_mutex->_locking_thread;
if (next_thread == NULL) {
// Looks like this mutex isn't actually locked, which means
// the last thread isn't really blocked--it just hasn't woken
// up yet to discover that. In any case, no deadlock.
break;
}
// The last thread is blocked on this "next thread"'s mutex, but
// what mutex is the next thread blocked on?
next_mutex = next_thread->_blocked_on_mutex;
}
// OK, no deadlock detected. Carry on.
this_thread->_blocked_on_mutex = this;
// Go to sleep on the condition variable until it's unlocked.
if (thread_cat.is_spam()) {
thread_cat.spam()
<< *this_thread << " blocking on " << *this << " (held by "
<< *_locking_thread << ")\n";
}
while (_locking_thread != (Thread *)NULL) {
_cvar.wait();
}
if (thread_cat.is_spam()) {
thread_cat.spam()
<< *this_thread << " awake\n";
}
this_thread->_blocked_on_mutex = NULL;
_locking_thread = this_thread;
++_lock_count;
nassertv(_lock_count == 1);
#ifdef DO_PSTATS
if (_pstats_wait_stop != NULL) {
(*_pstats_wait_stop)();
}
#endif // DO_PSTATS
}
}
////////////////////////////////////////////////////////////////////
// Function: MutexDebug::do_release
// Access: Private
// Description: The private implementation of lock() assumes that
// _global_mutex is held.
////////////////////////////////////////////////////////////////////
void MutexDebug::
do_release() {
// If this assertion is triggered, you tried to release a
// recently-destructed mutex.
nassertv(_lock_count != -100);
Thread *this_thread = Thread::get_current_thread();
if (_locking_thread != this_thread) {
ostringstream ostr;
ostr << *this_thread << " attempted to release "
<< *this << " which it does not own";
nassert_raise(ostr.str());
_global_mutex.release();
return;
}
nassertv(_lock_count > 0);
--_lock_count;
if (_lock_count == 0) {
// That was the last lock held by this thread. Release the lock.
_locking_thread = (Thread *)NULL;
_cvar.signal();
}
}
////////////////////////////////////////////////////////////////////
// Function: MutexDebug::do_debug_is_locked
// Access: Private
// Description: The private implementation of debug_is_locked()
// assumes that _global_mutex is held.
////////////////////////////////////////////////////////////////////
bool MutexDebug::
do_debug_is_locked() const {
return (_locking_thread == Thread::get_current_thread());
}
////////////////////////////////////////////////////////////////////
// Function: MutexDebug::report_deadlock
// Access: Private
// Description: Reports a detected deadlock situation. _global_mutex
// should be already held.
////////////////////////////////////////////////////////////////////
void MutexDebug::
report_deadlock(Thread *this_thread) {
thread_cat.error()
<< "\n\n"
<< "****************************************************************\n"
<< "***** Deadlock detected! *****\n"
<< "****************************************************************\n"
<< "\n";
thread_cat.error()
<< *this_thread << " attempted to lock " << *this
<< " which is held by " << *_locking_thread << "\n";
MutexDebug *next_mutex = this;
Thread *next_thread = next_mutex->_locking_thread;
next_mutex = next_thread->_blocked_on_mutex;
while (next_mutex != NULL) {
thread_cat.error()
<< *next_thread << " is blocked waiting on "
<< *next_mutex << " which is held by "
<< *next_mutex->_locking_thread << "\n";
next_thread = next_mutex->_locking_thread;
next_mutex = next_thread->_blocked_on_mutex;
}
thread_cat.error()
<< "Deadlock!\n";
}
#endif // DEBUG_THREADS