/** * Copyright (C) 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/db_raii.h" #include "mongo/db/catalog/database_holder.h" #include "mongo/db/curop.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/s/collection_sharding_state.h" namespace mongo { namespace { const boost::optional kDoNotChangeProfilingLevel = boost::none; } // namespace AutoStatsTracker::AutoStatsTracker(OperationContext* opCtx, const NamespaceString& nss, Top::LockType lockType, boost::optional dbProfilingLevel, Date_t deadline) : _opCtx(opCtx), _lockType(lockType) { if (!dbProfilingLevel) { // No profiling level was determined, attempt to read the profiling level from the Database // object. AutoGetDb autoDb(_opCtx, nss.db(), MODE_IS, deadline); if (autoDb.getDb()) { dbProfilingLevel = autoDb.getDb()->getProfilingLevel(); } } stdx::lock_guard clientLock(*_opCtx->getClient()); CurOp::get(_opCtx)->enter_inlock(nss.ns().c_str(), dbProfilingLevel); } AutoStatsTracker::~AutoStatsTracker() { auto curOp = CurOp::get(_opCtx); Top::get(_opCtx->getServiceContext()) .record(_opCtx, curOp->getNS(), curOp->getLogicalOp(), _lockType, durationCount(curOp->elapsedTimeExcludingPauses()), curOp->isCommand(), curOp->getReadWriteType()); } AutoGetCollectionForRead::AutoGetCollectionForRead(OperationContext* opCtx, const NamespaceStringOrUUID& nsOrUUID, AutoGetCollection::ViewMode viewMode, Date_t deadline) { const auto collectionLockMode = getLockModeForQuery(opCtx); _autoColl.emplace(opCtx, nsOrUUID, collectionLockMode, viewMode, deadline); while (true) { auto coll = _autoColl->getCollection(); if (!coll) { return; } auto minSnapshot = coll->getMinimumVisibleSnapshot(); if (!minSnapshot) { return; } auto mySnapshot = opCtx->recoveryUnit()->getMajorityCommittedSnapshot(); if (!mySnapshot) { return; } if (mySnapshot >= minSnapshot) { return; } // Yield locks in order to do the blocking call below _autoColl = boost::none; repl::ReplicationCoordinator::get(opCtx)->waitUntilSnapshotCommitted(opCtx, *minSnapshot); uassertStatusOK(opCtx->recoveryUnit()->setReadFromMajorityCommittedSnapshot()); { stdx::lock_guard lk(*opCtx->getClient()); CurOp::get(opCtx)->yielded(); } _autoColl.emplace(opCtx, nsOrUUID, collectionLockMode, viewMode, deadline); } } AutoGetCollectionForReadCommand::AutoGetCollectionForReadCommand( OperationContext* opCtx, const NamespaceStringOrUUID& nsOrUUID, AutoGetCollection::ViewMode viewMode, Date_t deadline) : _autoCollForRead(opCtx, nsOrUUID, viewMode, deadline), _statsTracker(opCtx, _autoCollForRead.getNss(), Top::LockType::ReadLocked, _autoCollForRead.getDb() ? _autoCollForRead.getDb()->getProfilingLevel() : kDoNotChangeProfilingLevel, deadline) { if (!_autoCollForRead.getView()) { // We have both the DB and collection locked, which is the prerequisite to do a stable shard // version check, but we'd like to do the check after we have a satisfactory snapshot. auto css = CollectionShardingState::get(opCtx, _autoCollForRead.getNss()); css->checkShardVersionOrThrow(opCtx); } } OldClientContext::OldClientContext(OperationContext* opCtx, const std::string& ns, bool doVersion) : OldClientContext(opCtx, ns, doVersion, dbHolder().get(opCtx, ns), false) {} OldClientContext::OldClientContext( OperationContext* opCtx, const std::string& ns, bool doVersion, Database* db, bool justCreated) : _opCtx(opCtx), _db(db), _justCreated(justCreated) { if (!_db) { const auto dbName = nsToDatabaseSubstring(ns); invariant(_opCtx->lockState()->isDbLockedForMode(dbName, MODE_X)); _db = dbHolder().openDb(_opCtx, dbName, &_justCreated); invariant(_db); } auto const currentOp = CurOp::get(_opCtx); if (doVersion) { switch (currentOp->getNetworkOp()) { case dbGetMore: // getMore is special and should be handled elsewhere case dbUpdate: // update & delete check shard version as part of the write executor case dbDelete: // path, so no need to check them here as well break; default: auto css = CollectionShardingState::get(_opCtx, ns); css->checkShardVersionOrThrow(_opCtx); break; } } stdx::lock_guard lk(*_opCtx->getClient()); currentOp->enter_inlock(ns.c_str(), _db->getProfilingLevel()); } OldClientContext::~OldClientContext() { // Lock must still be held invariant(_opCtx->lockState()->isLocked()); auto currentOp = CurOp::get(_opCtx); Top::get(_opCtx->getClient()->getServiceContext()) .record(_opCtx, currentOp->getNS(), currentOp->getLogicalOp(), _opCtx->lockState()->isWriteLocked() ? Top::LockType::WriteLocked : Top::LockType::ReadLocked, _timer.micros(), currentOp->isCommand(), currentOp->getReadWriteType()); } OldClientWriteContext::OldClientWriteContext(OperationContext* opCtx, StringData ns) : _opCtx(opCtx), _nss(ns) { // Lock the database and collection _autoCreateDb.emplace(opCtx, _nss.db(), MODE_IX); _collLock.emplace(opCtx->lockState(), _nss.ns(), MODE_IX); // TODO (Kal): None of the places which use OldClientWriteContext seem to require versioning, so // we should consider defaulting this to false const bool doShardVersionCheck = true; _clientContext.emplace(opCtx, _nss.ns(), doShardVersionCheck, _autoCreateDb->getDb(), _autoCreateDb->justCreated()); invariant(_autoCreateDb->getDb() == _clientContext->db()); // If the collection exists, there is no need to lock into stronger mode if (getCollection()) return; // If the database was just created, it is already locked in MODE_X so we can skip the relocking // code below if (_autoCreateDb->justCreated()) { dassert(opCtx->lockState()->isDbLockedForMode(_nss.db(), MODE_X)); return; } // If the collection doesn't exists, put the context in a state where the database is locked in // MODE_X so that the collection can be created _clientContext.reset(); _collLock.reset(); _autoCreateDb.reset(); _autoCreateDb.emplace(opCtx, _nss.db(), MODE_X); _clientContext.emplace(opCtx, _nss.ns(), doShardVersionCheck, _autoCreateDb->getDb(), _autoCreateDb->justCreated()); invariant(_autoCreateDb->getDb() == _clientContext->db()); } LockMode getLockModeForQuery(OperationContext* opCtx) { invariant(opCtx); if (repl::ReadConcernArgs::get(opCtx).getLevel() == repl::ReadConcernLevel::kSnapshotReadConcern) { return MODE_IX; } return MODE_IS; } } // namespace mongo