|
Due to our support of recursive locking, it is possible for an operation to hold a global IS, acquiring a read ticket, and then obtain an IX lock at a lower level without taking a write ticket.
This test passes:
// d_concurrency_test.cpp
|
|
TEST_F(DConcurrencyTestFixture, WrongTicket) {
|
auto clientOpctxPairs = makeKClientsWithLockers(1);
|
auto opctx1 = clientOpctxPairs[0].second.get();
|
|
Lock::GlobalLock read(opctx1, MODE_IS);
|
ASSERT_TRUE(opctx1->lockState()->hasReadTicket());
|
ASSERT_FALSE(opctx1->lockState()->hasWriteTicket());
|
ASSERT_FALSE(opctx1->lockState()->isWriteLocked());
|
|
Lock::GlobalLock write(opctx1, MODE_IX);
|
|
ASSERT_TRUE(opctx1->lockState()->hasReadTicket());
|
ASSERT_FALSE(opctx1->lockState()->hasWriteTicket());
|
ASSERT_TRUE(opctx1->lockState()->isWriteLocked());
|
}
|
We can discover violations of this with the following assertion:
diff --git a/src/mongo/db/concurrency/lock_state.cpp b/src/mongo/db/concurrency/lock_state.cpp
|
index a968d2eee6e..2d22ebf6790 100644
|
--- a/src/mongo/db/concurrency/lock_state.cpp
|
+++ b/src/mongo/db/concurrency/lock_state.cpp
|
@@ -409,6 +409,11 @@ void LockerImpl::lockGlobal(OperationContext* opCtx, LockMode mode, Date_t deadl
|
_acquireTicket(opCtx, mode, deadline));
|
}
|
_modeForTicket = mode;
|
+ } else if (!isModeCovered(mode, _modeForTicket)) {
|
+ LOGV2_FATAL(99999,
|
+ "Ticket held does not cover requested mode for global lock",
|
+ "held"_attr = _modeForTicket,
|
+ "requested"_attr = mode);
|
}
|
This is mostly an accounting problem since we don't use tickets to block reads or writes. We only use tickets for throttling reads and writes, and most applications do not hit this limit.
|