bool LockerImpl<IsForMMAPV1>::isCollectionLockedForMode(StringData ns, LockMode mode) const { invariant(nsIsFull(ns)); if (isW()) return true; if (isR() && isSharedLockMode(mode)) return true; const NamespaceString nss(ns); const ResourceId resIdDb(RESOURCE_DATABASE, nss.db()); LockMode dbMode = getLockMode(resIdDb); if (!shouldConflictWithSecondaryBatchApplication()) return true; switch (dbMode) { case MODE_NONE: return false; case MODE_X: return true; case MODE_S: return isSharedLockMode(mode); case MODE_IX: case MODE_IS: { const ResourceId resIdColl(RESOURCE_COLLECTION, ns); return isLockHeldForMode(resIdColl, mode); } break; case LockModesCount: break; } invariant(false); return false; }
void Lock::DBLock::relockWithMode(LockMode newMode) { // 2PL would delay the unlocking invariant(!_opCtx->lockState()->inAWriteUnitOfWork()); // Not allowed to change global intent invariant(!isSharedLockMode(_mode) || isSharedLockMode(newMode)); _opCtx->lockState()->unlock(_id); _mode = newMode; invariant(LOCK_OK == _opCtx->lockState()->lock(_opCtx, _id, _mode)); }
Lock::CollectionLock::CollectionLock(Locker* lockState, StringData ns, LockMode mode) : _id(RESOURCE_COLLECTION, ns), _lockState(lockState) { massert(28538, "need a non-empty collection name", nsIsFull(ns)); dassert(_lockState->isDbLockedForMode(nsToDatabaseSubstring(ns), isSharedLockMode(mode) ? MODE_IS : MODE_IX)); if (supportsDocLocking()) { _lockState->lock(_id, mode); } else { _lockState->lock(_id, isSharedLockMode(mode) ? MODE_S : MODE_X); } }
LockResult LockerImpl<IsForMMAPV1>::_lockGlobalBegin(LockMode mode, Milliseconds timeout) { dassert(isLocked() == (_modeForTicket != MODE_NONE)); if (_modeForTicket == MODE_NONE) { const bool reader = isSharedLockMode(mode); auto holder = ticketHolders[mode]; if (holder) { _clientState.store(reader ? kQueuedReader : kQueuedWriter); if (timeout == Milliseconds::max()) { holder->waitForTicket(); } else if (!holder->waitForTicketUntil(Date_t::now() + timeout)) { _clientState.store(kInactive); return LOCK_TIMEOUT; } } _clientState.store(reader ? kActiveReader : kActiveWriter); _modeForTicket = mode; } const LockResult result = lockBegin(resourceIdGlobal, mode); if (result == LOCK_OK) return LOCK_OK; // Currently, deadlock detection does not happen inline with lock acquisition so the only // unsuccessful result that the lock manager would return is LOCK_WAITING. invariant(result == LOCK_WAITING); return result; }
bool LockerImpl::saveLockStateAndUnlock(Locker::LockSnapshot* stateOut) { // We shouldn't be saving and restoring lock state from inside a WriteUnitOfWork. invariant(!inAWriteUnitOfWork()); // Clear out whatever is in stateOut. stateOut->locks.clear(); stateOut->globalMode = MODE_NONE; // First, we look at the global lock. There is special handling for this (as the flush // lock goes along with it) so we store it separately from the more pedestrian locks. LockRequestsMap::Iterator globalRequest = _requests.find(resourceIdGlobal); if (!globalRequest) { // If there's no global lock there isn't really anything to do. Check that. for (auto it = _requests.begin(); !it.finished(); it.next()) { invariant(it.key().getType() == RESOURCE_MUTEX); } return false; } // If the global lock or RSTL has been acquired more than once, we're probably somewhere in a // DBDirectClient call. It's not safe to release and reacquire locks -- the context using // the DBDirectClient is probably not prepared for lock release. LockRequestsMap::Iterator rstlRequest = _requests.find(resourceIdReplicationStateTransitionLock); if (globalRequest->recursiveCount > 1 || (rstlRequest && rstlRequest->recursiveCount > 1)) { return false; } // The global lock must have been acquired just once stateOut->globalMode = globalRequest->mode; invariant(unlock(resourceIdGlobal)); // Next, the non-global locks. for (LockRequestsMap::Iterator it = _requests.begin(); !it.finished(); it.next()) { const ResourceId resId = it.key(); const ResourceType resType = resId.getType(); if (resType == RESOURCE_MUTEX) continue; // 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)) || (resourceIdReplicationStateTransitionLock == resId && it->mode == MODE_IX)); // And, stuff the info into the out parameter. OneLock info; info.resourceId = resId; info.mode = it->mode; stateOut->locks.push_back(info); invariant(unlock(resId)); } invariant(!isLocked()); // Sort locks by ResourceId. They'll later be acquired in this canonical locking order. std::sort(stateOut->locks.begin(), stateOut->locks.end()); return true; }
void Lock::DBLock::relockWithMode(LockMode newMode) { // 2PL would delay the unlocking invariant(!_locker->inAWriteUnitOfWork()); // Not allowed to change global intent invariant(!isSharedLockMode(_mode) || isSharedLockMode(newMode)); _locker->unlock(_id); _mode = newMode; if (supportsDocLocking() || enableCollectionLocking) { invariant(LOCK_OK == _locker->lock(_id, _mode)); } else { invariant(LOCK_OK == _locker->lock(_id, isSharedLockMode(_mode) ? MODE_S : MODE_X)); } }
LockResult LockerImpl::_lockGlobalBegin(OperationContext* opCtx, LockMode mode, Date_t deadline) { dassert(isLocked() == (_modeForTicket != MODE_NONE)); if (_modeForTicket == MODE_NONE) { auto acquireTicketResult = _acquireTicket(opCtx, mode, deadline); if (acquireTicketResult != LOCK_OK) { return acquireTicketResult; } _modeForTicket = mode; } LockMode actualLockMode = mode; if (opCtx) { auto storageEngine = opCtx->getServiceContext()->getStorageEngine(); if (storageEngine && !storageEngine->supportsDBLocking()) { actualLockMode = isSharedLockMode(mode) ? MODE_S : MODE_X; } } const LockResult result = lockBegin(opCtx, resourceIdGlobal, actualLockMode); if (result == LOCK_OK) return LOCK_OK; invariant(result == LOCK_WAITING); return result; }
Lock::CollectionLock::CollectionLock(Locker* lockState, StringData ns, LockMode mode, Date_t deadline) : _id(RESOURCE_COLLECTION, ns), _result(LOCK_INVALID), _lockState(lockState) { massert(28538, "need a non-empty collection name", nsIsFull(ns)); dassert(_lockState->isDbLockedForMode(nsToDatabaseSubstring(ns), isSharedLockMode(mode) ? MODE_IS : MODE_IX)); LockMode actualLockMode = mode; if (!supportsDocLocking()) { actualLockMode = isSharedLockMode(mode) ? MODE_S : MODE_X; } _result = _lockState->lock(_id, actualLockMode, deadline); invariant(_result == LOCK_OK || deadline != Date_t::max()); }
Lock::DBLock::DBLock(Locker* locker, StringData db, LockMode mode) : _id(RESOURCE_DATABASE, db), _locker(locker), _mode(mode), _globalLock(locker, isSharedLockMode(_mode) ? MODE_IS : MODE_IX, UINT_MAX) { massert(28539, "need a valid database name", !db.empty() && nsIsDbOnly(db)); // Need to acquire the flush lock _locker->lockMMAPV1Flush(); // The check for the admin db is to ensure direct writes to auth collections // are serialized (see SERVER-16092). if ((_id == resourceIdAdminDB) && !isSharedLockMode(_mode)) { _mode = MODE_X; } invariant(LOCK_OK == _locker->lock(_id, _mode)); }
Lock::DBLock::DBLock(OperationContext* opCtx, StringData db, LockMode mode, Date_t deadline) : _id(RESOURCE_DATABASE, db), _opCtx(opCtx), _result(LOCK_INVALID), _mode(mode), _globalLock( opCtx, isSharedLockMode(_mode) ? MODE_IS : MODE_IX, deadline, InterruptBehavior::kThrow) { massert(28539, "need a valid database name", !db.empty() && nsIsDbOnly(db)); // The check for the admin db is to ensure direct writes to auth collections // are serialized (see SERVER-16092). if ((_id == resourceIdAdminDB) && !isSharedLockMode(_mode)) { _mode = MODE_X; } _opCtx->lockState()->lock(_opCtx, _id, _mode, deadline); _result = LOCK_OK; }
bool LockerImpl<IsForMMAPV1>::isDbLockedForMode(StringData dbName, LockMode mode) const { invariant(nsIsDbOnly(dbName)); if (isW()) return true; if (isR() && isSharedLockMode(mode)) return true; const ResourceId resIdDb(RESOURCE_DATABASE, dbName); return isLockHeldForMode(resIdDb, mode); }
LockResult LockerImpl<IsForMMAPV1>::_acquireTicket(OperationContext* opCtx, LockMode mode, Date_t deadline) { const bool reader = isSharedLockMode(mode); auto holder = shouldAcquireTicket() ? ticketHolders[mode] : nullptr; if (holder) { _clientState.store(reader ? kQueuedReader : kQueuedWriter); // If the ticket wait is interrupted, restore the state of the client. auto restoreStateOnErrorGuard = MakeGuard([&] { _clientState.store(kInactive); }); if (deadline == Date_t::max()) { holder->waitForTicket(opCtx); } else if (!holder->waitForTicketUntil(opCtx, deadline)) { return LOCK_TIMEOUT; } restoreStateOnErrorGuard.Dismiss(); } _clientState.store(reader ? kActiveReader : kActiveWriter); return LOCK_OK; }
void LockerImpl::reacquireTicket(OperationContext* opCtx) { invariant(_modeForTicket != MODE_NONE); auto clientState = _clientState.load(); const bool reader = isSharedLockMode(_modeForTicket); // Ensure that either we don't have a ticket, or the current ticket mode matches the lock mode. invariant(clientState == kInactive || (clientState == kActiveReader && reader) || (clientState == kActiveWriter && !reader)); // If we already have a ticket, there's nothing to do. if (clientState != kInactive) return; auto acquireTicketResult = _acquireTicket(opCtx, _modeForTicket, Date_t::max()); uassert(ErrorCodes::LockTimeout, str::stream() << "Unable to acquire ticket with mode '" << _modeForTicket << "' within a max lock request timeout of '" << _maxLockTimeout.get() << "' milliseconds.", acquireTicketResult == LOCK_OK || !_maxLockTimeout); // If no deadline is specified we should always get a ticket. invariant(acquireTicketResult == LOCK_OK); }
LockResult LockerImpl::_acquireTicket(OperationContext* opCtx, LockMode mode, Date_t deadline) { const bool reader = isSharedLockMode(mode); auto holder = shouldAcquireTicket() ? ticketHolders[mode] : nullptr; if (holder) { _clientState.store(reader ? kQueuedReader : kQueuedWriter); if (_maxLockTimeout && !_uninterruptibleLocksRequested) { deadline = std::min(deadline, Date_t::now() + _maxLockTimeout.get()); } // If the ticket wait is interrupted, restore the state of the client. auto restoreStateOnErrorGuard = MakeGuard([&] { _clientState.store(kInactive); }); OperationContext* interruptible = _uninterruptibleLocksRequested ? nullptr : opCtx; if (deadline == Date_t::max()) { holder->waitForTicket(interruptible); } else if (!holder->waitForTicketUntil(interruptible, deadline)) { return LOCK_TIMEOUT; } restoreStateOnErrorGuard.Dismiss(); } _clientState.store(reader ? kActiveReader : kActiveWriter); return LOCK_OK; }