diff options
author | Kaloian Manassiev <kaloian.manassiev@mongodb.com> | 2014-08-13 14:53:12 -0400 |
---|---|---|
committer | Kaloian Manassiev <kaloian.manassiev@mongodb.com> | 2014-08-25 16:58:58 -0400 |
commit | 052175eb9e0b793de19575fea37a7fdd95126a50 (patch) | |
tree | e9b3d5ad0b30e8825448c6e8dffe19dc5297af05 /src/mongo | |
parent | eacc825ccae11f81a3d06513ceced054b9562ef6 (diff) | |
download | mongo-052175eb9e0b793de19575fea37a7fdd95126a50.tar.gz |
SERVER-14668 Move DB-level locks to be on the LockManager
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/commands/write_commands/batch_executor.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/concurrency/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/db/concurrency/d_concurrency.cpp | 505 | ||||
-rw-r--r-- | src/mongo/db/concurrency/d_concurrency.h | 51 | ||||
-rw-r--r-- | src/mongo/db/concurrency/d_concurrency_test.cpp | 82 | ||||
-rw-r--r-- | src/mongo/db/concurrency/lock_state.cpp | 152 | ||||
-rw-r--r-- | src/mongo/db/concurrency/lock_state.h | 132 | ||||
-rw-r--r-- | src/mongo/db/concurrency/lock_state_test.cpp | 54 | ||||
-rw-r--r-- | src/mongo/db/instance.cpp | 2 | ||||
-rw-r--r-- | src/mongo/dbtests/framework.cpp | 6 | ||||
-rw-r--r-- | src/mongo/dbtests/threadedtests.cpp | 7 |
11 files changed, 393 insertions, 603 deletions
diff --git a/src/mongo/db/commands/write_commands/batch_executor.cpp b/src/mongo/db/commands/write_commands/batch_executor.cpp index 452c8155b52..c1ba188d76e 100644 --- a/src/mongo/db/commands/write_commands/batch_executor.cpp +++ b/src/mongo/db/commands/write_commands/batch_executor.cpp @@ -1120,7 +1120,7 @@ namespace mongo { } /////////////////////////////////////////// - Lock::DBWrite writeLock(txn->lockState(), nsString.ns(), useExperimentalDocLocking); + Lock::DBWrite writeLock(txn->lockState(), nsString.ns()); /////////////////////////////////////////// if (!checkShardVersion(txn, &shardingState, *updateItem.getRequest(), result)) diff --git a/src/mongo/db/concurrency/SConscript b/src/mongo/db/concurrency/SConscript index 5f8e83d3ed7..7f81b512ae4 100644 --- a/src/mongo/db/concurrency/SConscript +++ b/src/mongo/db/concurrency/SConscript @@ -22,7 +22,8 @@ env.Library( env.CppUnitTest( target='lock_mgr_test', - source=['lock_mgr_new_test.cpp', + source=['d_concurrency_test.cpp', + 'lock_mgr_new_test.cpp', 'lock_state_test.cpp' ], LIBDEPS=[ diff --git a/src/mongo/db/concurrency/d_concurrency.cpp b/src/mongo/db/concurrency/d_concurrency.cpp index f331af72ec5..82f71ae5c09 100644 --- a/src/mongo/db/concurrency/d_concurrency.cpp +++ b/src/mongo/db/concurrency/d_concurrency.cpp @@ -32,6 +32,7 @@ #include "mongo/db/concurrency/d_concurrency.h" +#include "mongo/db/client.h" #include "mongo/db/commands/server_status.h" #include "mongo/db/curop.h" #include "mongo/db/d_globals.h" @@ -40,7 +41,6 @@ #include "mongo/db/server_parameters.h" #include "mongo/db/operation_context.h" #include "mongo/util/assert_util.h" -#include "mongo/util/concurrency/mapsf.h" #include "mongo/util/concurrency/qlock.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" @@ -60,30 +60,6 @@ namespace mongo { virtual ~DBTryLockTimeoutException() throw() { } }; - /* dbname->lock - Currently these are never deleted - will linger if db was closed. (that should be fine.) - We don't put the lock inside the Database object as those can come and go with open and - closes and that would just add complexity. - Note there is no path concept for where the database is; if somehow you had two db's open - in different directories with the same name, it will be ok but they are sharing a lock - then. - */ - typedef mapsf< StringMap<WrapperForRWLock*> > DBLocksMap; - static DBLocksMap dblocks; - - /* we don't want to touch dblocks too much as a mutex is involved. thus party for that, - this is here... - */ - WrapperForRWLock *nestableLocks[] = { - 0, - new WrapperForRWLock("local"), - new WrapperForRWLock("admin") - }; - - LockStat* Lock::nestableLockStat( Nestable db ) { - return &nestableLocks[db]->getStats(); - } - class WrapperForQLock { public: QLock q; @@ -144,9 +120,28 @@ namespace mongo { }; static WrapperForQLock& qlk = *new WrapperForQLock(); - LockStat* Lock::globalLockStat() { - return &qlk.stats; - } + + /** + * SERVER-14978: This class is temporary and is used to aggregate the per-operation lock + * acquisition times. It will go away once we have figured out the lock stats reporting story. + */ + class TrackLockAcquireTime { + MONGO_DISALLOW_COPYING(TrackLockAcquireTime); + public: + TrackLockAcquireTime(char type) : _type(type) { + + } + + ~TrackLockAcquireTime() { + if (haveClient()) { + cc().curop()->lockStat().recordAcquireTimeMicros(_type, _timer.micros()); + } + } + + private: + char _type; + Timer _timer; + }; RWLockRecursive &Lock::ParallelBatchWriterMode::_batchLock = *(new RWLockRecursive("special")); @@ -172,75 +167,58 @@ namespace mongo { Lock::ScopedLock::ScopedLock(LockState* lockState, char type) - : _lockState(lockState), _pbws_lk(lockState), _type(type), _stat(0) { + : _lockState(lockState), _pbws_lk(lockState), _type(type) { _lockState->enterScopedLock(this); } Lock::ScopedLock::~ScopedLock() { - int prevCount = _lockState->recursiveCount(); - Lock::ScopedLock* what = _lockState->leaveScopedLock(); - fassert( 16171 , prevCount != 1 || what == this ); - } - - long long Lock::ScopedLock::acquireFinished( LockStat* stat ) { - long long acquisitionTime = _timer.micros(); - _timer.reset(); - _stat = stat; - - // increment the operation level statistics - cc().curop()->lockStat().recordAcquireTimeMicros( _type , acquisitionTime ); - - return acquisitionTime; + _lockState->leaveScopedLock(this); } void Lock::ScopedLock::tempRelease() { - long long micros = _timer.micros(); _tempRelease(); _pbws_lk.tempRelease(); - _recordTime( micros ); // might as well do after we unlock } - void Lock::ScopedLock::_recordTime( long long micros ) { - if ( _stat ) - _stat->recordLockTimeMicros( _type , micros ); - cc().curop()->lockStat().recordLockTimeMicros( _type , micros ); - } - - void Lock::ScopedLock::recordTime() { - _recordTime(_timer.micros()); + void Lock::ScopedLock::relock() { + _pbws_lk.relock(); + _relock(); } void Lock::ScopedLock::resetTime() { _timer.reset(); } - - void Lock::ScopedLock::relock() { - _pbws_lk.relock(); - resetTime(); - _relock(); + + void Lock::ScopedLock::recordTime() { + if (haveClient()) { + cc().curop()->lockStat().recordLockTimeMicros(_type, _timer.micros()); + } } Lock::TempRelease::TempRelease(LockState* lockState) : cant(lockState->isRecursive()), _lockState(lockState) { - if( cant ) + if (cant) { return; + } fassert(16116, _lockState->recursiveCount() == 1); fassert(16117, _lockState->threadState() != 0); - scopedLk = _lockState->leaveScopedLock(); + scopedLk = _lockState->getCurrentScopedLock(); fassert(16118, scopedLk); invariant(_lockState == scopedLk->_lockState); scopedLk->tempRelease(); + _lockState->leaveScopedLock(scopedLk); } - Lock::TempRelease::~TempRelease() - { - if( cant ) + + Lock::TempRelease::~TempRelease() { + if (cant) { return; + } fassert(16119, scopedLk); fassert(16120, _lockState->threadState() == 0); @@ -252,16 +230,18 @@ namespace mongo { void Lock::GlobalWrite::_tempRelease() { fassert(16121, !noop); char ts = _lockState->threadState(); - fassert(16122, ts != 'R'); // indicates downgraded; not allowed with temprelease fassert(16123, ts == 'W'); qlk.unlock_W(_lockState); + recordTime(); } void Lock::GlobalWrite::_relock() { fassert(16125, !noop); char ts = _lockState->threadState(); fassert(16126, ts == 0); - Acquiring a(this, *_lockState); + + TrackLockAcquireTime a('W'); qlk.lock_W(_lockState); + resetTime(); } void Lock::GlobalRead::_tempRelease() { @@ -269,26 +249,30 @@ namespace mongo { char ts = _lockState->threadState(); fassert(16128, ts == 'R'); qlk.unlock_R(_lockState); + recordTime(); } + void Lock::GlobalRead::_relock() { fassert(16129, !noop); char ts = _lockState->threadState(); fassert(16130, ts == 0); - Acquiring a(this, *_lockState); + + TrackLockAcquireTime a('R'); qlk.lock_R(_lockState); + resetTime(); } void Lock::DBWrite::_tempRelease() { unlockDB(); } void Lock::DBWrite::_relock() { - lockDB(_what); + lockDB(); } void Lock::DBRead::_tempRelease() { unlockDB(); } void Lock::DBRead::_relock() { - lockDB(_what); + lockDB(); } Lock::GlobalWrite::GlobalWrite(LockState* lockState, int timeoutms) @@ -302,7 +286,7 @@ namespace mongo { } dassert( ts == 0 ); - Acquiring a(this, *_lockState); + TrackLockAcquireTime a('W'); if ( timeoutms != -1 ) { bool success = qlk.lock_W_try(_lockState, timeoutms); @@ -311,18 +295,22 @@ namespace mongo { else { qlk.lock_W(_lockState); } + + resetTime(); } Lock::GlobalWrite::~GlobalWrite() { if( noop ) { return; } - recordTime(); // for lock stats + if (_lockState->threadState() == 'R') { // we downgraded qlk.unlock_R(_lockState); } else { qlk.unlock_W(_lockState); } + + recordTime(); } void Lock::GlobalWrite::downgrade() { verify( !noop ); @@ -351,7 +339,7 @@ namespace mongo { return; } - Acquiring a(this, *_lockState); + TrackLockAcquireTime a('R'); if ( timeoutms != -1 ) { bool success = qlk.lock_R_try(_lockState, timeoutms); @@ -361,302 +349,188 @@ namespace mongo { // we are unlocked in the qlock/top sense. lock_R will assert if we are in an in compatible state qlk.lock_R(_lockState); } + + resetTime(); } + Lock::GlobalRead::~GlobalRead() { if( !noop ) { - recordTime(); // for lock stats qlk.unlock_R(_lockState); + recordTime(); } } - void Lock::DBWrite::lockNestable(Nestable db) { - _nested = true; - - if (_lockState->nestableCount()) { - if( db != _lockState->whichNestable() ) { - error() << "can't lock local and admin db at the same time " << (int) db << ' ' << (int) _lockState->whichNestable() << endl; - fassert(16131,false); - } - verify( _lockState->nestableCount() > 0 ); - } - else { - fassert(16132,_weLocked==0); - _lockState->lockedNestable(db, 1); - _weLocked = nestableLocks[db]; - _weLocked->lock(); - } - } - void Lock::DBRead::lockNestable(Nestable db) { - _nested = true; - - if (_lockState->nestableCount()) { - // we are nested in our locking of local. previous lock could be read OR write lock on local. - } - else { - _lockState->lockedNestable(db, -1); - fassert(16133,_weLocked==0); - _weLocked = nestableLocks[db]; - _weLocked->lock_shared(); - } + static bool isLocalOrAdmin(const string& dbName) { + return (dbName == "local" || dbName == "admin"); } - void Lock::DBWrite::lockOtherRead(const StringData& db) { - fassert(18517, !db.empty()); - // we do checks first, as on assert destructor won't be called so don't want to be half finished with our work. - if( _lockState->otherCount() ) { - // nested. prev could be read or write. if/when we do temprelease with DBRead/DBWrite we will need to increment/decrement here - // (so we can not release or assert if nested). temprelease we should avoid if we can though, it's a bit of an anti-pattern. - invariant(db == _lockState->otherName()); - return; - } + Lock::DBWrite::DBWrite(LockState* lockState, const StringData& dbOrNs) + : ScopedLock(lockState, 'w'), + _lockAcquired(false), + _ns(dbOrNs.toString()) { - // first lock for this db. check consistent order with local db lock so we never deadlock. local always comes last - invariant(_lockState->nestableCount() == 0); + fassert(16253, !_ns.empty()); + lockDB(); + } - if (db != _lockState->otherName()) { - DBLocksMap::ref r(dblocks); - WrapperForRWLock*& lock = r[db]; - if (lock == NULL) { - lock = new WrapperForRWLock(db); - } + Lock::DBWrite::~DBWrite() { + unlockDB(); + } - _lockState->lockedOther(db, -1, lock); - } - else { - DEV OCCASIONALLY{ dassert(dblocks.get(db) == _lockState->otherLock()); } - _lockState->lockedOther(-1); + void Lock::DBWrite::lockTop() { + switch (_lockState->threadState()) { + case 0: + _lockState->lockedStart('w'); + qlk.q.lock_w(); + break; + case 'w': + break; + default: + invariant(!"Invalid thread state"); } - - fassert(18515, _weLocked == 0); - _lockState->otherLock()->lock_shared(); - _weLocked = _lockState->otherLock(); } - void Lock::DBWrite::lockOtherWrite(const StringData& db) { - fassert(16252, !db.empty()); - - // we do checks first, as on assert destructor won't be called so don't want to be half finished with our work. - if (_lockState->otherCount()) { - // nested. if/when we do temprelease with DBWrite we will need to increment here - // (so we can not release or assert if nested). - invariant(db == _lockState->otherName()); + void Lock::DBWrite::lockDB() { + if (_lockState->isW()) { return; } - // first lock for this db. check consistent order with local db lock so we never deadlock. local always comes last - invariant(_lockState->nestableCount() == 0); + TrackLockAcquireTime a('w'); - if (db != _lockState->otherName()) { - DBLocksMap::ref r(dblocks); - WrapperForRWLock*& lock = r[db]; - if (lock == NULL) { - lock = new WrapperForRWLock(db); - } + const StringData db = nsToDatabaseSubstring(_ns); + const newlm::ResourceId resIdDb(newlm::RESOURCE_DATABASE, db); - _lockState->lockedOther(db, 1, lock); + // As weird as it looks, currently the top (global 'w' lock) is acquired after the DB lock + // has been acquired in order to avoid deadlock with UpgradeGlobalLockToExclusive, which is + // called while holding some form of write lock on the database. + // + // However, for the local/admin database, since its lock is acquired usually after another + // DB lock is already held, the nested database's lock (local or admin) needs to be + // acquired after the global lock. Consider the following sequence: + // + // T1: Acquires DBWrite on 'other' DB, then 'w' + // T2: Wants to flush so it queues behind T1 for 'R' lock + // T3: Acquires DBRead on 'local' DB, then blocks on 'r' (T2 has precedence, due to 'R') + // T1: Does whatever writes it does and tries to acquire DBWrite on 'local' in order to + // insert in the OpLog, and blocks because of T3's 'r' lock on local. + // + // This is a deadlock T1 -> T3 -> T2 -> T1. + // + // Moving the acquisition of local or admin's lock to be after the global lock solves this + // problem, which would otherwise break replication. + // + // TODO: This whole lock ordering mess will go away once we make flush be it's own lock + // instead of doing upgrades on the global lock. + if (isLocalOrAdmin(db.toString())) { + lockTop(); } - else { - DEV OCCASIONALLY{ dassert(dblocks.get(db) == _lockState->otherLock()); } - _lockState->lockedOther(1); - } - - fassert(16134,_weLocked==0); - - _lockState->otherLock()->lock(); - _weLocked = _lockState->otherLock(); - } - - static Lock::Nestable n(const StringData& db) { - if( db == "local" ) - return Lock::local; - if( db == "admin" ) - return Lock::admin; - return Lock::notnestable; - } - - void Lock::DBWrite::lockDB(const string& ns) { - fassert( 16253, !ns.empty() ); - Acquiring a(this, *_lockState); - _locked_W=false; - _locked_w=false; - _weLocked=0; + _lockState->lock(resIdDb, newlm::MODE_X); - invariant(!_lockState->hasAnyReadLock()); + if (!isLocalOrAdmin(db.toString())) { + lockTop(); + } - if (_lockState->isW()) - return; + _lockAcquired = true; - StringData db = nsToDatabaseSubstring( ns ); - Nestable nested = n(db); - if( nested == admin ) { - // we can't nestedly lock both admin and local as implemented. so lock_W. - qlk.lock_W(_lockState); - _locked_W = true; - return; - } + resetTime(); + } - if (!nested) { - if (_isIntentWrite) { - lockOtherRead(db); - } - else { - lockOtherWrite(db); + void Lock::DBWrite::unlockDB() { + if (_lockAcquired) { + const StringData db = nsToDatabaseSubstring(_ns); + const newlm::ResourceId resIdDb(newlm::RESOURCE_DATABASE, db); + + _lockState->unlock(resIdDb); + + // The last one frees the Global lock + if (_lockState->recursiveCount() == 1) { + invariant(_lockState->threadState() == 'w'); + qlk.q.unlock_w(); + _lockState->unlocked(); + recordTime(); } - } - lockTop(); - if( nested ) - lockNestable(nested); + _lockAcquired = false; + } } - void Lock::DBRead::lockDB(const string& ns) { - fassert( 16254, !ns.empty() ); - - Acquiring a(this, *_lockState); - _locked_r=false; - _weLocked=0; - - if (_lockState->isRW()) - return; - - StringData db = nsToDatabaseSubstring(ns); - Nestable nested = n(db); - if( !nested ) - lockOther(db); - lockTop(); - if( nested ) - lockNestable(nested); - } - Lock::DBWrite::DBWrite(LockState* lockState, const StringData& ns, bool intentWrite) - : ScopedLock(lockState, 'w'), - _isIntentWrite(intentWrite), - _what(ns.toString()), - _nested(false) { - lockDB(_what); - } + Lock::DBRead::DBRead(LockState* lockState, const StringData& dbOrNs) + : ScopedLock(lockState, 'r'), + _lockAcquired(false), + _ns(dbOrNs.toString()) { - Lock::DBRead::DBRead(LockState* lockState, const StringData& ns) - : ScopedLock(lockState, 'r' ), _what(ns.toString()), _nested(false) { - lockDB( _what ); + fassert(16254, !_ns.empty()); + lockDB(); } - Lock::DBWrite::~DBWrite() { - unlockDB(); - } Lock::DBRead::~DBRead() { unlockDB(); } - void Lock::DBWrite::unlockDB() { - if( _weLocked ) { - recordTime(); // for lock stats - - if ( _nested ) - _lockState->unlockedNestable(); - else - _lockState->unlockedOther(); - - _weLocked->unlock(); - } - - if( _locked_w ) { - wassert(_lockState->threadState() == 'w'); - _lockState->unlocked(); - qlk.q.unlock_w(); - } - - if( _locked_W ) { - qlk.unlock_W(_lockState); - } - - _weLocked = 0; - _locked_W = _locked_w = false; - } - void Lock::DBRead::unlockDB() { - if( _weLocked ) { - recordTime(); // for lock stats - - if( _nested ) - _lockState->unlockedNestable(); - else - _lockState->unlockedOther(); - - _weLocked->unlock_shared(); - } - - if( _locked_r ) { - wassert(_lockState->threadState() == 'r'); - _lockState->unlocked(); - qlk.q.unlock_r(); - } - _weLocked = 0; - _locked_r = false; - } - - void Lock::DBWrite::lockTop() { + void Lock::DBRead::lockTop() { switch (_lockState->threadState()) { - case 'w': + case 0: + _lockState->lockedStart('r'); + qlk.q.lock_r(); break; - default: - verify(false); - case 0 : - verify(_lockState->threadState() == 0); - _lockState->lockedStart('w'); - qlk.q.lock_w(); - _locked_w = true; - } - } - void Lock::DBRead::lockTop() { - switch (_lockState->threadState()) { case 'r': case 'w': break; default: - verify(false); - case 0 : - verify(_lockState->threadState() == 0); - _lockState->lockedStart('r'); - qlk.q.lock_r(); - _locked_r = true; + invariant(false); } } - void Lock::DBRead::lockOther(const StringData& db) { - fassert( 16255, !db.empty() ); - - // we do checks first, as on assert destructor won't be called so don't want to be half finished with our work. - if( _lockState->otherCount() ) { - // nested. prev could be read or write. if/when we do temprelease with DBRead/DBWrite we will need to increment/decrement here - // (so we can not release or assert if nested). temprelease we should avoid if we can though, it's a bit of an anti-pattern. - invariant(db == _lockState->otherName()); + void Lock::DBRead::lockDB() { + if (_lockState->isRW()) { return; } - // first lock for this db. check consistent order with local db lock so we never deadlock. local always comes last - invariant(_lockState->nestableCount() == 0); + TrackLockAcquireTime a('r'); - if (db != _lockState->otherName()) { - DBLocksMap::ref r(dblocks); - WrapperForRWLock*& lock = r[db]; - if (lock == NULL) { - lock = new WrapperForRWLock(db); - } + const StringData db = nsToDatabaseSubstring(_ns); + const newlm::ResourceId resIdDb(newlm::RESOURCE_DATABASE, db); - _lockState->lockedOther(db, -1, lock); + // Keep the order of acquisition of the 'r' lock the same as it is in DBWrite in order to + // avoid deadlocks. See the comment inside DBWrite::lockDB for more information on the + // reason for the weird ordering of locks. + if (isLocalOrAdmin(db.toString())) { + lockTop(); } - else { - DEV OCCASIONALLY{ dassert(dblocks.get(db) == _lockState->otherLock()); } - _lockState->lockedOther(-1); + + _lockState->lock(resIdDb, newlm::MODE_S); + + if (!isLocalOrAdmin(db.toString())) { + lockTop(); } - fassert(16135,_weLocked==0); - _lockState->otherLock()->lock_shared(); - _weLocked = _lockState->otherLock(); + _lockAcquired = true; + resetTime(); } + void Lock::DBRead::unlockDB() { + if (_lockAcquired) { + const StringData db = nsToDatabaseSubstring(_ns); + const newlm::ResourceId resIdDb(newlm::RESOURCE_DATABASE, db); + + _lockState->unlock(resIdDb); + + // The last one frees the Global lock + if (_lockState->recursiveCount() == 1) { + invariant(_lockState->threadState() == 'r'); + qlk.q.unlock_r(); + _lockState->unlocked(); + recordTime(); + } + + _lockAcquired = false; + } + } + + Lock::UpgradeGlobalLockToExclusive::UpgradeGlobalLockToExclusive(LockState* lockState) : _lockState(lockState) { fassert( 16187, _lockState->threadState() == 'w' ); @@ -767,7 +641,7 @@ namespace mongo { BSONObjBuilder t; t.append( "totalTime" , (long long)(1000 * ( curTimeMillis64() - _started ) ) ); - t.append( "lockTime" , Lock::globalLockStat()->getTimeLocked( 'W' ) ); + t.append( "lockTime" , qlk.stats.getTimeLocked( 'W' ) ); // This returns the blocked lock states { @@ -811,14 +685,9 @@ namespace mongo { BSONObj generateSection( const BSONElement& configElement ) const { BSONObjBuilder b; b.append(".", qlk.stats.report()); - b.append("admin", nestableLocks[Lock::admin]->getStats().report()); - b.append("local", nestableLocks[Lock::local]->getStats().report()); - { - DBLocksMap::ref r(dblocks); - for( DBLocksMap::const_iterator i = r.r.begin(); i != r.r.end(); ++i ) { - b.append(i->first, i->second->getStats().report()); - } - } + + // TODO: Add per-db lock information here + return b.obj(); } diff --git a/src/mongo/db/concurrency/d_concurrency.h b/src/mongo/db/concurrency/d_concurrency.h index 03940bb13a8..bf77649601d 100644 --- a/src/mongo/db/concurrency/d_concurrency.h +++ b/src/mongo/db/concurrency/d_concurrency.h @@ -42,16 +42,10 @@ namespace mongo { - class WrapperForRWLock; class LockState; class Lock : boost::noncopyable { public: - enum Nestable { notnestable=0, local, admin }; - - static LockStat* globalLockStat(); - static LockStat* nestableLockStat( Nestable db ); - class ScopedLock; // note: avoid TempRelease when possible. not a good thing. @@ -84,19 +78,18 @@ namespace mongo { public: virtual ~ScopedLock(); - /** @return micros since we started acquiring */ - long long acquireFinished( LockStat* stat ); + // Start recording a new period, starting now() + void resetTime(); // Accrue elapsed lock time since last we called reset void recordTime(); - // Start recording a new period, starting now() - void resetTime(); protected: - explicit ScopedLock(LockState* lockState, char type ); + explicit ScopedLock(LockState* lockState, char type ); private: friend struct TempRelease; + void tempRelease(); // TempRelease class calls these void relock(); @@ -123,10 +116,8 @@ namespace mongo { ParallelBatchWriterSupport _pbws_lk; - void _recordTime( long long micros ); Timer _timer; char _type; // 'r','w','R','W' - LockStat* _stat; // the stat for the relevant lock to increment when we're done }; // note that for these classes recursive locking is ok if the recursive locking "makes sense" @@ -160,19 +151,8 @@ namespace mongo { // lock this database. do not shared_lock globally first, that is handledin herein. class DBWrite : public ScopedLock { - /** - * flow - * 1) lockDB - * a) lockTop - * b) lockNestable or lockOther - * 2) unlockDB - */ - void lockTop(); - void lockNestable(Nestable db); - void lockOtherWrite(const StringData& db); - void lockOtherRead(const StringData& db); - void lockDB(const std::string& ns); + void lockDB(); void unlockDB(); protected: @@ -180,24 +160,18 @@ namespace mongo { void _relock(); public: - DBWrite(LockState* lockState, const StringData& dbOrNs, bool intentWrite = false); + DBWrite(LockState* lockState, const StringData& dbOrNs); virtual ~DBWrite(); private: - bool _locked_w; - bool _locked_W; - bool _isIntentWrite; - WrapperForRWLock *_weLocked; - const std::string _what; - bool _nested; + bool _lockAcquired; + const std::string _ns; }; // lock this database for reading. do not shared_lock globally first, that is handledin herein. class DBRead : public ScopedLock { void lockTop(); - void lockNestable(Nestable db); - void lockOther(const StringData& db); - void lockDB(const std::string& ns); + void lockDB(); void unlockDB(); protected: @@ -209,11 +183,8 @@ namespace mongo { virtual ~DBRead(); private: - bool _locked_r; - WrapperForRWLock *_weLocked; - std::string _what; - bool _nested; - + bool _lockAcquired; + const std::string _ns; }; /** diff --git a/src/mongo/db/concurrency/d_concurrency_test.cpp b/src/mongo/db/concurrency/d_concurrency_test.cpp new file mode 100644 index 00000000000..19a2bbead66 --- /dev/null +++ b/src/mongo/db/concurrency/d_concurrency_test.cpp @@ -0,0 +1,82 @@ +/** + * Copyright (C) 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include <boost/scoped_ptr.hpp> +#include <boost/thread/thread.hpp> + +#include "mongo/db/concurrency/d_concurrency.h" +#include "mongo/db/concurrency/lock_state.h" +#include "mongo/unittest/unittest.h" + + +// Most of the tests here will be removed once we move everything over to using LockManager +// + +namespace mongo { + + // These two tests ensure that we have preserved the behaviour of TempRelease + TEST(DConcurrency, TempReleaseOneDB) { + LockState ls; + + Lock::DBRead r1(&ls, "db1"); + + { + Lock::TempRelease tempRelease(&ls); + } + + ls.assertAtLeastReadLocked("db1"); + } + + TEST(DConcurrency, TempReleaseRecursive) { + LockState ls; + + Lock::DBRead r1(&ls, "db1"); + Lock::DBRead r2(&ls, "db2"); + + { + Lock::TempRelease tempRelease(&ls); + + ls.assertAtLeastReadLocked("db1"); + ls.assertAtLeastReadLocked("db2"); + } + + ls.assertAtLeastReadLocked("db1"); + ls.assertAtLeastReadLocked("db2"); + } + + TEST(DConcurrency, MultipleDBLocks) { + LockState ls; + + Lock::DBWrite r1(&ls, "db1"); + Lock::DBRead r2(&ls, "db1"); + + ls.assertWriteLocked("db1"); + } +} // namespace mongo diff --git a/src/mongo/db/concurrency/lock_state.cpp b/src/mongo/db/concurrency/lock_state.cpp index a61d5133122..49307f95a7e 100644 --- a/src/mongo/db/concurrency/lock_state.cpp +++ b/src/mongo/db/concurrency/lock_state.cpp @@ -46,10 +46,6 @@ namespace mongo { _batchWriter(false), _recursive(0), _threadState(0), - _whichNestable( Lock::notnestable ), - _nestableCount(0), - _otherCount(0), - _otherLock(NULL), _scopedLk(NULL), _lockPending(false), _lockPendingParallelWriter(false) @@ -68,24 +64,6 @@ namespace mongo { return _threadState == 'r' || _threadState == 'R'; } - bool LockState::isLocked( const StringData& ns ) const { - char db[MaxDatabaseNameLen]; - nsToDatabase(ns, db); - - DEV verify( _otherName.find( '.' ) == string::npos ); // XXX this shouldn't be here, but somewhere - if ( _otherCount && db == _otherName ) - return true; - - if ( _nestableCount ) { - if ( mongoutils::str::equals( db , "local" ) ) - return _whichNestable == Lock::local; - if ( mongoutils::str::equals( db , "admin" ) ) - return _whichNestable == Lock::admin; - } - - return false; - } - bool LockState::isLocked() const { return threadState() != 0; } @@ -99,7 +77,10 @@ namespace mongo { return true; } - return isLocked(ns); + const StringData db = nsToDatabaseSubstring(ns); + const newlm::ResourceId resIdNs(newlm::RESOURCE_DATABASE, db); + + return isLockHeldForMode(resIdNs, newlm::MODE_X); } bool LockState::isAtLeastReadLocked(const StringData& ns) const { @@ -107,7 +88,11 @@ namespace mongo { return true; // global if (threadState() == 0) return false; - return isLocked(ns); + + const StringData db = nsToDatabaseSubstring(ns); + const newlm::ResourceId resIdNs(newlm::RESOURCE_DATABASE, db); + + return isLockHeldForMode(resIdNs, newlm::MODE_S); } bool LockState::isLockedForCommitting() const { @@ -138,6 +123,7 @@ namespace mongo { void LockState::lockedStart( char newState ) { _threadState = newState; } + void LockState::unlocked() { _threadState = 0; } @@ -174,22 +160,9 @@ namespace mongo { buf[1] = 0; b.append("^", buf); } - if( _nestableCount ) { - string s = "?"; - if( _whichNestable == Lock::local ) - s = "^local"; - else if( _whichNestable == Lock::admin ) - s = "^admin"; - b.append(s, kind(_nestableCount)); - } - if( _otherCount ) { - WrapperForRWLock *k = _otherLock; - if( k ) { - string s = "^"; - s += k->name(); - b.append(s, kind(_otherCount)); - } - } + + // SERVER-14978: Report state from the Locker + BSONObj o = b.obj(); if (!o.isEmpty()) { res->append("locks", o); @@ -205,104 +178,33 @@ namespace mongo { ss << "unlocked"; } else { - ss << s; - if( _recursive ) { - ss << " recursive:" << _recursive; - } - ss << " otherCount:" << _otherCount; - if( _otherCount ) { - ss << " otherdb:" << _otherName; - } - if( _nestableCount ) { - ss << " nestableCount:" << _nestableCount << " which:"; - if( _whichNestable == Lock::local ) - ss << "local"; - else if( _whichNestable == Lock::admin ) - ss << "admin"; - else - ss << (int)_whichNestable; - } + // SERVER-14978: Dump lock stats information } log() << ss.str() << endl; } - void LockState::enterScopedLock( Lock::ScopedLock* lock ) { + void LockState::enterScopedLock(Lock::ScopedLock* lock) { _recursive++; - if ( _recursive == 1 ) { - fassert(16115, _scopedLk == 0); + if (_recursive == 1) { + invariant(_scopedLk == NULL); _scopedLk = lock; } } - Lock::ScopedLock* LockState::leaveScopedLock() { - _recursive--; - dassert( _recursive < 10000 ); - Lock::ScopedLock* temp = _scopedLk; - - if ( _recursive > 0 ) { - return NULL; - } - - _scopedLk = NULL; - return temp; - } - - void LockState::lockedNestable( Lock::Nestable what , int type) { - verify( type ); - _whichNestable = what; - _nestableCount += type; - } - - void LockState::unlockedNestable() { - _whichNestable = Lock::notnestable; - _nestableCount = 0; - } - - void LockState::lockedOther( int type ) { - fassert( 16231 , _otherCount == 0 ); - _otherCount = type; - } - - void LockState::lockedOther( const StringData& other , int type , WrapperForRWLock* lock ) { - fassert( 16170 , _otherCount == 0 ); - _otherName = other.toString(); - _otherCount = type; - _otherLock = lock; - } - - void LockState::unlockedOther() { - // we leave _otherName and _otherLock set as - // _otherLock exists to cache a pointer - _otherCount = 0; + Lock::ScopedLock* LockState::getCurrentScopedLock() const { + invariant(_recursive == 1); + return _scopedLk; } - LockStat* LockState::getRelevantLockStat() { - if ( _whichNestable ) - return Lock::nestableLockStat( _whichNestable ); - - if ( _otherCount && _otherLock ) - return &_otherLock->getStats(); - - if ( isRW() ) - return Lock::globalLockStat(); - - return 0; - } - - - Acquiring::Acquiring( Lock::ScopedLock* lock, LockState& ls ) - : _lock( lock ), _ls( ls ){ - _ls._lockPending = true; - } - - Acquiring::~Acquiring() { - _ls._lockPending = false; - LockStat* stat = _ls.getRelevantLockStat(); - if ( stat && _lock ) { - // increment the global stats for this counter - stat->recordAcquireTimeMicros( _ls.threadState(), _lock->acquireFinished( stat ) ); + void LockState::leaveScopedLock(Lock::ScopedLock* lock) { + if (_recursive == 1) { + // Sanity check we are releasing the same lock + invariant(_scopedLk == lock); + _scopedLk = NULL; } + _recursive--; } + AcquiringParallelWriter::AcquiringParallelWriter( LockState& ls ) : _ls( ls ) { diff --git a/src/mongo/db/concurrency/lock_state.h b/src/mongo/db/concurrency/lock_state.h index dcc69216b78..d349fbea96f 100644 --- a/src/mongo/db/concurrency/lock_state.h +++ b/src/mongo/db/concurrency/lock_state.h @@ -1,32 +1,32 @@ // lock_state.h /** -* Copyright (C) 2008 10gen Inc. -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see <http://www.gnu.org/licenses/>. -* -* As a special exception, the copyright holders give permission to link the -* code of portions of this program with the OpenSSL library under certain -* conditions as described in each individual source file and distribute -* linked combinations including the program with the OpenSSL library. You -* must comply with the GNU Affero General Public License in all respects for -* all of the code used other than as permitted herein. If you modify file(s) -* with this exception, you may extend this exception to your version of the -* file(s), but you are not obligated to do so. If you do not wish to do so, -* delete this exception statement from your version. If you delete this -* exception statement from all source files in the program, then also delete -* it in the license file. -*/ + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ #pragma once @@ -35,10 +35,6 @@ namespace mongo { - - class Acquiring; - - namespace newlm { /** @@ -185,7 +181,14 @@ namespace newlm { } // namespace newlm - // per thread + /** + * One of these exists per OperationContext and serves as interface for acquiring locks and + * obtaining lock statistics for this particular operation. + * + * TODO: It is only temporary that this class inherits from Locker. Both will eventually be + * merged and most of the code in LockState will go away (i.e., once we move the GlobalLock to + * be its own lock resource under the lock manager). + */ class LockState : public newlm::Locker { public: LockState(); @@ -198,7 +201,9 @@ namespace newlm { unsigned recursiveCount() const { return _recursive; } /** - * @return 0 rwRW + * Indicates the mode of acquisition of the GlobalLock by this particular thread. The + * return values are '0' (no global lock is held), 'r', 'w', 'R', 'W'. See the commends of + * QLock for more information on what these modes mean. */ char threadState() const { return _threadState; } @@ -206,7 +211,6 @@ namespace newlm { bool isW() const; // W bool hasAnyReadLock() const; // explicitly rR - bool isLocked(const StringData& ns) const; // rwRW bool isLocked() const; bool isWriteLocked() const; bool isWriteLocked(const StringData& ns) const; @@ -231,25 +235,14 @@ namespace newlm { * this is mostly for W_to_R or R_to_W */ void changeLockState( char newstate ); - - Lock::Nestable whichNestable() const { return _whichNestable; } - int nestableCount() const { return _nestableCount; } - - int otherCount() const { return _otherCount; } - const std::string& otherName() const { return _otherName; } - WrapperForRWLock* otherLock() const { return _otherLock; } - void enterScopedLock( Lock::ScopedLock* lock ); - Lock::ScopedLock* leaveScopedLock(); - - void lockedNestable( Lock::Nestable what , int type ); - void unlockedNestable(); - void lockedOther( const StringData& db , int type , WrapperForRWLock* lock ); - void lockedOther( int type ); // "same lock as last time" case - void unlockedOther(); + // Those are only used for TempRelease. Eventually they should be removed. + void enterScopedLock(Lock::ScopedLock* lock); + Lock::ScopedLock* getCurrentScopedLock() const; + void leaveScopedLock(Lock::ScopedLock* lock); + bool _batchWriter; - LockStat* getRelevantLockStat(); void recordLockTime() { _scopedLk->recordTime(); } void resetLockTime() { _scopedLk->resetTime(); } @@ -259,14 +252,6 @@ namespace newlm { // global lock related char _threadState; // 0, 'r', 'w', 'R', 'W' - // db level locking related - Lock::Nestable _whichNestable; - int _nestableCount; // recursive lock count on local or admin db XXX - change name - - int _otherCount; // >0 means write lock, <0 read lock - XXX change name - std::string _otherName; // which database are we locking and working with (besides local/admin) - WrapperForRWLock* _otherLock; // so we don't have to check the map too often (the map has a mutex) - // for temprelease // for the nonrecursive case. otherwise there would be many // the first lock goes here, which is ok since we can't yield recursive locks @@ -275,42 +260,9 @@ namespace newlm { bool _lockPending; bool _lockPendingParallelWriter; - friend class Acquiring; friend class AcquiringParallelWriter; }; - class WrapperForRWLock : boost::noncopyable { - SimpleRWLock rw; - SimpleMutex m; - bool sharedLatching; - LockStat stats; - public: - std::string name() const { return rw.name; } - LockStat& getStats() { return stats; } - - WrapperForRWLock(const StringData& name) - : rw(name), m(name) { - // For the local datbase, all operations are short, - // either writing one entry, or doing a tail. - // In tests, use a SimpleMutex is much faster for the local db. - sharedLatching = name != "local"; - } - void lock() { if ( sharedLatching ) { rw.lock(); } else { m.lock(); } } - void lock_shared() { if ( sharedLatching ) { rw.lock_shared(); } else { m.lock(); } } - void unlock() { if ( sharedLatching ) { rw.unlock(); } else { m.unlock(); } } - void unlock_shared() { if ( sharedLatching ) { rw.unlock_shared(); } else { m.unlock(); } } - }; - - class ScopedLock; - - class Acquiring { - public: - Acquiring( Lock::ScopedLock* lock, LockState& ls ); - ~Acquiring(); - private: - Lock::ScopedLock* _lock; - LockState& _ls; - }; class AcquiringParallelWriter { public: diff --git a/src/mongo/db/concurrency/lock_state_test.cpp b/src/mongo/db/concurrency/lock_state_test.cpp index 29bf759c458..28cb9c700da 100644 --- a/src/mongo/db/concurrency/lock_state_test.cpp +++ b/src/mongo/db/concurrency/lock_state_test.cpp @@ -1,30 +1,32 @@ /** -* Copyright (C) 2014 MongoDB Inc. -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see <http://www.gnu.org/licenses/>. -* -* As a special exception, the copyright holders give permission to link the -* code of portions of this program with the OpenSSL library under certain -* conditions as described in each individual source file and distribute -* linked combinations including the program with the OpenSSL library. You -* must comply with the GNU Affero General Public License in all respects for -* all of the code used other than as permitted herein. If you modify file(s) -* with this exception, you may extend this exception to your version of the -* file(s), but you are not obligated to do so. If you do not wish to do so, -* delete this exception statement from your version. If you delete this -* exception statement from all source files in the program, then also delete -* it in the license file. -*/ + * Copyright (C) 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" #include <boost/scoped_ptr.hpp> #include <boost/thread/thread.hpp> diff --git a/src/mongo/db/instance.cpp b/src/mongo/db/instance.cpp index 6c8a8f5d61e..fb827ad9b99 100644 --- a/src/mongo/db/instance.cpp +++ b/src/mongo/db/instance.cpp @@ -603,7 +603,7 @@ namespace mongo { UpdateExecutor executor(&request, &op.debug()); uassertStatusOK(executor.prepare()); - Lock::DBWrite lk(txn->lockState(), ns.ns(), useExperimentalDocLocking); + Lock::DBWrite lk(txn->lockState(), ns.ns()); // if this ever moves to outside of lock, need to adjust check // Client::Context::_finishInit diff --git a/src/mongo/dbtests/framework.cpp b/src/mongo/dbtests/framework.cpp index c0704f60e2d..2604571300c 100644 --- a/src/mongo/dbtests/framework.cpp +++ b/src/mongo/dbtests/framework.cpp @@ -28,7 +28,7 @@ * then also delete it in the license file. */ -#include "mongo/pch.h" +#include "mongo/platform/basic.h" #include "mongo/dbtests/framework.h" @@ -40,6 +40,7 @@ #include "mongo/base/initializer.h" #include "mongo/base/status.h" #include "mongo/db/client.h" +#include "mongo/db/concurrency/lock_state.h" #include "mongo/db/ops/update.h" #include "mongo/db/storage/storage_engine.h" #include "mongo/dbtests/dbtests.h" @@ -92,6 +93,9 @@ namespace mongo { } else if (minutesRunning > 1){ warning() << currentTestName << " has been running for more than " << minutesRunning-1 << " minutes." << endl; + + // See what is stuck + newlm::Locker::dumpGlobalLockManager(); } } } diff --git a/src/mongo/dbtests/threadedtests.cpp b/src/mongo/dbtests/threadedtests.cpp index 425409d3848..ff03f10d65f 100644 --- a/src/mongo/dbtests/threadedtests.cpp +++ b/src/mongo/dbtests/threadedtests.cpp @@ -128,7 +128,14 @@ namespace ThreadedTests { virtual void subthread(int tnumber) { Client::initThread("mongomutextest"); + LockState lockState; + mongo::unittest::log().stream() + << "Thread " + << boost::this_thread::get_id() + << " has lock state " + << &lockState + << '\n'; sleepmillis(0); for( int i = 0; i < N; i++ ) { |