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:
parent
ed0947b4ca
commit
e192dc1257
@ -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) {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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).
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user