diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/base/error_codes.yml | 1 | ||||
-rw-r--r-- | src/mongo/platform/mutex.cpp | 26 | ||||
-rw-r--r-- | src/mongo/platform/mutex.h | 61 | ||||
-rw-r--r-- | src/mongo/util/SConscript | 13 | ||||
-rw-r--r-- | src/mongo/util/diagnostic_info.cpp | 12 | ||||
-rw-r--r-- | src/mongo/util/hierarchical_acquisition.h | 8 | ||||
-rw-r--r-- | src/mongo/util/hierarchical_acquisition_test.cpp | 2 | ||||
-rw-r--r-- | src/mongo/util/latch_analyzer.cpp | 112 | ||||
-rw-r--r-- | src/mongo/util/latch_analyzer_test.cpp | 108 |
9 files changed, 301 insertions, 42 deletions
diff --git a/src/mongo/base/error_codes.yml b/src/mongo/base/error_codes.yml index 1dca1fea005..877a90fff0d 100644 --- a/src/mongo/base/error_codes.yml +++ b/src/mongo/base/error_codes.yml @@ -330,6 +330,7 @@ error_codes: - {code: 294,name: InvalidTopologyType} - {code: 295,name: InvalidHeartBeatFrequency} - {code: 296,name: TopologySetNameRequired} + - {code: 297,name: HierarchicalAcquisitionLevelViolation} # Error codes 4000-8999 are reserved. diff --git a/src/mongo/platform/mutex.cpp b/src/mongo/platform/mutex.cpp index 3f2a18fe58a..620afcd2b48 100644 --- a/src/mongo/platform/mutex.cpp +++ b/src/mongo/platform/mutex.cpp @@ -33,16 +33,16 @@ namespace mongo { void Mutex::lock() { if (_mutex.try_lock()) { - _onQuickLock(_name); + _onQuickLock(_id); return; } - _onContendedLock(_name); + _onContendedLock(_id); _mutex.lock(); - _onSlowLock(_name); + _onSlowLock(_id); } void Mutex::unlock() { - _onUnlock(_name); + _onUnlock(_id); _mutex.unlock(); } bool Mutex::try_lock() { @@ -50,7 +50,7 @@ bool Mutex::try_lock() { return false; } - _onQuickLock(_name); + _onQuickLock(_id); return true; } @@ -60,31 +60,31 @@ void Mutex::addLockListener(LockListener* listener) { state.list.push_back(listener); } -void Mutex::_onContendedLock(const StringData& name) noexcept { +void Mutex::_onContendedLock(const Identity& id) noexcept { auto& state = _getListenerState(); for (auto listener : state.list) { - listener->onContendedLock(name); + listener->onContendedLock(id); } } -void Mutex::_onQuickLock(const StringData& name) noexcept { +void Mutex::_onQuickLock(const Identity& id) noexcept { auto& state = _getListenerState(); for (auto listener : state.list) { - listener->onQuickLock(name); + listener->onQuickLock(id); } } -void Mutex::_onSlowLock(const StringData& name) noexcept { +void Mutex::_onSlowLock(const Identity& id) noexcept { auto& state = _getListenerState(); for (auto listener : state.list) { - listener->onSlowLock(name); + listener->onSlowLock(id); } } -void Mutex::_onUnlock(const StringData& name) noexcept { +void Mutex::_onUnlock(const Identity& id) noexcept { auto& state = _getListenerState(); for (auto listener : state.list) { - listener->onUnlock(name); + listener->onUnlock(id); } } diff --git a/src/mongo/platform/mutex.h b/src/mongo/platform/mutex.h index dd6bd4996a0..ee86cfd8772 100644 --- a/src/mongo/platform/mutex.h +++ b/src/mongo/platform/mutex.h @@ -34,8 +34,10 @@ #include "mongo/base/error_codes.h" #include "mongo/base/string_data.h" #include "mongo/platform/atomic_word.h" +#include "mongo/platform/source_location.h" #include "mongo/stdx/mutex.h" #include "mongo/util/duration.h" +#include "mongo/util/hierarchical_acquisition.h" namespace mongo { @@ -60,18 +62,40 @@ public: static constexpr auto kAnonymousMutexStr = "AnonymousMutex"_sd; - Mutex() : Mutex(kAnonymousMutexStr) {} - // Note that StringData is a view type, thus the underlying string for _name must outlive any - // given Mutex - explicit Mutex(const StringData& name) : _name(name) {} - void lock() override; void unlock() override; bool try_lock() override; StringData getName() const override { - return _name; + return StringData(_id.name); } + struct Identity { + Identity(StringData name = kAnonymousMutexStr) : Identity(boost::none, boost::none, name) {} + + Identity(SourceLocationHolder sourceLocation, StringData name = kAnonymousMutexStr) + : Identity(boost::none, sourceLocation, name) {} + + Identity(hierarchical_acquisition_detail::Level level, StringData name = kAnonymousMutexStr) + : Identity(level, boost::none, name) {} + + Identity(boost::optional<hierarchical_acquisition_detail::Level> level, + boost::optional<SourceLocationHolder> sourceLocation, + StringData name = kAnonymousMutexStr) + : level(level), sourceLocation(sourceLocation), name(name.toString()) {} + + boost::optional<hierarchical_acquisition_detail::Level> level; + boost::optional<SourceLocationHolder> sourceLocation; + std::string name; + }; + + Mutex() : Mutex(Identity()) {} + + Mutex(const Identity& id) : _id(id) {} + + struct LatchSetState { + hierarchical_acquisition_detail::Set levelsHeld; + }; + /** * This function adds a LockListener subclass to the triggers for certain actions. * @@ -93,12 +117,13 @@ private: return state; } - static void _onContendedLock(const StringData& name) noexcept; - static void _onQuickLock(const StringData& name) noexcept; - static void _onSlowLock(const StringData& name) noexcept; - static void _onUnlock(const StringData& name) noexcept; + static void _onContendedLock(const Identity& id) noexcept; + static void _onQuickLock(const Identity& id) noexcept; + static void _onSlowLock(const Identity& id) noexcept; + static void _onUnlock(const Identity& id) noexcept; + + const Identity _id; - const StringData _name; stdx::mutex _mutex; // NOLINT }; @@ -114,22 +139,22 @@ public: /** * Action to do when a lock cannot be immediately acquired */ - virtual void onContendedLock(const StringData& name) = 0; + virtual void onContendedLock(const Identity& id) = 0; /** * Action to do when a lock was acquired without blocking */ - virtual void onQuickLock(const StringData& name) = 0; + virtual void onQuickLock(const Identity& id) = 0; /** * Action to do when a lock was acquired after blocking */ - virtual void onSlowLock(const StringData& name) = 0; + virtual void onSlowLock(const Identity& id) = 0; /** * Action to do when a lock is unlocked */ - virtual void onUnlock(const StringData& name) = 0; + virtual void onUnlock(const Identity& id) = 0; }; } // namespace mongo @@ -137,7 +162,7 @@ public: /** * Define a mongo::Mutex with all arguments passed through to the ctor */ -#define MONGO_MAKE_LATCH(...) \ - mongo::Mutex { \ - __VA_ARGS__ \ +#define MONGO_MAKE_LATCH(...) \ + mongo::Mutex { \ + mongo::Mutex::Identity(__VA_ARGS__) \ } diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript index 22836567ba2..ef37ce0e00a 100644 --- a/src/mongo/util/SConscript +++ b/src/mongo/util/SConscript @@ -284,6 +284,17 @@ env.Library( ], ) +env.Library( + target='latch_analyzer', + source= [ + 'latch_analyzer.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + "$BUILD_DIR/mongo/db/service_context", + ], +) + env.Benchmark( target='clock_source_bm', source=[ @@ -552,6 +563,7 @@ icuEnv.CppUnitTest( 'icu_test.cpp', 'invalidating_lru_cache_test.cpp', 'itoa_test.cpp', + 'latch_analyzer_test.cpp', 'lockable_adapter_test.cpp', 'lru_cache_test.cpp', 'md5_test.cpp', @@ -584,6 +596,7 @@ icuEnv.CppUnitTest( 'clock_source_mock', 'clock_sources', 'diagnostic_info', + 'latch_analyzer', 'dns_query', 'fail_point', 'icu', diff --git a/src/mongo/util/diagnostic_info.cpp b/src/mongo/util/diagnostic_info.cpp index e6bf99a15bf..869dd13babe 100644 --- a/src/mongo/util/diagnostic_info.cpp +++ b/src/mongo/util/diagnostic_info.cpp @@ -174,24 +174,24 @@ const auto getDiagnosticInfoHandle = Client::declareDecoration<DiagnosticInfoHan MONGO_INITIALIZER(LockListener)(InitializerContext* context) { class LockListener : public Mutex::LockListener { - void onContendedLock(const StringData& name) override { + void onContendedLock(const Mutex::Identity& id) override { if (auto client = Client::getCurrent()) { auto& handle = getDiagnosticInfoHandle(client); stdx::lock_guard<stdx::mutex> lk(handle.mutex); - handle.list.emplace_front(DiagnosticInfo::capture(name)); + handle.list.emplace_front(DiagnosticInfo::capture(id.name)); if (currentOpSpawnsThreadWaitingForLatch.shouldFail() && - (name == kBlockedOpMutexName)) { + (id.name == kBlockedOpMutexName)) { gBlockedOp.setIsContended(true); } } } - void onQuickLock(const StringData&) override { + void onQuickLock(const Mutex::Identity&) override { // Do nothing } - void onSlowLock(const StringData& name) override { + void onSlowLock(const Mutex::Identity& id) override { if (auto client = Client::getCurrent()) { auto& handle = getDiagnosticInfoHandle(client); stdx::lock_guard<stdx::mutex> lk(handle.mutex); @@ -201,7 +201,7 @@ MONGO_INITIALIZER(LockListener)(InitializerContext* context) { } } - void onUnlock(const StringData&) override { + void onUnlock(const Mutex::Identity&) override { // Do nothing } }; diff --git a/src/mongo/util/hierarchical_acquisition.h b/src/mongo/util/hierarchical_acquisition.h index e4955088b9d..04a881b6cc9 100644 --- a/src/mongo/util/hierarchical_acquisition.h +++ b/src/mongo/util/hierarchical_acquisition.h @@ -36,7 +36,7 @@ namespace mongo { -namespace hierachical_acquisition_detail { +namespace hierarchical_acquisition_detail { /** * Hierarchical acquisition types are light-weight wrappers around bitwise math @@ -254,9 +254,9 @@ private: ValueType _value = 0; }; -} // namespace hierachical_acquisition_detail +} // namespace hierarchical_acquisition_detail -using HierarchicalAcquisitionSet = hierachical_acquisition_detail::Set; -using HierarchicalAcquisitionLevel = hierachical_acquisition_detail::Level; +using HierarchicalAcquisitionSet = hierarchical_acquisition_detail::Set; +using HierarchicalAcquisitionLevel = hierarchical_acquisition_detail::Level; } // namespace mongo diff --git a/src/mongo/util/hierarchical_acquisition_test.cpp b/src/mongo/util/hierarchical_acquisition_test.cpp index 97eed6c7cd5..5e1c99e3359 100644 --- a/src/mongo/util/hierarchical_acquisition_test.cpp +++ b/src/mongo/util/hierarchical_acquisition_test.cpp @@ -39,7 +39,7 @@ namespace mongo { namespace { -using namespace hierachical_acquisition_detail; +using namespace hierarchical_acquisition_detail; struct Context { friend std::string toString(Context context) { diff --git a/src/mongo/util/latch_analyzer.cpp b/src/mongo/util/latch_analyzer.cpp new file mode 100644 index 00000000000..5a868fedb05 --- /dev/null +++ b/src/mongo/util/latch_analyzer.cpp @@ -0,0 +1,112 @@ +/** + * 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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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/client.h" +#include "mongo/platform/mutex.h" + +namespace mongo { + +using Set = HierarchicalAcquisitionSet; + +namespace { + +const auto getLatchSetState = Client::declareDecoration<Mutex::LatchSetState>(); + +/** + * LockListener sub-class to implement updating set in LatchSetState + */ +class MutexLockListener : public Mutex::LockListener { + +public: + void onContendedLock(const Mutex::Identity&) { + // Do nothing + } + + void onQuickLock(const Mutex::Identity& id) { + onAcquire(id); + } + + void onSlowLock(const Mutex::Identity& id) { + onAcquire(id); + } + + void onUnlock(const Mutex::Identity& id) { + onRelease(id); + } + +private: + void onAcquire(const Mutex::Identity& id) { + if (!id.level) { + return; + } + if (auto client = Client::getCurrent()) { + auto& handle = getLatchSetState(client); + auto result = handle.levelsHeld.add(id.level.get()); + if (result != Set::AddResult::kValidWasAbsent) { + // TODO: SERVER-44570 Create a non process-fatal variant of invariant() + fassert(31360, + Status(ErrorCodes::HierarchicalAcquisitionLevelViolation, + str::stream() + << "Theoretical deadlock alert - " << toString(result) + << " latch acquisition at " << id.sourceLocation->toString() + << " on " << id.name)); + } + } + } + + void onRelease(const Mutex::Identity& id) { + if (!id.level) { + return; + } + if (auto client = Client::getCurrent()) { + auto& handle = getLatchSetState(client); + auto result = handle.levelsHeld.remove(id.level.get()); + if (result != Set::RemoveResult::kValidWasPresent) { + // TODO: SERVER-44570 Create a non process-fatal variant of invariant() + fassert(31361, + Status(ErrorCodes::HierarchicalAcquisitionLevelViolation, + str::stream() + << "Theoretical deadlock alert - " << toString(result) + << " latch release at " << id.sourceLocation->toString() + << " on " << id.name)); + } + } + } +}; + +MONGO_INITIALIZER(CreateMutexLockListener)(InitializerContext* context) { + static auto& listener = *new MutexLockListener; + Mutex::addLockListener(&listener); + return Status::OK(); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/util/latch_analyzer_test.cpp b/src/mongo/util/latch_analyzer_test.cpp new file mode 100644 index 00000000000..adfa5688f8a --- /dev/null +++ b/src/mongo/util/latch_analyzer_test.cpp @@ -0,0 +1,108 @@ +/** + * 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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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/service_context_test_fixture.h" +#include "mongo/platform/mutex.h" +#include "mongo/platform/source_location.h" +#include "mongo/unittest/death_test.h" +#include "mongo/unittest/unittest.h" +#include "mongo/util/hierarchical_acquisition.h" + +namespace mongo { +namespace { + +using Level = HierarchicalAcquisitionLevel; + +class LatchAnalyzerTest : public ServiceContextTest {}; + +DEATH_TEST_F(LatchAnalyzerTest, AddInvalidWasAbsent, "Fatal assertion 31360") { + + Mutex lowerLevel = MONGO_MAKE_LATCH( + Level(1), (SourceLocationHolder)MONGO_SOURCE_LOCATION(), "AddInvalidWasAbsent::lowerLevel"); + lowerLevel.lock(); + Mutex higherLevel = MONGO_MAKE_LATCH(Level(2), + (SourceLocationHolder)MONGO_SOURCE_LOCATION(), + "AddInvalidWasAbsent::higherLevel"); + higherLevel.lock(); +} + +DEATH_TEST_F(LatchAnalyzerTest, AddInvalidWasPresent, "Fatal assertion 31360") { + Mutex m1 = MONGO_MAKE_LATCH( + Level(1), (SourceLocationHolder)MONGO_SOURCE_LOCATION(), "AddInvalidWasPresent::m1"); + Mutex m2 = MONGO_MAKE_LATCH( + Level(1), (SourceLocationHolder)MONGO_SOURCE_LOCATION(), "AddInvalidWasPresent::m2"); + m1.lock(); + m2.lock(); +} + +DEATH_TEST_F(LatchAnalyzerTest, RemoveInvalidWasAbsent, "Fatal assertion 31361") { + Mutex m = MONGO_MAKE_LATCH( + Level(1), (SourceLocationHolder)MONGO_SOURCE_LOCATION(), "RemoveInvalidWasAbsent::m"); + m.unlock(); + m.unlock(); +} + +DEATH_TEST_F(LatchAnalyzerTest, RemoveInvalidWasPresent, "Fatal assertion 31361") { + Mutex higherLevel = MONGO_MAKE_LATCH(Level(2), + (SourceLocationHolder)MONGO_SOURCE_LOCATION(), + "RemoveInvalidWasPresent::higherLevel"); + higherLevel.lock(); + Mutex lowerLevel = MONGO_MAKE_LATCH(Level(1), + (SourceLocationHolder)MONGO_SOURCE_LOCATION(), + "RemoveInvalidWasPresent::lowerLevel"); + lowerLevel.lock(); + higherLevel.unlock(); +} + +TEST_F(LatchAnalyzerTest, AddValidWasAbsent) { + Mutex higherLevel = MONGO_MAKE_LATCH( + Level(2), (SourceLocationHolder)MONGO_SOURCE_LOCATION(), "AddValidWasAbsent::higherLevel"); + higherLevel.lock(); + Mutex lowerLevel = MONGO_MAKE_LATCH( + Level(1), (SourceLocationHolder)MONGO_SOURCE_LOCATION(), "AddValidWasAbsent::lowerLevel"); + lowerLevel.lock(); +} + +TEST_F(LatchAnalyzerTest, RemoveValidWasPresent) { + + Mutex higherLevel = MONGO_MAKE_LATCH(Level(2), + (SourceLocationHolder)MONGO_SOURCE_LOCATION(), + "RemoveValidWasPresent::higherLevel"); + higherLevel.lock(); + Mutex lowerLevel = MONGO_MAKE_LATCH(Level(1), + (SourceLocationHolder)MONGO_SOURCE_LOCATION(), + "RemoveValidWasPresent::lowerLevel"); + lowerLevel.lock(); + + lowerLevel.unlock(); + higherLevel.unlock(); +} + +} // namespace +} // namespace mongo |