summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buildscripts/gdb/mongo.py2
-rw-r--r--src/mongo/db/SConscript1
-rw-r--r--src/mongo/db/client_strand.cpp19
-rw-r--r--src/mongo/db/client_strand.h8
-rw-r--r--src/mongo/db/client_strand_test.cpp32
-rw-r--r--src/mongo/logv2/log_source.h8
-rw-r--r--src/mongo/util/concurrency/SConscript3
-rw-r--r--src/mongo/util/concurrency/thread_name.cpp132
-rw-r--r--src/mongo/util/concurrency/thread_name.h85
-rw-r--r--src/mongo/util/thread_context.cpp5
-rw-r--r--src/mongo/util/thread_context.h5
11 files changed, 240 insertions, 60 deletions
diff --git a/buildscripts/gdb/mongo.py b/buildscripts/gdb/mongo.py
index 2a48fa9091c..08a26df539b 100644
--- a/buildscripts/gdb/mongo.py
+++ b/buildscripts/gdb/mongo.py
@@ -62,7 +62,7 @@ def get_current_thread_name():
fallback_name = '"%s"' % (gdb.selected_thread().name or '')
try:
# This goes through the pretty printer for StringData which adds "" around the name.
- name = str(gdb.parse_and_eval("mongo::for_debuggers::threadName"))
+ name = str(gdb.parse_and_eval("mongo::ThreadName::getStaticString()"))
if name == '""':
return fallback_name
return name
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index efa0d798997..0b02fee892e 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -515,6 +515,7 @@ env.Clone().InjectModule("enterprise").Library(
"audit.cpp",
],
LIBDEPS=[
+ "$BUILD_DIR/mongo/base",
],
)
diff --git a/src/mongo/db/client_strand.cpp b/src/mongo/db/client_strand.cpp
index 1455485ebf9..97b3d92bfaf 100644
--- a/src/mongo/db/client_strand.cpp
+++ b/src/mongo/db/client_strand.cpp
@@ -35,6 +35,7 @@
#include "mongo/logv2/log.h"
#include "mongo/util/concurrency/thread_name.h"
+#include "mongo/util/thread_context.h"
namespace mongo {
namespace {
@@ -66,12 +67,9 @@ void ClientStrand::_setCurrent() noexcept {
Client::setCurrent(std::move(_client));
// Set up the thread name.
- auto oldThreadName = getThreadName();
- StringData threadName = _clientPtr->desc();
- if (oldThreadName != threadName) {
- _oldThreadName = oldThreadName.toString();
- setThreadName(threadName);
- LOGV2_DEBUG(5127802, kDiagnosticLogLevel, "Set thread name", "name"_attr = threadName);
+ _oldThreadName = ThreadName::set(ThreadContext::get(), _threadName);
+ if (_oldThreadName) {
+ LOGV2_DEBUG(5127802, kDiagnosticLogLevel, "Set thread name", "name"_attr = *_threadName);
}
}
@@ -83,9 +81,12 @@ void ClientStrand::_releaseCurrent() noexcept {
_client = Client::releaseCurrent();
invariant(_client.get() == _clientPtr, kUnableToRecoverClient);
- if (!_oldThreadName.empty()) {
- // Reset the old thread name.
- setThreadName(_oldThreadName);
+ if (_oldThreadName) {
+ // Reset the last thread name because it was previously set in the OS.
+ ThreadName::set(ThreadContext::get(), std::move(_oldThreadName));
+ } else {
+ // Release the thread name for reuse.
+ ThreadName::release(ThreadContext::get());
}
LOGV2_DEBUG(
diff --git a/src/mongo/db/client_strand.h b/src/mongo/db/client_strand.h
index 20b9d940d27..a8ee8024fd0 100644
--- a/src/mongo/db/client_strand.h
+++ b/src/mongo/db/client_strand.h
@@ -33,6 +33,7 @@
#include "mongo/db/service_context.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/stdx/mutex.h"
+#include "mongo/util/concurrency/thread_name.h"
#include "mongo/util/intrusive_counter.h"
#include "mongo/util/out_of_line_executor.h"
@@ -129,7 +130,9 @@ public:
static boost::intrusive_ptr<ClientStrand> get(Client* client);
ClientStrand(ServiceContext::UniqueClient client)
- : _clientPtr(client.get()), _client(std::move(client)) {}
+ : _clientPtr(client.get()),
+ _client(std::move(client)),
+ _threadName(make_intrusive<ThreadName>(_client->desc())) {}
/**
* Get a pointer to the underlying Client.
@@ -200,7 +203,8 @@ private:
ServiceContext::UniqueClient _client;
- std::string _oldThreadName;
+ boost::intrusive_ptr<ThreadName> _threadName;
+ boost::intrusive_ptr<ThreadName> _oldThreadName;
};
inline void ClientStrand::Executor::schedule(Task task) {
diff --git a/src/mongo/db/client_strand_test.cpp b/src/mongo/db/client_strand_test.cpp
index 68c055f53c0..cce87321862 100644
--- a/src/mongo/db/client_strand_test.cpp
+++ b/src/mongo/db/client_strand_test.cpp
@@ -31,6 +31,7 @@
#include <memory>
+#include "mongo/db/client.h"
#include "mongo/db/client_strand.h"
#include "mongo/db/service_context_test_fixture.h"
#include "mongo/unittest/barrier.h"
@@ -39,6 +40,7 @@
#include "mongo/util/assert_util.h"
#include "mongo/util/concurrency/thread_name.h"
#include "mongo/util/executor_test_util.h"
+#include "mongo/util/thread_context.h"
namespace mongo {
namespace {
@@ -48,6 +50,24 @@ public:
constexpr static auto kClientName1 = "foo";
constexpr static auto kClientName2 = "bar";
+ /**
+ * Clean up any leftover thread_local pieces.
+ */
+ void releaseClient() {
+ ThreadName::release(ThreadContext::get());
+ if (haveClient()) {
+ Client::releaseCurrent();
+ }
+ }
+
+ void setUp() override {
+ releaseClient();
+ }
+
+ void tearDown() override {
+ releaseClient();
+ }
+
void assertStrandNotBound(const ClientStrandPtr& strand) {
ASSERT_FALSE(haveClient());
ASSERT_FALSE(strand->isBound());
@@ -113,6 +133,8 @@ TEST_F(ClientStrandTest, BindMultipleTimes) {
// We have no bound Client again.
assertStrandNotBound(strand);
+ ASSERT_EQ(strand->getClientPointer()->desc(), getThreadName())
+ << "We should retain the previous strand's name";
}
}
@@ -129,6 +151,8 @@ TEST_F(ClientStrandTest, BindMultipleTimesAndDismiss) {
// Dismiss the current guard.
guard.dismiss();
assertStrandNotBound(strand);
+ ASSERT_EQ(strand->getClientPointer()->desc(), getThreadName())
+ << "We should retain the previous strand's name";
// Assign a new guard.
guard = strand->bind();
@@ -285,8 +309,12 @@ TEST_F(ClientStrandTest, SwapStrands) {
assertStrandNotBound(strand2);
for (size_t i = 0; i < 100; ++i) {
- // Alternate between binding strand1 and strand2.
- auto& strand = (i % 2 == 0) ? strand1 : strand2;
+ // Alternate between binding strand1 and strand2. Start on strand2 so it has a different
+ // thread name than the previous test.
+ auto& strand = (i % 2 == 0) ? strand2 : strand1;
+
+ ASSERT_NE(strand->getClientPointer()->desc(), getThreadName())
+ << "We should be binding over the previous strand's name";
auto guard = strand->bind();
assertStrandBound(strand);
diff --git a/src/mongo/logv2/log_source.h b/src/mongo/logv2/log_source.h
index 8e83cb6ad54..4051bcfb55a 100644
--- a/src/mongo/logv2/log_source.h
+++ b/src/mongo/logv2/log_source.h
@@ -74,10 +74,10 @@ public:
add_attribute_unlocked(attributes::timeStamp(), boost::log::attributes::make_function([]() {
return Date_t::now();
}));
- add_attribute_unlocked(attributes::threadName(),
- boost::log::attributes::make_function([isShutdown]() {
- return isShutdown ? "shutdown"_sd : getThreadName();
- }));
+ add_attribute_unlocked(
+ attributes::threadName(), boost::log::attributes::make_function([isShutdown]() {
+ return isShutdown ? "shutdown"_sd : ThreadName::getStaticString();
+ }));
}
explicit LogSource(const LogDomain::Internal* domain) : LogSource(domain, false) {}
diff --git a/src/mongo/util/concurrency/SConscript b/src/mongo/util/concurrency/SConscript
index 535635079a0..540f2ea03ef 100644
--- a/src/mongo/util/concurrency/SConscript
+++ b/src/mongo/util/concurrency/SConscript
@@ -38,6 +38,9 @@ env.Library(
source=[
"spin_lock.cpp",
],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ ]
)
env.CppUnitTest(
diff --git a/src/mongo/util/concurrency/thread_name.cpp b/src/mongo/util/concurrency/thread_name.cpp
index 571ed983507..1a950c24bfa 100644
--- a/src/mongo/util/concurrency/thread_name.cpp
+++ b/src/mongo/util/concurrency/thread_name.cpp
@@ -49,6 +49,8 @@
#include <sys/types.h>
#endif
+#include <fmt/format.h>
+
#include "mongo/base/init.h"
#include "mongo/config.h"
#include "mongo/logv2/log.h"
@@ -56,6 +58,7 @@
#include "mongo/util/str.h"
namespace mongo {
+using namespace fmt::literals;
namespace {
@@ -89,43 +92,49 @@ void setWindowsThreadName(DWORD dwThreadID, const char* threadName) {
}
#endif
-AtomicWord<long long> nextUnnamedThreadId{1};
-
-// It is unsafe to access threadName before its dynamic initialization has completed. Use
-// the execution of mongo initializers (which only happens once we have entered main, and
-// therefore after dynamic initialization is complete) to signal that it is safe to use
-// 'threadName'.
-bool mongoInitializersHaveRun{};
-MONGO_INITIALIZER(ThreadNameInitializer)(InitializerContext*) {
- mongoInitializersHaveRun = true;
- // The global initializers should only ever be run from main, so setting thread name
- // here makes sense.
- setThreadName("main");
- return Status::OK();
+constexpr auto kMainId = size_t{0};
+
+auto makeAnonymousThreadName() {
+ static auto gNextAnonymousId = AtomicWord<size_t>{kMainId};
+ auto id = gNextAnonymousId.fetchAndAdd(1);
+ if (id == kMainId) {
+ // The first thread name should always be "main".
+ return make_intrusive<ThreadName>("main");
+ } else {
+ return make_intrusive<ThreadName>("thread{}"_format(id));
+ }
}
-thread_local std::string threadNameStorage;
-} // namespace
+struct ThreadNameSconce {
+ ThreadNameSconce() : cachedPtr(makeAnonymousThreadName()) {
+ // Note that we're not setting the thread name here. It will log differently, but appear the
+ // same in top and like.
+ }
+
+ // At any given time, either cachedPtr or activePtr can be valid, but not both.
+ boost::intrusive_ptr<ThreadName> activePtr;
+ boost::intrusive_ptr<ThreadName> cachedPtr;
+};
-namespace for_debuggers {
-// This needs external linkage to ensure that debuggers can use it.
-thread_local StringData threadName;
-} // namespace for_debuggers
-using for_debuggers::threadName;
+auto getSconce = ThreadContext::declareDecoration<ThreadNameSconce>();
+auto& getThreadName(const boost::intrusive_ptr<ThreadContext>& context) {
+ auto& sconce = getSconce(context.get());
+ if (sconce.activePtr) {
+ return sconce.activePtr;
+ }
-void setThreadName(StringData name) {
- invariant(mongoInitializersHaveRun);
- threadNameStorage = name.toString();
- threadName = threadNameStorage;
+ return sconce.cachedPtr;
+}
+void setOSThreadName(StringData threadName) {
#if defined(_WIN32)
// Naming should not be expensive compared to thread creation and connection set up, but if
// testing shows otherwise we should make this depend on DEBUG again.
- setWindowsThreadName(GetCurrentThreadId(), threadNameStorage.c_str());
+ setWindowsThreadName(GetCurrentThreadId(), threadName.rawData());
#elif defined(__APPLE__)
// Maximum thread name length on OS X is MAXTHREADNAMESIZE (64 characters). This assumes
// OS X 10.6 or later.
- std::string threadNameCopy = threadNameStorage;
+ std::string threadNameCopy = threadName.toString();
if (threadNameCopy.size() > MAXTHREADNAMESIZE) {
threadNameCopy.resize(MAXTHREADNAMESIZE - 4);
threadNameCopy += "...";
@@ -165,20 +174,71 @@ void setThreadName(StringData name) {
#endif
}
-StringData getThreadName() {
- if (MONGO_unlikely(!mongoInitializersHaveRun)) {
- // 'getThreadName' has been called before dynamic initialization for this
- // translation unit has completed, so return a fallback value rather than accessing
- // the 'threadName' variable, which requires dynamic initialization. We assume that
- // we are in the 'main' thread.
- static const std::string kFallback = "main";
+} // namespace
+
+ThreadName::Id ThreadName::_nextId() {
+ static auto gNextId = AtomicWord<Id>{0};
+ return gNextId.fetchAndAdd(1);
+}
+
+StringData ThreadName::getStaticString() {
+ auto& context = ThreadContext::get();
+ if (!context) {
+ // Use a static fallback to avoid allocations. This is the string that will be used before
+ // initializers run in main a.k.a. pre-init.
+ static constexpr auto kFallback = "-"_sd;
return kFallback;
}
- if (threadName.empty()) {
- setThreadName(str::stream() << "thread" << nextUnnamedThreadId.fetchAndAdd(1));
+ return getThreadName(context)->toString();
+}
+
+boost::intrusive_ptr<ThreadName> ThreadName::get(boost::intrusive_ptr<ThreadContext> context) {
+ return getThreadName(context);
+}
+
+boost::intrusive_ptr<ThreadName> ThreadName::set(boost::intrusive_ptr<ThreadContext> context,
+ boost::intrusive_ptr<ThreadName> name) {
+ invariant(name);
+
+ auto& sconce = getSconce(context.get());
+
+ if (sconce.activePtr) {
+ invariant(!sconce.cachedPtr);
+ if (*sconce.activePtr == *name) {
+ // The name was already set, skip setting it to the OS thread name.
+ return {};
+ } else {
+ // Replace the current active name with the new one, and set the OS thread name.
+ setOSThreadName(name->toString());
+ return std::exchange(sconce.activePtr, name);
+ }
+ } else if (sconce.cachedPtr) {
+ if (*sconce.cachedPtr == *name) {
+ // The name was cached, set it as active and skip setting it to the OS thread name.
+ sconce.activePtr = std::exchange(sconce.cachedPtr, {});
+ return {};
+ } else {
+ // The new name is different than the cached name, set the active, reset the cached, and
+ // set the OS thread name.
+ setOSThreadName(name->toString());
+
+ sconce.activePtr = name;
+ sconce.cachedPtr.reset();
+ return {};
+ }
+ }
+
+ MONGO_UNREACHABLE;
+}
+
+void ThreadName::release(boost::intrusive_ptr<ThreadContext> context) {
+ auto& sconce = getSconce(context.get());
+ if (sconce.activePtr) {
+ sconce.cachedPtr = std::exchange(sconce.activePtr, {});
}
- return threadName;
}
+ThreadName::ThreadName(StringData name) : _id(_nextId()), _storage(name.toString()){};
+
} // namespace mongo
diff --git a/src/mongo/util/concurrency/thread_name.h b/src/mongo/util/concurrency/thread_name.h
index f608cfa624c..2efd004263a 100644
--- a/src/mongo/util/concurrency/thread_name.h
+++ b/src/mongo/util/concurrency/thread_name.h
@@ -32,19 +32,100 @@
#include <string>
#include "mongo/base/string_data.h"
+#include "mongo/util/intrusive_counter.h"
+#include "mongo/util/thread_context.h"
namespace mongo {
/**
+ * ThreadName is a uniquely identifyable, immutable, ref-counted string.
+ *
+ * This class is used for three purposes:
+ * - Setting the official thread name with the OS.
+ * - Populating the "ctx" field for log lines.
+ * - Providing a thread name to gdb.
+ */
+class ThreadName : public RefCountable {
+public:
+ using Id = size_t;
+
+ /**
+ * Create a new instance.
+ *
+ * Note that this does not set it to be the official one for the thread.
+ */
+ explicit ThreadName(StringData name);
+ ThreadName(const ThreadName&) = delete;
+ ThreadName(ThreadName&&) = delete;
+
+ /**
+ * Get the official ThreadName for the current thread via the ThreadContext.
+ */
+ static boost::intrusive_ptr<ThreadName> get(boost::intrusive_ptr<ThreadContext> context);
+
+ /**
+ * Set the official ThreadName for the current thread via the ThreadContext.
+ *
+ * Note that this also will set the OS thread name if the name is different from the current
+ * one.
+ *
+ * If a different non-anonymous thread name was previously set, this returns that name. If the
+ * given name was already set, a previous name was released, or the initial name was set, this
+ * returns an empty pointer.
+ */
+ static boost::intrusive_ptr<ThreadName> set(boost::intrusive_ptr<ThreadContext> context,
+ boost::intrusive_ptr<ThreadName> name);
+
+ /**
+ * Release the current thread name.
+ *
+ * This does not unset the OS thread name or change the current storage. Instead, this marks the
+ * current name as available for reuse or replacement.
+ */
+ static void release(boost::intrusive_ptr<ThreadContext> context);
+
+ /**
+ * Get a string for the current thread without new allocations.
+ *
+ * In pre-init, this returns "-". That value will mostly be associated with the main thread.
+ * If a thread is somehow started in pre-init and dodges our ThreadSafetyContext checks, it will
+ * also return "-" for this function.
+ */
+ static StringData getStaticString();
+
+ StringData toString() const {
+ return _storage;
+ }
+
+ friend bool operator==(const ThreadName& lhs, const ThreadName& rhs) noexcept {
+ return lhs._id == rhs._id;
+ }
+
+ friend bool operator!=(const ThreadName& lhs, const ThreadName& rhs) noexcept {
+ return lhs._id != rhs._id;
+ }
+
+private:
+ static Id _nextId();
+
+ const Id _id;
+ const std::string _storage;
+};
+
+/**
* Sets the name of the current thread.
*/
-void setThreadName(StringData name);
+inline void setThreadName(StringData name) {
+ ThreadName::set(ThreadContext::get(), make_intrusive<ThreadName>(name));
+}
/**
* Retrieves the name of the current thread, as previously set, or "thread#" if no name was
* previously set. The returned StringData is always null terminated so it is safe to pass to APIs
* that expect c-strings.
*/
-StringData getThreadName();
+inline StringData getThreadName() {
+ return ThreadName::get(ThreadContext::get())->toString();
+}
} // namespace mongo
diff --git a/src/mongo/util/thread_context.cpp b/src/mongo/util/thread_context.cpp
index 5d0e23647eb..3bcde4a9fdf 100644
--- a/src/mongo/util/thread_context.cpp
+++ b/src/mongo/util/thread_context.cpp
@@ -27,12 +27,11 @@
* it in the license file.
*/
-#include "mongo/platform/basic.h"
-
#include "mongo/util/thread_context.h"
#include "mongo/base/init.h"
#include "mongo/stdx/thread.h"
+#include "mongo/util/assert_util.h"
namespace mongo {
namespace {
@@ -40,6 +39,8 @@ const auto kMainThreadId = stdx::this_thread::get_id();
AtomicWord<bool> gHasInitializedMain{false};
} // namespace
+thread_local ThreadContext::Handle ThreadContext::_handle;
+
MONGO_INITIALIZER(ThreadContextsInitialized)(InitializerContext*) {
ThreadContext::initializeMain();
return Status::OK();
diff --git a/src/mongo/util/thread_context.h b/src/mongo/util/thread_context.h
index 7c4b9bbfbc8..e839b947a97 100644
--- a/src/mongo/util/thread_context.h
+++ b/src/mongo/util/thread_context.h
@@ -29,9 +29,10 @@
#pragma once
+#include "mongo/platform/basic.h"
+
#include "mongo/platform/atomic_word.h"
#include "mongo/platform/process_id.h"
-#include "mongo/util/assert_util.h"
#include "mongo/util/decorable.h"
#include "mongo/util/intrusive_counter.h"
@@ -115,7 +116,7 @@ private:
boost::intrusive_ptr<ThreadContext> instance;
};
- inline static thread_local auto _handle = Handle{};
+ static thread_local Handle _handle;
const ProcessId _threadId = ProcessId::getCurrentThreadId();
AtomicWord<bool> _isAlive{true};