diff options
author | Judah Schvimer <judah@mongodb.com> | 2018-11-18 15:50:40 -0500 |
---|---|---|
committer | Judah Schvimer <judah@mongodb.com> | 2018-11-18 15:50:40 -0500 |
commit | 2b71741cfee235cdbfb69c101bb6ac06a5da436c (patch) | |
tree | ab42093c4256016351b0d52e1db939f386ed70c9 /src/mongo | |
parent | d167e6129256d6b60f01a19ed14bde5604255df7 (diff) | |
download | mongo-2b71741cfee235cdbfb69c101bb6ac06a5da436c.tar.gz |
SERVER-37989 introduce a new ReplicationStateTransitionLock resource
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/concurrency/d_concurrency.cpp | 31 | ||||
-rw-r--r-- | src/mongo/db/concurrency/d_concurrency.h | 2 | ||||
-rw-r--r-- | src/mongo/db/concurrency/d_concurrency_test.cpp | 150 | ||||
-rw-r--r-- | src/mongo/db/concurrency/lock_manager_defs.h | 9 | ||||
-rw-r--r-- | src/mongo/db/concurrency/lock_state.cpp | 15 | ||||
-rw-r--r-- | src/mongo/db/concurrency/lock_state_test.cpp | 38 |
6 files changed, 233 insertions, 12 deletions
diff --git a/src/mongo/db/concurrency/d_concurrency.cpp b/src/mongo/db/concurrency/d_concurrency.cpp index 52f7e64e257..9eaf5fbb014 100644 --- a/src/mongo/db/concurrency/d_concurrency.cpp +++ b/src/mongo/db/concurrency/d_concurrency.cpp @@ -1,4 +1,3 @@ - /** * Copyright (C) 2018-present MongoDB, Inc. * @@ -175,7 +174,25 @@ void Lock::GlobalLock::_enqueue(LockMode lockMode, Date_t deadline) { _pbwm.lock(MODE_IS); } - _result = _opCtx->lockState()->lockGlobalBegin(_opCtx, lockMode, deadline); + _result = _opCtx->lockState()->lock( + _opCtx, resourceIdReplicationStateTransitionLock, MODE_IX, deadline); + if (_result != LOCK_OK) { + if (_opCtx->lockState()->shouldConflictWithSecondaryBatchApplication()) { + _pbwm.unlock(); + } + return; + } + + // At this point the RSTL is locked and must be unlocked if acquiring the GlobalLock fails. + // We only want to unlock the RSTL if we were interrupted acquiring the GlobalLock and not + // if we were interrupted acquiring the RSTL itself. If we were interrupted acquiring the + // RSTL then the RSTL will not be locked and we do not want to attempt to unlock it. + try { + _result = _opCtx->lockState()->lockGlobalBegin(_opCtx, lockMode, deadline); + } catch (...) { + _opCtx->lockState()->unlock(resourceIdReplicationStateTransitionLock); + throw; + } } catch (const ExceptionForCat<ErrorCategory::Interruption>&) { // The kLeaveUnlocked behavior suppresses this exception. if (_interruptBehavior == InterruptBehavior::kThrow) @@ -189,11 +206,15 @@ void Lock::GlobalLock::waitForLockUntil(Date_t deadline) { _result = _opCtx->lockState()->lockGlobalComplete(_opCtx, deadline); } - if (_result != LOCK_OK && - _opCtx->lockState()->shouldConflictWithSecondaryBatchApplication()) { - _pbwm.unlock(); + if (_result != LOCK_OK) { + _opCtx->lockState()->unlock(resourceIdReplicationStateTransitionLock); + + if (_opCtx->lockState()->shouldConflictWithSecondaryBatchApplication()) { + _pbwm.unlock(); + } } } catch (const ExceptionForCat<ErrorCategory::Interruption>&) { + _opCtx->lockState()->unlock(resourceIdReplicationStateTransitionLock); // The kLeaveUnlocked behavior suppresses this exception. if (_interruptBehavior == InterruptBehavior::kThrow) throw; diff --git a/src/mongo/db/concurrency/d_concurrency.h b/src/mongo/db/concurrency/d_concurrency.h index 9c50ca50f25..4ba068ac445 100644 --- a/src/mongo/db/concurrency/d_concurrency.h +++ b/src/mongo/db/concurrency/d_concurrency.h @@ -1,4 +1,3 @@ - /** * Copyright (C) 2018-present MongoDB, Inc. * @@ -231,6 +230,7 @@ public: } _unlock(); } + _opCtx->lockState()->unlock(resourceIdReplicationStateTransitionLock); } /** diff --git a/src/mongo/db/concurrency/d_concurrency_test.cpp b/src/mongo/db/concurrency/d_concurrency_test.cpp index 74453094986..b3b64385c35 100644 --- a/src/mongo/db/concurrency/d_concurrency_test.cpp +++ b/src/mongo/db/concurrency/d_concurrency_test.cpp @@ -1,4 +1,3 @@ - /** * Copyright (C) 2018-present MongoDB, Inc. * @@ -255,6 +254,7 @@ TEST_F(DConcurrencyTestFixture, GlobalRead) { opCtx->swapLockState(stdx::make_unique<LockerImpl>()); Lock::GlobalRead globalRead(opCtx.get()); ASSERT(opCtx->lockState()->isR()); + ASSERT_EQ(opCtx->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); } TEST_F(DConcurrencyTestFixture, GlobalWrite) { @@ -262,6 +262,7 @@ TEST_F(DConcurrencyTestFixture, GlobalWrite) { opCtx->swapLockState(stdx::make_unique<LockerImpl>()); Lock::GlobalWrite globalWrite(opCtx.get()); ASSERT(opCtx->lockState()->isW()); + ASSERT_EQ(opCtx->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); } TEST_F(DConcurrencyTestFixture, GlobalWriteAndGlobalRead) { @@ -278,6 +279,7 @@ TEST_F(DConcurrencyTestFixture, GlobalWriteAndGlobalRead) { } ASSERT(lockState->isW()); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); } TEST_F(DConcurrencyTestFixture, @@ -292,6 +294,7 @@ TEST_F(DConcurrencyTestFixture, ASSERT(lockState->isW()); ASSERT(MODE_X == lockState->getLockMode(globalId)) << "unexpected global lock mode " << modeName(lockState->getLockMode(globalId)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); { Lock::DBLock dbWrite(opCtx.get(), "db", MODE_IX); @@ -305,11 +308,14 @@ TEST_F(DConcurrencyTestFixture, // This test case illustrates non-recommended usage of the RAII types. See SERVER-30948. globalWrite = {}; ASSERT(lockState->isW()); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); + lockState->downgrade(globalId, MODE_IX); ASSERT_FALSE(lockState->isW()); ASSERT(lockState->isWriteLocked()); ASSERT(MODE_IX == lockState->getLockMode(globalId)) << "unexpected global lock mode " << modeName(lockState->getLockMode(globalId)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); } @@ -317,6 +323,7 @@ TEST_F(DConcurrencyTestFixture, ASSERT_FALSE(lockState->isWriteLocked()); ASSERT(MODE_NONE == lockState->getLockMode(globalId)) << "unexpected global lock mode " << modeName(lockState->getLockMode(globalId)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); } TEST_F(DConcurrencyTestFixture, @@ -331,12 +338,14 @@ TEST_F(DConcurrencyTestFixture, ASSERT(lockState->isW()); ASSERT(MODE_X == lockState->getLockMode(globalId)) << "unexpected global lock mode " << modeName(lockState->getLockMode(globalId)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); { Lock::DBLock dbWrite(opCtx.get(), "db", MODE_IX); ASSERT(lockState->isW()); ASSERT(MODE_X == lockState->getLockMode(globalId)) << "unexpected global lock mode " << modeName(lockState->getLockMode(globalId)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); // Downgrade global lock resource to MODE_IX to allow other write operations to make // progress. @@ -345,16 +354,19 @@ TEST_F(DConcurrencyTestFixture, ASSERT(lockState->isWriteLocked()); ASSERT(MODE_IX == lockState->getLockMode(globalId)) << "unexpected global lock mode " << modeName(lockState->getLockMode(globalId)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); } ASSERT_FALSE(lockState->isW()); ASSERT(lockState->isWriteLocked()); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); globalWrite = {}; ASSERT_FALSE(lockState->isW()); ASSERT_FALSE(lockState->isWriteLocked()); ASSERT(MODE_NONE == lockState->getLockMode(globalId)) << "unexpected global lock mode " << modeName(lockState->getLockMode(globalId)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); } TEST_F(DConcurrencyTestFixture, @@ -367,12 +379,14 @@ TEST_F(DConcurrencyTestFixture, auto outerGlobalWrite = stdx::make_unique<Lock::GlobalWrite>(opCtx.get()); auto innerGlobalWrite = stdx::make_unique<Lock::GlobalWrite>(opCtx.get()); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); { Lock::DBLock dbWrite(opCtx.get(), "db", MODE_IX); ASSERT(lockState->isW()); ASSERT(MODE_X == lockState->getLockMode(globalId)) << "unexpected global lock mode " << modeName(lockState->getLockMode(globalId)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); // Downgrade global lock resource to MODE_IX to allow other write operations to make // progress. @@ -381,22 +395,26 @@ TEST_F(DConcurrencyTestFixture, ASSERT(lockState->isWriteLocked()); ASSERT(MODE_IX == lockState->getLockMode(globalId)) << "unexpected global lock mode " << modeName(lockState->getLockMode(globalId)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); } ASSERT_FALSE(lockState->isW()); ASSERT(lockState->isWriteLocked()); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); innerGlobalWrite = {}; ASSERT_FALSE(lockState->isW()); ASSERT(lockState->isWriteLocked()); ASSERT(MODE_IX == lockState->getLockMode(globalId)) << "unexpected global lock mode " << modeName(lockState->getLockMode(globalId)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); outerGlobalWrite = {}; ASSERT_FALSE(lockState->isW()); ASSERT_FALSE(lockState->isWriteLocked()); ASSERT(MODE_NONE == lockState->getLockMode(globalId)) << "unexpected global lock mode " << modeName(lockState->getLockMode(globalId)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); } TEST_F(DConcurrencyTestFixture, GlobalLockS_Timeout) { @@ -426,6 +444,27 @@ TEST_F(DConcurrencyTestFixture, GlobalLockX_Timeout) { ASSERT(!globalWriteTry.isLocked()); } +TEST_F(DConcurrencyTestFixture, RSTLmodeX_Timeout) { + auto clients = makeKClientsWithLockers(2); + Lock::ResourceLock rstl( + clients[0].second.get()->lockState(), resourceIdReplicationStateTransitionLock, MODE_X); + ASSERT_EQ( + clients[0].second.get()->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), + MODE_X); + + Lock::GlobalLock globalWriteTry(clients[1].second.get(), + MODE_X, + Date_t::now() + Milliseconds(1), + Lock::InterruptBehavior::kThrow); + ASSERT(!globalWriteTry.isLocked()); + ASSERT_EQ( + clients[0].second.get()->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), + MODE_X); + ASSERT_EQ( + clients[1].second.get()->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), + MODE_NONE); +} + TEST_F(DConcurrencyTestFixture, GlobalLockXSetsGlobalLockTakenOnOperationContext) { auto clients = makeKClientsWithLockers(1); auto opCtx = clients[0].second.get(); @@ -561,13 +600,16 @@ TEST_F(DConcurrencyTestFixture, TempReleaseGlobalWrite) { opCtx->swapLockState(stdx::make_unique<LockerImpl>()); auto lockState = opCtx->lockState(); Lock::GlobalWrite globalWrite(opCtx.get()); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); { Lock::TempRelease tempRelease(lockState); ASSERT(!lockState->isLocked()); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); } ASSERT(lockState->isW()); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); } TEST_F(DConcurrencyTestFixture, TempReleaseRecursive) { @@ -576,14 +618,17 @@ TEST_F(DConcurrencyTestFixture, TempReleaseRecursive) { auto lockState = opCtx->lockState(); Lock::GlobalWrite globalWrite(opCtx.get()); Lock::DBLock lk(opCtx.get(), "SomeDBName", MODE_X); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); { Lock::TempRelease tempRelease(lockState); ASSERT(lockState->isW()); ASSERT(lockState->isDbLockedForMode("SomeDBName", MODE_X)); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); } ASSERT(lockState->isW()); + ASSERT_EQ(lockState->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); } TEST_F(DConcurrencyTestFixture, GlobalLockWaitIsInterruptible) { @@ -601,6 +646,29 @@ TEST_F(DConcurrencyTestFixture, GlobalLockWaitIsInterruptible) { }); ASSERT_THROWS_CODE(result.get(), AssertionException, ErrorCodes::Interrupted); + ASSERT_EQ(opCtx1->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); + ASSERT_EQ(opCtx2->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), + MODE_NONE); +} + +TEST_F(DConcurrencyTestFixture, GlobalLockWaitIsInterruptibleBlockedOnRSTL) { + auto clients = makeKClientsWithLockers(2); + auto opCtx1 = clients[0].second.get(); + auto opCtx2 = clients[1].second.get(); + + // The main thread takes an exclusive lock, causing the spawned thread to wait when it attempts + // to acquire a conflicting lock. + Lock::ResourceLock rstl(opCtx1->lockState(), resourceIdReplicationStateTransitionLock, MODE_X); + + auto result = runTaskAndKill(opCtx2, [&]() { + // Killing the lock wait should throw an exception. + Lock::GlobalLock g(opCtx2, MODE_S); + }); + + ASSERT_THROWS_CODE(result.get(), AssertionException, ErrorCodes::Interrupted); + ASSERT_EQ(opCtx1->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), MODE_X); + ASSERT_EQ(opCtx2->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), + MODE_NONE); } TEST_F(DConcurrencyTestFixture, GlobalLockWaitIsInterruptibleMMAP) { @@ -641,6 +709,35 @@ TEST_F(DConcurrencyTestFixture, GlobalLockWaitNotInterruptedWithLeaveUnlockedBeh ASSERT(g1.isLocked()); ASSERT(g2 != boost::none); ASSERT(!g2->isLocked()); + ASSERT_EQ(opCtx1->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); + ASSERT_EQ(opCtx2->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), + MODE_NONE); + + // Should not throw an exception. + result.get(); +} + +TEST_F(DConcurrencyTestFixture, + GlobalLockWaitNotInterruptedWithLeaveUnlockedBehaviorBlockedOnRSTL) { + auto clients = makeKClientsWithLockers(2); + auto opCtx1 = clients[0].second.get(); + auto opCtx2 = clients[1].second.get(); + + // The main thread takes an exclusive lock, causing the spawned thread to wait when it attempts + // to acquire a conflicting lock. + Lock::ResourceLock rstl(opCtx1->lockState(), resourceIdReplicationStateTransitionLock, MODE_X); + // Acquire this later to confirm that it stays unlocked. + boost::optional<Lock::GlobalLock> g2 = boost::none; + + // Killing the lock wait should not interrupt it, but rather leave it lock unlocked. + auto result = runTaskAndKill(opCtx2, [&]() { + g2.emplace(opCtx2, MODE_S, Date_t::max(), Lock::InterruptBehavior::kLeaveUnlocked); + }); + ASSERT(g2 != boost::none); + ASSERT(!g2->isLocked()); + ASSERT_EQ(opCtx1->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), MODE_X); + ASSERT_EQ(opCtx2->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), + MODE_NONE); // Should not throw an exception. result.get(); @@ -706,6 +803,30 @@ TEST_F(DConcurrencyTestFixture, SetMaxLockTimeoutMillisAndDoNotUsingWithInterrup ASSERT(g1.isLocked()); ASSERT(!g2.isLocked()); + + ASSERT_EQ(opCtx1->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); + ASSERT_EQ(opCtx2->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), + MODE_NONE); +} + +TEST_F(DConcurrencyTestFixture, SetMaxLockTimeoutMillisAndNotUsingInterruptBehaviorBlockedOnRSTL) { + auto clients = makeKClientsWithLockers(2); + auto opCtx1 = clients[0].second.get(); + auto opCtx2 = clients[1].second.get(); + + // Take the exclusive lock with the first caller. + Lock::ResourceLock rstl(opCtx1->lockState(), resourceIdReplicationStateTransitionLock, MODE_X); + + // Set a max timeout on the second caller that will override provided lock request deadlines. + // Then requesting a lock with Date_t::max() should cause a LockTimeout error to be thrown + // and then caught by the Lock::InterruptBehavior::kLeaveUnlocked setting. + opCtx2->lockState()->setMaxLockTimeout(Milliseconds(100)); + Lock::GlobalLock g2(opCtx2, MODE_S, Date_t::max(), Lock::InterruptBehavior::kLeaveUnlocked); + + ASSERT_EQ(opCtx1->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), MODE_X); + ASSERT_EQ(opCtx2->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), + MODE_NONE); + ASSERT(!g2.isLocked()); } TEST_F(DConcurrencyTestFixture, SetMaxLockTimeoutMillisAndThrowUsingInterruptBehavior) { @@ -726,6 +847,33 @@ TEST_F(DConcurrencyTestFixture, SetMaxLockTimeoutMillisAndThrowUsingInterruptBeh ErrorCodes::LockTimeout); ASSERT(g1.isLocked()); + + ASSERT_EQ(opCtx1->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); + ASSERT_EQ(opCtx2->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), + MODE_NONE); +} + +TEST_F(DConcurrencyTestFixture, + SetMaxLockTimeoutMillisAndThrowUsingInterruptBehaviorBlockedOnRSTL) { + auto clients = makeKClientsWithLockers(2); + auto opCtx1 = clients[0].second.get(); + auto opCtx2 = clients[1].second.get(); + + // Take the exclusive lock with the first caller. + Lock::ResourceLock rstl(opCtx1->lockState(), resourceIdReplicationStateTransitionLock, MODE_X); + + // Set a max timeout on the second caller that will override provided lock request deadlines. + // Then requesting a lock with Date_t::max() should cause a LockTimeout error to be thrown. + opCtx2->lockState()->setMaxLockTimeout(Milliseconds(100)); + + ASSERT_THROWS_CODE( + Lock::GlobalLock(opCtx2, MODE_S, Date_t::max(), Lock::InterruptBehavior::kThrow), + DBException, + ErrorCodes::LockTimeout); + + ASSERT_EQ(opCtx1->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), MODE_X); + ASSERT_EQ(opCtx2->lockState()->getLockMode(resourceIdReplicationStateTransitionLock), + MODE_NONE); } TEST_F(DConcurrencyTestFixture, DBLockWaitIsInterruptible) { diff --git a/src/mongo/db/concurrency/lock_manager_defs.h b/src/mongo/db/concurrency/lock_manager_defs.h index 130a4e7af21..3e161da066a 100644 --- a/src/mongo/db/concurrency/lock_manager_defs.h +++ b/src/mongo/db/concurrency/lock_manager_defs.h @@ -1,4 +1,3 @@ - /** * Copyright (C) 2018-present MongoDB, Inc. * @@ -189,6 +188,7 @@ public: enum SingletonHashIds { SINGLETON_INVALID = 0, SINGLETON_PARALLEL_BATCH_WRITER_MODE, + SINGLETON_REPLICATION_STATE_TRANSITION_LOCK, SINGLETON_GLOBAL, }; @@ -265,6 +265,13 @@ extern const ResourceId resourceIdAdminDB; // TODO: Merge this with resourceIdGlobal extern const ResourceId resourceIdParallelBatchWriterMode; +// Hardcoded resource id for the ReplicationStateTransitionLock (RSTL). We use the same resource +// type as resourceIdGlobal. This will also ensure the waits are reported as global, which is +// appropriate. This lock is acquired in mode X for any replication state transition and is acquired +// by all other reads and writes in mode IX. This lock is acquired after the PBWM but before the +// resourceIdGlobal. +extern const ResourceId resourceIdReplicationStateTransitionLock; + /** * Interface on which granted lock requests will be notified. See the contract for the notify * method for more information and also the LockManager::lock call. diff --git a/src/mongo/db/concurrency/lock_state.cpp b/src/mongo/db/concurrency/lock_state.cpp index 555a031570c..2320b9421ff 100644 --- a/src/mongo/db/concurrency/lock_state.cpp +++ b/src/mongo/db/concurrency/lock_state.cpp @@ -1,4 +1,3 @@ - /** * Copyright (C) 2018-present MongoDB, Inc. * @@ -614,7 +613,8 @@ bool LockerImpl::saveLockStateAndUnlock(Locker::LockSnapshot* stateOut) { // We should never have to save and restore metadata locks. invariant(RESOURCE_DATABASE == resId.getType() || RESOURCE_COLLECTION == resId.getType() || - (RESOURCE_GLOBAL == resId.getType() && isSharedLockMode(it->mode))); + (RESOURCE_GLOBAL == resId.getType() && isSharedLockMode(it->mode)) || + (resourceIdReplicationStateTransitionLock == resId && it->mode == MODE_IX)); // And, stuff the info into the out parameter. OneLock info; @@ -639,12 +639,19 @@ void LockerImpl::restoreLockState(OperationContext* opCtx, const Locker::LockSna invariant(_modeForTicket == MODE_NONE); std::vector<OneLock>::const_iterator it = state.locks.begin(); - // If we locked the PBWM, it must be locked before the resourceIdGlobal resource. + // If we locked the PBWM, it must be locked before the resourceIdGlobal and + // resourceIdReplicationStateTransitionLock resources. if (it != state.locks.end() && it->resourceId == resourceIdParallelBatchWriterMode) { invariant(LOCK_OK == lock(opCtx, it->resourceId, it->mode)); it++; } + // If we locked the RSTL, it must be locked before the resourceIdGlobal resource. + if (it != state.locks.end() && it->resourceId == resourceIdReplicationStateTransitionLock) { + invariant(LOCK_OK == lock(opCtx, it->resourceId, it->mode)); + it++; + } + invariant(LOCK_OK == lockGlobal(opCtx, state.globalMode)); for (; it != state.locks.end(); it++) { invariant(LOCK_OK == lock(it->resourceId, it->mode)); @@ -904,5 +911,7 @@ const ResourceId resourceIdOplog = ResourceId(RESOURCE_COLLECTION, StringData("l const ResourceId resourceIdAdminDB = ResourceId(RESOURCE_DATABASE, StringData("admin")); const ResourceId resourceIdParallelBatchWriterMode = ResourceId(RESOURCE_GLOBAL, ResourceId::SINGLETON_PARALLEL_BATCH_WRITER_MODE); +const ResourceId resourceIdReplicationStateTransitionLock = + ResourceId(RESOURCE_GLOBAL, ResourceId::SINGLETON_REPLICATION_STATE_TRANSITION_LOCK); } // namespace mongo diff --git a/src/mongo/db/concurrency/lock_state_test.cpp b/src/mongo/db/concurrency/lock_state_test.cpp index e2055f11724..a0094d3d485 100644 --- a/src/mongo/db/concurrency/lock_state_test.cpp +++ b/src/mongo/db/concurrency/lock_state_test.cpp @@ -1,4 +1,3 @@ - /** * Copyright (C) 2018-present MongoDB, Inc. * @@ -162,6 +161,43 @@ TEST(LockerImpl, saveAndRestoreGlobal) { } /** + * Test that saveLockerImpl can save and restore the RSTL. + */ +TEST(LockerImpl, saveAndRestoreRSTL) { + Locker::LockSnapshot lockInfo; + + LockerImpl locker; + + const ResourceId resIdDatabase(RESOURCE_DATABASE, "TestDB"_sd); + + // Acquire locks. + ASSERT_EQUALS(LOCK_OK, locker.lock(resourceIdReplicationStateTransitionLock, MODE_IX)); + locker.lockGlobal(MODE_IX); + ASSERT_EQUALS(LOCK_OK, locker.lock(resIdDatabase, MODE_IX)); + + // Save the lock state. + locker.saveLockStateAndUnlock(&lockInfo); + ASSERT(!locker.isLocked()); + ASSERT_EQUALS(MODE_IX, lockInfo.globalMode); + + // Check locks are unlocked. + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resourceIdReplicationStateTransitionLock)); + ASSERT(!locker.isLocked()); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); + + // Restore the lock(s) we had. + locker.restoreLockState(lockInfo); + + // Check locks are re-locked. + ASSERT(locker.isLocked()); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resourceIdReplicationStateTransitionLock)); + + ASSERT(locker.unlockGlobal()); + ASSERT(locker.unlock(resourceIdReplicationStateTransitionLock)); +} + +/** * Test that we don't unlock when we have the global lock more than once. */ TEST(LockerImpl, saveAndRestoreGlobalAcquiredTwice) { |