diff options
| author | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2025-02-11 15:07:35 -0300 |
|---|---|---|
| committer | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2025-02-11 15:07:35 -0300 |
| commit | 4cb8841196d0625dfa3825aa326f071cd27c7b8b (patch) | |
| tree | 1682a647d4463397c119183369ae6f750d5fdcff /src/mongo/util/concurrency | |
| parent | aa03c6362cbaa767638e6eed9b031d86dd2643d1 (diff) | |
| parent | 8f0827553e09872941945a093b647a4211a9db7f (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/SConscript | 1 | ||||
| -rw-r--r-- | src/mongo/util/concurrency/thread_name.cpp | 241 | ||||
| -rw-r--r-- | src/mongo/util/concurrency/thread_name.h | 162 | ||||
| -rw-r--r-- | src/mongo/util/concurrency/thread_pool.h | 7 | ||||
| -rw-r--r-- | src/mongo/util/concurrency/ticketholder.cpp | 3 | ||||
| -rw-r--r-- | src/mongo/util/concurrency/ticketholder_bm.cpp | 93 | ||||
| -rw-r--r-- | src/mongo/util/concurrency/ticketholder_test.cpp | 82 |
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>>()); |
