0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-12-01 09:32:32 +01:00

SERVER-15262/SERVER-15711 Flush thread should have precedence and discover deadlocks

This commit is contained in:
Kaloian Manassiev 2014-10-27 16:35:27 -04:00
parent ed0947b4ca
commit e192dc1257
6 changed files with 109 additions and 96 deletions

View File

@ -90,27 +90,46 @@ namespace mongo {
}
}
TEST(DConcurrency, readlocktryTimeoutDueToFlushLock) {
TEST(DConcurrency, readlocktryNoTimeoutDueToFlushLockS) {
MMAPV1LockerImpl ls(1);
AutoAcquireFlushLockForMMAPV1Commit autoFlushLock(&ls);
{
MMAPV1LockerImpl lsTry(2);
readlocktry lockTry(&lsTry, 1);
MMAPV1LockerImpl lsTry(2);
readlocktry lockTry(&lsTry, 1);
ASSERT(!lockTry.got());
}
ASSERT(lockTry.got());
}
TEST(DConcurrency, writelocktryTimeoutDueToFlushLock) {
TEST(DConcurrency, writelocktryTimeoutDueToFlushLockS) {
MMAPV1LockerImpl ls(1);
AutoAcquireFlushLockForMMAPV1Commit autoFlushLock(&ls);
{
MMAPV1LockerImpl lsTry(2);
writelocktry lockTry(&lsTry, 1);
ASSERT(!lockTry.got());
}
MMAPV1LockerImpl lsTry(2);
writelocktry lockTry(&lsTry, 1);
ASSERT(!lockTry.got());
}
TEST(DConcurrency, readlocktryTimeoutDueToFlushLockX) {
MMAPV1LockerImpl ls(1);
AutoAcquireFlushLockForMMAPV1Commit autoFlushLock(&ls);
autoFlushLock.upgradeFlushLockToExclusive();
MMAPV1LockerImpl lsTry(2);
readlocktry lockTry(&lsTry, 1);
ASSERT(!lockTry.got());
}
TEST(DConcurrency, writelocktryTimeoutDueToFlushLockX) {
MMAPV1LockerImpl ls(1);
AutoAcquireFlushLockForMMAPV1Commit autoFlushLock(&ls);
autoFlushLock.upgradeFlushLockToExclusive();
MMAPV1LockerImpl lsTry(2);
writelocktry lockTry(&lsTry, 1);
ASSERT(!lockTry.got());
}
TEST(DConcurrency, TempReleaseGlobalWrite) {

View File

@ -377,7 +377,8 @@ namespace mongo {
invariant(_requests.empty());
}
else {
// No upgrades on the GlobalLock are allowed until we can handle deadlocks
// No upgrades on the GlobalLock are currently necessary. Should not be used until we
// are handling deadlocks on anything other than the flush thread.
invariant(it->mode >= mode);
}
@ -449,21 +450,32 @@ namespace mongo {
template<bool IsForMMAPV1>
void LockerImpl<IsForMMAPV1>::endWriteUnitOfWork() {
_wuowNestingLevel--;
if (_wuowNestingLevel > 0) {
invariant(_wuowNestingLevel > 0);
if (--_wuowNestingLevel > 0) {
// Don't do anything unless leaving outermost WUOW.
return;
}
invariant(_wuowNestingLevel == 0);
while (!_resourcesToUnlockAtEndOfUnitOfWork.empty()) {
unlock(_resourcesToUnlockAtEndOfUnitOfWork.front());
_resourcesToUnlockAtEndOfUnitOfWork.pop();
}
// For MMAP V1, we need to yield the flush lock so that the flush thread can run
if (IsForMMAPV1) {
_yieldFlushLockForMMAPV1();
invariant(unlock(resourceIdMMAPV1Flush));
while (true) {
LockResult result =
lock(resourceIdMMAPV1Flush, _getModeForMMAPV1FlushLock(), UINT_MAX, true);
if (result == LOCK_OK) break;
invariant(result == LOCK_DEADLOCK);
invariant(unlock(resourceIdMMAPV1Flush));
}
}
}
@ -524,7 +536,7 @@ namespace mongo {
// This will occasionally dump the global lock manager in case lock acquisition is
// taking too long.
if (elapsedTimeMs > 1000U) {
if (elapsedTimeMs > 5000U) {
dumpGlobalLockManagerThrottled();
}
}
@ -547,6 +559,12 @@ namespace mongo {
return result;
}
template<bool IsForMMAPV1>
void LockerImpl<IsForMMAPV1>::downgrade(const ResourceId& resId, LockMode newMode) {
LockRequestsMap::Iterator it = _requests.find(resId);
globalLockManager.downgrade(it.objAddr(), newMode);
}
template<bool IsForMMAPV1>
bool LockerImpl<IsForMMAPV1>::unlock(const ResourceId& resId) {
LockRequestsMap::Iterator it = _requests.find(resId);
@ -706,16 +724,6 @@ namespace mongo {
return false;
}
template<bool IsForMMAPV1>
void LockerImpl<IsForMMAPV1>::_yieldFlushLockForMMAPV1() {
invariant(IsForMMAPV1);
if (!inAWriteUnitOfWork()) {
invariant(unlock(resourceIdMMAPV1Flush));
invariant(LOCK_OK ==
lock(resourceIdMMAPV1Flush, _getModeForMMAPV1FlushLock(), UINT_MAX));
}
}
template<bool IsForMMAPV1>
LockMode LockerImpl<IsForMMAPV1>::_getModeForMMAPV1FlushLock() const {
invariant(IsForMMAPV1);
@ -760,13 +768,29 @@ namespace mongo {
AutoAcquireFlushLockForMMAPV1Commit::AutoAcquireFlushLockForMMAPV1Commit(Locker* locker)
: _locker(static_cast<MMAPV1LockerImpl*>(locker)) {
: _locker(static_cast<MMAPV1LockerImpl*>(locker)),
_isReleased(false) {
invariant(LOCK_OK == _locker->lock(resourceIdMMAPV1Flush, MODE_X, UINT_MAX));
invariant(LOCK_OK == _locker->lock(resourceIdMMAPV1Flush, MODE_S));
}
void AutoAcquireFlushLockForMMAPV1Commit::upgradeFlushLockToExclusive() {
invariant(LOCK_OK == _locker->lock(resourceIdMMAPV1Flush, MODE_X));
// Lock bumps the recursive count. Drop it back down so that the destructor doesn't
// complain
invariant(!_locker->unlock(resourceIdMMAPV1Flush));
}
void AutoAcquireFlushLockForMMAPV1Commit::release() {
invariant(_locker->unlock(resourceIdMMAPV1Flush));
_isReleased = true;
}
AutoAcquireFlushLockForMMAPV1Commit::~AutoAcquireFlushLockForMMAPV1Commit() {
invariant(_locker->unlock(resourceIdMMAPV1Flush));
if (!_isReleased) {
invariant(_locker->unlock(resourceIdMMAPV1Flush));
}
}

View File

@ -109,6 +109,8 @@ namespace mongo {
unsigned timeoutMs = UINT_MAX,
bool checkDeadlock = false);
virtual void downgrade(const ResourceId& resId, LockMode newMode);
virtual bool unlock(const ResourceId& resId);
virtual LockMode getLockMode(const ResourceId& resId) const;
@ -142,13 +144,6 @@ namespace mongo {
*/
bool _unlockImpl(LockRequestsMap::Iterator& it);
/**
* Temporarily yields the flush lock, if not in a write unit of work so that the commit
* thread can take turn. This is called automatically at each lock acquisition point, but
* can also be called more frequently than that if need be.
*/
void _yieldFlushLockForMMAPV1();
/**
* MMAP V1 locking code yields and re-acquires the flush lock occasionally in order to
* allow the flush thread proceed. This call returns in what mode the flush lock should be
@ -268,10 +263,9 @@ namespace mongo {
/**
* The resourceIdMMAPV1Flush lock is used to implement the MMAP V1 storage engine durability
* system synchronization. This is how it works :
* This explains how the MMAP V1 durability system is implemented.
*
* Every server operation (OperationContext), which calls lockGlobal as the first locking
* Every server operation (OperationContext), must call Locker::lockGlobal as the first lock
* action (it is illegal to acquire any other locks without calling this first). This action
* acquires the global and flush locks in the appropriate modes (IS for read operations, IX
* for write operations). Having the flush lock in one of these modes indicates to the flush
@ -279,24 +273,37 @@ namespace mongo {
*
* Whenever the flush thread(dur.cpp) activates, it goes through the following steps :
*
* - Acquire the flush lock in S - mode by creating a stack instance of
* AutoAcquireFlushLockForMMAPV1Commit. This waits till all write activity on the system
* completes and does not allow new write operations to start. Readers may still proceed.
* Acquire the flush lock in S mode using AutoAcquireFlushLockForMMAPV1Commit. This waits until
* all current write activity on the system completes and does not allow any new operations to
* start.
*
* - Once the flush lock is granted in S - mode, the flush thread writes the journal entries
* to disk and applies them to the shared view. After that, it upgrades the S - lock to X
* and remaps the private view.
* Once the S lock is granted, the flush thread writes the journal entries to disk (it is
* guaranteed that there will not be any modifications) and applies them to the shared view.
*
* NOTE: There should be only one usage of this class and this should be in dur.cpp.
* After that, it upgrades the S lock to X and remaps the private view.
*
* NOTE: There should be only one usage of this class and this should be in dur.cpp
*/
class AutoAcquireFlushLockForMMAPV1Commit {
public:
AutoAcquireFlushLockForMMAPV1Commit(Locker* locker);
~AutoAcquireFlushLockForMMAPV1Commit();
/**
* We need the exclusive lock in order to do the shared view remap.
*/
void upgradeFlushLockToExclusive();
/**
* Allows the acquired flush lock to be prematurely released. This is helpful for the case
* where we know that we won't be doing a remap after gathering the write intents, so the
* rest can be done outside of flush lock.
*/
void release();
private:
MMAPV1LockerImpl* _locker;
bool _isReleased;;
};

View File

@ -124,50 +124,6 @@ namespace mongo {
locker.unlockAll();
}
TEST(LockerImpl, WriteTransactionWithCommit) {
const ResourceId resIdCollection(RESOURCE_COLLECTION, std::string("TestDB.collection"));
const ResourceId resIdRecordS(RESOURCE_DOCUMENT, 1);
const ResourceId resIdRecordX(RESOURCE_DOCUMENT, 2);
MMAPV1LockerImpl locker(1);
locker.lockGlobal(MODE_IX);
{
ASSERT(LOCK_OK == locker.lock(resIdCollection, MODE_IX, 0));
locker.beginWriteUnitOfWork();
ASSERT(LOCK_OK == locker.lock(resIdRecordS, MODE_S, 0));
ASSERT(locker.getLockMode(resIdRecordS) == MODE_S);
ASSERT(LOCK_OK == locker.lock(resIdRecordX, MODE_X, 0));
ASSERT(locker.getLockMode(resIdRecordX) == MODE_X);
ASSERT(locker.unlock(resIdRecordS));
ASSERT(locker.getLockMode(resIdRecordS) == MODE_NONE);
ASSERT(!locker.unlock(resIdRecordX));
ASSERT(locker.getLockMode(resIdRecordX) == MODE_X);
locker.endWriteUnitOfWork();
{
AutoYieldFlushLockForMMAPV1Commit flushLockYield(&locker);
// This block simulates the flush/remap thread
{
MMAPV1LockerImpl flushLocker(2);
AutoAcquireFlushLockForMMAPV1Commit flushLockAcquire(&flushLocker);
}
}
ASSERT(locker.getLockMode(resIdRecordX) == MODE_NONE);
ASSERT(locker.unlock(resIdCollection));
}
locker.unlockAll();
}
/**
* Test that saveMMAPV1LockerImpl works by examining the output.
*/

View File

@ -135,6 +135,11 @@ namespace mongo {
unsigned timeoutMs = UINT_MAX,
bool checkDeadlock = false) = 0;
/**
* Downgrades the specified resource's lock mode without changing the reference count.
*/
virtual void downgrade(const ResourceId& resId, LockMode newMode) = 0;
/**
* Releases a lock previously acquired through a lock call. It is an error to try to
* release lock which has not been previously acquired (invariant violation).

View File

@ -651,11 +651,13 @@ namespace mongo {
OperationContextImpl txn;
// Waits for all active operations to drain and won't let new ones start. This
// should be optimized to allow readers in (see SERVER-15262).
// Waits for all active writers to drain and won't let new ones start, but
// lets the readers go on.
AutoAcquireFlushLockForMMAPV1Commit flushLock(txn.lockState());
groupCommit();
// Causes everybody to stall so that the in-memory view can be remapped.
flushLock.upgradeFlushLockToExclusive();
remapPrivateView();
}
catch(std::exception& e) {