summaryrefslogtreecommitdiff
path: root/src/mongo/util/concurrency/mutexdebugger.h
blob: 8c41a68749090c9ecec5e8f960d6955d978361df (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// @file mutexdebugger.h

#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;
            verify( 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;
                    verify(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;
                verify( 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;
                verify( preceeding[m] >= 0 );
            }
        }
    };
    extern MutexDebugger &mutexDebugger;

}