/**
* Copyright (C) 2008-2014 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/platform/basic.h"
#include "mongo/db/concurrency/d_concurrency.h"
#include
#include "mongo/db/namespace_string.h"
#include "mongo/db/server_parameters.h"
#include "mongo/db/service_context.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/mongoutils/str.h"
#include "mongo/util/stacktrace.h"
namespace mongo {
Lock::TempRelease::TempRelease(Locker* lockState)
: _lockState(lockState),
_lockSnapshot(),
_locksReleased(_lockState->saveLockStateAndUnlock(&_lockSnapshot)) {}
Lock::TempRelease::~TempRelease() {
if (_locksReleased) {
invariant(!_lockState->isLocked());
_lockState->restoreLockState(_lockSnapshot);
}
}
Lock::GlobalLock::GlobalLock(Locker* locker)
: _locker(locker), _result(LOCK_INVALID), _pbwm(locker, resourceIdParallelBatchWriterMode) {}
Lock::GlobalLock::GlobalLock(Locker* locker, LockMode lockMode, unsigned timeoutMs)
: GlobalLock(locker, lockMode, EnqueueOnly()) {
waitForLock(timeoutMs);
}
Lock::GlobalLock::GlobalLock(Locker* locker, LockMode lockMode, EnqueueOnly enqueueOnly)
: _locker(locker), _result(LOCK_INVALID), _pbwm(locker, resourceIdParallelBatchWriterMode) {
_enqueue(lockMode);
}
void Lock::GlobalLock::_enqueue(LockMode lockMode) {
if (!_locker->isBatchWriter()) {
_pbwm.lock(MODE_IS);
}
_result = _locker->lockGlobalBegin(lockMode);
}
void Lock::GlobalLock::waitForLock(unsigned timeoutMs) {
if (_result == LOCK_WAITING) {
_result = _locker->lockGlobalComplete(timeoutMs);
}
if (_result != LOCK_OK && !_locker->isBatchWriter()) {
_pbwm.unlock();
}
}
void Lock::GlobalLock::_unlock() {
if (isLocked()) {
_locker->unlockGlobal();
_result = LOCK_INVALID;
}
}
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() {
_locker->unlock(_id);
}
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;
invariant(LOCK_OK == _locker->lock(_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);
}
}
Lock::CollectionLock::~CollectionLock() {
_lockState->unlock(_id);
}
void Lock::CollectionLock::relockAsDatabaseExclusive(Lock::DBLock& dbLock) {
_lockState->unlock(_id);
dbLock.relockWithMode(MODE_X);
// don't need the lock, but need something to unlock in the destructor
_lockState->lock(_id, MODE_IX);
}
namespace {
stdx::mutex oplogSerialization; // for OplogIntentWriteLock
} // namespace
Lock::OplogIntentWriteLock::OplogIntentWriteLock(Locker* lockState)
: _lockState(lockState), _serialized(false) {
_lockState->lock(resourceIdOplog, MODE_IX);
}
Lock::OplogIntentWriteLock::~OplogIntentWriteLock() {
if (_serialized) {
oplogSerialization.unlock();
}
_lockState->unlock(resourceIdOplog);
}
void Lock::OplogIntentWriteLock::serializeIfNeeded() {
if (!supportsDocLocking() && !_serialized) {
oplogSerialization.lock();
_serialized = true;
}
}
Lock::ParallelBatchWriterMode::ParallelBatchWriterMode(Locker* lockState)
: _pbwm(lockState, resourceIdParallelBatchWriterMode, MODE_X) {}
void Lock::ResourceLock::lock(LockMode mode) {
invariant(_result == LOCK_INVALID);
_result = _locker->lock(_rid, mode);
invariant(_result == LOCK_OK);
}
void Lock::ResourceLock::unlock() {
if (_result == LOCK_OK) {
_locker->unlock(_rid);
_result = LOCK_INVALID;
}
}
void synchronizeOnCappedInFlightResource(Locker* lockState, const NamespaceString& cappedNs) {
dassert(lockState->inAWriteUnitOfWork());
const ResourceId resource = cappedNs.db() == "local" ? resourceCappedInFlightForLocalDb
: resourceCappedInFlightForOtherDb;
// It is illegal to acquire the capped in-flight lock for non-local dbs while holding the
// capped in-flight lock for the local db. (Unless we already hold the otherDb lock since
// reacquiring a lock in the same mode never blocks.)
if (resource == resourceCappedInFlightForOtherDb) {
dassert(!lockState->isLockHeldForMode(resourceCappedInFlightForLocalDb, MODE_IX) ||
lockState->isLockHeldForMode(resourceCappedInFlightForOtherDb, MODE_IX));
}
Lock::ResourceLock{lockState, resource, MODE_IX}; // held until end of WUOW.
}
} // namespace mongo