diff options
-rw-r--r-- | buildscripts/gdb/mongo.py | 2 | ||||
-rw-r--r-- | src/mongo/db/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/client_strand.cpp | 19 | ||||
-rw-r--r-- | src/mongo/db/client_strand.h | 8 | ||||
-rw-r--r-- | src/mongo/db/client_strand_test.cpp | 32 | ||||
-rw-r--r-- | src/mongo/logv2/log_source.h | 8 | ||||
-rw-r--r-- | src/mongo/util/concurrency/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/util/concurrency/thread_name.cpp | 132 | ||||
-rw-r--r-- | src/mongo/util/concurrency/thread_name.h | 85 | ||||
-rw-r--r-- | src/mongo/util/thread_context.cpp | 5 | ||||
-rw-r--r-- | src/mongo/util/thread_context.h | 5 |
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}; |