mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-21 22:51:34 -04:00
276 lines
8.7 KiB
C++
Executable File
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
|