summaryrefslogtreecommitdiff
path: root/src/mongo/util/concurrency/mutexdebugger.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/util/concurrency/mutexdebugger.h')
-rw-r--r--src/mongo/util/concurrency/mutexdebugger.h117
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;
+
+}