From de55cd2ac227dcc8cae2fd021abc291e86b2abb2 Mon Sep 17 00:00:00 2001 From: Kaloian Manassiev Date: Wed, 17 May 2023 20:08:16 +0000 Subject: SERVER-77224 Rename lock_state.h/.cpp to locker_impl.h/.cpp to match the class name --- buildscripts/gdb/mongo.py | 2 - buildscripts/gdb/mongo_lock.py | 2 - src/mongo/crypto/SConscript | 1 + src/mongo/db/SConscript | 2 +- src/mongo/db/auth/SConscript | 1 + src/mongo/db/catalog/capped_collection_test.cpp | 4 +- src/mongo/db/catalog/collection_catalog_bm.cpp | 2 +- src/mongo/db/catalog/rename_collection.cpp | 4 +- src/mongo/db/catalog_raii_test.cpp | 2 +- src/mongo/db/commands/SConscript | 1 + src/mongo/db/commands/current_op.cpp | 3 - src/mongo/db/commands/lock_info.cpp | 6 - src/mongo/db/concurrency/SConscript | 25 +- src/mongo/db/concurrency/d_concurrency.cpp | 23 +- src/mongo/db/concurrency/d_concurrency.h | 21 +- src/mongo/db/concurrency/lock_manager.h | 2 - src/mongo/db/concurrency/lock_manager_defs.cpp | 2 +- src/mongo/db/concurrency/lock_manager_test_help.h | 2 +- src/mongo/db/concurrency/lock_state.cpp | 1245 ----------------- src/mongo/db/concurrency/lock_state.h | 449 ------- src/mongo/db/concurrency/lock_state_test.cpp | 1392 -------------------- src/mongo/db/concurrency/locker.cpp | 38 + src/mongo/db/concurrency/locker.h | 4 +- src/mongo/db/concurrency/locker_impl.cpp | 1241 +++++++++++++++++ src/mongo/db/concurrency/locker_impl.h | 439 ++++++ src/mongo/db/concurrency/locker_impl_test.cpp | 1392 ++++++++++++++++++++ src/mongo/db/curop.h | 2 - src/mongo/db/db_raii_multi_collection_test.cpp | 2 +- src/mongo/db/db_raii_test.cpp | 2 +- src/mongo/db/exec/sbe/abt/sbe_abt_diff_test.cpp | 2 +- src/mongo/db/index/SConscript | 2 - src/mongo/db/introspect.cpp | 2 +- src/mongo/db/mongod_main.cpp | 2 +- src/mongo/db/operation_context.h | 6 +- .../shardsvr_process_interface_test.cpp | 2 +- src/mongo/db/process_health/SConscript | 1 + src/mongo/db/query/SConscript | 14 +- src/mongo/db/query/ce/maxdiff_histogram_test.cpp | 2 +- src/mongo/db/query/optimizer/SConscript | 1 + src/mongo/db/repl/apply_ops.cpp | 1 - src/mongo/db/repl/replication_coordinator_impl.cpp | 5 +- .../db/repl/replication_coordinator_impl_test.cpp | 6 +- src/mongo/db/service_entry_point_mongod.cpp | 6 +- src/mongo/db/shard_role.cpp | 5 - src/mongo/db/shard_role.h | 6 +- src/mongo/db/storage/SConscript | 2 +- .../kv/kv_drop_pending_ident_reaper_test.cpp | 5 - src/mongo/db/storage/storage_engine_init.cpp | 8 +- .../wiredtiger/wiredtiger_oplog_manager.cpp | 9 +- .../db/transaction/transaction_participant.cpp | 3 +- .../transaction/transaction_participant_test.cpp | 1 - src/mongo/embedded/embedded.cpp | 5 +- src/mongo/executor/SConscript | 1 + src/mongo/s/SConscript | 1 + src/mongo/shell/SConscript | 1 + src/mongo/tools/mongobridge_tool/SConscript | 13 +- src/mongo/transport/SConscript | 2 + src/mongo/unittest/SConscript | 3 +- src/mongo/util/concurrency/SConscript | 14 +- .../util/concurrency/priority_ticketholder.cpp | 5 +- .../concurrency/priority_ticketholder_test.cpp | 1 - .../util/concurrency/semaphore_ticketholder.cpp | 7 +- src/mongo/util/concurrency/ticketholder.cpp | 7 +- 63 files changed, 3215 insertions(+), 3247 deletions(-) delete mode 100644 src/mongo/db/concurrency/lock_state.cpp delete mode 100644 src/mongo/db/concurrency/lock_state.h delete mode 100644 src/mongo/db/concurrency/lock_state_test.cpp create mode 100644 src/mongo/db/concurrency/locker.cpp create mode 100644 src/mongo/db/concurrency/locker_impl.cpp create mode 100644 src/mongo/db/concurrency/locker_impl.h create mode 100644 src/mongo/db/concurrency/locker_impl_test.cpp diff --git a/buildscripts/gdb/mongo.py b/buildscripts/gdb/mongo.py index c7ea555513e..8f20f1caa85 100644 --- a/buildscripts/gdb/mongo.py +++ b/buildscripts/gdb/mongo.py @@ -584,8 +584,6 @@ class MongoDBDumpLocks(gdb.Command): try: # Call into mongod, and dump the state of lock manager # Note that output will go to mongod's standard output, not the debugger output window - # Do not call mongo::getGlobalLockManager() due to the compiler optimizing this function in a very weird way - # See SERVER-72816 for more context gdb.execute( "call mongo::LockManager::get((mongo::ServiceContext*) mongo::getGlobalServiceContext())->dump()", from_tty=False, to_string=False) diff --git a/buildscripts/gdb/mongo_lock.py b/buildscripts/gdb/mongo_lock.py index d05acaf6779..a7f7e271926 100644 --- a/buildscripts/gdb/mongo_lock.py +++ b/buildscripts/gdb/mongo_lock.py @@ -325,8 +325,6 @@ def find_lock_manager_holders(graph, thread_dict, show): locker_ptr_type = gdb.lookup_type("mongo::LockerImpl").pointer() - # Do not call mongo::getGlobalLockManager() due to the compiler optimizing this function in a very weird way - # See SERVER-72816 for more context lock_head = gdb.parse_and_eval( "mongo::LockManager::get((mongo::ServiceContext*) mongo::getGlobalServiceContext())->_getBucket(resId)->findOrInsert(resId)" ) diff --git a/src/mongo/crypto/SConscript b/src/mongo/crypto/SConscript index 67aadbffd76..3271c7b0b73 100644 --- a/src/mongo/crypto/SConscript +++ b/src/mongo/crypto/SConscript @@ -156,6 +156,7 @@ env.CppUnitTest( LIBDEPS=[ '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/base/secure_allocator', + '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/shell/kms_idl', '$BUILD_DIR/mongo/util/net/http_client_impl', '$BUILD_DIR/mongo/util/net/openssl_init' if ssl_provider == 'openssl' else [], diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index d0e8b323673..eaafde9af99 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -767,7 +767,7 @@ env.Library( ) env.Library( - target="metadata_consistency_types_idl", + target='metadata_consistency_types_idl', source=[ 'metadata_consistency_types.idl', ], diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 59b102026fd..ee465b1e438 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -489,6 +489,7 @@ env.Library( 'authz_session_external_state_mock.cpp', ], LIBDEPS=[ + '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/db/query_expressions', '$BUILD_DIR/mongo/db/service_context', '$BUILD_DIR/mongo/db/update/update_driver', diff --git a/src/mongo/db/catalog/capped_collection_test.cpp b/src/mongo/db/catalog/capped_collection_test.cpp index b95317f207d..3d23339c768 100644 --- a/src/mongo/db/catalog/capped_collection_test.cpp +++ b/src/mongo/db/catalog/capped_collection_test.cpp @@ -27,13 +27,11 @@ * it in the license file. */ -#include "mongo/platform/basic.h" - #include "mongo/db/catalog/capped_visibility.h" #include "mongo/db/catalog/catalog_test_fixture.h" #include "mongo/db/catalog/collection.h" #include "mongo/db/catalog/collection_write_path.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/db_raii.h" #include "mongo/db/record_id_helpers.h" #include "mongo/db/repl/replication_coordinator_mock.h" diff --git a/src/mongo/db/catalog/collection_catalog_bm.cpp b/src/mongo/db/catalog/collection_catalog_bm.cpp index 57898636095..0fd4bf9d9bc 100644 --- a/src/mongo/db/catalog/collection_catalog_bm.cpp +++ b/src/mongo/db/catalog/collection_catalog_bm.cpp @@ -32,7 +32,7 @@ #include "mongo/db/catalog/collection_catalog.h" #include "mongo/db/catalog/collection_mock.h" #include "mongo/db/concurrency/d_concurrency.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/operation_context.h" #include "mongo/db/service_context.h" #include "mongo/util/uuid.h" diff --git a/src/mongo/db/catalog/rename_collection.cpp b/src/mongo/db/catalog/rename_collection.cpp index c0f0f633423..8c171c2532f 100644 --- a/src/mongo/db/catalog/rename_collection.cpp +++ b/src/mongo/db/catalog/rename_collection.cpp @@ -42,7 +42,6 @@ #include "mongo/db/catalog/unique_collection_name.h" #include "mongo/db/client.h" #include "mongo/db/concurrency/exception_util.h" -#include "mongo/db/concurrency/lock_state.h" #include "mongo/db/curop.h" #include "mongo/db/db_raii.h" #include "mongo/db/index/index_descriptor.h" @@ -54,10 +53,9 @@ #include "mongo/db/ops/insert.h" #include "mongo/db/query/query_knobs_gen.h" #include "mongo/db/repl/replication_coordinator.h" -#include "mongo/db/s/database_sharding_state.h" -#include "mongo/db/s/operation_sharding_state.h" #include "mongo/db/server_options.h" #include "mongo/db/service_context.h" +#include "mongo/db/shard_role.h" #include "mongo/db/storage/storage_parameters_gen.h" #include "mongo/logv2/log.h" #include "mongo/util/fail_point.h" diff --git a/src/mongo/db/catalog_raii_test.cpp b/src/mongo/db/catalog_raii_test.cpp index f035cc6bd2e..158b8111163 100644 --- a/src/mongo/db/catalog_raii_test.cpp +++ b/src/mongo/db/catalog_raii_test.cpp @@ -30,7 +30,7 @@ #include "mongo/db/catalog/database_holder_mock.h" #include "mongo/db/catalog_raii.h" #include "mongo/db/client.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/service_context_test_fixture.h" #include "mongo/db/storage/recovery_unit_noop.h" #include "mongo/logv2/log.h" diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index a1b52019334..26ed44f60af 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -199,6 +199,7 @@ env.Library( '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/db/query/op_metrics', '$BUILD_DIR/mongo/db/storage/backup_cursor_hooks', + '$BUILD_DIR/mongo/util/background_job', 'fsync_locked', ], ) diff --git a/src/mongo/db/commands/current_op.cpp b/src/mongo/db/commands/current_op.cpp index dd888b36db8..913265cba27 100644 --- a/src/mongo/db/commands/current_op.cpp +++ b/src/mongo/db/commands/current_op.cpp @@ -27,8 +27,6 @@ * it in the license file. */ -#include "mongo/platform/basic.h" - #include "mongo/db/commands/current_op_common.h" #include "mongo/db/auth/action_type.h" @@ -39,7 +37,6 @@ #include "mongo/db/exec/document_value/document.h" #include "mongo/db/pipeline/aggregate_command_gen.h" #include "mongo/db/pipeline/aggregation_request_helper.h" -#include "mongo/db/stats/fill_locker_info.h" namespace mongo { diff --git a/src/mongo/db/commands/lock_info.cpp b/src/mongo/db/commands/lock_info.cpp index d7e77df6453..2f75ed1be36 100644 --- a/src/mongo/db/commands/lock_info.cpp +++ b/src/mongo/db/commands/lock_info.cpp @@ -27,16 +27,10 @@ * it in the license file. */ -#include "mongo/platform/basic.h" - -#include - #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/client.h" #include "mongo/db/commands.h" -#include "mongo/db/concurrency/lock_manager_defs.h" -#include "mongo/db/concurrency/lock_state.h" #include "mongo/db/operation_context.h" #include "mongo/db/service_context.h" diff --git a/src/mongo/db/concurrency/SConscript b/src/mongo/db/concurrency/SConscript index efd2d335cf2..872b28b8047 100644 --- a/src/mongo/db/concurrency/SConscript +++ b/src/mongo/db/concurrency/SConscript @@ -36,40 +36,30 @@ env.Library( ], ) -env.Library( - target='lock_manager_defs', - source=[ - 'lock_manager_defs.cpp', - ], - LIBDEPS=[ - '$BUILD_DIR/mongo/base', - '$BUILD_DIR/mongo/util/namespace_string_database_name_util', - ], -) - env.Library( target='lock_manager', source=[ 'd_concurrency.cpp', 'lock_manager.cpp', - 'lock_state.cpp', + 'lock_manager_defs.cpp', 'lock_stats.cpp', + 'locker.cpp', + 'locker_impl.cpp', 'replication_state_transition_lock_guard.cpp', 'resource_catalog.cpp', ], LIBDEPS=[ - '$BUILD_DIR/mongo/db/service_context', '$BUILD_DIR/mongo/db/storage/concurrency_adjustment_parameters', '$BUILD_DIR/mongo/db/storage/storage_engine_parameters', - '$BUILD_DIR/mongo/util/background_job', '$BUILD_DIR/mongo/util/concurrency/spin_lock', '$BUILD_DIR/mongo/util/concurrency/ticketholder', - '$BUILD_DIR/third_party/shim_boost', - 'lock_manager_defs', ], LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/db/concurrency/flow_control_ticketholder', '$BUILD_DIR/mongo/db/server_base', + '$BUILD_DIR/mongo/db/service_context', + '$BUILD_DIR/mongo/util/background_job', + '$BUILD_DIR/mongo/util/namespace_string_database_name_util', ], ) @@ -89,6 +79,7 @@ env.Benchmark( 'd_concurrency_bm.cpp', ], LIBDEPS=[ + '$BUILD_DIR/mongo/db/service_context', 'lock_manager', ], ) @@ -99,8 +90,8 @@ env.CppUnitTest( 'd_concurrency_test.cpp', 'fast_map_noalloc_test.cpp', 'lock_manager_test.cpp', - 'lock_state_test.cpp', 'lock_stats_test.cpp', + 'locker_impl_test.cpp', 'resource_catalog_test.cpp', ], LIBDEPS=[ diff --git a/src/mongo/db/concurrency/d_concurrency.cpp b/src/mongo/db/concurrency/d_concurrency.cpp index fe7daed7201..9fe447c15f0 100644 --- a/src/mongo/db/concurrency/d_concurrency.cpp +++ b/src/mongo/db/concurrency/d_concurrency.cpp @@ -29,11 +29,9 @@ #include "mongo/db/concurrency/d_concurrency.h" -#include #include #include -#include "mongo/db/concurrency/flow_control_ticketholder.h" #include "mongo/db/namespace_string.h" #include "mongo/db/service_context.h" #include "mongo/logv2/log.h" @@ -178,6 +176,27 @@ Lock::GlobalLock::GlobalLock(GlobalLock&& otherLock) otherLock._result = LOCK_INVALID; } +Lock::GlobalLock::~GlobalLock() { + // Preserve the original lock result which will be overridden by unlock(). + auto lockResult = _result; + auto* locker = _opCtx->lockState(); + + if (isLocked()) { + // Abandon our snapshot if destruction of the GlobalLock object results in actually + // unlocking the global lock. Recursive locking and the two-phase locking protocol may + // prevent lock release. + const bool willReleaseLock = _isOutermostLock && !locker->inAWriteUnitOfWork(); + if (willReleaseLock) { + _opCtx->recoveryUnit()->abandonSnapshot(); + } + _unlock(); + } + + if (!_skipRSTLLock && (lockResult == LOCK_OK || lockResult == LOCK_WAITING)) { + locker->unlock(resourceIdReplicationStateTransitionLock); + } +} + void Lock::GlobalLock::_unlock() { _opCtx->lockState()->unlockGlobal(); _result = LOCK_INVALID; diff --git a/src/mongo/db/concurrency/d_concurrency.h b/src/mongo/db/concurrency/d_concurrency.h index aacd3b6d724..9cf4aef7c7e 100644 --- a/src/mongo/db/concurrency/d_concurrency.h +++ b/src/mongo/db/concurrency/d_concurrency.h @@ -29,8 +29,6 @@ #pragma once -#include // For UINT_MAX - #include "mongo/db/concurrency/locker.h" #include "mongo/db/operation_context.h" #include "mongo/util/timer.h" @@ -246,24 +244,7 @@ public: GlobalLock(GlobalLock&&); - ~GlobalLock() { - // Preserve the original lock result which will be overridden by unlock(). - auto lockResult = _result; - if (isLocked()) { - // Abandon our snapshot if destruction of the GlobalLock object results in actually - // unlocking the global lock. Recursive locking and the two-phase locking protocol - // may prevent lock release. - const bool willReleaseLock = _isOutermostLock && - !(_opCtx->lockState() && _opCtx->lockState()->inAWriteUnitOfWork()); - if (willReleaseLock) { - _opCtx->recoveryUnit()->abandonSnapshot(); - } - _unlock(); - } - if (!_skipRSTLLock && (lockResult == LOCK_OK || lockResult == LOCK_WAITING)) { - _opCtx->lockState()->unlock(resourceIdReplicationStateTransitionLock); - } - } + ~GlobalLock(); bool isLocked() const { return _result == LOCK_OK; diff --git a/src/mongo/db/concurrency/lock_manager.h b/src/mongo/db/concurrency/lock_manager.h index 4b82349432c..18a20900332 100644 --- a/src/mongo/db/concurrency/lock_manager.h +++ b/src/mongo/db/concurrency/lock_manager.h @@ -63,8 +63,6 @@ public: * Retrieves the lock manager instance attached to this ServiceContext. * The lock manager is now a decoration on the service context and this is the accessor that * most callers should prefer outside of startup, lock internals, and debugger scripts. - * Using the ServiceContext and OperationContext versions where possible is preferable to - * getGlobalLockManager(). */ static LockManager* get(ServiceContext* service); static LockManager* get(ServiceContext& service); diff --git a/src/mongo/db/concurrency/lock_manager_defs.cpp b/src/mongo/db/concurrency/lock_manager_defs.cpp index 38f3976c0ea..655340a355d 100644 --- a/src/mongo/db/concurrency/lock_manager_defs.cpp +++ b/src/mongo/db/concurrency/lock_manager_defs.cpp @@ -27,7 +27,7 @@ * it in the license file. */ -#include "lock_manager_defs.h" +#include "mongo/db/concurrency/lock_manager_defs.h" namespace mongo { diff --git a/src/mongo/db/concurrency/lock_manager_test_help.h b/src/mongo/db/concurrency/lock_manager_test_help.h index 5eba9d33dbd..c7b9212dc03 100644 --- a/src/mongo/db/concurrency/lock_manager_test_help.h +++ b/src/mongo/db/concurrency/lock_manager_test_help.h @@ -30,7 +30,7 @@ #pragma once #include "mongo/db/concurrency/lock_manager.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" namespace mongo { diff --git a/src/mongo/db/concurrency/lock_state.cpp b/src/mongo/db/concurrency/lock_state.cpp deleted file mode 100644 index e53a2e955b7..00000000000 --- a/src/mongo/db/concurrency/lock_state.cpp +++ /dev/null @@ -1,1245 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * 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 - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side 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 Server Side 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/lock_state.h" - -#include - -#include "mongo/bson/bsonobjbuilder.h" -#include "mongo/bson/json.h" -#include "mongo/db/concurrency/flow_control_ticketholder.h" -#include "mongo/db/namespace_string.h" -#include "mongo/db/service_context.h" -#include "mongo/db/storage/flow_control.h" -#include "mongo/db/storage/ticketholder_manager.h" -#include "mongo/logv2/log.h" -#include "mongo/platform/compiler.h" -#include "mongo/stdx/new.h" -#include "mongo/util/background.h" -#include "mongo/util/concurrency/ticketholder.h" -#include "mongo/util/debug_util.h" -#include "mongo/util/fail_point.h" -#include "mongo/util/scopeguard.h" -#include "mongo/util/str.h" -#include "mongo/util/testing_proctor.h" - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault - -namespace mongo { - -MONGO_FAIL_POINT_DEFINE(failNonIntentLocksIfWaitNeeded); -MONGO_FAIL_POINT_DEFINE(enableTestOnlyFlagforRSTL); - -namespace { - -// Ignore data races in certain functions when running with TSAN. For performance reasons, -// diagnostic commands are expected to race with concurrent lock acquisitions while gathering -// statistics. -#if __has_feature(thread_sanitizer) -#define MONGO_TSAN_IGNORE __attribute__((no_sanitize("thread"))) -#else -#define MONGO_TSAN_IGNORE -#endif - -/** - * Tracks global (across all clients) lock acquisition statistics, partitioned into multiple - * buckets to minimize concurrent access conflicts. - * - * Each client has a LockerId that monotonically increases across all client instances. The - * LockerId % 8 is used to index into one of 8 LockStats instances. These LockStats objects must be - * atomically accessed, so maintaining 8 that are indexed by LockerId reduces client conflicts and - * improves concurrent write access. A reader, to collect global lock statics for reporting, will - * sum the results of all 8 disjoint 'buckets' of stats. - */ -class PartitionedInstanceWideLockStats { - PartitionedInstanceWideLockStats(const PartitionedInstanceWideLockStats&) = delete; - PartitionedInstanceWideLockStats& operator=(const PartitionedInstanceWideLockStats&) = delete; - -public: - PartitionedInstanceWideLockStats() {} - - void recordAcquisition(LockerId id, ResourceId resId, LockMode mode) { - _get(id).recordAcquisition(resId, mode); - } - - void recordWait(LockerId id, ResourceId resId, LockMode mode) { - _get(id).recordWait(resId, mode); - } - - void recordWaitTime(LockerId id, ResourceId resId, LockMode mode, uint64_t waitMicros) { - _get(id).recordWaitTime(resId, mode, waitMicros); - } - - void report(SingleThreadedLockStats* outStats) const { - for (int i = 0; i < NumPartitions; i++) { - outStats->append(_partitions[i].stats); - } - } - - void reset() { - for (int i = 0; i < NumPartitions; i++) { - _partitions[i].stats.reset(); - } - } - -private: - // This alignment is a best effort approach to ensure that each partition falls on a - // separate page/cache line in order to avoid false sharing. - struct alignas(stdx::hardware_destructive_interference_size) AlignedLockStats { - AtomicLockStats stats; - }; - - enum { NumPartitions = 8 }; - - - AtomicLockStats& _get(LockerId id) { - return _partitions[id % NumPartitions].stats; - } - - - AlignedLockStats _partitions[NumPartitions]; -}; - -// How often (in millis) to check for deadlock if a lock has not been granted for some time -const Milliseconds MaxWaitTime = Milliseconds(500); - -// Dispenses unique LockerId identifiers -AtomicWord idCounter(0); - -// Tracks lock statistics across all Locker instances. Distributes stats across multiple buckets -// indexed by LockerId in order to minimize concurrent access conflicts. -PartitionedInstanceWideLockStats globalStats; - -} // namespace - -bool LockerImpl::_shouldDelayUnlock(ResourceId resId, LockMode mode) const { - switch (resId.getType()) { - case RESOURCE_MUTEX: - return false; - - case RESOURCE_GLOBAL: - case RESOURCE_TENANT: - case RESOURCE_DATABASE: - case RESOURCE_COLLECTION: - case RESOURCE_METADATA: - break; - - default: - MONGO_UNREACHABLE; - } - - switch (mode) { - case MODE_X: - case MODE_IX: - return true; - - case MODE_IS: - case MODE_S: - return _sharedLocksShouldTwoPhaseLock; - - default: - MONGO_UNREACHABLE; - } -} - -bool LockerImpl::isW() const { - return getLockMode(resourceIdGlobal) == MODE_X; -} - -bool LockerImpl::isR() const { - return getLockMode(resourceIdGlobal) == MODE_S; -} - -bool LockerImpl::isLocked() const { - return getLockMode(resourceIdGlobal) != MODE_NONE; -} - -bool LockerImpl::isWriteLocked() const { - return isLockHeldForMode(resourceIdGlobal, MODE_IX); -} - -bool LockerImpl::isReadLocked() const { - return isLockHeldForMode(resourceIdGlobal, MODE_IS); -} - -bool LockerImpl::isRSTLExclusive() const { - return getLockMode(resourceIdReplicationStateTransitionLock) == MODE_X; -} - -bool LockerImpl::isRSTLLocked() const { - return getLockMode(resourceIdReplicationStateTransitionLock) != MODE_NONE; -} - -void LockerImpl::dump() const { - struct Entry { - ResourceId key; - LockRequest::Status status; - LockMode mode; - unsigned int recursiveCount; - unsigned int unlockPending; - - BSONObj toBSON() const { - BSONObjBuilder b; - b.append("key", key.toString()); - b.append("status", lockRequestStatusName(status)); - b.append("recursiveCount", static_cast(recursiveCount)); - b.append("unlockPending", static_cast(unlockPending)); - b.append("mode", modeName(mode)); - return b.obj(); - } - std::string toString() const { - return tojson(toBSON()); - } - }; - std::vector entries; - { - auto lg = stdx::lock_guard(_lock); - for (auto it = _requests.begin(); !it.finished(); it.next()) - entries.push_back( - {it.key(), it->status, it->mode, it->recursiveCount, it->unlockPending}); - } - LOGV2(20523, - "Locker id {id} status: {requests}", - "Locker status", - "id"_attr = _id, - "requests"_attr = entries); -} - -void LockerImpl::_dumpLockerAndLockManagerRequests() { - // Log the _requests that this locker holds. This will provide identifying information to cross - // reference with the LockManager dump below for extra information. - dump(); - - // Log the LockManager's lock information. Given the locker 'dump()' above, we should be able to - // easily cross reference to find the lock info matching this operation. The LockManager can - // safely access (under internal locks) the LockRequest data that the locker cannot. - BSONObjBuilder builder; - auto lockToClientMap = LockManager::getLockToClientMap(getGlobalServiceContext()); - getGlobalLockManager()->getLockInfoBSON(lockToClientMap, &builder); - auto lockInfo = builder.done(); - LOGV2_ERROR(5736000, "Operation ending while holding locks.", "LockInfo"_attr = lockInfo); -} - - -// -// CondVarLockGrantNotification -// - -CondVarLockGrantNotification::CondVarLockGrantNotification() { - clear(); -} - -void CondVarLockGrantNotification::clear() { - _result = LOCK_INVALID; -} - -LockResult CondVarLockGrantNotification::wait(Milliseconds timeout) { - stdx::unique_lock lock(_mutex); - return _cond.wait_for( - lock, timeout.toSystemDuration(), [this] { return _result != LOCK_INVALID; }) - ? _result - : LOCK_TIMEOUT; -} - -LockResult CondVarLockGrantNotification::wait(OperationContext* opCtx, Milliseconds timeout) { - invariant(opCtx); - stdx::unique_lock lock(_mutex); - if (opCtx->waitForConditionOrInterruptFor( - _cond, lock, timeout, [this] { return _result != LOCK_INVALID; })) { - // Because waitForConditionOrInterruptFor evaluates the predicate before checking for - // interrupt, it is possible that a killed operation can acquire a lock if the request is - // granted quickly. For that reason, it is necessary to check if the operation has been - // killed at least once before accepting the lock grant. - opCtx->checkForInterrupt(); - return _result; - } - return LOCK_TIMEOUT; -} - -void CondVarLockGrantNotification::notify(ResourceId resId, LockResult result) { - stdx::unique_lock lock(_mutex); - invariant(_result == LOCK_INVALID); - _result = result; - - _cond.notify_all(); -} - -// -// Locker -// - -LockerImpl::LockerImpl(ServiceContext* serviceCtx) - : _id(idCounter.addAndFetch(1)), - _wuowNestingLevel(0), - _threadId(stdx::this_thread::get_id()), - _ticketHolderManager(TicketHolderManager::get(serviceCtx)) {} - -stdx::thread::id LockerImpl::getThreadId() const { - return _threadId; -} - -void LockerImpl::updateThreadIdToCurrentThread() { - _threadId = stdx::this_thread::get_id(); -} - -void LockerImpl::unsetThreadId() { - _threadId = stdx::thread::id(); // Reset to represent a non-executing thread. -} - -LockerImpl::~LockerImpl() { - // Cannot delete the Locker while there are still outstanding requests, because the - // LockManager may attempt to access deleted memory. Besides it is probably incorrect - // to delete with unaccounted locks anyways. - invariant(!inAWriteUnitOfWork()); - invariant(_numResourcesToUnlockAtEndUnitOfWork == 0); - invariant(!_ticket || !_ticket->valid()); - - if (!_requests.empty()) { - _dumpLockerAndLockManagerRequests(); - } - invariant(_requests.empty()); - - invariant(_modeForTicket == MODE_NONE); -} - -Locker::ClientState LockerImpl::getClientState() const { - auto state = _clientState.load(); - if (state == kActiveReader && hasLockPending()) - state = kQueuedReader; - if (state == kActiveWriter && hasLockPending()) - state = kQueuedWriter; - - return state; -} - -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; - - if (_acquireTicket(opCtx, _modeForTicket, Date_t::now())) { - return; - } - - do { - for (auto it = _requests.begin(); it; it.next()) { - invariant(it->mode == LockMode::MODE_IS || it->mode == LockMode::MODE_IX); - opCtx->checkForInterrupt(); - - // If we've reached this point then that means we tried to acquire a ticket but were - // unsuccessful, implying that tickets are currently exhausted. Additionally, since - // we're holding an IS or IX lock for this resource, any pending requests for the same - // resource must be S or X and will not be able to be granted. Thus, since such a - // pending lock request may also be holding a ticket, if there are any present we fail - // this ticket reacquisition in order to avoid a deadlock. - uassert(ErrorCodes::LockTimeout, - fmt::format("Unable to acquire ticket with mode '{}' due to detected lock " - "conflict for resource {}", - _modeForTicket, - it.key().toString()), - !getGlobalLockManager()->hasConflictingRequests(it.key(), it.objAddr())); - } - } while (!_acquireTicket(opCtx, _modeForTicket, Date_t::now() + Milliseconds{100})); -} - -bool LockerImpl::_acquireTicket(OperationContext* opCtx, LockMode mode, Date_t deadline) { - // Upon startup, the holder is not guaranteed to be initialized. - auto holder = _ticketHolderManager ? _ticketHolderManager->getTicketHolder(mode) : nullptr; - const bool reader = isSharedLockMode(mode); - - if (!shouldWaitForTicket() && holder) { - holder->reportImmediatePriorityAdmission(); - } else if (mode != MODE_X && mode != MODE_NONE && holder) { - // MODE_X is exclusive of all other locks, thus acquiring a ticket is unnecessary. - _clientState.store(reader ? kQueuedReader : kQueuedWriter); - // If the ticket wait is interrupted, restore the state of the client. - ScopeGuard restoreStateOnErrorGuard([&] { _clientState.store(kInactive); }); - - // Acquiring a ticket is a potentially blocking operation. This must not be called after a - // transaction timestamp has been set, indicating this transaction has created an oplog - // hole. - invariant(!opCtx->recoveryUnit()->isTimestamped()); - - if (auto ticket = holder->waitForTicketUntil( - _uninterruptibleLocksRequested ? nullptr : opCtx, &_admCtx, deadline)) { - _ticket = std::move(*ticket); - } else { - return false; - } - restoreStateOnErrorGuard.dismiss(); - } - - _clientState.store(reader ? kActiveReader : kActiveWriter); - return true; -} - -void LockerImpl::lockGlobal(OperationContext* opCtx, LockMode mode, Date_t deadline) { - dassert(isLocked() == (_modeForTicket != MODE_NONE)); - if (_modeForTicket == MODE_NONE) { - if (_uninterruptibleLocksRequested) { - // Ignore deadline. - invariant(_acquireTicket(opCtx, mode, Date_t::max())); - } else { - auto beforeAcquire = Date_t::now(); - uassert(ErrorCodes::LockTimeout, - str::stream() << "Unable to acquire ticket with mode '" << mode - << "' within a max lock request timeout of '" - << Date_t::now() - beforeAcquire << "' milliseconds.", - _acquireTicket(opCtx, mode, deadline)); - } - _modeForTicket = mode; - } else if (TestingProctor::instance().isEnabled() && !isModeCovered(mode, _modeForTicket)) { - LOGV2_FATAL( - 6614500, - "Ticket held does not cover requested mode for global lock. Global lock upgrades are " - "not allowed", - "held"_attr = modeName(_modeForTicket), - "requested"_attr = modeName(mode)); - } - - const LockResult result = _lockBegin(opCtx, resourceIdGlobal, mode); - // Fast, uncontended path - if (result == LOCK_OK) - return; - - invariant(result == LOCK_WAITING); - _lockComplete(opCtx, resourceIdGlobal, mode, deadline, nullptr); -} - -bool LockerImpl::unlockGlobal() { - if (!unlock(resourceIdGlobal)) { - return false; - } - - invariant(!inAWriteUnitOfWork()); - - LockRequestsMap::Iterator it = _requests.begin(); - while (!it.finished()) { - // If we're here we should only have one reference to any lock. It is a programming - // error for any lock used with multi-granularity locking to have more references than - // the global lock, because every scope starts by calling lockGlobal. - const auto resType = it.key().getType(); - if (resType == RESOURCE_GLOBAL || resType == RESOURCE_MUTEX) { - it.next(); - } else { - invariant(_unlockImpl(&it)); - } - } - - return true; -} - -void LockerImpl::beginWriteUnitOfWork() { - _wuowNestingLevel++; -} - -void LockerImpl::endWriteUnitOfWork() { - invariant(_wuowNestingLevel > 0); - - if (--_wuowNestingLevel > 0) { - // Don't do anything unless leaving outermost WUOW. - return; - } - - LockRequestsMap::Iterator it = _requests.begin(); - while (_numResourcesToUnlockAtEndUnitOfWork > 0) { - if (it->unlockPending) { - invariant(!it.finished()); - _numResourcesToUnlockAtEndUnitOfWork--; - } - while (it->unlockPending > 0) { - // If a lock is converted, unlock() may be called multiple times on a resource within - // the same WriteUnitOfWork. All such unlock() requests must thus be fulfilled here. - it->unlockPending--; - unlock(it.key()); - } - it.next(); - } -} - -void LockerImpl::releaseWriteUnitOfWork(WUOWLockSnapshot* stateOut) { - stateOut->wuowNestingLevel = _wuowNestingLevel; - _wuowNestingLevel = 0; - - for (auto it = _requests.begin(); _numResourcesToUnlockAtEndUnitOfWork > 0; it.next()) { - if (it->unlockPending) { - while (it->unlockPending) { - it->unlockPending--; - stateOut->unlockPendingLocks.push_back({it.key(), it->mode}); - } - _numResourcesToUnlockAtEndUnitOfWork--; - } - } -} - -void LockerImpl::restoreWriteUnitOfWork(const WUOWLockSnapshot& stateToRestore) { - invariant(_numResourcesToUnlockAtEndUnitOfWork == 0); - invariant(!inAWriteUnitOfWork()); - - for (auto& lock : stateToRestore.unlockPendingLocks) { - auto it = _requests.begin(); - while (it && !(it.key() == lock.resourceId && it->mode == lock.mode)) { - it.next(); - } - invariant(!it.finished()); - if (!it->unlockPending) { - _numResourcesToUnlockAtEndUnitOfWork++; - } - it->unlockPending++; - } - // Equivalent to call beginWriteUnitOfWork() multiple times. - _wuowNestingLevel = stateToRestore.wuowNestingLevel; -} - -void LockerImpl::releaseWriteUnitOfWorkAndUnlock(LockSnapshot* stateOut) { - // Only the global WUOW can be released, since we never need to release and restore - // nested WUOW's. Thus we don't have to remember the nesting level. - invariant(_wuowNestingLevel == 1); - --_wuowNestingLevel; - invariant(!isGlobalLockedRecursively()); - - // All locks should be pending to unlock. - invariant(_requests.size() == _numResourcesToUnlockAtEndUnitOfWork); - for (auto it = _requests.begin(); it; it.next()) { - // No converted lock so we don't need to unlock more than once. - invariant(it->unlockPending == 1); - it->unlockPending--; - } - _numResourcesToUnlockAtEndUnitOfWork = 0; - - saveLockStateAndUnlock(stateOut); -} - -void LockerImpl::restoreWriteUnitOfWorkAndLock(OperationContext* opCtx, - const LockSnapshot& stateToRestore) { - if (stateToRestore.globalMode != MODE_NONE) { - restoreLockState(opCtx, stateToRestore); - } - - invariant(_numResourcesToUnlockAtEndUnitOfWork == 0); - for (auto it = _requests.begin(); it; it.next()) { - invariant(_shouldDelayUnlock(it.key(), (it->mode))); - invariant(it->unlockPending == 0); - it->unlockPending++; - } - _numResourcesToUnlockAtEndUnitOfWork = static_cast(_requests.size()); - - beginWriteUnitOfWork(); -} - -void LockerImpl::lock(OperationContext* opCtx, ResourceId resId, LockMode mode, Date_t deadline) { - // `lockGlobal` must be called to lock `resourceIdGlobal`. - invariant(resId != resourceIdGlobal); - - const LockResult result = _lockBegin(opCtx, resId, mode); - - // Fast, uncontended path - if (result == LOCK_OK) - return; - - invariant(result == LOCK_WAITING); - _lockComplete(opCtx, resId, mode, deadline, nullptr); -} - -void LockerImpl::downgrade(ResourceId resId, LockMode newMode) { - LockRequestsMap::Iterator it = _requests.find(resId); - getGlobalLockManager()->downgrade(it.objAddr(), newMode); -} - -bool LockerImpl::unlock(ResourceId resId) { - LockRequestsMap::Iterator it = _requests.find(resId); - - // Don't attempt to unlock twice. This can happen when an interrupted global lock is destructed. - if (it.finished()) - return false; - - if (inAWriteUnitOfWork() && _shouldDelayUnlock(it.key(), (it->mode))) { - // Only delay unlocking if the lock is not acquired more than once. Otherwise, we can simply - // call _unlockImpl to decrement recursiveCount instead of incrementing unlockPending. This - // is safe because the lock is still being held in the strongest mode necessary. - if (it->recursiveCount > 1) { - // Invariant that the lock is still being held. - invariant(!_unlockImpl(&it)); - return false; - } - if (!it->unlockPending) { - _numResourcesToUnlockAtEndUnitOfWork++; - } - it->unlockPending++; - // unlockPending will be incremented if a lock is converted or acquired in the same mode - // recursively, and unlock() is called multiple times on one ResourceId. - invariant(it->unlockPending <= it->recursiveCount); - return false; - } - - return _unlockImpl(&it); -} - -bool LockerImpl::unlockRSTLforPrepare() { - auto rstlRequest = _requests.find(resourceIdReplicationStateTransitionLock); - - // Don't attempt to unlock twice. This can happen when an interrupted global lock is destructed. - if (!rstlRequest) - return false; - - // If the RSTL is 'unlockPending' and we are fully unlocking it, then we do not want to - // attempt to unlock the RSTL when the WUOW ends, since it will already be unlocked. - if (rstlRequest->unlockPending) { - rstlRequest->unlockPending = 0; - _numResourcesToUnlockAtEndUnitOfWork--; - } - - // Reset the recursiveCount to 1 so that we fully unlock the RSTL. Since it will be fully - // unlocked, any future unlocks will be noops anyways. - rstlRequest->recursiveCount = 1; - - return _unlockImpl(&rstlRequest); -} - -LockMode LockerImpl::getLockMode(ResourceId resId) const { - scoped_spinlock scopedLock(_lock); - - const LockRequestsMap::ConstIterator it = _requests.find(resId); - if (!it) - return MODE_NONE; - - return it->mode; -} - -bool LockerImpl::isLockHeldForMode(ResourceId resId, LockMode mode) const { - return isModeCovered(mode, getLockMode(resId)); -} - -boost::optional LockerImpl::_globalAndTenantLocksImplyDBOrCollectionLockedForMode( - const boost::optional& tenantId, LockMode lockMode) const { - if (isW()) { - return true; - } - if (isR() && isSharedLockMode(lockMode)) { - return true; - } - if (tenantId) { - const ResourceId tenantResourceId{ResourceType::RESOURCE_TENANT, *tenantId}; - switch (getLockMode(tenantResourceId)) { - case MODE_NONE: - return false; - case MODE_X: - return true; - case MODE_S: - return isSharedLockMode(lockMode); - case MODE_IX: - case MODE_IS: - break; - default: - MONGO_UNREACHABLE_TASSERT(6671502); - } - } - return boost::none; -} - -bool LockerImpl::isDbLockedForMode(const DatabaseName& dbName, LockMode mode) const { - if (auto lockedForMode = - _globalAndTenantLocksImplyDBOrCollectionLockedForMode(dbName.tenantId(), mode); - lockedForMode) { - return *lockedForMode; - } - - const ResourceId resIdDb(RESOURCE_DATABASE, dbName); - return isLockHeldForMode(resIdDb, mode); -} - -bool LockerImpl::isCollectionLockedForMode(const NamespaceString& nss, LockMode mode) const { - invariant(nss.coll().size()); - - if (!shouldConflictWithSecondaryBatchApplication()) - return true; - - if (auto lockedForMode = - _globalAndTenantLocksImplyDBOrCollectionLockedForMode(nss.tenantId(), mode); - lockedForMode) { - return *lockedForMode; - } - - const ResourceId resIdDb(RESOURCE_DATABASE, nss.dbName()); - LockMode dbMode = getLockMode(resIdDb); - - 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, nss); - return isLockHeldForMode(resIdColl, mode); - } break; - case LockModesCount: - break; - } - - MONGO_UNREACHABLE; - return false; -} - -bool LockerImpl::wasGlobalLockTakenForWrite() const { - return _globalLockMode & ((1 << MODE_IX) | (1 << MODE_X)); -} - -bool LockerImpl::wasGlobalLockTakenInModeConflictingWithWrites() const { - return _wasGlobalLockTakenInModeConflictingWithWrites.load(); -} - -bool LockerImpl::wasGlobalLockTaken() const { - return _globalLockMode != (1 << MODE_NONE); -} - -void LockerImpl::setGlobalLockTakenInMode(LockMode mode) { - _globalLockMode |= (1 << mode); - - if (mode == MODE_IX || mode == MODE_X || mode == MODE_S) { - _wasGlobalLockTakenInModeConflictingWithWrites.store(true); - } -} - -ResourceId LockerImpl::getWaitingResource() const { - scoped_spinlock scopedLock(_lock); - - return _waitingResource; -} - -MONGO_TSAN_IGNORE -void LockerImpl::getLockerInfo(LockerInfo* lockerInfo, - const boost::optional lockStatsBase) const { - invariant(lockerInfo); - - // Zero-out the contents - lockerInfo->locks.clear(); - lockerInfo->waitingResource = ResourceId(); - lockerInfo->stats.reset(); - - _lock.lock(); - LockRequestsMap::ConstIterator it = _requests.begin(); - while (!it.finished()) { - OneLock info; - info.resourceId = it.key(); - info.mode = it->mode; - - lockerInfo->locks.push_back(info); - it.next(); - } - _lock.unlock(); - - std::sort(lockerInfo->locks.begin(), lockerInfo->locks.end()); - - lockerInfo->waitingResource = getWaitingResource(); - lockerInfo->stats.append(_stats); - - // lockStatsBase is a snapshot of lock stats taken when the sub-operation starts. Only - // sub-operations have lockStatsBase. - if (lockStatsBase) - // Adjust the lock stats by subtracting the lockStatsBase. No mutex is needed because - // lockStatsBase is immutable. - lockerInfo->stats.subtract(*lockStatsBase); -} - -boost::optional LockerImpl::getLockerInfo( - const boost::optional lockStatsBase) const { - Locker::LockerInfo lockerInfo; - getLockerInfo(&lockerInfo, lockStatsBase); - return std::move(lockerInfo); -} - -void LockerImpl::saveLockStateAndUnlock(Locker::LockSnapshot* stateOut) { - // We shouldn't be saving and restoring lock state from inside a WriteUnitOfWork. - invariant(!inAWriteUnitOfWork()); - - // Callers must guarantee that they can actually yield. - if (MONGO_unlikely(!canSaveLockState())) { - dump(); - LOGV2_FATAL(7033800, - "Attempted to yield locks but we are either not holding locks, holding a " - "strong MODE_S/MODE_X lock, or holding one recursively"); - } - - // 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 so we store it - // separately from the more pedestrian locks. - auto globalRequest = _requests.find(resourceIdGlobal); - invariant(globalRequest); - - 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 == resType || RESOURCE_COLLECTION == resType || - resId == resourceIdParallelBatchWriterMode || RESOURCE_TENANT == resType || - resId == resourceIdFeatureCompatibilityVersion || - resId == resourceIdReplicationStateTransitionLock); - - // 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()); -} - -void LockerImpl::restoreLockState(OperationContext* opCtx, const Locker::LockSnapshot& state) { - // We shouldn't be restoring lock state from inside a WriteUnitOfWork. - invariant(!inAWriteUnitOfWork()); - invariant(_modeForTicket == MODE_NONE); - invariant(_clientState.load() == kInactive); - - getFlowControlTicket(opCtx, state.globalMode); - - std::vector::const_iterator it = state.locks.begin(); - // If we locked the PBWM, it must be locked before the - // resourceIdFeatureCompatibilityVersion, resourceIdReplicationStateTransitionLock, and - // resourceIdGlobal resources. - if (it != state.locks.end() && it->resourceId == resourceIdParallelBatchWriterMode) { - lock(opCtx, it->resourceId, it->mode); - it++; - } - - // If we locked the FCV lock, it must be locked before the - // resourceIdReplicationStateTransitionLock and resourceIdGlobal resources. - if (it != state.locks.end() && it->resourceId == resourceIdFeatureCompatibilityVersion) { - lock(opCtx, it->resourceId, it->mode); - it++; - } - - // If we locked the RSTL, it must be locked before the resourceIdGlobal resource. - if (it != state.locks.end() && it->resourceId == resourceIdReplicationStateTransitionLock) { - lock(opCtx, it->resourceId, it->mode); - it++; - } - - lockGlobal(opCtx, state.globalMode); - for (; it != state.locks.end(); it++) { - // Ensures we don't acquire locks out of order which can lead to deadlock. - invariant(it->resourceId.getType() != ResourceType::RESOURCE_GLOBAL); - lock(opCtx, it->resourceId, it->mode); - } - invariant(_modeForTicket != MODE_NONE); -} - -MONGO_TSAN_IGNORE -FlowControlTicketholder::CurOp LockerImpl::getFlowControlStats() const { - return _flowControlStats; -} - -MONGO_TSAN_IGNORE -LockResult LockerImpl::_lockBegin(OperationContext* opCtx, ResourceId resId, LockMode mode) { - dassert(!getWaitingResource().isValid()); - - // Operations which are holding open an oplog hole cannot block when acquiring locks. - if (opCtx && !shouldAllowLockAcquisitionOnTimestampedUnitOfWork() && - resId.getType() != RESOURCE_METADATA && resId.getType() != RESOURCE_MUTEX) { - invariant(!opCtx->recoveryUnit()->isTimestamped(), - str::stream() - << "Operation holding open an oplog hole tried to acquire locks. ResourceId: " - << resId << ", mode: " << modeName(mode)); - } - - LockRequest* request; - bool isNew = true; - - LockRequestsMap::Iterator it = _requests.find(resId); - if (!it) { - scoped_spinlock scopedLock(_lock); - LockRequestsMap::Iterator itNew = _requests.insert(resId); - itNew->initNew(this, &_notify); - - request = itNew.objAddr(); - } else { - request = it.objAddr(); - isNew = false; - } - - // If unlockPending is nonzero, that means a LockRequest already exists for this resource but - // is planned to be released at the end of this WUOW due to two-phase locking. Rather than - // unlocking the existing request, we can reuse it if the existing mode matches the new mode. - if (request->unlockPending && isModeCovered(mode, request->mode)) { - request->unlockPending--; - if (!request->unlockPending) { - _numResourcesToUnlockAtEndUnitOfWork--; - } - return LOCK_OK; - } - - // Making this call here will record lock re-acquisitions and conversions as well. - globalStats.recordAcquisition(_id, resId, mode); - _stats.recordAcquisition(resId, mode); - - // Give priority to the full modes for Global, PBWM, and RSTL resources so we don't stall global - // operations such as shutdown or stepdown. - const ResourceType resType = resId.getType(); - if (resType == RESOURCE_GLOBAL) { - if (mode == MODE_S || mode == MODE_X) { - request->enqueueAtFront = true; - request->compatibleFirst = true; - } - } else if (resType != RESOURCE_MUTEX) { - // This is all sanity checks that the global locks are always be acquired - // before any other lock has been acquired and they must be in sync with the nesting. - if (kDebugBuild) { - const LockRequestsMap::Iterator itGlobal = _requests.find(resourceIdGlobal); - invariant(itGlobal->recursiveCount > 0); - invariant(itGlobal->mode != MODE_NONE); - }; - } - - // The notification object must be cleared before we invoke the lock manager, because - // otherwise we might reset state if the lock becomes granted very fast. - _notify.clear(); - - LockResult result = isNew ? getGlobalLockManager()->lock(resId, request, mode) - : getGlobalLockManager()->convert(resId, request, mode); - - if (result == LOCK_WAITING) { - globalStats.recordWait(_id, resId, mode); - _stats.recordWait(resId, mode); - _setWaitingResource(resId); - } else if (result == LOCK_OK && opCtx && _uninterruptibleLocksRequested == 0) { - // Lock acquisitions are not allowed to succeed when opCtx is marked as interrupted, unless - // the caller requested an uninterruptible lock. - auto interruptStatus = opCtx->checkForInterruptNoAssert(); - if (!interruptStatus.isOK()) { - auto unlockIt = _requests.find(resId); - invariant(unlockIt); - _unlockImpl(&unlockIt); - uassertStatusOK(interruptStatus); - } - } - - return result; -} - -void LockerImpl::_lockComplete(OperationContext* opCtx, - ResourceId resId, - LockMode mode, - Date_t deadline, - const LockTimeoutCallback& onTimeout) { - // Operations which are holding open an oplog hole cannot block when acquiring locks. Lock - // requests entering this function have been queued up and will be granted the lock as soon as - // the lock is released, which is a blocking operation. - if (opCtx && !shouldAllowLockAcquisitionOnTimestampedUnitOfWork() && - resId.getType() != RESOURCE_METADATA && resId.getType() != RESOURCE_MUTEX) { - invariant(!opCtx->recoveryUnit()->isTimestamped(), - str::stream() - << "Operation holding open an oplog hole tried to acquire locks. ResourceId: " - << resId << ", mode: " << modeName(mode)); - } - - // Clean up the state on any failed lock attempts. - ScopeGuard unlockOnErrorGuard([&] { - LockRequestsMap::Iterator it = _requests.find(resId); - invariant(it); - _unlockImpl(&it); - _setWaitingResource(ResourceId()); - }); - - // This failpoint is used to time out non-intent locks if they cannot be granted immediately - // for user operations. Testing-only. - const bool isUserOperation = opCtx && opCtx->getClient()->isFromUserConnection(); - if (!_uninterruptibleLocksRequested && isUserOperation && - MONGO_unlikely(failNonIntentLocksIfWaitNeeded.shouldFail())) { - uassert(ErrorCodes::LockTimeout, - str::stream() << "Cannot immediately acquire lock '" << resId.toString() - << "'. Timing out due to failpoint.", - (mode == MODE_IS || mode == MODE_IX)); - } - - LockResult result; - Milliseconds timeout; - if (deadline == Date_t::max()) { - timeout = Milliseconds::max(); - } else if (deadline <= Date_t()) { - timeout = Milliseconds(0); - } else { - timeout = deadline - Date_t::now(); - } - timeout = std::min(timeout, _maxLockTimeout ? *_maxLockTimeout : Milliseconds::max()); - if (_uninterruptibleLocksRequested) { - timeout = Milliseconds::max(); - } - - // Don't go sleeping without bound in order to be able to report long waits. - Milliseconds waitTime = std::min(timeout, MaxWaitTime); - const uint64_t startOfTotalWaitTime = curTimeMicros64(); - uint64_t startOfCurrentWaitTime = startOfTotalWaitTime; - - while (true) { - // It is OK if this call wakes up spuriously, because we re-evaluate the remaining - // wait time anyways. - // If we have an operation context, we want to use its interruptible wait so that - // pending lock acquisitions can be cancelled, so long as no callers have requested an - // uninterruptible lock. - if (opCtx && _uninterruptibleLocksRequested == 0) { - result = _notify.wait(opCtx, waitTime); - } else { - result = _notify.wait(waitTime); - } - - // Account for the time spent waiting on the notification object - const uint64_t curTimeMicros = curTimeMicros64(); - const uint64_t elapsedTimeMicros = curTimeMicros - startOfCurrentWaitTime; - startOfCurrentWaitTime = curTimeMicros; - - globalStats.recordWaitTime(_id, resId, mode, elapsedTimeMicros); - _stats.recordWaitTime(resId, mode, elapsedTimeMicros); - - if (result == LOCK_OK) - break; - - // If infinite timeout was requested, just keep waiting - if (timeout == Milliseconds::max()) { - continue; - } - - const auto totalBlockTime = duration_cast( - Microseconds(int64_t(curTimeMicros - startOfTotalWaitTime))); - waitTime = (totalBlockTime < timeout) ? std::min(timeout - totalBlockTime, MaxWaitTime) - : Milliseconds(0); - - // Check if the lock acquisition has timed out. If we have an operation context and client - // we can provide additional diagnostics data. - if (waitTime == Milliseconds(0)) { - if (onTimeout) { - onTimeout(); - } - std::string timeoutMessage = str::stream() - << "Unable to acquire " << modeName(mode) << " lock on '" << resId.toString() - << "' within " << timeout << "."; - if (opCtx && opCtx->getClient()) { - timeoutMessage = str::stream() - << timeoutMessage << " opId: " << opCtx->getOpID() - << ", op: " << opCtx->getClient()->desc() - << ", connId: " << opCtx->getClient()->getConnectionId() << "."; - } - uasserted(ErrorCodes::LockTimeout, timeoutMessage); - } - } - - invariant(result == LOCK_OK); - unlockOnErrorGuard.dismiss(); - _setWaitingResource(ResourceId()); -} - -void LockerImpl::getFlowControlTicket(OperationContext* opCtx, LockMode lockMode) { - auto ticketholder = FlowControlTicketholder::get(opCtx); - if (ticketholder && lockMode == LockMode::MODE_IX && _clientState.load() == kInactive && - _admCtx.getPriority() != AdmissionContext::Priority::kImmediate && - !_uninterruptibleLocksRequested) { - // FlowControl only acts when a MODE_IX global lock is being taken. The clientState is only - // being modified here to change serverStatus' `globalLock.currentQueue` metrics. This - // method must not exit with a side-effect on the clientState. That value is also used for - // tracking whether other resources need to be released. - _clientState.store(kQueuedWriter); - ScopeGuard restoreState([&] { _clientState.store(kInactive); }); - // Acquiring a ticket is a potentially blocking operation. This must not be called after a - // transaction timestamp has been set, indicating this transaction has created an oplog - // hole. - invariant(!opCtx->recoveryUnit()->isTimestamped()); - ticketholder->getTicket(opCtx, &_flowControlStats); - } -} - -LockResult LockerImpl::lockRSTLBegin(OperationContext* opCtx, LockMode mode) { - bool testOnly = false; - - if (MONGO_unlikely(enableTestOnlyFlagforRSTL.shouldFail())) { - testOnly = true; - } - - invariant(testOnly || mode == MODE_IX || mode == MODE_X); - return _lockBegin(opCtx, resourceIdReplicationStateTransitionLock, mode); -} - -void LockerImpl::lockRSTLComplete(OperationContext* opCtx, - LockMode mode, - Date_t deadline, - const LockTimeoutCallback& onTimeout) { - _lockComplete(opCtx, resourceIdReplicationStateTransitionLock, mode, deadline, onTimeout); -} - -void LockerImpl::releaseTicket() { - invariant(_modeForTicket != MODE_NONE); - _releaseTicket(); -} - -void LockerImpl::_releaseTicket() { - _ticket.reset(); - _clientState.store(kInactive); -} - -bool LockerImpl::_unlockImpl(LockRequestsMap::Iterator* it) { - if (getGlobalLockManager()->unlock(it->objAddr())) { - if (it->key() == resourceIdGlobal) { - invariant(_modeForTicket != MODE_NONE); - - // We may have already released our ticket through a call to releaseTicket(). - if (_clientState.load() != kInactive) { - _releaseTicket(); - } - - _modeForTicket = MODE_NONE; - } - - scoped_spinlock scopedLock(_lock); - it->remove(); - - return true; - } - - return false; -} - -bool LockerImpl::isGlobalLockedRecursively() { - auto globalLockRequest = _requests.find(resourceIdGlobal); - return !globalLockRequest.finished() && globalLockRequest->recursiveCount > 1; -} - -bool LockerImpl::canSaveLockState() { - // We cannot yield strong global locks. - if (_modeForTicket == MODE_S || _modeForTicket == MODE_X) { - return false; - } - - // If we don't have a global lock, we do not yield. - if (_modeForTicket == MODE_NONE) { - auto globalRequest = _requests.find(resourceIdGlobal); - invariant(!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; - } - - for (auto it = _requests.begin(); !it.finished(); it.next()) { - const ResourceId resId = it.key(); - const ResourceType resType = resId.getType(); - if (resType == RESOURCE_MUTEX) - continue; - - // If any lock 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. This logic applies to all - // locks in the hierarchy. - if (it->recursiveCount > 1) { - return false; - } - - // We cannot yield any other lock in a strong lock mode. - if (it->mode == MODE_S || it->mode == MODE_X) { - return false; - } - } - - return true; -} - -void LockerImpl::_setWaitingResource(ResourceId resId) { - scoped_spinlock scopedLock(_lock); - - _waitingResource = resId; -} - -// -// Auto classes -// - -namespace { -/** - * Periodically purges unused lock buckets. The first time the lock is used again after - * cleanup it needs to be allocated, and similarly, every first use by a client for an intent - * mode may need to create a partitioned lock head. Cleanup is done roughly once a minute. - */ -class UnusedLockCleaner : PeriodicTask { -public: - std::string taskName() const { - return "UnusedLockCleaner"; - } - - void taskDoWork() { - LOGV2_DEBUG(20524, 2, "cleaning up unused lock buckets of the global lock manager"); - getGlobalLockManager()->cleanupUnusedLocks(); - } -} unusedLockCleaner; -} // namespace - -// -// Standalone functions -// - -LockManager* getGlobalLockManager() { - auto serviceContext = getGlobalServiceContext(); - invariant(serviceContext); - return LockManager::get(serviceContext); -} - -void reportGlobalLockingStats(SingleThreadedLockStats* outStats) { - globalStats.report(outStats); -} - -void resetGlobalLockStats() { - globalStats.reset(); -} - -} // namespace mongo diff --git a/src/mongo/db/concurrency/lock_state.h b/src/mongo/db/concurrency/lock_state.h deleted file mode 100644 index 0d19a10cac8..00000000000 --- a/src/mongo/db/concurrency/lock_state.h +++ /dev/null @@ -1,449 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * 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 - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side 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 Server Side 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 - -#include "mongo/db/concurrency/fast_map_noalloc.h" -#include "mongo/db/concurrency/lock_manager_defs.h" -#include "mongo/db/concurrency/locker.h" -#include "mongo/db/operation_context.h" -#include "mongo/db/storage/ticketholder_manager.h" -#include "mongo/platform/atomic_word.h" -#include "mongo/util/concurrency/spin_lock.h" -#include "mongo/util/concurrency/ticketholder.h" - -namespace mongo { - -/** - * Notfication callback, which stores the last notification result and signals a condition - * variable, which can be waited on. - */ -class CondVarLockGrantNotification : public LockGrantNotification { - CondVarLockGrantNotification(const CondVarLockGrantNotification&) = delete; - CondVarLockGrantNotification& operator=(const CondVarLockGrantNotification&) = delete; - -public: - CondVarLockGrantNotification(); - - /** - * Clears the object so it can be reused. - */ - void clear(); - - /** - * Uninterruptible blocking method, which waits for the notification to fire. - * - * @param timeout How many milliseconds to wait before returning LOCK_TIMEOUT. - */ - LockResult wait(Milliseconds timeout); - - /** - * Interruptible blocking method, which waits for the notification to fire or an interrupt from - * the operation context. - * - * @param opCtx OperationContext to wait on for an interrupt. - * @param timeout How many milliseconds to wait before returning LOCK_TIMEOUT. - */ - LockResult wait(OperationContext* opCtx, Milliseconds timeout); - -private: - virtual void notify(ResourceId resId, LockResult result); - - // These two go together to implement the conditional variable pattern. - Mutex _mutex = MONGO_MAKE_LATCH("CondVarLockGrantNotification::_mutex"); - stdx::condition_variable _cond; - - // Result from the last call to notify - LockResult _result; -}; - -/** - * Interface for acquiring locks. One of those objects will have to be instantiated for each - * request (transaction). - * - * Lock/unlock methods must always be called from a single thread. - * - * All instances reference a single global lock manager. - * - */ -class LockerImpl : public Locker { -public: - /** - * Instantiates new locker. Must be given a unique identifier for disambiguation. Lockers - * having the same identifier will not conflict on lock acquisition. - */ - LockerImpl(ServiceContext* serviceContext); - - virtual ~LockerImpl(); - - virtual ClientState getClientState() const; - - virtual LockerId getId() const { - return _id; - } - - stdx::thread::id getThreadId() const override; - - void updateThreadIdToCurrentThread() override; - void unsetThreadId() override; - - void setSharedLocksShouldTwoPhaseLock(bool sharedLocksShouldTwoPhaseLock) override { - _sharedLocksShouldTwoPhaseLock = sharedLocksShouldTwoPhaseLock; - } - - void setMaxLockTimeout(Milliseconds maxTimeout) override { - _maxLockTimeout = maxTimeout; - } - - bool hasMaxLockTimeout() override { - return static_cast(_maxLockTimeout); - } - - void unsetMaxLockTimeout() override { - _maxLockTimeout = boost::none; - } - - /** - * Acquires the ticket within the deadline (or _maxLockTimeout) and tries to grab the lock. - */ - virtual void lockGlobal(OperationContext* opCtx, - LockMode mode, - Date_t deadline = Date_t::max()); - - virtual bool unlockGlobal(); - - virtual LockResult lockRSTLBegin(OperationContext* opCtx, LockMode mode); - virtual void lockRSTLComplete(OperationContext* opCtx, - LockMode mode, - Date_t deadline, - const LockTimeoutCallback& onTimeout); - - virtual bool unlockRSTLforPrepare(); - - virtual void beginWriteUnitOfWork() override; - virtual void endWriteUnitOfWork() override; - - virtual bool inAWriteUnitOfWork() const { - return _wuowNestingLevel > 0; - } - - bool wasGlobalLockTakenForWrite() const override; - - bool wasGlobalLockTakenInModeConflictingWithWrites() const override; - - bool wasGlobalLockTaken() const override; - - void setGlobalLockTakenInMode(LockMode mode) override; - - /** - * Requests a lock for resource 'resId' with mode 'mode'. An OperationContext 'opCtx' must be - * provided to interrupt waiting on the locker condition variable that indicates status of - * the lock acquisition. A lock operation would otherwise wait until a timeout or the lock is - * granted. - */ - virtual void lock(OperationContext* opCtx, - ResourceId resId, - LockMode mode, - Date_t deadline = Date_t::max()); - - virtual void lock(ResourceId resId, LockMode mode, Date_t deadline = Date_t::max()) { - lock(nullptr, resId, mode, deadline); - } - - virtual void downgrade(ResourceId resId, LockMode newMode); - - virtual bool unlock(ResourceId resId); - - virtual LockMode getLockMode(ResourceId resId) const; - virtual bool isLockHeldForMode(ResourceId resId, LockMode mode) const; - virtual bool isDbLockedForMode(const DatabaseName& dbName, LockMode mode) const; - virtual bool isCollectionLockedForMode(const NamespaceString& nss, LockMode mode) const; - - virtual ResourceId getWaitingResource() const; - - virtual void getLockerInfo(LockerInfo* lockerInfo, - boost::optional lockStatsBase) const; - virtual boost::optional getLockerInfo( - boost::optional lockStatsBase) const final; - - virtual void saveLockStateAndUnlock(LockSnapshot* stateOut); - - virtual void restoreLockState(OperationContext* opCtx, const LockSnapshot& stateToRestore); - - void releaseWriteUnitOfWorkAndUnlock(LockSnapshot* stateOut) override; - void restoreWriteUnitOfWorkAndLock(OperationContext* opCtx, - const LockSnapshot& stateToRestore) override; - - void releaseWriteUnitOfWork(WUOWLockSnapshot* stateOut) override; - void restoreWriteUnitOfWork(const WUOWLockSnapshot& stateToRestore) override; - - virtual void releaseTicket(); - virtual void reacquireTicket(OperationContext* opCtx); - - bool hasReadTicket() const override { - return _modeForTicket == MODE_IS || _modeForTicket == MODE_S; - } - - bool hasWriteTicket() const override { - return _modeForTicket == MODE_IX || _modeForTicket == MODE_X; - } - - void getFlowControlTicket(OperationContext* opCtx, LockMode lockMode) override; - - FlowControlTicketholder::CurOp getFlowControlStats() const override; - - // - // Below functions are for testing only. - // - - FastMapNoAlloc getRequestsForTest() const { - scoped_spinlock scopedLock(_lock); - return _requests; - } - - LockResult lockBeginForTest(OperationContext* opCtx, ResourceId resId, LockMode mode) { - return _lockBegin(opCtx, resId, mode); - } - - void lockCompleteForTest(OperationContext* opCtx, - ResourceId resId, - LockMode mode, - Date_t deadline) { - _lockComplete(opCtx, resId, mode, deadline, nullptr); - } - -private: - typedef FastMapNoAlloc LockRequestsMap; - - /** - * Allows for lock requests to be requested in a non-blocking way. There can be only one - * outstanding pending lock request per locker object. - * - * _lockBegin posts a request to the lock manager for the specified lock to be acquired, - * which either immediately grants the lock, or puts the requestor on the conflict queue - * and returns immediately with the result of the acquisition. The result can be one of: - * - * LOCK_OK - Nothing more needs to be done. The lock is granted. - * LOCK_WAITING - The request has been queued up and will be granted as soon as the lock - * is free. If this result is returned, typically _lockComplete needs to be called in - * order to wait for the actual grant to occur. If the caller no longer needs to wait - * for the grant to happen, unlock needs to be called with the same resource passed - * to _lockBegin. - * - * In other words for each call to _lockBegin, which does not return LOCK_OK, there needs to - * be a corresponding call to either _lockComplete or unlock. - * - * If an operation context is provided that represents an interrupted operation, _lockBegin will - * throw an exception whenever it would have been possible to grant the lock with LOCK_OK. This - * behavior can be disabled with an UninterruptibleLockGuard. - * - * NOTE: These methods are not public and should only be used inside the class - * implementation and for unit-tests and not called directly. - */ - LockResult _lockBegin(OperationContext* opCtx, ResourceId resId, LockMode mode); - - /** - * Waits for the completion of a lock, previously requested through _lockBegin/ - * Must only be called, if _lockBegin returned LOCK_WAITING. - * - * @param opCtx Operation context that, if not null, will be used to allow interruptible lock - * acquisition. - * @param resId Resource id which was passed to an earlier _lockBegin call. Must match. - * @param mode Mode which was passed to an earlier _lockBegin call. Must match. - * @param deadline The absolute time point when this lock acquisition will time out, if not yet - * granted. - * @param onTimeout Callback which will run if the lock acquisition is about to time out. - * - * Throws an exception if it is interrupted. - */ - void _lockComplete(OperationContext* opCtx, - ResourceId resId, - LockMode mode, - Date_t deadline, - const LockTimeoutCallback& onTimeout); - - /** - * The main functionality of the unlock method, except accepts iterator in order to avoid - * additional lookups during unlockGlobal. Frees locks immediately, so must not be called from - * inside a WUOW. - */ - bool _unlockImpl(LockRequestsMap::Iterator* it); - - /** - * Whether we should use two phase locking. Returns true if the particular lock's release should - * be delayed until the end of the operation. - * - * We delay release of write operation locks (X, IX) in order to ensure that the data changes - * they protect are committed successfully. endWriteUnitOfWork will release them afterwards. - * This protects other threads from seeing inconsistent in-memory state. - * - * Shared locks (S, IS) will also participate in two-phase locking if - * '_sharedLocksShouldTwoPhaseLock' is true. This will protect open storage engine transactions - * across network calls. - */ - bool _shouldDelayUnlock(ResourceId resId, LockMode mode) const; - - /** - * Releases the ticket for the Locker. - */ - void _releaseTicket(); - - /** - * Acquires a ticket for the Locker under 'mode'. - * Returns true if a ticket is successfully acquired. - * false if it cannot acquire a ticket within 'deadline'. - * It may throw an exception when it is interrupted. - */ - bool _acquireTicket(OperationContext* opCtx, LockMode mode, Date_t deadline); - - void _setWaitingResource(ResourceId resId); - - /** - * Calls dump() on this locker instance and the lock manager. - */ - void _dumpLockerAndLockManagerRequests(); - - /** - * Determines whether global and tenant lock state implies that some database or lower level - * resource, such as a collection, belonging to a tenant identified by 'tenantId' is locked in - * 'lockMode'. - * - * Returns: - * true, if the global and tenant locks imply that the resource is locked for 'mode'; - * false, if the global and tenant locks imply that the resource is not locked for 'mode'; - * boost::none, if the global and tenant lock state does not imply either outcome and lower - * level locks should be consulted. - */ - boost::optional _globalAndTenantLocksImplyDBOrCollectionLockedForMode( - const boost::optional& tenantId, LockMode lockMode) const; - - // Used to disambiguate different lockers - const LockerId _id; - - // The only reason we have this spin lock here is for the diagnostic tools, which could - // iterate through the LockRequestsMap on a separate thread and need it to be stable. - // Apart from that, all accesses to the LockerImpl are always from a single thread. - // - // This has to be locked inside const methods, hence the mutable. - mutable SpinLock _lock; - // Note: this data structure must always guarantee the continued validity of pointers/references - // to its contents (LockRequests). The LockManager maintains a LockRequestList of pointers to - // the LockRequests managed by this data structure. - LockRequestsMap _requests; - - // Reuse the notification object across requests so we don't have to create a new mutex - // and condition variable every time. - CondVarLockGrantNotification _notify; - - // Per-locker locking statistics. Reported in the slow-query log message and through - // db.currentOp. Complementary to the per-instance locking statistics. - AtomicLockStats _stats; - - // Delays release of exclusive/intent-exclusive locked resources until the write unit of - // work completes. Value of 0 means we are not inside a write unit of work. - int _wuowNestingLevel; - - // Mode for which the Locker acquired a ticket, or MODE_NONE if no ticket was acquired. - LockMode _modeForTicket = MODE_NONE; - - // Indicates whether the client is active reader/writer or is queued. - AtomicWord _clientState{kInactive}; - - // Track the thread who owns the lock for debugging purposes - stdx::thread::id _threadId; - - // If true, shared locks will participate in two-phase locking. - bool _sharedLocksShouldTwoPhaseLock = false; - - // If this is set, dictates the max number of milliseconds that we will wait for lock - // acquisition. Effectively resets lock acquisition deadlines to time out sooner. If set to 0, - // for example, lock attempts will time out immediately if the lock is not immediately - // available. Note this will be ineffective if uninterruptible lock guard is set. - boost::optional _maxLockTimeout; - - // A structure for accumulating time spent getting flow control tickets. - FlowControlTicketholder::CurOp _flowControlStats; - - // The global ticketholders of the service context. - TicketHolderManager* _ticketHolderManager; - - // This will only be valid when holding a ticket. - boost::optional _ticket; - - // Tracks the global lock modes ever acquired in this Locker's life. This value should only ever - // be accessed from the thread that owns the Locker. - unsigned char _globalLockMode = (1 << MODE_NONE); - - // Tracks whether this operation should be killed on step down. - AtomicWord _wasGlobalLockTakenInModeConflictingWithWrites{false}; - - // If isValid(), the ResourceId of the resource currently waiting for the lock. If not valid, - // there is no resource currently waiting. - ResourceId _waitingResource; - - ////////////////////////////////////////////////////////////////////////////////////////// - // - // Methods merged from LockState, which should eventually be removed or changed to methods - // on the LockerImpl interface. - // - -public: - virtual void dump() const; - - virtual bool isW() const; - virtual bool isR() const; - - virtual bool isLocked() const; - virtual bool isWriteLocked() const; - virtual bool isReadLocked() const; - - virtual bool isRSTLExclusive() const; - virtual bool isRSTLLocked() const; - - bool isGlobalLockedRecursively() override; - bool canSaveLockState() override; - - virtual bool hasLockPending() const { - return getWaitingResource().isValid(); - } -}; - -/** - * Retrieves the global lock manager instance. - * Legacy global lock manager accessor for internal lock implementation * and debugger scripts - * such as gdb/mongo_lock.py. - * The lock manager is now a decoration on the service context and this accessor is retained for - * startup, lock internals, and debugger scripts. - * Using LockManager::get(ServiceContext*) where possible is preferable. - */ -LockManager* getGlobalLockManager(); - -} // namespace mongo diff --git a/src/mongo/db/concurrency/lock_state_test.cpp b/src/mongo/db/concurrency/lock_state_test.cpp deleted file mode 100644 index 6e67f7c31ea..00000000000 --- a/src/mongo/db/concurrency/lock_state_test.cpp +++ /dev/null @@ -1,1392 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * 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 - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side 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 Server Side 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 -#include -#include - -#include "mongo/config.h" -#include "mongo/db/concurrency/lock_manager_test_help.h" -#include "mongo/db/concurrency/locker.h" -#include "mongo/db/curop.h" -#include "mongo/db/service_context_test_fixture.h" -#include "mongo/transport/session.h" -#include "mongo/transport/transport_layer_mock.h" -#include "mongo/unittest/death_test.h" -#include "mongo/unittest/unittest.h" -#include "mongo/util/fail_point.h" -#include "mongo/util/timer.h" - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - - -namespace mongo { - -class LockerImplTest : public ServiceContextTest {}; - -TEST_F(LockerImplTest, LockNoConflict) { - auto opCtx = makeOperationContext(); - - const ResourceId resId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - LockerImpl locker(opCtx->getServiceContext()); - locker.lockGlobal(opCtx.get(), MODE_IX); - - locker.lock(resId, MODE_X); - - ASSERT(locker.isLockHeldForMode(resId, MODE_X)); - ASSERT(locker.isLockHeldForMode(resId, MODE_S)); - - ASSERT(locker.unlock(resId)); - - ASSERT(locker.isLockHeldForMode(resId, MODE_NONE)); - - locker.unlockGlobal(); -} - -TEST_F(LockerImplTest, ReLockNoConflict) { - auto opCtx = makeOperationContext(); - - const ResourceId resId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - LockerImpl locker(opCtx->getServiceContext()); - locker.lockGlobal(opCtx.get(), MODE_IX); - - locker.lock(resId, MODE_S); - locker.lock(resId, MODE_X); - - ASSERT(!locker.unlock(resId)); - ASSERT(locker.isLockHeldForMode(resId, MODE_X)); - - ASSERT(locker.unlock(resId)); - ASSERT(locker.isLockHeldForMode(resId, MODE_NONE)); - - ASSERT(locker.unlockGlobal()); -} - -TEST_F(LockerImplTest, ConflictWithTimeout) { - auto opCtx = makeOperationContext(); - - const ResourceId resId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - LockerImpl locker1(opCtx->getServiceContext()); - locker1.lockGlobal(opCtx.get(), MODE_IX); - locker1.lock(resId, MODE_X); - - LockerImpl locker2(opCtx->getServiceContext()); - locker2.lockGlobal(opCtx.get(), MODE_IX); - - ASSERT_THROWS_CODE(locker2.lock(opCtx.get(), resId, MODE_S, Date_t::now()), - AssertionException, - ErrorCodes::LockTimeout); - - ASSERT(locker2.getLockMode(resId) == MODE_NONE); - - ASSERT(locker1.unlock(resId)); - - ASSERT(locker1.unlockGlobal()); - ASSERT(locker2.unlockGlobal()); -} - -TEST_F(LockerImplTest, ConflictUpgradeWithTimeout) { - auto opCtx = makeOperationContext(); - - const ResourceId resId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - LockerImpl locker1(opCtx->getServiceContext()); - locker1.lockGlobal(opCtx.get(), MODE_IS); - locker1.lock(resId, MODE_S); - - LockerImpl locker2(opCtx->getServiceContext()); - locker2.lockGlobal(opCtx.get(), MODE_IS); - locker2.lock(resId, MODE_S); - - // Try upgrading locker 1, which should block and timeout - ASSERT_THROWS_CODE(locker1.lock(opCtx.get(), resId, MODE_X, Date_t::now() + Milliseconds(1)), - AssertionException, - ErrorCodes::LockTimeout); - - locker1.unlockGlobal(); - locker2.unlockGlobal(); -} - -TEST_F(LockerImplTest, FailPointInLockFailsGlobalNonIntentLocksIfTheyCannotBeImmediatelyGranted) { - transport::TransportLayerMock transportLayer; - std::shared_ptr session = transportLayer.createSession(); - - auto newClient = getServiceContext()->makeClient("userClient", session); - AlternativeClientRegion acr(newClient); - auto newOpCtx = cc().makeOperationContext(); - - LockerImpl locker1(newOpCtx->getServiceContext()); - locker1.lockGlobal(newOpCtx.get(), MODE_IX); - - { - FailPointEnableBlock failWaitingNonPartitionedLocks("failNonIntentLocksIfWaitNeeded"); - - // MODE_S attempt. - LockerImpl locker2(newOpCtx->getServiceContext()); - ASSERT_THROWS_CODE( - locker2.lockGlobal(newOpCtx.get(), MODE_S), DBException, ErrorCodes::LockTimeout); - - // MODE_X attempt. - LockerImpl locker3(newOpCtx->getServiceContext()); - ASSERT_THROWS_CODE( - locker3.lockGlobal(newOpCtx.get(), MODE_X), DBException, ErrorCodes::LockTimeout); - } - - locker1.unlockGlobal(); -} - -TEST_F(LockerImplTest, FailPointInLockFailsNonIntentLocksIfTheyCannotBeImmediatelyGranted) { - transport::TransportLayerMock transportLayer; - std::shared_ptr session = transportLayer.createSession(); - - auto newClient = getServiceContext()->makeClient("userClient", session); - AlternativeClientRegion acr(newClient); - auto newOpCtx = cc().makeOperationContext(); - - // Granted MODE_X lock, fail incoming MODE_S and MODE_X. - const ResourceId resId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - LockerImpl locker1(newOpCtx->getServiceContext()); - locker1.lockGlobal(newOpCtx.get(), MODE_IX); - locker1.lock(newOpCtx.get(), resId, MODE_X); - - { - FailPointEnableBlock failWaitingNonPartitionedLocks("failNonIntentLocksIfWaitNeeded"); - - // MODE_S attempt. - LockerImpl locker2(newOpCtx->getServiceContext()); - locker2.lockGlobal(newOpCtx.get(), MODE_IS); - ASSERT_THROWS_CODE(locker2.lock(newOpCtx.get(), resId, MODE_S, Date_t::max()), - DBException, - ErrorCodes::LockTimeout); - - // The timed out MODE_S attempt shouldn't be present in the map of lock requests because it - // won't ever be granted. - ASSERT(locker2.getRequestsForTest().find(resId).finished()); - locker2.unlockGlobal(); - - // MODE_X attempt. - LockerImpl locker3(newOpCtx->getServiceContext()); - locker3.lockGlobal(newOpCtx.get(), MODE_IX); - ASSERT_THROWS_CODE(locker3.lock(newOpCtx.get(), resId, MODE_X, Date_t::max()), - DBException, - ErrorCodes::LockTimeout); - - // The timed out MODE_X attempt shouldn't be present in the map of lock requests because it - // won't ever be granted. - ASSERT(locker3.getRequestsForTest().find(resId).finished()); - locker3.unlockGlobal(); - } - - locker1.unlockGlobal(); -} - -TEST_F(LockerImplTest, ReadTransaction) { - auto opCtx = makeOperationContext(); - - LockerImpl locker(opCtx->getServiceContext()); - - locker.lockGlobal(opCtx.get(), MODE_IS); - locker.unlockGlobal(); - - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.unlockGlobal(); - - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lockGlobal(opCtx.get(), MODE_IS); - locker.unlockGlobal(); - locker.unlockGlobal(); -} - -/** - * Test that saveLockerImpl works by examining the output. - */ -TEST_F(LockerImplTest, saveAndRestoreGlobal) { - auto opCtx = makeOperationContext(); - - LockerImpl locker(opCtx->getServiceContext()); - - // No lock requests made, no locks held. - ASSERT_FALSE(locker.canSaveLockState()); - - // Lock the global lock, but just once. - locker.lockGlobal(opCtx.get(), MODE_IX); - - // We've locked the global lock. This should be reflected in the lockInfo. - Locker::LockSnapshot lockInfo; - locker.saveLockStateAndUnlock(&lockInfo); - ASSERT(!locker.isLocked()); - ASSERT_EQUALS(MODE_IX, lockInfo.globalMode); - - // Restore the lock(s) we had. - locker.restoreLockState(opCtx.get(), lockInfo); - - ASSERT(locker.isLocked()); - ASSERT(locker.unlockGlobal()); -} - -/** - * Test that saveLockerImpl can save and restore the RSTL. - */ -TEST_F(LockerImplTest, saveAndRestoreRSTL) { - auto opCtx = makeOperationContext(); - - Locker::LockSnapshot lockInfo; - - LockerImpl locker(opCtx->getServiceContext()); - - const ResourceId resIdDatabase(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); - - // Acquire locks. - locker.lock(resourceIdReplicationStateTransitionLock, MODE_IX); - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lock(resIdDatabase, MODE_IX); - - // Save the lock state. - locker.saveLockStateAndUnlock(&lockInfo); - ASSERT(!locker.isLocked()); - ASSERT_EQUALS(MODE_IX, lockInfo.globalMode); - - // Check locks are unlocked. - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resourceIdReplicationStateTransitionLock)); - ASSERT(!locker.isLocked()); - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); - - // Restore the lock(s) we had. - locker.restoreLockState(opCtx.get(), lockInfo); - - // Check locks are re-locked. - ASSERT(locker.isLocked()); - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resourceIdReplicationStateTransitionLock)); - - ASSERT(locker.unlockGlobal()); - ASSERT(locker.unlock(resourceIdReplicationStateTransitionLock)); -} - -/** - * Test that we don't unlock when we have the global lock more than once. - */ -TEST_F(LockerImplTest, saveAndRestoreGlobalAcquiredTwice) { - auto opCtx = makeOperationContext(); - - LockerImpl locker(opCtx->getServiceContext()); - - // No lock requests made, no locks held. - ASSERT_FALSE(locker.canSaveLockState()); - - // Lock the global lock. - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lockGlobal(opCtx.get(), MODE_IX); - - // This shouldn't actually unlock as we're in a nested scope. - ASSERT_FALSE(locker.canSaveLockState()); - - ASSERT(locker.isLocked()); - - // We must unlockGlobal twice. - ASSERT(!locker.unlockGlobal()); - ASSERT(locker.unlockGlobal()); -} - -/** - * Tests that restoreLockerImpl works by locking a db and collection and saving + restoring. - */ -TEST_F(LockerImplTest, saveAndRestoreDBAndCollection) { - auto opCtx = makeOperationContext(); - - Locker::LockSnapshot lockInfo; - - LockerImpl locker(opCtx->getServiceContext()); - - const ResourceId resIdDatabase(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); - const ResourceId resIdCollection( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - // Lock some stuff. - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lock(resIdDatabase, MODE_IX); - locker.lock(resIdCollection, MODE_IX); - locker.saveLockStateAndUnlock(&lockInfo); - - // Things shouldn't be locked anymore. - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); - - // Restore lock state. - locker.restoreLockState(opCtx.get(), lockInfo); - - // Make sure things were re-locked. - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection)); - - ASSERT(locker.unlockGlobal()); -} - -TEST_F(LockerImplTest, releaseWriteUnitOfWork) { - auto opCtx = makeOperationContext(); - - Locker::LockSnapshot lockInfo; - - LockerImpl locker(opCtx->getServiceContext()); - - const ResourceId resIdDatabase(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); - const ResourceId resIdCollection( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - locker.beginWriteUnitOfWork(); - // Lock some stuff. - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lock(resIdDatabase, MODE_IX); - locker.lock(resIdCollection, MODE_IX); - // Unlock them so that they will be pending to unlock. - ASSERT_FALSE(locker.unlock(resIdCollection)); - ASSERT_FALSE(locker.unlock(resIdDatabase)); - ASSERT_FALSE(locker.unlockGlobal()); - - locker.releaseWriteUnitOfWorkAndUnlock(&lockInfo); - - // Things shouldn't be locked anymore. - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); - ASSERT_FALSE(locker.isLocked()); - - // Destructor should succeed since the locker's state should be empty. -} - -TEST_F(LockerImplTest, restoreWriteUnitOfWork) { - auto opCtx = makeOperationContext(); - - Locker::LockSnapshot lockInfo; - - LockerImpl locker(opCtx->getServiceContext()); - - const ResourceId resIdDatabase(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); - const ResourceId resIdCollection( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - locker.beginWriteUnitOfWork(); - // Lock some stuff. - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lock(resIdDatabase, MODE_IX); - locker.lock(resIdCollection, MODE_IX); - // Unlock them so that they will be pending to unlock. - ASSERT_FALSE(locker.unlock(resIdCollection)); - ASSERT_FALSE(locker.unlock(resIdDatabase)); - ASSERT_FALSE(locker.unlockGlobal()); - - locker.releaseWriteUnitOfWorkAndUnlock(&lockInfo); - - // Things shouldn't be locked anymore. - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); - ASSERT_FALSE(locker.isLocked()); - - // Restore lock state. - locker.restoreWriteUnitOfWorkAndLock(opCtx.get(), lockInfo); - - // Make sure things were re-locked. - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection)); - ASSERT(locker.isLocked()); - - locker.endWriteUnitOfWork(); - - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); - ASSERT_FALSE(locker.isLocked()); -} - -TEST_F(LockerImplTest, releaseAndRestoreWriteUnitOfWorkWithoutUnlock) { - auto opCtx = makeOperationContext(); - - Locker::WUOWLockSnapshot lockInfo; - - LockerImpl locker(opCtx->getServiceContext()); - - const ResourceId resIdDatabase(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); - const ResourceId resIdCollection( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - const ResourceId resIdCollection2( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection2")); - - locker.beginWriteUnitOfWork(); - // Lock some stuff. - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lock(resIdDatabase, MODE_IX); - locker.lock(resIdCollection, MODE_X); - - // Recursive global lock. - locker.lockGlobal(opCtx.get(), MODE_IX); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 2U); - - ASSERT_FALSE(locker.unlockGlobal()); - - // Unlock them so that they will be pending to unlock. - ASSERT_FALSE(locker.unlock(resIdCollection)); - ASSERT_FALSE(locker.unlock(resIdDatabase)); - ASSERT_FALSE(locker.unlockGlobal()); - ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 3UL); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); - - locker.releaseWriteUnitOfWork(&lockInfo); - ASSERT_EQ(lockInfo.unlockPendingLocks.size(), 3UL); - - // Things should still be locked. - ASSERT_EQUALS(MODE_X, locker.getLockMode(resIdCollection)); - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 0U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); - ASSERT(locker.isLocked()); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 0U); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); - - // The locker is no longer participating the two-phase locking. - ASSERT_FALSE(locker.inAWriteUnitOfWork()); - ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 0UL); - - // Start a new WUOW with the same locker. Any new locks acquired in the new WUOW - // should participate two-phase locking. - { - locker.beginWriteUnitOfWork(); - - // Grab new locks inside the new WUOW. - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lock(resIdDatabase, MODE_IX); - locker.lock(resIdCollection2, MODE_IX); - - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection2)); - ASSERT(locker.isLocked()); - - locker.unlock(resIdCollection2); - ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 1UL); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 0U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 2U); - locker.unlock(resIdDatabase); - ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 1UL); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 0U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); - locker.unlockGlobal(); - ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 1UL); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 0U); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); - locker.endWriteUnitOfWork(); - } - ASSERT_FALSE(locker.inAWriteUnitOfWork()); - ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 0UL); - - ASSERT_EQUALS(MODE_X, locker.getLockMode(resIdCollection)); - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 0U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); - ASSERT(locker.isLocked()); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 0U); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); - // The new locks has been released. - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection2)); - - // Restore lock state. - locker.restoreWriteUnitOfWork(lockInfo); - - ASSERT_TRUE(locker.inAWriteUnitOfWork()); - - // Make sure things are still locked. - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_X, locker.getLockMode(resIdCollection)); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); - ASSERT(locker.isLocked()); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); - - locker.endWriteUnitOfWork(); - - ASSERT_FALSE(locker.inAWriteUnitOfWork()); - - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection2)); - ASSERT_FALSE(locker.isLocked()); - ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 0U); - ASSERT(locker.getRequestsForTest().find(resourceIdGlobal).finished()); -} - -TEST_F(LockerImplTest, releaseAndRestoreReadOnlyWriteUnitOfWork) { - auto opCtx = makeOperationContext(); - - Locker::LockSnapshot lockInfo; - - LockerImpl locker(opCtx->getServiceContext()); - - const ResourceId resIdDatabase(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); - const ResourceId resIdCollection( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - // Snapshot transactions delay shared locks as well. - locker.setSharedLocksShouldTwoPhaseLock(true); - - locker.beginWriteUnitOfWork(); - // Lock some stuff in IS mode. - locker.lockGlobal(opCtx.get(), MODE_IS); - locker.lock(resIdDatabase, MODE_IS); - locker.lock(resIdCollection, MODE_IS); - // Unlock them. - ASSERT_FALSE(locker.unlock(resIdCollection)); - ASSERT_FALSE(locker.unlock(resIdDatabase)); - ASSERT_FALSE(locker.unlockGlobal()); - ASSERT_EQ(3u, locker.numResourcesToUnlockAtEndUnitOfWorkForTest()); - - // Things shouldn't be locked anymore. - locker.releaseWriteUnitOfWorkAndUnlock(&lockInfo); - - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); - ASSERT_FALSE(locker.isLocked()); - - // Restore lock state. - locker.restoreWriteUnitOfWorkAndLock(opCtx.get(), lockInfo); - - ASSERT_EQUALS(MODE_IS, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_IS, locker.getLockMode(resIdCollection)); - ASSERT_TRUE(locker.isLocked()); - - locker.endWriteUnitOfWork(); - - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); - ASSERT_FALSE(locker.isLocked()); -} - -TEST_F(LockerImplTest, releaseAndRestoreEmptyWriteUnitOfWork) { - Locker::LockSnapshot lockInfo; - auto opCtx = makeOperationContext(); - LockerImpl locker(opCtx->getServiceContext()); - - // Snapshot transactions delay shared locks as well. - locker.setSharedLocksShouldTwoPhaseLock(true); - - locker.beginWriteUnitOfWork(); - - // Nothing to yield. - ASSERT_FALSE(locker.canSaveLockState()); - ASSERT_FALSE(locker.isLocked()); - - locker.endWriteUnitOfWork(); -} - -TEST_F(LockerImplTest, releaseAndRestoreWriteUnitOfWorkWithRecursiveLocks) { - auto opCtx = makeOperationContext(); - - Locker::LockSnapshot lockInfo; - - LockerImpl locker(opCtx->getServiceContext()); - - const ResourceId resIdDatabase(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); - const ResourceId resIdCollection( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - locker.beginWriteUnitOfWork(); - // Lock some stuff. - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lock(resIdDatabase, MODE_IX); - locker.lock(resIdCollection, MODE_IX); - // Recursively lock them again with a weaker mode. - locker.lockGlobal(opCtx.get(), MODE_IS); - locker.lock(resIdDatabase, MODE_IS); - locker.lock(resIdCollection, MODE_IS); - - // Make sure locks are converted. - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection)); - ASSERT_TRUE(locker.isWriteLocked()); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 2U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 2U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->recursiveCount, 2U); - - // Unlock them so that they will be pending to unlock. - ASSERT_FALSE(locker.unlock(resIdCollection)); - ASSERT_FALSE(locker.unlock(resIdDatabase)); - ASSERT_FALSE(locker.unlockGlobal()); - // Make sure locks are still acquired in the correct mode. - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection)); - ASSERT_TRUE(locker.isWriteLocked()); - // Make sure unlocking converted locks decrements the locks' recursiveCount instead of - // incrementing unlockPending. - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 0U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 0U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->recursiveCount, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->unlockPending, 0U); - - // Unlock again so unlockPending == recursiveCount. - ASSERT_FALSE(locker.unlock(resIdCollection)); - ASSERT_FALSE(locker.unlock(resIdDatabase)); - ASSERT_FALSE(locker.unlockGlobal()); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->recursiveCount, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->unlockPending, 1U); - - locker.releaseWriteUnitOfWorkAndUnlock(&lockInfo); - - // Things shouldn't be locked anymore. - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); - ASSERT_FALSE(locker.isLocked()); - - // Restore lock state. - locker.restoreWriteUnitOfWorkAndLock(opCtx.get(), lockInfo); - - // Make sure things were re-locked in the correct mode. - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection)); - ASSERT_TRUE(locker.isWriteLocked()); - // Make sure locks were coalesced after restore and are pending to unlock as before. - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->recursiveCount, 1U); - ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->unlockPending, 1U); - - locker.endWriteUnitOfWork(); - - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); - ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); - ASSERT_FALSE(locker.isLocked()); -} - -TEST_F(LockerImplTest, DefaultLocker) { - auto opCtx = makeOperationContext(); - - const ResourceId resId(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); - - LockerImpl locker(opCtx->getServiceContext()); - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lock(resId, MODE_X); - - // Make sure only Global and TestDB resources are locked. - Locker::LockerInfo info; - locker.getLockerInfo(&info, boost::none); - ASSERT(!info.waitingResource.isValid()); - ASSERT_EQUALS(2U, info.locks.size()); - ASSERT_EQUALS(RESOURCE_GLOBAL, info.locks[0].resourceId.getType()); - ASSERT_EQUALS(resId, info.locks[1].resourceId); - - ASSERT(locker.unlockGlobal()); -} - -TEST_F(LockerImplTest, SharedLocksShouldTwoPhaseLockIsTrue) { - // Test that when setSharedLocksShouldTwoPhaseLock is true and we are in a WUOW, unlock on IS - // and S locks are postponed until endWriteUnitOfWork() is called. Mode IX and X locks always - // participate in two-phased locking, regardless of the setting. - - auto opCtx = makeOperationContext(); - - const ResourceId resId1(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB1")); - const ResourceId resId2(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB2")); - const ResourceId resId3( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection3")); - const ResourceId resId4( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection4")); - - LockerImpl locker(opCtx->getServiceContext()); - locker.setSharedLocksShouldTwoPhaseLock(true); - - locker.lockGlobal(opCtx.get(), MODE_IS); - ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_IS); - - locker.lock(resourceIdReplicationStateTransitionLock, MODE_IS); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IS); - - locker.lock(resId1, MODE_IS); - locker.lock(resId2, MODE_IX); - locker.lock(resId3, MODE_S); - locker.lock(resId4, MODE_X); - ASSERT_EQ(locker.getLockMode(resId1), MODE_IS); - ASSERT_EQ(locker.getLockMode(resId2), MODE_IX); - ASSERT_EQ(locker.getLockMode(resId3), MODE_S); - ASSERT_EQ(locker.getLockMode(resId4), MODE_X); - - locker.beginWriteUnitOfWork(); - - ASSERT_FALSE(locker.unlock(resId1)); - ASSERT_FALSE(locker.unlock(resId2)); - ASSERT_FALSE(locker.unlock(resId3)); - ASSERT_FALSE(locker.unlock(resId4)); - ASSERT_EQ(locker.getLockMode(resId1), MODE_IS); - ASSERT_EQ(locker.getLockMode(resId2), MODE_IX); - ASSERT_EQ(locker.getLockMode(resId3), MODE_S); - ASSERT_EQ(locker.getLockMode(resId4), MODE_X); - - ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IS); - - ASSERT_FALSE(locker.unlockGlobal()); - ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_IS); - - locker.endWriteUnitOfWork(); - - ASSERT_EQ(locker.getLockMode(resId1), MODE_NONE); - ASSERT_EQ(locker.getLockMode(resId2), MODE_NONE); - ASSERT_EQ(locker.getLockMode(resId3), MODE_NONE); - ASSERT_EQ(locker.getLockMode(resId4), MODE_NONE); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); - ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_NONE); -} - -TEST_F(LockerImplTest, ModeIXAndXLockParticipatesInTwoPhaseLocking) { - // Unlock on mode IX and X locks during a WUOW should always be postponed until - // endWriteUnitOfWork() is called. Mode IS and S locks should unlock immediately. - - auto opCtx = makeOperationContext(); - - const ResourceId resId1(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB1")); - const ResourceId resId2(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB2")); - const ResourceId resId3( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection3")); - const ResourceId resId4( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection4")); - - LockerImpl locker(opCtx->getServiceContext()); - - locker.lockGlobal(opCtx.get(), MODE_IX); - ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_IX); - - locker.lock(resourceIdReplicationStateTransitionLock, MODE_IX); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); - - locker.lock(resId1, MODE_IS); - locker.lock(resId2, MODE_IX); - locker.lock(resId3, MODE_S); - locker.lock(resId4, MODE_X); - ASSERT_EQ(locker.getLockMode(resId1), MODE_IS); - ASSERT_EQ(locker.getLockMode(resId2), MODE_IX); - ASSERT_EQ(locker.getLockMode(resId3), MODE_S); - ASSERT_EQ(locker.getLockMode(resId4), MODE_X); - - locker.beginWriteUnitOfWork(); - - ASSERT_TRUE(locker.unlock(resId1)); - ASSERT_FALSE(locker.unlock(resId2)); - ASSERT_TRUE(locker.unlock(resId3)); - ASSERT_FALSE(locker.unlock(resId4)); - ASSERT_EQ(locker.getLockMode(resId1), MODE_NONE); - ASSERT_EQ(locker.getLockMode(resId2), MODE_IX); - ASSERT_EQ(locker.getLockMode(resId3), MODE_NONE); - ASSERT_EQ(locker.getLockMode(resId4), MODE_X); - - ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); - - ASSERT_FALSE(locker.unlockGlobal()); - ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_IX); - - locker.endWriteUnitOfWork(); - - ASSERT_EQ(locker.getLockMode(resId2), MODE_NONE); - ASSERT_EQ(locker.getLockMode(resId4), MODE_NONE); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); - ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_NONE); -} - -TEST_F(LockerImplTest, RSTLUnlocksWithNestedLock) { - auto opCtx = makeOperationContext(); - LockerImpl locker(opCtx->getServiceContext()); - - locker.lock(resourceIdReplicationStateTransitionLock, MODE_IX); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); - - locker.beginWriteUnitOfWork(); - - // Do a nested lock acquisition. - locker.lock(resourceIdReplicationStateTransitionLock, MODE_IX); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); - - ASSERT(locker.unlockRSTLforPrepare()); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); - - ASSERT_FALSE(locker.unlockRSTLforPrepare()); - - locker.endWriteUnitOfWork(); - - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); - - ASSERT_FALSE(locker.unlockRSTLforPrepare()); - ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); -} - -TEST_F(LockerImplTest, RSTLModeIXWithTwoPhaseLockingCanBeUnlockedWhenPrepared) { - auto opCtx = makeOperationContext(); - LockerImpl locker(opCtx->getServiceContext()); - - locker.lock(resourceIdReplicationStateTransitionLock, MODE_IX); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); - - locker.beginWriteUnitOfWork(); - - ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); - - ASSERT(locker.unlockRSTLforPrepare()); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); - - ASSERT_FALSE(locker.unlockRSTLforPrepare()); - - locker.endWriteUnitOfWork(); - - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); - - ASSERT_FALSE(locker.unlockRSTLforPrepare()); - ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); -} - -TEST_F(LockerImplTest, RSTLModeISWithTwoPhaseLockingCanBeUnlockedWhenPrepared) { - auto opCtx = makeOperationContext(); - LockerImpl locker(opCtx->getServiceContext()); - - locker.lock(resourceIdReplicationStateTransitionLock, MODE_IS); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IS); - - locker.beginWriteUnitOfWork(); - - ASSERT(locker.unlockRSTLforPrepare()); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); - - ASSERT_FALSE(locker.unlockRSTLforPrepare()); - - locker.endWriteUnitOfWork(); - - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); - - ASSERT_FALSE(locker.unlockRSTLforPrepare()); - ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); -} - -TEST_F(LockerImplTest, RSTLTwoPhaseLockingBehaviorModeIS) { - auto opCtx = makeOperationContext(); - LockerImpl locker(opCtx->getServiceContext()); - - locker.lock(resourceIdReplicationStateTransitionLock, MODE_IS); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IS); - - locker.beginWriteUnitOfWork(); - - ASSERT_TRUE(locker.unlock(resourceIdReplicationStateTransitionLock)); - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); - - ASSERT_FALSE(locker.unlockRSTLforPrepare()); - - locker.endWriteUnitOfWork(); - - ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); - - ASSERT_FALSE(locker.unlockRSTLforPrepare()); - ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); -} - -TEST_F(LockerImplTest, OverrideLockRequestTimeout) { - auto opCtx = makeOperationContext(); - - const ResourceId resIdFirstDB(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "FirstDB")); - const ResourceId resIdSecondDB( - RESOURCE_DATABASE, DatabaseName::createDatabaseName_forTest(boost::none, "SecondDB")); - - LockerImpl locker1(opCtx->getServiceContext()); - LockerImpl locker2(opCtx->getServiceContext()); - - // Set up locker2 to override lock requests' provided timeout if greater than 1000 milliseconds. - locker2.setMaxLockTimeout(Milliseconds(1000)); - - locker1.lockGlobal(opCtx.get(), MODE_IX); - locker2.lockGlobal(opCtx.get(), MODE_IX); - - // locker1 acquires FirstDB under an exclusive lock. - locker1.lock(resIdFirstDB, MODE_X); - ASSERT_TRUE(locker1.isLockHeldForMode(resIdFirstDB, MODE_X)); - - // locker2's attempt to acquire FirstDB with unlimited wait time should timeout after 1000 - // milliseconds and throw because _maxLockRequestTimeout is set to 1000 milliseconds. - ASSERT_THROWS_CODE(locker2.lock(opCtx.get(), resIdFirstDB, MODE_X, Date_t::max()), - AssertionException, - ErrorCodes::LockTimeout); - - // locker2's attempt to acquire an uncontested lock should still succeed normally. - locker2.lock(resIdSecondDB, MODE_X); - - ASSERT_TRUE(locker1.unlock(resIdFirstDB)); - ASSERT_TRUE(locker1.isLockHeldForMode(resIdFirstDB, MODE_NONE)); - ASSERT_TRUE(locker2.unlock(resIdSecondDB)); - ASSERT_TRUE(locker2.isLockHeldForMode(resIdSecondDB, MODE_NONE)); - - ASSERT(locker1.unlockGlobal()); - ASSERT(locker2.unlockGlobal()); -} - -TEST_F(LockerImplTest, DoNotWaitForLockAcquisition) { - auto opCtx = makeOperationContext(); - - const ResourceId resIdFirstDB(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "FirstDB")); - const ResourceId resIdSecondDB( - RESOURCE_DATABASE, DatabaseName::createDatabaseName_forTest(boost::none, "SecondDB")); - - LockerImpl locker1(opCtx->getServiceContext()); - LockerImpl locker2(opCtx->getServiceContext()); - - // Set up locker2 to immediately return if a lock is unavailable, regardless of supplied - // deadlines in the lock request. - locker2.setMaxLockTimeout(Milliseconds(0)); - - locker1.lockGlobal(opCtx.get(), MODE_IX); - locker2.lockGlobal(opCtx.get(), MODE_IX); - - // locker1 acquires FirstDB under an exclusive lock. - locker1.lock(resIdFirstDB, MODE_X); - ASSERT_TRUE(locker1.isLockHeldForMode(resIdFirstDB, MODE_X)); - - // locker2's attempt to acquire FirstDB with unlimited wait time should fail immediately and - // throw because _maxLockRequestTimeout was set to 0. - ASSERT_THROWS_CODE(locker2.lock(opCtx.get(), resIdFirstDB, MODE_X, Date_t::max()), - AssertionException, - ErrorCodes::LockTimeout); - - // locker2's attempt to acquire an uncontested lock should still succeed normally. - locker2.lock(resIdSecondDB, MODE_X); - - ASSERT_TRUE(locker1.unlock(resIdFirstDB)); - ASSERT_TRUE(locker1.isLockHeldForMode(resIdFirstDB, MODE_NONE)); - ASSERT_TRUE(locker2.unlock(resIdSecondDB)); - ASSERT_TRUE(locker2.isLockHeldForMode(resIdSecondDB, MODE_NONE)); - - ASSERT(locker1.unlockGlobal()); - ASSERT(locker2.unlockGlobal()); -} - -namespace { -/** - * Helper function to determine if 'lockerInfo' contains a lock with ResourceId 'resourceId' and - * lock mode 'mode' within 'lockerInfo.locks'. - */ -bool lockerInfoContainsLock(const Locker::LockerInfo& lockerInfo, - const ResourceId& resourceId, - const LockMode& mode) { - return (1U == - std::count_if(lockerInfo.locks.begin(), - lockerInfo.locks.end(), - [&resourceId, &mode](const Locker::OneLock& lock) { - return lock.resourceId == resourceId && lock.mode == mode; - })); -} -} // namespace - -TEST_F(LockerImplTest, GetLockerInfoShouldReportHeldLocks) { - auto opCtx = makeOperationContext(); - - const ResourceId dbId(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); - const ResourceId collectionId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - // Take an exclusive lock on the collection. - LockerImpl locker(opCtx->getServiceContext()); - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lock(dbId, MODE_IX); - locker.lock(collectionId, MODE_X); - - // Assert it shows up in the output of getLockerInfo(). - Locker::LockerInfo lockerInfo; - locker.getLockerInfo(&lockerInfo, boost::none); - - ASSERT(lockerInfoContainsLock(lockerInfo, resourceIdGlobal, MODE_IX)); - ASSERT(lockerInfoContainsLock(lockerInfo, dbId, MODE_IX)); - ASSERT(lockerInfoContainsLock(lockerInfo, collectionId, MODE_X)); - ASSERT_EQ(3U, lockerInfo.locks.size()); - - ASSERT(locker.unlock(collectionId)); - ASSERT(locker.unlock(dbId)); - ASSERT(locker.unlockGlobal()); -} - -TEST_F(LockerImplTest, GetLockerInfoShouldReportPendingLocks) { - auto opCtx = makeOperationContext(); - - const ResourceId dbId(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); - const ResourceId collectionId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - // Take an exclusive lock on the collection. - LockerImpl successfulLocker(opCtx->getServiceContext()); - successfulLocker.lockGlobal(opCtx.get(), MODE_IX); - successfulLocker.lock(dbId, MODE_IX); - successfulLocker.lock(collectionId, MODE_X); - - // Now attempt to get conflicting locks. - LockerImpl conflictingLocker(opCtx->getServiceContext()); - conflictingLocker.lockGlobal(opCtx.get(), MODE_IS); - conflictingLocker.lock(dbId, MODE_IS); - ASSERT_EQ(LOCK_WAITING, - conflictingLocker.lockBeginForTest(nullptr /* opCtx */, collectionId, MODE_IS)); - - // Assert the held locks show up in the output of getLockerInfo(). - Locker::LockerInfo lockerInfo; - conflictingLocker.getLockerInfo(&lockerInfo, boost::none); - ASSERT(lockerInfoContainsLock(lockerInfo, resourceIdGlobal, MODE_IS)); - ASSERT(lockerInfoContainsLock(lockerInfo, dbId, MODE_IS)); - ASSERT(lockerInfoContainsLock(lockerInfo, collectionId, MODE_IS)); - ASSERT_EQ(3U, lockerInfo.locks.size()); - - // Assert it reports that it is waiting for the collection lock. - ASSERT_EQ(collectionId, lockerInfo.waitingResource); - - // Make sure it no longer reports waiting once unlocked. - ASSERT(successfulLocker.unlock(collectionId)); - ASSERT(successfulLocker.unlock(dbId)); - ASSERT(successfulLocker.unlockGlobal()); - - conflictingLocker.lockCompleteForTest( - nullptr /* opCtx */, collectionId, MODE_IS, Date_t::now()); - - conflictingLocker.getLockerInfo(&lockerInfo, boost::none); - ASSERT_FALSE(lockerInfo.waitingResource.isValid()); - - ASSERT(conflictingLocker.unlock(collectionId)); - ASSERT(conflictingLocker.unlock(dbId)); - ASSERT(conflictingLocker.unlockGlobal()); -} - -TEST_F(LockerImplTest, GetLockerInfoShouldSubtractBase) { - auto opCtx = makeOperationContext(); - auto locker = opCtx->lockState(); - const ResourceId dbId(RESOURCE_DATABASE, - DatabaseName::createDatabaseName_forTest(boost::none, "SubtractTestDB")); - - auto numAcquisitions = [&](boost::optional baseStats) { - Locker::LockerInfo info; - locker->getLockerInfo(&info, baseStats); - return info.stats.get(dbId, MODE_IX).numAcquisitions; - }; - auto getBaseStats = [&] { - return CurOp::get(opCtx.get())->getLockStatsBase(); - }; - - locker->lockGlobal(opCtx.get(), MODE_IX); - - // Obtain a lock before any other ops have been pushed to the stack. - locker->lock(dbId, MODE_IX); - locker->unlock(dbId); - - ASSERT_EQUALS(numAcquisitions(getBaseStats()), 1) << "The acquisition should be reported"; - - // Push another op to the stack and obtain a lock. - CurOp superOp; - superOp.push(opCtx.get()); - locker->lock(dbId, MODE_IX); - locker->unlock(dbId); - - ASSERT_EQUALS(numAcquisitions(getBaseStats()), 1) - << "Only superOp's acquisition should be reported"; - - // Then push another op to the stack and obtain another lock. - CurOp subOp; - subOp.push(opCtx.get()); - locker->lock(dbId, MODE_IX); - locker->unlock(dbId); - - ASSERT_EQUALS(numAcquisitions(getBaseStats()), 1) - << "Only the latest acquisition should be reported"; - - ASSERT_EQUALS(numAcquisitions({}), 3) - << "All acquisitions should be reported when no base is subtracted out."; - - ASSERT(locker->unlockGlobal()); -} - -TEST_F(LockerImplTest, ReaquireLockPendingUnlock) { - auto opCtx = makeOperationContext(); - - const ResourceId resId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - LockerImpl locker(opCtx->getServiceContext()); - locker.lockGlobal(opCtx.get(), MODE_IS); - - locker.lock(resId, MODE_X); - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); - - locker.beginWriteUnitOfWork(); - - ASSERT_FALSE(locker.unlock(resId)); - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); - ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); - - // Reacquire lock pending unlock. - locker.lock(resId, MODE_X); - ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 0); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 0); - - locker.endWriteUnitOfWork(); - - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); - - locker.unlockGlobal(); -} - -TEST_F(LockerImplTest, AcquireLockPendingUnlockWithCoveredMode) { - auto opCtx = makeOperationContext(); - - const ResourceId resId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - LockerImpl locker(opCtx->getServiceContext()); - locker.lockGlobal(opCtx.get(), MODE_IS); - - locker.lock(resId, MODE_X); - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); - - locker.beginWriteUnitOfWork(); - - ASSERT_FALSE(locker.unlock(resId)); - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); - ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); - - // Attempt to lock the resource with a mode that is covered by the existing mode. - locker.lock(resId, MODE_IX); - ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 0); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 0); - - locker.endWriteUnitOfWork(); - - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_IX)); - - locker.unlockGlobal(); -} - -TEST_F(LockerImplTest, ConvertLockPendingUnlock) { - auto opCtx = makeOperationContext(); - - const ResourceId resId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - LockerImpl locker(opCtx->getServiceContext()); - locker.lockGlobal(opCtx.get(), MODE_IS); - - locker.lock(resId, MODE_IX); - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_IX)); - - locker.beginWriteUnitOfWork(); - - ASSERT_FALSE(locker.unlock(resId)); - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_IX)); - ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->recursiveCount == 1); - - // Convert lock pending unlock. - locker.lock(resId, MODE_X); - ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->recursiveCount == 2); - - locker.endWriteUnitOfWork(); - - ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 0); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 0); - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); - - locker.unlockGlobal(); -} - -TEST_F(LockerImplTest, ConvertLockPendingUnlockAndUnlock) { - auto opCtx = makeOperationContext(); - - const ResourceId resId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - LockerImpl locker(opCtx->getServiceContext()); - locker.lockGlobal(opCtx.get(), MODE_IS); - - locker.lock(resId, MODE_IX); - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_IX)); - - locker.beginWriteUnitOfWork(); - - ASSERT_FALSE(locker.unlock(resId)); - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_IX)); - ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->recursiveCount == 1); - - // Convert lock pending unlock. - locker.lock(resId, MODE_X); - ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->recursiveCount == 2); - - // Unlock the lock conversion. - ASSERT_FALSE(locker.unlock(resId)); - ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); - // Make sure we still hold X lock and unlock the weaker mode to decrement recursiveCount instead - // of incrementing unlockPending. - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); - ASSERT(locker.getRequestsForTest().find(resId).objAddr()->recursiveCount == 1); - - locker.endWriteUnitOfWork(); - - ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 0); - ASSERT(locker.getRequestsForTest().find(resId).finished()); - ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_NONE)); - - locker.unlockGlobal(); -} - -TEST_F(LockerImplTest, SetTicketAcquisitionForLockRAIIType) { - auto opCtx = makeOperationContext(); - - // By default, ticket acquisition is required. - ASSERT_TRUE(opCtx->lockState()->shouldWaitForTicket()); - - { - ScopedAdmissionPriorityForLock setTicketAquisition(opCtx->lockState(), - AdmissionContext::Priority::kImmediate); - ASSERT_FALSE(opCtx->lockState()->shouldWaitForTicket()); - } - - ASSERT_TRUE(opCtx->lockState()->shouldWaitForTicket()); - - opCtx->lockState()->setAdmissionPriority(AdmissionContext::Priority::kImmediate); - ASSERT_FALSE(opCtx->lockState()->shouldWaitForTicket()); - - { - ScopedAdmissionPriorityForLock setTicketAquisition(opCtx->lockState(), - AdmissionContext::Priority::kImmediate); - ASSERT_FALSE(opCtx->lockState()->shouldWaitForTicket()); - } - - ASSERT_FALSE(opCtx->lockState()->shouldWaitForTicket()); -} - -// This test exercises the lock dumping code in ~LockerImpl in case locks are held on destruction. -DEATH_TEST_F(LockerImplTest, - LocksHeldOnDestructionCausesALocksDump, - "Operation ending while holding locks.") { - auto opCtx = makeOperationContext(); - - const ResourceId resId( - RESOURCE_COLLECTION, - NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); - - LockerImpl locker(opCtx->getServiceContext()); - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lock(resId, MODE_X); - - ASSERT(locker.isLockHeldForMode(resId, MODE_X)); - ASSERT(locker.isLockHeldForMode(resId, MODE_S)); - - // 'locker' destructor should invariant because locks are still held. -} - -DEATH_TEST_F(LockerImplTest, SaveAndRestoreGlobalRecursivelyIsFatal, "7033800") { - auto opCtx = makeOperationContext(); - - Locker::LockSnapshot lockInfo; - - LockerImpl locker(opCtx->getServiceContext()); - - // No lock requests made, no locks held. - locker.saveLockStateAndUnlock(&lockInfo); - ASSERT_EQUALS(0U, lockInfo.locks.size()); - - // Lock the global lock. - locker.lockGlobal(opCtx.get(), MODE_IX); - locker.lockGlobal(opCtx.get(), MODE_IX); - - // Should invariant - locker.saveLockStateAndUnlock(&lockInfo); -} - -} // namespace mongo diff --git a/src/mongo/db/concurrency/locker.cpp b/src/mongo/db/concurrency/locker.cpp new file mode 100644 index 00000000000..296ed9f9e63 --- /dev/null +++ b/src/mongo/db/concurrency/locker.cpp @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2023-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side 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 Server Side 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/db/concurrency/locker.h" + +namespace mongo { + +Locker::Locker() = default; + +Locker::~Locker() = default; + +} // namespace mongo diff --git a/src/mongo/db/concurrency/locker.h b/src/mongo/db/concurrency/locker.h index 59a4c92d9c5..26f12adbd0b 100644 --- a/src/mongo/db/concurrency/locker.h +++ b/src/mongo/db/concurrency/locker.h @@ -57,7 +57,7 @@ class Locker { public: using LockTimeoutCallback = std::function; - virtual ~Locker() {} + virtual ~Locker(); /** * Returns true if this is an instance of LockerNoop. Because LockerNoop doesn't implement many @@ -570,7 +570,7 @@ public: } protected: - Locker() {} + Locker(); /** * The number of callers that are guarding from lock interruptions. diff --git a/src/mongo/db/concurrency/locker_impl.cpp b/src/mongo/db/concurrency/locker_impl.cpp new file mode 100644 index 00000000000..a45cb05679d --- /dev/null +++ b/src/mongo/db/concurrency/locker_impl.cpp @@ -0,0 +1,1241 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side 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 Server Side 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/db/concurrency/locker_impl.h" + +#include + +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/bson/json.h" +#include "mongo/db/namespace_string.h" +#include "mongo/db/service_context.h" +#include "mongo/db/storage/flow_control.h" +#include "mongo/db/storage/ticketholder_manager.h" +#include "mongo/logv2/log.h" +#include "mongo/platform/compiler.h" +#include "mongo/stdx/new.h" +#include "mongo/util/background.h" +#include "mongo/util/concurrency/ticketholder.h" +#include "mongo/util/debug_util.h" +#include "mongo/util/fail_point.h" +#include "mongo/util/scopeguard.h" +#include "mongo/util/str.h" +#include "mongo/util/testing_proctor.h" + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault + +namespace mongo { + +MONGO_FAIL_POINT_DEFINE(failNonIntentLocksIfWaitNeeded); +MONGO_FAIL_POINT_DEFINE(enableTestOnlyFlagforRSTL); + +namespace { + +// Ignore data races in certain functions when running with TSAN. For performance reasons, +// diagnostic commands are expected to race with concurrent lock acquisitions while gathering +// statistics. +#if __has_feature(thread_sanitizer) +#define MONGO_TSAN_IGNORE __attribute__((no_sanitize("thread"))) +#else +#define MONGO_TSAN_IGNORE +#endif + +/** + * Tracks global (across all clients) lock acquisition statistics, partitioned into multiple + * buckets to minimize concurrent access conflicts. + * + * Each client has a LockerId that monotonically increases across all client instances. The + * LockerId % 8 is used to index into one of 8 LockStats instances. These LockStats objects must be + * atomically accessed, so maintaining 8 that are indexed by LockerId reduces client conflicts and + * improves concurrent write access. A reader, to collect global lock statics for reporting, will + * sum the results of all 8 disjoint 'buckets' of stats. + */ +class PartitionedInstanceWideLockStats { + PartitionedInstanceWideLockStats(const PartitionedInstanceWideLockStats&) = delete; + PartitionedInstanceWideLockStats& operator=(const PartitionedInstanceWideLockStats&) = delete; + +public: + PartitionedInstanceWideLockStats() {} + + void recordAcquisition(LockerId id, ResourceId resId, LockMode mode) { + _get(id).recordAcquisition(resId, mode); + } + + void recordWait(LockerId id, ResourceId resId, LockMode mode) { + _get(id).recordWait(resId, mode); + } + + void recordWaitTime(LockerId id, ResourceId resId, LockMode mode, uint64_t waitMicros) { + _get(id).recordWaitTime(resId, mode, waitMicros); + } + + void report(SingleThreadedLockStats* outStats) const { + for (int i = 0; i < NumPartitions; i++) { + outStats->append(_partitions[i].stats); + } + } + + void reset() { + for (int i = 0; i < NumPartitions; i++) { + _partitions[i].stats.reset(); + } + } + +private: + // This alignment is a best effort approach to ensure that each partition falls on a + // separate page/cache line in order to avoid false sharing. + struct alignas(stdx::hardware_destructive_interference_size) AlignedLockStats { + AtomicLockStats stats; + }; + + enum { NumPartitions = 8 }; + + + AtomicLockStats& _get(LockerId id) { + return _partitions[id % NumPartitions].stats; + } + + + AlignedLockStats _partitions[NumPartitions]; +}; + +// How often (in millis) to check for deadlock if a lock has not been granted for some time +const Milliseconds MaxWaitTime = Milliseconds(500); + +// Dispenses unique LockerId identifiers +AtomicWord idCounter(0); + +// Tracks lock statistics across all Locker instances. Distributes stats across multiple buckets +// indexed by LockerId in order to minimize concurrent access conflicts. +PartitionedInstanceWideLockStats globalStats; + +} // namespace + +LockManager* getGlobalLockManager() { + auto serviceContext = getGlobalServiceContext(); + invariant(serviceContext); + return LockManager::get(serviceContext); +} + +bool LockerImpl::_shouldDelayUnlock(ResourceId resId, LockMode mode) const { + switch (resId.getType()) { + case RESOURCE_MUTEX: + return false; + + case RESOURCE_GLOBAL: + case RESOURCE_TENANT: + case RESOURCE_DATABASE: + case RESOURCE_COLLECTION: + case RESOURCE_METADATA: + break; + + default: + MONGO_UNREACHABLE; + } + + switch (mode) { + case MODE_X: + case MODE_IX: + return true; + + case MODE_IS: + case MODE_S: + return _sharedLocksShouldTwoPhaseLock; + + default: + MONGO_UNREACHABLE; + } +} + +bool LockerImpl::isW() const { + return getLockMode(resourceIdGlobal) == MODE_X; +} + +bool LockerImpl::isR() const { + return getLockMode(resourceIdGlobal) == MODE_S; +} + +bool LockerImpl::isLocked() const { + return getLockMode(resourceIdGlobal) != MODE_NONE; +} + +bool LockerImpl::isWriteLocked() const { + return isLockHeldForMode(resourceIdGlobal, MODE_IX); +} + +bool LockerImpl::isReadLocked() const { + return isLockHeldForMode(resourceIdGlobal, MODE_IS); +} + +bool LockerImpl::isRSTLExclusive() const { + return getLockMode(resourceIdReplicationStateTransitionLock) == MODE_X; +} + +bool LockerImpl::isRSTLLocked() const { + return getLockMode(resourceIdReplicationStateTransitionLock) != MODE_NONE; +} + +void LockerImpl::dump() const { + struct Entry { + ResourceId key; + LockRequest::Status status; + LockMode mode; + unsigned int recursiveCount; + unsigned int unlockPending; + + BSONObj toBSON() const { + BSONObjBuilder b; + b.append("key", key.toString()); + b.append("status", lockRequestStatusName(status)); + b.append("recursiveCount", static_cast(recursiveCount)); + b.append("unlockPending", static_cast(unlockPending)); + b.append("mode", modeName(mode)); + return b.obj(); + } + std::string toString() const { + return tojson(toBSON()); + } + }; + std::vector entries; + { + auto lg = stdx::lock_guard(_lock); + for (auto it = _requests.begin(); !it.finished(); it.next()) + entries.push_back( + {it.key(), it->status, it->mode, it->recursiveCount, it->unlockPending}); + } + LOGV2(20523, + "Locker id {id} status: {requests}", + "Locker status", + "id"_attr = _id, + "requests"_attr = entries); +} + +void LockerImpl::_dumpLockerAndLockManagerRequests() { + // Log the _requests that this locker holds. This will provide identifying information to cross + // reference with the LockManager dump below for extra information. + dump(); + + // Log the LockManager's lock information. Given the locker 'dump()' above, we should be able to + // easily cross reference to find the lock info matching this operation. The LockManager can + // safely access (under internal locks) the LockRequest data that the locker cannot. + BSONObjBuilder builder; + auto lockToClientMap = LockManager::getLockToClientMap(getGlobalServiceContext()); + getGlobalLockManager()->getLockInfoBSON(lockToClientMap, &builder); + auto lockInfo = builder.done(); + LOGV2_ERROR(5736000, "Operation ending while holding locks.", "LockInfo"_attr = lockInfo); +} + + +// +// CondVarLockGrantNotification +// + +CondVarLockGrantNotification::CondVarLockGrantNotification() { + clear(); +} + +void CondVarLockGrantNotification::clear() { + _result = LOCK_INVALID; +} + +LockResult CondVarLockGrantNotification::wait(Milliseconds timeout) { + stdx::unique_lock lock(_mutex); + return _cond.wait_for( + lock, timeout.toSystemDuration(), [this] { return _result != LOCK_INVALID; }) + ? _result + : LOCK_TIMEOUT; +} + +LockResult CondVarLockGrantNotification::wait(OperationContext* opCtx, Milliseconds timeout) { + invariant(opCtx); + stdx::unique_lock lock(_mutex); + if (opCtx->waitForConditionOrInterruptFor( + _cond, lock, timeout, [this] { return _result != LOCK_INVALID; })) { + // Because waitForConditionOrInterruptFor evaluates the predicate before checking for + // interrupt, it is possible that a killed operation can acquire a lock if the request is + // granted quickly. For that reason, it is necessary to check if the operation has been + // killed at least once before accepting the lock grant. + opCtx->checkForInterrupt(); + return _result; + } + return LOCK_TIMEOUT; +} + +void CondVarLockGrantNotification::notify(ResourceId resId, LockResult result) { + stdx::unique_lock lock(_mutex); + invariant(_result == LOCK_INVALID); + _result = result; + + _cond.notify_all(); +} + +// +// Locker +// + +LockerImpl::LockerImpl(ServiceContext* serviceCtx) + : _id(idCounter.addAndFetch(1)), + _wuowNestingLevel(0), + _threadId(stdx::this_thread::get_id()), + _ticketHolderManager(TicketHolderManager::get(serviceCtx)) {} + +stdx::thread::id LockerImpl::getThreadId() const { + return _threadId; +} + +void LockerImpl::updateThreadIdToCurrentThread() { + _threadId = stdx::this_thread::get_id(); +} + +void LockerImpl::unsetThreadId() { + _threadId = stdx::thread::id(); // Reset to represent a non-executing thread. +} + +LockerImpl::~LockerImpl() { + // Cannot delete the Locker while there are still outstanding requests, because the + // LockManager may attempt to access deleted memory. Besides it is probably incorrect + // to delete with unaccounted locks anyways. + invariant(!inAWriteUnitOfWork()); + invariant(_numResourcesToUnlockAtEndUnitOfWork == 0); + invariant(!_ticket || !_ticket->valid()); + + if (!_requests.empty()) { + _dumpLockerAndLockManagerRequests(); + } + invariant(_requests.empty()); + + invariant(_modeForTicket == MODE_NONE); +} + +Locker::ClientState LockerImpl::getClientState() const { + auto state = _clientState.load(); + if (state == kActiveReader && hasLockPending()) + state = kQueuedReader; + if (state == kActiveWriter && hasLockPending()) + state = kQueuedWriter; + + return state; +} + +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; + + if (_acquireTicket(opCtx, _modeForTicket, Date_t::now())) { + return; + } + + do { + for (auto it = _requests.begin(); it; it.next()) { + invariant(it->mode == LockMode::MODE_IS || it->mode == LockMode::MODE_IX); + opCtx->checkForInterrupt(); + + // If we've reached this point then that means we tried to acquire a ticket but were + // unsuccessful, implying that tickets are currently exhausted. Additionally, since + // we're holding an IS or IX lock for this resource, any pending requests for the same + // resource must be S or X and will not be able to be granted. Thus, since such a + // pending lock request may also be holding a ticket, if there are any present we fail + // this ticket reacquisition in order to avoid a deadlock. + uassert(ErrorCodes::LockTimeout, + fmt::format("Unable to acquire ticket with mode '{}' due to detected lock " + "conflict for resource {}", + _modeForTicket, + it.key().toString()), + !getGlobalLockManager()->hasConflictingRequests(it.key(), it.objAddr())); + } + } while (!_acquireTicket(opCtx, _modeForTicket, Date_t::now() + Milliseconds{100})); +} + +bool LockerImpl::_acquireTicket(OperationContext* opCtx, LockMode mode, Date_t deadline) { + // Upon startup, the holder is not guaranteed to be initialized. + auto holder = _ticketHolderManager ? _ticketHolderManager->getTicketHolder(mode) : nullptr; + const bool reader = isSharedLockMode(mode); + + if (!shouldWaitForTicket() && holder) { + holder->reportImmediatePriorityAdmission(); + } else if (mode != MODE_X && mode != MODE_NONE && holder) { + // MODE_X is exclusive of all other locks, thus acquiring a ticket is unnecessary. + _clientState.store(reader ? kQueuedReader : kQueuedWriter); + // If the ticket wait is interrupted, restore the state of the client. + ScopeGuard restoreStateOnErrorGuard([&] { _clientState.store(kInactive); }); + + // Acquiring a ticket is a potentially blocking operation. This must not be called after a + // transaction timestamp has been set, indicating this transaction has created an oplog + // hole. + invariant(!opCtx->recoveryUnit()->isTimestamped()); + + if (auto ticket = holder->waitForTicketUntil( + _uninterruptibleLocksRequested ? nullptr : opCtx, &_admCtx, deadline)) { + _ticket = std::move(*ticket); + } else { + return false; + } + restoreStateOnErrorGuard.dismiss(); + } + + _clientState.store(reader ? kActiveReader : kActiveWriter); + return true; +} + +void LockerImpl::lockGlobal(OperationContext* opCtx, LockMode mode, Date_t deadline) { + dassert(isLocked() == (_modeForTicket != MODE_NONE)); + if (_modeForTicket == MODE_NONE) { + if (_uninterruptibleLocksRequested) { + // Ignore deadline. + invariant(_acquireTicket(opCtx, mode, Date_t::max())); + } else { + auto beforeAcquire = Date_t::now(); + uassert(ErrorCodes::LockTimeout, + str::stream() << "Unable to acquire ticket with mode '" << mode + << "' within a max lock request timeout of '" + << Date_t::now() - beforeAcquire << "' milliseconds.", + _acquireTicket(opCtx, mode, deadline)); + } + _modeForTicket = mode; + } else if (TestingProctor::instance().isEnabled() && !isModeCovered(mode, _modeForTicket)) { + LOGV2_FATAL( + 6614500, + "Ticket held does not cover requested mode for global lock. Global lock upgrades are " + "not allowed", + "held"_attr = modeName(_modeForTicket), + "requested"_attr = modeName(mode)); + } + + const LockResult result = _lockBegin(opCtx, resourceIdGlobal, mode); + // Fast, uncontended path + if (result == LOCK_OK) + return; + + invariant(result == LOCK_WAITING); + _lockComplete(opCtx, resourceIdGlobal, mode, deadline, nullptr); +} + +bool LockerImpl::unlockGlobal() { + if (!unlock(resourceIdGlobal)) { + return false; + } + + invariant(!inAWriteUnitOfWork()); + + LockRequestsMap::Iterator it = _requests.begin(); + while (!it.finished()) { + // If we're here we should only have one reference to any lock. It is a programming + // error for any lock used with multi-granularity locking to have more references than + // the global lock, because every scope starts by calling lockGlobal. + const auto resType = it.key().getType(); + if (resType == RESOURCE_GLOBAL || resType == RESOURCE_MUTEX) { + it.next(); + } else { + invariant(_unlockImpl(&it)); + } + } + + return true; +} + +void LockerImpl::beginWriteUnitOfWork() { + _wuowNestingLevel++; +} + +void LockerImpl::endWriteUnitOfWork() { + invariant(_wuowNestingLevel > 0); + + if (--_wuowNestingLevel > 0) { + // Don't do anything unless leaving outermost WUOW. + return; + } + + LockRequestsMap::Iterator it = _requests.begin(); + while (_numResourcesToUnlockAtEndUnitOfWork > 0) { + if (it->unlockPending) { + invariant(!it.finished()); + _numResourcesToUnlockAtEndUnitOfWork--; + } + while (it->unlockPending > 0) { + // If a lock is converted, unlock() may be called multiple times on a resource within + // the same WriteUnitOfWork. All such unlock() requests must thus be fulfilled here. + it->unlockPending--; + unlock(it.key()); + } + it.next(); + } +} + +void LockerImpl::releaseWriteUnitOfWork(WUOWLockSnapshot* stateOut) { + stateOut->wuowNestingLevel = _wuowNestingLevel; + _wuowNestingLevel = 0; + + for (auto it = _requests.begin(); _numResourcesToUnlockAtEndUnitOfWork > 0; it.next()) { + if (it->unlockPending) { + while (it->unlockPending) { + it->unlockPending--; + stateOut->unlockPendingLocks.push_back({it.key(), it->mode}); + } + _numResourcesToUnlockAtEndUnitOfWork--; + } + } +} + +void LockerImpl::restoreWriteUnitOfWork(const WUOWLockSnapshot& stateToRestore) { + invariant(_numResourcesToUnlockAtEndUnitOfWork == 0); + invariant(!inAWriteUnitOfWork()); + + for (auto& lock : stateToRestore.unlockPendingLocks) { + auto it = _requests.begin(); + while (it && !(it.key() == lock.resourceId && it->mode == lock.mode)) { + it.next(); + } + invariant(!it.finished()); + if (!it->unlockPending) { + _numResourcesToUnlockAtEndUnitOfWork++; + } + it->unlockPending++; + } + // Equivalent to call beginWriteUnitOfWork() multiple times. + _wuowNestingLevel = stateToRestore.wuowNestingLevel; +} + +void LockerImpl::releaseWriteUnitOfWorkAndUnlock(LockSnapshot* stateOut) { + // Only the global WUOW can be released, since we never need to release and restore + // nested WUOW's. Thus we don't have to remember the nesting level. + invariant(_wuowNestingLevel == 1); + --_wuowNestingLevel; + invariant(!isGlobalLockedRecursively()); + + // All locks should be pending to unlock. + invariant(_requests.size() == _numResourcesToUnlockAtEndUnitOfWork); + for (auto it = _requests.begin(); it; it.next()) { + // No converted lock so we don't need to unlock more than once. + invariant(it->unlockPending == 1); + it->unlockPending--; + } + _numResourcesToUnlockAtEndUnitOfWork = 0; + + saveLockStateAndUnlock(stateOut); +} + +void LockerImpl::restoreWriteUnitOfWorkAndLock(OperationContext* opCtx, + const LockSnapshot& stateToRestore) { + if (stateToRestore.globalMode != MODE_NONE) { + restoreLockState(opCtx, stateToRestore); + } + + invariant(_numResourcesToUnlockAtEndUnitOfWork == 0); + for (auto it = _requests.begin(); it; it.next()) { + invariant(_shouldDelayUnlock(it.key(), (it->mode))); + invariant(it->unlockPending == 0); + it->unlockPending++; + } + _numResourcesToUnlockAtEndUnitOfWork = static_cast(_requests.size()); + + beginWriteUnitOfWork(); +} + +void LockerImpl::lock(OperationContext* opCtx, ResourceId resId, LockMode mode, Date_t deadline) { + // `lockGlobal` must be called to lock `resourceIdGlobal`. + invariant(resId != resourceIdGlobal); + + const LockResult result = _lockBegin(opCtx, resId, mode); + + // Fast, uncontended path + if (result == LOCK_OK) + return; + + invariant(result == LOCK_WAITING); + _lockComplete(opCtx, resId, mode, deadline, nullptr); +} + +void LockerImpl::downgrade(ResourceId resId, LockMode newMode) { + LockRequestsMap::Iterator it = _requests.find(resId); + getGlobalLockManager()->downgrade(it.objAddr(), newMode); +} + +bool LockerImpl::unlock(ResourceId resId) { + LockRequestsMap::Iterator it = _requests.find(resId); + + // Don't attempt to unlock twice. This can happen when an interrupted global lock is destructed. + if (it.finished()) + return false; + + if (inAWriteUnitOfWork() && _shouldDelayUnlock(it.key(), (it->mode))) { + // Only delay unlocking if the lock is not acquired more than once. Otherwise, we can simply + // call _unlockImpl to decrement recursiveCount instead of incrementing unlockPending. This + // is safe because the lock is still being held in the strongest mode necessary. + if (it->recursiveCount > 1) { + // Invariant that the lock is still being held. + invariant(!_unlockImpl(&it)); + return false; + } + if (!it->unlockPending) { + _numResourcesToUnlockAtEndUnitOfWork++; + } + it->unlockPending++; + // unlockPending will be incremented if a lock is converted or acquired in the same mode + // recursively, and unlock() is called multiple times on one ResourceId. + invariant(it->unlockPending <= it->recursiveCount); + return false; + } + + return _unlockImpl(&it); +} + +bool LockerImpl::unlockRSTLforPrepare() { + auto rstlRequest = _requests.find(resourceIdReplicationStateTransitionLock); + + // Don't attempt to unlock twice. This can happen when an interrupted global lock is destructed. + if (!rstlRequest) + return false; + + // If the RSTL is 'unlockPending' and we are fully unlocking it, then we do not want to + // attempt to unlock the RSTL when the WUOW ends, since it will already be unlocked. + if (rstlRequest->unlockPending) { + rstlRequest->unlockPending = 0; + _numResourcesToUnlockAtEndUnitOfWork--; + } + + // Reset the recursiveCount to 1 so that we fully unlock the RSTL. Since it will be fully + // unlocked, any future unlocks will be noops anyways. + rstlRequest->recursiveCount = 1; + + return _unlockImpl(&rstlRequest); +} + +LockMode LockerImpl::getLockMode(ResourceId resId) const { + scoped_spinlock scopedLock(_lock); + + const LockRequestsMap::ConstIterator it = _requests.find(resId); + if (!it) + return MODE_NONE; + + return it->mode; +} + +bool LockerImpl::isLockHeldForMode(ResourceId resId, LockMode mode) const { + return isModeCovered(mode, getLockMode(resId)); +} + +boost::optional LockerImpl::_globalAndTenantLocksImplyDBOrCollectionLockedForMode( + const boost::optional& tenantId, LockMode lockMode) const { + if (isW()) { + return true; + } + if (isR() && isSharedLockMode(lockMode)) { + return true; + } + if (tenantId) { + const ResourceId tenantResourceId{ResourceType::RESOURCE_TENANT, *tenantId}; + switch (getLockMode(tenantResourceId)) { + case MODE_NONE: + return false; + case MODE_X: + return true; + case MODE_S: + return isSharedLockMode(lockMode); + case MODE_IX: + case MODE_IS: + break; + default: + MONGO_UNREACHABLE_TASSERT(6671502); + } + } + return boost::none; +} + +bool LockerImpl::isDbLockedForMode(const DatabaseName& dbName, LockMode mode) const { + if (auto lockedForMode = + _globalAndTenantLocksImplyDBOrCollectionLockedForMode(dbName.tenantId(), mode); + lockedForMode) { + return *lockedForMode; + } + + const ResourceId resIdDb(RESOURCE_DATABASE, dbName); + return isLockHeldForMode(resIdDb, mode); +} + +bool LockerImpl::isCollectionLockedForMode(const NamespaceString& nss, LockMode mode) const { + invariant(nss.coll().size()); + + if (!shouldConflictWithSecondaryBatchApplication()) + return true; + + if (auto lockedForMode = + _globalAndTenantLocksImplyDBOrCollectionLockedForMode(nss.tenantId(), mode); + lockedForMode) { + return *lockedForMode; + } + + const ResourceId resIdDb(RESOURCE_DATABASE, nss.dbName()); + LockMode dbMode = getLockMode(resIdDb); + + 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, nss); + return isLockHeldForMode(resIdColl, mode); + } break; + case LockModesCount: + break; + } + + MONGO_UNREACHABLE; + return false; +} + +bool LockerImpl::wasGlobalLockTakenForWrite() const { + return _globalLockMode & ((1 << MODE_IX) | (1 << MODE_X)); +} + +bool LockerImpl::wasGlobalLockTakenInModeConflictingWithWrites() const { + return _wasGlobalLockTakenInModeConflictingWithWrites.load(); +} + +bool LockerImpl::wasGlobalLockTaken() const { + return _globalLockMode != (1 << MODE_NONE); +} + +void LockerImpl::setGlobalLockTakenInMode(LockMode mode) { + _globalLockMode |= (1 << mode); + + if (mode == MODE_IX || mode == MODE_X || mode == MODE_S) { + _wasGlobalLockTakenInModeConflictingWithWrites.store(true); + } +} + +ResourceId LockerImpl::getWaitingResource() const { + scoped_spinlock scopedLock(_lock); + + return _waitingResource; +} + +MONGO_TSAN_IGNORE +void LockerImpl::getLockerInfo(LockerInfo* lockerInfo, + const boost::optional lockStatsBase) const { + invariant(lockerInfo); + + // Zero-out the contents + lockerInfo->locks.clear(); + lockerInfo->waitingResource = ResourceId(); + lockerInfo->stats.reset(); + + _lock.lock(); + LockRequestsMap::ConstIterator it = _requests.begin(); + while (!it.finished()) { + OneLock info; + info.resourceId = it.key(); + info.mode = it->mode; + + lockerInfo->locks.push_back(info); + it.next(); + } + _lock.unlock(); + + std::sort(lockerInfo->locks.begin(), lockerInfo->locks.end()); + + lockerInfo->waitingResource = getWaitingResource(); + lockerInfo->stats.append(_stats); + + // lockStatsBase is a snapshot of lock stats taken when the sub-operation starts. Only + // sub-operations have lockStatsBase. + if (lockStatsBase) + // Adjust the lock stats by subtracting the lockStatsBase. No mutex is needed because + // lockStatsBase is immutable. + lockerInfo->stats.subtract(*lockStatsBase); +} + +boost::optional LockerImpl::getLockerInfo( + const boost::optional lockStatsBase) const { + Locker::LockerInfo lockerInfo; + getLockerInfo(&lockerInfo, lockStatsBase); + return std::move(lockerInfo); +} + +void LockerImpl::saveLockStateAndUnlock(Locker::LockSnapshot* stateOut) { + // We shouldn't be saving and restoring lock state from inside a WriteUnitOfWork. + invariant(!inAWriteUnitOfWork()); + + // Callers must guarantee that they can actually yield. + if (MONGO_unlikely(!canSaveLockState())) { + dump(); + LOGV2_FATAL(7033800, + "Attempted to yield locks but we are either not holding locks, holding a " + "strong MODE_S/MODE_X lock, or holding one recursively"); + } + + // 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 so we store it + // separately from the more pedestrian locks. + auto globalRequest = _requests.find(resourceIdGlobal); + invariant(globalRequest); + + 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 == resType || RESOURCE_COLLECTION == resType || + resId == resourceIdParallelBatchWriterMode || RESOURCE_TENANT == resType || + resId == resourceIdFeatureCompatibilityVersion || + resId == resourceIdReplicationStateTransitionLock); + + // 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()); +} + +void LockerImpl::restoreLockState(OperationContext* opCtx, const Locker::LockSnapshot& state) { + // We shouldn't be restoring lock state from inside a WriteUnitOfWork. + invariant(!inAWriteUnitOfWork()); + invariant(_modeForTicket == MODE_NONE); + invariant(_clientState.load() == kInactive); + + getFlowControlTicket(opCtx, state.globalMode); + + std::vector::const_iterator it = state.locks.begin(); + // If we locked the PBWM, it must be locked before the + // resourceIdFeatureCompatibilityVersion, resourceIdReplicationStateTransitionLock, and + // resourceIdGlobal resources. + if (it != state.locks.end() && it->resourceId == resourceIdParallelBatchWriterMode) { + lock(opCtx, it->resourceId, it->mode); + it++; + } + + // If we locked the FCV lock, it must be locked before the + // resourceIdReplicationStateTransitionLock and resourceIdGlobal resources. + if (it != state.locks.end() && it->resourceId == resourceIdFeatureCompatibilityVersion) { + lock(opCtx, it->resourceId, it->mode); + it++; + } + + // If we locked the RSTL, it must be locked before the resourceIdGlobal resource. + if (it != state.locks.end() && it->resourceId == resourceIdReplicationStateTransitionLock) { + lock(opCtx, it->resourceId, it->mode); + it++; + } + + lockGlobal(opCtx, state.globalMode); + for (; it != state.locks.end(); it++) { + // Ensures we don't acquire locks out of order which can lead to deadlock. + invariant(it->resourceId.getType() != ResourceType::RESOURCE_GLOBAL); + lock(opCtx, it->resourceId, it->mode); + } + invariant(_modeForTicket != MODE_NONE); +} + +MONGO_TSAN_IGNORE +FlowControlTicketholder::CurOp LockerImpl::getFlowControlStats() const { + return _flowControlStats; +} + +MONGO_TSAN_IGNORE +LockResult LockerImpl::_lockBegin(OperationContext* opCtx, ResourceId resId, LockMode mode) { + dassert(!getWaitingResource().isValid()); + + // Operations which are holding open an oplog hole cannot block when acquiring locks. + if (opCtx && !shouldAllowLockAcquisitionOnTimestampedUnitOfWork() && + resId.getType() != RESOURCE_METADATA && resId.getType() != RESOURCE_MUTEX) { + invariant(!opCtx->recoveryUnit()->isTimestamped(), + str::stream() + << "Operation holding open an oplog hole tried to acquire locks. ResourceId: " + << resId << ", mode: " << modeName(mode)); + } + + LockRequest* request; + bool isNew = true; + + LockRequestsMap::Iterator it = _requests.find(resId); + if (!it) { + scoped_spinlock scopedLock(_lock); + LockRequestsMap::Iterator itNew = _requests.insert(resId); + itNew->initNew(this, &_notify); + + request = itNew.objAddr(); + } else { + request = it.objAddr(); + isNew = false; + } + + // If unlockPending is nonzero, that means a LockRequest already exists for this resource but + // is planned to be released at the end of this WUOW due to two-phase locking. Rather than + // unlocking the existing request, we can reuse it if the existing mode matches the new mode. + if (request->unlockPending && isModeCovered(mode, request->mode)) { + request->unlockPending--; + if (!request->unlockPending) { + _numResourcesToUnlockAtEndUnitOfWork--; + } + return LOCK_OK; + } + + // Making this call here will record lock re-acquisitions and conversions as well. + globalStats.recordAcquisition(_id, resId, mode); + _stats.recordAcquisition(resId, mode); + + // Give priority to the full modes for Global, PBWM, and RSTL resources so we don't stall global + // operations such as shutdown or stepdown. + const ResourceType resType = resId.getType(); + if (resType == RESOURCE_GLOBAL) { + if (mode == MODE_S || mode == MODE_X) { + request->enqueueAtFront = true; + request->compatibleFirst = true; + } + } else if (resType != RESOURCE_MUTEX) { + // This is all sanity checks that the global locks are always be acquired + // before any other lock has been acquired and they must be in sync with the nesting. + if (kDebugBuild) { + const LockRequestsMap::Iterator itGlobal = _requests.find(resourceIdGlobal); + invariant(itGlobal->recursiveCount > 0); + invariant(itGlobal->mode != MODE_NONE); + }; + } + + // The notification object must be cleared before we invoke the lock manager, because + // otherwise we might reset state if the lock becomes granted very fast. + _notify.clear(); + + LockResult result = isNew ? getGlobalLockManager()->lock(resId, request, mode) + : getGlobalLockManager()->convert(resId, request, mode); + + if (result == LOCK_WAITING) { + globalStats.recordWait(_id, resId, mode); + _stats.recordWait(resId, mode); + _setWaitingResource(resId); + } else if (result == LOCK_OK && opCtx && _uninterruptibleLocksRequested == 0) { + // Lock acquisitions are not allowed to succeed when opCtx is marked as interrupted, unless + // the caller requested an uninterruptible lock. + auto interruptStatus = opCtx->checkForInterruptNoAssert(); + if (!interruptStatus.isOK()) { + auto unlockIt = _requests.find(resId); + invariant(unlockIt); + _unlockImpl(&unlockIt); + uassertStatusOK(interruptStatus); + } + } + + return result; +} + +void LockerImpl::_lockComplete(OperationContext* opCtx, + ResourceId resId, + LockMode mode, + Date_t deadline, + const LockTimeoutCallback& onTimeout) { + // Operations which are holding open an oplog hole cannot block when acquiring locks. Lock + // requests entering this function have been queued up and will be granted the lock as soon as + // the lock is released, which is a blocking operation. + if (opCtx && !shouldAllowLockAcquisitionOnTimestampedUnitOfWork() && + resId.getType() != RESOURCE_METADATA && resId.getType() != RESOURCE_MUTEX) { + invariant(!opCtx->recoveryUnit()->isTimestamped(), + str::stream() + << "Operation holding open an oplog hole tried to acquire locks. ResourceId: " + << resId << ", mode: " << modeName(mode)); + } + + // Clean up the state on any failed lock attempts. + ScopeGuard unlockOnErrorGuard([&] { + LockRequestsMap::Iterator it = _requests.find(resId); + invariant(it); + _unlockImpl(&it); + _setWaitingResource(ResourceId()); + }); + + // This failpoint is used to time out non-intent locks if they cannot be granted immediately + // for user operations. Testing-only. + const bool isUserOperation = opCtx && opCtx->getClient()->isFromUserConnection(); + if (!_uninterruptibleLocksRequested && isUserOperation && + MONGO_unlikely(failNonIntentLocksIfWaitNeeded.shouldFail())) { + uassert(ErrorCodes::LockTimeout, + str::stream() << "Cannot immediately acquire lock '" << resId.toString() + << "'. Timing out due to failpoint.", + (mode == MODE_IS || mode == MODE_IX)); + } + + LockResult result; + Milliseconds timeout; + if (deadline == Date_t::max()) { + timeout = Milliseconds::max(); + } else if (deadline <= Date_t()) { + timeout = Milliseconds(0); + } else { + timeout = deadline - Date_t::now(); + } + timeout = std::min(timeout, _maxLockTimeout ? *_maxLockTimeout : Milliseconds::max()); + if (_uninterruptibleLocksRequested) { + timeout = Milliseconds::max(); + } + + // Don't go sleeping without bound in order to be able to report long waits. + Milliseconds waitTime = std::min(timeout, MaxWaitTime); + const uint64_t startOfTotalWaitTime = curTimeMicros64(); + uint64_t startOfCurrentWaitTime = startOfTotalWaitTime; + + while (true) { + // It is OK if this call wakes up spuriously, because we re-evaluate the remaining + // wait time anyways. + // If we have an operation context, we want to use its interruptible wait so that + // pending lock acquisitions can be cancelled, so long as no callers have requested an + // uninterruptible lock. + if (opCtx && _uninterruptibleLocksRequested == 0) { + result = _notify.wait(opCtx, waitTime); + } else { + result = _notify.wait(waitTime); + } + + // Account for the time spent waiting on the notification object + const uint64_t curTimeMicros = curTimeMicros64(); + const uint64_t elapsedTimeMicros = curTimeMicros - startOfCurrentWaitTime; + startOfCurrentWaitTime = curTimeMicros; + + globalStats.recordWaitTime(_id, resId, mode, elapsedTimeMicros); + _stats.recordWaitTime(resId, mode, elapsedTimeMicros); + + if (result == LOCK_OK) + break; + + // If infinite timeout was requested, just keep waiting + if (timeout == Milliseconds::max()) { + continue; + } + + const auto totalBlockTime = duration_cast( + Microseconds(int64_t(curTimeMicros - startOfTotalWaitTime))); + waitTime = (totalBlockTime < timeout) ? std::min(timeout - totalBlockTime, MaxWaitTime) + : Milliseconds(0); + + // Check if the lock acquisition has timed out. If we have an operation context and client + // we can provide additional diagnostics data. + if (waitTime == Milliseconds(0)) { + if (onTimeout) { + onTimeout(); + } + std::string timeoutMessage = str::stream() + << "Unable to acquire " << modeName(mode) << " lock on '" << resId.toString() + << "' within " << timeout << "."; + if (opCtx && opCtx->getClient()) { + timeoutMessage = str::stream() + << timeoutMessage << " opId: " << opCtx->getOpID() + << ", op: " << opCtx->getClient()->desc() + << ", connId: " << opCtx->getClient()->getConnectionId() << "."; + } + uasserted(ErrorCodes::LockTimeout, timeoutMessage); + } + } + + invariant(result == LOCK_OK); + unlockOnErrorGuard.dismiss(); + _setWaitingResource(ResourceId()); +} + +void LockerImpl::getFlowControlTicket(OperationContext* opCtx, LockMode lockMode) { + auto ticketholder = FlowControlTicketholder::get(opCtx); + if (ticketholder && lockMode == LockMode::MODE_IX && _clientState.load() == kInactive && + _admCtx.getPriority() != AdmissionContext::Priority::kImmediate && + !_uninterruptibleLocksRequested) { + // FlowControl only acts when a MODE_IX global lock is being taken. The clientState is only + // being modified here to change serverStatus' `globalLock.currentQueue` metrics. This + // method must not exit with a side-effect on the clientState. That value is also used for + // tracking whether other resources need to be released. + _clientState.store(kQueuedWriter); + ScopeGuard restoreState([&] { _clientState.store(kInactive); }); + // Acquiring a ticket is a potentially blocking operation. This must not be called after a + // transaction timestamp has been set, indicating this transaction has created an oplog + // hole. + invariant(!opCtx->recoveryUnit()->isTimestamped()); + ticketholder->getTicket(opCtx, &_flowControlStats); + } +} + +LockResult LockerImpl::lockRSTLBegin(OperationContext* opCtx, LockMode mode) { + bool testOnly = false; + + if (MONGO_unlikely(enableTestOnlyFlagforRSTL.shouldFail())) { + testOnly = true; + } + + invariant(testOnly || mode == MODE_IX || mode == MODE_X); + return _lockBegin(opCtx, resourceIdReplicationStateTransitionLock, mode); +} + +void LockerImpl::lockRSTLComplete(OperationContext* opCtx, + LockMode mode, + Date_t deadline, + const LockTimeoutCallback& onTimeout) { + _lockComplete(opCtx, resourceIdReplicationStateTransitionLock, mode, deadline, onTimeout); +} + +void LockerImpl::releaseTicket() { + invariant(_modeForTicket != MODE_NONE); + _releaseTicket(); +} + +void LockerImpl::_releaseTicket() { + _ticket.reset(); + _clientState.store(kInactive); +} + +bool LockerImpl::_unlockImpl(LockRequestsMap::Iterator* it) { + if (getGlobalLockManager()->unlock(it->objAddr())) { + if (it->key() == resourceIdGlobal) { + invariant(_modeForTicket != MODE_NONE); + + // We may have already released our ticket through a call to releaseTicket(). + if (_clientState.load() != kInactive) { + _releaseTicket(); + } + + _modeForTicket = MODE_NONE; + } + + scoped_spinlock scopedLock(_lock); + it->remove(); + + return true; + } + + return false; +} + +bool LockerImpl::isGlobalLockedRecursively() { + auto globalLockRequest = _requests.find(resourceIdGlobal); + return !globalLockRequest.finished() && globalLockRequest->recursiveCount > 1; +} + +bool LockerImpl::canSaveLockState() { + // We cannot yield strong global locks. + if (_modeForTicket == MODE_S || _modeForTicket == MODE_X) { + return false; + } + + // If we don't have a global lock, we do not yield. + if (_modeForTicket == MODE_NONE) { + auto globalRequest = _requests.find(resourceIdGlobal); + invariant(!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; + } + + for (auto it = _requests.begin(); !it.finished(); it.next()) { + const ResourceId resId = it.key(); + const ResourceType resType = resId.getType(); + if (resType == RESOURCE_MUTEX) + continue; + + // If any lock 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. This logic applies to all + // locks in the hierarchy. + if (it->recursiveCount > 1) { + return false; + } + + // We cannot yield any other lock in a strong lock mode. + if (it->mode == MODE_S || it->mode == MODE_X) { + return false; + } + } + + return true; +} + +void LockerImpl::_setWaitingResource(ResourceId resId) { + scoped_spinlock scopedLock(_lock); + + _waitingResource = resId; +} + +// +// Auto classes +// + +namespace { +/** + * Periodically purges unused lock buckets. The first time the lock is used again after + * cleanup it needs to be allocated, and similarly, every first use by a client for an intent + * mode may need to create a partitioned lock head. Cleanup is done roughly once a minute. + */ +class UnusedLockCleaner : PeriodicTask { +public: + std::string taskName() const { + return "UnusedLockCleaner"; + } + + void taskDoWork() { + LOGV2_DEBUG(20524, 2, "cleaning up unused lock buckets of the global lock manager"); + getGlobalLockManager()->cleanupUnusedLocks(); + } +} unusedLockCleaner; +} // namespace + +// +// Standalone functions +// + +void reportGlobalLockingStats(SingleThreadedLockStats* outStats) { + globalStats.report(outStats); +} + +void resetGlobalLockStats() { + globalStats.reset(); +} + +} // namespace mongo diff --git a/src/mongo/db/concurrency/locker_impl.h b/src/mongo/db/concurrency/locker_impl.h new file mode 100644 index 00000000000..2f83580de83 --- /dev/null +++ b/src/mongo/db/concurrency/locker_impl.h @@ -0,0 +1,439 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side 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 Server Side 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 + +#include "mongo/db/concurrency/fast_map_noalloc.h" +#include "mongo/db/concurrency/lock_manager_defs.h" +#include "mongo/db/concurrency/locker.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/storage/ticketholder_manager.h" +#include "mongo/platform/atomic_word.h" +#include "mongo/util/concurrency/spin_lock.h" +#include "mongo/util/concurrency/ticketholder.h" + +namespace mongo { + +/** + * Notfication callback, which stores the last notification result and signals a condition + * variable, which can be waited on. + */ +class CondVarLockGrantNotification : public LockGrantNotification { + CondVarLockGrantNotification(const CondVarLockGrantNotification&) = delete; + CondVarLockGrantNotification& operator=(const CondVarLockGrantNotification&) = delete; + +public: + CondVarLockGrantNotification(); + + /** + * Clears the object so it can be reused. + */ + void clear(); + + /** + * Uninterruptible blocking method, which waits for the notification to fire. + * + * @param timeout How many milliseconds to wait before returning LOCK_TIMEOUT. + */ + LockResult wait(Milliseconds timeout); + + /** + * Interruptible blocking method, which waits for the notification to fire or an interrupt from + * the operation context. + * + * @param opCtx OperationContext to wait on for an interrupt. + * @param timeout How many milliseconds to wait before returning LOCK_TIMEOUT. + */ + LockResult wait(OperationContext* opCtx, Milliseconds timeout); + +private: + virtual void notify(ResourceId resId, LockResult result); + + // These two go together to implement the conditional variable pattern. + Mutex _mutex = MONGO_MAKE_LATCH("CondVarLockGrantNotification::_mutex"); + stdx::condition_variable _cond; + + // Result from the last call to notify + LockResult _result; +}; + +/** + * Interface for acquiring locks. One of those objects will have to be instantiated for each + * request (transaction). + * + * Lock/unlock methods must always be called from a single thread. + * + * All instances reference a single global lock manager. + * + */ +class LockerImpl : public Locker { +public: + /** + * Instantiates new locker. Must be given a unique identifier for disambiguation. Lockers + * having the same identifier will not conflict on lock acquisition. + */ + LockerImpl(ServiceContext* serviceContext); + + virtual ~LockerImpl(); + + virtual ClientState getClientState() const; + + virtual LockerId getId() const { + return _id; + } + + stdx::thread::id getThreadId() const override; + + void updateThreadIdToCurrentThread() override; + void unsetThreadId() override; + + void setSharedLocksShouldTwoPhaseLock(bool sharedLocksShouldTwoPhaseLock) override { + _sharedLocksShouldTwoPhaseLock = sharedLocksShouldTwoPhaseLock; + } + + void setMaxLockTimeout(Milliseconds maxTimeout) override { + _maxLockTimeout = maxTimeout; + } + + bool hasMaxLockTimeout() override { + return static_cast(_maxLockTimeout); + } + + void unsetMaxLockTimeout() override { + _maxLockTimeout = boost::none; + } + + /** + * Acquires the ticket within the deadline (or _maxLockTimeout) and tries to grab the lock. + */ + virtual void lockGlobal(OperationContext* opCtx, + LockMode mode, + Date_t deadline = Date_t::max()); + + virtual bool unlockGlobal(); + + virtual LockResult lockRSTLBegin(OperationContext* opCtx, LockMode mode); + virtual void lockRSTLComplete(OperationContext* opCtx, + LockMode mode, + Date_t deadline, + const LockTimeoutCallback& onTimeout); + + virtual bool unlockRSTLforPrepare(); + + virtual void beginWriteUnitOfWork() override; + virtual void endWriteUnitOfWork() override; + + virtual bool inAWriteUnitOfWork() const { + return _wuowNestingLevel > 0; + } + + bool wasGlobalLockTakenForWrite() const override; + + bool wasGlobalLockTakenInModeConflictingWithWrites() const override; + + bool wasGlobalLockTaken() const override; + + void setGlobalLockTakenInMode(LockMode mode) override; + + /** + * Requests a lock for resource 'resId' with mode 'mode'. An OperationContext 'opCtx' must be + * provided to interrupt waiting on the locker condition variable that indicates status of + * the lock acquisition. A lock operation would otherwise wait until a timeout or the lock is + * granted. + */ + virtual void lock(OperationContext* opCtx, + ResourceId resId, + LockMode mode, + Date_t deadline = Date_t::max()); + + virtual void lock(ResourceId resId, LockMode mode, Date_t deadline = Date_t::max()) { + lock(nullptr, resId, mode, deadline); + } + + virtual void downgrade(ResourceId resId, LockMode newMode); + + virtual bool unlock(ResourceId resId); + + virtual LockMode getLockMode(ResourceId resId) const; + virtual bool isLockHeldForMode(ResourceId resId, LockMode mode) const; + virtual bool isDbLockedForMode(const DatabaseName& dbName, LockMode mode) const; + virtual bool isCollectionLockedForMode(const NamespaceString& nss, LockMode mode) const; + + virtual ResourceId getWaitingResource() const; + + virtual void getLockerInfo(LockerInfo* lockerInfo, + boost::optional lockStatsBase) const; + virtual boost::optional getLockerInfo( + boost::optional lockStatsBase) const final; + + virtual void saveLockStateAndUnlock(LockSnapshot* stateOut); + + virtual void restoreLockState(OperationContext* opCtx, const LockSnapshot& stateToRestore); + + void releaseWriteUnitOfWorkAndUnlock(LockSnapshot* stateOut) override; + void restoreWriteUnitOfWorkAndLock(OperationContext* opCtx, + const LockSnapshot& stateToRestore) override; + + void releaseWriteUnitOfWork(WUOWLockSnapshot* stateOut) override; + void restoreWriteUnitOfWork(const WUOWLockSnapshot& stateToRestore) override; + + virtual void releaseTicket(); + virtual void reacquireTicket(OperationContext* opCtx); + + bool hasReadTicket() const override { + return _modeForTicket == MODE_IS || _modeForTicket == MODE_S; + } + + bool hasWriteTicket() const override { + return _modeForTicket == MODE_IX || _modeForTicket == MODE_X; + } + + void getFlowControlTicket(OperationContext* opCtx, LockMode lockMode) override; + + FlowControlTicketholder::CurOp getFlowControlStats() const override; + + // + // Below functions are for testing only. + // + + FastMapNoAlloc getRequestsForTest() const { + scoped_spinlock scopedLock(_lock); + return _requests; + } + + LockResult lockBeginForTest(OperationContext* opCtx, ResourceId resId, LockMode mode) { + return _lockBegin(opCtx, resId, mode); + } + + void lockCompleteForTest(OperationContext* opCtx, + ResourceId resId, + LockMode mode, + Date_t deadline) { + _lockComplete(opCtx, resId, mode, deadline, nullptr); + } + +private: + typedef FastMapNoAlloc LockRequestsMap; + + /** + * Allows for lock requests to be requested in a non-blocking way. There can be only one + * outstanding pending lock request per locker object. + * + * _lockBegin posts a request to the lock manager for the specified lock to be acquired, + * which either immediately grants the lock, or puts the requestor on the conflict queue + * and returns immediately with the result of the acquisition. The result can be one of: + * + * LOCK_OK - Nothing more needs to be done. The lock is granted. + * LOCK_WAITING - The request has been queued up and will be granted as soon as the lock + * is free. If this result is returned, typically _lockComplete needs to be called in + * order to wait for the actual grant to occur. If the caller no longer needs to wait + * for the grant to happen, unlock needs to be called with the same resource passed + * to _lockBegin. + * + * In other words for each call to _lockBegin, which does not return LOCK_OK, there needs to + * be a corresponding call to either _lockComplete or unlock. + * + * If an operation context is provided that represents an interrupted operation, _lockBegin will + * throw an exception whenever it would have been possible to grant the lock with LOCK_OK. This + * behavior can be disabled with an UninterruptibleLockGuard. + * + * NOTE: These methods are not public and should only be used inside the class + * implementation and for unit-tests and not called directly. + */ + LockResult _lockBegin(OperationContext* opCtx, ResourceId resId, LockMode mode); + + /** + * Waits for the completion of a lock, previously requested through _lockBegin/ + * Must only be called, if _lockBegin returned LOCK_WAITING. + * + * @param opCtx Operation context that, if not null, will be used to allow interruptible lock + * acquisition. + * @param resId Resource id which was passed to an earlier _lockBegin call. Must match. + * @param mode Mode which was passed to an earlier _lockBegin call. Must match. + * @param deadline The absolute time point when this lock acquisition will time out, if not yet + * granted. + * @param onTimeout Callback which will run if the lock acquisition is about to time out. + * + * Throws an exception if it is interrupted. + */ + void _lockComplete(OperationContext* opCtx, + ResourceId resId, + LockMode mode, + Date_t deadline, + const LockTimeoutCallback& onTimeout); + + /** + * The main functionality of the unlock method, except accepts iterator in order to avoid + * additional lookups during unlockGlobal. Frees locks immediately, so must not be called from + * inside a WUOW. + */ + bool _unlockImpl(LockRequestsMap::Iterator* it); + + /** + * Whether we should use two phase locking. Returns true if the particular lock's release should + * be delayed until the end of the operation. + * + * We delay release of write operation locks (X, IX) in order to ensure that the data changes + * they protect are committed successfully. endWriteUnitOfWork will release them afterwards. + * This protects other threads from seeing inconsistent in-memory state. + * + * Shared locks (S, IS) will also participate in two-phase locking if + * '_sharedLocksShouldTwoPhaseLock' is true. This will protect open storage engine transactions + * across network calls. + */ + bool _shouldDelayUnlock(ResourceId resId, LockMode mode) const; + + /** + * Releases the ticket for the Locker. + */ + void _releaseTicket(); + + /** + * Acquires a ticket for the Locker under 'mode'. + * Returns true if a ticket is successfully acquired. + * false if it cannot acquire a ticket within 'deadline'. + * It may throw an exception when it is interrupted. + */ + bool _acquireTicket(OperationContext* opCtx, LockMode mode, Date_t deadline); + + void _setWaitingResource(ResourceId resId); + + /** + * Calls dump() on this locker instance and the lock manager. + */ + void _dumpLockerAndLockManagerRequests(); + + /** + * Determines whether global and tenant lock state implies that some database or lower level + * resource, such as a collection, belonging to a tenant identified by 'tenantId' is locked in + * 'lockMode'. + * + * Returns: + * true, if the global and tenant locks imply that the resource is locked for 'mode'; + * false, if the global and tenant locks imply that the resource is not locked for 'mode'; + * boost::none, if the global and tenant lock state does not imply either outcome and lower + * level locks should be consulted. + */ + boost::optional _globalAndTenantLocksImplyDBOrCollectionLockedForMode( + const boost::optional& tenantId, LockMode lockMode) const; + + // Used to disambiguate different lockers + const LockerId _id; + + // The only reason we have this spin lock here is for the diagnostic tools, which could + // iterate through the LockRequestsMap on a separate thread and need it to be stable. + // Apart from that, all accesses to the LockerImpl are always from a single thread. + // + // This has to be locked inside const methods, hence the mutable. + mutable SpinLock _lock; + // Note: this data structure must always guarantee the continued validity of pointers/references + // to its contents (LockRequests). The LockManager maintains a LockRequestList of pointers to + // the LockRequests managed by this data structure. + LockRequestsMap _requests; + + // Reuse the notification object across requests so we don't have to create a new mutex + // and condition variable every time. + CondVarLockGrantNotification _notify; + + // Per-locker locking statistics. Reported in the slow-query log message and through + // db.currentOp. Complementary to the per-instance locking statistics. + AtomicLockStats _stats; + + // Delays release of exclusive/intent-exclusive locked resources until the write unit of + // work completes. Value of 0 means we are not inside a write unit of work. + int _wuowNestingLevel; + + // Mode for which the Locker acquired a ticket, or MODE_NONE if no ticket was acquired. + LockMode _modeForTicket = MODE_NONE; + + // Indicates whether the client is active reader/writer or is queued. + AtomicWord _clientState{kInactive}; + + // Track the thread who owns the lock for debugging purposes + stdx::thread::id _threadId; + + // If true, shared locks will participate in two-phase locking. + bool _sharedLocksShouldTwoPhaseLock = false; + + // If this is set, dictates the max number of milliseconds that we will wait for lock + // acquisition. Effectively resets lock acquisition deadlines to time out sooner. If set to 0, + // for example, lock attempts will time out immediately if the lock is not immediately + // available. Note this will be ineffective if uninterruptible lock guard is set. + boost::optional _maxLockTimeout; + + // A structure for accumulating time spent getting flow control tickets. + FlowControlTicketholder::CurOp _flowControlStats; + + // The global ticketholders of the service context. + TicketHolderManager* _ticketHolderManager; + + // This will only be valid when holding a ticket. + boost::optional _ticket; + + // Tracks the global lock modes ever acquired in this Locker's life. This value should only ever + // be accessed from the thread that owns the Locker. + unsigned char _globalLockMode = (1 << MODE_NONE); + + // Tracks whether this operation should be killed on step down. + AtomicWord _wasGlobalLockTakenInModeConflictingWithWrites{false}; + + // If isValid(), the ResourceId of the resource currently waiting for the lock. If not valid, + // there is no resource currently waiting. + ResourceId _waitingResource; + + ////////////////////////////////////////////////////////////////////////////////////////// + // + // Methods merged from LockState, which should eventually be removed or changed to methods + // on the LockerImpl interface. + // + +public: + virtual void dump() const; + + virtual bool isW() const; + virtual bool isR() const; + + virtual bool isLocked() const; + virtual bool isWriteLocked() const; + virtual bool isReadLocked() const; + + virtual bool isRSTLExclusive() const; + virtual bool isRSTLLocked() const; + + bool isGlobalLockedRecursively() override; + bool canSaveLockState() override; + + virtual bool hasLockPending() const { + return getWaitingResource().isValid(); + } +}; + +} // namespace mongo diff --git a/src/mongo/db/concurrency/locker_impl_test.cpp b/src/mongo/db/concurrency/locker_impl_test.cpp new file mode 100644 index 00000000000..6e67f7c31ea --- /dev/null +++ b/src/mongo/db/concurrency/locker_impl_test.cpp @@ -0,0 +1,1392 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side 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 Server Side 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 +#include +#include + +#include "mongo/config.h" +#include "mongo/db/concurrency/lock_manager_test_help.h" +#include "mongo/db/concurrency/locker.h" +#include "mongo/db/curop.h" +#include "mongo/db/service_context_test_fixture.h" +#include "mongo/transport/session.h" +#include "mongo/transport/transport_layer_mock.h" +#include "mongo/unittest/death_test.h" +#include "mongo/unittest/unittest.h" +#include "mongo/util/fail_point.h" +#include "mongo/util/timer.h" + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest + + +namespace mongo { + +class LockerImplTest : public ServiceContextTest {}; + +TEST_F(LockerImplTest, LockNoConflict) { + auto opCtx = makeOperationContext(); + + const ResourceId resId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + LockerImpl locker(opCtx->getServiceContext()); + locker.lockGlobal(opCtx.get(), MODE_IX); + + locker.lock(resId, MODE_X); + + ASSERT(locker.isLockHeldForMode(resId, MODE_X)); + ASSERT(locker.isLockHeldForMode(resId, MODE_S)); + + ASSERT(locker.unlock(resId)); + + ASSERT(locker.isLockHeldForMode(resId, MODE_NONE)); + + locker.unlockGlobal(); +} + +TEST_F(LockerImplTest, ReLockNoConflict) { + auto opCtx = makeOperationContext(); + + const ResourceId resId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + LockerImpl locker(opCtx->getServiceContext()); + locker.lockGlobal(opCtx.get(), MODE_IX); + + locker.lock(resId, MODE_S); + locker.lock(resId, MODE_X); + + ASSERT(!locker.unlock(resId)); + ASSERT(locker.isLockHeldForMode(resId, MODE_X)); + + ASSERT(locker.unlock(resId)); + ASSERT(locker.isLockHeldForMode(resId, MODE_NONE)); + + ASSERT(locker.unlockGlobal()); +} + +TEST_F(LockerImplTest, ConflictWithTimeout) { + auto opCtx = makeOperationContext(); + + const ResourceId resId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + LockerImpl locker1(opCtx->getServiceContext()); + locker1.lockGlobal(opCtx.get(), MODE_IX); + locker1.lock(resId, MODE_X); + + LockerImpl locker2(opCtx->getServiceContext()); + locker2.lockGlobal(opCtx.get(), MODE_IX); + + ASSERT_THROWS_CODE(locker2.lock(opCtx.get(), resId, MODE_S, Date_t::now()), + AssertionException, + ErrorCodes::LockTimeout); + + ASSERT(locker2.getLockMode(resId) == MODE_NONE); + + ASSERT(locker1.unlock(resId)); + + ASSERT(locker1.unlockGlobal()); + ASSERT(locker2.unlockGlobal()); +} + +TEST_F(LockerImplTest, ConflictUpgradeWithTimeout) { + auto opCtx = makeOperationContext(); + + const ResourceId resId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + LockerImpl locker1(opCtx->getServiceContext()); + locker1.lockGlobal(opCtx.get(), MODE_IS); + locker1.lock(resId, MODE_S); + + LockerImpl locker2(opCtx->getServiceContext()); + locker2.lockGlobal(opCtx.get(), MODE_IS); + locker2.lock(resId, MODE_S); + + // Try upgrading locker 1, which should block and timeout + ASSERT_THROWS_CODE(locker1.lock(opCtx.get(), resId, MODE_X, Date_t::now() + Milliseconds(1)), + AssertionException, + ErrorCodes::LockTimeout); + + locker1.unlockGlobal(); + locker2.unlockGlobal(); +} + +TEST_F(LockerImplTest, FailPointInLockFailsGlobalNonIntentLocksIfTheyCannotBeImmediatelyGranted) { + transport::TransportLayerMock transportLayer; + std::shared_ptr session = transportLayer.createSession(); + + auto newClient = getServiceContext()->makeClient("userClient", session); + AlternativeClientRegion acr(newClient); + auto newOpCtx = cc().makeOperationContext(); + + LockerImpl locker1(newOpCtx->getServiceContext()); + locker1.lockGlobal(newOpCtx.get(), MODE_IX); + + { + FailPointEnableBlock failWaitingNonPartitionedLocks("failNonIntentLocksIfWaitNeeded"); + + // MODE_S attempt. + LockerImpl locker2(newOpCtx->getServiceContext()); + ASSERT_THROWS_CODE( + locker2.lockGlobal(newOpCtx.get(), MODE_S), DBException, ErrorCodes::LockTimeout); + + // MODE_X attempt. + LockerImpl locker3(newOpCtx->getServiceContext()); + ASSERT_THROWS_CODE( + locker3.lockGlobal(newOpCtx.get(), MODE_X), DBException, ErrorCodes::LockTimeout); + } + + locker1.unlockGlobal(); +} + +TEST_F(LockerImplTest, FailPointInLockFailsNonIntentLocksIfTheyCannotBeImmediatelyGranted) { + transport::TransportLayerMock transportLayer; + std::shared_ptr session = transportLayer.createSession(); + + auto newClient = getServiceContext()->makeClient("userClient", session); + AlternativeClientRegion acr(newClient); + auto newOpCtx = cc().makeOperationContext(); + + // Granted MODE_X lock, fail incoming MODE_S and MODE_X. + const ResourceId resId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + LockerImpl locker1(newOpCtx->getServiceContext()); + locker1.lockGlobal(newOpCtx.get(), MODE_IX); + locker1.lock(newOpCtx.get(), resId, MODE_X); + + { + FailPointEnableBlock failWaitingNonPartitionedLocks("failNonIntentLocksIfWaitNeeded"); + + // MODE_S attempt. + LockerImpl locker2(newOpCtx->getServiceContext()); + locker2.lockGlobal(newOpCtx.get(), MODE_IS); + ASSERT_THROWS_CODE(locker2.lock(newOpCtx.get(), resId, MODE_S, Date_t::max()), + DBException, + ErrorCodes::LockTimeout); + + // The timed out MODE_S attempt shouldn't be present in the map of lock requests because it + // won't ever be granted. + ASSERT(locker2.getRequestsForTest().find(resId).finished()); + locker2.unlockGlobal(); + + // MODE_X attempt. + LockerImpl locker3(newOpCtx->getServiceContext()); + locker3.lockGlobal(newOpCtx.get(), MODE_IX); + ASSERT_THROWS_CODE(locker3.lock(newOpCtx.get(), resId, MODE_X, Date_t::max()), + DBException, + ErrorCodes::LockTimeout); + + // The timed out MODE_X attempt shouldn't be present in the map of lock requests because it + // won't ever be granted. + ASSERT(locker3.getRequestsForTest().find(resId).finished()); + locker3.unlockGlobal(); + } + + locker1.unlockGlobal(); +} + +TEST_F(LockerImplTest, ReadTransaction) { + auto opCtx = makeOperationContext(); + + LockerImpl locker(opCtx->getServiceContext()); + + locker.lockGlobal(opCtx.get(), MODE_IS); + locker.unlockGlobal(); + + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.unlockGlobal(); + + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lockGlobal(opCtx.get(), MODE_IS); + locker.unlockGlobal(); + locker.unlockGlobal(); +} + +/** + * Test that saveLockerImpl works by examining the output. + */ +TEST_F(LockerImplTest, saveAndRestoreGlobal) { + auto opCtx = makeOperationContext(); + + LockerImpl locker(opCtx->getServiceContext()); + + // No lock requests made, no locks held. + ASSERT_FALSE(locker.canSaveLockState()); + + // Lock the global lock, but just once. + locker.lockGlobal(opCtx.get(), MODE_IX); + + // We've locked the global lock. This should be reflected in the lockInfo. + Locker::LockSnapshot lockInfo; + locker.saveLockStateAndUnlock(&lockInfo); + ASSERT(!locker.isLocked()); + ASSERT_EQUALS(MODE_IX, lockInfo.globalMode); + + // Restore the lock(s) we had. + locker.restoreLockState(opCtx.get(), lockInfo); + + ASSERT(locker.isLocked()); + ASSERT(locker.unlockGlobal()); +} + +/** + * Test that saveLockerImpl can save and restore the RSTL. + */ +TEST_F(LockerImplTest, saveAndRestoreRSTL) { + auto opCtx = makeOperationContext(); + + Locker::LockSnapshot lockInfo; + + LockerImpl locker(opCtx->getServiceContext()); + + const ResourceId resIdDatabase(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); + + // Acquire locks. + locker.lock(resourceIdReplicationStateTransitionLock, MODE_IX); + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lock(resIdDatabase, MODE_IX); + + // Save the lock state. + locker.saveLockStateAndUnlock(&lockInfo); + ASSERT(!locker.isLocked()); + ASSERT_EQUALS(MODE_IX, lockInfo.globalMode); + + // Check locks are unlocked. + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resourceIdReplicationStateTransitionLock)); + ASSERT(!locker.isLocked()); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); + + // Restore the lock(s) we had. + locker.restoreLockState(opCtx.get(), lockInfo); + + // Check locks are re-locked. + ASSERT(locker.isLocked()); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resourceIdReplicationStateTransitionLock)); + + ASSERT(locker.unlockGlobal()); + ASSERT(locker.unlock(resourceIdReplicationStateTransitionLock)); +} + +/** + * Test that we don't unlock when we have the global lock more than once. + */ +TEST_F(LockerImplTest, saveAndRestoreGlobalAcquiredTwice) { + auto opCtx = makeOperationContext(); + + LockerImpl locker(opCtx->getServiceContext()); + + // No lock requests made, no locks held. + ASSERT_FALSE(locker.canSaveLockState()); + + // Lock the global lock. + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lockGlobal(opCtx.get(), MODE_IX); + + // This shouldn't actually unlock as we're in a nested scope. + ASSERT_FALSE(locker.canSaveLockState()); + + ASSERT(locker.isLocked()); + + // We must unlockGlobal twice. + ASSERT(!locker.unlockGlobal()); + ASSERT(locker.unlockGlobal()); +} + +/** + * Tests that restoreLockerImpl works by locking a db and collection and saving + restoring. + */ +TEST_F(LockerImplTest, saveAndRestoreDBAndCollection) { + auto opCtx = makeOperationContext(); + + Locker::LockSnapshot lockInfo; + + LockerImpl locker(opCtx->getServiceContext()); + + const ResourceId resIdDatabase(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); + const ResourceId resIdCollection( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + // Lock some stuff. + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lock(resIdDatabase, MODE_IX); + locker.lock(resIdCollection, MODE_IX); + locker.saveLockStateAndUnlock(&lockInfo); + + // Things shouldn't be locked anymore. + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); + + // Restore lock state. + locker.restoreLockState(opCtx.get(), lockInfo); + + // Make sure things were re-locked. + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection)); + + ASSERT(locker.unlockGlobal()); +} + +TEST_F(LockerImplTest, releaseWriteUnitOfWork) { + auto opCtx = makeOperationContext(); + + Locker::LockSnapshot lockInfo; + + LockerImpl locker(opCtx->getServiceContext()); + + const ResourceId resIdDatabase(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); + const ResourceId resIdCollection( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + locker.beginWriteUnitOfWork(); + // Lock some stuff. + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lock(resIdDatabase, MODE_IX); + locker.lock(resIdCollection, MODE_IX); + // Unlock them so that they will be pending to unlock. + ASSERT_FALSE(locker.unlock(resIdCollection)); + ASSERT_FALSE(locker.unlock(resIdDatabase)); + ASSERT_FALSE(locker.unlockGlobal()); + + locker.releaseWriteUnitOfWorkAndUnlock(&lockInfo); + + // Things shouldn't be locked anymore. + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); + ASSERT_FALSE(locker.isLocked()); + + // Destructor should succeed since the locker's state should be empty. +} + +TEST_F(LockerImplTest, restoreWriteUnitOfWork) { + auto opCtx = makeOperationContext(); + + Locker::LockSnapshot lockInfo; + + LockerImpl locker(opCtx->getServiceContext()); + + const ResourceId resIdDatabase(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); + const ResourceId resIdCollection( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + locker.beginWriteUnitOfWork(); + // Lock some stuff. + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lock(resIdDatabase, MODE_IX); + locker.lock(resIdCollection, MODE_IX); + // Unlock them so that they will be pending to unlock. + ASSERT_FALSE(locker.unlock(resIdCollection)); + ASSERT_FALSE(locker.unlock(resIdDatabase)); + ASSERT_FALSE(locker.unlockGlobal()); + + locker.releaseWriteUnitOfWorkAndUnlock(&lockInfo); + + // Things shouldn't be locked anymore. + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); + ASSERT_FALSE(locker.isLocked()); + + // Restore lock state. + locker.restoreWriteUnitOfWorkAndLock(opCtx.get(), lockInfo); + + // Make sure things were re-locked. + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection)); + ASSERT(locker.isLocked()); + + locker.endWriteUnitOfWork(); + + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); + ASSERT_FALSE(locker.isLocked()); +} + +TEST_F(LockerImplTest, releaseAndRestoreWriteUnitOfWorkWithoutUnlock) { + auto opCtx = makeOperationContext(); + + Locker::WUOWLockSnapshot lockInfo; + + LockerImpl locker(opCtx->getServiceContext()); + + const ResourceId resIdDatabase(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); + const ResourceId resIdCollection( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + const ResourceId resIdCollection2( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection2")); + + locker.beginWriteUnitOfWork(); + // Lock some stuff. + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lock(resIdDatabase, MODE_IX); + locker.lock(resIdCollection, MODE_X); + + // Recursive global lock. + locker.lockGlobal(opCtx.get(), MODE_IX); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 2U); + + ASSERT_FALSE(locker.unlockGlobal()); + + // Unlock them so that they will be pending to unlock. + ASSERT_FALSE(locker.unlock(resIdCollection)); + ASSERT_FALSE(locker.unlock(resIdDatabase)); + ASSERT_FALSE(locker.unlockGlobal()); + ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 3UL); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); + + locker.releaseWriteUnitOfWork(&lockInfo); + ASSERT_EQ(lockInfo.unlockPendingLocks.size(), 3UL); + + // Things should still be locked. + ASSERT_EQUALS(MODE_X, locker.getLockMode(resIdCollection)); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 0U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); + ASSERT(locker.isLocked()); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 0U); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); + + // The locker is no longer participating the two-phase locking. + ASSERT_FALSE(locker.inAWriteUnitOfWork()); + ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 0UL); + + // Start a new WUOW with the same locker. Any new locks acquired in the new WUOW + // should participate two-phase locking. + { + locker.beginWriteUnitOfWork(); + + // Grab new locks inside the new WUOW. + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lock(resIdDatabase, MODE_IX); + locker.lock(resIdCollection2, MODE_IX); + + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection2)); + ASSERT(locker.isLocked()); + + locker.unlock(resIdCollection2); + ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 1UL); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 0U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 2U); + locker.unlock(resIdDatabase); + ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 1UL); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 0U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); + locker.unlockGlobal(); + ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 1UL); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 0U); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); + locker.endWriteUnitOfWork(); + } + ASSERT_FALSE(locker.inAWriteUnitOfWork()); + ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 0UL); + + ASSERT_EQUALS(MODE_X, locker.getLockMode(resIdCollection)); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 0U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); + ASSERT(locker.isLocked()); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 0U); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); + // The new locks has been released. + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection2)); + + // Restore lock state. + locker.restoreWriteUnitOfWork(lockInfo); + + ASSERT_TRUE(locker.inAWriteUnitOfWork()); + + // Make sure things are still locked. + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_X, locker.getLockMode(resIdCollection)); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); + ASSERT(locker.isLocked()); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); + + locker.endWriteUnitOfWork(); + + ASSERT_FALSE(locker.inAWriteUnitOfWork()); + + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection2)); + ASSERT_FALSE(locker.isLocked()); + ASSERT_EQ(locker.numResourcesToUnlockAtEndUnitOfWorkForTest(), 0U); + ASSERT(locker.getRequestsForTest().find(resourceIdGlobal).finished()); +} + +TEST_F(LockerImplTest, releaseAndRestoreReadOnlyWriteUnitOfWork) { + auto opCtx = makeOperationContext(); + + Locker::LockSnapshot lockInfo; + + LockerImpl locker(opCtx->getServiceContext()); + + const ResourceId resIdDatabase(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); + const ResourceId resIdCollection( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + // Snapshot transactions delay shared locks as well. + locker.setSharedLocksShouldTwoPhaseLock(true); + + locker.beginWriteUnitOfWork(); + // Lock some stuff in IS mode. + locker.lockGlobal(opCtx.get(), MODE_IS); + locker.lock(resIdDatabase, MODE_IS); + locker.lock(resIdCollection, MODE_IS); + // Unlock them. + ASSERT_FALSE(locker.unlock(resIdCollection)); + ASSERT_FALSE(locker.unlock(resIdDatabase)); + ASSERT_FALSE(locker.unlockGlobal()); + ASSERT_EQ(3u, locker.numResourcesToUnlockAtEndUnitOfWorkForTest()); + + // Things shouldn't be locked anymore. + locker.releaseWriteUnitOfWorkAndUnlock(&lockInfo); + + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); + ASSERT_FALSE(locker.isLocked()); + + // Restore lock state. + locker.restoreWriteUnitOfWorkAndLock(opCtx.get(), lockInfo); + + ASSERT_EQUALS(MODE_IS, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_IS, locker.getLockMode(resIdCollection)); + ASSERT_TRUE(locker.isLocked()); + + locker.endWriteUnitOfWork(); + + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); + ASSERT_FALSE(locker.isLocked()); +} + +TEST_F(LockerImplTest, releaseAndRestoreEmptyWriteUnitOfWork) { + Locker::LockSnapshot lockInfo; + auto opCtx = makeOperationContext(); + LockerImpl locker(opCtx->getServiceContext()); + + // Snapshot transactions delay shared locks as well. + locker.setSharedLocksShouldTwoPhaseLock(true); + + locker.beginWriteUnitOfWork(); + + // Nothing to yield. + ASSERT_FALSE(locker.canSaveLockState()); + ASSERT_FALSE(locker.isLocked()); + + locker.endWriteUnitOfWork(); +} + +TEST_F(LockerImplTest, releaseAndRestoreWriteUnitOfWorkWithRecursiveLocks) { + auto opCtx = makeOperationContext(); + + Locker::LockSnapshot lockInfo; + + LockerImpl locker(opCtx->getServiceContext()); + + const ResourceId resIdDatabase(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); + const ResourceId resIdCollection( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + locker.beginWriteUnitOfWork(); + // Lock some stuff. + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lock(resIdDatabase, MODE_IX); + locker.lock(resIdCollection, MODE_IX); + // Recursively lock them again with a weaker mode. + locker.lockGlobal(opCtx.get(), MODE_IS); + locker.lock(resIdDatabase, MODE_IS); + locker.lock(resIdCollection, MODE_IS); + + // Make sure locks are converted. + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection)); + ASSERT_TRUE(locker.isWriteLocked()); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 2U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 2U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->recursiveCount, 2U); + + // Unlock them so that they will be pending to unlock. + ASSERT_FALSE(locker.unlock(resIdCollection)); + ASSERT_FALSE(locker.unlock(resIdDatabase)); + ASSERT_FALSE(locker.unlockGlobal()); + // Make sure locks are still acquired in the correct mode. + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection)); + ASSERT_TRUE(locker.isWriteLocked()); + // Make sure unlocking converted locks decrements the locks' recursiveCount instead of + // incrementing unlockPending. + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 0U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 0U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->recursiveCount, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->unlockPending, 0U); + + // Unlock again so unlockPending == recursiveCount. + ASSERT_FALSE(locker.unlock(resIdCollection)); + ASSERT_FALSE(locker.unlock(resIdDatabase)); + ASSERT_FALSE(locker.unlockGlobal()); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->recursiveCount, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->unlockPending, 1U); + + locker.releaseWriteUnitOfWorkAndUnlock(&lockInfo); + + // Things shouldn't be locked anymore. + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); + ASSERT_FALSE(locker.isLocked()); + + // Restore lock state. + locker.restoreWriteUnitOfWorkAndLock(opCtx.get(), lockInfo); + + // Make sure things were re-locked in the correct mode. + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_IX, locker.getLockMode(resIdCollection)); + ASSERT_TRUE(locker.isWriteLocked()); + // Make sure locks were coalesced after restore and are pending to unlock as before. + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->recursiveCount, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resourceIdGlobal).objAddr()->unlockPending, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->recursiveCount, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdDatabase).objAddr()->unlockPending, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->recursiveCount, 1U); + ASSERT_EQ(locker.getRequestsForTest().find(resIdCollection).objAddr()->unlockPending, 1U); + + locker.endWriteUnitOfWork(); + + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdDatabase)); + ASSERT_EQUALS(MODE_NONE, locker.getLockMode(resIdCollection)); + ASSERT_FALSE(locker.isLocked()); +} + +TEST_F(LockerImplTest, DefaultLocker) { + auto opCtx = makeOperationContext(); + + const ResourceId resId(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); + + LockerImpl locker(opCtx->getServiceContext()); + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lock(resId, MODE_X); + + // Make sure only Global and TestDB resources are locked. + Locker::LockerInfo info; + locker.getLockerInfo(&info, boost::none); + ASSERT(!info.waitingResource.isValid()); + ASSERT_EQUALS(2U, info.locks.size()); + ASSERT_EQUALS(RESOURCE_GLOBAL, info.locks[0].resourceId.getType()); + ASSERT_EQUALS(resId, info.locks[1].resourceId); + + ASSERT(locker.unlockGlobal()); +} + +TEST_F(LockerImplTest, SharedLocksShouldTwoPhaseLockIsTrue) { + // Test that when setSharedLocksShouldTwoPhaseLock is true and we are in a WUOW, unlock on IS + // and S locks are postponed until endWriteUnitOfWork() is called. Mode IX and X locks always + // participate in two-phased locking, regardless of the setting. + + auto opCtx = makeOperationContext(); + + const ResourceId resId1(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB1")); + const ResourceId resId2(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB2")); + const ResourceId resId3( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection3")); + const ResourceId resId4( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection4")); + + LockerImpl locker(opCtx->getServiceContext()); + locker.setSharedLocksShouldTwoPhaseLock(true); + + locker.lockGlobal(opCtx.get(), MODE_IS); + ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_IS); + + locker.lock(resourceIdReplicationStateTransitionLock, MODE_IS); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IS); + + locker.lock(resId1, MODE_IS); + locker.lock(resId2, MODE_IX); + locker.lock(resId3, MODE_S); + locker.lock(resId4, MODE_X); + ASSERT_EQ(locker.getLockMode(resId1), MODE_IS); + ASSERT_EQ(locker.getLockMode(resId2), MODE_IX); + ASSERT_EQ(locker.getLockMode(resId3), MODE_S); + ASSERT_EQ(locker.getLockMode(resId4), MODE_X); + + locker.beginWriteUnitOfWork(); + + ASSERT_FALSE(locker.unlock(resId1)); + ASSERT_FALSE(locker.unlock(resId2)); + ASSERT_FALSE(locker.unlock(resId3)); + ASSERT_FALSE(locker.unlock(resId4)); + ASSERT_EQ(locker.getLockMode(resId1), MODE_IS); + ASSERT_EQ(locker.getLockMode(resId2), MODE_IX); + ASSERT_EQ(locker.getLockMode(resId3), MODE_S); + ASSERT_EQ(locker.getLockMode(resId4), MODE_X); + + ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IS); + + ASSERT_FALSE(locker.unlockGlobal()); + ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_IS); + + locker.endWriteUnitOfWork(); + + ASSERT_EQ(locker.getLockMode(resId1), MODE_NONE); + ASSERT_EQ(locker.getLockMode(resId2), MODE_NONE); + ASSERT_EQ(locker.getLockMode(resId3), MODE_NONE); + ASSERT_EQ(locker.getLockMode(resId4), MODE_NONE); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); + ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_NONE); +} + +TEST_F(LockerImplTest, ModeIXAndXLockParticipatesInTwoPhaseLocking) { + // Unlock on mode IX and X locks during a WUOW should always be postponed until + // endWriteUnitOfWork() is called. Mode IS and S locks should unlock immediately. + + auto opCtx = makeOperationContext(); + + const ResourceId resId1(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB1")); + const ResourceId resId2(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB2")); + const ResourceId resId3( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection3")); + const ResourceId resId4( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection4")); + + LockerImpl locker(opCtx->getServiceContext()); + + locker.lockGlobal(opCtx.get(), MODE_IX); + ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_IX); + + locker.lock(resourceIdReplicationStateTransitionLock, MODE_IX); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); + + locker.lock(resId1, MODE_IS); + locker.lock(resId2, MODE_IX); + locker.lock(resId3, MODE_S); + locker.lock(resId4, MODE_X); + ASSERT_EQ(locker.getLockMode(resId1), MODE_IS); + ASSERT_EQ(locker.getLockMode(resId2), MODE_IX); + ASSERT_EQ(locker.getLockMode(resId3), MODE_S); + ASSERT_EQ(locker.getLockMode(resId4), MODE_X); + + locker.beginWriteUnitOfWork(); + + ASSERT_TRUE(locker.unlock(resId1)); + ASSERT_FALSE(locker.unlock(resId2)); + ASSERT_TRUE(locker.unlock(resId3)); + ASSERT_FALSE(locker.unlock(resId4)); + ASSERT_EQ(locker.getLockMode(resId1), MODE_NONE); + ASSERT_EQ(locker.getLockMode(resId2), MODE_IX); + ASSERT_EQ(locker.getLockMode(resId3), MODE_NONE); + ASSERT_EQ(locker.getLockMode(resId4), MODE_X); + + ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); + + ASSERT_FALSE(locker.unlockGlobal()); + ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_IX); + + locker.endWriteUnitOfWork(); + + ASSERT_EQ(locker.getLockMode(resId2), MODE_NONE); + ASSERT_EQ(locker.getLockMode(resId4), MODE_NONE); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); + ASSERT_EQ(locker.getLockMode(resourceIdGlobal), MODE_NONE); +} + +TEST_F(LockerImplTest, RSTLUnlocksWithNestedLock) { + auto opCtx = makeOperationContext(); + LockerImpl locker(opCtx->getServiceContext()); + + locker.lock(resourceIdReplicationStateTransitionLock, MODE_IX); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); + + locker.beginWriteUnitOfWork(); + + // Do a nested lock acquisition. + locker.lock(resourceIdReplicationStateTransitionLock, MODE_IX); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); + + ASSERT(locker.unlockRSTLforPrepare()); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); + + ASSERT_FALSE(locker.unlockRSTLforPrepare()); + + locker.endWriteUnitOfWork(); + + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); + + ASSERT_FALSE(locker.unlockRSTLforPrepare()); + ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); +} + +TEST_F(LockerImplTest, RSTLModeIXWithTwoPhaseLockingCanBeUnlockedWhenPrepared) { + auto opCtx = makeOperationContext(); + LockerImpl locker(opCtx->getServiceContext()); + + locker.lock(resourceIdReplicationStateTransitionLock, MODE_IX); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); + + locker.beginWriteUnitOfWork(); + + ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IX); + + ASSERT(locker.unlockRSTLforPrepare()); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); + + ASSERT_FALSE(locker.unlockRSTLforPrepare()); + + locker.endWriteUnitOfWork(); + + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); + + ASSERT_FALSE(locker.unlockRSTLforPrepare()); + ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); +} + +TEST_F(LockerImplTest, RSTLModeISWithTwoPhaseLockingCanBeUnlockedWhenPrepared) { + auto opCtx = makeOperationContext(); + LockerImpl locker(opCtx->getServiceContext()); + + locker.lock(resourceIdReplicationStateTransitionLock, MODE_IS); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IS); + + locker.beginWriteUnitOfWork(); + + ASSERT(locker.unlockRSTLforPrepare()); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); + + ASSERT_FALSE(locker.unlockRSTLforPrepare()); + + locker.endWriteUnitOfWork(); + + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); + + ASSERT_FALSE(locker.unlockRSTLforPrepare()); + ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); +} + +TEST_F(LockerImplTest, RSTLTwoPhaseLockingBehaviorModeIS) { + auto opCtx = makeOperationContext(); + LockerImpl locker(opCtx->getServiceContext()); + + locker.lock(resourceIdReplicationStateTransitionLock, MODE_IS); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_IS); + + locker.beginWriteUnitOfWork(); + + ASSERT_TRUE(locker.unlock(resourceIdReplicationStateTransitionLock)); + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); + + ASSERT_FALSE(locker.unlockRSTLforPrepare()); + + locker.endWriteUnitOfWork(); + + ASSERT_EQ(locker.getLockMode(resourceIdReplicationStateTransitionLock), MODE_NONE); + + ASSERT_FALSE(locker.unlockRSTLforPrepare()); + ASSERT_FALSE(locker.unlock(resourceIdReplicationStateTransitionLock)); +} + +TEST_F(LockerImplTest, OverrideLockRequestTimeout) { + auto opCtx = makeOperationContext(); + + const ResourceId resIdFirstDB(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "FirstDB")); + const ResourceId resIdSecondDB( + RESOURCE_DATABASE, DatabaseName::createDatabaseName_forTest(boost::none, "SecondDB")); + + LockerImpl locker1(opCtx->getServiceContext()); + LockerImpl locker2(opCtx->getServiceContext()); + + // Set up locker2 to override lock requests' provided timeout if greater than 1000 milliseconds. + locker2.setMaxLockTimeout(Milliseconds(1000)); + + locker1.lockGlobal(opCtx.get(), MODE_IX); + locker2.lockGlobal(opCtx.get(), MODE_IX); + + // locker1 acquires FirstDB under an exclusive lock. + locker1.lock(resIdFirstDB, MODE_X); + ASSERT_TRUE(locker1.isLockHeldForMode(resIdFirstDB, MODE_X)); + + // locker2's attempt to acquire FirstDB with unlimited wait time should timeout after 1000 + // milliseconds and throw because _maxLockRequestTimeout is set to 1000 milliseconds. + ASSERT_THROWS_CODE(locker2.lock(opCtx.get(), resIdFirstDB, MODE_X, Date_t::max()), + AssertionException, + ErrorCodes::LockTimeout); + + // locker2's attempt to acquire an uncontested lock should still succeed normally. + locker2.lock(resIdSecondDB, MODE_X); + + ASSERT_TRUE(locker1.unlock(resIdFirstDB)); + ASSERT_TRUE(locker1.isLockHeldForMode(resIdFirstDB, MODE_NONE)); + ASSERT_TRUE(locker2.unlock(resIdSecondDB)); + ASSERT_TRUE(locker2.isLockHeldForMode(resIdSecondDB, MODE_NONE)); + + ASSERT(locker1.unlockGlobal()); + ASSERT(locker2.unlockGlobal()); +} + +TEST_F(LockerImplTest, DoNotWaitForLockAcquisition) { + auto opCtx = makeOperationContext(); + + const ResourceId resIdFirstDB(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "FirstDB")); + const ResourceId resIdSecondDB( + RESOURCE_DATABASE, DatabaseName::createDatabaseName_forTest(boost::none, "SecondDB")); + + LockerImpl locker1(opCtx->getServiceContext()); + LockerImpl locker2(opCtx->getServiceContext()); + + // Set up locker2 to immediately return if a lock is unavailable, regardless of supplied + // deadlines in the lock request. + locker2.setMaxLockTimeout(Milliseconds(0)); + + locker1.lockGlobal(opCtx.get(), MODE_IX); + locker2.lockGlobal(opCtx.get(), MODE_IX); + + // locker1 acquires FirstDB under an exclusive lock. + locker1.lock(resIdFirstDB, MODE_X); + ASSERT_TRUE(locker1.isLockHeldForMode(resIdFirstDB, MODE_X)); + + // locker2's attempt to acquire FirstDB with unlimited wait time should fail immediately and + // throw because _maxLockRequestTimeout was set to 0. + ASSERT_THROWS_CODE(locker2.lock(opCtx.get(), resIdFirstDB, MODE_X, Date_t::max()), + AssertionException, + ErrorCodes::LockTimeout); + + // locker2's attempt to acquire an uncontested lock should still succeed normally. + locker2.lock(resIdSecondDB, MODE_X); + + ASSERT_TRUE(locker1.unlock(resIdFirstDB)); + ASSERT_TRUE(locker1.isLockHeldForMode(resIdFirstDB, MODE_NONE)); + ASSERT_TRUE(locker2.unlock(resIdSecondDB)); + ASSERT_TRUE(locker2.isLockHeldForMode(resIdSecondDB, MODE_NONE)); + + ASSERT(locker1.unlockGlobal()); + ASSERT(locker2.unlockGlobal()); +} + +namespace { +/** + * Helper function to determine if 'lockerInfo' contains a lock with ResourceId 'resourceId' and + * lock mode 'mode' within 'lockerInfo.locks'. + */ +bool lockerInfoContainsLock(const Locker::LockerInfo& lockerInfo, + const ResourceId& resourceId, + const LockMode& mode) { + return (1U == + std::count_if(lockerInfo.locks.begin(), + lockerInfo.locks.end(), + [&resourceId, &mode](const Locker::OneLock& lock) { + return lock.resourceId == resourceId && lock.mode == mode; + })); +} +} // namespace + +TEST_F(LockerImplTest, GetLockerInfoShouldReportHeldLocks) { + auto opCtx = makeOperationContext(); + + const ResourceId dbId(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); + const ResourceId collectionId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + // Take an exclusive lock on the collection. + LockerImpl locker(opCtx->getServiceContext()); + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lock(dbId, MODE_IX); + locker.lock(collectionId, MODE_X); + + // Assert it shows up in the output of getLockerInfo(). + Locker::LockerInfo lockerInfo; + locker.getLockerInfo(&lockerInfo, boost::none); + + ASSERT(lockerInfoContainsLock(lockerInfo, resourceIdGlobal, MODE_IX)); + ASSERT(lockerInfoContainsLock(lockerInfo, dbId, MODE_IX)); + ASSERT(lockerInfoContainsLock(lockerInfo, collectionId, MODE_X)); + ASSERT_EQ(3U, lockerInfo.locks.size()); + + ASSERT(locker.unlock(collectionId)); + ASSERT(locker.unlock(dbId)); + ASSERT(locker.unlockGlobal()); +} + +TEST_F(LockerImplTest, GetLockerInfoShouldReportPendingLocks) { + auto opCtx = makeOperationContext(); + + const ResourceId dbId(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "TestDB")); + const ResourceId collectionId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + // Take an exclusive lock on the collection. + LockerImpl successfulLocker(opCtx->getServiceContext()); + successfulLocker.lockGlobal(opCtx.get(), MODE_IX); + successfulLocker.lock(dbId, MODE_IX); + successfulLocker.lock(collectionId, MODE_X); + + // Now attempt to get conflicting locks. + LockerImpl conflictingLocker(opCtx->getServiceContext()); + conflictingLocker.lockGlobal(opCtx.get(), MODE_IS); + conflictingLocker.lock(dbId, MODE_IS); + ASSERT_EQ(LOCK_WAITING, + conflictingLocker.lockBeginForTest(nullptr /* opCtx */, collectionId, MODE_IS)); + + // Assert the held locks show up in the output of getLockerInfo(). + Locker::LockerInfo lockerInfo; + conflictingLocker.getLockerInfo(&lockerInfo, boost::none); + ASSERT(lockerInfoContainsLock(lockerInfo, resourceIdGlobal, MODE_IS)); + ASSERT(lockerInfoContainsLock(lockerInfo, dbId, MODE_IS)); + ASSERT(lockerInfoContainsLock(lockerInfo, collectionId, MODE_IS)); + ASSERT_EQ(3U, lockerInfo.locks.size()); + + // Assert it reports that it is waiting for the collection lock. + ASSERT_EQ(collectionId, lockerInfo.waitingResource); + + // Make sure it no longer reports waiting once unlocked. + ASSERT(successfulLocker.unlock(collectionId)); + ASSERT(successfulLocker.unlock(dbId)); + ASSERT(successfulLocker.unlockGlobal()); + + conflictingLocker.lockCompleteForTest( + nullptr /* opCtx */, collectionId, MODE_IS, Date_t::now()); + + conflictingLocker.getLockerInfo(&lockerInfo, boost::none); + ASSERT_FALSE(lockerInfo.waitingResource.isValid()); + + ASSERT(conflictingLocker.unlock(collectionId)); + ASSERT(conflictingLocker.unlock(dbId)); + ASSERT(conflictingLocker.unlockGlobal()); +} + +TEST_F(LockerImplTest, GetLockerInfoShouldSubtractBase) { + auto opCtx = makeOperationContext(); + auto locker = opCtx->lockState(); + const ResourceId dbId(RESOURCE_DATABASE, + DatabaseName::createDatabaseName_forTest(boost::none, "SubtractTestDB")); + + auto numAcquisitions = [&](boost::optional baseStats) { + Locker::LockerInfo info; + locker->getLockerInfo(&info, baseStats); + return info.stats.get(dbId, MODE_IX).numAcquisitions; + }; + auto getBaseStats = [&] { + return CurOp::get(opCtx.get())->getLockStatsBase(); + }; + + locker->lockGlobal(opCtx.get(), MODE_IX); + + // Obtain a lock before any other ops have been pushed to the stack. + locker->lock(dbId, MODE_IX); + locker->unlock(dbId); + + ASSERT_EQUALS(numAcquisitions(getBaseStats()), 1) << "The acquisition should be reported"; + + // Push another op to the stack and obtain a lock. + CurOp superOp; + superOp.push(opCtx.get()); + locker->lock(dbId, MODE_IX); + locker->unlock(dbId); + + ASSERT_EQUALS(numAcquisitions(getBaseStats()), 1) + << "Only superOp's acquisition should be reported"; + + // Then push another op to the stack and obtain another lock. + CurOp subOp; + subOp.push(opCtx.get()); + locker->lock(dbId, MODE_IX); + locker->unlock(dbId); + + ASSERT_EQUALS(numAcquisitions(getBaseStats()), 1) + << "Only the latest acquisition should be reported"; + + ASSERT_EQUALS(numAcquisitions({}), 3) + << "All acquisitions should be reported when no base is subtracted out."; + + ASSERT(locker->unlockGlobal()); +} + +TEST_F(LockerImplTest, ReaquireLockPendingUnlock) { + auto opCtx = makeOperationContext(); + + const ResourceId resId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + LockerImpl locker(opCtx->getServiceContext()); + locker.lockGlobal(opCtx.get(), MODE_IS); + + locker.lock(resId, MODE_X); + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); + + locker.beginWriteUnitOfWork(); + + ASSERT_FALSE(locker.unlock(resId)); + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); + ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); + + // Reacquire lock pending unlock. + locker.lock(resId, MODE_X); + ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 0); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 0); + + locker.endWriteUnitOfWork(); + + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); + + locker.unlockGlobal(); +} + +TEST_F(LockerImplTest, AcquireLockPendingUnlockWithCoveredMode) { + auto opCtx = makeOperationContext(); + + const ResourceId resId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + LockerImpl locker(opCtx->getServiceContext()); + locker.lockGlobal(opCtx.get(), MODE_IS); + + locker.lock(resId, MODE_X); + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); + + locker.beginWriteUnitOfWork(); + + ASSERT_FALSE(locker.unlock(resId)); + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); + ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); + + // Attempt to lock the resource with a mode that is covered by the existing mode. + locker.lock(resId, MODE_IX); + ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 0); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 0); + + locker.endWriteUnitOfWork(); + + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_IX)); + + locker.unlockGlobal(); +} + +TEST_F(LockerImplTest, ConvertLockPendingUnlock) { + auto opCtx = makeOperationContext(); + + const ResourceId resId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + LockerImpl locker(opCtx->getServiceContext()); + locker.lockGlobal(opCtx.get(), MODE_IS); + + locker.lock(resId, MODE_IX); + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_IX)); + + locker.beginWriteUnitOfWork(); + + ASSERT_FALSE(locker.unlock(resId)); + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_IX)); + ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->recursiveCount == 1); + + // Convert lock pending unlock. + locker.lock(resId, MODE_X); + ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->recursiveCount == 2); + + locker.endWriteUnitOfWork(); + + ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 0); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 0); + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); + + locker.unlockGlobal(); +} + +TEST_F(LockerImplTest, ConvertLockPendingUnlockAndUnlock) { + auto opCtx = makeOperationContext(); + + const ResourceId resId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + LockerImpl locker(opCtx->getServiceContext()); + locker.lockGlobal(opCtx.get(), MODE_IS); + + locker.lock(resId, MODE_IX); + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_IX)); + + locker.beginWriteUnitOfWork(); + + ASSERT_FALSE(locker.unlock(resId)); + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_IX)); + ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->recursiveCount == 1); + + // Convert lock pending unlock. + locker.lock(resId, MODE_X); + ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->recursiveCount == 2); + + // Unlock the lock conversion. + ASSERT_FALSE(locker.unlock(resId)); + ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 1); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->unlockPending == 1); + // Make sure we still hold X lock and unlock the weaker mode to decrement recursiveCount instead + // of incrementing unlockPending. + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_X)); + ASSERT(locker.getRequestsForTest().find(resId).objAddr()->recursiveCount == 1); + + locker.endWriteUnitOfWork(); + + ASSERT(locker.numResourcesToUnlockAtEndUnitOfWorkForTest() == 0); + ASSERT(locker.getRequestsForTest().find(resId).finished()); + ASSERT_TRUE(locker.isLockHeldForMode(resId, MODE_NONE)); + + locker.unlockGlobal(); +} + +TEST_F(LockerImplTest, SetTicketAcquisitionForLockRAIIType) { + auto opCtx = makeOperationContext(); + + // By default, ticket acquisition is required. + ASSERT_TRUE(opCtx->lockState()->shouldWaitForTicket()); + + { + ScopedAdmissionPriorityForLock setTicketAquisition(opCtx->lockState(), + AdmissionContext::Priority::kImmediate); + ASSERT_FALSE(opCtx->lockState()->shouldWaitForTicket()); + } + + ASSERT_TRUE(opCtx->lockState()->shouldWaitForTicket()); + + opCtx->lockState()->setAdmissionPriority(AdmissionContext::Priority::kImmediate); + ASSERT_FALSE(opCtx->lockState()->shouldWaitForTicket()); + + { + ScopedAdmissionPriorityForLock setTicketAquisition(opCtx->lockState(), + AdmissionContext::Priority::kImmediate); + ASSERT_FALSE(opCtx->lockState()->shouldWaitForTicket()); + } + + ASSERT_FALSE(opCtx->lockState()->shouldWaitForTicket()); +} + +// This test exercises the lock dumping code in ~LockerImpl in case locks are held on destruction. +DEATH_TEST_F(LockerImplTest, + LocksHeldOnDestructionCausesALocksDump, + "Operation ending while holding locks.") { + auto opCtx = makeOperationContext(); + + const ResourceId resId( + RESOURCE_COLLECTION, + NamespaceString::createNamespaceString_forTest(boost::none, "TestDB.collection")); + + LockerImpl locker(opCtx->getServiceContext()); + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lock(resId, MODE_X); + + ASSERT(locker.isLockHeldForMode(resId, MODE_X)); + ASSERT(locker.isLockHeldForMode(resId, MODE_S)); + + // 'locker' destructor should invariant because locks are still held. +} + +DEATH_TEST_F(LockerImplTest, SaveAndRestoreGlobalRecursivelyIsFatal, "7033800") { + auto opCtx = makeOperationContext(); + + Locker::LockSnapshot lockInfo; + + LockerImpl locker(opCtx->getServiceContext()); + + // No lock requests made, no locks held. + locker.saveLockStateAndUnlock(&lockInfo); + ASSERT_EQUALS(0U, lockInfo.locks.size()); + + // Lock the global lock. + locker.lockGlobal(opCtx.get(), MODE_IX); + locker.lockGlobal(opCtx.get(), MODE_IX); + + // Should invariant + locker.saveLockStateAndUnlock(&lockInfo); +} + +} // namespace mongo diff --git a/src/mongo/db/curop.h b/src/mongo/db/curop.h index 9f5c32b10d1..c19586d5833 100644 --- a/src/mongo/db/curop.h +++ b/src/mongo/db/curop.h @@ -30,8 +30,6 @@ #pragma once -#include - #include "mongo/config.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/user_acquisition_stats.h" diff --git a/src/mongo/db/db_raii_multi_collection_test.cpp b/src/mongo/db/db_raii_multi_collection_test.cpp index da00f5b9fa5..439697d2128 100644 --- a/src/mongo/db/db_raii_multi_collection_test.cpp +++ b/src/mongo/db/db_raii_multi_collection_test.cpp @@ -29,7 +29,7 @@ #include "mongo/db/catalog/catalog_test_fixture.h" #include "mongo/db/client.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/db_raii.h" #include "mongo/logv2/log.h" #include "mongo/unittest/unittest.h" diff --git a/src/mongo/db/db_raii_test.cpp b/src/mongo/db/db_raii_test.cpp index d2eba6919cc..64f421c988e 100644 --- a/src/mongo/db/db_raii_test.cpp +++ b/src/mongo/db/db_raii_test.cpp @@ -31,7 +31,7 @@ #include "mongo/db/catalog/catalog_test_fixture.h" #include "mongo/db/client.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/db_raii.h" #include "mongo/db/query/find_common.h" #include "mongo/db/query/get_executor.h" diff --git a/src/mongo/db/exec/sbe/abt/sbe_abt_diff_test.cpp b/src/mongo/db/exec/sbe/abt/sbe_abt_diff_test.cpp index d71b3d040d6..7557690d993 100644 --- a/src/mongo/db/exec/sbe/abt/sbe_abt_diff_test.cpp +++ b/src/mongo/db/exec/sbe/abt/sbe_abt_diff_test.cpp @@ -27,7 +27,7 @@ * it in the license file. */ -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/exec/sbe/abt/sbe_abt_test_util.h" #include "mongo/unittest/temp_dir.h" diff --git a/src/mongo/db/index/SConscript b/src/mongo/db/index/SConscript index 6a386ae1ada..c26571bde0f 100644 --- a/src/mongo/db/index/SConscript +++ b/src/mongo/db/index/SConscript @@ -70,13 +70,11 @@ iamEnv.Library( '$BUILD_DIR/mongo/db/multi_key_path_tracker', '$BUILD_DIR/mongo/db/pipeline/document_path_support', '$BUILD_DIR/mongo/db/query/collation/collator_factory_interface', - '$BUILD_DIR/mongo/db/query/collation/collator_interface', '$BUILD_DIR/mongo/db/query/op_metrics', '$BUILD_DIR/mongo/db/query/projection_ast', '$BUILD_DIR/mongo/db/query/sort_pattern', '$BUILD_DIR/mongo/db/query_expressions', '$BUILD_DIR/mongo/db/record_id_helpers', - '$BUILD_DIR/mongo/db/repl/repl_coordinator_interface', '$BUILD_DIR/mongo/db/resumable_index_builds_idl', '$BUILD_DIR/mongo/db/server_base', '$BUILD_DIR/mongo/db/service_context', diff --git a/src/mongo/db/introspect.cpp b/src/mongo/db/introspect.cpp index aa52b86af04..e757543277e 100644 --- a/src/mongo/db/introspect.cpp +++ b/src/mongo/db/introspect.cpp @@ -35,7 +35,7 @@ #include "mongo/db/catalog/collection_write_path.h" #include "mongo/db/client.h" #include "mongo/db/concurrency/exception_util.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/curop.h" #include "mongo/db/db_raii.h" #include "mongo/db/jsobj.h" diff --git a/src/mongo/db/mongod_main.cpp b/src/mongo/db/mongod_main.cpp index 8a8e057a8b4..4fdd1c6f456 100644 --- a/src/mongo/db/mongod_main.cpp +++ b/src/mongo/db/mongod_main.cpp @@ -72,7 +72,7 @@ #include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/concurrency/flow_control_ticketholder.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/concurrency/replication_state_transition_lock_guard.h" #include "mongo/db/db_raii.h" #include "mongo/db/dbdirectclient.h" diff --git a/src/mongo/db/operation_context.h b/src/mongo/db/operation_context.h index 0027cb683b1..32898918484 100644 --- a/src/mongo/db/operation_context.h +++ b/src/mongo/db/operation_context.h @@ -29,10 +29,7 @@ #pragma once -#include "mongo/util/assert_util.h" #include -#include -#include #include "mongo/base/status.h" #include "mongo/db/client.h" @@ -49,6 +46,7 @@ #include "mongo/platform/mutex.h" #include "mongo/stdx/condition_variable.h" #include "mongo/transport/session.h" +#include "mongo/util/assert_util.h" #include "mongo/util/cancellation.h" #include "mongo/util/concurrency/with_lock.h" #include "mongo/util/decorable.h" @@ -61,9 +59,7 @@ namespace mongo { class CurOp; -class ProgressMeter; class ServiceContext; -class StringData; namespace repl { class UnreplicatedWritesBlock; diff --git a/src/mongo/db/pipeline/process_interface/shardsvr_process_interface_test.cpp b/src/mongo/db/pipeline/process_interface/shardsvr_process_interface_test.cpp index 3a457d8112d..417739a8c34 100644 --- a/src/mongo/db/pipeline/process_interface/shardsvr_process_interface_test.cpp +++ b/src/mongo/db/pipeline/process_interface/shardsvr_process_interface_test.cpp @@ -27,7 +27,7 @@ * it in the license file. */ -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/pipeline/document_source_out.h" #include "mongo/db/pipeline/document_source_queue.h" #include "mongo/db/pipeline/process_interface/shardsvr_process_interface.h" diff --git a/src/mongo/db/process_health/SConscript b/src/mongo/db/process_health/SConscript index d30b66e619b..036c0a49fcb 100644 --- a/src/mongo/db/process_health/SConscript +++ b/src/mongo/db/process_health/SConscript @@ -65,6 +65,7 @@ env.CppUnitTest( ], LIBDEPS=[ '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/executor/network_interface_mock', '$BUILD_DIR/mongo/executor/task_executor_test_fixture', '$BUILD_DIR/mongo/executor/thread_pool_task_executor_test_fixture', diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript index 7f9e1c69a00..d552d581044 100644 --- a/src/mongo/db/query/SConscript +++ b/src/mongo/db/query/SConscript @@ -275,16 +275,18 @@ env.Library( ) env.Library( - target="query_test_service_context", + target='query_test_service_context', source=[ - "query_test_service_context.cpp", + 'query_test_service_context.cpp', ], LIBDEPS=[ - "$BUILD_DIR/mongo/db/service_context", - "$BUILD_DIR/mongo/db/session/logical_session_id", - "collation/collator_factory_mock", + '$BUILD_DIR/mongo/db/service_context', + '$BUILD_DIR/mongo/db/session/logical_session_id', + 'collation/collator_factory_mock', + ], + LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/db/concurrency/lock_manager', ], - LIBDEPS_PRIVATE=[], ) env.Library( diff --git a/src/mongo/db/query/ce/maxdiff_histogram_test.cpp b/src/mongo/db/query/ce/maxdiff_histogram_test.cpp index 7225c1a8774..10e528cd6e4 100644 --- a/src/mongo/db/query/ce/maxdiff_histogram_test.cpp +++ b/src/mongo/db/query/ce/maxdiff_histogram_test.cpp @@ -27,7 +27,7 @@ * it in the license file. */ -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/exec/sbe/abt/sbe_abt_test_util.h" #include "mongo/db/exec/sbe/values/value.h" #include "mongo/db/query/ce/histogram_predicate_estimation.h" diff --git a/src/mongo/db/query/optimizer/SConscript b/src/mongo/db/query/optimizer/SConscript index 2abdccf1f04..80f144ee898 100644 --- a/src/mongo/db/query/optimizer/SConscript +++ b/src/mongo/db/query/optimizer/SConscript @@ -150,6 +150,7 @@ env.CppUnitTest( "interval_simplify_test.cpp", ], LIBDEPS=[ + "$BUILD_DIR/mongo/db/concurrency/lock_manager", "$BUILD_DIR/mongo/db/service_context_test_fixture", "optimizer", "unit_test_pipeline_utils", diff --git a/src/mongo/db/repl/apply_ops.cpp b/src/mongo/db/repl/apply_ops.cpp index 374ebb27ae9..1fdf6450262 100644 --- a/src/mongo/db/repl/apply_ops.cpp +++ b/src/mongo/db/repl/apply_ops.cpp @@ -35,7 +35,6 @@ #include "mongo/db/catalog/document_validation.h" #include "mongo/db/client.h" #include "mongo/db/concurrency/exception_util.h" -#include "mongo/db/concurrency/lock_state.h" #include "mongo/db/curop.h" #include "mongo/db/database_name.h" #include "mongo/db/db_raii.h" diff --git a/src/mongo/db/repl/replication_coordinator_impl.cpp b/src/mongo/db/repl/replication_coordinator_impl.cpp index c402aca5bdd..dece58c6c7c 100644 --- a/src/mongo/db/repl/replication_coordinator_impl.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl.cpp @@ -52,7 +52,7 @@ #include "mongo/db/commands.h" #include "mongo/db/commands/feature_compatibility_version.h" #include "mongo/db/concurrency/d_concurrency.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/lock_manager.h" #include "mongo/db/concurrency/replication_state_transition_lock_guard.h" #include "mongo/db/curop.h" #include "mongo/db/curop_failpoint_helpers.h" @@ -91,6 +91,7 @@ #include "mongo/db/serverless/serverless_operation_lock_registry.h" #include "mongo/db/session/kill_sessions_local.h" #include "mongo/db/session/session_catalog.h" +#include "mongo/db/shard_role.h" #include "mongo/db/shutdown_in_progress_quiesce_info.h" #include "mongo/db/storage/control/journal_flusher.h" #include "mongo/db/storage/storage_options.h" @@ -2632,7 +2633,7 @@ ReplicationCoordinatorImpl::AutoGetRstlForStepUpStepDown::AutoGetRstlForStepUpSt } // Dump all locks to identify which thread(s) are holding RSTL. - getGlobalLockManager()->dump(); + LockManager::get(opCtx)->dump(); auto lockerInfo = opCtx->lockState()->getLockerInfo(CurOp::get(opCtx)->getLockStatsBase()); BSONObjBuilder lockRep; diff --git a/src/mongo/db/repl/replication_coordinator_impl_test.cpp b/src/mongo/db/repl/replication_coordinator_impl_test.cpp index ae89606f267..adfa65af6f7 100644 --- a/src/mongo/db/repl/replication_coordinator_impl_test.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl_test.cpp @@ -27,9 +27,6 @@ * it in the license file. */ - -#include "mongo/platform/basic.h" - #include #include #include @@ -38,7 +35,7 @@ #include "mongo/bson/util/bson_extract.h" #include "mongo/db/catalog/commit_quorum_options.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/concurrency/replication_state_transition_lock_guard.h" #include "mongo/db/read_write_concern_defaults.h" #include "mongo/db/repl/bson_extract_optime.h" @@ -83,7 +80,6 @@ #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - namespace mongo { namespace repl { namespace { diff --git a/src/mongo/db/service_entry_point_mongod.cpp b/src/mongo/db/service_entry_point_mongod.cpp index a09e32be199..ecf95334f83 100644 --- a/src/mongo/db/service_entry_point_mongod.cpp +++ b/src/mongo/db/service_entry_point_mongod.cpp @@ -27,13 +27,10 @@ * it in the license file. */ - -#include "mongo/platform/basic.h" - #include "mongo/db/service_entry_point_mongod.h" #include "mongo/db/commands/fsync_locked.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/curop.h" #include "mongo/db/read_concern.h" #include "mongo/db/repl/repl_client_info.h" @@ -53,7 +50,6 @@ #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand - namespace mongo { class ServiceEntryPointMongod::Hooks final : public ServiceEntryPointCommon::Hooks { diff --git a/src/mongo/db/shard_role.cpp b/src/mongo/db/shard_role.cpp index 4e5707931e3..5a65652ee8a 100644 --- a/src/mongo/db/shard_role.cpp +++ b/src/mongo/db/shard_role.cpp @@ -40,13 +40,8 @@ #include "mongo/db/concurrency/exception_util.h" #include "mongo/db/curop.h" #include "mongo/db/db_raii.h" -#include "mongo/db/namespace_string.h" #include "mongo/db/repl/read_concern_args.h" #include "mongo/db/s/collection_sharding_runtime.h" -#include "mongo/db/s/collection_sharding_state.h" -#include "mongo/db/s/database_sharding_state.h" -#include "mongo/db/s/operation_sharding_state.h" -#include "mongo/db/s/scoped_collection_metadata.h" #include "mongo/db/storage/capped_snapshots.h" #include "mongo/logv2/log.h" #include "mongo/util/decorable.h" diff --git a/src/mongo/db/shard_role.h b/src/mongo/db/shard_role.h index 2d778d3049c..e8a8b298e84 100644 --- a/src/mongo/db/shard_role.h +++ b/src/mongo/db/shard_role.h @@ -30,10 +30,10 @@ #pragma once #include "mongo/db/catalog/collection_catalog.h" -#include "mongo/db/repl/read_concern_args.h" -#include "mongo/db/s/scoped_collection_metadata.h" +#include "mongo/db/s/collection_sharding_state.h" +#include "mongo/db/s/database_sharding_state.h" +#include "mongo/db/s/operation_sharding_state.h" #include "mongo/db/transaction_resources.h" -#include "mongo/s/database_version.h" namespace mongo { diff --git a/src/mongo/db/storage/SConscript b/src/mongo/db/storage/SConscript index 8f7b2f1e731..eb04f974fb5 100644 --- a/src/mongo/db/storage/SConscript +++ b/src/mongo/db/storage/SConscript @@ -32,7 +32,7 @@ env.Library( 'snapshot_helper.cpp', ], LIBDEPS_PRIVATE=[ - '$BUILD_DIR/mongo/db/concurrency/lock_manager_defs', + '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/db/repl/read_concern_args', '$BUILD_DIR/mongo/db/repl/repl_coordinator_interface', '$BUILD_DIR/mongo/db/server_base', diff --git a/src/mongo/db/storage/kv/kv_drop_pending_ident_reaper_test.cpp b/src/mongo/db/storage/kv/kv_drop_pending_ident_reaper_test.cpp index 9b36ac2afd9..f0cbd2a0795 100644 --- a/src/mongo/db/storage/kv/kv_drop_pending_ident_reaper_test.cpp +++ b/src/mongo/db/storage/kv/kv_drop_pending_ident_reaper_test.cpp @@ -27,11 +27,6 @@ * it in the license file. */ -#include "mongo/platform/basic.h" - -#include - -#include "mongo/db/concurrency/lock_state.h" #include "mongo/db/service_context_test_fixture.h" #include "mongo/db/storage/ident.h" #include "mongo/db/storage/kv/kv_drop_pending_ident_reaper.h" diff --git a/src/mongo/db/storage/storage_engine_init.cpp b/src/mongo/db/storage/storage_engine_init.cpp index 6e2b2e23eec..76e88753b2b 100644 --- a/src/mongo/db/storage/storage_engine_init.cpp +++ b/src/mongo/db/storage/storage_engine_init.cpp @@ -27,17 +27,13 @@ * it in the license file. */ - -#include "mongo/platform/basic.h" - #include "mongo/db/storage/storage_engine_init.h" #include -#include #include "mongo/base/init.h" #include "mongo/bson/bsonobjbuilder.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/operation_context.h" #include "mongo/db/storage/control/storage_control.h" #include "mongo/db/storage/execution_control/concurrency_adjustment_parameters_gen.h" @@ -59,9 +55,7 @@ #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kStorage - namespace mongo { - namespace { /** * Creates the lock file used to prevent concurrent processes from accessing the data files, diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_oplog_manager.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_oplog_manager.cpp index 068e6d15098..888935ca7e4 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_oplog_manager.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_oplog_manager.cpp @@ -27,15 +27,11 @@ * it in the license file. */ +#include "mongo/db/storage/wiredtiger/wiredtiger_oplog_manager.h" -#include "mongo/platform/basic.h" - -#include - -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h" -#include "mongo/db/storage/wiredtiger/wiredtiger_oplog_manager.h" #include "mongo/db/storage/wiredtiger/wiredtiger_util.h" #include "mongo/logv2/log.h" #include "mongo/platform/mutex.h" @@ -44,7 +40,6 @@ #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kStorage - namespace mongo { MONGO_FAIL_POINT_DEFINE(WTPauseOplogVisibilityUpdateLoop); diff --git a/src/mongo/db/transaction/transaction_participant.cpp b/src/mongo/db/transaction/transaction_participant.cpp index 2bba3023e3c..d31e7a5403d 100644 --- a/src/mongo/db/transaction/transaction_participant.cpp +++ b/src/mongo/db/transaction/transaction_participant.cpp @@ -44,8 +44,7 @@ #include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/concurrency/exception_util.h" -#include "mongo/db/concurrency/lock_state.h" -#include "mongo/db/concurrency/locker.h" +#include "mongo/db/concurrency/locker_impl.h" #include "mongo/db/concurrency/replication_state_transition_lock_guard.h" #include "mongo/db/curop_failpoint_helpers.h" #include "mongo/db/dbdirectclient.h" diff --git a/src/mongo/db/transaction/transaction_participant_test.cpp b/src/mongo/db/transaction/transaction_participant_test.cpp index 54376f4cc6a..59c8d988c8f 100644 --- a/src/mongo/db/transaction/transaction_participant_test.cpp +++ b/src/mongo/db/transaction/transaction_participant_test.cpp @@ -50,7 +50,6 @@ #include "mongo/db/session/session_catalog_mongod.h" #include "mongo/db/session/session_txn_record_gen.h" #include "mongo/db/shard_role.h" -#include "mongo/db/stats/fill_locker_info.h" #include "mongo/db/storage/durable_history_pin.h" #include "mongo/db/transaction/server_transactions_metrics.h" #include "mongo/db/transaction/session_catalog_mongod_transaction_interface_impl.h" diff --git a/src/mongo/embedded/embedded.cpp b/src/mongo/embedded/embedded.cpp index ee0fca37913..b746a2142a6 100644 --- a/src/mongo/embedded/embedded.cpp +++ b/src/mongo/embedded/embedded.cpp @@ -27,9 +27,6 @@ * it in the license file. */ - -#include "mongo/platform/basic.h" - #include "mongo/embedded/embedded.h" #include "mongo/base/initializer.h" @@ -42,7 +39,7 @@ #include "mongo/db/client.h" #include "mongo/db/commands/feature_compatibility_version.h" #include "mongo/db/commands/fsync_locked.h" -#include "mongo/db/concurrency/lock_state.h" +#include "mongo/db/concurrency/locker.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/global_settings.h" #include "mongo/db/op_observer/op_observer_impl.h" diff --git a/src/mongo/executor/SConscript b/src/mongo/executor/SConscript index 587c4713785..d071ae92c4c 100644 --- a/src/mongo/executor/SConscript +++ b/src/mongo/executor/SConscript @@ -435,6 +435,7 @@ env.CppIntegrationTest( LIBDEPS=[ '$BUILD_DIR/mongo/client/async_client', '$BUILD_DIR/mongo/client/clientdriver_network', + '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/db/wire_version', '$BUILD_DIR/mongo/executor/network_interface_factory', '$BUILD_DIR/mongo/executor/network_interface_thread_pool', diff --git a/src/mongo/s/SConscript b/src/mongo/s/SConscript index cc3596046fd..3ca8219b298 100644 --- a/src/mongo/s/SConscript +++ b/src/mongo/s/SConscript @@ -618,6 +618,7 @@ env.Library( ], LIBDEPS=[ '$BUILD_DIR/mongo/client/remote_command_targeter_mock', + '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/db/service_context_test_fixture', '$BUILD_DIR/mongo/executor/network_interface_mock', '$BUILD_DIR/mongo/executor/network_test_env', diff --git a/src/mongo/shell/SConscript b/src/mongo/shell/SConscript index a8af390807f..aa68e6a4c67 100644 --- a/src/mongo/shell/SConscript +++ b/src/mongo/shell/SConscript @@ -320,6 +320,7 @@ if not has_option('noshell') and jsEngine: # library to inject a static or mongo initializer to mongo, # please add that library as a private libdep of # mongo_initializers. + "$BUILD_DIR/mongo/db/concurrency/lock_manager", "$BUILD_DIR/mongo/s/write_ops/batch_write_types", "$BUILD_DIR/mongo/transport/transport_layer", "$BUILD_DIR/mongo/util/net/ssl_manager", diff --git a/src/mongo/tools/mongobridge_tool/SConscript b/src/mongo/tools/mongobridge_tool/SConscript index f917a686bd2..6c9df84daf9 100644 --- a/src/mongo/tools/mongobridge_tool/SConscript +++ b/src/mongo/tools/mongobridge_tool/SConscript @@ -7,15 +7,16 @@ yamlEnv = env.Clone() yamlEnv.InjectThirdParty(libraries=['yaml']) mongobridge = env.Program( - target="mongobridge", + target='mongobridge', source=[ - "bridge.cpp", - "bridge_commands.cpp", - "mongobridge_options.cpp", - "mongobridge_options.idl", - "mongobridge_options_init.cpp", + 'bridge.cpp', + 'bridge_commands.cpp', + 'mongobridge_options.cpp', + 'mongobridge_options.idl', + 'mongobridge_options_init.cpp', ], LIBDEPS=[ + '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/db/dbmessage', '$BUILD_DIR/mongo/rpc/rpc', '$BUILD_DIR/mongo/transport/message_compressor', diff --git a/src/mongo/transport/SConscript b/src/mongo/transport/SConscript index 868890f48ba..64ae7d6d536 100644 --- a/src/mongo/transport/SConscript +++ b/src/mongo/transport/SConscript @@ -285,6 +285,7 @@ env.Benchmark( ], LIBDEPS=[ '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/db/service_context_test_fixture', 'service_executor', 'transport_layer_mock', @@ -297,6 +298,7 @@ env.Benchmark( 'session_workflow_bm.cpp', ], LIBDEPS=[ + '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/db/service_context_test_fixture', 'service_entry_point', 'service_executor', diff --git a/src/mongo/unittest/SConscript b/src/mongo/unittest/SConscript index 0d1dfc44e67..1e5e540a79c 100644 --- a/src/mongo/unittest/SConscript +++ b/src/mongo/unittest/SConscript @@ -63,7 +63,7 @@ env.Library( ) env.Library( - target="integration_test_main", + target='integration_test_main', source=[ 'integration_test_main.cpp', 'integration_test_main.idl', @@ -75,6 +75,7 @@ env.Library( LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/client/connection_string', '$BUILD_DIR/mongo/db/commands/test_commands_enabled', + '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/db/server_options', '$BUILD_DIR/mongo/db/server_options_base', '$BUILD_DIR/mongo/db/serverinit', diff --git a/src/mongo/util/concurrency/SConscript b/src/mongo/util/concurrency/SConscript index acc81c451b4..e715ef3a9a8 100644 --- a/src/mongo/util/concurrency/SConscript +++ b/src/mongo/util/concurrency/SConscript @@ -41,13 +41,20 @@ env.Library( ], ) -env.Library(target='admission_context', source=['admission_context.cpp'], - LIBDEPS=['$BUILD_DIR/mongo/base']) +env.Library( + target='admission_context', + source=[ + 'admission_context.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + ], +) env.Library( target='spin_lock', source=[ - "spin_lock.cpp", + 'spin_lock.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/base', @@ -81,6 +88,7 @@ env.Benchmark( 'ticketholder_bm.cpp', ], LIBDEPS=[ + '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/db/service_context', 'ticketholder', ], diff --git a/src/mongo/util/concurrency/priority_ticketholder.cpp b/src/mongo/util/concurrency/priority_ticketholder.cpp index 4656c7ab4f4..f99eb6a0331 100644 --- a/src/mongo/util/concurrency/priority_ticketholder.cpp +++ b/src/mongo/util/concurrency/priority_ticketholder.cpp @@ -27,14 +27,11 @@ * it in the license file. */ -#include "mongo/platform/basic.h" - -#include "mongo/db/service_context.h" -#include "mongo/util/concurrency/admission_context.h" #include "mongo/util/concurrency/priority_ticketholder.h" #include +#include "mongo/db/service_context.h" #include "mongo/logv2/log.h" #include "mongo/util/str.h" diff --git a/src/mongo/util/concurrency/priority_ticketholder_test.cpp b/src/mongo/util/concurrency/priority_ticketholder_test.cpp index fff70a982ec..1bce5630521 100644 --- a/src/mongo/util/concurrency/priority_ticketholder_test.cpp +++ b/src/mongo/util/concurrency/priority_ticketholder_test.cpp @@ -30,7 +30,6 @@ #include "mongo/db/concurrency/locker_noop_client_observer.h" #include "mongo/logv2/log.h" #include "mongo/unittest/barrier.h" -#include "mongo/util/concurrency/admission_context.h" #include "mongo/util/concurrency/priority_ticketholder.h" #include "mongo/util/concurrency/ticketholder_test_fixture.h" #include "mongo/util/periodic_runner_factory.h" diff --git a/src/mongo/util/concurrency/semaphore_ticketholder.cpp b/src/mongo/util/concurrency/semaphore_ticketholder.cpp index 00517a0fd31..f849a88b9ce 100644 --- a/src/mongo/util/concurrency/semaphore_ticketholder.cpp +++ b/src/mongo/util/concurrency/semaphore_ticketholder.cpp @@ -27,16 +27,13 @@ * it in the license file. */ -#include "mongo/platform/basic.h" - -#include "mongo/db/service_context.h" -#include "mongo/util/concurrency/admission_context.h" #include "mongo/util/concurrency/semaphore_ticketholder.h" -#include "mongo/util/concurrency/ticketholder.h" #include +#include "mongo/db/service_context.h" #include "mongo/logv2/log.h" +#include "mongo/util/concurrency/ticketholder.h" #include "mongo/util/str.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault diff --git a/src/mongo/util/concurrency/ticketholder.cpp b/src/mongo/util/concurrency/ticketholder.cpp index dd527f9e177..efc043ba816 100644 --- a/src/mongo/util/concurrency/ticketholder.cpp +++ b/src/mongo/util/concurrency/ticketholder.cpp @@ -28,14 +28,13 @@ */ #include "mongo/util/concurrency/ticketholder.h" + +#include + #include "mongo/db/service_context.h" #include "mongo/db/storage/execution_control/concurrency_adjustment_parameters_gen.h" #include "mongo/db/storage/storage_engine_feature_flags_gen.h" #include "mongo/db/storage/storage_engine_parameters_gen.h" -#include "mongo/util/concurrency/admission_context.h" - -#include - #include "mongo/logv2/log.h" #include "mongo/util/str.h" -- cgit v1.2.1