diff options
Diffstat (limited to 'src/mongo/db/concurrency/lock_state.cpp')
-rw-r--r-- | src/mongo/db/concurrency/lock_state.cpp | 1293 |
1 files changed, 643 insertions, 650 deletions
diff --git a/src/mongo/db/concurrency/lock_state.cpp b/src/mongo/db/concurrency/lock_state.cpp index 6c12a8ae1b1..37ca3c7f611 100644 --- a/src/mongo/db/concurrency/lock_state.cpp +++ b/src/mongo/db/concurrency/lock_state.cpp @@ -46,104 +46,102 @@ namespace mongo { namespace { - /** - * Partitioned global lock statistics, so we don't hit the same bucket. - */ - class PartitionedInstanceWideLockStats { - MONGO_DISALLOW_COPYING(PartitionedInstanceWideLockStats); - public: +/** + * Partitioned global lock statistics, so we don't hit the same bucket. + */ +class PartitionedInstanceWideLockStats { + MONGO_DISALLOW_COPYING(PartitionedInstanceWideLockStats); - PartitionedInstanceWideLockStats() { } +public: + PartitionedInstanceWideLockStats() {} - void recordAcquisition(LockerId id, ResourceId resId, LockMode mode) { - _get(id).recordAcquisition(resId, mode); - } + void recordAcquisition(LockerId id, ResourceId resId, LockMode mode) { + _get(id).recordAcquisition(resId, mode); + } - void recordWait(LockerId id, ResourceId resId, LockMode mode) { - _get(id).recordWait(resId, mode); - } + void recordWait(LockerId id, ResourceId resId, LockMode mode) { + _get(id).recordWait(resId, mode); + } - void recordWaitTime(LockerId id, ResourceId resId, LockMode mode, uint64_t waitMicros) { - _get(id).recordWaitTime(resId, mode, waitMicros); - } + void recordWaitTime(LockerId id, ResourceId resId, LockMode mode, uint64_t waitMicros) { + _get(id).recordWaitTime(resId, mode, waitMicros); + } - void recordDeadlock(ResourceId resId, LockMode mode) { - _get(resId).recordDeadlock(resId, mode); - } + void recordDeadlock(ResourceId resId, LockMode mode) { + _get(resId).recordDeadlock(resId, mode); + } - void report(SingleThreadedLockStats* outStats) const { - for (int i = 0; i < NumPartitions; i++) { - outStats->append(_partitions[i].stats); - } + void report(SingleThreadedLockStats* outStats) const { + for (int i = 0; i < NumPartitions; i++) { + outStats->append(_partitions[i].stats); } + } - void reset() { - for (int i = 0; i < NumPartitions; i++) { - _partitions[i].stats.reset(); - } + void reset() { + for (int i = 0; i < NumPartitions; i++) { + _partitions[i].stats.reset(); } + } - private: - - // This alignment is a best effort approach to ensure that each partition falls on a - // separate page/cache line in order to avoid false sharing. - struct MONGO_COMPILER_ALIGN_TYPE(128) AlignedLockStats { - AtomicLockStats stats; - }; +private: + // This alignment is a best effort approach to ensure that each partition falls on a + // separate page/cache line in order to avoid false sharing. + struct MONGO_COMPILER_ALIGN_TYPE(128) AlignedLockStats { + AtomicLockStats stats; + }; - enum { NumPartitions = 8 }; + enum { NumPartitions = 8 }; - AtomicLockStats& _get(LockerId id) { - return _partitions[id % NumPartitions].stats; - } + AtomicLockStats& _get(LockerId id) { + return _partitions[id % NumPartitions].stats; + } - AlignedLockStats _partitions[NumPartitions]; - }; + AlignedLockStats _partitions[NumPartitions]; +}; - // Global lock manager instance. - LockManager globalLockManager; +// Global lock manager instance. +LockManager globalLockManager; - // Global lock. Every server operation, which uses the Locker must acquire this lock at least - // once. See comments in the header file (begin/endTransaction) for more information. - const ResourceId resourceIdGlobal = ResourceId(RESOURCE_GLOBAL, - ResourceId::SINGLETON_GLOBAL); +// Global lock. Every server operation, which uses the Locker must acquire this lock at least +// once. See comments in the header file (begin/endTransaction) for more information. +const ResourceId resourceIdGlobal = ResourceId(RESOURCE_GLOBAL, ResourceId::SINGLETON_GLOBAL); - // Flush lock. This is only used for the MMAP V1 storage engine and synchronizes journal writes - // to the shared view and remaps. See the comments in the header for information on how MMAP V1 - // concurrency control works. - const ResourceId resourceIdMMAPV1Flush = ResourceId(RESOURCE_MMAPV1_FLUSH, - ResourceId::SINGLETON_MMAPV1_FLUSH); +// Flush lock. This is only used for the MMAP V1 storage engine and synchronizes journal writes +// to the shared view and remaps. See the comments in the header for information on how MMAP V1 +// concurrency control works. +const ResourceId resourceIdMMAPV1Flush = + ResourceId(RESOURCE_MMAPV1_FLUSH, ResourceId::SINGLETON_MMAPV1_FLUSH); - // How often (in millis) to check for deadlock if a lock has not been granted for some time - const unsigned DeadlockTimeoutMs = 500; +// How often (in millis) to check for deadlock if a lock has not been granted for some time +const unsigned DeadlockTimeoutMs = 500; - // Dispenses unique LockerId identifiers - AtomicUInt64 idCounter(0); +// Dispenses unique LockerId identifiers +AtomicUInt64 idCounter(0); - // Partitioned global lock statistics, so we don't hit the same bucket - PartitionedInstanceWideLockStats globalStats; +// Partitioned global lock statistics, so we don't hit the same bucket +PartitionedInstanceWideLockStats globalStats; - /** - * Whether the particular lock's release should be held until the end of the operation. We - * delay release of exclusive locks (locks that are for write operations) in order to ensure - * that the data they protect is committed successfully. - */ - bool shouldDelayUnlock(ResourceId resId, LockMode mode) { - // Global and flush lock are not used to protect transactional resources and as such, they - // need to be acquired and released when requested. - if (resId.getType() == RESOURCE_GLOBAL) { - return false; - } +/** + * Whether the particular lock's release should be held until the end of the operation. We + * delay release of exclusive locks (locks that are for write operations) in order to ensure + * that the data they protect is committed successfully. + */ +bool shouldDelayUnlock(ResourceId resId, LockMode mode) { + // Global and flush lock are not used to protect transactional resources and as such, they + // need to be acquired and released when requested. + if (resId.getType() == RESOURCE_GLOBAL) { + return false; + } - if (resId == resourceIdMMAPV1Flush) { - return false; - } + if (resId == resourceIdMMAPV1Flush) { + return false; + } - switch (mode) { + switch (mode) { case MODE_X: case MODE_IX: return true; @@ -154,614 +152,612 @@ namespace { default: invariant(false); - } } +} -} // namespace +} // namespace - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::isW() const { - return getLockMode(resourceIdGlobal) == MODE_X; - } +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::isW() const { + return getLockMode(resourceIdGlobal) == MODE_X; +} - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::isR() const { - return getLockMode(resourceIdGlobal) == MODE_S; - } +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::isR() const { + return getLockMode(resourceIdGlobal) == MODE_S; +} - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::isLocked() const { - return getLockMode(resourceIdGlobal) != MODE_NONE; - } +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::isLocked() const { + return getLockMode(resourceIdGlobal) != MODE_NONE; +} - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::isWriteLocked() const { - return isLockHeldForMode(resourceIdGlobal, MODE_IX); - } +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::isWriteLocked() const { + return isLockHeldForMode(resourceIdGlobal, MODE_IX); +} - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::isReadLocked() const { - return isLockHeldForMode(resourceIdGlobal, MODE_IS); - } +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::isReadLocked() const { + return isLockHeldForMode(resourceIdGlobal, MODE_IS); +} - template<bool IsForMMAPV1> - void LockerImpl<IsForMMAPV1>::assertEmptyAndReset() { - invariant(!inAWriteUnitOfWork()); - invariant(_resourcesToUnlockAtEndOfUnitOfWork.empty()); - invariant(_requests.empty()); +template <bool IsForMMAPV1> +void LockerImpl<IsForMMAPV1>::assertEmptyAndReset() { + invariant(!inAWriteUnitOfWork()); + invariant(_resourcesToUnlockAtEndOfUnitOfWork.empty()); + invariant(_requests.empty()); - // Reset the locking statistics so the object can be reused - _stats.reset(); - } + // Reset the locking statistics so the object can be reused + _stats.reset(); +} - template<bool IsForMMAPV1> - void LockerImpl<IsForMMAPV1>::dump() const { - StringBuilder ss; - ss << "Locker id " << _id << " status: "; +template <bool IsForMMAPV1> +void LockerImpl<IsForMMAPV1>::dump() const { + StringBuilder ss; + ss << "Locker id " << _id << " status: "; - _lock.lock(); - LockRequestsMap::ConstIterator it = _requests.begin(); - while (!it.finished()) { - ss << it.key().toString() << " " - << lockRequestStatusName(it->status) << " in " - << modeName(it->mode) << "; "; - it.next(); - } - _lock.unlock(); - - log() << ss.str() << std::endl; + _lock.lock(); + LockRequestsMap::ConstIterator it = _requests.begin(); + while (!it.finished()) { + ss << it.key().toString() << " " << lockRequestStatusName(it->status) << " in " + << modeName(it->mode) << "; "; + it.next(); } + _lock.unlock(); + log() << ss.str() << std::endl; +} - // - // CondVarLockGrantNotification - // - CondVarLockGrantNotification::CondVarLockGrantNotification() { - clear(); - } +// +// CondVarLockGrantNotification +// - void CondVarLockGrantNotification::clear() { - _result = LOCK_INVALID; - } +CondVarLockGrantNotification::CondVarLockGrantNotification() { + clear(); +} - LockResult CondVarLockGrantNotification::wait(unsigned timeoutMs) { - stdx::unique_lock<stdx::mutex> lock(_mutex); - while (_result == LOCK_INVALID) { - if (boost::cv_status::timeout == _cond.wait_for(lock, Milliseconds(timeoutMs))) { - // Timeout - return LOCK_TIMEOUT; - } - } +void CondVarLockGrantNotification::clear() { + _result = LOCK_INVALID; +} - return _result; +LockResult CondVarLockGrantNotification::wait(unsigned timeoutMs) { + stdx::unique_lock<stdx::mutex> lock(_mutex); + while (_result == LOCK_INVALID) { + if (boost::cv_status::timeout == _cond.wait_for(lock, Milliseconds(timeoutMs))) { + // Timeout + return LOCK_TIMEOUT; + } } - void CondVarLockGrantNotification::notify(ResourceId resId, LockResult result) { - stdx::unique_lock<stdx::mutex> lock(_mutex); - invariant(_result == LOCK_INVALID); - _result = result; + return _result; +} - _cond.notify_all(); - } +void CondVarLockGrantNotification::notify(ResourceId resId, LockResult result) { + stdx::unique_lock<stdx::mutex> lock(_mutex); + invariant(_result == LOCK_INVALID); + _result = result; + _cond.notify_all(); +} - // - // Locker - // - template<bool IsForMMAPV1> - LockerImpl<IsForMMAPV1>::LockerImpl() - : _id(idCounter.addAndFetch(1)), - _requestStartTime(0), - _wuowNestingLevel(0), - _batchWriter(false) { - } +// +// Locker +// - template<bool IsForMMAPV1> - LockerImpl<IsForMMAPV1>::~LockerImpl() { - // Cannot delete the Locker while there are still outstanding requests, because the - // LockManager may attempt to access deleted memory. Besides it is probably incorrect - // to delete with unaccounted locks anyways. - assertEmptyAndReset(); - } +template <bool IsForMMAPV1> +LockerImpl<IsForMMAPV1>::LockerImpl() + : _id(idCounter.addAndFetch(1)), + _requestStartTime(0), + _wuowNestingLevel(0), + _batchWriter(false) {} - template<bool IsForMMAPV1> - LockResult LockerImpl<IsForMMAPV1>::lockGlobal(LockMode mode, unsigned timeoutMs) { - LockResult result = lockGlobalBegin(mode); - if (result == LOCK_WAITING) { - result = lockGlobalComplete(timeoutMs); - } +template <bool IsForMMAPV1> +LockerImpl<IsForMMAPV1>::~LockerImpl() { + // Cannot delete the Locker while there are still outstanding requests, because the + // LockManager may attempt to access deleted memory. Besides it is probably incorrect + // to delete with unaccounted locks anyways. + assertEmptyAndReset(); +} - if (result == LOCK_OK) { - lockMMAPV1Flush(); - } +template <bool IsForMMAPV1> +LockResult LockerImpl<IsForMMAPV1>::lockGlobal(LockMode mode, unsigned timeoutMs) { + LockResult result = lockGlobalBegin(mode); + if (result == LOCK_WAITING) { + result = lockGlobalComplete(timeoutMs); + } - return result; + if (result == LOCK_OK) { + lockMMAPV1Flush(); } - template<bool IsForMMAPV1> - LockResult LockerImpl<IsForMMAPV1>::lockGlobalBegin(LockMode mode) { - const LockResult result = lockBegin(resourceIdGlobal, mode); - if (result == LOCK_OK) return LOCK_OK; + return result; +} - // Currently, deadlock detection does not happen inline with lock acquisition so the only - // unsuccessful result that the lock manager would return is LOCK_WAITING. - invariant(result == LOCK_WAITING); +template <bool IsForMMAPV1> +LockResult LockerImpl<IsForMMAPV1>::lockGlobalBegin(LockMode mode) { + const LockResult result = lockBegin(resourceIdGlobal, mode); + if (result == LOCK_OK) + return LOCK_OK; - return result; - } + // Currently, deadlock detection does not happen inline with lock acquisition so the only + // unsuccessful result that the lock manager would return is LOCK_WAITING. + invariant(result == LOCK_WAITING); - template<bool IsForMMAPV1> - LockResult LockerImpl<IsForMMAPV1>::lockGlobalComplete(unsigned timeoutMs) { - return lockComplete(resourceIdGlobal, getLockMode(resourceIdGlobal), timeoutMs, false); - } + return result; +} - template<bool IsForMMAPV1> - void LockerImpl<IsForMMAPV1>::lockMMAPV1Flush() { - if (!IsForMMAPV1) return; +template <bool IsForMMAPV1> +LockResult LockerImpl<IsForMMAPV1>::lockGlobalComplete(unsigned timeoutMs) { + return lockComplete(resourceIdGlobal, getLockMode(resourceIdGlobal), timeoutMs, false); +} - // The flush lock always has a reference count of 1, because it is dropped at the end of - // each write unit of work in order to allow the flush thread to run. See the comments in - // the header for information on how the MMAP V1 journaling system works. - LockRequest* globalLockRequest = _requests.find(resourceIdGlobal).objAddr(); - if (globalLockRequest->recursiveCount == 1) { - invariant(LOCK_OK == lock(resourceIdMMAPV1Flush, _getModeForMMAPV1FlushLock())); - } +template <bool IsForMMAPV1> +void LockerImpl<IsForMMAPV1>::lockMMAPV1Flush() { + if (!IsForMMAPV1) + return; - dassert(getLockMode(resourceIdMMAPV1Flush) == _getModeForMMAPV1FlushLock()); + // The flush lock always has a reference count of 1, because it is dropped at the end of + // each write unit of work in order to allow the flush thread to run. See the comments in + // the header for information on how the MMAP V1 journaling system works. + LockRequest* globalLockRequest = _requests.find(resourceIdGlobal).objAddr(); + if (globalLockRequest->recursiveCount == 1) { + invariant(LOCK_OK == lock(resourceIdMMAPV1Flush, _getModeForMMAPV1FlushLock())); } - template<bool IsForMMAPV1> - void LockerImpl<IsForMMAPV1>::downgradeGlobalXtoSForMMAPV1() { - invariant(!inAWriteUnitOfWork()); + dassert(getLockMode(resourceIdMMAPV1Flush) == _getModeForMMAPV1FlushLock()); +} - LockRequest* globalLockRequest = _requests.find(resourceIdGlobal).objAddr(); - invariant(globalLockRequest->mode == MODE_X); - invariant(globalLockRequest->recursiveCount == 1); +template <bool IsForMMAPV1> +void LockerImpl<IsForMMAPV1>::downgradeGlobalXtoSForMMAPV1() { + invariant(!inAWriteUnitOfWork()); - // Making this call here will record lock downgrades as acquisitions, which is acceptable - globalStats.recordAcquisition(_id, resourceIdGlobal, MODE_S); - _stats.recordAcquisition(resourceIdGlobal, MODE_S); + LockRequest* globalLockRequest = _requests.find(resourceIdGlobal).objAddr(); + invariant(globalLockRequest->mode == MODE_X); + invariant(globalLockRequest->recursiveCount == 1); - globalLockManager.downgrade(globalLockRequest, MODE_S); + // Making this call here will record lock downgrades as acquisitions, which is acceptable + globalStats.recordAcquisition(_id, resourceIdGlobal, MODE_S); + _stats.recordAcquisition(resourceIdGlobal, MODE_S); - if (IsForMMAPV1) { - invariant(unlock(resourceIdMMAPV1Flush)); - } + globalLockManager.downgrade(globalLockRequest, MODE_S); + + if (IsForMMAPV1) { + invariant(unlock(resourceIdMMAPV1Flush)); } +} - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::unlockAll() { - if (!unlock(resourceIdGlobal)) { - return false; - } +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::unlockAll() { + if (!unlock(resourceIdGlobal)) { + return false; + } - LockRequestsMap::Iterator it = _requests.begin(); - while (!it.finished()) { - // If we're here we should only have one reference to any lock. It is a programming - // error for any lock to have more references than the global lock, because every - // scope starts by calling lockGlobal. - if (it.key().getType() == RESOURCE_GLOBAL) { - it.next(); - } - else { - invariant(_unlockImpl(it)); - } + LockRequestsMap::Iterator it = _requests.begin(); + while (!it.finished()) { + // If we're here we should only have one reference to any lock. It is a programming + // error for any lock to have more references than the global lock, because every + // scope starts by calling lockGlobal. + if (it.key().getType() == RESOURCE_GLOBAL) { + it.next(); + } else { + invariant(_unlockImpl(it)); } - - return true; } - template<bool IsForMMAPV1> - void LockerImpl<IsForMMAPV1>::beginWriteUnitOfWork() { - // Sanity check that write transactions under MMAP V1 have acquired the flush lock, so we - // don't allow partial changes to be written. - dassert(!IsForMMAPV1 || isLockHeldForMode(resourceIdMMAPV1Flush, MODE_IX)); + return true; +} - _wuowNestingLevel++; - } +template <bool IsForMMAPV1> +void LockerImpl<IsForMMAPV1>::beginWriteUnitOfWork() { + // Sanity check that write transactions under MMAP V1 have acquired the flush lock, so we + // don't allow partial changes to be written. + dassert(!IsForMMAPV1 || isLockHeldForMode(resourceIdMMAPV1Flush, MODE_IX)); - template<bool IsForMMAPV1> - void LockerImpl<IsForMMAPV1>::endWriteUnitOfWork() { - invariant(_wuowNestingLevel > 0); + _wuowNestingLevel++; +} - if (--_wuowNestingLevel > 0) { - // Don't do anything unless leaving outermost WUOW. - return; - } +template <bool IsForMMAPV1> +void LockerImpl<IsForMMAPV1>::endWriteUnitOfWork() { + invariant(_wuowNestingLevel > 0); - while (!_resourcesToUnlockAtEndOfUnitOfWork.empty()) { - unlock(_resourcesToUnlockAtEndOfUnitOfWork.front()); - _resourcesToUnlockAtEndOfUnitOfWork.pop(); - } + if (--_wuowNestingLevel > 0) { + // Don't do anything unless leaving outermost WUOW. + return; + } - // For MMAP V1, we need to yield the flush lock so that the flush thread can run - if (IsForMMAPV1) { - invariant(unlock(resourceIdMMAPV1Flush)); - invariant(LOCK_OK == lock(resourceIdMMAPV1Flush, _getModeForMMAPV1FlushLock())); - } + while (!_resourcesToUnlockAtEndOfUnitOfWork.empty()) { + unlock(_resourcesToUnlockAtEndOfUnitOfWork.front()); + _resourcesToUnlockAtEndOfUnitOfWork.pop(); } - template<bool IsForMMAPV1> - LockResult LockerImpl<IsForMMAPV1>::lock(ResourceId resId, - LockMode mode, - unsigned timeoutMs, - bool checkDeadlock) { + // For MMAP V1, we need to yield the flush lock so that the flush thread can run + if (IsForMMAPV1) { + invariant(unlock(resourceIdMMAPV1Flush)); + invariant(LOCK_OK == lock(resourceIdMMAPV1Flush, _getModeForMMAPV1FlushLock())); + } +} - const LockResult result = lockBegin(resId, mode); +template <bool IsForMMAPV1> +LockResult LockerImpl<IsForMMAPV1>::lock(ResourceId resId, + LockMode mode, + unsigned timeoutMs, + bool checkDeadlock) { + const LockResult result = lockBegin(resId, mode); - // Fast, uncontended path - if (result == LOCK_OK) return LOCK_OK; + // Fast, uncontended path + if (result == LOCK_OK) + return LOCK_OK; - // Currently, deadlock detection does not happen inline with lock acquisition so the only - // unsuccessful result that the lock manager would return is LOCK_WAITING. - invariant(result == LOCK_WAITING); + // Currently, deadlock detection does not happen inline with lock acquisition so the only + // unsuccessful result that the lock manager would return is LOCK_WAITING. + invariant(result == LOCK_WAITING); - return lockComplete(resId, mode, timeoutMs, checkDeadlock); - } + return lockComplete(resId, mode, timeoutMs, checkDeadlock); +} - template<bool IsForMMAPV1> - void LockerImpl<IsForMMAPV1>::downgrade(ResourceId resId, LockMode newMode) { - LockRequestsMap::Iterator it = _requests.find(resId); - globalLockManager.downgrade(it.objAddr(), newMode); - } +template <bool IsForMMAPV1> +void LockerImpl<IsForMMAPV1>::downgrade(ResourceId resId, LockMode newMode) { + LockRequestsMap::Iterator it = _requests.find(resId); + globalLockManager.downgrade(it.objAddr(), newMode); +} - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::unlock(ResourceId resId) { - LockRequestsMap::Iterator it = _requests.find(resId); - return _unlockImpl(it); - } +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::unlock(ResourceId resId) { + LockRequestsMap::Iterator it = _requests.find(resId); + return _unlockImpl(it); +} - template<bool IsForMMAPV1> - LockMode LockerImpl<IsForMMAPV1>::getLockMode(ResourceId resId) const { - scoped_spinlock scopedLock(_lock); +template <bool IsForMMAPV1> +LockMode LockerImpl<IsForMMAPV1>::getLockMode(ResourceId resId) const { + scoped_spinlock scopedLock(_lock); - const LockRequestsMap::ConstIterator it = _requests.find(resId); - if (!it) return MODE_NONE; + const LockRequestsMap::ConstIterator it = _requests.find(resId); + if (!it) + return MODE_NONE; - return it->mode; - } + return it->mode; +} - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::isLockHeldForMode(ResourceId resId, LockMode mode) const { - return isModeCovered(mode, getLockMode(resId)); - } +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::isLockHeldForMode(ResourceId resId, LockMode mode) const { + return isModeCovered(mode, getLockMode(resId)); +} - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::isDbLockedForMode(StringData dbName, - LockMode mode) const { - invariant(nsIsDbOnly(dbName)); +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::isDbLockedForMode(StringData dbName, LockMode mode) const { + invariant(nsIsDbOnly(dbName)); - if (isW()) return true; - if (isR() && isSharedLockMode(mode)) return true; + if (isW()) + return true; + if (isR() && isSharedLockMode(mode)) + return true; - const ResourceId resIdDb(RESOURCE_DATABASE, dbName); - return isLockHeldForMode(resIdDb, mode); - } + const ResourceId resIdDb(RESOURCE_DATABASE, dbName); + return isLockHeldForMode(resIdDb, mode); +} - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::isCollectionLockedForMode(StringData ns, - LockMode mode) const { - invariant(nsIsFull(ns)); +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::isCollectionLockedForMode(StringData ns, LockMode mode) const { + invariant(nsIsFull(ns)); - if (isW()) return true; - if (isR() && isSharedLockMode(mode)) return true; + if (isW()) + return true; + if (isR() && isSharedLockMode(mode)) + return true; - const NamespaceString nss(ns); - const ResourceId resIdDb(RESOURCE_DATABASE, nss.db()); + const NamespaceString nss(ns); + const ResourceId resIdDb(RESOURCE_DATABASE, nss.db()); - LockMode dbMode = getLockMode(resIdDb); + LockMode dbMode = getLockMode(resIdDb); - switch (dbMode) { - case MODE_NONE: return false; - case MODE_X: return true; - case MODE_S: return isSharedLockMode(mode); + switch (dbMode) { + case MODE_NONE: + return false; + case MODE_X: + return true; + case MODE_S: + return isSharedLockMode(mode); case MODE_IX: - case MODE_IS: - { - const ResourceId resIdColl(RESOURCE_COLLECTION, ns); - return isLockHeldForMode(resIdColl, mode); - } - break; + case MODE_IS: { + const ResourceId resIdColl(RESOURCE_COLLECTION, ns); + return isLockHeldForMode(resIdColl, mode); + } break; case LockModesCount: break; - } - - invariant(false); - return false; } - template<bool IsForMMAPV1> - ResourceId LockerImpl<IsForMMAPV1>::getWaitingResource() const { - scoped_spinlock scopedLock(_lock); + invariant(false); + return false; +} - LockRequestsMap::ConstIterator it = _requests.begin(); - while (!it.finished()) { - if (it->status != LockRequest::STATUS_GRANTED) { - return it.key(); - } +template <bool IsForMMAPV1> +ResourceId LockerImpl<IsForMMAPV1>::getWaitingResource() const { + scoped_spinlock scopedLock(_lock); - it.next(); + LockRequestsMap::ConstIterator it = _requests.begin(); + while (!it.finished()) { + if (it->status != LockRequest::STATUS_GRANTED) { + return it.key(); } - return ResourceId(); + it.next(); } - template<bool IsForMMAPV1> - void LockerImpl<IsForMMAPV1>::getLockerInfo(LockerInfo* lockerInfo) const { - invariant(lockerInfo); + return ResourceId(); +} - // Zero-out the contents - lockerInfo->locks.clear(); - lockerInfo->waitingResource = ResourceId(); - lockerInfo->stats.reset(); +template <bool IsForMMAPV1> +void LockerImpl<IsForMMAPV1>::getLockerInfo(LockerInfo* lockerInfo) const { + invariant(lockerInfo); - _lock.lock(); - LockRequestsMap::ConstIterator it = _requests.begin(); - while (!it.finished()) { - OneLock info; - info.resourceId = it.key(); - info.mode = it->mode; + // Zero-out the contents + lockerInfo->locks.clear(); + lockerInfo->waitingResource = ResourceId(); + lockerInfo->stats.reset(); - lockerInfo->locks.push_back(info); - it.next(); - } - _lock.unlock(); - - std::sort(lockerInfo->locks.begin(), lockerInfo->locks.end()); + _lock.lock(); + LockRequestsMap::ConstIterator it = _requests.begin(); + while (!it.finished()) { + OneLock info; + info.resourceId = it.key(); + info.mode = it->mode; - lockerInfo->waitingResource = getWaitingResource(); - lockerInfo->stats.append(_stats); + lockerInfo->locks.push_back(info); + it.next(); } + _lock.unlock(); - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::saveLockStateAndUnlock(Locker::LockSnapshot* stateOut) { - // We shouldn't be saving and restoring lock state from inside a WriteUnitOfWork. - invariant(!inAWriteUnitOfWork()); - - // Clear out whatever is in stateOut. - stateOut->locks.clear(); - stateOut->globalMode = MODE_NONE; - - // First, we look at the global lock. There is special handling for this (as the flush - // lock goes along with it) so we store it separately from the more pedestrian locks. - LockRequestsMap::Iterator globalRequest = _requests.find(resourceIdGlobal); - if (!globalRequest) { - // If there's no global lock there isn't really anything to do. - invariant(_requests.empty()); - return false; - } - - // If the global lock has been acquired more than once, we're probably somewhere in a - // DBDirectClient call. It's not safe to release and reacquire locks -- the context using - // the DBDirectClient is probably not prepared for lock release. - if (globalRequest->recursiveCount > 1) { - return false; - } + std::sort(lockerInfo->locks.begin(), lockerInfo->locks.end()); - // The global lock must have been acquired just once - stateOut->globalMode = globalRequest->mode; - invariant(unlock(resourceIdGlobal)); + lockerInfo->waitingResource = getWaitingResource(); + lockerInfo->stats.append(_stats); +} - // Next, the non-global locks. - for (LockRequestsMap::Iterator it = _requests.begin(); !it.finished(); it.next()) { - const ResourceId resId = it.key(); +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::saveLockStateAndUnlock(Locker::LockSnapshot* stateOut) { + // We shouldn't be saving and restoring lock state from inside a WriteUnitOfWork. + invariant(!inAWriteUnitOfWork()); - // We should never have to save and restore metadata locks. - invariant((IsForMMAPV1 && (resourceIdMMAPV1Flush == resId)) || - RESOURCE_DATABASE == resId.getType() || - RESOURCE_COLLECTION == resId.getType() || - (RESOURCE_GLOBAL == resId.getType() && isSharedLockMode(it->mode))); + // Clear out whatever is in stateOut. + stateOut->locks.clear(); + stateOut->globalMode = MODE_NONE; - // And, stuff the info into the out parameter. - OneLock info; - info.resourceId = resId; - info.mode = it->mode; + // First, we look at the global lock. There is special handling for this (as the flush + // lock goes along with it) so we store it separately from the more pedestrian locks. + LockRequestsMap::Iterator globalRequest = _requests.find(resourceIdGlobal); + if (!globalRequest) { + // If there's no global lock there isn't really anything to do. + invariant(_requests.empty()); + return false; + } - stateOut->locks.push_back(info); + // If the global lock has been acquired more than once, we're probably somewhere in a + // DBDirectClient call. It's not safe to release and reacquire locks -- the context using + // the DBDirectClient is probably not prepared for lock release. + if (globalRequest->recursiveCount > 1) { + return false; + } - invariant(unlock(resId)); - } + // The global lock must have been acquired just once + stateOut->globalMode = globalRequest->mode; + invariant(unlock(resourceIdGlobal)); - // Sort locks by ResourceId. They'll later be acquired in this canonical locking order. - std::sort(stateOut->locks.begin(), stateOut->locks.end()); + // Next, the non-global locks. + for (LockRequestsMap::Iterator it = _requests.begin(); !it.finished(); it.next()) { + const ResourceId resId = it.key(); - return true; - } + // We should never have to save and restore metadata locks. + invariant((IsForMMAPV1 && (resourceIdMMAPV1Flush == resId)) || + RESOURCE_DATABASE == resId.getType() || RESOURCE_COLLECTION == resId.getType() || + (RESOURCE_GLOBAL == resId.getType() && isSharedLockMode(it->mode))); - template<bool IsForMMAPV1> - void LockerImpl<IsForMMAPV1>::restoreLockState(const Locker::LockSnapshot& state) { - // We shouldn't be saving and restoring lock state from inside a WriteUnitOfWork. - invariant(!inAWriteUnitOfWork()); + // And, stuff the info into the out parameter. + OneLock info; + info.resourceId = resId; + info.mode = it->mode; - std::vector<OneLock>::const_iterator it = state.locks.begin(); - // If we locked the PBWM, it must be locked before the resourceIdGlobal resource. - if (it != state.locks.end() && it->resourceId == resourceIdParallelBatchWriterMode) { - invariant(LOCK_OK == lock(it->resourceId, it->mode)); - it++; - } + stateOut->locks.push_back(info); - invariant(LOCK_OK == lockGlobal(state.globalMode)); - for (; it != state.locks.end(); it++) { - // This is a sanity check that lockGlobal restored the MMAP V1 flush lock in the - // expected mode. - if (IsForMMAPV1 && (it->resourceId == resourceIdMMAPV1Flush)) { - invariant(it->mode == _getModeForMMAPV1FlushLock()); - } - else { - invariant(LOCK_OK == lock(it->resourceId, it->mode)); - } - } + invariant(unlock(resId)); } - template<bool IsForMMAPV1> - LockResult LockerImpl<IsForMMAPV1>::lockBegin(ResourceId resId, LockMode mode) { - dassert(!getWaitingResource().isValid()); + // Sort locks by ResourceId. They'll later be acquired in this canonical locking order. + std::sort(stateOut->locks.begin(), stateOut->locks.end()); - LockRequest* request; - bool isNew = true; + return true; +} - LockRequestsMap::Iterator it = _requests.find(resId); - if (!it) { - scoped_spinlock scopedLock(_lock); - LockRequestsMap::Iterator itNew = _requests.insert(resId); - itNew->initNew(this, &_notify); +template <bool IsForMMAPV1> +void LockerImpl<IsForMMAPV1>::restoreLockState(const Locker::LockSnapshot& state) { + // We shouldn't be saving and restoring lock state from inside a WriteUnitOfWork. + invariant(!inAWriteUnitOfWork()); - request = itNew.objAddr(); - } - else { - request = it.objAddr(); - isNew = false; - } + std::vector<OneLock>::const_iterator it = state.locks.begin(); + // If we locked the PBWM, it must be locked before the resourceIdGlobal resource. + if (it != state.locks.end() && it->resourceId == resourceIdParallelBatchWriterMode) { + invariant(LOCK_OK == lock(it->resourceId, it->mode)); + it++; + } - // Making this call here will record lock re-acquisitions and conversions as well. - globalStats.recordAcquisition(_id, resId, mode); - _stats.recordAcquisition(resId, mode); - - // Give priority to the full modes for global, parallel batch writer mode, - // and flush lock so we don't stall global operations such as shutdown or flush. - const ResourceType resType = resId.getType(); - if (resType == RESOURCE_GLOBAL || (IsForMMAPV1 && resId == resourceIdMMAPV1Flush)) { - if (mode == MODE_S || mode == MODE_X) { - request->enqueueAtFront = true; - request->compatibleFirst = true; - } - } - else { - // This is all sanity checks that the global and flush locks are always be acquired - // before any other lock has been acquired and they must be in sync with the nesting. - DEV { - const LockRequestsMap::Iterator itGlobal = _requests.find(resourceIdGlobal); - invariant(itGlobal->recursiveCount > 0); - invariant(itGlobal->mode != MODE_NONE); - - // Check the MMAP V1 flush lock is held in the appropriate mode - invariant(!IsForMMAPV1 || isLockHeldForMode(resourceIdMMAPV1Flush, - _getModeForMMAPV1FlushLock())); - }; + invariant(LOCK_OK == lockGlobal(state.globalMode)); + for (; it != state.locks.end(); it++) { + // This is a sanity check that lockGlobal restored the MMAP V1 flush lock in the + // expected mode. + if (IsForMMAPV1 && (it->resourceId == resourceIdMMAPV1Flush)) { + invariant(it->mode == _getModeForMMAPV1FlushLock()); + } else { + invariant(LOCK_OK == lock(it->resourceId, it->mode)); } + } +} - // The notification object must be cleared before we invoke the lock manager, because - // otherwise we might reset state if the lock becomes granted very fast. - _notify.clear(); - - LockResult result = isNew ? globalLockManager.lock(resId, request, mode) : - globalLockManager.convert(resId, request, mode); +template <bool IsForMMAPV1> +LockResult LockerImpl<IsForMMAPV1>::lockBegin(ResourceId resId, LockMode mode) { + dassert(!getWaitingResource().isValid()); - if (result == LOCK_WAITING) { - // Start counting the wait time so that lockComplete can update that metric - _requestStartTime = curTimeMicros64(); - globalStats.recordWait(_id, resId, mode); - _stats.recordWait(resId, mode); - } + LockRequest* request; + bool isNew = true; - return result; + LockRequestsMap::Iterator it = _requests.find(resId); + if (!it) { + scoped_spinlock scopedLock(_lock); + LockRequestsMap::Iterator itNew = _requests.insert(resId); + itNew->initNew(this, &_notify); + + request = itNew.objAddr(); + } else { + request = it.objAddr(); + isNew = false; + } + + // Making this call here will record lock re-acquisitions and conversions as well. + globalStats.recordAcquisition(_id, resId, mode); + _stats.recordAcquisition(resId, mode); + + // Give priority to the full modes for global, parallel batch writer mode, + // and flush lock so we don't stall global operations such as shutdown or flush. + const ResourceType resType = resId.getType(); + if (resType == RESOURCE_GLOBAL || (IsForMMAPV1 && resId == resourceIdMMAPV1Flush)) { + if (mode == MODE_S || mode == MODE_X) { + request->enqueueAtFront = true; + request->compatibleFirst = true; + } + } else { + // This is all sanity checks that the global and flush locks are always be acquired + // before any other lock has been acquired and they must be in sync with the nesting. + DEV { + const LockRequestsMap::Iterator itGlobal = _requests.find(resourceIdGlobal); + invariant(itGlobal->recursiveCount > 0); + invariant(itGlobal->mode != MODE_NONE); + + // Check the MMAP V1 flush lock is held in the appropriate mode + invariant(!IsForMMAPV1 || + isLockHeldForMode(resourceIdMMAPV1Flush, _getModeForMMAPV1FlushLock())); + }; } - template<bool IsForMMAPV1> - LockResult LockerImpl<IsForMMAPV1>::lockComplete(ResourceId resId, - LockMode mode, - unsigned timeoutMs, - bool checkDeadlock) { + // The notification object must be cleared before we invoke the lock manager, because + // otherwise we might reset state if the lock becomes granted very fast. + _notify.clear(); - // Under MMAP V1 engine a deadlock can occur if a thread goes to sleep waiting on - // DB lock, while holding the flush lock, so it has to be released. This is only - // correct to do if not in a write unit of work. - const bool yieldFlushLock = IsForMMAPV1 && - !inAWriteUnitOfWork() && - resId.getType() != RESOURCE_GLOBAL && - resId != resourceIdMMAPV1Flush; - if (yieldFlushLock) { - invariant(unlock(resourceIdMMAPV1Flush)); - } + LockResult result = isNew ? globalLockManager.lock(resId, request, mode) + : globalLockManager.convert(resId, request, mode); - LockResult result; + if (result == LOCK_WAITING) { + // Start counting the wait time so that lockComplete can update that metric + _requestStartTime = curTimeMicros64(); + globalStats.recordWait(_id, resId, mode); + _stats.recordWait(resId, mode); + } - // Don't go sleeping without bound in order to be able to report long waits or wake up for - // deadlock detection. - unsigned waitTimeMs = std::min(timeoutMs, DeadlockTimeoutMs); - while (true) { - // It is OK if this call wakes up spuriously, because we re-evaluate the remaining - // wait time anyways. - result = _notify.wait(waitTimeMs); + return result; +} - // Account for the time spent waiting on the notification object - const uint64_t elapsedTimeMicros = curTimeMicros64() - _requestStartTime; - globalStats.recordWaitTime(_id, resId, mode, elapsedTimeMicros); - _stats.recordWaitTime(resId, mode, elapsedTimeMicros); +template <bool IsForMMAPV1> +LockResult LockerImpl<IsForMMAPV1>::lockComplete(ResourceId resId, + LockMode mode, + unsigned timeoutMs, + bool checkDeadlock) { + // Under MMAP V1 engine a deadlock can occur if a thread goes to sleep waiting on + // DB lock, while holding the flush lock, so it has to be released. This is only + // correct to do if not in a write unit of work. + const bool yieldFlushLock = IsForMMAPV1 && !inAWriteUnitOfWork() && + resId.getType() != RESOURCE_GLOBAL && resId != resourceIdMMAPV1Flush; + if (yieldFlushLock) { + invariant(unlock(resourceIdMMAPV1Flush)); + } - if (result == LOCK_OK) break; + LockResult result; - if (checkDeadlock) { - DeadlockDetector wfg(globalLockManager, this); - if (wfg.check().hasCycle()) { - warning() << "Deadlock found: " << wfg.toString(); + // Don't go sleeping without bound in order to be able to report long waits or wake up for + // deadlock detection. + unsigned waitTimeMs = std::min(timeoutMs, DeadlockTimeoutMs); + while (true) { + // It is OK if this call wakes up spuriously, because we re-evaluate the remaining + // wait time anyways. + result = _notify.wait(waitTimeMs); - globalStats.recordDeadlock(resId, mode); - _stats.recordDeadlock(resId, mode); + // Account for the time spent waiting on the notification object + const uint64_t elapsedTimeMicros = curTimeMicros64() - _requestStartTime; + globalStats.recordWaitTime(_id, resId, mode, elapsedTimeMicros); + _stats.recordWaitTime(resId, mode, elapsedTimeMicros); - result = LOCK_DEADLOCK; - break; - } - } + if (result == LOCK_OK) + break; - // If infinite timeout was requested, just keep waiting - if (timeoutMs == UINT_MAX) { - continue; - } + if (checkDeadlock) { + DeadlockDetector wfg(globalLockManager, this); + if (wfg.check().hasCycle()) { + warning() << "Deadlock found: " << wfg.toString(); - const unsigned elapsedTimeMs = elapsedTimeMicros / 1000; - waitTimeMs = (elapsedTimeMs < timeoutMs) ? - std::min(timeoutMs - elapsedTimeMs, DeadlockTimeoutMs) : 0; + globalStats.recordDeadlock(resId, mode); + _stats.recordDeadlock(resId, mode); - if (waitTimeMs == 0) { + result = LOCK_DEADLOCK; break; } } - // Cleanup the state, since this is an unused lock now - if (result != LOCK_OK) { - LockRequestsMap::Iterator it = _requests.find(resId); - if (globalLockManager.unlock(it.objAddr())) { - scoped_spinlock scopedLock(_lock); - it.remove(); - } - } - - if (yieldFlushLock) { - // We cannot obey the timeout here, because it is not correct to return from the lock - // request with the flush lock released. - invariant(LOCK_OK == lock(resourceIdMMAPV1Flush, _getModeForMMAPV1FlushLock())); + // If infinite timeout was requested, just keep waiting + if (timeoutMs == UINT_MAX) { + continue; } - return result; - } + const unsigned elapsedTimeMs = elapsedTimeMicros / 1000; + waitTimeMs = (elapsedTimeMs < timeoutMs) + ? std::min(timeoutMs - elapsedTimeMs, DeadlockTimeoutMs) + : 0; - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::_unlockImpl(LockRequestsMap::Iterator& it) { - if (inAWriteUnitOfWork() && shouldDelayUnlock(it.key(), it->mode)) { - _resourcesToUnlockAtEndOfUnitOfWork.push(it.key()); - return false; + if (waitTimeMs == 0) { + break; } + } + // Cleanup the state, since this is an unused lock now + if (result != LOCK_OK) { + LockRequestsMap::Iterator it = _requests.find(resId); if (globalLockManager.unlock(it.objAddr())) { scoped_spinlock scopedLock(_lock); it.remove(); - - return true; } + } + if (yieldFlushLock) { + // We cannot obey the timeout here, because it is not correct to return from the lock + // request with the flush lock released. + invariant(LOCK_OK == lock(resourceIdMMAPV1Flush, _getModeForMMAPV1FlushLock())); + } + + return result; +} + +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::_unlockImpl(LockRequestsMap::Iterator& it) { + if (inAWriteUnitOfWork() && shouldDelayUnlock(it.key(), it->mode)) { + _resourcesToUnlockAtEndOfUnitOfWork.push(it.key()); return false; } - template<bool IsForMMAPV1> - LockMode LockerImpl<IsForMMAPV1>::_getModeForMMAPV1FlushLock() const { - invariant(IsForMMAPV1); + if (globalLockManager.unlock(it.objAddr())) { + scoped_spinlock scopedLock(_lock); + it.remove(); + + return true; + } - LockMode mode = getLockMode(resourceIdGlobal); - switch (mode) { + return false; +} + +template <bool IsForMMAPV1> +LockMode LockerImpl<IsForMMAPV1>::_getModeForMMAPV1FlushLock() const { + invariant(IsForMMAPV1); + + LockMode mode = getLockMode(resourceIdGlobal); + switch (mode) { case MODE_X: case MODE_IX: return MODE_IX; @@ -771,153 +767,150 @@ namespace { default: invariant(false); return MODE_NONE; - } } +} - template<bool IsForMMAPV1> - bool LockerImpl<IsForMMAPV1>::hasStrongLocks() const { - if (!isLocked()) return false; - - stdx::lock_guard<SpinLock> lk(_lock); - LockRequestsMap::ConstIterator it = _requests.begin(); - while (!it.finished()) { - if (it->mode == MODE_X || it->mode == MODE_S) { - return true; - } +template <bool IsForMMAPV1> +bool LockerImpl<IsForMMAPV1>::hasStrongLocks() const { + if (!isLocked()) + return false; - it.next(); + stdx::lock_guard<SpinLock> lk(_lock); + LockRequestsMap::ConstIterator it = _requests.begin(); + while (!it.finished()) { + if (it->mode == MODE_X || it->mode == MODE_S) { + return true; } - return false; + it.next(); } + return false; +} - // - // Auto classes - // - AutoYieldFlushLockForMMAPV1Commit::AutoYieldFlushLockForMMAPV1Commit(Locker* locker) - : _locker(static_cast<MMAPV1LockerImpl*>(locker)) { +// +// Auto classes +// - // Explicit yielding of the flush lock should happen only at global synchronization points - // such as database drop. There should not be any active writes at these points. - invariant(!_locker->inAWriteUnitOfWork()); +AutoYieldFlushLockForMMAPV1Commit::AutoYieldFlushLockForMMAPV1Commit(Locker* locker) + : _locker(static_cast<MMAPV1LockerImpl*>(locker)) { + // Explicit yielding of the flush lock should happen only at global synchronization points + // such as database drop. There should not be any active writes at these points. + invariant(!_locker->inAWriteUnitOfWork()); - if (isMMAPV1()) { - invariant(_locker->unlock(resourceIdMMAPV1Flush)); - } + if (isMMAPV1()) { + invariant(_locker->unlock(resourceIdMMAPV1Flush)); } +} - AutoYieldFlushLockForMMAPV1Commit::~AutoYieldFlushLockForMMAPV1Commit() { - if (isMMAPV1()) { - invariant(LOCK_OK == _locker->lock(resourceIdMMAPV1Flush, - _locker->_getModeForMMAPV1FlushLock())); - } +AutoYieldFlushLockForMMAPV1Commit::~AutoYieldFlushLockForMMAPV1Commit() { + if (isMMAPV1()) { + invariant(LOCK_OK == + _locker->lock(resourceIdMMAPV1Flush, _locker->_getModeForMMAPV1FlushLock())); } +} - AutoAcquireFlushLockForMMAPV1Commit::AutoAcquireFlushLockForMMAPV1Commit(Locker* locker) - : _locker(locker), - _released(false) { - - // The journal thread acquiring the journal lock in S-mode opens opportunity for deadlock - // involving operations which do not acquire and release the Oplog collection's X lock - // inside a WUOW (see SERVER-17416 for the sequence of events), therefore acquire it with - // check for deadlock and back-off if one is encountered. - // - // This exposes theoretical chance that we might starve the journaling system, but given - // that these deadlocks happen extremely rarely and are usually due to incorrect locking - // policy, and we have the deadlock counters as part of the locking statistics, this is a - // reasonable handling. - // - // In the worst case, if we are to starve the journaling system, the server will shut down - // due to too much uncommitted in-memory journal, but won't have corruption. +AutoAcquireFlushLockForMMAPV1Commit::AutoAcquireFlushLockForMMAPV1Commit(Locker* locker) + : _locker(locker), _released(false) { + // The journal thread acquiring the journal lock in S-mode opens opportunity for deadlock + // involving operations which do not acquire and release the Oplog collection's X lock + // inside a WUOW (see SERVER-17416 for the sequence of events), therefore acquire it with + // check for deadlock and back-off if one is encountered. + // + // This exposes theoretical chance that we might starve the journaling system, but given + // that these deadlocks happen extremely rarely and are usually due to incorrect locking + // policy, and we have the deadlock counters as part of the locking statistics, this is a + // reasonable handling. + // + // In the worst case, if we are to starve the journaling system, the server will shut down + // due to too much uncommitted in-memory journal, but won't have corruption. - while (true) { - LockResult result = _locker->lock(resourceIdMMAPV1Flush, MODE_S, UINT_MAX, true); - if (result == LOCK_OK) { - break; - } + while (true) { + LockResult result = _locker->lock(resourceIdMMAPV1Flush, MODE_S, UINT_MAX, true); + if (result == LOCK_OK) { + break; + } - invariant(result == LOCK_DEADLOCK); + invariant(result == LOCK_DEADLOCK); - warning() << "Delayed journaling in order to avoid deadlock during MMAP V1 journal " << - "lock acquisition. See the previous messages for information on the " << - "involved threads."; - } + warning() << "Delayed journaling in order to avoid deadlock during MMAP V1 journal " + << "lock acquisition. See the previous messages for information on the " + << "involved threads."; } +} - void AutoAcquireFlushLockForMMAPV1Commit::upgradeFlushLockToExclusive() { - // This should not be able to deadlock, since we already hold the S journal lock, which - // means all writers are kicked out. Readers always yield the journal lock if they block - // waiting on any other lock. - invariant(LOCK_OK == _locker->lock(resourceIdMMAPV1Flush, MODE_X, UINT_MAX, false)); +void AutoAcquireFlushLockForMMAPV1Commit::upgradeFlushLockToExclusive() { + // This should not be able to deadlock, since we already hold the S journal lock, which + // means all writers are kicked out. Readers always yield the journal lock if they block + // waiting on any other lock. + invariant(LOCK_OK == _locker->lock(resourceIdMMAPV1Flush, MODE_X, UINT_MAX, false)); - // Lock bumps the recursive count. Drop it back down so that the destructor doesn't - // complain. - invariant(!_locker->unlock(resourceIdMMAPV1Flush)); - } + // Lock bumps the recursive count. Drop it back down so that the destructor doesn't + // complain. + invariant(!_locker->unlock(resourceIdMMAPV1Flush)); +} - void AutoAcquireFlushLockForMMAPV1Commit::release() { - if (!_released) { - invariant(_locker->unlock(resourceIdMMAPV1Flush)); - _released = true; - } +void AutoAcquireFlushLockForMMAPV1Commit::release() { + if (!_released) { + invariant(_locker->unlock(resourceIdMMAPV1Flush)); + _released = true; } +} - AutoAcquireFlushLockForMMAPV1Commit::~AutoAcquireFlushLockForMMAPV1Commit() { - release(); - } +AutoAcquireFlushLockForMMAPV1Commit::~AutoAcquireFlushLockForMMAPV1Commit() { + release(); +} namespace { - /** - * Periodically purges unused lock buckets. The first time the lock is used again after - * cleanup it needs to be allocated, and similarly, every first use by a client for an intent - * mode may need to create a partitioned lock head. Cleanup is done roughtly once a minute. - */ - class UnusedLockCleaner : PeriodicTask { - public: - std::string taskName() const { - return "UnusedLockCleaner"; - } +/** + * Periodically purges unused lock buckets. The first time the lock is used again after + * cleanup it needs to be allocated, and similarly, every first use by a client for an intent + * mode may need to create a partitioned lock head. Cleanup is done roughtly once a minute. + */ +class UnusedLockCleaner : PeriodicTask { +public: + std::string taskName() const { + return "UnusedLockCleaner"; + } - void taskDoWork() { - LOG(2) << "cleaning up unused lock buckets of the global lock manager"; - getGlobalLockManager()->cleanupUnusedLocks(); - } - } unusedLockCleaner; -} // namespace + void taskDoWork() { + LOG(2) << "cleaning up unused lock buckets of the global lock manager"; + getGlobalLockManager()->cleanupUnusedLocks(); + } +} unusedLockCleaner; +} // namespace - // - // Standalone functions - // +// +// Standalone functions +// - LockManager* getGlobalLockManager() { - return &globalLockManager; - } +LockManager* getGlobalLockManager() { + return &globalLockManager; +} - void reportGlobalLockingStats(SingleThreadedLockStats* outStats) { - globalStats.report(outStats); - } +void reportGlobalLockingStats(SingleThreadedLockStats* outStats) { + globalStats.report(outStats); +} + +void resetGlobalLockStats() { + globalStats.reset(); +} - void resetGlobalLockStats() { - globalStats.reset(); - } - - // Ensures that there are two instances compiled for LockerImpl for the two values of the - // template argument. - template class LockerImpl<true>; - template class LockerImpl<false>; +// Ensures that there are two instances compiled for LockerImpl for the two values of the +// template argument. +template class LockerImpl<true>; +template class LockerImpl<false>; - // Definition for the hardcoded localdb and oplog collection info - const ResourceId resourceIdLocalDB = ResourceId(RESOURCE_DATABASE, StringData("local")); - const ResourceId resourceIdOplog = - ResourceId(RESOURCE_COLLECTION, StringData("local.oplog.rs")); - const ResourceId resourceIdAdminDB = ResourceId(RESOURCE_DATABASE, StringData("admin")); - const ResourceId resourceIdParallelBatchWriterMode = - ResourceId(RESOURCE_GLOBAL, ResourceId::SINGLETON_PARALLEL_BATCH_WRITER_MODE); +// Definition for the hardcoded localdb and oplog collection info +const ResourceId resourceIdLocalDB = ResourceId(RESOURCE_DATABASE, StringData("local")); +const ResourceId resourceIdOplog = ResourceId(RESOURCE_COLLECTION, StringData("local.oplog.rs")); +const ResourceId resourceIdAdminDB = ResourceId(RESOURCE_DATABASE, StringData("admin")); +const ResourceId resourceIdParallelBatchWriterMode = + ResourceId(RESOURCE_GLOBAL, ResourceId::SINGLETON_PARALLEL_BATCH_WRITER_MODE); -} // namespace mongo +} // namespace mongo |