summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorJudah Schvimer <judah@mongodb.com>2018-11-18 15:50:40 -0500
committerJudah Schvimer <judah@mongodb.com>2018-11-18 15:50:40 -0500
commit2b71741cfee235cdbfb69c101bb6ac06a5da436c (patch)
treeab42093c4256016351b0d52e1db939f386ed70c9 /src/mongo
parentd167e6129256d6b60f01a19ed14bde5604255df7 (diff)
downloadmongo-2b71741cfee235cdbfb69c101bb6ac06a5da436c.tar.gz
SERVER-37989 introduce a new ReplicationStateTransitionLock resource
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/concurrency/d_concurrency.cpp31
-rw-r--r--src/mongo/db/concurrency/d_concurrency.h2
-rw-r--r--src/mongo/db/concurrency/d_concurrency_test.cpp150
-rw-r--r--src/mongo/db/concurrency/lock_manager_defs.h9
-rw-r--r--src/mongo/db/concurrency/lock_state.cpp15
-rw-r--r--src/mongo/db/concurrency/lock_state_test.cpp38
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) {