// Filename: mutexDebug.cxx // Created by: drose (13Feb06) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) Carnegie Mellon University. All rights reserved. // // All use of this software is subject to the terms of the revised BSD // license. You should have received a copy of this license along // with this source code in a file named "LICENSE." // //////////////////////////////////////////////////////////////////// #include "mutexDebug.h" #include "thread.h" #include "config_pipeline.h" #ifdef DEBUG_THREADS MutexTrueImpl *MutexDebug::_global_lock; //////////////////////////////////////////////////////////////////// // Function: MutexDebug::Constructor // Access: Protected // Description: //////////////////////////////////////////////////////////////////// MutexDebug:: MutexDebug(const string &name, bool allow_recursion, bool lightweight) : Namable(name), _allow_recursion(allow_recursion), _lightweight(lightweight), _locking_thread(NULL), _lock_count(0), _deleted_name(NULL), _cvar_impl(*get_global_lock()) { #ifndef SIMPLE_THREADS // If we're using real threads, there's no such thing as a // lightweight mutex. _lightweight = false; #endif } //////////////////////////////////////////////////////////////////// // Function: MutexDebug::Destructor // Access: Protected, Virtual // Description: //////////////////////////////////////////////////////////////////// MutexDebug:: ~MutexDebug() { nassertv(_locking_thread == NULL && _lock_count == 0); // If the config variable says to, allocate (and leak) a string name // for the mutex, so we can report which mutex it is that has // destructed after the fact. if (name_deleted_mutexes) { ostringstream strm; strm << *this; string name = strm.str(); _deleted_name = strdup((char *)name.c_str()); } // 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 (_lightweight) { out << "Light"; } if (_allow_recursion) { out << "ReMutex " << get_name() << " " << (void *)this; } else { out << "Mutex " << get_name() << " " << (void *)this; } } //////////////////////////////////////////////////////////////////// // Function: MutexDebug::output_with_holder // Access: Public // Description: Reports the mutex as well as the thread that is // currently holding it, if any. //////////////////////////////////////////////////////////////////// void MutexDebug:: output_with_holder(ostream &out) const { _global_lock->acquire(); output(out); if (_locking_thread != (Thread *)NULL) { out << " (held by " << *_locking_thread << ")\n"; } _global_lock->release(); } //////////////////////////////////////////////////////////////////// // Function: MutexDebug::do_acquire // Access: Private // Description: The private implementation of acquire() assumes that // _lock_impl is held. //////////////////////////////////////////////////////////////////// void MutexDebug:: do_acquire(Thread *current_thread) { // If this assertion is triggered, you tried to lock a // recently-destructed mutex. nassertd(_lock_count != -100) { pipeline_cat.error() << "Destructed mutex: " << (void *)this << "\n"; if (name_deleted_mutexes && _deleted_name != NULL) { pipeline_cat.error() << _deleted_name << "\n"; } else { pipeline_cat.error() << "Configure name-deleted-mutexes 1 to see the mutex name.\n"; } return; } if (_locking_thread == (Thread *)NULL) { // The mutex is not already locked by anyone. Lock it. _locking_thread = current_thread; ++_lock_count; nassertv(_lock_count == 1); } else if (_locking_thread == current_thread) { // The mutex is already locked by this thread. Increment the lock // count. nassertv(_lock_count > 0); if (!_allow_recursion) { ostringstream ostr; ostr << *current_thread << " attempted to double-lock non-reentrant " << *this; nassert_raise(ostr.str()); } ++_lock_count; } else { // The mutex is locked by some other thread. if (_lightweight) { // In this case, it's not a real mutex. Just watch it go by. MissedThreads::iterator mi = _missed_threads.insert(MissedThreads::value_type(current_thread, 0)).first; if ((*mi).second == 0) { thread_cat.info() << *current_thread << " not stopped by " << *this << " (held by " << *_locking_thread << ")\n"; } else { if (!_allow_recursion) { ostringstream ostr; ostr << *current_thread << " attempted to double-lock non-reentrant " << *this; nassert_raise(ostr.str()); } } ++((*mi).second); } else { // This is the real case. It's a real mutex, so block if necessary. // Check for deadlock. MutexDebug *next_mutex = this; while (next_mutex != NULL) { if (next_mutex->_locking_thread == current_thread) { // Whoops, the thread is blocked on me! Deadlock! report_deadlock(current_thread); nassert_raise("Deadlock"); 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. current_thread->_blocked_on_mutex = this; // Go to sleep on the condition variable until it's unlocked. if (thread_cat->is_debug()) { thread_cat.debug() << *current_thread << " blocking on " << *this << " (held by " << *_locking_thread << ")\n"; } while (_locking_thread != (Thread *)NULL) { thread_cat.debug() << *current_thread << " still blocking on " << *this << " (held by " << *_locking_thread << ")\n"; _cvar_impl.wait(); } if (thread_cat.is_debug()) { thread_cat.debug() << *current_thread << " acquired " << *this << "\n"; } current_thread->_blocked_on_mutex = NULL; _locking_thread = current_thread; ++_lock_count; nassertv(_lock_count == 1); } } } //////////////////////////////////////////////////////////////////// // Function: MutexDebug::do_try_acquire // Access: Private // Description: The private implementation of acquire(false) assumes // that _lock_impl is held. //////////////////////////////////////////////////////////////////// bool MutexDebug:: do_try_acquire(Thread *current_thread) { // If this assertion is triggered, you tried to lock a // recently-destructed mutex. nassertd(_lock_count != -100) { pipeline_cat.error() << "Destructed mutex: " << (void *)this << "\n"; if (name_deleted_mutexes && _deleted_name != NULL) { pipeline_cat.error() << _deleted_name << "\n"; } else { pipeline_cat.error() << "Configure name-deleted-mutexes 1 to see the mutex name.\n"; } return false; } bool acquired = true; if (_locking_thread == (Thread *)NULL) { // The mutex is not already locked by anyone. Lock it. _locking_thread = current_thread; ++_lock_count; nassertr(_lock_count == 1, false); } else if (_locking_thread == current_thread) { // The mutex is already locked by this thread. Increment the lock // count. nassertr(_lock_count > 0, false); if (!_allow_recursion) { // Non-recursive lock; return false. acquired = false; } else { ++_lock_count; } } else { // The mutex is locked by some other thread. Return false. if (_lightweight) { // In this case, it's not a real mutex. Just watch it go by. MissedThreads::iterator mi = _missed_threads.insert(MissedThreads::value_type(current_thread, 0)).first; if ((*mi).second == 0) { thread_cat.info() << *current_thread << " not stopped by " << *this << " (held by " << *_locking_thread << ")\n"; } else { if (!_allow_recursion) { ostringstream ostr; ostr << *current_thread << " attempted to double-lock non-reentrant " << *this; nassert_raise(ostr.str()); } } ++((*mi).second); } else { // This is the real case. acquired = false; } } return acquired; } //////////////////////////////////////////////////////////////////// // Function: MutexDebug::do_release // Access: Private // Description: The private implementation of acquire() assumes that // _lock_impl is held. //////////////////////////////////////////////////////////////////// void MutexDebug:: do_release() { // If this assertion is triggered, you tried to release a // recently-destructed mutex. nassertd(_lock_count != -100) { pipeline_cat.error() << "Destructed mutex: " << (void *)this << "\n"; if (name_deleted_mutexes && _deleted_name != NULL) { pipeline_cat.error() << _deleted_name << "\n"; } else { pipeline_cat.error() << "Configure name-deleted-mutexes 1 to see the mutex name.\n"; } return; } Thread *current_thread = Thread::get_current_thread(); if (_locking_thread != current_thread) { // We're not holding this mutex. if (_lightweight) { // Not a real mutex. This just means we blew past a mutex // without locking it, above. MissedThreads::iterator mi = _missed_threads.find(current_thread); nassertv(mi != _missed_threads.end()); nassertv((*mi).second > 0); --((*mi).second); if ((*mi).second == 0) { _missed_threads.erase(mi); } } else { // In the real-mutex case, this is an error condition. ostringstream ostr; ostr << *current_thread << " attempted to release " << *this << " which it does not own"; nassert_raise(ostr.str()); } 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; if (_lightweight) { if (!_missed_threads.empty()) { // Promote some other thread to be the honorary lock holder. MissedThreads::iterator mi = _missed_threads.begin(); _locking_thread = (*mi).first; _lock_count = (*mi).second; _missed_threads.erase(mi); nassertv(_lock_count > 0); } } else { /* if (thread_cat.is_debug()) { thread_cat.debug() << *current_thread << " releasing " << *this << "\n"; } */ _cvar_impl.notify(); } } } //////////////////////////////////////////////////////////////////// // Function: MutexDebug::do_debug_is_locked // Access: Private // Description: The private implementation of debug_is_locked() // assumes that _lock_impl is held. //////////////////////////////////////////////////////////////////// bool MutexDebug:: do_debug_is_locked() const { Thread *current_thread = Thread::get_current_thread(); if (_locking_thread == current_thread) { return true; } if (_lightweight) { MissedThreads::const_iterator mi = _missed_threads.find(current_thread); if (mi != _missed_threads.end()) { nassertr((*mi).second > 0, false); return true; } } return false; } //////////////////////////////////////////////////////////////////// // Function: MutexDebug::report_deadlock // Access: Private // Description: Reports a detected deadlock situation. _lock_impl // should be already held. //////////////////////////////////////////////////////////////////// void MutexDebug:: report_deadlock(Thread *current_thread) { thread_cat->error() << "\n\n" << "****************************************************************\n" << "***** Deadlock detected! *****\n" << "****************************************************************\n" << "\n"; thread_cat.error() << *current_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