summaryrefslogtreecommitdiff
path: root/src/mongo/util/concurrency
diff options
context:
space:
mode:
authorLucas de Castro Borges <lucas@gnuabordo.com.br>2025-02-11 15:07:35 -0300
committerLucas de Castro Borges <lucas@gnuabordo.com.br>2025-02-11 15:07:35 -0300
commit4cb8841196d0625dfa3825aa326f071cd27c7b8b (patch)
tree1682a647d4463397c119183369ae6f750d5fdcff /src/mongo/util/concurrency
parentaa03c6362cbaa767638e6eed9b031d86dd2643d1 (diff)
parent8f0827553e09872941945a093b647a4211a9db7f (diff)
Update upstream source from tag 'upstream/6.0.0'master
Update to upstream version '6.0.0' with Debian dir 5604a80ec1c96ca76f25f40d78e6ef855abec322
Diffstat (limited to 'src/mongo/util/concurrency')
-rw-r--r--src/mongo/util/concurrency/SConscript1
-rw-r--r--src/mongo/util/concurrency/thread_name.cpp241
-rw-r--r--src/mongo/util/concurrency/thread_name.h162
-rw-r--r--src/mongo/util/concurrency/thread_pool.h7
-rw-r--r--src/mongo/util/concurrency/ticketholder.cpp3
-rw-r--r--src/mongo/util/concurrency/ticketholder_bm.cpp93
-rw-r--r--src/mongo/util/concurrency/ticketholder_test.cpp82
7 files changed, 345 insertions, 244 deletions
diff --git a/src/mongo/util/concurrency/SConscript b/src/mongo/util/concurrency/SConscript
index 3d4460e782a..7ac61ec0025 100644
--- a/src/mongo/util/concurrency/SConscript
+++ b/src/mongo/util/concurrency/SConscript
@@ -29,7 +29,6 @@ env.Library('ticketholder',
['ticketholder.cpp'],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
- '$BUILD_DIR/mongo/db/commands/test_commands_enabled',
'$BUILD_DIR/mongo/db/service_context',
'$BUILD_DIR/third_party/shim_boost',
])
diff --git a/src/mongo/util/concurrency/thread_name.cpp b/src/mongo/util/concurrency/thread_name.cpp
index 5109b079c6d..1a950c24bfa 100644
--- a/src/mongo/util/concurrency/thread_name.cpp
+++ b/src/mongo/util/concurrency/thread_name.cpp
@@ -44,6 +44,10 @@
#include <mach/thread_info.h>
#endif
#endif
+#if defined(__linux__)
+#include <sys/syscall.h>
+#include <sys/types.h>
+#endif
#include <fmt/format.h>
@@ -51,18 +55,13 @@
#include "mongo/config.h"
#include "mongo/logv2/log.h"
#include "mongo/platform/atomic_word.h"
-#include "mongo/platform/process_id.h"
-#include "mongo/util/thread_context.h"
+#include "mongo/util/str.h"
namespace mongo {
using namespace fmt::literals;
namespace {
-bool isMainThread() {
- return ProcessId::getCurrent() == ProcessId::getCurrentThreadId();
-}
-
#ifdef _WIN32
// From https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx
// Note: The thread name is only set for the thread if the debugger is attached.
@@ -93,15 +92,49 @@ void setWindowsThreadName(DWORD dwThreadID, const char* threadName) {
}
#endif
-void setOSThreadName(const std::string& threadName) {
+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));
+ }
+}
+
+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;
+};
+
+auto getSconce = ThreadContext::declareDecoration<ThreadNameSconce>();
+auto& getThreadName(const boost::intrusive_ptr<ThreadContext>& context) {
+ auto& sconce = getSconce(context.get());
+ if (sconce.activePtr) {
+ return sconce.activePtr;
+ }
+
+ 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(), threadName.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 = threadName;
+ std::string threadNameCopy = threadName.toString();
if (threadNameCopy.size() > MAXTHREADNAMESIZE) {
threadNameCopy.resize(MAXTHREADNAMESIZE - 4);
threadNameCopy += "...";
@@ -117,137 +150,95 @@ void setOSThreadName(const std::string& threadName) {
// Do not set thread name on the main() thread. Setting the name on main thread breaks
// pgrep/pkill since these programs base this name on /proc/*/status which displays the thread
// name, not the executable name.
- if (isMainThread())
- return;
- // Maximum thread name length supported on Linux is 16 including the null terminator.
- // Ideally we use short and descriptive thread names that fit: this helps for log
- // readability as well. Still, as the limit is so low and a few current names exceed the
- // limit, it's best to shorten long names.
- static constexpr size_t kMaxThreadNameLength = 16 - 1;
- boost::optional<std::string> shortNameBuf;
- const char* truncName = threadName.c_str();
- if (threadName.size() > kMaxThreadNameLength) {
- StringData sd = threadName;
- shortNameBuf = "{}.{}"_format(sd.substr(0, 7), sd.substr(sd.size() - 7));
- truncName = shortNameBuf->c_str();
- }
-
- int error = pthread_setname_np(pthread_self(), truncName);
- if (error) {
- LOGV2(23103,
- "Ignoring error from setting thread name: {error}",
- "Ignoring error from setting thread name",
- "error"_attr = errnoWithDescription(error));
+ if (getpid() != syscall(SYS_gettid)) {
+ // Maximum thread name length supported on Linux is 16 including the null terminator.
+ // Ideally we use short and descriptive thread names that fit: this helps for log
+ // readability as well. Still, as the limit is so low and a few current names exceed the
+ // limit, it's best to shorten long names.
+ int error = 0;
+ if (threadName.size() > 15) {
+ std::string shortName = str::stream()
+ << threadName.substr(0, 7) << '.' << threadName.substr(threadName.size() - 7);
+ error = pthread_setname_np(pthread_self(), shortName.c_str());
+ } else {
+ error = pthread_setname_np(pthread_self(), threadName.rawData());
+ }
+
+ if (error) {
+ LOGV2(23103,
+ "Ignoring error from setting thread name: {error}",
+ "Ignoring error from setting thread name",
+ "error"_attr = errnoWithDescription(error));
+ }
}
#endif
}
-/**
- * Manages the relationship of our high-level ThreadNameRef strings to
- * the thread local context, and efficiently notifying the OS of name
- * changes. We try to apply temporary names to threads to make them
- * meaningful representations of the kind of work the thread is doing.
- * But sharing these names with the OS is slow and name length is limited. So
- * ThreadNameInfo is an auxiliary resource to the OS thread name, available to
- * the LOGV2 system and to GDB.
- *
- * ThreadNameInfo is a decoration of ThreadContext.
- * ThreadContext are held by thread_local storage and so threads started
- * after server initialization will have an associated ThreadNameInfo.
- *
- * A name is "active" when it has been pushed to the OS by `setHandle`. The
- * association can be abandoned by calling `release`. This doesn't affect the
- * OS, but indicates that the name binding is abandoned and shouldn't be
- * preserved by returning it from subsequent `setHandle` calls. We do however
- * retain the inactive reference in hopes of perhaps identifying redundant
- * `setHandle` calls that would set the OS thread name to the same value it
- * already has.
- *
- * Upon construction, a ThreadNameInfo has an inactive unique name that
- * the OS doesn't know about yet. A push/pop style call sequence of
- * `h=getHandle()` then (eventually) `setHandle(h)` can make this name
- * the active (known to the OS) thread name.
- */
-class ThreadNameInfo {
-public:
- /** Returns the thread name ref, whether it's active or not. */
- const ThreadNameRef& getHandle() const {
- return _h;
- }
+} // namespace
- /**
- * Changes the thread name ref to `name`, marking it active,
- * and updating the OS thread name if necessary.
- *
- * If there was a previous active thread name, it is returned so that
- * callers can perhaps restore it and implement a temporary rename.
- * Inactive thread names are considered abandoned and are not returned.
- */
- ThreadNameRef setHandle(ThreadNameRef name) {
- bool alreadyActive = std::exchange(_active, true);
- if (name == _h)
- return {};
- auto old = std::exchange(_h, std::move(name));
- setOSThreadName(*_h);
- if (alreadyActive)
- return old;
- return {};
- }
+ThreadName::Id ThreadName::_nextId() {
+ static auto gNextId = AtomicWord<Id>{0};
+ return gNextId.fetchAndAdd(1);
+}
- /**
- * Mark the current ThreadNameRef as inactive. This is only a marking and
- * does not affect the OS thread name. The ThreadNameRef is retained
- * so that redundant setHandle calls can be recognized and elided.
- */
- void release() {
- _active = false;
+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;
}
- /**
- * Get a pointer to this thread's ThreadNameInfo.
- * Returns null if there's no ThreadContext to get it from.
- */
- static ThreadNameInfo* forThisThread() {
- auto& context = ThreadContext::get();
- return context ? &_decoration(*context) : nullptr;
- }
+ return getThreadName(context)->toString();
+}
-private:
- inline static auto _decoration = ThreadContext::declareDecoration<ThreadNameInfo>();
-
- /**
- * Main thread always gets "main". Other threads are sequentially
- * named as "thread1", "thread2", etc.
- */
- static std::string _makeAnonymousThreadName() {
- if (isMainThread())
- return "main";
- static AtomicWord<uint64_t> next{1};
- return "thread{}"_format(next.fetchAndAdd(1));
- }
+boost::intrusive_ptr<ThreadName> ThreadName::get(boost::intrusive_ptr<ThreadContext> context) {
+ return getThreadName(context);
+}
- ThreadNameRef _h{_makeAnonymousThreadName()};
- bool _active = false;
-};
+boost::intrusive_ptr<ThreadName> ThreadName::set(boost::intrusive_ptr<ThreadContext> context,
+ boost::intrusive_ptr<ThreadName> name) {
+ invariant(name);
-} // namespace
+ auto& sconce = getSconce(context.get());
-ThreadNameRef getThreadNameRef() {
- if (auto info = ThreadNameInfo::forThisThread())
- return info->getHandle();
- return {};
-}
+ 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());
-ThreadNameRef setThreadNameRef(ThreadNameRef name) {
- invariant(name);
- if (auto info = ThreadNameInfo::forThisThread())
- return info->setHandle(std::move(name));
- return {};
+ sconce.activePtr = name;
+ sconce.cachedPtr.reset();
+ return {};
+ }
+ }
+
+ MONGO_UNREACHABLE;
}
-void releaseThreadNameRef() {
- if (auto info = ThreadNameInfo::forThisThread())
- info->release();
+void ThreadName::release(boost::intrusive_ptr<ThreadContext> context) {
+ auto& sconce = getSconce(context.get());
+ if (sconce.activePtr) {
+ sconce.cachedPtr = std::exchange(sconce.activePtr, {});
+ }
}
+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 f4bc4582a3c..2efd004263a 100644
--- a/src/mongo/util/concurrency/thread_name.h
+++ b/src/mongo/util/concurrency/thread_name.h
@@ -29,141 +29,103 @@
#pragma once
-#include <memory>
#include <string>
#include "mongo/base/string_data.h"
-#include "mongo/util/static_immortal.h"
+#include "mongo/util/intrusive_counter.h"
+#include "mongo/util/thread_context.h"
namespace mongo {
/**
- * A nullable handle pinning a ref-counted immutable string.
- * Copies of a ThreadNameString refer to the same string object.
- * Equality comparisons consider only that string's identity, not its value.
- *
- * This class is just a kind of refcounted string handle and does not itself
- * interact with the OS or with thread storage.
- *
- * Presents a pointer-like API with `get()`, and dereference operators, and
- * explicit bool conversion. Dereferencing yields a reference to a
- * string value if nonempty. Dereferencing an empty reference is allowed and
- * yields the singleton string value "-".
- *
- * Copyable and movable, with the usual refcounting semantics. Copies refer
- * to the same string and will compare equal to each other.
+ * 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 ThreadNameRef {
+class ThreadName : public RefCountable {
public:
- /** An empty ref (empty refs still stringify as "-"). */
- ThreadNameRef() = default;
+ using Id = size_t;
- /** A ref to the string value `name`. */
- explicit ThreadNameRef(std::string name)
- : _ptr{std::make_shared<std::string>(std::move(name))} {}
+ /**
+ * 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;
/**
- * Dereferences this. If nonempty, returns its string value.
- * Otherwise, returns a singleton "-" string.
+ * Get the official ThreadName for the current thread via the ThreadContext.
*/
- const std::string* get() const {
- if (_ptr)
- return &*_ptr;
- static const StaticImmortal whenEmpty = std::string("-");
- return &*whenEmpty;
- }
+ static boost::intrusive_ptr<ThreadName> get(boost::intrusive_ptr<ThreadContext> context);
- const std::string* operator->() const {
- return get();
- }
+ /**
+ * 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);
- const std::string& operator*() const {
- return *get();
- }
+ /**
+ * 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);
- /** Returns true if nonempty. */
- explicit operator bool() const {
- return !!_ptr;
- }
+ /**
+ * 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();
- operator StringData() const {
- return **this;
+ StringData toString() const {
+ return _storage;
}
- /**
- * Two ThreadNameRef are equal if and only if they are copies of the same
- * original ThreadNameRef object. Equality of string value is insufficient.
- */
- friend bool operator==(const ThreadNameRef& a, const ThreadNameRef& b) noexcept {
- return a._ptr == b._ptr;
+ friend bool operator==(const ThreadName& lhs, const ThreadName& rhs) noexcept {
+ return lhs._id == rhs._id;
}
- friend bool operator!=(const ThreadNameRef& a, const ThreadNameRef& b) noexcept {
- return !(a == b);
+ friend bool operator!=(const ThreadName& lhs, const ThreadName& rhs) noexcept {
+ return lhs._id != rhs._id;
}
private:
- std::shared_ptr<const std::string> _ptr;
-};
-
-/**
- * Returns the name reference attached to current thread. Returns an empty
- * ThreadNameRef if current thread has no ThreadContext. The empty ThreadNameRef
- * still has a valid string value of "-".
- *
- * This string is not limited in length, so it will be a better name
- * than the name the OS uses to refer to the same thread.
- */
-ThreadNameRef getThreadNameRef();
+ static Id _nextId();
-/**
- * Swaps in a new active name, returns the old one if it was active.
- *
- * The active thread name is used for:
- * - Setting the thread name in the OS. As an optimization, clearing
- * the thread name in the OS is performed lazily.
- * - Populating the "ctx" field for log lines.
- * - Providing a thread name to GDB.
- *
- * Has no effect if there is no `ThreadContext` for this thread.
- */
-ThreadNameRef setThreadNameRef(ThreadNameRef name);
-
-/**
- * Marks the ThreadNameRef attached to the current thread as inactive.
- * - The inactive thread name remains attached to the thread.
- * - The thread name according to the OS is not changed.
- * - A subsequent `setThreadNameRef` call will not return it.
- * - An immediately subsequent `setThreadNameRef` call with the same name will
- * cheaply reactivate it, saving two OS thread rename operations.
- * This is an optimization on the assumption that a thread name will be
- * temporarily set to the same `ThreadNameRef` repeatedly, so setting it and
- * resetting it with the OS on each change would be wasteful.
- *
- * Has no effect if there is no `ThreadContext` for this thread.
- */
-void releaseThreadNameRef();
+ const Id _id;
+ const std::string _storage;
+};
/**
* Sets the name of the current thread.
*/
-inline void setThreadName(std::string name) {
- setThreadNameRef(ThreadNameRef{std::move(name)});
+inline void setThreadName(StringData name) {
+ ThreadName::set(ThreadContext::get(), make_intrusive<ThreadName>(name));
}
/**
- * Returns current thread's name, as previously set, or "main", or
- * "thread#" if no name was previously set.
- *
- * Before the ThreadContext API is initialized, this returns "-". That value
- * will mostly be associated with the main thread, or threads that were started
- * before ThreadContext API initialization.
- *
- * Used by the MongoDB GDB pretty printer extentions in `gdb/mongo.py`.
+ * 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.
*/
inline StringData getThreadName() {
- return *getThreadNameRef();
+ return ThreadName::get(ThreadContext::get())->toString();
}
} // namespace mongo
diff --git a/src/mongo/util/concurrency/thread_pool.h b/src/mongo/util/concurrency/thread_pool.h
index 1cbd6b8b263..29acd9e09c0 100644
--- a/src/mongo/util/concurrency/thread_pool.h
+++ b/src/mongo/util/concurrency/thread_pool.h
@@ -147,11 +147,6 @@ public:
// from ThreadPoolInterface
void startup() override;
void shutdown() override;
-
- /**
- * Joins all scheduled tasks. Can also spawn a free thread that ignores maxThread options to
- * execute pending tasks.
- */
void join() override;
/**
@@ -163,8 +158,6 @@ public:
*
* May be called multiple times, by multiple threads. May not be called by a task in the thread
* pool.
- *
- * Not safe to use when shutdown can be called concurrently.
*/
void waitForIdle();
diff --git a/src/mongo/util/concurrency/ticketholder.cpp b/src/mongo/util/concurrency/ticketholder.cpp
index f0ff7fdb4ce..30523f97f0f 100644
--- a/src/mongo/util/concurrency/ticketholder.cpp
+++ b/src/mongo/util/concurrency/ticketholder.cpp
@@ -31,7 +31,6 @@
#include "mongo/platform/basic.h"
-#include "mongo/db/commands/test_commands_enabled.h"
#include "mongo/db/service_context.h"
#include "mongo/util/concurrency/admission_context.h"
#include "mongo/util/concurrency/ticket.h"
@@ -151,7 +150,7 @@ void SemaphoreTicketHolder::release(AdmissionContext* admCtx, Ticket&& ticket) {
Status SemaphoreTicketHolder::resize(int newSize) {
stdx::lock_guard<Latch> lk(_resizeMutex);
- if (newSize < 5 && !getTestCommandsEnabled())
+ if (newSize < 5)
return Status(ErrorCodes::BadValue,
str::stream() << "Minimum value for semaphore is 5; given " << newSize);
diff --git a/src/mongo/util/concurrency/ticketholder_bm.cpp b/src/mongo/util/concurrency/ticketholder_bm.cpp
index 2e40e65a270..be4c3034d44 100644
--- a/src/mongo/util/concurrency/ticketholder_bm.cpp
+++ b/src/mongo/util/concurrency/ticketholder_bm.cpp
@@ -42,7 +42,7 @@ namespace mongo {
namespace {
static int kTickets = 128;
-static int kThreadMin = 16;
+static int kThreadMin = 8;
static int kThreadMax = 1024;
static TicketHolder::WaitMode waitMode = TicketHolder::WaitMode::kUninterruptible;
@@ -69,6 +69,86 @@ public:
};
template <class TicketHolderImpl>
+void BM_tryAcquire(benchmark::State& state) {
+ static std::unique_ptr<TicketHolderFixture<TicketHolderImpl>> p;
+ if (state.thread_index == 0) {
+ p = std::make_unique<TicketHolderFixture<TicketHolderImpl>>(state.threads);
+ }
+ double attempted = 0, acquired = 0;
+ for (auto _ : state) {
+ AdmissionContext admCtx;
+ auto ticket = p->ticketHolder->tryAcquire(&admCtx);
+ state.PauseTiming();
+ sleepmicros(1);
+ attempted++;
+ if (ticket) {
+ acquired++;
+ p->ticketHolder->release(&admCtx, std::move(*ticket));
+ }
+ state.ResumeTiming();
+ }
+ state.counters["Attempted"] = attempted;
+ state.counters["Acquired"] = acquired;
+}
+
+BENCHMARK_TEMPLATE(BM_tryAcquire, SemaphoreTicketHolder)->ThreadRange(kThreadMin, kThreadMax);
+
+BENCHMARK_TEMPLATE(BM_tryAcquire, FifoTicketHolder)->ThreadRange(kThreadMin, kThreadMax);
+
+template <class TicketHolderImpl>
+void BM_acquire(benchmark::State& state) {
+ static std::unique_ptr<TicketHolderFixture<TicketHolderImpl>> p;
+ if (state.thread_index == 0) {
+ p = std::make_unique<TicketHolderFixture<TicketHolderImpl>>(state.threads);
+ }
+ double acquired = 0;
+ for (auto _ : state) {
+ AdmissionContext admCtx;
+ auto opCtx = p->opCtxs[state.thread_index].get();
+ auto ticket = p->ticketHolder->waitForTicket(opCtx, &admCtx, waitMode);
+ state.PauseTiming();
+ sleepmicros(1);
+ p->ticketHolder->release(&admCtx, std::move(ticket));
+ acquired++;
+ state.ResumeTiming();
+ }
+ state.counters["Acquired"] = benchmark::Counter(acquired, benchmark::Counter::kIsRate);
+ state.counters["AcquiredPerThread"] =
+ benchmark::Counter(acquired, benchmark::Counter::kAvgThreadsRate);
+}
+
+BENCHMARK_TEMPLATE(BM_acquire, SemaphoreTicketHolder)->ThreadRange(kThreadMin, kThreadMax);
+
+BENCHMARK_TEMPLATE(BM_acquire, FifoTicketHolder)->ThreadRange(kThreadMin, kThreadMax);
+
+template <class TicketHolderImpl>
+void BM_release(benchmark::State& state) {
+ static std::unique_ptr<TicketHolderFixture<TicketHolderImpl>> p;
+ if (state.thread_index == 0) {
+ p = std::make_unique<TicketHolderFixture<TicketHolderImpl>>(state.threads);
+ }
+ double acquired = 0;
+ for (auto _ : state) {
+ AdmissionContext admCtx;
+ auto opCtx = p->opCtxs[state.thread_index].get();
+ state.PauseTiming();
+ auto ticket = p->ticketHolder->waitForTicket(opCtx, &admCtx, waitMode);
+ sleepmicros(1);
+ state.ResumeTiming();
+ p->ticketHolder->release(&admCtx, std::move(ticket));
+ acquired++;
+ }
+ state.counters["Acquired"] = benchmark::Counter(acquired, benchmark::Counter::kIsRate);
+ state.counters["AcquiredPerThread"] =
+ benchmark::Counter(acquired, benchmark::Counter::kAvgThreadsRate);
+}
+
+BENCHMARK_TEMPLATE(BM_release, SemaphoreTicketHolder)->ThreadRange(kThreadMin, kThreadMax);
+
+BENCHMARK_TEMPLATE(BM_release, FifoTicketHolder)->ThreadRange(kThreadMin, kThreadMax);
+
+
+template <class TicketHolderImpl>
void BM_acquireAndRelease(benchmark::State& state) {
static std::unique_ptr<TicketHolderFixture<TicketHolderImpl>> p;
if (state.thread_index == 0) {
@@ -91,14 +171,9 @@ void BM_acquireAndRelease(benchmark::State& state) {
}
BENCHMARK_TEMPLATE(BM_acquireAndRelease, SemaphoreTicketHolder)
- ->Threads(kThreadMin)
- ->Threads(kTickets)
- ->Threads(kThreadMax);
-
-BENCHMARK_TEMPLATE(BM_acquireAndRelease, FifoTicketHolder)
- ->Threads(kThreadMin)
- ->Threads(kTickets)
- ->Threads(kThreadMax);
+ ->ThreadRange(kThreadMin, kThreadMax);
+
+BENCHMARK_TEMPLATE(BM_acquireAndRelease, FifoTicketHolder)->ThreadRange(kThreadMin, kThreadMax);
} // namespace
} // namespace mongo
diff --git a/src/mongo/util/concurrency/ticketholder_test.cpp b/src/mongo/util/concurrency/ticketholder_test.cpp
index 2d0a04f88cc..5eba4fd046a 100644
--- a/src/mongo/util/concurrency/ticketholder_test.cpp
+++ b/src/mongo/util/concurrency/ticketholder_test.cpp
@@ -153,6 +153,88 @@ private:
TicketHolder* _holder;
};
+TEST_F(TicketHolderTest, FifoBasicMetrics) {
+ ServiceContext serviceContext;
+ serviceContext.setTickSource(std::make_unique<TickSourceMock<Microseconds>>());
+ auto tickSource = dynamic_cast<TickSourceMock<Microseconds>*>(serviceContext.getTickSource());
+ FifoTicketHolder holder(1, &serviceContext);
+ Stats stats(&holder);
+ AdmissionContext admCtx;
+
+ auto ticket =
+ holder.waitForTicket(_opCtx.get(), &admCtx, TicketHolder::WaitMode::kInterruptible);
+
+ unittest::Barrier barrier(2);
+ stdx::thread waiting([this, &holder, &barrier]() {
+ auto client = this->getServiceContext()->makeClient("waiting");
+ auto opCtx = client->makeOperationContext();
+ AdmissionContext admCtx;
+
+ auto ticket =
+ holder.waitForTicket(opCtx.get(), &admCtx, TicketHolder::WaitMode::kInterruptible);
+ barrier.countDownAndWait();
+ holder.release(&admCtx, std::move(ticket));
+ });
+
+ while (holder.queued() == 0) {
+ // Wait for thread to start waiting.
+ }
+
+ {
+ // Test that the metrics eventually converge to the following set of values. There can be
+ // cases where the values are incorrect for brief periods of time due to optimistic
+ // concurrency.
+ auto deadline = Date_t::now() + Milliseconds{100};
+ while (true) {
+ try {
+ ASSERT_EQ(stats["out"], 1);
+ ASSERT_EQ(stats["available"], 0);
+ ASSERT_EQ(stats["addedToQueue"], 1);
+ ASSERT_EQ(stats["queueLength"], 1);
+ break;
+ } catch (...) {
+ if (Date_t::now() > deadline) {
+ throw;
+ }
+ // Sleep to allow other threads to process and converge the metrics.
+ stdx::this_thread::sleep_for(Milliseconds{1}.toSystemDuration());
+ }
+ }
+ }
+ tickSource->advance(Microseconds(100));
+ holder.release(&admCtx, std::move(ticket));
+
+ while (holder.queued() > 0) {
+ // Wait for thread to take ticket.
+ }
+
+ tickSource->advance(Microseconds(200));
+ barrier.countDownAndWait();
+
+ waiting.join();
+
+ ASSERT_EQ(admCtx.getAdmissions(), 1);
+ ASSERT_EQ(stats["out"], 0);
+ ASSERT_EQ(stats["available"], 1);
+ ASSERT_EQ(stats["addedToQueue"], 1);
+ ASSERT_EQ(stats["removedFromQueue"], 1);
+ ASSERT_EQ(stats["queueLength"], 0);
+ ASSERT_EQ(stats["totalTimeQueuedMicros"], 100);
+ ASSERT_EQ(stats["startedProcessing"], 2);
+ ASSERT_EQ(stats["finishedProcessing"], 2);
+ ASSERT_EQ(stats["processing"], 0);
+ ASSERT_EQ(stats["totalTimeProcessingMicros"], 300);
+ ASSERT_EQ(stats["canceled"], 0);
+ ASSERT_EQ(stats["newAdmissions"], 2);
+
+ // Retake ticket.
+ ticket = holder.waitForTicket(_opCtx.get(), &admCtx, TicketHolder::WaitMode::kInterruptible);
+ holder.release(&admCtx, std::move(ticket));
+
+ ASSERT_EQ(admCtx.getAdmissions(), 2);
+ ASSERT_EQ(stats["newAdmissions"], 2);
+}
+
TEST_F(TicketHolderTest, FifoCanceled) {
ServiceContext serviceContext;
serviceContext.setTickSource(std::make_unique<TickSourceMock<Microseconds>>());