summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/platform/mutex.cpp31
-rw-r--r--src/mongo/platform/mutex.h153
-rw-r--r--src/mongo/util/diagnostic_info.cpp8
-rw-r--r--src/mongo/util/latch_analyzer.cpp13
-rw-r--r--src/mongo/util/latch_analyzer.h10
5 files changed, 114 insertions, 101 deletions
diff --git a/src/mongo/platform/mutex.cpp b/src/mongo/platform/mutex.cpp
index cd24a87a84b..fd5a5dd212f 100644
--- a/src/mongo/platform/mutex.cpp
+++ b/src/mongo/platform/mutex.cpp
@@ -77,22 +77,10 @@ StringData Mutex::getName() const {
return StringData(_data->identity().name());
}
-void Mutex::addLockListener(LockListener* listener) {
- auto& state = _getListenerState();
-
- invariant(!state.isFinalized.load());
- state.listeners.push_back(listener);
-}
-
-void Mutex::finalizeLockListeners() {
- auto& state = _getListenerState();
- state.isFinalized.store(true);
-}
-
void Mutex::_onContendedLock() noexcept {
_data->counts().contended.fetchAndAdd(1);
- auto& state = _getListenerState();
+ auto& state = latch_detail::getDiagnosticListenerState();
if (!state.isFinalized.load()) {
return;
}
@@ -105,7 +93,7 @@ void Mutex::_onContendedLock() noexcept {
void Mutex::_onQuickLock() noexcept {
_data->counts().acquired.fetchAndAdd(1);
- auto& state = _getListenerState();
+ auto& state = latch_detail::getDiagnosticListenerState();
if (!state.isFinalized.load()) {
return;
}
@@ -118,7 +106,7 @@ void Mutex::_onQuickLock() noexcept {
void Mutex::_onSlowLock() noexcept {
_data->counts().acquired.fetchAndAdd(1);
- auto& state = _getListenerState();
+ auto& state = latch_detail::getDiagnosticListenerState();
if (!state.isFinalized.load()) {
return;
}
@@ -131,7 +119,7 @@ void Mutex::_onSlowLock() noexcept {
void Mutex::_onUnlock() noexcept {
_data->counts().released.fetchAndAdd(1);
- auto& state = _getListenerState();
+ auto& state = latch_detail::getDiagnosticListenerState();
if (!state.isFinalized.load()) {
return;
}
@@ -142,12 +130,13 @@ void Mutex::_onUnlock() noexcept {
}
/**
- * Any MONGO_INITIALIZER that adds a LockListener will want to list FinalizeLockListeners as
- * a dependent initializer. This means that all LockListeners are certified to be added before main
- * and no LockListeners are ever invoked before main.
+ * Any MONGO_INITIALIZER that adds a DiagnosticListener will want to list
+ * FinalizeDiagnosticListeners as a dependent initializer. This means that all DiagnosticListeners
+ * are certified to be added before main and no DiagnosticListeners are ever invoked before main.
*/
-MONGO_INITIALIZER(FinalizeLockListeners)(InitializerContext* context) {
- Mutex::finalizeLockListeners();
+MONGO_INITIALIZER(FinalizeDiagnosticListeners)(InitializerContext* context) {
+ auto& state = latch_detail::getDiagnosticListenerState();
+ state.isFinalized.store(true);
return Status::OK();
}
diff --git a/src/mongo/platform/mutex.h b/src/mongo/platform/mutex.h
index 5165d239bdc..25ad8f04a7a 100644
--- a/src/mongo/platform/mutex.h
+++ b/src/mongo/platform/mutex.h
@@ -41,6 +41,7 @@
#include "mongo/platform/source_location.h"
#include "mongo/stdx/mutex.h"
#include "mongo/util/assert_util.h"
+#include "mongo/util/concepts.h"
#include "mongo/util/decorable.h"
#include "mongo/util/duration.h"
#include "mongo/util/hierarchical_acquisition.h"
@@ -58,6 +59,9 @@ static constexpr auto kAnonymousName = "AnonymousLatch"_sd;
/**
* An Identity encapsulates the context around a latch
+ *
+ * Identities are intended to be constructed rarely and utilized via constant reference or pointer.
+ * Once an Identity is visible in a multithreaded context, it will be effectively constant.
*/
class Identity {
public:
@@ -125,6 +129,74 @@ private:
};
/**
+ * A set of actions to happen upon notable events on a Lockable-conceptualized type
+ *
+ * The event handlers on this type will be invoked extremely frequently and can substantially affect
+ * the efficiency and overall health of the process. As a general rule, avoid logging, disk io, and
+ * networking in any DiagnosticListener functions. System functions not related to those activities
+ * should be avoided as much as possible. For example, the overhead of taking an elementary stack
+ * trace voa backtrace_symbols_fd(3) proved too heavyweight to be used in a DiagnosticListener.
+ * Additionally, in parts of our system, Mutexes can outlive the invocation of main() and, indeed,
+ * certain process global variables. DiagnosticListeners usually need to be dynamically allocated
+ * and leaked.
+ *
+ * In short, HERE BE DRAGONS. DO NOT IMPLEMENT NON-DIAGNOSTIC FUNCTIONALITY USING THIS CLASS.
+ */
+class DiagnosticListener {
+ friend class Mutex;
+
+public:
+ using Identity = latch_detail::Identity;
+
+ virtual ~DiagnosticListener() = default;
+
+ /**
+ * Action to do when a lock cannot be immediately acquired
+ */
+ virtual void onContendedLock(const Identity& id) = 0;
+
+ /**
+ * Action to do when a lock was acquired without blocking
+ */
+ virtual void onQuickLock(const Identity& id) = 0;
+
+ /**
+ * Action to do when a lock was acquired after blocking
+ */
+ virtual void onSlowLock(const Identity& id) = 0;
+
+ /**
+ * Action to do when a lock is unlocked
+ */
+ virtual void onUnlock(const Identity& id) = 0;
+};
+
+
+inline auto& getDiagnosticListenerState() noexcept {
+ struct State {
+ AtomicWord<bool> isFinalized{false};
+ std::vector<latch_detail::DiagnosticListener*> listeners;
+ };
+ static State state;
+ return state;
+}
+
+/**
+ * Creates a DiagnosticListener subclass and adds it to the triggers for certain actions.
+ *
+ * DiagnosticListeners can only be added and not removed. If you wish to deactivate a
+ * DiagnosticListeners subclass, please provide the switch on that subclass to noop its
+ * functions. It is only safe to add a DiagnosticListener during a MONGO_INITIALIZER.
+ */
+TEMPLATE(typename ListenerT)
+REQUIRES(std::is_base_of_v<DiagnosticListener, ListenerT>)
+void installDiagnosticListener() {
+ auto& state = getDiagnosticListenerState();
+ state.listeners.push_back(new ListenerT());
+ invariant(!state.isFinalized.load());
+}
+
+/**
* This class holds working data for a latchable resource
*
* All member data is either i) synchronized or ii) constant.
@@ -161,7 +233,7 @@ private:
};
/**
- * latch_details::Catalog holds a collection of Data objects for use with Mutexes
+ * latch_details::Catalog holds a set of Data objects for use with Mutexes
*
* All rules for LockFreeCollection apply:
* - Synchronization is provided internally
@@ -176,7 +248,7 @@ public:
};
/**
- * Simple registration object that construct with an Identity and provides access to a Data
+ * Simple registration object that takes an Identity and provides access to a Data
*
* This object actually owns the Data object to make lifetime management simpler.
*/
@@ -218,6 +290,14 @@ inline auto defaultData() {
}
} // namespace latch_detail
+/**
+ * Latch is an abstract base class that implements the Lockable concept
+ *
+ * This class is useful for designing function APIs that take stdx::unique_lock around an
+ * ambiguously defined resource. A stdx::unique_lock<Latch> can be constructed from a Mutex.
+ * A stdx::unique_lock<Latch> cannot be constructed from a stdx::unique_lock<Mutex>. Sometimes,
+ * standard types are not as powerful as we would like them to be.
+ */
class Latch {
public:
virtual ~Latch() = default;
@@ -236,11 +316,17 @@ public:
*
* This class is intended to be used wherever a stdx::mutex would previously be used. It provides
* a generic event-listener interface for instrumenting around lock()/unlock()/try_lock().
+ * Conceptually, this type is similar to most unique_lock and timed_mutex implementations.
+ *
+ * If you believe that you need syncronization with absolutely no additional nanosecond latency or
+ * need to exist at a very core level (e.g. code living in the base folder), please excuse yourself
+ * from the linter and use a stdx::mutex.
+ *
+ * If you believe that you need logical synchronization at a user-facing level, you may need
+ * a database Lock instead. Talk to Storage Execution.
*/
-class Mutex : public Latch {
+class Mutex final : public Latch {
public:
- class LockListener;
-
void lock() override;
void unlock() override;
bool try_lock() override;
@@ -251,32 +337,7 @@ public:
~Mutex();
- /**
- * This function adds a LockListener subclass to the triggers for certain actions.
- *
- * LockListeners can only be added and not removed. If you wish to deactivate a LockListeners
- * subclass, please provide the switch on that subclass to noop its functions. It is only safe
- * to add a LockListener during a MONGO_INITIALIZER.
- */
- static void addLockListener(LockListener* listener);
-
- /**
- * This function finalizes the list of LockListener subclasses and prevents more from being
- * added.
- */
- static void finalizeLockListeners();
-
private:
- static auto& _getListenerState() noexcept {
- struct State {
- AtomicWord<bool> isFinalized{false};
- std::vector<LockListener*> listeners;
- };
-
- static State state;
- return state;
- }
-
void _onContendedLock() noexcept;
void _onQuickLock() noexcept;
void _onSlowLock() noexcept;
@@ -288,38 +349,6 @@ private:
bool _isLocked = false;
};
-/**
- * A set of actions to happen upon notable events on a Lockable-conceptualized type
- */
-class Mutex::LockListener {
- friend class Mutex;
-
-public:
- using Identity = latch_detail::Identity;
-
- virtual ~LockListener() = default;
-
- /**
- * Action to do when a lock cannot be immediately acquired
- */
- virtual void onContendedLock(const Identity& id) = 0;
-
- /**
- * Action to do when a lock was acquired without blocking
- */
- virtual void onQuickLock(const Identity& id) = 0;
-
- /**
- * Action to do when a lock was acquired after blocking
- */
- virtual void onSlowLock(const Identity& id) = 0;
-
- /**
- * Action to do when a lock is unlocked
- */
- virtual void onUnlock(const Identity& id) = 0;
-};
-
} // namespace mongo
/**
diff --git a/src/mongo/util/diagnostic_info.cpp b/src/mongo/util/diagnostic_info.cpp
index 0f66a6f96f4..f0b7bb8b1ce 100644
--- a/src/mongo/util/diagnostic_info.cpp
+++ b/src/mongo/util/diagnostic_info.cpp
@@ -174,9 +174,9 @@ struct DiagnosticInfoHandle {
};
const auto getDiagnosticInfoHandle = Client::declareDecoration<DiagnosticInfoHandle>();
-MONGO_INITIALIZER_GENERAL(DiagnosticInfo, (/* NO PREREQS */), ("FinalizeLockListeners"))
+MONGO_INITIALIZER_GENERAL(DiagnosticInfo, (/* NO PREREQS */), ("FinalizeDiagnosticListeners"))
(InitializerContext* context) {
- class LockListener : public Mutex::LockListener {
+ class DiagnosticListener : public latch_detail::DiagnosticListener {
void onContendedLock(const Identity& id) override {
if (auto client = Client::getCurrent()) {
auto& handle = getDiagnosticInfoHandle(client);
@@ -209,9 +209,7 @@ MONGO_INITIALIZER_GENERAL(DiagnosticInfo, (/* NO PREREQS */), ("FinalizeLockList
}
};
- // Intentionally leaked, people use Latches in detached threads
- static auto& listener = *new LockListener;
- Mutex::addLockListener(&listener);
+ latch_detail::installDiagnosticListener<DiagnosticListener>();
return Status::OK();
}
diff --git a/src/mongo/util/latch_analyzer.cpp b/src/mongo/util/latch_analyzer.cpp
index 2062daa3d9a..b60254886d2 100644
--- a/src/mongo/util/latch_analyzer.cpp
+++ b/src/mongo/util/latch_analyzer.cpp
@@ -59,9 +59,9 @@ auto kLatchViolationKey = "hierarchicalAcquisitionLevelViolations"_sd;
const auto getLatchAnalyzer = ServiceContext::declareDecoration<LatchAnalyzer>();
/**
- * LockListener sub-class to implement updating set in LatchSetState
+ * DiagnosticListener sub-class to implement updating set in LatchSetState
*/
-class LockListener : public Mutex::LockListener {
+class DiagnosticListener : public latch_detail::DiagnosticListener {
public:
void onContendedLock(const Identity& id) override {
if (auto client = Client::getCurrent()) {
@@ -88,13 +88,10 @@ public:
}
};
-// Register our LockListener with the Mutex class
-MONGO_INITIALIZER_GENERAL(LatchAnalysis, (/* NO PREREQS */), ("FinalizeLockListeners"))
+// Register our DiagnosticListener
+MONGO_INITIALIZER_GENERAL(LatchAnalysis, (/* NO PREREQS */), ("FinalizeDiagnosticListeners"))
(InitializerContext* context) {
-
- // Intentionally leaked, people use Latches in detached threads
- static auto& listener = *new LockListener;
- Mutex::addLockListener(&listener);
+ latch_detail::installDiagnosticListener<DiagnosticListener>();
return Status::OK();
}
diff --git a/src/mongo/util/latch_analyzer.h b/src/mongo/util/latch_analyzer.h
index 41f41dca15f..328e5549695 100644
--- a/src/mongo/util/latch_analyzer.h
+++ b/src/mongo/util/latch_analyzer.h
@@ -44,11 +44,11 @@ namespace mongo {
* LatchAnalyzer is a ServiceContext decoration that aggregates latch events
*
* This class is intended to provide a platform for hierarchical analysis on latches. To that end,
- * onContention(), onAcquire(), and onRelease() are currently called by a Mutex::LockListener
- * subclass defined in source. This class does much more work for each event when the
- * enableLatchAnalysis failpoint is set to "alwaysOn". This failpoint provides a wealth of data for
- * future analysis, but involves additional mutexes and mapping structures that may prove too costly
- * for production usage at the least.
+ * onContention(), onAcquire(), and onRelease() are currently called by a
+ * latch_detail::DiagnosticListener subclass defined in source. This class does much more work for
+ * each event when the enableLatchAnalysis failpoint is set to "alwaysOn". This failpoint provides a
+ * wealth of data for future analysis, but involves additional mutexes and mapping structures that
+ * may prove too costly for production usage at the least.
*/
class LatchAnalyzer {
public: