summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRahul Sundararaman <rahul.sundararaman@mongodb.com>2019-11-19 20:52:49 +0000
committerevergreen <evergreen@mongodb.com>2019-11-19 20:52:49 +0000
commitebbc4ed166c5f5aec7700e44b47b1c5879917c61 (patch)
tree50d4cec5c97b768ec8c50138191bb21c9867ff0b
parent88956acd276472b1b4c0192f73b07d67c4cae29c (diff)
downloadmongo-ebbc4ed166c5f5aec7700e44b47b1c5879917c61.tar.gz
SERVER-42897 Validate base-level latches
-rw-r--r--src/mongo/base/error_codes.yml1
-rw-r--r--src/mongo/platform/mutex.cpp26
-rw-r--r--src/mongo/platform/mutex.h61
-rw-r--r--src/mongo/util/SConscript13
-rw-r--r--src/mongo/util/diagnostic_info.cpp12
-rw-r--r--src/mongo/util/hierarchical_acquisition.h8
-rw-r--r--src/mongo/util/hierarchical_acquisition_test.cpp2
-rw-r--r--src/mongo/util/latch_analyzer.cpp112
-rw-r--r--src/mongo/util/latch_analyzer_test.cpp108
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