diff options
author | Nathan Myers <ncm@cantrip.org> | 2017-08-18 13:42:27 -0400 |
---|---|---|
committer | Nathan Myers <ncm@cantrip.org> | 2017-08-23 23:35:06 -0400 |
commit | 200d56c6eec8acdc6cddf0541f31c60f4ed2e760 (patch) | |
tree | c90fcee8cd991ff171f3e1f9c83b5325e131d2d4 /src | |
parent | 655d02968973aec47b63aa750dadd10386adb3c8 (diff) | |
download | mongo-200d56c6eec8acdc6cddf0541f31c60f4ed2e760.tar.gz |
SERVER-30748 WithLock to replace _inlock
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/s/balancer/migration_manager.cpp | 26 | ||||
-rw-r--r-- | src/mongo/db/s/balancer/migration_manager.h | 17 | ||||
-rw-r--r-- | src/mongo/db/s/collection_range_deleter.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/s/metadata_manager.cpp | 70 | ||||
-rw-r--r-- | src/mongo/db/s/metadata_manager.h | 29 | ||||
-rw-r--r-- | src/mongo/db/s/migration_destination_manager.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/s/migration_destination_manager.h | 5 | ||||
-rw-r--r-- | src/mongo/util/concurrency/with_lock.h | 98 |
8 files changed, 179 insertions, 75 deletions
diff --git a/src/mongo/db/s/balancer/migration_manager.cpp b/src/mongo/db/s/balancer/migration_manager.cpp index 9552c959323..cd2d1baa30e 100644 --- a/src/mongo/db/s/balancer/migration_manager.cpp +++ b/src/mongo/db/s/balancer/migration_manager.cpp @@ -399,7 +399,7 @@ void MigrationManager::interruptAndDisableMigrations() { } } - _checkDrained_inlock(); + _checkDrained(lock); } void MigrationManager::drainActiveMigrations() { @@ -470,14 +470,15 @@ shared_ptr<Notification<RemoteCommandResponse>> MigrationManager::_schedule( auto retVal = migration.completionNotification; - _schedule_inlock(opCtx, fromHostStatus.getValue(), std::move(migration)); + _schedule(lock, opCtx, fromHostStatus.getValue(), std::move(migration)); return retVal; } -void MigrationManager::_schedule_inlock(OperationContext* opCtx, - const HostAndPort& targetHost, - Migration migration) { +void MigrationManager::_schedule(WithLock lock, + OperationContext* opCtx, + const HostAndPort& targetHost, + Migration migration) { executor::TaskExecutor* const executor = Grid::get(opCtx)->getExecutorPool()->getFixedExecutor(); @@ -526,7 +527,7 @@ void MigrationManager::_schedule_inlock(OperationContext* opCtx, auto opCtx = cc().makeOperationContext(); stdx::lock_guard<stdx::mutex> lock(_mutex); - _complete_inlock(opCtx.get(), itMigration, args.response); + _complete(lock, opCtx.get(), itMigration, args.response); }); if (callbackHandleWithStatus.isOK()) { @@ -534,12 +535,13 @@ void MigrationManager::_schedule_inlock(OperationContext* opCtx, return; } - _complete_inlock(opCtx, itMigration, std::move(callbackHandleWithStatus.getStatus())); + _complete(lock, opCtx, itMigration, std::move(callbackHandleWithStatus.getStatus())); } -void MigrationManager::_complete_inlock(OperationContext* opCtx, - MigrationsList::iterator itMigration, - const RemoteCommandResponse& remoteCommandResponse) { +void MigrationManager::_complete(WithLock lock, + OperationContext* opCtx, + MigrationsList::iterator itMigration, + const RemoteCommandResponse& remoteCommandResponse) { const NamespaceString nss(itMigration->nss); // Make sure to signal the notification last, after the distributed lock is freed, so that we @@ -557,13 +559,13 @@ void MigrationManager::_complete_inlock(OperationContext* opCtx, Grid::get(opCtx)->catalogClient()->getDistLockManager()->unlock( opCtx, _lockSessionID, nss.ns()); _activeMigrations.erase(it); - _checkDrained_inlock(); + _checkDrained(lock); } notificationToSignal->set(remoteCommandResponse); } -void MigrationManager::_checkDrained_inlock() { +void MigrationManager::_checkDrained(WithLock) { if (_state == State::kEnabled || _state == State::kRecovering) { return; } diff --git a/src/mongo/db/s/balancer/migration_manager.h b/src/mongo/db/s/balancer/migration_manager.h index 26da3f057f7..a99b5696d80 100644 --- a/src/mongo/db/s/balancer/migration_manager.h +++ b/src/mongo/db/s/balancer/migration_manager.h @@ -44,6 +44,7 @@ #include "mongo/stdx/mutex.h" #include "mongo/stdx/unordered_map.h" #include "mongo/util/concurrency/notification.h" +#include "mongo/util/concurrency/with_lock.h" namespace mongo { @@ -194,9 +195,10 @@ private: * The distributed lock is acquired before scheduling the first migration for the collection and * is only released when all active migrations on the collection have finished. */ - void _schedule_inlock(OperationContext* opCtx, - const HostAndPort& targetHost, - Migration migration); + void _schedule(WithLock, + OperationContext* opCtx, + const HostAndPort& targetHost, + Migration migration); /** * Used internally for migrations scheduled with the distributed lock acquired by the config @@ -204,15 +206,16 @@ private: * passed iterator and if this is the last migration for the collection will free the collection * distributed lock. */ - void _complete_inlock(OperationContext* opCtx, - MigrationsList::iterator itMigration, - const executor::RemoteCommandResponse& remoteCommandResponse); + void _complete(WithLock, + OperationContext* opCtx, + MigrationsList::iterator itMigration, + const executor::RemoteCommandResponse& remoteCommandResponse); /** * If the state of the migration manager is kStopping, checks whether there are any outstanding * scheduled requests and if there aren't any signals the class condition variable. */ - void _checkDrained_inlock(); + void _checkDrained(WithLock); /** * Blocking call, which waits for the migration manager to leave the recovering state (if it is diff --git a/src/mongo/db/s/collection_range_deleter.cpp b/src/mongo/db/s/collection_range_deleter.cpp index cafc540ebb3..2e17d047d5d 100644 --- a/src/mongo/db/s/collection_range_deleter.cpp +++ b/src/mongo/db/s/collection_range_deleter.cpp @@ -105,7 +105,7 @@ auto CollectionRangeDeleter::cleanUpNextRange(OperationContext* opCtx, << nss.ns(); } stdx::lock_guard<stdx::mutex> lk(css->_metadataManager->_managerLock); - css->_metadataManager->_clearAllCleanups(); + css->_metadataManager->_clearAllCleanups(lk); return boost::none; } if (!forTestOnly && scopedCollectionMetadata->getCollVersion().epoch() != epoch) { @@ -160,6 +160,7 @@ auto CollectionRangeDeleter::cleanUpNextRange(OperationContext* opCtx, } catch (DBException const& e) { stdx::lock_guard<stdx::mutex> scopedLock(css->_metadataManager->_managerLock); css->_metadataManager->_clearAllCleanups( + scopedLock, {e.code(), str::stream() << "cannot push startRangeDeletion record to Op Log," " abandoning scheduled range deletions: " << e.what()}); diff --git a/src/mongo/db/s/metadata_manager.cpp b/src/mongo/db/s/metadata_manager.cpp index d975a785e4e..02a0b57beed 100644 --- a/src/mongo/db/s/metadata_manager.cpp +++ b/src/mongo/db/s/metadata_manager.cpp @@ -126,18 +126,19 @@ MetadataManager::MetadataManager(ServiceContext* sc, NamespaceString nss, TaskEx MetadataManager::~MetadataManager() { stdx::lock_guard<stdx::mutex> scopedLock(_managerLock); - _clearAllCleanups(); + _clearAllCleanups(scopedLock); auto metadata = std::move(_metadata); } -void MetadataManager::_clearAllCleanups() { +void MetadataManager::_clearAllCleanups(WithLock lock) { _clearAllCleanups( + lock, {ErrorCodes::InterruptedDueToReplStateChange, str::stream() << "Range deletions in " << _nss.ns() << " abandoned because collection was dropped or became unsharded"}); } -void MetadataManager::_clearAllCleanups(Status status) { +void MetadataManager::_clearAllCleanups(WithLock, Status status) { for (auto& metadata : _metadata) { std::ignore = _rangesToClean.add(std::move(metadata->_tracker.orphans)); } @@ -147,7 +148,7 @@ void MetadataManager::_clearAllCleanups(Status status) { ScopedCollectionMetadata MetadataManager::getActiveMetadata(std::shared_ptr<MetadataManager> self) { stdx::lock_guard<stdx::mutex> scopedLock(_managerLock); if (!_metadata.empty()) { - return ScopedCollectionMetadata(std::move(self), _metadata.back()); + return ScopedCollectionMetadata(scopedLock, std::move(self), _metadata.back()); } return ScopedCollectionMetadata(); } @@ -175,7 +176,7 @@ void MetadataManager::refreshActiveMetadata(std::unique_ptr<CollectionMetadata> << " as no longer sharded"; _receivingChunks.clear(); - _clearAllCleanups(); + _clearAllCleanups(scopedLock); _metadata.clear(); return; } @@ -192,7 +193,7 @@ void MetadataManager::refreshActiveMetadata(std::unique_ptr<CollectionMetadata> invariant(_receivingChunks.empty()); invariant(_rangesToClean.isEmpty()); - _setActiveMetadata_inlock(std::move(remoteMetadata)); + _setActiveMetadata(scopedLock, std::move(remoteMetadata)); return; } @@ -206,8 +207,8 @@ void MetadataManager::refreshActiveMetadata(std::unique_ptr<CollectionMetadata> << " due to epoch change"; _receivingChunks.clear(); - _setActiveMetadata_inlock(std::move(remoteMetadata)); - _clearAllCleanups(); + _setActiveMetadata(scopedLock, std::move(remoteMetadata)); + _clearAllCleanups(scopedLock); return; } @@ -240,16 +241,17 @@ void MetadataManager::refreshActiveMetadata(std::unique_ptr<CollectionMetadata> it = _receivingChunks.begin(); } - _setActiveMetadata_inlock(std::move(remoteMetadata)); + _setActiveMetadata(scopedLock, std::move(remoteMetadata)); } -void MetadataManager::_setActiveMetadata_inlock(std::unique_ptr<CollectionMetadata> newMetadata) { +void MetadataManager::_setActiveMetadata(WithLock lock, + std::unique_ptr<CollectionMetadata> newMetadata) { invariant(newMetadata); _metadata.push_back(std::move(newMetadata)); - _retireExpiredMetadata(); + _retireExpiredMetadata(lock); } -void MetadataManager::_retireExpiredMetadata() { +void MetadataManager::_retireExpiredMetadata(WithLock lock) { if (_metadata.empty()) { return; // The collection was dropped, or went unsharded, before the query was cleaned up. } @@ -260,7 +262,7 @@ void MetadataManager::_retireExpiredMetadata() { << " range(s) finished; scheduling ranges for deletion"; // It is safe to push orphan ranges from _metadata.back(), even though new queries might // start any time, because any request to delete a range it maps is rejected. - _pushListToClean(std::move(_metadata.front()->_tracker.orphans)); + _pushListToClean(lock, std::move(_metadata.front()->_tracker.orphans)); } if (&_metadata.front() == &_metadata.back()) break; // do not pop the active chunk mapping! @@ -269,8 +271,8 @@ void MetadataManager::_retireExpiredMetadata() { // ScopedCollectionMetadata members -// call with MetadataManager locked -ScopedCollectionMetadata::ScopedCollectionMetadata(std::shared_ptr<MetadataManager> manager, +ScopedCollectionMetadata::ScopedCollectionMetadata(WithLock, + std::shared_ptr<MetadataManager> manager, std::shared_ptr<CollectionMetadata> metadata) : _metadata(std::move(metadata)), _manager(std::move(manager)) { invariant(_metadata); @@ -318,7 +320,7 @@ void ScopedCollectionMetadata::_clear() { // zero, some go to zero but can't be expired yet.) Note that new instances of // ScopedCollectionMetadata may get attached to _metadata.back(), so its usage count can // increase from zero, unlike other reference counts. - _manager->_retireExpiredMetadata(); + _manager->_retireExpiredMetadata(managerLock); } _metadata.reset(); _manager.reset(); @@ -415,17 +417,17 @@ void scheduleCleanup(executor::TaskExecutor* executor, } // namespace -auto MetadataManager::_pushRangeToClean(ChunkRange const& range, Date_t when) +auto MetadataManager::_pushRangeToClean(WithLock lock, ChunkRange const& range, Date_t when) -> CleanupNotification { std::list<Deletion> ranges; auto ownedRange = ChunkRange{range.getMin().getOwned(), range.getMax().getOwned()}; ranges.emplace_back(Deletion{std::move(ownedRange), when}); auto& notifn = ranges.back().notification; - _pushListToClean(std::move(ranges)); + _pushListToClean(lock, std::move(ranges)); return notifn; } -void MetadataManager::_pushListToClean(std::list<Deletion> ranges) { +void MetadataManager::_pushListToClean(WithLock, std::list<Deletion> ranges) { auto when = _rangesToClean.add(std::move(ranges)); if (when) { auto epoch = _metadata.back()->getCollVersion().epoch(); @@ -434,7 +436,7 @@ void MetadataManager::_pushListToClean(std::list<Deletion> ranges) { invariant(ranges.empty()); } -void MetadataManager::_addToReceiving(ChunkRange const& range) { +void MetadataManager::_addToReceiving(WithLock, ChunkRange const& range) { _receivingChunks.insert( std::make_pair(range.getMin().getOwned(), CachedChunkInfo(range.getMax().getOwned(), ChunkVersion::IGNORED()))); @@ -444,17 +446,17 @@ auto MetadataManager::beginReceive(ChunkRange const& range) -> CleanupNotificati stdx::unique_lock<stdx::mutex> scopedLock(_managerLock); invariant(!_metadata.empty()); - if (_overlapsInUseChunk(range)) { + if (_overlapsInUseChunk(scopedLock, range)) { return Status{ErrorCodes::RangeOverlapConflict, "Documents in target range may still be in use on the destination shard."}; } - _addToReceiving(range); + _addToReceiving(scopedLock, range); log() << "Scheduling deletion of any documents in " << _nss.ns() << " range " << redact(range.toString()) << " before migrating in a chunk covering the range"; - return _pushRangeToClean(range, Date_t{}); + return _pushRangeToClean(scopedLock, range, Date_t{}); } -void MetadataManager::_removeFromReceiving(ChunkRange const& range) { +void MetadataManager::_removeFromReceiving(WithLock, ChunkRange const& range) { auto it = _receivingChunks.find(range.getMin()); invariant(it != _receivingChunks.end()); _receivingChunks.erase(it); @@ -469,9 +471,9 @@ void MetadataManager::forgetReceive(ChunkRange const& range) { log() << "Abandoning in-migration of " << _nss.ns() << " range " << range << "; scheduling deletion of any documents already copied"; - invariant(!_overlapsInUseChunk(range)); - _removeFromReceiving(range); - _pushRangeToClean(range, Date_t{}).abandon(); + invariant(!_overlapsInUseChunk(scopedLock, range)); + _removeFromReceiving(scopedLock, range); + _pushRangeToClean(scopedLock, range, Date_t{}).abandon(); } auto MetadataManager::cleanUpRange(ChunkRange const& range, Date_t whenToDelete) @@ -492,11 +494,11 @@ auto MetadataManager::cleanUpRange(ChunkRange const& range, Date_t whenToDelete) } StringData whenStr = (whenToDelete == Date_t{}) ? "immediate"_sd : "deferred"_sd; - if (!_overlapsInUseChunk(range)) { + if (!_overlapsInUseChunk(scopedLock, range)) { // No running queries can depend on it, so queue it for deletion immediately. log() << "Scheduling " << whenStr << " deletion of " << _nss.ns() << " range " << redact(range.toString()); - return _pushRangeToClean(range, whenToDelete); + return _pushRangeToClean(scopedLock, range, whenToDelete); } auto ownedRange = ChunkRange{range.getMin().getOwned(), range.getMax().getOwned()}; activeMetadata->_tracker.orphans.emplace_back(Deletion{std::move(ownedRange), whenToDelete}); @@ -517,13 +519,13 @@ auto MetadataManager::overlappingMetadata(std::shared_ptr<MetadataManager> const auto it = _metadata.crbegin(); // start with the current active chunk mapping if ((*it)->rangeOverlapsChunk(range)) { // We ignore the refcount of the active mapping; effectively, we assume it is in use. - result.push_back(ScopedCollectionMetadata(self, *it)); + result.push_back(ScopedCollectionMetadata(scopedLock, self, *it)); } ++it; // step to snapshots for (auto end = _metadata.crend(); it != end; ++it) { // We want all the overlapping snapshot mappings still possibly in use by a query. if ((*it)->_tracker.usageCounter > 0 && (*it)->rangeOverlapsChunk(range)) { - result.push_back(ScopedCollectionMetadata(self, *it)); + result.push_back(ScopedCollectionMetadata(scopedLock, self, *it)); } } return result; @@ -546,14 +548,14 @@ size_t MetadataManager::numberOfRangesToClean() { auto MetadataManager::trackOrphanedDataCleanup(ChunkRange const& range) -> boost::optional<CleanupNotification> { stdx::unique_lock<stdx::mutex> scopedLock(_managerLock); - auto overlaps = _overlapsInUseCleanups(range); + auto overlaps = _overlapsInUseCleanups(scopedLock, range); if (overlaps) { return overlaps; } return _rangesToClean.overlaps(range); } -bool MetadataManager::_overlapsInUseChunk(ChunkRange const& range) { +bool MetadataManager::_overlapsInUseChunk(WithLock, ChunkRange const& range) { invariant(!_metadata.empty()); for (auto it = _metadata.begin(), end = --_metadata.end(); it != end; ++it) { if (((*it)->_tracker.usageCounter != 0) && (*it)->rangeOverlapsChunk(range)) { @@ -566,7 +568,7 @@ bool MetadataManager::_overlapsInUseChunk(ChunkRange const& range) { return false; } -auto MetadataManager::_overlapsInUseCleanups(ChunkRange const& range) +auto MetadataManager::_overlapsInUseCleanups(WithLock, ChunkRange const& range) -> boost::optional<CleanupNotification> { invariant(!_metadata.empty()); diff --git a/src/mongo/db/s/metadata_manager.h b/src/mongo/db/s/metadata_manager.h index 48cd08df125..42210d59154 100644 --- a/src/mongo/db/s/metadata_manager.h +++ b/src/mongo/db/s/metadata_manager.h @@ -41,6 +41,7 @@ #include "mongo/s/catalog/type_chunk.h" #include "mongo/stdx/memory.h" #include "mongo/util/concurrency/notification.h" +#include "mongo/util/concurrency/with_lock.h" namespace mongo { @@ -141,65 +142,64 @@ public: boost::optional<KeyRange> getNextOrphanRange(BSONObj const& from); private: - // All of the following functions must be called while holding _managerLock. - /** * Cancels all scheduled deletions of orphan ranges, notifying listeners with specified status. */ - void _clearAllCleanups(Status); + void _clearAllCleanups(WithLock, Status); /** * Cancels all scheduled deletions of orphan ranges, notifying listeners with status * InterruptedDueToReplStateChange. */ - void _clearAllCleanups(); + void _clearAllCleanups(WithLock); /** * Retires any metadata that has fallen out of use, and pushes any orphan ranges found in them * to the list of ranges actively being cleaned up. */ - void _retireExpiredMetadata(); + void _retireExpiredMetadata(WithLock); /** * Pushes current set of chunks, if any, to _metadataInUse, replaces it with newMetadata. */ - void _setActiveMetadata_inlock(std::unique_ptr<CollectionMetadata> newMetadata); + void _setActiveMetadata(WithLock, std::unique_ptr<CollectionMetadata> newMetadata); /** * Returns true if the specified range overlaps any chunk that might be currently in use by a * running query. */ - bool _overlapsInUseChunk(ChunkRange const& range); + bool _overlapsInUseChunk(WithLock, ChunkRange const& range); /** * Returns a notification if any range (possibly) still in use, but scheduled for cleanup, * overlaps the argument range. */ - auto _overlapsInUseCleanups(ChunkRange const& range) -> boost::optional<CleanupNotification>; + auto _overlapsInUseCleanups(WithLock, ChunkRange const& range) + -> boost::optional<CleanupNotification>; /** * Copies the argument range to the list of ranges scheduled for immediate deletion, and * schedules a a background task to perform the work. */ - auto _pushRangeToClean(ChunkRange const& range, Date_t when) -> CleanupNotification; + auto _pushRangeToClean(WithLock, ChunkRange const& range, Date_t when) -> CleanupNotification; /** * Splices the argument list elements to the list of ranges scheduled for immediate deletion, * and schedules a a background task to perform the work. */ - void _pushListToClean(std::list<Deletion> range); + void _pushListToClean(WithLock, std::list<Deletion> range); /** * Adds a range from the receiving map, so getNextOrphanRange will skip ranges migrating in. */ - void _addToReceiving(ChunkRange const& range); + void _addToReceiving(WithLock, ChunkRange const& range); /** * Removes a range from the receiving map after a migration failure. The range must * exactly match an element of _receivingChunks. */ - void _removeFromReceiving(ChunkRange const& range); + void _removeFromReceiving(WithLock, ChunkRange const& range); // data members @@ -288,13 +288,12 @@ private: * * Must be called with manager->_managerLock held. Arguments must be non-null. */ - ScopedCollectionMetadata(std::shared_ptr<MetadataManager> manager, + ScopedCollectionMetadata(WithLock, + std::shared_ptr<MetadataManager> manager, std::shared_ptr<CollectionMetadata> metadata); /** * Disconnect from the CollectionMetadata, possibly triggering GC of unused CollectionMetadata. - * - * Must be called with manager->_managerLock held. */ void _clear(); diff --git a/src/mongo/db/s/migration_destination_manager.cpp b/src/mongo/db/s/migration_destination_manager.cpp index d1e9b919f96..ca2c736a6b1 100644 --- a/src/mongo/db/s/migration_destination_manager.cpp +++ b/src/mongo/db/s/migration_destination_manager.cpp @@ -244,10 +244,10 @@ void MigrationDestinationManager::setStateFailWarn(std::string msg) { bool MigrationDestinationManager::isActive() const { stdx::lock_guard<stdx::mutex> lk(_mutex); - return _isActive_inlock(); + return _isActive(lk); } -bool MigrationDestinationManager::_isActive_inlock() const { +bool MigrationDestinationManager::_isActive(WithLock) const { return _sessionId.is_initialized(); } @@ -283,7 +283,7 @@ void MigrationDestinationManager::report(BSONObjBuilder& b) { BSONObj MigrationDestinationManager::getMigrationStatusReport() { stdx::lock_guard<stdx::mutex> lk(_mutex); - if (_isActive_inlock()) { + if (_isActive(lk)) { return migrationutil::makeMigrationStatusDocument( _nss, _fromShard, _toShard, false, _min, _max); } else { diff --git a/src/mongo/db/s/migration_destination_manager.h b/src/mongo/db/s/migration_destination_manager.h index e88ad70f61f..610f70196c1 100644 --- a/src/mongo/db/s/migration_destination_manager.h +++ b/src/mongo/db/s/migration_destination_manager.h @@ -43,6 +43,7 @@ #include "mongo/stdx/condition_variable.h" #include "mongo/stdx/mutex.h" #include "mongo/stdx/thread.h" +#include "mongo/util/concurrency/with_lock.h" #include "mongo/util/timer.h" namespace mongo { @@ -174,10 +175,8 @@ private: /** * Checks whether the MigrationDestinationManager is currently handling a migration by checking * that the migration "_sessionId" is initialized. - * - * Expects the caller to have the class _mutex locked! */ - bool _isActive_inlock() const; + bool _isActive(WithLock) const; // Mutex to guard all fields mutable stdx::mutex _mutex; diff --git a/src/mongo/util/concurrency/with_lock.h b/src/mongo/util/concurrency/with_lock.h new file mode 100644 index 00000000000..d26c675e293 --- /dev/null +++ b/src/mongo/util/concurrency/with_lock.h @@ -0,0 +1,98 @@ +/** Copyright 2017 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 <http://www.gnu.org/licenses/>. + * + * 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. + */ + +#pragma once + +#include "mongo/stdx/mutex.h" +#include "mongo/util/invariant.h" + +#include <utility> + +namespace mongo { + +/** + * WithLock is an attestion to pass as an argument to functions that must be called only while + * holding a lock, as a rigorous alternative to an unchecked naming convention and/or stern + * comments. It helps prevent a common usage error. + * + * It may be used to modernize code from (something like) this + * + * // Member _mutex MUST be held when calling this: + * void _clobber_inlock(OperationContext* opCtx) { + * _stuff = makeStuff(opCtx); + * } + * + * into + * + * void _clobber(WithLock, OperationContext* opCtx) { + * _stuff = makeStuff(opCtx); + * } + * + * A call to such a function looks like this: + * + * stdx::lock_guard<stdx::mutex> lk(_mutex); + * _clobber(lk, opCtx); // instead of _clobber_inlock(opCtx) + * + * Note that the formal argument need not (and should not) be named unless it is needed to pass + * the attestation along to another function: + * + * void _clobber(WithLock lock, OperationContext* opCtx) { + * _really_clobber(lock, opCtx); + * } + * + */ +struct WithLock { + template <typename Mutex> + WithLock(stdx::lock_guard<Mutex> const&) noexcept {} + + template <typename Mutex> + WithLock(stdx::unique_lock<Mutex> const& lock) noexcept { + invariant(lock.owns_lock()); + } + + // Pass by value is OK. + WithLock(WithLock const&) noexcept {} + WithLock(WithLock&&) noexcept {} + + WithLock() = delete; + + // No assigning WithLocks. + void operator=(WithLock const&) = delete; + void operator=(WithLock&&) = delete; + + // No accidentally moving a unique_lock<> in. + template <typename Mutex> + WithLock(stdx::unique_lock<Mutex>&&) = delete; +}; + +} // namespace mongo + +namespace std { +// No moving a WithLock: +template <> +mongo::WithLock&& move<mongo::WithLock>(mongo::WithLock&&) = delete; +} // namespace std |