diff options
-rw-r--r-- | src/mongo/db/catalog/collection.h | 6 | ||||
-rw-r--r-- | src/mongo/db/catalog/database_holder.h | 13 | ||||
-rw-r--r-- | src/mongo/db/catalog/database_holder_impl.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/catalog/database_holder_impl.h | 3 | ||||
-rw-r--r-- | src/mongo/db/catalog/database_holder_mock.h | 5 | ||||
-rw-r--r-- | src/mongo/db/catalog_raii.cpp | 37 | ||||
-rw-r--r-- | src/mongo/db/catalog_raii.h | 65 | ||||
-rw-r--r-- | src/mongo/db/concurrency/d_concurrency.cpp | 32 | ||||
-rw-r--r-- | src/mongo/db/concurrency/d_concurrency.h | 12 | ||||
-rw-r--r-- | src/mongo/db/views/durable_view_catalog.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/views/view_catalog.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/views/view_catalog.h | 6 |
12 files changed, 181 insertions, 20 deletions
diff --git a/src/mongo/db/catalog/collection.h b/src/mongo/db/catalog/collection.h index b080647e78d..5f79c148a09 100644 --- a/src/mongo/db/catalog/collection.h +++ b/src/mongo/db/catalog/collection.h @@ -58,19 +58,15 @@ #include "mongo/util/decorable.h" namespace mongo { + class CappedCallback; class CollectionPtr; -class ExtentManager; class IndexCatalog; class IndexCatalogEntry; -class IndexDescriptor; -class DatabaseImpl; class MatchExpression; class OpDebug; class OperationContext; class RecordCursor; -class UpdateDriver; -class UpdateRequest; /** * Holds information update an update operation. diff --git a/src/mongo/db/catalog/database_holder.h b/src/mongo/db/catalog/database_holder.h index 890ad037de1..32e422b2e0c 100644 --- a/src/mongo/db/catalog/database_holder.h +++ b/src/mongo/db/catalog/database_holder.h @@ -42,6 +42,7 @@ class CollectionCatalogEntry; class Database; class OperationContext; class RecordStore; +class ViewCatalog; /** * Registry of opened databases. @@ -65,6 +66,18 @@ public: virtual Database* getDb(OperationContext* const opCtx, const StringData ns) const = 0; /** + * Fetches the ViewCatalog decorating the Database matching 'dbName', or returns nullptr if the + * database does not exist. The returned ViewCatalog is safe to access without a lock because it + * is held as a shared_ptr. + * + * The ViewCatalog must be fetched through this interface if the caller holds no database lock + * to ensure the Database object is safe to access. This class' internal mutex provides + * concurrency protection around looking up and accessing the Database object matching 'dbName. + */ + virtual std::shared_ptr<ViewCatalog> getSharedViewCatalog(OperationContext* const opCtx, + StringData dbName) const = 0; + + /** * Retrieves a database reference if it is already opened, or opens it if it hasn't been * opened/created yet. Must be called with the database locked in X-mode. * diff --git a/src/mongo/db/catalog/database_holder_impl.cpp b/src/mongo/db/catalog/database_holder_impl.cpp index 0fc3fdeb8dc..962504982a6 100644 --- a/src/mongo/db/catalog/database_holder_impl.cpp +++ b/src/mongo/db/catalog/database_holder_impl.cpp @@ -43,6 +43,7 @@ #include "mongo/db/service_context.h" #include "mongo/db/stats/top.h" #include "mongo/db/storage/storage_engine.h" +#include "mongo/db/views/view_catalog.h" #include "mongo/logv2/log.h" namespace mongo { @@ -67,7 +68,6 @@ StringData _todb(StringData ns) { } // namespace - Database* DatabaseHolderImpl::getDb(OperationContext* opCtx, StringData ns) const { const StringData db = _todb(ns); invariant(opCtx->lockState()->isDbLockedForMode(db, MODE_IS) || @@ -82,6 +82,17 @@ Database* DatabaseHolderImpl::getDb(OperationContext* opCtx, StringData ns) cons return nullptr; } +std::shared_ptr<ViewCatalog> DatabaseHolderImpl::getSharedViewCatalog(OperationContext* opCtx, + StringData dbName) const { + stdx::lock_guard<SimpleMutex> lk(_m); + DBs::const_iterator it = _dbs.find(dbName); + if (it != _dbs.end()) { + return ViewCatalog::getShared(it->second); + } + + return nullptr; +} + std::set<std::string> DatabaseHolderImpl::_getNamesWithConflictingCasing_inlock(StringData name) { std::set<std::string> duplicates; diff --git a/src/mongo/db/catalog/database_holder_impl.h b/src/mongo/db/catalog/database_holder_impl.h index 8b200c22a51..b97c0502cdc 100644 --- a/src/mongo/db/catalog/database_holder_impl.h +++ b/src/mongo/db/catalog/database_holder_impl.h @@ -42,6 +42,9 @@ public: Database* getDb(OperationContext* opCtx, StringData ns) const override; + std::shared_ptr<ViewCatalog> getSharedViewCatalog(OperationContext* const opCtx, + StringData dbName) const override; + Database* openDb(OperationContext* opCtx, StringData ns, bool* justCreated = nullptr) override; void dropDb(OperationContext* opCtx, Database* db) override; diff --git a/src/mongo/db/catalog/database_holder_mock.h b/src/mongo/db/catalog/database_holder_mock.h index 7aaa86f530f..27a2d0c204c 100644 --- a/src/mongo/db/catalog/database_holder_mock.h +++ b/src/mongo/db/catalog/database_holder_mock.h @@ -41,6 +41,11 @@ public: return nullptr; } + std::shared_ptr<ViewCatalog> getSharedViewCatalog(OperationContext* const opCtx, + StringData dbName) const override { + return nullptr; + } + Database* openDb(OperationContext* opCtx, StringData ns, bool* justCreated = nullptr) override { return nullptr; } diff --git a/src/mongo/db/catalog_raii.cpp b/src/mongo/db/catalog_raii.cpp index f19b9738453..2e1c62ad736 100644 --- a/src/mongo/db/catalog_raii.cpp +++ b/src/mongo/db/catalog_raii.cpp @@ -194,6 +194,43 @@ Collection* AutoGetCollection::getWritableCollection(CollectionCatalog::Lifetime return _writableColl; } +AutoGetCollectionLockFree::AutoGetCollectionLockFree(OperationContext* opCtx, + const NamespaceStringOrUUID& nsOrUUID, + AutoGetCollectionViewMode viewMode, + Date_t deadline) + : _globalLock( + opCtx, MODE_IS, deadline, Lock::InterruptBehavior::kThrow, true /* skipRSTLLock */) { + + // Wait for a configured amount of time after acquiring locks if the failpoint is enabled + setAutoGetCollectionWait.execute( + [&](const BSONObj& data) { sleepFor(Milliseconds(data["waitForMillis"].numberInt())); }); + + _resolvedNss = CollectionCatalog::get(opCtx).resolveNamespaceStringOrUUID(opCtx, nsOrUUID); + _collection = + CollectionCatalog::get(opCtx).lookupCollectionByNamespaceForRead(opCtx, _resolvedNss); + _collectionPtr = CollectionPtr(_collection); + + // TODO (SERVER-51319): add DatabaseShardingState::checkDbVersion somewhere. + + if (_collection) { + // If the collection exists, there is no need to check for views. + return; + } + + // Returns nullptr for 'viewCatalog' if db does not exist. + auto viewCatalog = DatabaseHolder::get(opCtx)->getSharedViewCatalog(opCtx, _resolvedNss.db()); + if (!viewCatalog) { + return; + } + + // TODO (SERVER-51320): this code will invariant in ViewCatalog::lookup() because it takes a + // CollectionLock that expects a DBLock to be held on the database already. + _view = viewCatalog->lookup(opCtx, _resolvedNss.ns()); + uassert(ErrorCodes::CommandNotSupportedOnView, + str::stream() << "Namespace " << _resolvedNss.ns() << " is a view, not a collection", + !_view || viewMode == AutoGetCollectionViewMode::kViewsPermitted); +} + struct CollectionWriter::SharedImpl { SharedImpl(CollectionWriter* parent) : _parent(parent) {} diff --git a/src/mongo/db/catalog_raii.h b/src/mongo/db/catalog_raii.h index eade7f99c4b..d5c499c4bc7 100644 --- a/src/mongo/db/catalog_raii.h +++ b/src/mongo/db/catalog_raii.h @@ -198,6 +198,71 @@ protected: }; /** + * RAII-style class that acquires the global MODE_IS lock. This class should only be used for reads. + * + * NOTE: Throws NamespaceNotFound if the collection UUID cannot be resolved to a nss. + * + * The collection references returned by this class will no longer be safe to retain after this + * object goes out of scope. This object ensures the continued existence of a Collection reference, + * if the collection exists when this object is instantiated. + */ +class AutoGetCollectionLockFree { + AutoGetCollectionLockFree(const AutoGetCollectionLockFree&) = delete; + AutoGetCollectionLockFree& operator=(const AutoGetCollectionLockFree&) = delete; + +public: + AutoGetCollectionLockFree( + OperationContext* opCtx, + const NamespaceStringOrUUID& nsOrUUID, + AutoGetCollectionViewMode viewMode = AutoGetCollectionViewMode::kViewsForbidden, + Date_t deadline = Date_t::max()); + + /** + * AutoGetCollectionLockFree can be used as a Collection pointer with the -> operator. + */ + const Collection* operator->() const { + return getCollection().get(); + } + + /** + * Returns nullptr if the collection didn't exist. + */ + const CollectionPtr& getCollection() const { + return _collectionPtr; + } + + /** + * Returns nullptr if the view didn't exist. + */ + ViewDefinition* getView() const { + return _view.get(); + } + + /** + * Returns the resolved namespace of the collection or view. + */ + const NamespaceString& getNss() const { + return _resolvedNss; + } + +private: + Lock::GlobalLock _globalLock; + + // If the object was instantiated with a UUID, contains the resolved namespace, otherwise it is + // the same as the input namespace string + NamespaceString _resolvedNss; + + // The Collection shared_ptr will keep the Collection instance alive even if it is removed from + // the CollectionCatalog while this lock-free operation runs. + std::shared_ptr<const Collection> _collection; + + // The CollectionPtr is the access point to the Collection instance for callers. + CollectionPtr _collectionPtr; + + std::shared_ptr<ViewDefinition> _view; +}; + +/** * RAII-style class to handle the lifetime of writable Collections. * It does not take any locks, concurrency needs to be handled separately using explicit locks or * AutoGetCollection. This class can serve as an adaptor to unify different methods of acquiring a diff --git a/src/mongo/db/concurrency/d_concurrency.cpp b/src/mongo/db/concurrency/d_concurrency.cpp index a48d176689e..ea4738e55aa 100644 --- a/src/mongo/db/concurrency/d_concurrency.cpp +++ b/src/mongo/db/concurrency/d_concurrency.cpp @@ -139,11 +139,13 @@ bool Lock::ResourceMutex::isAtLeastReadLocked(Locker* locker) { Lock::GlobalLock::GlobalLock(OperationContext* opCtx, LockMode lockMode, Date_t deadline, - InterruptBehavior behavior) + InterruptBehavior behavior, + bool skipRSTLLock) : _opCtx(opCtx), _result(LOCK_INVALID), _pbwm(opCtx->lockState(), resourceIdParallelBatchWriterMode), _interruptBehavior(behavior), + _skipRSTLLock(skipRSTLLock), _isOutermostLock(!opCtx->lockState()->isLocked()) { _opCtx->lockState()->getFlowControlTicket(_opCtx, lockMode); @@ -157,17 +159,14 @@ Lock::GlobalLock::GlobalLock(OperationContext* opCtx, } }); - _opCtx->lockState()->lock( - _opCtx, resourceIdReplicationStateTransitionLock, MODE_IX, deadline); - - auto unlockRSTL = makeGuard( - [this] { _opCtx->lockState()->unlock(resourceIdReplicationStateTransitionLock); }); - _result = LOCK_INVALID; - _opCtx->lockState()->lockGlobal(_opCtx, lockMode, deadline); + if (skipRSTLLock) { + _takeGlobalLockOnly(lockMode, deadline); + } else { + _takeGlobalAndRSTLLocks(lockMode, deadline); + } _result = LOCK_OK; - unlockRSTL.dismiss(); unlockPBWM.dismiss(); } catch (const ExceptionForCat<ErrorCategory::Interruption>&) { // The kLeaveUnlocked behavior suppresses this exception. @@ -178,11 +177,26 @@ Lock::GlobalLock::GlobalLock(OperationContext* opCtx, _opCtx->lockState()->setGlobalLockTakenInMode(acquiredLockMode); } +void Lock::GlobalLock::_takeGlobalLockOnly(LockMode lockMode, Date_t deadline) { + _opCtx->lockState()->lockGlobal(_opCtx, lockMode, deadline); +} + +void Lock::GlobalLock::_takeGlobalAndRSTLLocks(LockMode lockMode, Date_t deadline) { + _opCtx->lockState()->lock(_opCtx, resourceIdReplicationStateTransitionLock, MODE_IX, deadline); + auto unlockRSTL = makeGuard( + [this] { _opCtx->lockState()->unlock(resourceIdReplicationStateTransitionLock); }); + + _opCtx->lockState()->lockGlobal(_opCtx, lockMode, deadline); + + unlockRSTL.dismiss(); +} + Lock::GlobalLock::GlobalLock(GlobalLock&& otherLock) : _opCtx(otherLock._opCtx), _result(otherLock._result), _pbwm(std::move(otherLock._pbwm)), _interruptBehavior(otherLock._interruptBehavior), + _skipRSTLLock(otherLock._skipRSTLLock), _isOutermostLock(otherLock._isOutermostLock) { // Mark as moved so the destructor doesn't invalidate the newly-constructed lock. otherLock._result = LOCK_INVALID; diff --git a/src/mongo/db/concurrency/d_concurrency.h b/src/mongo/db/concurrency/d_concurrency.h index 187cb3d5a41..4ae7af8cdb9 100644 --- a/src/mongo/db/concurrency/d_concurrency.h +++ b/src/mongo/db/concurrency/d_concurrency.h @@ -221,7 +221,8 @@ public: GlobalLock(OperationContext* opCtx, LockMode lockMode, Date_t deadline, - InterruptBehavior behavior); + InterruptBehavior behavior, + bool skipRSTLLock = false); GlobalLock(GlobalLock&&); @@ -239,7 +240,7 @@ public: } _unlock(); } - if (lockResult == LOCK_OK || lockResult == LOCK_WAITING) { + if (!_skipRSTLLock && (lockResult == LOCK_OK || lockResult == LOCK_WAITING)) { _opCtx->lockState()->unlock(resourceIdReplicationStateTransitionLock); } } @@ -249,12 +250,19 @@ public: } private: + /** + * Constructor helper functions, to handle skipping or taking the RSTL lock. + */ + void _takeGlobalLockOnly(LockMode lockMode, Date_t deadline); + void _takeGlobalAndRSTLLocks(LockMode lockMode, Date_t deadline); + void _unlock(); OperationContext* const _opCtx; LockResult _result; ResourceLock _pbwm; InterruptBehavior _interruptBehavior; + bool _skipRSTLLock; const bool _isOutermostLock; }; diff --git a/src/mongo/db/views/durable_view_catalog.cpp b/src/mongo/db/views/durable_view_catalog.cpp index 25b0c2a28a6..838d883fc0d 100644 --- a/src/mongo/db/views/durable_view_catalog.cpp +++ b/src/mongo/db/views/durable_view_catalog.cpp @@ -86,8 +86,7 @@ void DurableViewCatalog::onSystemViewsCollectionDrop(OperationContext* opCtx, if (db) { // If the 'system.views' collection is dropped, we need to clear the in-memory state of the // view catalog. - ViewCatalog* viewCatalog = ViewCatalog::get(db); - viewCatalog->clear(); + ViewCatalog::get(db)->clear(); } } diff --git a/src/mongo/db/views/view_catalog.cpp b/src/mongo/db/views/view_catalog.cpp index d816f520460..2ed8ac9ee17 100644 --- a/src/mongo/db/views/view_catalog.cpp +++ b/src/mongo/db/views/view_catalog.cpp @@ -60,7 +60,7 @@ namespace mongo { namespace { -auto getViewCatalog = Database::declareDecoration<std::unique_ptr<ViewCatalog>>(); +auto getViewCatalog = Database::declareDecoration<std::shared_ptr<ViewCatalog>>(); StatusWith<std::unique_ptr<CollatorInterface>> parseCollator(OperationContext* opCtx, BSONObj collationSpec) { @@ -73,6 +73,10 @@ StatusWith<std::unique_ptr<CollatorInterface>> parseCollator(OperationContext* o } } // namespace +std::shared_ptr<ViewCatalog> ViewCatalog::getShared(const Database* db) { + return getViewCatalog(db); +} + ViewCatalog* ViewCatalog::get(const Database* db) { return getViewCatalog(db).get(); } diff --git a/src/mongo/db/views/view_catalog.h b/src/mongo/db/views/view_catalog.h index 44103a0ed24..ecfff9de990 100644 --- a/src/mongo/db/views/view_catalog.h +++ b/src/mongo/db/views/view_catalog.h @@ -67,6 +67,12 @@ public: using ViewMap = StringMap<std::shared_ptr<ViewDefinition>>; using ViewIteratorCallback = std::function<void(const ViewDefinition& view)>; + /** + * This getter should only be used when not holding a database lock. Otherwise the regular get() + * is appropriate and safe. + */ + static std::shared_ptr<ViewCatalog> getShared(const Database* db); + static ViewCatalog* get(const Database* db); static void set(Database* db, std::unique_ptr<ViewCatalog> catalog); |