diff options
Diffstat (limited to 'src/mongo/util/concurrency/mutexdebugger.h')
-rw-r--r-- | src/mongo/util/concurrency/mutexdebugger.h | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/src/mongo/util/concurrency/mutexdebugger.h b/src/mongo/util/concurrency/mutexdebugger.h new file mode 100644 index 00000000000..7dc57f29e98 --- /dev/null +++ b/src/mongo/util/concurrency/mutexdebugger.h @@ -0,0 +1,117 @@ +#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 || m == 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 || m == 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; + +} |