118 lines
4.3 KiB
C++
118 lines
4.3 KiB
C++
#pragma once
|
|
|
|
namespace mongo {
|
|
|
|
/** only used on _DEBUG builds.
|
|
MutexDebugger checks that we always acquire locks for multiple mutexes in a consistant (acyclic) order.
|
|
If we were inconsistent we could deadlock.
|
|
*/
|
|
class MutexDebugger {
|
|
typedef const char * mid; // mid = mutex ID
|
|
typedef map<mid,int> Preceeding;
|
|
map< mid, int > maxNest;
|
|
boost::thread_specific_ptr< Preceeding > us;
|
|
map< mid, set<mid> > followers;
|
|
boost::mutex &x;
|
|
unsigned magic;
|
|
void aBreakPoint() { } // for debugging
|
|
public:
|
|
// set these to create an assert that
|
|
// b must never be locked before a
|
|
// so
|
|
// a.lock(); b.lock(); is fine
|
|
// b.lock(); alone is fine too
|
|
// only checked on _DEBUG builds.
|
|
string a,b;
|
|
|
|
/** outputs some diagnostic info on mutexes (on _DEBUG builds) */
|
|
void programEnding();
|
|
|
|
MutexDebugger();
|
|
|
|
string currentlyLocked() const {
|
|
Preceeding *_preceeding = us.get();
|
|
if( _preceeding == 0 )
|
|
return "";
|
|
Preceeding &preceeding = *_preceeding;
|
|
stringstream q;
|
|
for( Preceeding::const_iterator i = preceeding.begin(); i != preceeding.end(); i++ ) {
|
|
if( i->second > 0 )
|
|
q << " " << i->first << ' ' << i->second << '\n';
|
|
}
|
|
return q.str();
|
|
}
|
|
|
|
void entering(mid m) {
|
|
if( this == 0 ) return;
|
|
assert( magic == 0x12345678 );
|
|
|
|
Preceeding *_preceeding = us.get();
|
|
if( _preceeding == 0 )
|
|
us.reset( _preceeding = new Preceeding() );
|
|
Preceeding &preceeding = *_preceeding;
|
|
|
|
if( a == m ) {
|
|
aBreakPoint();
|
|
if( preceeding[b.c_str()] ) {
|
|
cout << "****** MutexDebugger error! warning " << b << " was locked before " << a << endl;
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
preceeding[m]++;
|
|
if( preceeding[m] > 1 ) {
|
|
// recursive re-locking.
|
|
if( preceeding[m] > maxNest[m] )
|
|
maxNest[m] = preceeding[m];
|
|
return;
|
|
}
|
|
|
|
bool failed = false;
|
|
string err;
|
|
{
|
|
boost::mutex::scoped_lock lk(x);
|
|
followers[m];
|
|
for( Preceeding::iterator i = preceeding.begin(); i != preceeding.end(); i++ ) {
|
|
if( m != i->first && i->second > 0 ) {
|
|
followers[i->first].insert(m);
|
|
if( followers[m].count(i->first) != 0 ) {
|
|
failed = true;
|
|
stringstream ss;
|
|
mid bad = i->first;
|
|
ss << "mutex problem" <<
|
|
"\n when locking " << m <<
|
|
"\n " << bad << " was already locked and should not be."
|
|
"\n set a and b above to debug.\n";
|
|
stringstream q;
|
|
for( Preceeding::iterator i = preceeding.begin(); i != preceeding.end(); i++ ) {
|
|
if( i->first != m && i->first != bad && i->second > 0 )
|
|
q << " " << i->first << '\n';
|
|
}
|
|
string also = q.str();
|
|
if( !also.empty() )
|
|
ss << "also locked before " << m << " in this thread (no particular order):\n" << also;
|
|
err = ss.str();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if( failed ) {
|
|
cout << err << endl;
|
|
assert( 0 );
|
|
}
|
|
}
|
|
void leaving(mid m) {
|
|
if( this == 0 ) return; // still in startup pre-main()
|
|
Preceeding& preceeding = *us.get();
|
|
preceeding[m]--;
|
|
if( preceeding[m] < 0 ) {
|
|
cout << "ERROR: lock count for " << m << " is " << preceeding[m] << endl;
|
|
assert( preceeding[m] >= 0 );
|
|
}
|
|
}
|
|
};
|
|
extern MutexDebugger &mutexDebugger;
|
|
|
|
}
|