279 lines
9.2 KiB
C++
279 lines
9.2 KiB
C++
// @file mutex.h
|
|
|
|
/* Copyright 2009 10gen Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <map>
|
|
#include <set>
|
|
#include "../heapcheck.h"
|
|
|
|
namespace mongo {
|
|
|
|
void printStackTrace( ostream &o );
|
|
|
|
class mutex;
|
|
|
|
inline boost::xtime incxtimemillis( long long s ) {
|
|
boost::xtime xt;
|
|
boost::xtime_get(&xt, boost::TIME_UTC);
|
|
xt.sec += (int)( s / 1000 );
|
|
xt.nsec += (int)(( s % 1000 ) * 1000000);
|
|
if ( xt.nsec >= 1000000000 ) {
|
|
xt.nsec -= 1000000000;
|
|
xt.sec++;
|
|
}
|
|
return xt;
|
|
}
|
|
|
|
/** 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();
|
|
|
|
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;
|
|
|
|
// If you create a local static instance of this class, that instance will be destroyed
|
|
// before all global static objects are destroyed, so _destroyingStatics will be set
|
|
// to true before the global static variables are destroyed.
|
|
class StaticObserver : boost::noncopyable {
|
|
public:
|
|
static bool _destroyingStatics;
|
|
~StaticObserver() { _destroyingStatics = true; }
|
|
};
|
|
|
|
/** On pthread systems, it is an error to destroy a mutex while held (boost mutex
|
|
* may use pthread). Static global mutexes may be held upon shutdown in our
|
|
* implementation, and this way we avoid destroying them.
|
|
* NOT recursive.
|
|
*/
|
|
class mutex : boost::noncopyable {
|
|
public:
|
|
#if defined(_DEBUG)
|
|
const char * const _name;
|
|
mutex(const char *name) : _name(name)
|
|
#else
|
|
mutex(const char *)
|
|
#endif
|
|
{
|
|
_m = new boost::timed_mutex();
|
|
IGNORE_OBJECT( _m ); // Turn-off heap checking on _m
|
|
}
|
|
~mutex() {
|
|
if( !StaticObserver::_destroyingStatics ) {
|
|
UNIGNORE_OBJECT( _m );
|
|
delete _m;
|
|
}
|
|
}
|
|
|
|
class try_lock : boost::noncopyable {
|
|
public:
|
|
try_lock( mongo::mutex &m , int millis = 0 )
|
|
: _l( m.boost() , incxtimemillis( millis ) ) ,
|
|
#if BOOST_VERSION >= 103500
|
|
ok( _l.owns_lock() )
|
|
#else
|
|
ok( _l.locked() )
|
|
#endif
|
|
{ }
|
|
private:
|
|
boost::timed_mutex::scoped_timed_lock _l;
|
|
public:
|
|
const bool ok;
|
|
};
|
|
|
|
class scoped_lock : boost::noncopyable {
|
|
public:
|
|
#if defined(_DEBUG)
|
|
struct PostStaticCheck {
|
|
PostStaticCheck() {
|
|
if ( StaticObserver::_destroyingStatics ) {
|
|
cout << "trying to lock a mongo::mutex during static shutdown" << endl;
|
|
printStackTrace( cout );
|
|
}
|
|
}
|
|
};
|
|
|
|
PostStaticCheck _check;
|
|
mongo::mutex * const _mut;
|
|
#endif
|
|
scoped_lock( mongo::mutex &m ) :
|
|
#if defined(_DEBUG)
|
|
_mut(&m),
|
|
#endif
|
|
_l( m.boost() ) {
|
|
#if defined(_DEBUG)
|
|
mutexDebugger.entering(_mut->_name);
|
|
#endif
|
|
}
|
|
~scoped_lock() {
|
|
#if defined(_DEBUG)
|
|
mutexDebugger.leaving(_mut->_name);
|
|
#endif
|
|
}
|
|
boost::timed_mutex::scoped_lock &boost() { return _l; }
|
|
private:
|
|
boost::timed_mutex::scoped_lock _l;
|
|
};
|
|
private:
|
|
boost::timed_mutex &boost() { return *_m; }
|
|
boost::timed_mutex *_m;
|
|
};
|
|
|
|
typedef mutex::scoped_lock scoped_lock;
|
|
typedef boost::recursive_mutex::scoped_lock recursive_scoped_lock;
|
|
|
|
/** The concept with SimpleMutex is that it is a basic lock/unlock with no
|
|
special functionality (such as try and try timeout). Thus it can be
|
|
implemented using OS-specific facilities in all environments (if desired).
|
|
On Windows, the implementation below is faster than boost mutex.
|
|
*/
|
|
#if defined(_WIN32)
|
|
class SimpleMutex : boost::noncopyable {
|
|
CRITICAL_SECTION _cs;
|
|
public:
|
|
SimpleMutex(const char *name) { InitializeCriticalSection(&_cs); }
|
|
~SimpleMutex() { DeleteCriticalSection(&_cs); }
|
|
|
|
void lock() { EnterCriticalSection(&_cs); }
|
|
void unlock() { LeaveCriticalSection(&_cs); }
|
|
|
|
class scoped_lock : boost::noncopyable {
|
|
SimpleMutex& _m;
|
|
public:
|
|
scoped_lock( SimpleMutex &m ) : _m(m) { _m.lock(); }
|
|
~scoped_lock() { _m.unlock(); }
|
|
};
|
|
};
|
|
#else
|
|
class SimpleMutex : boost::noncopyable {
|
|
public:
|
|
SimpleMutex(const char* name) { assert( pthread_mutex_init(&_lock,0) == 0 ); }
|
|
~SimpleMutex(){
|
|
if ( ! StaticObserver::_destroyingStatics ) {
|
|
assert( pthread_mutex_destroy(&_lock) == 0 );
|
|
}
|
|
}
|
|
|
|
void lock() { assert( pthread_mutex_lock(&_lock) == 0 ); }
|
|
void unlock() { assert( pthread_mutex_unlock(&_lock) == 0 ); }
|
|
|
|
class scoped_lock : boost::noncopyable {
|
|
SimpleMutex& _m;
|
|
public:
|
|
scoped_lock( SimpleMutex &m ) : _m(m) { _m.lock(); }
|
|
~scoped_lock() { _m.unlock(); }
|
|
};
|
|
|
|
private:
|
|
pthread_mutex_t _lock;
|
|
};
|
|
|
|
#endif
|
|
|
|
}
|