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 | |
| 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')
89 files changed, 802 insertions, 6423 deletions
diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript index 429b0dfcb42..724ba71b17d 100644 --- a/src/mongo/util/SConscript +++ b/src/mongo/util/SConscript @@ -33,7 +33,7 @@ def fmtBuildInfo(data): fmtStr(env.subst(obj['value'])), fmtBool(obj['inBuildInfo']), fmtBool(obj['inVersion'])) - return ',\n'.join([fmtObj(obj) for _, obj in data.items()]) + return ',\n'.join([fmtObj(obj) for _,obj in data.items()]) buildInfoInitializer = fmtBuildInfo(env['MONGO_BUILDINFO_ENVIRONMENT_DATA']) @@ -83,7 +83,6 @@ env.SConscript( dirs=[ 'cmdline_utils', 'concurrency', - 'immutable', 'net', 'options_parser', 'version', @@ -564,14 +563,6 @@ env.Benchmark( ], ) -env.Benchmark( - target='tick_source_bm', - source=[ - 'tick_source_bm.cpp', - ], - LIBDEPS=[], -) - env.Library( target='future_util', source=[ @@ -582,21 +573,6 @@ env.Library( ], ) -if env.TargetOSIs('linux'): - env.Library( - target='pin_code_segments', - source=[ - 'pin_code_segments.cpp', - 'pin_code_segments_params.idl', - ], - LIBDEPS=[ - '$BUILD_DIR/mongo/base', - ], - LIBDEPS_PRIVATE=[ - '$BUILD_DIR/mongo/idl/server_parameter', - ], - ) - env.Benchmark( target='hash_table_bm', source='hash_table_bm.cpp', @@ -716,7 +692,6 @@ icuEnv.CppUnitTest( 'future_test_future_int.cpp', 'future_test_future_move_only.cpp', 'future_test_future_void.cpp', - 'future_test_valid.cpp', 'future_test_promise_int.cpp', 'future_test_promise_void.cpp', 'future_test_shared_future.cpp', @@ -725,7 +700,6 @@ icuEnv.CppUnitTest( 'hierarchical_acquisition_test.cpp', 'icu_test.cpp', 'static_immortal_test.cpp', - 'interruptible_test.cpp', 'invalidating_lru_cache_test.cpp', 'itoa_test.cpp', 'latch_analyzer_test.cpp' if get_option('use-diagnostic-latches') == 'on' else [], @@ -745,8 +719,6 @@ icuEnv.CppUnitTest( 'registry_list_test.cpp', 'represent_as_test.cpp', 'safe_num_test.cpp', - 'scoped_unlock_test.cpp', - 'shared_buffer_test.cpp', 'secure_zero_memory_test.cpp', 'signal_handlers_synchronous_test.cpp' if not env.TargetOSIs('windows') else [], 'str_test.cpp', @@ -779,7 +751,6 @@ icuEnv.CppUnitTest( 'processinfo', 'procparser' if env.TargetOSIs('linux') else [], 'progress_meter', - 'regex_util', 'safe_num', 'secure_zero_memory', 'summation', @@ -881,3 +852,4 @@ env.Benchmark( 'processinfo', ], ) + diff --git a/src/mongo/util/assert_util.h b/src/mongo/util/assert_util.h index d03605e6192..4a9e7479443 100644 --- a/src/mongo/util/assert_util.h +++ b/src/mongo/util/assert_util.h @@ -150,19 +150,6 @@ public: }; /** - * Use `throwTransactionTooLargeForCache()` instead of throwing - * `TransactionTooLargeForCache` directly. - */ -class TransactionTooLargeForCacheException final : public DBException { -public: - TransactionTooLargeForCacheException(const Status& status) : DBException(status) {} - -private: - void defineOnlyInFinalSubclassToPreventSlicing() final {} -}; - - -/** * The base class of all DBExceptions for codes of the given ErrorCategory to allow catching by * category. */ @@ -178,8 +165,6 @@ protected: } }; -class WriteConflictException; -class TemporarilyUnavailableException; /** * This namespace contains implementation details for our error handling code and should not be used @@ -218,21 +203,6 @@ struct ExceptionForDispatcher<code, CategoryList<categories...>> { ExceptionForImpl<code, ExceptionForCat<categories>...>>; }; -template <> -struct ExceptionForDispatcher<ErrorCodes::WriteConflict> { - using type = WriteConflictException; -}; - -template <> -struct ExceptionForDispatcher<ErrorCodes::TemporarilyUnavailable> { - using type = TemporarilyUnavailableException; -}; - -template <> -struct ExceptionForDispatcher<ErrorCodes::TransactionTooLargeForCache> { - using type = TransactionTooLargeForCacheException; -}; - } // namespace error_details @@ -700,21 +670,3 @@ Status exceptionToStatus() noexcept; * Like `MONGO_UNREACHABLE`, but triggers a `tassert` instead of an `invariant` */ #define MONGO_UNREACHABLE_TASSERT(msgid) tasserted(msgid, "Hit a MONGO_UNREACHABLE_TASSERT!") - -/** - * Produces an invariant failure if executed. Subset of MONGO_UNREACHABLE, but specifically - * to indicate that the program has reached a function that is unimplemented and should be - * unreachable from production. - * Example: - * - * void myFuncToDo() { - * MONGO_UNIMPLEMENTED; - * } - */ -#define MONGO_UNIMPLEMENTED \ - ::mongo::invariantFailed("Hit a MONGO_UNIMPLEMENTED!", __FILE__, __LINE__); - -/** - * Like `MONGO_UNIMPLEMENTED`, but triggers a `tassert` instead of an `invariant` - */ -#define MONGO_UNIMPLEMENTED_TASSERT(msgid) tasserted(msgid, "Hit a MONGO_UNIMPLEMENTED_TASSERT!") diff --git a/src/mongo/util/assert_util_test.cpp b/src/mongo/util/assert_util_test.cpp index 68e45dff3e4..d4740ea966e 100644 --- a/src/mongo/util/assert_util_test.cpp +++ b/src/mongo/util/assert_util_test.cpp @@ -144,7 +144,7 @@ TEST(AssertUtils, UassertNamedCodeWithTwoCategories) { } MONGO_STATIC_ASSERT(!error_details::isNamedCode<19999>); -// ExceptionFor<ErrorCodes::Error19999)> invalidType; // Must not compile. +// ExceptionFor<ErrorCodes::Error(19999)> invalidType; // Must not compile. TEST(AssertUtils, UassertNumericCode) { ASSERT_CATCHES(19999, DBException); diff --git a/src/mongo/util/bufreader.h b/src/mongo/util/bufreader.h index b8d931add9a..8c30070bada 100644 --- a/src/mongo/util/bufreader.h +++ b/src/mongo/util/bufreader.h @@ -106,11 +106,6 @@ public: invariant(_pos >= _start); } - /** back up to beginging of buffer */ - void rewindToStart() { - _pos = _start; - } - /** return current position pointer, and advance by len */ const void* skip(unsigned len) { ConstDataRangeCursor cdrc(_pos, _end); @@ -129,14 +124,6 @@ public: s = readCStr().toString(); } - /** - * Return a view of the next len bytes and advance by len. - */ - StringData readBytes(size_t len) { - // Note: the call to skip() includes a check that at least 'len' bytes remain in the buffer. - return StringData(reinterpret_cast<const char*>(skip(len)), len); - } - const void* pos() { return _pos; } 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>>()); diff --git a/src/mongo/util/dns_query_posix-impl.h b/src/mongo/util/dns_query_posix-impl.h index d6ef7f6764f..93431114f7f 100644 --- a/src/mongo/util/dns_query_posix-impl.h +++ b/src/mongo/util/dns_query_posix-impl.h @@ -54,7 +54,6 @@ #include <boost/noncopyable.hpp> -#include "mongo/platform/mutex.h" #include "mongo/util/duration.h" namespace mongo { @@ -366,19 +365,12 @@ public: } DNSQueryState() : _state() { - // res_ninit may modify the global conf object creating a data race when multiple instances - // of DNSQueryState are created concurrently. - stdx::lock_guard<Latch> lk(_staticMutex); res_ninit(&_state); } private: struct __res_state _state; - static Mutex _staticMutex; }; - -Mutex DNSQueryState::_staticMutex = MONGO_MAKE_LATCH("DNSQueryState::_staticMutex"); - } // namespace } // namespace dns } // namespace mongo diff --git a/src/mongo/util/errno_util.cpp b/src/mongo/util/errno_util.cpp index 73aac860c8e..6e75b4b511a 100644 --- a/src/mongo/util/errno_util.cpp +++ b/src/mongo/util/errno_util.cpp @@ -27,34 +27,16 @@ * it in the license file. */ -#include "mongo/platform/basic.h" - #include "mongo/util/errno_util.h" #include <cerrno> #include <fmt/format.h> #include <system_error> -#ifdef _WIN32 -#include <errhandlingapi.h> -#include <winsock2.h> -#endif - namespace mongo { using namespace fmt::literals; -#ifdef _WIN32 -namespace errno_util_win32_detail { -int gle() { - return GetLastError(); -} -int wsaGle() { - return WSAGetLastError(); -} -} // namespace errno_util_win32_detail -#endif - std::string errorMessage(std::error_code ec) { std::string r = ec.message(); bool vague = false; diff --git a/src/mongo/util/errno_util.h b/src/mongo/util/errno_util.h index bbc12c3b40f..0464c38a409 100644 --- a/src/mongo/util/errno_util.h +++ b/src/mongo/util/errno_util.h @@ -29,7 +29,6 @@ #pragma once -#include <cstdlib> #include <system_error> #include <utility> @@ -37,36 +36,6 @@ namespace mongo { -#ifdef _WIN32 -namespace errno_util_win32_detail { -int gle(); -int wsaGle(); -} // namespace errno_util_win32_detail -#endif - -/** - * Returns category to use for POSIX errno error codes. - * On POSIX, `errno` codes are the `std::system_category`. - * On Windows, the `errno` codes are the `std::generic_category`. - */ -inline const std::error_category& posixCategory() { -#ifdef _WIN32 - return std::generic_category(); -#else - return std::system_category(); -#endif -} - -/** Wraps POSIX `errno` value in an appropriate `std::error_code`. */ -inline std::error_code posixError(int e) { - return std::error_code(e, posixCategory()); -} - -/** Wraps `e` in a `std::error_code` with `std::system_category`. */ -inline std::error_code systemError(int e) { - return std::error_code(e, std::system_category()); -} - /** * Returns `{errno, std::generic_category()}`. * Windows has both Windows errors and POSIX errors. That is, there's a @@ -79,39 +48,22 @@ inline std::error_code systemError(int e) { * On POSIX systems, `std::system_category` is potentially a superset of * `std::generic_category`, so `lastSystemError` should be preferred for * handling system errors. - * - * Guaranteed to not modify `errno`. */ inline std::error_code lastPosixError() { - return posixError(errno); + return std::error_code(errno, std::generic_category()); } /** * On POSIX, returns `{errno, std::system_category()}`. * On Windows, returns `{GetLastError(), std::system_category()}`, but see `lastPosixError`. - * - * Guaranteed to not modify the system error code variable. */ inline std::error_code lastSystemError() { #ifdef _WIN32 - return systemError(errno_util_win32_detail::gle()); -#else - return systemError(errno); -#endif -} - -/** - * Portable wrapper for socket API calls. On POSIX platforms this is just - * `lastSystemError`. On Windows, Winsock API callers must query last error with - * `WSAGetLastError` instead of `GetLastError`. The Winsock errors can use the - * same error code category as other Windows API calls. - */ -inline std::error_code lastSocketError() { -#ifdef _WIN32 - return systemError(errno_util_win32_detail::wsaGle()); + int e = GetLastError(); #else - return lastSystemError(); + int e = errno; #endif + return std::error_code(e, std::system_category()); } /** diff --git a/src/mongo/util/future.h b/src/mongo/util/future.h index cfdb724a101..5d36bb39861 100644 --- a/src/mongo/util/future.h +++ b/src/mongo/util/future.h @@ -181,16 +181,6 @@ public: } /** - * Returns whether this SemiFuture can or will be able to access a deferred status or value. - * - * NOTE: valid() will still return true if the value inside of the future is moved from. This - * should not be used as a way to determine usage validity until SERVER-66036. - */ - bool valid() const { - return _impl.valid(); - } - - /** * Returns when the Semifuture isReady(). * * Throws if the interruptible passed is interrupted (explicitly or via deadline). @@ -326,7 +316,6 @@ public: using SemiFuture<T>::SemiFuture; // Constructors. using SemiFuture<T>::share; using SemiFuture<T>::isReady; - using SemiFuture<T>::valid; using SemiFuture<T>::wait; using SemiFuture<T>::waitNoThrow; using SemiFuture<T>::get; @@ -605,7 +594,6 @@ public: using value_type = T; using SemiFuture<T>::share; using SemiFuture<T>::isReady; - using SemiFuture<T>::valid; using SemiFuture<T>::wait; using SemiFuture<T>::waitNoThrow; using SemiFuture<T>::get; @@ -982,10 +970,6 @@ public: return _shared.isReady(); } - bool valid() const { - return _shared.valid(); - } - void wait(Interruptible* interruptible = Interruptible::notInterruptible()) const { _shared.wait(interruptible); } diff --git a/src/mongo/util/future_impl.h b/src/mongo/util/future_impl.h index b1ef0ffd955..6fe25a51fb5 100644 --- a/src/mongo/util/future_impl.h +++ b/src/mongo/util/future_impl.h @@ -654,10 +654,6 @@ public: return _shared->state.load(std::memory_order_acquire) == SSBState::kFinished; } - bool valid() const { - return _shared != nullptr; - } - void wait(Interruptible* interruptible) const { _shared->wait(interruptible); } @@ -761,10 +757,6 @@ public: return _inner.isReady(); } - bool valid() const { - return _inner.valid(); - } - void wait(Interruptible* interruptible) const { _inner.wait(interruptible); } @@ -832,16 +824,6 @@ public: return _immediate || _shared.isReady(); } - /** - * Returns whether the Future has or can eventually have access to a deferred value or status. - * - * NOTE: this does not return whether that deferred value is itself valid. It could have been - * moved from. - */ - bool valid() const { - return _immediate || _shared.valid(); - } - void wait(Interruptible* interruptible) const { if (_immediate) return; diff --git a/src/mongo/util/future_test_utils.h b/src/mongo/util/future_test_utils.h index ec7bd783030..8bbecfd1faa 100644 --- a/src/mongo/util/future_test_utils.h +++ b/src/mongo/util/future_test_utils.h @@ -71,6 +71,12 @@ class DummyInterruptible final : public Interruptible { // Must be implemented because it's called by Interruptible::waitForConditionOrInterrupt. return Status::OK(); } + IgnoreInterruptsState pushIgnoreInterrupts() override { + MONGO_UNREACHABLE; + } + void popIgnoreInterrupts(IgnoreInterruptsState iis) override { + MONGO_UNREACHABLE; + } DeadlineState pushArtificialDeadline(Date_t deadline, ErrorCodes::Error error) override { MONGO_UNREACHABLE; } diff --git a/src/mongo/util/future_test_valid.cpp b/src/mongo/util/future_test_valid.cpp deleted file mode 100644 index 2ca85a19f26..00000000000 --- a/src/mongo/util/future_test_valid.cpp +++ /dev/null @@ -1,285 +0,0 @@ -/** - * Copyright (C) 2022-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/util/future.h" - -#include "mongo/stdx/thread.h" -#include "mongo/unittest/death_test.h" -#include "mongo/unittest/unittest.h" -#include "mongo/util/future_test_utils.h" - -#include <boost/optional.hpp> - -namespace mongo { -namespace { - -/** - * TODO(SERVER-66036): Expand testing to ensure that `valid()` semantics are in line with "valid - * usage" semantics. - */ - -/** - * These tests validate the postconditions of operations on the 4 future types: - * - Future - * - SemiFuture - * - SharedSemiFuture - * - ExecutorFuture - * - * TODO(SERVER-66036): use FUTURE_SUCCESS_TEST in all helpers for better coverage of ExecutorFuture. - */ - -/** Asserts that the Future is still valid() after `func`. */ -template <typename TestFunc> -void assertFutureValidAfter(const TestFunc& func) { - FUTURE_SUCCESS_TEST([] { return 0; }, - [func](auto&& fut) { - func(std::move(fut)); - ASSERT_TRUE(fut.valid()); - }); -} - -/** Asserts that `func` returns a valid() future while making the input Future non-valid(). */ -template <DoExecutorFuture doExecutorFuture = kDoExecutorFuture, typename TestFunc> -void assertFutureTransfersValid(const TestFunc& func) { - // TODO(SERVER-66036): use FUTURE_SUCCESS_TEST once moves from _immediate have the same - // semantics as moves from SharedState - auto [promise, fut] = makePromiseFuture<int>(); - promise.emplaceValue(0); - auto otherFut = func(std::move(fut)); - ASSERT_FALSE(fut.valid()); // NOLINT - ASSERT_TRUE(otherFut.valid()); -} - -TEST(FutureValid, ValidAtStart) { - FUTURE_SUCCESS_TEST([] { return 0; }, [](auto&& fut) { ASSERT_TRUE(fut.valid()); }); -} - -// TODO SERVER-64948: this test is only needed if the lvalue& getter is kept around. -TEST(FutureValid, ValidAfterGetLvalue) { - assertFutureValidAfter([](auto&& fut) { [[maybe_unused]] auto val = fut.get(); }); -} - -TEST(FutureValid, ValidAfterGetConstLvalue) { - assertFutureValidAfter([](const auto& fut) { [[maybe_unused]] auto val = fut.get(); }); -} - -TEST(FutureValid, ValidAfterGetNoThrowConstLvalue) { - assertFutureValidAfter([](const auto& fut) { [[maybe_unused]] auto val = fut.getNoThrow(); }); -} - -TEST(FutureValid, ValidAfterWait) { - assertFutureValidAfter([](auto&& fut) { fut.wait(); }); -} - -TEST(FutureValid, ValidAfterWaitNoThrow) { - assertFutureValidAfter([](auto&& fut) { [[maybe_unused]] auto status = fut.waitNoThrow(); }); -} - -TEST(FutureValid, ThenRunOnTransfersValid) { - assertFutureTransfersValid([](auto&& fut) { - auto exec = InlineQueuedCountingExecutor::make(); - return std::move(fut).thenRunOn(exec); - }); -} - -TEST(FutureValid, MoveTransfersValid) { - assertFutureTransfersValid([](auto&& fut) { return std::move(fut); }); -} - -TEST(FutureValid, SemiTransfersValid) { - assertFutureTransfersValid([](auto&& fut) { return std::move(fut).semi(); }); -} - -TEST(FutureValid, ShareTransfersValid) { - assertFutureTransfersValid([](auto&& fut) { return std::move(fut).share(); }); -} - -/** Asserts that the SemiFuture is still valid() after `func`. */ -template <typename TestFunc> -void assertSemiFutureValidAfter(const TestFunc& func) { - FUTURE_SUCCESS_TEST([] { return 0; }, - [func](auto&& fut) { - auto semiFut = std::move(fut).semi(); - func(std::move(semiFut)); - ASSERT_TRUE(semiFut.valid()); - }); -} - -/* Asserts that `func` returns a valid() future while making the input SemiFuture non-valid(). */ -template <typename TestFunc> -void assertSemiFutureTransfersValid(const TestFunc& func) { - // TODO(SERVER-66036): use FUTURE_SUCCESS_TEST once moves from _immediate have the same - // semantics as moves from SharedState - auto [promise, fut] = makePromiseFuture<int>(); - promise.emplaceValue(0); - auto semiFut = std::move(fut).semi(); - auto otherFut = func(std::move(semiFut)); - ASSERT_FALSE(semiFut.valid()); // NOLINT - ASSERT_TRUE(otherFut.valid()); -} - -// TODO SERVER-64948: this test is only needed if the lvalue& getter is kept around. -TEST(SemiFutureValid, ValidAfterGetLvalue) { - assertSemiFutureValidAfter([](auto&& fut) { [[maybe_unused]] auto val = fut.get(); }); -} - -TEST(SemiFutureValid, ValidAfterGetConstLvalue) { - assertSemiFutureValidAfter([](const auto& fut) { [[maybe_unused]] auto val = fut.get(); }); -} - -TEST(SemiFutureValid, ValidAfterGetNoThrowConstLvalue) { - assertSemiFutureValidAfter( - [](const auto& fut) { [[maybe_unused]] auto val = fut.getNoThrow(); }); -} - -TEST(SemiFutureValid, ValidAfterWait) { - assertSemiFutureValidAfter([](auto&& fut) { fut.wait(); }); -} - -TEST(SemiFutureValid, ValidAfterWaitNoThrow) { - assertSemiFutureValidAfter( - [](auto&& fut) { [[maybe_unused]] auto status = fut.waitNoThrow(); }); -} - -TEST(SemiFutureValid, ThenRunOnTransfersValid) { - assertSemiFutureTransfersValid([](auto&& fut) { - auto exec = InlineQueuedCountingExecutor::make(); - return std::move(fut).thenRunOn(exec); - }); -} - -TEST(SemiFutureValid, MoveTransfersValid) { - assertSemiFutureTransfersValid([](auto&& fut) { return std::move(fut); }); -} - -TEST(SemiFutureValid, SemiTransfersValid) { - assertSemiFutureTransfersValid([](auto&& fut) { return std::move(fut).semi(); }); -} - -TEST(SemiFutureValid, ShareTransfersValid) { - assertSemiFutureTransfersValid([](auto&& fut) { return std::move(fut).share(); }); -} - -TEST(SemiFutureValid, UnsafeToInlineFutureTransfersValid) { - assertSemiFutureTransfersValid( - [](auto&& fut) { return std::move(fut).unsafeToInlineFuture(); }); -} - -/** Asserts that the SharedSemiFuture is still valid() after `func`. */ -template <typename TestFunc> -void assertSharedSemiFutureValidAfter(const TestFunc& func) { - FUTURE_SUCCESS_TEST([] { return 0; }, - [func](auto&& fut) { - auto sharedFut = std::move(fut).share(); - func(std::move(sharedFut)); - ASSERT_TRUE(sharedFut.valid()); - }); -} - -/** - * Asserts that `func` returns a valid() future while making the input SharedSemiFuture non-valid(). - */ -template <typename TestFunc> -void assertSharedSemiFutureTransfersValid(const TestFunc& func) { - // TODO(SERVER-66036): use FUTURE_SUCCESS_TEST once moves from _immediate have the same - // semantics as moves from SharedState - auto [promise, fut] = makePromiseFuture<int>(); - promise.emplaceValue(0); - auto sharedFut = std::move(fut).share(); - auto otherFut = func(std::move(sharedFut)); - ASSERT_FALSE(sharedFut.valid()); // NOLINT - ASSERT_TRUE(otherFut.valid()); -} - -/** Asserts that `func` returns a valid() Future and keeps the input SharedSemiFuture valid(). */ -template <typename TestFunc> -void assertSharedSemiFutureSplits(const TestFunc& func) { - // TODO(SERVER-66036): use FUTURE_SUCCESS_TEST once moves from _immediate have the same - // semantics as moves from SharedState - auto [promise, fut] = makePromiseFuture<int>(); - promise.emplaceValue(0); - auto sharedFut = std::move(fut).share(); - auto otherFut = func(std::move(sharedFut)); - ASSERT_TRUE(sharedFut.valid()); // NOLINT - ASSERT_TRUE(otherFut.valid()); -} - -TEST(SharedSemiFutureValid, ValidAfterGetLvalue) { - assertSharedSemiFutureValidAfter([](auto&& fut) { [[maybe_unused]] auto val = fut.get(); }); -} - -TEST(SharedSemiFutureValid, ValidAfterGetConstLvalue) { - assertSharedSemiFutureValidAfter( - [](const auto& fut) { [[maybe_unused]] auto val = fut.get(); }); -} - -TEST(SharedSemiFutureValid, ValidAfterGetNoThrowLvalue) { - assertSharedSemiFutureValidAfter( - [](auto&& fut) { [[maybe_unused]] auto val = fut.getNoThrow(); }); -} - -TEST(SharedSemiFutureValid, ValidAfterGetNoThrowConstLvalue) { - assertSharedSemiFutureValidAfter( - [](const auto& fut) { [[maybe_unused]] auto val = fut.getNoThrow(); }); -} - -TEST(SharedSemiFutureValid, ValidAfterWait) { - assertSharedSemiFutureValidAfter([](auto&& fut) { fut.wait(); }); -} - -TEST(SharedSemiFutureValid, ValidAfterWaitNoThrow) { - assertSharedSemiFutureValidAfter( - [](auto&& fut) { [[maybe_unused]] auto status = fut.waitNoThrow(); }); -} - -TEST(SharedSemiFutureValid, ValidAfterThenRunOn) { - assertSharedSemiFutureSplits([](auto&& fut) { - auto exec = InlineQueuedCountingExecutor::make(); - return fut.thenRunOn(exec); - }); -} - -TEST(SharedSemiFutureValid, MoveTransfersValid) { - assertSharedSemiFutureTransfersValid([](auto&& fut) { return std::move(fut); }); -} - -TEST(SharedSemiFutureValid, SemiRetainsValid) { - assertSharedSemiFutureSplits([](auto&& fut) { return std::move(fut).semi(); }); -} - -TEST(SharedSemiFutureValid, SplitRetainsValid) { - assertSharedSemiFutureSplits([](auto&& fut) { return std::move(fut).split(); }); -} - -TEST(SharedSemiFutureValid, UnsafeToInlineFutureRetainsValid) { - assertSharedSemiFutureSplits([](auto&& fut) { return std::move(fut).unsafeToInlineFuture(); }); -} - -} // namespace -} // namespace mongo diff --git a/src/mongo/util/immutable/README.md b/src/mongo/util/immutable/README.md deleted file mode 100644 index 74f5e07259d..00000000000 --- a/src/mongo/util/immutable/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Immutable Containers - -This folder contains a number of _immutable_ container classes. Sometimes called _persistent data -structures_ in the literature, these classes provide interfaces similar to STL containers with one -key difference. Operations which "modify" the container are `const` and return a modified copy of -the container rather than modifying it in-place. This makes the containers implicitly thread-safe to -read and write, but external synchronization will still be needed in many cases to address isolation -and serializability concerns (i.e. -[MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control)). - -## When To Use Immutable Containers - -If the container will be copied frequently, e.g. to support a copy-on-write pattern, consider using -an immutable container. Otherwise, a standard container may make more sense. - -## Supported Containers - -The currently supported containers are all based on classes from the -[`immer`](https://sinusoid.es/immer/) library. - - - [`immutable::map`](map.h): ordered map interface backed by `immer::flex_vector` - - [`immutable::set`](set.h): ordered set interface backed by `immer::flex_vector` - - [`immutable::unordered_map`](unordered_map.h): typedef for `immer:map` - - [`immutable::unordered_set`](unordered_set.h): typedef for `immer:set` - - [`immutable::vector`](vector.h): typedef for `immer::vector` - -Both ordered and unordered map and set variants support heterogeneous lookup. - -## A Note on Performance - -The internal implementations of these containers are optimized to support an internal copy-on-write -pattern so that copies and modifications are $O(log(n))$ or even $O(1)$. However, the constants on -these runtime guarantees, as well as those for lookups, are typically worse than those of the -corresponding STL or Abseil containers. For this reason, they should not be considered a -general-purpose drop-in replacement. diff --git a/src/mongo/util/immutable/SConscript b/src/mongo/util/immutable/SConscript deleted file mode 100644 index 72e517d93c5..00000000000 --- a/src/mongo/util/immutable/SConscript +++ /dev/null @@ -1,29 +0,0 @@ -# -*- mode: python -*- - -Import("env") - -env = env.Clone() - -env.CppUnitTest( - target='immutable_test', - source=[ - 'immutable_ordered_test.cpp', - 'immutable_unordered_test.cpp', - 'immutable_vector_test.cpp', - ], - LIBDEPS=[], -) - -env.Benchmark( - target='immutable_absl_comparison_bm', - source=[ - 'immutable_absl_comparison_bm.cpp', - ], -) - -env.Benchmark( - target='immutable_std_comparison_bm', - source=[ - 'immutable_std_comparison_bm.cpp', - ], -) diff --git a/src/mongo/util/immutable/details/map.h b/src/mongo/util/immutable/details/map.h deleted file mode 100644 index a66db19b412..00000000000 --- a/src/mongo/util/immutable/details/map.h +++ /dev/null @@ -1,233 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include <algorithm> - -namespace mongo::immutable::details::map { - -template <typename map, typename SearchKey> -bool equal(const typename map::key_type& a, const SearchKey& b) { - return !typename map::comp{}(a, b) && !typename map::comp{}(b, a); -} - -template <typename map, typename SearchKey> -[[nodiscard]] typename map::iterator lower_bound(const typename map::storage_type& storage, - const SearchKey& key) { - return std::lower_bound(storage.begin(), - storage.end(), - key, - [](const typename map::value_type& a, const SearchKey& b) -> bool { - return typename map::comp{}(a.first, b); - }); -} - -template <typename map, typename SearchKey> -[[nodiscard]] typename map::iterator find(const typename map::storage_type& storage, - const SearchKey& key) { - auto it = lower_bound<map>(storage, key); - if (it != storage.end() && equal<map>(it->first, key)) { - return it; - } - - return storage.end(); -} - -template <typename map, typename S, typename K, typename V> -[[nodiscard]] typename map::storage_type insert(S&& storage, K&& key, V&& value) { - auto it = lower_bound<map>(storage, key); - if (it != storage.end() && equal<map>(it->first, key)) { - return std::forward<S>(storage); - } - if (it == storage.end()) { - return std::forward<S>(storage).push_back( - std::make_pair(std::forward<K>(key), std::forward<V>(value))); - } - return std::forward<S>(storage).insert( - it.index(), std::make_pair(std::forward<K>(key), std::forward<V>(value))); -} - -template <typename map, typename S, typename K, typename V> -[[nodiscard]] typename map::storage_type insert(S&& storage, - typename map::iterator it, - K&& key, - V&& value) { - if (it != storage.end() && equal<map>(it->first, key)) { - return std::forward<S>(storage); - } - - if (it == storage.end()) { - if (typename map::comp{}(storage[storage.size() - 1].first, key)) { - return std::forward<S>(storage).push_back( - std::make_pair(std::forward<K>(key), std::forward<V>(value))); - } - return insert<map>(std::forward<S>(storage), std::forward<K>(key), std::forward<V>(value)); - } - - if (typename map::comp{}(key, it->first) && - (it.index() == 0 || typename map::comp{}((it - 1)->first, key))) { - return std::forward<S>(storage).insert( - it.index(), std::make_pair(std::forward<K>(key), std::forward<V>(value))); - } - - return insert<map>(std::forward<S>(storage), std::forward<K>(key), std::forward<V>(value)); -} - -template <typename map, typename S, typename K, typename U> -[[nodiscard]] typename map::storage_type update(S&& storage, K&& key, U&& valueUpdate) { - auto it = lower_bound<map>(storage, key); - if (it == storage.end()) { - return std::forward<S>(storage).push_back( - std::make_pair(std::forward<K>(key), valueUpdate(typename map::default_value{}()))); - } - - if (equal<map>(it->first, key)) { - return std::forward<S>(storage).set( - it.index(), std::make_pair(std::forward<K>(key), valueUpdate(it->second))); - } - - return insert<map>(std::forward<S>(storage), - std::forward<K>(key), - valueUpdate(typename map::default_value{}())); -} - -template <typename map, typename S, typename K, typename U> -[[nodiscard]] typename map::storage_type update(S&& storage, - typename map::iterator it, - K&& key, - U&& valueUpdate) { - if (it == storage.end()) { - if (typename map::comp{}(storage[storage.size() - 1].first, key)) { - return std::forward<S>(storage).push_back( - std::make_pair(std::forward<K>(key), valueUpdate(typename map::default_value{}()))); - } - return update<map>( - std::forward<S>(storage), std::forward<K>(key), std::forward<U>(valueUpdate)); - } - - if (equal<map>(it->first, key)) { - return std::forward<S>(storage).set( - it.index(), std::make_pair(std::forward<K>(key), valueUpdate(it->second))); - } - - if (typename map::comp{}(key, it->first) && - (it.index() == 0 || typename map::comp{}((it - 1)->first, key))) { - return std::forward<S>(storage).insert( - it.index(), - std::make_pair(std::forward<K>(key), valueUpdate(typename map::default_value{}()))); - } - - return insert<map>(std::forward<S>(storage), - std::forward<K>(key), - valueUpdate(typename map::default_value{}())); -} - -template <typename map, typename S, typename K, typename U> -[[nodiscard]] typename map::storage_type update_if_exists(S&& storage, K&& key, U&& valueUpdate) { - auto it = find<map>(storage, key); - if (it == storage.end()) { - return std::forward<S>(storage); - } - return std::forward<S>(storage).set( - it.index(), std::make_pair(std::forward<K>(key), valueUpdate(it->second))); -} - -template <typename map, typename S, typename K, typename U> -[[nodiscard]] typename map::storage_type update_if_exists(S&& storage, - typename map::iterator it, - K&& key, - U&& valueUpdate) { - if (it == storage.end() || !equal<map>(it->first, key)) { - return update_if_exists<map>( - std::forward<S>(storage), std::forward<K>(key), std::forward<U>(valueUpdate)); - } - return std::forward<S>(storage).set( - it.index(), std::make_pair(std::forward<K>(key), valueUpdate(it->second))); -} - -template <typename map, typename S, typename K, typename V> -[[nodiscard]] typename map::storage_type set(S&& storage, K&& key, V&& value) { - auto it = lower_bound<map>(storage, key); - if (it == storage.end()) { - return std::forward<S>(storage).push_back( - std::make_pair(std::forward<K>(key), std::forward<V>(value))); - } else if (!equal<map>(it->first, key)) { - return std::forward<S>(storage).insert( - it.index(), std::make_pair(std::forward<K>(key), std::forward<V>(value))); - } - return std::forward<S>(storage).set( - it.index(), std::make_pair(std::forward<K>(key), std::forward<V>(value))); -} - -template <typename map, typename S, typename K, typename V> -[[nodiscard]] typename map::storage_type set(S&& storage, - typename map::iterator it, - K&& key, - V&& value) { - if (it == storage.end()) { - if (typename map::comp{}(storage[storage.size() - 1].first, key)) { - return std::forward<S>(storage).push_back( - std::make_pair(std::forward<K>(key), std::forward<V>(value))); - } - return set<map>(std::forward<S>(storage), std::forward<K>(key), std::forward<V>(value)); - } - - if (equal<map>(it->first, key)) { - return std::forward<S>(storage).set( - it.index(), std::make_pair(std::forward<K>(key), std::forward<V>(value))); - } - - if (typename map::comp{}(key, it->first) && - (it.index() == 0 || typename map::comp{}((it - 1)->first, key))) { - return std::forward<S>(storage).insert( - it.index(), std::make_pair(std::forward<K>(key), std::forward<V>(value))); - } - - return set<map>(std::forward<S>(storage), std::forward<K>(key), std::forward<V>(value)); -} - -template <typename map, typename S, typename K> -[[nodiscard]] typename map::storage_type erase(S&& storage, K&& key) { - auto it = find<map>(storage, key); - if (it == storage.end()) { - return std::forward<S>(storage); - } - return std::forward<S>(storage).erase(it.index()); -} - -template <typename map, typename S, typename K> -[[nodiscard]] typename map::storage_type erase(S&& storage, typename map::iterator it, K&& key) { - if (it == storage.end() || !equal<map>(it->first, key)) { - return erase<map>(std::forward<S>(storage), std::forward<K>(key)); - } - return std::forward<S>(storage).erase(it.index()); -} - -} // namespace mongo::immutable::details::map diff --git a/src/mongo/util/immutable/details/memory_policy.h b/src/mongo/util/immutable/details/memory_policy.h deleted file mode 100644 index 01923f032b7..00000000000 --- a/src/mongo/util/immutable/details/memory_policy.h +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include <immer/memory_policy.hpp> - -namespace mongo::immutable::detail { - -// Memory allocations using regular new/delete operators -using HeapPolicy = immer::heap_policy<immer::cpp_heap>; - -// Refcounting using atomics for thread safety -using RefcountPolicy = immer::refcount_policy; - -// We are not using any features from immer that requires locking. Use 'void' as the lock type which -// would fail compilation if locking was needed anywhere. -using LockPolicy = void; - -// No transience policy (this is just used for garbage collection) -using TransiencePolicy = immer::no_transience_policy; - -using MemoryPolicy = immer::memory_policy<HeapPolicy, - RefcountPolicy, - LockPolicy, - TransiencePolicy, - /*PreferFewerBiggerObjects*/ true, - /*UseTransientRValues*/ true>; - -// Verify that the recommended settings for our memory policy is as expected. We need to investigate -// if any of these fire during a library upgrade. -static_assert( - std::is_same<immer::get_transience_policy_t<RefcountPolicy>, TransiencePolicy>::value); -static_assert(immer::get_prefer_fewer_bigger_objects_v<HeapPolicy> == true); -static_assert(immer::get_use_transient_rvalues_v<RefcountPolicy> == true); - -} // namespace mongo::immutable::detail diff --git a/src/mongo/util/immutable/details/set.h b/src/mongo/util/immutable/details/set.h deleted file mode 100644 index 491412e696f..00000000000 --- a/src/mongo/util/immutable/details/set.h +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include <algorithm> - -namespace mongo::immutable::details::set { - -template <typename set, typename SearchKey> -bool equal(const typename set::key_type& a, const SearchKey& b) { - return !typename set::comp{}(a, b) && !typename set::comp{}(b, a); -} - -template <typename set, class SearchKey> -[[nodiscard]] typename set::iterator lower_bound(const typename set::storage_type& storage, - const SearchKey& key) { - return std::lower_bound(storage.begin(), storage.end(), key, typename set::comp{}); -} - -template <typename set, typename SearchKey> -[[nodiscard]] typename set::iterator find(const typename set::storage_type& storage, - const SearchKey& key) { - auto it = lower_bound<set>(storage, key); - if (it != storage.end() && equal<set>(*it, key)) { - return it; - } - - return storage.end(); -} - -template <typename set, typename S, typename K> -[[nodiscard]] typename set::storage_type insert(S&& storage, K&& key) { - auto it = lower_bound<set>(storage, key); - if (it == storage.end()) { - return std::forward<S>(storage).push_back(std::forward<K>(key)); - } - - if (equal<set>(*it, key)) { - return std::forward<S>(storage); - } - - return std::forward<S>(storage).insert(it.index(), std::forward<K>(key)); -} - -template <typename set, typename S, typename K> -[[nodiscard]] typename set::storage_type insert(S&& storage, typename set::iterator it, K&& key) { - if (it != storage.end() && equal<set>(*it, key)) { - return std::forward<S>(storage); - } - - if (it == storage.end()) { - if (typename set::comp{}(storage[storage.size() - 1], key)) { - return std::forward<S>(storage).push_back(std::forward<K>(key)); - } - return insert<set>(std::forward<S>(storage), std::forward<K>(key)); - } - - if (typename set::comp{}(key, *it) && - (it.index() == 0 || typename set::comp{}(*(it - 1), key))) { - return std::forward<S>(storage).insert(it.index(), std::forward<K>(key)); - } - - return insert<set>(std::forward<S>(storage), std::forward<K>(key)); -} - -template <typename set, typename S, typename K> -[[nodiscard]] typename set::storage_type erase(S&& storage, K&& key) { - auto it = find<set>(storage, key); - if (it == storage.end()) { - return std::forward<S>(storage); - } - return std::forward<S>(storage).erase(it.index()); -} - -template <typename set, typename S, typename K> -[[nodiscard]] typename set::storage_type erase(S&& storage, typename set::iterator it, K&& key) { - if (it == storage.end() || !equal<set>(*it, key)) { - return erase<set>(std::forward<S>(storage), std::forward<K>(key)); - } - return std::forward<S>(storage).erase(it.index()); -} - -} // namespace mongo::immutable::details::set diff --git a/src/mongo/util/immutable/immutable_absl_comparison_bm.cpp b/src/mongo/util/immutable/immutable_absl_comparison_bm.cpp deleted file mode 100644 index 78358aae0ff..00000000000 --- a/src/mongo/util/immutable/immutable_absl_comparison_bm.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include <benchmark/benchmark.h> - -#include "mongo/stdx/unordered_map.h" -#include "mongo/util/immutable/unordered_map.h" - -namespace mongo { - -static void BM_absl_insert_op(benchmark::State& state) { - stdx::unordered_map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map[i] = i; - } - - for (auto _ : state) { - map[i] = i; - i++; - benchmark::ClobberMemory(); - } -} - -static void BM_absl_copy_op(benchmark::State& state) { - stdx::unordered_map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map[i] = i; - } - - stdx::unordered_map<int, int> mapCopy; - for (auto _ : state) { - mapCopy = map; - benchmark::ClobberMemory(); - } -} - -static void BM_absl_find_op(benchmark::State& state) { - stdx::unordered_map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map[i] = i; - } - - for (auto _ : state) { - benchmark::DoNotOptimize(map.find(i - 1)); - benchmark::ClobberMemory(); - } -} - -static void BM_absl_copy_and_insert_op(benchmark::State& state) { - stdx::unordered_map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map[i] = i; - } - - stdx::unordered_map<int, int> mapCopy; - for (auto _ : state) { - mapCopy = map; - mapCopy[i] = i; - benchmark::ClobberMemory(); - } -} - -static void BM_immutable_insert_op(benchmark::State& state) { - immutable::unordered_map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map = std::move(map).set(i, i); - } - - for (auto _ : state) { - map = std::move(map).set(i, i); - i++; - benchmark::ClobberMemory(); - } -} - -static void BM_immutable_copy_op(benchmark::State& state) { - immutable::unordered_map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map = std::move(map).set(i, i); - } - - immutable::unordered_map<int, int> mapCopy; - for (auto _ : state) { - mapCopy = map; - benchmark::ClobberMemory(); - } -} - -static void BM_immutable_find_op(benchmark::State& state) { - immutable::unordered_map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map = std::move(map).set(i, i); - } - - for (auto _ : state) { - benchmark::DoNotOptimize(map.find(i - 1)); - benchmark::ClobberMemory(); - } -} - -static void BM_immutable_copy_and_insert_op(benchmark::State& state) { - immutable::unordered_map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map = std::move(map).set(i, i); - } - - immutable::unordered_map<int, int> mapCopy; - for (auto _ : state) { - mapCopy = map.set(i, i); - benchmark::ClobberMemory(); - } -} - -// Run with varying container sizes: [ 8, 16, 32, 64, 128, 256, 512, 1024, 2k, 4k, 8k ]. -BENCHMARK(BM_absl_insert_op)->RangeMultiplier(2)->Range(8, 8 << 10); -BENCHMARK(BM_immutable_insert_op)->RangeMultiplier(2)->Range(8, 8 << 10); - -BENCHMARK(BM_absl_copy_op)->RangeMultiplier(2)->Range(8, 8 << 10); -BENCHMARK(BM_immutable_copy_op)->RangeMultiplier(2)->Range(8, 8 << 10); - -BENCHMARK(BM_absl_find_op)->RangeMultiplier(2)->Range(8, 8 << 10); -BENCHMARK(BM_immutable_find_op)->RangeMultiplier(2)->Range(8, 8 << 10); - -BENCHMARK(BM_absl_copy_and_insert_op)->RangeMultiplier(2)->Range(8, 8 << 10); -BENCHMARK(BM_immutable_copy_and_insert_op)->RangeMultiplier(2)->Range(8, 8 << 10); -} // namespace mongo diff --git a/src/mongo/util/immutable/immutable_ordered_test.cpp b/src/mongo/util/immutable/immutable_ordered_test.cpp deleted file mode 100644 index 2b9308d5caf..00000000000 --- a/src/mongo/util/immutable/immutable_ordered_test.cpp +++ /dev/null @@ -1,1336 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - - -#include "mongo/unittest/unittest.h" - -#include "mongo/util/immutable/map.h" -#include "mongo/util/immutable/set.h" -#include "mongo/util/string_map.h" -#include <stdexcept> - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - -namespace mongo { -namespace { - -class UserDefinedKey { -public: - UserDefinedKey() = default; - explicit UserDefinedKey(int val) : a(val) {} - - bool operator==(const UserDefinedKey& rhs) const { - return a == rhs.a; - } - - bool operator<(const UserDefinedKey& rhs) const { - return a < rhs.a; - } - - std::string toString() const { - return std::to_string(a); - } - -private: - int a = 0; -}; - - -class Incomparable { - friend struct CompareIncomparable; - -public: - Incomparable() = default; - explicit Incomparable(int val) : a(val) {} - - bool operator==(const Incomparable&) = delete; - bool operator<(const Incomparable&) = delete; - - std::string toString() const { - return std::to_string(a); - } - -private: - int a = 0; -}; - -struct CompareIncomparable { - bool operator()(const Incomparable& a, const Incomparable& b) const { - return a.a < b.a; - } - - // Pair comparator needed for some testing macros to function properly for both maps and sets. - bool operator()(const std::pair<Incomparable, int>& a, - const std::pair<Incomparable, int>& b) const { - return a.first.a < b.first.a; - } -}; - -struct StringCompare { - bool operator()(const std::string& a, const std::string& b) const { - return a < b; - } - bool operator()(const std::string& a, const StringData& b) const { - return a < b; - } - bool operator()(const StringData& a, const std::string& b) const { - return a < b; - } - bool operator()(const std::string& a, const char* b) const { - return a < b; - } - bool operator()(const char* a, const std::string& b) const { - return a < b; - } -}; - -template <typename C, typename L> -void ensureContainerInvariants(const C& container, L&& less) { - size_t visited = 0; - for (auto it = container.begin(); it != container.end(); ++it) { - if (++visited > 1) { - ASSERT(less(*(it - 1), *it)); - } - } - ASSERT_EQ(visited, container.size()); -} - - -template <typename C> -void ensureContainerInvariants(const C& container) { - size_t visited = 0; - for (auto it = container.begin(); it != container.end(); ++it) { - if (++visited > 1) { - ASSERT(*(it - 1) < *it); - } - } - ASSERT_EQ(visited, container.size()); -} - -template <typename C> -void ensureContainerInvariants(const std::tuple<C, C, C, C>& containers) { - ensureContainerInvariants(std::get<0>(containers)); - ensureContainerInvariants(std::get<1>(containers)); - ensureContainerInvariants(std::get<2>(containers)); - ensureContainerInvariants(std::get<3>(containers)); -} - -template <typename C> -void ensureContainerInvariants(std::initializer_list<const C> containers) { - for (auto& c : containers) { - ensureContainerInvariants(c); - } -} - -template <typename K, typename V, typename M = immutable::map<K, V>> -std::tuple<M, M, M, M> init_maps() { - return std::make_tuple(M{}, M{}, M{}, M{}); -} - -template <typename K, typename S = immutable::set<K>> -std::tuple<S, S, S, S> init_sets() { - return std::make_tuple(S{}, S{}, S{}, S{}); -} - -std::ostream& operator<<(std::ostream& s, const UserDefinedKey& k) { - return s << k.toString(); -} - -std::ostream& operator<<(std::ostream& s, const Incomparable& k) { - return s << k.toString(); -} - -template <typename T, typename U> -std::ostream& operator<<(std::ostream& s, const std::pair<T, U> pair) { - return s << "(" << pair.first << "," << pair.second << ")"; -} - -template <typename C> -struct ContainerWrapper { - ContainerWrapper(const C& c) : container{c} {} - const C& container; -}; -template <typename T> -std::ostream& operator<<(std::ostream& str, const ContainerWrapper<T>& wrapper) { - str << "{"; - bool first = true; - for (auto& el : wrapper.container) { - if (first) { - first = false; - } else { - str << ", "; - } - str << el; - } - str << "}"; - return str; -} - -#define ASSERT_CONTAINS(containers, k) \ - { \ - ASSERT_TRUE(std::get<0>(containers).contains(k)) \ - << ContainerWrapper{std::get<0>(containers)}; \ - ASSERT_TRUE(std::get<1>(containers).contains(k)) \ - << ContainerWrapper{std::get<0>(containers)}; \ - ASSERT_TRUE(std::get<2>(containers).contains(k)) \ - << ContainerWrapper{std::get<0>(containers)}; \ - ASSERT_TRUE(std::get<3>(containers).contains(k)) \ - << ContainerWrapper{std::get<0>(containers)}; \ - } - -#define ASSERT_NOT_CONTAINS(containers, k) \ - { \ - ASSERT_FALSE(std::get<0>(containers).contains(k)) \ - << ContainerWrapper{std::get<0>(containers)}; \ - ASSERT_FALSE(std::get<1>(containers).contains(k)) \ - << ContainerWrapper{std::get<0>(containers)}; \ - ASSERT_FALSE(std::get<2>(containers).contains(k)) \ - << ContainerWrapper{std::get<0>(containers)}; \ - ASSERT_FALSE(std::get<3>(containers).contains(k)) \ - << ContainerWrapper{std::get<0>(containers)}; \ - } - -#define ASSERT_VALUE_EQ(its, v) \ - { \ - ASSERT_EQ(std::get<0>(its)->second, v); \ - ASSERT_EQ(std::get<1>(its)->second, v); \ - ASSERT_EQ(std::get<2>(its)->second, v); \ - ASSERT_EQ(std::get<3>(its)->second, v); \ - } - -#define MUTATE_KV(containers, fn, k, v) \ - [&]() { \ - auto _k1 = k; \ - auto _v1 = v; \ - auto _m1 = std::get<0>(containers).fn(_k1, _v1); \ - ensureContainerInvariants(_m1); \ - \ - auto _k2 = k; \ - auto _v2 = v; \ - auto _m2 = std::move(std::get<1>(containers)).fn(_k2, _v2); \ - ensureContainerInvariants(_m2); \ - \ - auto _k3 = k; \ - auto _v3 = v; \ - auto _m3 = std::get<2>(containers).fn(std::move(_k3), std::move(_v3)); \ - ensureContainerInvariants(_m3); \ - \ - auto _k4 = k; \ - auto _v4 = v; \ - auto _m4 = std::move(std::get<3>(containers)).fn(std::move(_k4), std::move(_v4)); \ - ensureContainerInvariants(_m4); \ - \ - return std::make_tuple(_m1, _m2, _m3, _m4); \ - }(); - -#define MUTATE_K(containers, fn, k) \ - ([&]() { \ - auto _k1 = k; \ - auto _m1 = std::get<0>(containers).fn(_k1); \ - ensureContainerInvariants(_m1); \ - \ - auto _k2 = k; \ - auto _m2 = std::move(std::get<1>(containers)).fn(_k2); \ - ensureContainerInvariants(_m2); \ - \ - auto _k3 = k; \ - auto _m3 = std::get<2>(containers).fn(std::move(_k3)); \ - ensureContainerInvariants(_m3); \ - \ - auto _k4 = k; \ - auto _m4 = std::move(std::get<3>(containers)).fn(std::move(_k4)); \ - ensureContainerInvariants(_m4); \ - \ - return std::make_tuple(_m1, _m2, _m3, _m4); \ - }()); - -#define MUTATE_IT_KV(containers, fn, its, k, v) \ - [&]() { \ - auto _k1 = k; \ - auto _v1 = v; \ - auto _m1 = std::get<0>(containers).fn(std::get<0>(its), _k1, _v1); \ - ensureContainerInvariants(_m1); \ - \ - auto _k2 = k; \ - auto _v2 = v; \ - auto _m2 = std::move(std::get<1>(containers)).fn(std::get<1>(its), _k2, _v2); \ - ensureContainerInvariants(_m2); \ - \ - auto _k3 = k; \ - auto _v3 = v; \ - auto _m3 = std::get<2>(containers).fn(std::get<2>(its), std::move(_k3), std::move(_v3)); \ - ensureContainerInvariants(_m3); \ - \ - auto _k4 = k; \ - auto _v4 = v; \ - auto _m4 = std::move(std::get<3>(containers)) \ - .fn(std::get<3>(its), std::move(_k4), std::move(_v4)); \ - ensureContainerInvariants(_m4); \ - \ - return std::make_tuple(_m1, _m2, _m3, _m4); \ - }(); - -#define MUTATE_IT_K(containers, fn, its, k) \ - [&]() { \ - auto _k1 = k; \ - auto _m1 = std::get<0>(containers).fn(std::get<0>(its), _k1); \ - ensureContainerInvariants(_m1); \ - \ - auto _k2 = k; \ - auto _m2 = std::move(std::get<1>(containers)).fn(std::get<1>(its), _k2); \ - ensureContainerInvariants(_m2); \ - \ - auto _k3 = k; \ - auto _m3 = std::get<2>(containers).fn(std::get<2>(its), std::move(_k3)); \ - ensureContainerInvariants(_m3); \ - \ - auto _k4 = k; \ - auto _m4 = std::move(std::get<3>(containers)).fn(std::get<3>(its), std::move(_k4)); \ - ensureContainerInvariants(_m4); \ - \ - return std::make_tuple(_m1, _m2, _m3, _m4); \ - }(); - -#define SEARCH(containers, fn, k) \ - [&]() { \ - auto _i1 = std::get<0>(containers).fn(k); \ - auto _i2 = std::get<1>(containers).fn(k); \ - auto _i3 = std::get<2>(containers).fn(k); \ - auto _i4 = std::get<3>(containers).fn(k); \ - return std::make_tuple(_i1, _i2, _i3, _i4); \ - }(); - -#define END(containers) \ - [&]() { \ - auto _i1 = std::get<0>(containers).end(); \ - auto _i2 = std::get<1>(containers).end(); \ - auto _i3 = std::get<2>(containers).end(); \ - auto _i4 = std::get<3>(containers).end(); \ - return std::make_tuple(_i1, _i2, _i3, _i4); \ - }(); - -TEST(ImmutableMap, Basic) { - // Insert some values and verify that the data structure is behaving as expected - immutable::map<int, int> v0; - auto v1 = v0.set(1, 2); - // Record the pointer to the value '1', verify that this doesn't change after performing more - // inserts - auto v1Val = v1.find(1); - - // Create distinct branches of the history from v1. v0 and v1 should be unaffected - auto v2 = v1.update_if_exists(1, [](int v) { return v += 1; }); - auto v3 = v1.set(2, 3); - - // Verify that values are as expected - ASSERT_EQ(v0.size(), 0); - - ASSERT_EQ(v1.size(), 1); - ASSERT_TRUE(v1.contains(1)); - ASSERT_EQ(v1.find(1)->second, 2); - - ASSERT_EQ(v2.size(), 1); - ASSERT_TRUE(v2.contains(1)); - ASSERT_EQ(v2.find(1)->second, 3); - ASSERT_FALSE(v2.contains(2)); - - ASSERT_EQ(v3.size(), 2); - ASSERT_TRUE(v3.contains(1)); - ASSERT_EQ(v3.find(1)->second, 2); - ASSERT_TRUE(v3.contains(2)); - ASSERT_EQ(v3.find(2)->second, 3); - - // Verify that v1's value did not change - ASSERT(v1.find(1) == v1Val); - - // Verify that erase works as expected, and preserves history. - auto v4 = v3.erase(1).erase(2); - ASSERT_FALSE(v4.contains(1)); - ASSERT_FALSE(v4.contains(2)); - ASSERT_TRUE(v3.contains(1)); - ASSERT_EQ(v3.find(1)->second, 2); - ASSERT_TRUE(v3.contains(2)); - ASSERT_EQ(v3.find(2)->second, 3); - ASSERT_TRUE(v2.contains(1)); - ASSERT_EQ(v2.find(1)->second, 3); - ASSERT_FALSE(v2.contains(2)); - ASSERT_TRUE(v1.contains(1)); - ASSERT_EQ(v1.find(1)->second, 2); - ASSERT_FALSE(v1.contains(2)); - - ensureContainerInvariants({v0, v1, v2, v3, v4}); -} - -TEST(ImmutableMap, UserDefinedType) { - immutable::map<UserDefinedKey, int> v0; - auto v1 = v0.set(UserDefinedKey(1), 2); - ASSERT(v1.find(UserDefinedKey(1)) != v1.end()); - ASSERT_EQ(v1.find(UserDefinedKey(1))->second, 2); - - ensureContainerInvariants({v0, v1}); -} - -TEST(ImmutableMap, IncomparableType) { - immutable::map<Incomparable, int, CompareIncomparable> v0; - auto v1 = v0.set(Incomparable(1), 2); - ASSERT(v1.find(Incomparable(1)) != v1.end()); - ASSERT_EQ(v1.find(Incomparable(1))->second, 2); - - ensureContainerInvariants(v0, CompareIncomparable{}); - ensureContainerInvariants(v1, CompareIncomparable{}); -} - -TEST(ImmutableMap, HeterogeneousLookup) { - immutable::map<std::string, int, StringCompare> v0; - auto v1 = v0.set("str", 1); - - // Lookup using StringData without the need to convert to string. - ASSERT(v1.find("str"_sd) != v1.end()); - - ensureContainerInvariants({v0, v1}); -} - -TEST(ImmutableMap, Accessors) { - immutable::map<int, int> v0; - auto v1 = v0.insert(1, 1).insert(2, 2).insert(3, 3); - - ASSERT_EQ(v1[1], 1); - ASSERT_EQ(v1[2], 2); - ASSERT_EQ(v1[3], 3); - ASSERT_EQ(v1.at(1), 1); - ASSERT_EQ(v1.at(2), 2); - ASSERT_EQ(v1.at(3), 3); - - // Handling of missing elements - ASSERT_EQ(v1[4], 0); - ASSERT_THROWS(v1.at(4), std::out_of_range); - - ensureContainerInvariants({v0, v1}); -} - -TEST(ImmutableMap, Bounds) { - immutable::map<int, int> map; - constexpr int numKeys = 100; - for (int i = 0; i < numKeys; ++i) { - map = map.set(2 * i, 2 * i); - ensureContainerInvariants(map); - } - - for (int i = 0; i < numKeys - 1; ++i) { - auto lowerExact = map.lower_bound(2 * i); - ASSERT(lowerExact != map.end() && lowerExact->first == 2 * i); - auto upperExact = map.upper_bound(2 * i); - ASSERT(upperExact != map.end() && upperExact->first == 2 * (i + 1)); - - auto lowerNear = map.lower_bound(2 * i + 1); - ASSERT(lowerNear != map.end() && lowerNear->first == 2 * (i + 1)); - auto upperNear = map.upper_bound(2 * i + 1); - ASSERT(upperNear != map.end() && upperNear->first == 2 * (i + 1)); - } -} - -TEST(ImmutableMap, Iteration) { - immutable::map<int, int> map; - constexpr int numKeys = 100; - for (int i = 0; i < numKeys; ++i) { - map = map.set(2 * i, 2 * i); - ensureContainerInvariants(map); - } - - auto map0 = map; - auto it0 = map0.begin(); - - for (int i = 0; i < numKeys; ++i) { - map = map.set(2 * i + 1, 2 * i + 1); - ensureContainerInvariants(map); - } - - auto map1 = map; - auto it1 = map1.begin(); - - for (int i = 0; i < numKeys; ++i) { - ASSERT(it0 != map0.end()); - ASSERT_EQ(it0->first, 2 * i); - ++it0; - - ASSERT(it1 != map1.end()); - ASSERT_EQ(it1->first, 2 * i); - ++it1; - - ASSERT(it1 != map1.end()); - ASSERT_EQ(it1->first, 2 * i + 1); - ++it1; - } - ASSERT(it0 == map0.end()); - ASSERT(it1 == map1.end()); -} - -TEST(ImmutableMap, Insert) { - auto v0 = init_maps<int, int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_KV(v0, insert, 1, 1); - auto v2 = MUTATE_KV(v1, insert, 2, 2); - auto v3 = MUTATE_KV(v2, insert, 3, 3); - auto v4 = MUTATE_KV(v3, insert, 5, 5); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 5); - - // Insert of existing key is a noop. - auto v5 = MUTATE_KV(v4, insert, 1, 2); - ASSERT_CONTAINS(v5, 1); - auto i5 = SEARCH(v5, find, 1); - ASSERT_VALUE_EQ(i5, 1); - - // Insert at beginning works. - auto v6 = MUTATE_KV(v4, insert, 0, 0); - ASSERT_CONTAINS(v6, 0); - auto i6 = SEARCH(v6, find, 0); - ASSERT_VALUE_EQ(i6, 0); - - // Insert at end works. - auto v7 = MUTATE_KV(v4, insert, 6, 6); - ASSERT_CONTAINS(v7, 6); - auto i7 = SEARCH(v7, find, 6); - ASSERT_VALUE_EQ(i7, 6); - - // Insert in middle works. - auto v8 = MUTATE_KV(v4, insert, 4, 4); - ASSERT_CONTAINS(v8, 4); - auto i8 = SEARCH(v8, find, 4); - ASSERT_VALUE_EQ(i8, 4); -} - -TEST(ImmutableMap, InsertViaIterator) { - auto v0 = init_maps<int, int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_KV(v0, insert, 1, 1); - auto v2 = MUTATE_KV(v1, insert, 2, 2); - auto v3 = MUTATE_KV(v2, insert, 3, 3); - auto v4 = MUTATE_KV(v3, insert, 6, 6); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 6); - - // Giving the iterator for an existing element does a noop. - - auto it4_2 = SEARCH(v4, find, 2); - auto v5 = MUTATE_IT_KV(v4, insert, it4_2, 2, 5); - auto it5_2 = SEARCH(v5, find, 2); - ASSERT_VALUE_EQ(it5_2, 2); - - // Giving end() as hint works appropriately whether hint is accurate or not. - - auto end5 = END(v5); - auto v6 = MUTATE_IT_KV(v5, insert, end5, 2, 5); - auto it6_2 = SEARCH(v6, find, 2); - ASSERT_VALUE_EQ(it6_2, 2); - - auto end6 = END(v6); - auto v7 = MUTATE_IT_KV(v6, insert, end6, 4, 4); - ASSERT_NOT_CONTAINS(v6, 4); - ASSERT_CONTAINS(v7, 4); - auto it7_4 = SEARCH(v7, find, 4); - ASSERT_VALUE_EQ(it7_4, 4); - - auto end7 = END(v7); - auto v8 = MUTATE_IT_KV(v7, insert, end7, 7, 7); - ASSERT_NOT_CONTAINS(v7, 7); - ASSERT_CONTAINS(v8, 7); - auto it8_7 = SEARCH(v8, find, 7); - ASSERT_VALUE_EQ(it8_7, 7); - - // Giving hint from lower_bound works for both existing and new entries. - - auto lb8_2 = SEARCH(v8, lower_bound, 2); - auto v9 = MUTATE_IT_KV(v8, insert, lb8_2, 2, 5); - auto it9_2 = SEARCH(v9, find, 2); - ASSERT_VALUE_EQ(it9_2, 2); - - auto lb9_0 = SEARCH(v9, lower_bound, 0); - auto v10 = MUTATE_IT_KV(v9, insert, lb9_0, 0, 0); - ASSERT_NOT_CONTAINS(v9, 0); - ASSERT_CONTAINS(v10, 0); - auto it10_0 = SEARCH(v10, find, 0); - ASSERT_VALUE_EQ(it10_0, 0); - - auto lb10_5 = SEARCH(v10, lower_bound, 5); - auto v11 = MUTATE_IT_KV(v10, insert, lb10_5, 5, 5); - ASSERT_NOT_CONTAINS(v10, 5); - ASSERT_CONTAINS(v11, 5); - auto it11_5 = SEARCH(v11, find, 5); - ASSERT_VALUE_EQ(it11_5, 5); - - auto lb11_8 = SEARCH(v11, lower_bound, 8); - auto v12 = MUTATE_IT_KV(v11, insert, lb11_8, 8, 8); - ASSERT_NOT_CONTAINS(v11, 8); - ASSERT_CONTAINS(v12, 8); - auto it12_8 = SEARCH(v12, find, 8); - ASSERT_VALUE_EQ(it12_8, 8); -} - -TEST(ImmutableMap, Set) { - auto v0 = init_maps<int, int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_KV(v0, insert, 1, 1); - auto v2 = MUTATE_KV(v1, insert, 2, 2); - auto v3 = MUTATE_KV(v2, insert, 3, 3); - auto v4 = MUTATE_KV(v3, insert, 5, 5); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 5); - - // Set on existing key updates value. - auto v5 = MUTATE_KV(v4, set, 1, 2); - ASSERT_CONTAINS(v5, 1); - auto i5 = SEARCH(v5, find, 1); - ASSERT_VALUE_EQ(i5, 2); - - // Set to insert at beginning works. - auto v6 = MUTATE_KV(v4, set, 0, 0); - ASSERT_CONTAINS(v6, 0); - auto i6 = SEARCH(v6, find, 0); - ASSERT_VALUE_EQ(i6, 0); - - // Set to insert at end works. - auto v7 = MUTATE_KV(v4, set, 6, 6); - ASSERT_CONTAINS(v7, 6); - auto i7 = SEARCH(v7, find, 6); - ASSERT_VALUE_EQ(i7, 6); - - // Set to insert in middle works. - auto v8 = MUTATE_KV(v4, set, 4, 4); - ASSERT_CONTAINS(v8, 4); - auto i8 = SEARCH(v8, find, 4); - ASSERT_VALUE_EQ(i8, 4); -} - -TEST(ImmutableMap, SetViaIterator) { - auto v0 = init_maps<int, int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_KV(v0, insert, 1, 1); - auto v2 = MUTATE_KV(v1, insert, 2, 2); - auto v3 = MUTATE_KV(v2, insert, 3, 3); - auto v4 = MUTATE_KV(v3, insert, 6, 6); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 6); - - // Giving the iterator for an existing element updates the value. - - auto it4_2 = SEARCH(v4, find, 2); - auto v5 = MUTATE_IT_KV(v4, set, it4_2, 2, 5); - auto it5_2 = SEARCH(v5, find, 2); - ASSERT_VALUE_EQ(it5_2, 5); - - // Giving end() as hint works appropriately whether hint is accurate or not. - - auto end5 = END(v5); - auto v6 = MUTATE_IT_KV(v5, set, end5, 2, 7); - auto it6_2 = SEARCH(v6, find, 2); - ASSERT_VALUE_EQ(it6_2, 7); - - auto end6 = END(v6); - auto v7 = MUTATE_IT_KV(v6, set, end6, 4, 4); - ASSERT_NOT_CONTAINS(v6, 4); - ASSERT_CONTAINS(v7, 4); - auto it7_4 = SEARCH(v7, find, 4); - ASSERT_VALUE_EQ(it7_4, 4); - - auto end7 = END(v7); - auto v8 = MUTATE_IT_KV(v7, set, end7, 7, 7); - ASSERT_NOT_CONTAINS(v7, 7); - ASSERT_CONTAINS(v8, 7); - auto it8_7 = SEARCH(v8, find, 7); - ASSERT_VALUE_EQ(it8_7, 7); - - // Giving hint from lower_bound works for both existing and new entries. - - auto lb8_2 = SEARCH(v8, lower_bound, 2); - auto v9 = MUTATE_IT_KV(v8, set, lb8_2, 2, 9); - auto it9_2 = SEARCH(v9, find, 2); - ASSERT_VALUE_EQ(it9_2, 9); - - auto lb9_0 = SEARCH(v9, lower_bound, 0); - auto v10 = MUTATE_IT_KV(v9, set, lb9_0, 0, 0); - ASSERT_NOT_CONTAINS(v9, 0); - ASSERT_CONTAINS(v10, 0); - auto it10_0 = SEARCH(v10, find, 0); - ASSERT_VALUE_EQ(it10_0, 0); - - auto lb10_5 = SEARCH(v10, lower_bound, 5); - auto v11 = MUTATE_IT_KV(v10, set, lb10_5, 5, 5); - ASSERT_NOT_CONTAINS(v10, 5); - ASSERT_CONTAINS(v11, 5); - auto it11_5 = SEARCH(v11, find, 5); - ASSERT_VALUE_EQ(it11_5, 5); - - auto lb11_8 = SEARCH(v11, lower_bound, 8); - auto v12 = MUTATE_IT_KV(v11, set, lb11_8, 8, 8); - ASSERT_NOT_CONTAINS(v11, 8); - ASSERT_CONTAINS(v12, 8); - auto it12_8 = SEARCH(v12, find, 8); - ASSERT_VALUE_EQ(it12_8, 8); -} - -TEST(ImmutableMap, Update) { - auto v0 = init_maps<int, int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_KV(v0, insert, 1, 1); - auto v2 = MUTATE_KV(v1, insert, 2, 2); - auto v3 = MUTATE_KV(v2, insert, 3, 3); - auto v4 = MUTATE_KV(v3, insert, 5, 5); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 5); - - // Update on existing key updates value. - auto v5 = MUTATE_KV(v4, update, 1, [](int x) { return x + 1; }); - ASSERT_CONTAINS(v5, 1); - auto i5 = SEARCH(v5, find, 1); - ASSERT_VALUE_EQ(i5, 2); - - // Update to insert at beginning works. - auto v6 = MUTATE_KV(v4, update, 0, [](int x) { return x + 1; }); - ASSERT_CONTAINS(v6, 0); - auto i6 = SEARCH(v6, find, 0); - ASSERT_VALUE_EQ(i6, 1); - - // Update to insert at end works. - auto v7 = MUTATE_KV(v4, update, 6, [](int x) { return x + 1; }); - ASSERT_CONTAINS(v7, 6); - auto i7 = SEARCH(v7, find, 6); - ASSERT_VALUE_EQ(i7, 1); - - // Update to insert in middle works. - auto v8 = MUTATE_KV(v4, update, 4, [](int x) { return x + 1; }); - ASSERT_CONTAINS(v8, 4); - auto i8 = SEARCH(v8, find, 4); - ASSERT_VALUE_EQ(i8, 1); -} - -TEST(ImmutableMap, UpdateViaIterator) { - auto v0 = init_maps<int, int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_KV(v0, insert, 1, 1); - auto v2 = MUTATE_KV(v1, insert, 2, 2); - auto v3 = MUTATE_KV(v2, insert, 3, 3); - auto v4 = MUTATE_KV(v3, insert, 6, 6); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 6); - - // Giving the iterator for an existing element updates the value. - - auto it4_2 = SEARCH(v4, find, 2); - auto v5 = MUTATE_IT_KV(v4, update, it4_2, 2, [](int x) { return x + 1; }); - auto it5_2 = SEARCH(v5, find, 2); - ASSERT_VALUE_EQ(it5_2, 3); - - // Giving end() as hint works appropriately whether hint is accurate or not. - - auto end5 = END(v5); - auto v6 = MUTATE_IT_KV(v5, update, end5, 2, [](int x) { return x + 1; }); - auto it6_2 = SEARCH(v6, find, 2); - ASSERT_VALUE_EQ(it6_2, 4); - - auto end6 = END(v6); - auto v7 = MUTATE_IT_KV(v6, update, end6, 4, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v6, 4); - ASSERT_CONTAINS(v7, 4); - auto it7_4 = SEARCH(v7, find, 4); - ASSERT_VALUE_EQ(it7_4, 1); - - auto end7 = END(v7); - auto v8 = MUTATE_IT_KV(v7, update, end7, 7, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v7, 7); - ASSERT_CONTAINS(v8, 7); - auto it8_7 = SEARCH(v8, find, 7); - ASSERT_VALUE_EQ(it8_7, 1); - - // Giving hint from lower_bound works for both existing and new entries. - - auto lb8_2 = SEARCH(v8, lower_bound, 2); - auto v9 = MUTATE_IT_KV(v8, update, lb8_2, 2, [](int x) { return x + 1; }); - auto it9_2 = SEARCH(v9, find, 2); - ASSERT_VALUE_EQ(it9_2, 5); - - auto lb9_0 = SEARCH(v9, lower_bound, 0); - auto v10 = MUTATE_IT_KV(v9, update, lb9_0, 0, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v9, 0); - ASSERT_CONTAINS(v10, 0); - auto it10_0 = SEARCH(v10, find, 0); - ASSERT_VALUE_EQ(it10_0, 1); - - auto lb10_5 = SEARCH(v10, lower_bound, 5); - auto v11 = MUTATE_IT_KV(v10, update, lb10_5, 5, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v10, 5); - ASSERT_CONTAINS(v11, 5); - auto it11_5 = SEARCH(v11, find, 5); - ASSERT_VALUE_EQ(it11_5, 1); - - auto lb11_8 = SEARCH(v11, lower_bound, 8); - auto v12 = MUTATE_IT_KV(v11, update, lb11_8, 8, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v11, 8); - ASSERT_CONTAINS(v12, 8); - auto it12_8 = SEARCH(v12, find, 8); - ASSERT_VALUE_EQ(it12_8, 1); -} - -TEST(ImmutableMap, UpdateIfExists) { - auto v0 = init_maps<int, int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_KV(v0, insert, 1, 1); - auto v2 = MUTATE_KV(v1, insert, 2, 2); - auto v3 = MUTATE_KV(v2, insert, 3, 3); - auto v4 = MUTATE_KV(v3, insert, 5, 5); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 5); - - // Update on existing key updates value. - auto v5 = MUTATE_KV(v4, update_if_exists, 1, [](int x) { return x + 1; }); - ASSERT_CONTAINS(v5, 1); - auto i5 = SEARCH(v5, find, 1); - ASSERT_VALUE_EQ(i5, 2); - - // Update to insert at beginning does nothing. - auto v6 = MUTATE_KV(v4, update_if_exists, 0, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v6, 0); - - // Update to insert at end does nothing. - auto v7 = MUTATE_KV(v4, update_if_exists, 6, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v7, 6); - - // Update to insert in middle does nothing. - auto v8 = MUTATE_KV(v4, update_if_exists, 4, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v8, 4); -} - -TEST(ImmutableMap, UpdateIfExistsViaIterator) { - auto v0 = init_maps<int, int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_KV(v0, insert, 1, 1); - auto v2 = MUTATE_KV(v1, insert, 2, 2); - auto v3 = MUTATE_KV(v2, insert, 3, 3); - auto v4 = MUTATE_KV(v3, insert, 6, 6); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 6); - - // Giving the iterator for an existing element updates the value. - - auto it4_2 = SEARCH(v4, find, 2); - auto v5 = MUTATE_IT_KV(v4, update_if_exists, it4_2, 2, [](int x) { return x + 1; }); - auto it5_2 = SEARCH(v5, find, 2); - ASSERT_VALUE_EQ(it5_2, 3); - - // Giving end() as hint works appropriately whether hint is accurate or not. - - auto end5 = END(v5); - auto v6 = MUTATE_IT_KV(v5, update_if_exists, end5, 2, [](int x) { return x + 1; }); - auto it6_2 = SEARCH(v6, find, 2); - ASSERT_VALUE_EQ(it6_2, 4); - - auto end6 = END(v6); - auto v7 = MUTATE_IT_KV(v6, update_if_exists, end6, 4, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v7, 4); - - auto end7 = END(v7); - auto v8 = MUTATE_IT_KV(v7, update_if_exists, end7, 7, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v8, 7); - - // Giving hint from lower_bound works for both existing and non-existing entries. - - auto lb8_2 = SEARCH(v8, lower_bound, 2); - auto v9 = MUTATE_IT_KV(v8, update_if_exists, lb8_2, 2, [](int x) { return x + 1; }); - auto it9_2 = SEARCH(v9, find, 2); - ASSERT_VALUE_EQ(it9_2, 5); - - auto lb9_0 = SEARCH(v9, lower_bound, 0); - auto v10 = MUTATE_IT_KV(v9, update_if_exists, lb9_0, 0, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v10, 0); - - auto lb10_5 = SEARCH(v10, lower_bound, 5); - auto v11 = MUTATE_IT_KV(v10, update_if_exists, lb10_5, 5, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v11, 5); - - auto lb11_8 = SEARCH(v11, lower_bound, 8); - auto v12 = MUTATE_IT_KV(v11, update_if_exists, lb11_8, 8, [](int x) { return x + 1; }); - ASSERT_NOT_CONTAINS(v12, 8); -} - -TEST(ImmutableMap, Erase) { - auto v0 = init_maps<int, int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_KV(v0, insert, 1, 1); - auto v2 = MUTATE_KV(v1, insert, 2, 2); - auto v3 = MUTATE_KV(v2, insert, 3, 3); - auto v4 = MUTATE_KV(v3, insert, 5, 5); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 5); - - // Erase on existing key removes value. - auto v5 = MUTATE_K(v4, erase, 1); - ASSERT_NOT_CONTAINS(v5, 1); - - // Erase on non-existent key does nothing. - auto v6 = MUTATE_K(v5, erase, 0); - ASSERT_NOT_CONTAINS(v6, 0); - ASSERT_CONTAINS(v6, 2); - ASSERT_CONTAINS(v6, 3); - ASSERT_CONTAINS(v6, 5); -} - -TEST(ImmutableMap, EraseViaIterator) { - auto v0 = init_maps<int, int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_KV(v0, insert, 1, 1); - auto v2 = MUTATE_KV(v1, insert, 2, 2); - auto v3 = MUTATE_KV(v2, insert, 3, 3); - auto v4 = MUTATE_KV(v3, insert, 5, 5); - auto v5 = MUTATE_KV(v4, insert, 6, 6); - ASSERT_CONTAINS(v5, 1); - ASSERT_CONTAINS(v5, 2); - ASSERT_CONTAINS(v5, 3); - ASSERT_CONTAINS(v5, 5); - ASSERT_CONTAINS(v5, 6); - - // Giving the iterator for an existing element erases the value. - - auto it5_2 = SEARCH(v5, find, 2); - auto v6 = MUTATE_IT_K(v5, erase, it5_2, 2); - ASSERT_NOT_CONTAINS(v6, 2); - - // Giving end() as hint works appropriately whether hint is accurate or not. - - auto end6 = END(v6); - auto v7 = MUTATE_IT_K(v6, erase, end6, 3); - ASSERT_NOT_CONTAINS(v7, 3); - - auto end7 = END(v7); - auto v8 = MUTATE_IT_K(v7, erase, end7, 4); - ASSERT_NOT_CONTAINS(v8, 4); - - auto end8 = END(v8); - auto v9 = MUTATE_IT_K(v8, erase, end8, 7); - ASSERT_NOT_CONTAINS(v9, 7); - - // Giving hint from lower_bound works for both existing and non-existing entries. - - auto lb9_5 = SEARCH(v9, lower_bound, 5); - auto v10 = MUTATE_IT_K(v9, erase, lb9_5, 5); - ASSERT_NOT_CONTAINS(v10, 5); - - auto lb10_0 = SEARCH(v10, lower_bound, 0); - auto v11 = MUTATE_IT_K(v10, erase, lb10_0, 0); - - auto lb11_4 = SEARCH(v10, lower_bound, 4); - auto v12 = MUTATE_IT_K(v11, erase, lb11_4, 4); - - auto lb12_7 = SEARCH(v12, lower_bound, 7); - auto v13 = MUTATE_IT_K(v12, erase, lb12_7, 7); - - ASSERT_CONTAINS(v13, 1); - ASSERT_CONTAINS(v13, 6); - ASSERT_NOT_CONTAINS(v13, 0); - ASSERT_NOT_CONTAINS(v13, 2); - ASSERT_NOT_CONTAINS(v13, 3); - ASSERT_NOT_CONTAINS(v13, 4); - ASSERT_NOT_CONTAINS(v13, 5); - ASSERT_NOT_CONTAINS(v13, 7); -} - -TEST(ImmutableMap, ExclusiveOwnership) { - immutable::map<int, int> v0; - - auto v1 = v0.set(1, 1); - auto v2 = v1.set(2, 2); - auto v3 = v2.set(3, 3); - - ASSERT_TRUE(v1.contains(1)); - ASSERT_TRUE(v2.contains(1)); - ASSERT_TRUE(v3.contains(1)); - - // Claiming exclusive ownership over v3 means v3 will no longer be valid after mutation, but - // older versions should be unperturbed. - auto v4 = std::move(v3).erase(1); - ASSERT_TRUE(v1.contains(1)); - ASSERT_TRUE(v2.contains(1)); - ASSERT_FALSE(v4.contains(1)); -} - -TEST(ImmutableSet, Basic) { - // Insert some values and verify that the data structure is behaving as expected - immutable::set<int> v0; - auto v1 = v0.insert(1); - // Record the iterator for the key '1', verify that this doesn't change after performing - // more inserts - auto v1it = v1.find(1); - - // Create distinct branches of the history from v1. v0 and v1 should be unaffected - auto v2 = v1.insert(2); - auto v3 = v1.insert(3); - - // Verify that values are as expected - ASSERT_EQ(v0.size(), 0); - - ASSERT_EQ(v1.size(), 1); - ASSERT_TRUE(v1.contains(1)); - ASSERT_EQ(*v1.find(1), 1); - ASSERT_FALSE(v1.contains(2)); - ASSERT_FALSE(v1.contains(3)); - - ASSERT_EQ(v2.size(), 2); - ASSERT_TRUE(v2.contains(1)); - ASSERT_EQ(*v2.find(1), 1); - ASSERT_TRUE(v2.contains(2)); - ASSERT_EQ(*v2.find(2), 2); - ASSERT_FALSE(v2.contains(3)); - - ASSERT_EQ(v3.size(), 2); - ASSERT_TRUE(v3.contains(1)); - ASSERT_EQ(*v3.find(1), 1); - ASSERT_TRUE(v3.contains(3)); - ASSERT_EQ(*v3.find(3), 3); - ASSERT_FALSE(v3.contains(2)); - - // Verify that v1's iterator did not change - ASSERT(v1.find(1) == v1it); - - // Verify that erase works as expected, and preserves history. - auto v4 = v3.erase(1).erase(3); - ASSERT_EQ(v4.size(), 0); - ASSERT_FALSE(v4.contains(1)); - ASSERT_FALSE(v4.contains(2)); - ASSERT_FALSE(v4.contains(3)); - ASSERT_EQ(v3.size(), 2); - ASSERT_TRUE(v3.contains(1)); - ASSERT_TRUE(v3.contains(3)); - ASSERT_EQ(v2.size(), 2); - ASSERT_TRUE(v2.contains(1)); - ASSERT_TRUE(v2.contains(2)); - ASSERT_EQ(v1.size(), 1); - ASSERT_TRUE(v1.contains(1)); - - ensureContainerInvariants({v0, v1, v2, v3, v4}); -} - -TEST(ImmutableSet, UserDefinedType) { - immutable::set<UserDefinedKey> v0; - auto v1 = v0.insert(UserDefinedKey(1)); - ASSERT(v1.find(UserDefinedKey(1)) != v1.end()); - ASSERT_EQ(*v1.find(UserDefinedKey(1)), UserDefinedKey(1)); - - ensureContainerInvariants({v0, v1}); -} - -TEST(ImmutableSet, IncomparableType) { - immutable::set<Incomparable, CompareIncomparable> v0; - auto v1 = v0.insert(Incomparable(1)); - ASSERT_TRUE(v1.contains(Incomparable(1))); - - ensureContainerInvariants(v0, CompareIncomparable{}); - ensureContainerInvariants(v1, CompareIncomparable{}); -} - -TEST(ImmutableSet, HeterogeneousLookup) { - immutable::set<std::string, StringCompare> v0; - auto v1 = v0.insert("str"); - - // Lookup using StringData without the need to convert to string. - ASSERT(v1.find("str"_sd) != v1.end()); - - ensureContainerInvariants({v0, v1}); -} - -TEST(ImmutableSet, Bounds) { - immutable::set<int> set; - constexpr int numKeys = 100; - for (int i = 0; i < numKeys; ++i) { - set = set.insert(2 * i); - ensureContainerInvariants(set); - } - - for (int i = 0; i < numKeys - 1; ++i) { - auto lowerExact = set.lower_bound(2 * i); - ASSERT(lowerExact != set.end() && *lowerExact == 2 * i); - auto upperExact = set.upper_bound(2 * i); - ASSERT(upperExact != set.end() && *upperExact == 2 * (i + 1)); - - auto lowerNear = set.lower_bound(2 * i + 1); - ASSERT(lowerNear != set.end() && *lowerNear == 2 * (i + 1)); - auto upperNear = set.upper_bound(2 * i + 1); - ASSERT(upperNear != set.end() && *upperNear == 2 * (i + 1)); - } -} - -TEST(ImmutableSet, Iteration) { - immutable::set<int> set; - constexpr int numKeys = 100; - for (int i = 0; i < numKeys; ++i) { - set = set.insert(2 * i); - ensureContainerInvariants(set); - } - - auto set0 = set; - auto it0 = set0.begin(); - - for (int i = 0; i < numKeys; ++i) { - set = set.insert(2 * i + 1); - ensureContainerInvariants(set); - } - - auto set1 = set; - auto it1 = set1.begin(); - - for (int i = 0; i < numKeys; ++i) { - ASSERT(it0 != set0.end()); - ASSERT_EQ(*it0, 2 * i); - ++it0; - - ASSERT(it1 != set1.end()); - ASSERT_EQ(*it1, 2 * i); - ++it1; - - ASSERT(it1 != set1.end()); - ASSERT_EQ(*it1, 2 * i + 1); - ++it1; - } - ASSERT(it0 == set0.end()); - ASSERT(it1 == set1.end()); -} - -TEST(ImmutableSet, Insert) { - auto v0 = init_sets<int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_K(v0, insert, 1); - auto v2 = MUTATE_K(v1, insert, 2); - auto v3 = MUTATE_K(v2, insert, 3); - auto v4 = MUTATE_K(v3, insert, 5); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 5); - - // Insert of existing key is a noop. - auto v5 = MUTATE_K(v4, insert, 1); // enforces no duplicates - ASSERT_CONTAINS(v5, 1); - - // Insert at beginning works. - auto v6 = MUTATE_K(v4, insert, 0); - ASSERT_CONTAINS(v6, 0); - - // Insert at end works. - auto v7 = MUTATE_K(v4, insert, 6); - ASSERT_CONTAINS(v7, 6); - - // Insert in middle works. - auto v8 = MUTATE_K(v4, insert, 4); - ASSERT_CONTAINS(v8, 4); -} - -TEST(ImmutableSet, InsertViaIterator) { - auto v0 = init_sets<int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_K(v0, insert, 1); - auto v2 = MUTATE_K(v1, insert, 2); - auto v3 = MUTATE_K(v2, insert, 3); - auto v4 = MUTATE_K(v3, insert, 6); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 6); - - // Giving the iterator for an existing element does a noop. - - auto it4_2 = SEARCH(v4, find, 2); - auto v5 = MUTATE_IT_K(v4, insert, it4_2, 2); // enforces no duplicates - ASSERT_CONTAINS(v5, 2); - - // Giving end() as hint works appropriately whether hint is accurate or not. - - auto end5 = END(v5); - auto v6 = MUTATE_IT_K(v5, insert, end5, 2); // enforces no duplicates - ASSERT_CONTAINS(v6, 2); - - auto end6 = END(v6); - auto v7 = MUTATE_IT_K(v6, insert, end6, 4); - ASSERT_CONTAINS(v7, 4); - - auto end7 = END(v7); - auto v8 = MUTATE_IT_K(v7, insert, end7, 7); - ASSERT_CONTAINS(v8, 7); - - // Giving hint from lower_bound works for both existing and new entries. - - auto lb8_2 = SEARCH(v8, lower_bound, 2); - auto v9 = MUTATE_IT_K(v8, insert, lb8_2, 2); // enforces no duplicates - ASSERT_CONTAINS(v9, 2); - - auto lb9_0 = SEARCH(v9, lower_bound, 0); - auto v10 = MUTATE_IT_K(v9, insert, lb9_0, 0); - ASSERT_CONTAINS(v10, 0); - - auto lb10_5 = SEARCH(v10, lower_bound, 5); - auto v11 = MUTATE_IT_K(v10, insert, lb10_5, 5); - ASSERT_CONTAINS(v11, 5); - - auto lb11_8 = SEARCH(v11, lower_bound, 8); - auto v12 = MUTATE_IT_K(v11, insert, lb11_8, 8); - ASSERT_CONTAINS(v12, 8); -} - -TEST(ImmutableSet, Erase) { - auto v0 = init_sets<int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_K(v0, insert, 1); - auto v2 = MUTATE_K(v1, insert, 2); - auto v3 = MUTATE_K(v2, insert, 3); - auto v4 = MUTATE_K(v3, insert, 5); - ASSERT_CONTAINS(v4, 1); - ASSERT_CONTAINS(v4, 2); - ASSERT_CONTAINS(v4, 3); - ASSERT_CONTAINS(v4, 5); - - // Erase on existing key removes value. - auto v5 = MUTATE_K(v4, erase, 1); - ASSERT_NOT_CONTAINS(v5, 1); - - // Erase on non-existent key does nothing. - auto v6 = MUTATE_K(v5, erase, 0); - ASSERT_NOT_CONTAINS(v6, 0); - ASSERT_CONTAINS(v6, 2); - ASSERT_CONTAINS(v6, 3); - ASSERT_CONTAINS(v6, 5); -} - -TEST(ImmutableSet, EraseViaIterator) { - auto v0 = init_sets<int>(); - - // Populate an initial set of values. - auto v1 = MUTATE_K(v0, insert, 1); - auto v2 = MUTATE_K(v1, insert, 2); - auto v3 = MUTATE_K(v2, insert, 3); - auto v4 = MUTATE_K(v3, insert, 5); - auto v5 = MUTATE_K(v4, insert, 6); - ASSERT_CONTAINS(v5, 1); - ASSERT_CONTAINS(v5, 2); - ASSERT_CONTAINS(v5, 3); - ASSERT_CONTAINS(v5, 5); - ASSERT_CONTAINS(v5, 6); - - // Giving the iterator for an existing element erases the value. - - auto it5_2 = SEARCH(v5, find, 2); - auto v6 = MUTATE_IT_K(v5, erase, it5_2, 2); - ASSERT_NOT_CONTAINS(v6, 2); - - // Giving end() as hint works appropriately whether hint is accurate or not. - - auto end6 = END(v6); - auto v7 = MUTATE_IT_K(v6, erase, end6, 3); - ASSERT_NOT_CONTAINS(v7, 3); - - auto end7 = END(v7); - auto v8 = MUTATE_IT_K(v7, erase, end7, 4); - ASSERT_NOT_CONTAINS(v8, 4); - - auto end8 = END(v8); - auto v9 = MUTATE_IT_K(v8, erase, end8, 7); - ASSERT_NOT_CONTAINS(v9, 7); - - // Giving hint from lower_bound works for both existing and non-existing entries. - - auto lb9_5 = SEARCH(v9, lower_bound, 5); - auto v10 = MUTATE_IT_K(v9, erase, lb9_5, 5); - ASSERT_NOT_CONTAINS(v10, 5); - - auto lb10_0 = SEARCH(v10, lower_bound, 0); - auto v11 = MUTATE_IT_K(v10, erase, lb10_0, 0); - - auto lb11_4 = SEARCH(v10, lower_bound, 4); - auto v12 = MUTATE_IT_K(v11, erase, lb11_4, 4); - - auto lb12_7 = SEARCH(v12, lower_bound, 7); - auto v13 = MUTATE_IT_K(v12, erase, lb12_7, 7); - - ASSERT_CONTAINS(v13, 1); - ASSERT_CONTAINS(v13, 6); - ASSERT_NOT_CONTAINS(v13, 0); - ASSERT_NOT_CONTAINS(v13, 2); - ASSERT_NOT_CONTAINS(v13, 3); - ASSERT_NOT_CONTAINS(v13, 4); - ASSERT_NOT_CONTAINS(v13, 5); - ASSERT_NOT_CONTAINS(v13, 7); -} - -TEST(ImmutableSet, ExclusiveOwnership) { - immutable::set<int> v0; - - auto v1 = v0.insert(1); - auto v2 = v1.insert(2); - auto v3 = v2.insert(3); - - ASSERT_TRUE(v1.contains(1)); - ASSERT_TRUE(v2.contains(1)); - ASSERT_TRUE(v3.contains(1)); - - // Claiming exclusive ownership over v3 means v3 will no longer be valid after mutation, but - // older versions should be unperturbed. - auto v4 = std::move(v3).erase(1); - ASSERT_TRUE(v1.contains(1)); - ASSERT_TRUE(v2.contains(1)); - ASSERT_FALSE(v4.contains(1)); -} - -} // namespace -} // namespace mongo diff --git a/src/mongo/util/immutable/immutable_std_comparison_bm.cpp b/src/mongo/util/immutable/immutable_std_comparison_bm.cpp deleted file mode 100644 index 503984ca1d2..00000000000 --- a/src/mongo/util/immutable/immutable_std_comparison_bm.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include <benchmark/benchmark.h> - -#include "mongo/util/immutable/map.h" -#include <map> - -namespace mongo { - -static void BM_std_insert_op(benchmark::State& state) { - std::map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map[i] = i; - } - - for (auto _ : state) { - map[i] = i; - i++; - benchmark::ClobberMemory(); - } -} - -static void BM_std_copy_op(benchmark::State& state) { - std::map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map[i] = i; - } - - std::map<int, int> mapCopy; - for (auto _ : state) { - mapCopy = map; - benchmark::ClobberMemory(); - } -} - -static void BM_std_find_op(benchmark::State& state) { - std::map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map[i] = i; - } - - for (auto _ : state) { - benchmark::DoNotOptimize(map.find(i - 1)); - benchmark::ClobberMemory(); - } -} - -static void BM_std_copy_and_insert_op(benchmark::State& state) { - std::map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map[i] = i; - } - - std::map<int, int> mapCopy; - for (auto _ : state) { - mapCopy = map; - mapCopy[i] = i; - benchmark::ClobberMemory(); - } -} - -static void BM_immutable_insert_op(benchmark::State& state) { - immutable::map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map = std::move(map).set(i, i); - } - - for (auto _ : state) { - map = std::move(map).set(i, i); - i++; - benchmark::ClobberMemory(); - } -} - -static void BM_immutable_copy_op(benchmark::State& state) { - immutable::map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map = std::move(map).set(i, i); - } - - immutable::map<int, int> mapCopy; - for (auto _ : state) { - mapCopy = map; - benchmark::ClobberMemory(); - } -} - -static void BM_immutable_find_op(benchmark::State& state) { - immutable::map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map = std::move(map).set(i, i); - } - - for (auto _ : state) { - benchmark::DoNotOptimize(map.find(i - 1)); - benchmark::ClobberMemory(); - } -} - -static void BM_immutable_copy_and_insert_op(benchmark::State& state) { - immutable::map<int, int> map; - - int64_t i = 0; - for (; i < state.range(0); i++) { - map = std::move(map).set(i, i); - } - - immutable::map<int, int> mapCopy; - for (auto _ : state) { - mapCopy = map.set(i, i); - benchmark::ClobberMemory(); - } -} - -// Run with varying container sizes: [ 8, 16, 32, 64, 128, 256, 512, 1024, 2k, 4k, 8k ]. -BENCHMARK(BM_std_insert_op)->RangeMultiplier(2)->Range(8, 8 << 10); -BENCHMARK(BM_immutable_insert_op)->RangeMultiplier(2)->Range(8, 8 << 10); - -BENCHMARK(BM_std_copy_op)->RangeMultiplier(2)->Range(8, 8 << 10); -BENCHMARK(BM_immutable_copy_op)->RangeMultiplier(2)->Range(8, 8 << 10); - -BENCHMARK(BM_std_find_op)->RangeMultiplier(2)->Range(8, 8 << 10); -BENCHMARK(BM_immutable_find_op)->RangeMultiplier(2)->Range(8, 8 << 10); - -BENCHMARK(BM_std_copy_and_insert_op)->RangeMultiplier(2)->Range(8, 8 << 10); -BENCHMARK(BM_immutable_copy_and_insert_op)->RangeMultiplier(2)->Range(8, 8 << 10); -} // namespace mongo diff --git a/src/mongo/util/immutable/immutable_unordered_test.cpp b/src/mongo/util/immutable/immutable_unordered_test.cpp deleted file mode 100644 index d6e2aa055dd..00000000000 --- a/src/mongo/util/immutable/immutable_unordered_test.cpp +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - - -#include "mongo/unittest/unittest.h" - -#include "mongo/util/immutable/unordered_map.h" -#include "mongo/util/immutable/unordered_set.h" -#include "mongo/util/string_map.h" - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - -namespace mongo { -namespace { - -class UserDefinedKey { -public: - UserDefinedKey() = default; - explicit UserDefinedKey(int val) : a(val) {} - - bool operator==(const UserDefinedKey& rhs) const { - return a == rhs.a; - } - - // Use the Abseil hashing framework - template <typename H> - friend H AbslHashValue(H h, const UserDefinedKey& obj) { - return H::combine(std::move(h), obj.a); - } - -private: - int a = 0; -}; - -TEST(ImmutableUnorderedMap, Basic) { - // Insert some values and verify that the data structure is behaving as expected - immutable::unordered_map<int, int> v0; - auto v1 = v0.set(1, 2); - // Record the pointer to the value '1', verify that this doesn't change after performing more - // inserts - auto v1Val = v1.find(1); - - // Create distinct branches of the history from v1. v0 and v1 should be unaffected - auto v2 = v1.update(1, [](int v) { return v += 1; }); - auto v3 = v1.set(2, 3); - - // Verify that values are as expected - ASSERT_EQ(v0.size(), 0); - - ASSERT_EQ(v1.size(), 1); - ASSERT(v1.find(1)); - ASSERT_EQ(*v1.find(1), 2); - - ASSERT_EQ(v2.size(), 1); - ASSERT(v2.find(1)); - ASSERT_EQ(*v2.find(1), 3); - ASSERT(!v2.find(2)); - - ASSERT_EQ(v3.size(), 2); - ASSERT(v3.find(1)); - ASSERT_EQ(*v3.find(1), 2); - ASSERT(v3.find(2)); - ASSERT_EQ(*v3.find(2), 3); - - // Verify that pointer to v1's value did not change - ASSERT_EQ(v1.find(1), v1Val); -} - -TEST(ImmutableUnorderedMap, UserDefinedType) { - immutable::unordered_map<UserDefinedKey, int> v0; - auto v1 = v0.set(UserDefinedKey(1), 2); - ASSERT(v1.find(UserDefinedKey(1))); -} - -TEST(ImmutableUnorderedMap, HeterogeneousLookup) { - immutable::unordered_map<std::string, int, StringMapHasher, StringMapEq> v0; - auto v1 = v0.set("str", 1); - - // Lookup using StringData without the need to convert to string. - ASSERT(v1.find("str"_sd)); - - // Lookup using pre-hash - StringMapHashedKey hashedKey = StringMapHasher().hashed_key("str"_sd); - ASSERT(v1.find(hashedKey)); -} - -TEST(ImmutableUnorderedMap, BatchWrite) { - immutable::unordered_map<int, int> v0; - - auto transient = v0.transient(); - transient.set(1, 2); - transient.set(2, 3); - immutable::unordered_map<int, int> v1 = transient.persistent(); - - ASSERT(!v0.find(1)); - ASSERT(!v0.find(2)); - ASSERT(v1.find(1)); - ASSERT(v1.find(2)); -} - -TEST(ImmutableUnorderedMap, ExclusiveOwnership) { - immutable::unordered_map<int, int> v0; - - auto v1 = v0.set(1, 1); - auto v2 = v1.set(2, 2); - auto v3 = v2.set(3, 3); - - ASSERT(v1.find(1)); - ASSERT(v2.find(1)); - ASSERT(v3.find(1)); - - // Claiming exclusive ownership over v3 means v3 will no longer be valid after mutation, but - // older versions should be unperturbed. - auto v4 = std::move(v3).erase(1); - ASSERT(v1.find(1)); - ASSERT(v2.find(1)); - ASSERT(!v4.find(1)); -} - -TEST(ImmutableUnorderedSet, Basic) { - // Insert some values and verify that the data structure is behaving as expected - immutable::unordered_set<int> v0; - auto v1 = v0.insert(1); - // Record the pointer to the value '1', verify that this doesn't change after performing more - // inserts - auto v1Val = v1.find(1); - - // Make more versions of the data structure, v2 and v3 are now distinct history branches from - // v1. v0 and v1 should be unaffected - auto v2 = v1.insert(2); - auto v3 = v1.insert(2); - - // Verify that values are as expected - ASSERT_EQ(v0.size(), 0); - - ASSERT_EQ(v1.size(), 1); - ASSERT(v1.find(1)); - ASSERT_EQ(*v1.find(1), 1); - - ASSERT_EQ(v2.size(), 2); - ASSERT(v2.find(1)); - ASSERT_EQ(*v2.find(1), 1); - ASSERT(v2.find(2)); - ASSERT_EQ(*v2.find(2), 2); - - ASSERT_EQ(v3.size(), 2); - ASSERT(v3.find(1)); - ASSERT_EQ(*v3.find(1), 1); - ASSERT(v3.find(2)); - ASSERT_EQ(*v3.find(2), 2); - - // Verify that pointer to v1's value did not change - ASSERT_EQ(v1.find(1), v1Val); - // Key is stored in v2 and v3 with different addresses - ASSERT_NE(v2.find(2), v3.find(2)); -} - -TEST(ImmutableUnorderedSet, UserDefinedType) { - immutable::unordered_set<UserDefinedKey> v0; - auto v1 = v0.insert(UserDefinedKey(1)); - ASSERT(v1.find(UserDefinedKey(1))); -} - -TEST(ImmutableUnorderedSet, HeterogeneousLookup) { - immutable::unordered_set<std::string, StringMapHasher, StringMapEq> v0; - auto v1 = v0.insert("str"); - - // Lookup using StringData without the need to convert to string. - ASSERT(v1.find("str"_sd)); - - // Lookup using pre-hash - StringMapHashedKey hashedKey = StringMapHasher().hashed_key("str"_sd); - ASSERT(v1.find(hashedKey)); -} - -TEST(ImmutableUnorderedSet, BatchWrite) { - immutable::unordered_set<int> v0; - - auto transient = v0.transient(); - transient.insert(1); - transient.insert(2); - immutable::unordered_set<int> v1 = transient.persistent(); - - ASSERT(!v0.find(1)); - ASSERT(!v0.find(2)); - ASSERT(v1.find(1)); - ASSERT(v1.find(2)); -} - -TEST(ImmutableUnorderedSet, ExclusiveOwnership) { - immutable::unordered_set<int> v0; - - auto v1 = v0.insert(1); - auto v2 = v1.insert(2); - auto v3 = v2.insert(3); - - ASSERT(v1.find(1)); - ASSERT(v2.find(1)); - ASSERT(v3.find(1)); - - auto v4 = std::move(v3).erase(1); - ASSERT(v1.find(1)); - ASSERT(v2.find(1)); - ASSERT(!v4.find(1)); -} - -} // namespace -} // namespace mongo diff --git a/src/mongo/util/immutable/immutable_vector_test.cpp b/src/mongo/util/immutable/immutable_vector_test.cpp deleted file mode 100644 index 60329423403..00000000000 --- a/src/mongo/util/immutable/immutable_vector_test.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - - -#include "mongo/unittest/unittest.h" - -#include "mongo/util/immutable/vector.h" - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - -namespace mongo { -namespace { - -class UserDefinedType { -public: - UserDefinedType() = default; - explicit UserDefinedType(int val) : a(val) {} - - bool operator==(const UserDefinedType& rhs) const { - return a == rhs.a; - } - -private: - int a = 0; -}; - -TEST(ImmutableUnorderedMap, Basic) { - // Insert some values and verify that the data structure is behaving as expected - immutable::vector<int> v0; - auto v1 = v0.push_back(1); - // Record the pointer to the value at index '0', verify that this doesn't change after - // performing additional modifications - auto* v1Val = &(*v1.begin()); - - // Create distinct branches of the history from v1. v0 and v1 should be unaffected - auto v2 = v1.update(0, [](int v) { return v += 1; }); - auto v3 = v1.push_back(3); - auto v4 = v1.set(0, 4); - - // Verify that values are as expected - ASSERT_EQ(v0.size(), 0); - - ASSERT_EQ(v1.size(), 1); - ASSERT_EQ(v1.at(0), 1); - - ASSERT_EQ(v2.size(), 1); - ASSERT_EQ(v2.at(0), 2); - - ASSERT_EQ(v3.size(), 2); - ASSERT_EQ(v3.at(0), 1); - ASSERT_EQ(v3.at(1), 3); - - ASSERT_EQ(v4.size(), 1); - ASSERT_EQ(v4.at(0), 4); - - // Verify that pointer to v1's value did not change - ASSERT_EQ(&(*v1.begin()), v1Val); -} - -TEST(ImmutableUnorderedMap, UserDefinedType) { - immutable::vector<UserDefinedType> v0; - auto v1 = v0.push_back(UserDefinedType(1)); - ASSERT(v1.at(0) == UserDefinedType(1)); -} - -TEST(ImmutableUnorderedMap, BatchWrite) { - immutable::vector<int> v0; - - auto transient = v0.transient(); - transient.push_back(1); - transient.push_back(2); - immutable::vector<int> v1 = transient.persistent(); - - ASSERT_EQ(v0.size(), 0); - ASSERT_EQ(v1.size(), 2); - ASSERT_EQ(v1.at(0), 1); - ASSERT_EQ(v1.at(1), 2); -} - -TEST(ImmutableUnorderedMap, ExclusiveOwnership) { - immutable::vector<int> v0; - - auto v1 = v0.push_back(1); - auto v2 = v1.push_back(2); - auto v3 = v2.push_back(3); - - ASSERT_EQ(v1.size(), 1); - ASSERT_EQ(v2.size(), 2); - ASSERT_EQ(v3.size(), 3); - - // Claiming exclusive ownership over v3 means v3 will no longer be valid after mutation, but - // older versions should be unperturbed. - auto v4 = std::move(v3).take(0); - ASSERT_EQ(v1.size(), 1); - ASSERT_EQ(v2.size(), 2); - ASSERT_EQ(v4.size(), 0); -} - -} // namespace -} // namespace mongo diff --git a/src/mongo/util/immutable/map.h b/src/mongo/util/immutable/map.h deleted file mode 100644 index 351cfd560da..00000000000 --- a/src/mongo/util/immutable/map.h +++ /dev/null @@ -1,358 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include <algorithm> - -#include <immer/flex_vector.hpp> - -#include "mongo/util/immutable/details/map.h" -#include "mongo/util/immutable/details/memory_policy.h" - - -namespace mongo::immutable { - -/** - * Immutable ordered dictionary. - * - * Interfaces that "modify" the map are 'const' and return a new version of the map with the - * modifications applied and leaves the original version untouched. - * - * It is optimized for efficient copies and low memory usage when multiple versions of the map exist - * simultaneously at the expense of regular lookups not being as efficient as the regular std - * ordered containers. Suitable for use in code that uses the copy-on-write pattern. - * - * Thread-safety: All methods are const, it is safe to perform modifications that result in new - * versions from multiple threads concurrently. - * - * Memory management: Internal memory management is done using reference counting, memory is free'd - * as references to different versions of the map are released. - * - * Built on top of 'immer::flex_vector'. - * Documentation: 'immer/flex_vector.h' and https://sinusoid.es/immer/ - */ -template <typename Key, typename Value, typename Compare = std::less<Key>> -class map { -public: - using key_type = Key; - using mapped_type = Value; - using value_type = std::pair<Key, Value>; - using storage_type = immer::flex_vector<value_type, detail::MemoryPolicy>; - using iterator = typename storage_type::iterator; - using size_type = typename storage_type::size_type; - using diference_type = std::ptrdiff_t; - using reference = const value_type&; - using const_reference = const value_type&; - using memory_policy_type = detail::MemoryPolicy; - using comp = Compare; - - map() = default; - - struct default_value { - const mapped_type& operator()() const { - static mapped_type v = mapped_type{}; - return v; - } - }; - - struct error_value { - const mapped_type& operator()() const { - throw std::out_of_range{"key not found"}; - } - }; - - bool operator==(const map& other) const { - return _storage == other._storage; - } - - bool operator!=(const map& other) const { - return !(*this == other); - } - - [[nodiscard]] iterator begin() const { - return _storage.begin(); - } - - [[nodiscard]] iterator end() const { - return _storage.end(); - } - - size_t size() const { - return _storage.size(); - } - - /** - * Returns a reference to the value associated with 'key' if one exists, otherwise a default - * constructed Value. - */ - const mapped_type& operator[](const key_type& key) const { - auto it = find(key); - if (it == end()) { - return default_value{}(); - } - return it->second; - } - - /** - * Returns a reference to the value associated with 'key' if one exists, otherwise throws. - */ - const mapped_type& at(const key_type& key) const { - auto it = find(key); - if (it == end()) { - return error_value{}(); - } - return it->second; - } - - /** - * Insert a new 'key' and 'value' pair to the map. - * - * Returns the modified map, or the original if 'key' was already contained. - */ - template <typename K, typename V> - [[nodiscard]] map insert(K&& key, V&& value) const& { - return map{details::map::insert<map<key_type, mapped_type, comp>>( - _storage, std::forward<K>(key), std::forward<V>(value))}; - } - template <typename K, typename V> - [[nodiscard]] map insert(K&& key, V&& value) && { - return map{details::map::insert<map<key_type, mapped_type, comp>>( - std::move(_storage), std::forward<K>(key), std::forward<V>(value))}; - } - - /** - * Insert a new 'key' and 'value' pair to the map. Uses 'it' as a hint. - * - * Returns the modified map, or the original if 'key' was already contained. - */ - template <typename K, typename V> - [[nodiscard]] map insert(iterator it, K&& key, V&& value) const& { - return map{details::map::insert<map<key_type, mapped_type, comp>>( - _storage, it, std::forward<K>(key), std::forward<V>(value))}; - } - template <typename K, typename V> - [[nodiscard]] map insert(iterator it, K&& key, V&& value) && { - return map{details::map::insert<map<key_type, mapped_type, comp>>( - std::move(_storage), it, std::forward<K>(key), std::forward<V>(value))}; - } - - /** - * Sets the value associated with 'key' to 'value', inserting the new pair if 'key' does not - * exist. - * - * Returns the modified map. - */ - template <typename K, typename V> - [[nodiscard]] map set(K&& key, V&& value) const& { - return map{details::map::set<map<key_type, mapped_type, comp>>( - _storage, std::forward<K>(key), std::forward<V>(value))}; - } - template <typename K, typename V> - [[nodiscard]] map set(K&& key, V&& value) && { - return map{details::map::set<map<key_type, mapped_type, comp>>( - std::move(_storage), std::forward<K>(key), std::forward<V>(value))}; - } - - /** - * Sets the value associated with 'key' to 'value', inserting the new pair if 'key' does not - * exist. Treats 'it' as a hint. - * - * Returns the modified map. - */ - template <typename K, typename V> - [[nodiscard]] map set(iterator it, K&& key, V&& value) const& { - return map{details::map::set<map<key_type, mapped_type, comp>>( - _storage, it, std::forward<K>(key), std::forward<V>(value))}; - } - template <typename K, typename V> - [[nodiscard]] map set(iterator it, K&& key, V&& value) && { - return map{details::map::set<map<key_type, mapped_type, comp>>( - std::move(_storage), it, std::forward<K>(key), std::forward<V>(value))}; - } - - /** - * Sets the value associated with 'key' by applying 'valueUpdate' to the existing value, or to a - * default-constructed value if no entry for 'key' exists. - * - * The signature of 'valueUpdate' should be equivalent to - * std::function<mapped_type(const mapped_type&)>. Returns the modified map. - */ - template <typename K, typename U> - [[nodiscard]] map update(K&& key, U&& valueUpdate) const& { - return map{details::map::update<map<key_type, mapped_type, comp>>( - _storage, std::forward<K>(key), std::forward<U>(valueUpdate))}; - } - template <typename K, typename U> - [[nodiscard]] map update(K&& key, U&& valueUpdate) && { - return map{details::map::update<map<key_type, mapped_type, comp>>( - std::move(_storage), std::forward<K>(key), std::forward<U>(valueUpdate))}; - } - - /** - * Updates the value associated with 'key' by applying 'valueUpdate' to the existing value, or - * to a default-constructed value if no entry for 'key' exists. Uses 'it' as a hint. - * - * The signature of 'valueUpdate' should be equivalent to - * std::function<mapped_type(const mapped_type&)>. Returns the modified map. - */ - template <typename K, typename U> - [[nodiscard]] map update(iterator it, K&& key, U&& valueUpdate) const& { - return map{details::map::update<map<key_type, mapped_type, comp>>( - _storage, it, std::forward<K>(key), std::forward<U>(valueUpdate))}; - } - template <typename K, typename U> - [[nodiscard]] map update(iterator it, K&& key, U&& valueUpdate) && { - return map{details::map::update<map<key_type, mapped_type, comp>>( - std::move(_storage), it, std::forward<K>(key), std::forward<U>(valueUpdate))}; - } - - /** - * Updates the value associated with 'key' if it exists by applying 'valueUpdate' to the - * existing value. - * - * The signature of 'valueUpdate' should be equivalent to - * std::function<mapped_type(const mapped_type&)>. Returns the modified map, or the original if - * 'key' does not exist. - */ - template <typename K, typename U> - [[nodiscard]] map update_if_exists(K&& key, U&& valueUpdate) const& { - return map{details::map::update_if_exists<map<key_type, mapped_type, comp>>( - _storage, std::forward<K>(key), std::forward<U>(valueUpdate))}; - } - template <typename K, typename U> - [[nodiscard]] map update_if_exists(K&& key, U&& valueUpdate) && { - return map{details::map::update_if_exists<map<key_type, mapped_type, comp>>( - std::move(_storage), std::forward<K>(key), std::forward<U>(valueUpdate))}; - } - - /** - * Updates the value associated with 'key' if it exists by applying 'valueUpdate' to the - * existing value. Uses 'it' as a hint. - * - * The signature of 'valueUpdate' should be equivalent to - * std::function<mapped_type(const mapped_type&)>. Returns the modified map, or the original if - * 'key' does not exist. - */ - template <typename K, typename U> - [[nodiscard]] map update_if_exists(iterator it, K&& key, U&& valueUpdate) const& { - return map{details::map::update_if_exists<map<key_type, mapped_type, comp>>( - _storage, it, std::forward<K>(key), std::forward<U>(valueUpdate))}; - } - template <typename K, typename U> - [[nodiscard]] map update_if_exists(iterator it, K&& key, U&& valueUpdate) && { - return map{details::map::update_if_exists<map<key_type, mapped_type, comp>>( - std::move(_storage), it, std::forward<K>(key), std::forward<U>(valueUpdate))}; - } - - /** - * Removes 'key' and its associated value from the map. - * - * Returns the modified map, or the original if 'key' does not exist. - */ - template <typename K> - [[nodiscard]] map erase(K&& key) const& { - return map{ - details::map::erase<map<key_type, mapped_type, comp>>(_storage, std::forward<K>(key))}; - } - template <typename K> - [[nodiscard]] map erase(K&& key) && { - return map{details::map::erase<map<key_type, mapped_type, comp>>(std::move(_storage), - std::forward<K>(key))}; - } - - /** - * Removes entry assocated with 'it' from the map. 'it' must match 'key' or it will not be - * erased. - * - * Returns the modified map, or the original if 'it' is equal to 'end()'. - */ - template <typename K> - [[nodiscard]] map erase(iterator it, K&& key) const& { - return map{details::map::erase<map<key_type, mapped_type, comp>>( - _storage, it, std::forward<K>(key))}; - } - template <typename K> - [[nodiscard]] map erase(iterator it, K&& key) && { - return map{details::map::erase<map<key_type, mapped_type, comp>>( - std::move(_storage), it, std::forward<K>(key))}; - } - - /** - * Returns an iterator to the entry for 'key' if it exists, 'end()' otherwise. - * - * Supports heterogeneous lookup. - */ - template <typename SearchKey> - [[nodiscard]] iterator find(const SearchKey& key) const { - return details::map::find<map<key_type, mapped_type, comp>>(_storage, key); - } - - /** - * Returns true if map contains an entry for 'key'. - * - * Supports heterogeneous lookup. - */ - template <typename SearchKey> - bool contains(const SearchKey& key) const { - return find(key) != end(); - } - - /** - * Returns the first the entry greater than or equal to 'key' if one exists, 'end()' otherwise. - * - * Supports heterogeneous lookup. - */ - template <typename SearchKey> - [[nodiscard]] iterator lower_bound(const SearchKey& key) const { - return details::map::lower_bound<map<key_type, mapped_type, comp>>(_storage, key); - } - - /** - * Returns the first the entry strictly greater than 'key' if one exists, 'end()' otherwise. - * - * Supports heterogeneous lookup. - */ - template <typename SearchKey> - [[nodiscard]] iterator upper_bound(const SearchKey& key) const { - return std::upper_bound( - _storage.begin(), - _storage.end(), - key, - [](const SearchKey& a, const value_type& b) -> bool { return comp{}(a, b.first); }); - } - -private: - template <typename S> - explicit map(S&& s) : _storage{std::forward<S>(s)} {} - - storage_type _storage; -}; - -} // namespace mongo::immutable diff --git a/src/mongo/util/immutable/set.h b/src/mongo/util/immutable/set.h deleted file mode 100644 index 24e5183c34f..00000000000 --- a/src/mongo/util/immutable/set.h +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include <algorithm> - -#include <immer/flex_vector.hpp> - -#include "mongo/util/immutable/details/memory_policy.h" -#include "mongo/util/immutable/details/set.h" - -namespace mongo::immutable { - -/** - * Immutable ordered set. - * - * Interfaces that "modify" the set are 'const' and return a new version of the set with the - * modifications applied and leaves the original version untouched. - * - * It is optimized for efficient copies and low memory usage when multiple versions of the set exist - * simultaneously at the expense of regular lookups not being as efficient as the regular std - * ordered containers. Suitable for use in code that uses the copy-on-write pattern. - * - * Thread-safety: All methods are const, it is safe to perform modifications that result in new - * versions from multiple threads concurrently. - * - * Memory management: Internal memory management is done using reference counting, memory is free'd - * as references to different versions of the set are released. - * - * Built on top of 'immer::flex_vector'. - * Documentation: 'immer/flex_vector.h' and https://sinusoid.es/immer/ - */ -template <typename Key, typename Compare = std::less<Key>> -class set { -public: - using key_type = Key; - using storage_type = immer::flex_vector<key_type, detail::MemoryPolicy>; - using iterator = typename storage_type::iterator; - using size_type = typename storage_type::size_type; - using diference_type = std::ptrdiff_t; - using reference = const key_type&; - using const_reference = const key_type&; - using memory_policy_type = detail::MemoryPolicy; - using comp = Compare; - - set() = default; - - bool operator==(const set& other) const { - return _storage == other._storage; - } - - bool operator!=(const set& other) const { - return !(*this == other); - } - - [[nodiscard]] iterator begin() const { - return _storage.begin(); - } - - [[nodiscard]] iterator end() const { - return _storage.end(); - } - - size_t size() const { - return _storage.size(); - } - - /** - * Insert a new 'key' to the set. - * - * Returns the modified set, or the original if 'key' was already contained. - */ - template <typename K> - [[nodiscard]] set insert(K&& key) const& { - return set{details::set::insert<set>(_storage, std::forward<K>(key))}; - } - template <typename K> - [[nodiscard]] set insert(K&& key) && { - return set{details::set::insert<set>(std::move(_storage), std::forward<K>(key))}; - } - - /** - * Insert a new 'key' to the set. Uses 'it' as a hint. - * - * Returns the modified set, or the original if 'key' was already contained. - */ - template <typename K> - [[nodiscard]] set insert(iterator it, K&& key) const& { - return set{details::set::insert<set>(_storage, it, std::forward<K>(key))}; - } - template <typename K> - [[nodiscard]] set insert(iterator it, K&& key) && { - return set{details::set::insert<set>(std::move(_storage), it, std::forward<K>(key))}; - } - - /** - * Removes 'key' from the set. - * - * Returns the modified set, or the original if 'key' does not exist. - */ - template <typename K> - [[nodiscard]] set erase(K&& key) const& { - return set{details::set::erase<set>(_storage, std::forward<K>(key))}; - } - template <typename K> - [[nodiscard]] set erase(K&& key) && { - return set{details::set::erase<set>(std::move(_storage), std::forward<K>(key))}; - } - - /** - * Removes key associated with 'key' from the set. Uses 'it' as a hint. - * - * Returns the modified set, or the original if 'key' does not exist. - */ - template <typename K> - [[nodiscard]] set erase(iterator it, K&& key) const& { - return set{details::set::erase<set>(_storage, it, std::forward<K>(key))}; - } - template <typename K> - [[nodiscard]] set erase(iterator it, K&& key) && { - return set{details::set::erase<set>(std::move(_storage), it, std::forward<K>(key))}; - } - - /** - * Returns an iterator to the element for 'key' if it exists, 'end()' otherwise. - * - * Supports heterogeneous lookup. - */ - template <class SearchKey> - [[nodiscard]] iterator find(const SearchKey& key) const { - return details::set::find<set>(_storage, key); - } - - /** - * Returns true if set contains 'key'. - * - * Supports heterogeneous lookup. - */ - template <class SearchKey> - bool contains(const SearchKey& key) const { - return find(key) != end(); - } - - /** - * Returns the first the element greater than or equal to 'key' if one exists, 'end()' - * otherwise. - * - * Supports heterogeneous lookup. - */ - template <class SearchKey> - [[nodiscard]] iterator lower_bound(const SearchKey& key) const { - return details::set::lower_bound<set>(_storage, key); - } - - /** - * Returns the first the element strictly greater than 'key' if one exists, 'end()' otherwise. - * - * Supports heterogeneous lookup. - */ - template <class SearchKey> - [[nodiscard]] iterator upper_bound(const SearchKey& key) const { - return std::upper_bound(_storage.begin(), _storage.end(), key, comp{}); - } - -private: - template <typename S> - explicit set(S&& s) : _storage{std::forward<S>(s)} {} - - storage_type _storage; -}; - -} // namespace mongo::immutable diff --git a/src/mongo/util/immutable/unordered_map.h b/src/mongo/util/immutable/unordered_map.h deleted file mode 100644 index b024bd5b64a..00000000000 --- a/src/mongo/util/immutable/unordered_map.h +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include <immer/map.hpp> -#include <immer/map_transient.hpp> - -#include "mongo/stdx/trusted_hasher.h" -#include "mongo/util/immutable/details/memory_policy.h" - -namespace mongo::immutable { - -/** - * Immutable unordered hash table. - * - * Interfaces that "modify" the hash table are 'const' and returns a new version of the table with - * the modifications applied and leaves the original version untouched. - * - * It is optimized for efficient copies and low memory usage when multiple versions of the table - * exist simultaneously at the expense of regular lookups not being as efficient as the regular - * stdx unordered containers. Suitable for use in code that uses the copy-on-write pattern. - * - * Thread-safety: All methods are const, it is safe to perform modifications that result in new - * versions from multiple threads concurrently. - * - * Memory management: Internal memory management is done using reference counting, memory is free'd - * as references to different versions of the table are released. - * - * Multiple modifications can be done efficiently using the 'transient()' interface. - * - * Documentation: 'immer/map.h' and https://sinusoid.es/immer/ - */ -template <class K, - class V, - class Hasher = DefaultHasher<K>, - class Eq = absl::container_internal::hash_default_eq<K>> -using unordered_map = immer::map<K, V, EnsureTrustedHasher<Hasher, K>, Eq, detail::MemoryPolicy>; -} // namespace mongo::immutable diff --git a/src/mongo/util/immutable/unordered_set.h b/src/mongo/util/immutable/unordered_set.h deleted file mode 100644 index c5133218007..00000000000 --- a/src/mongo/util/immutable/unordered_set.h +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include <immer/set.hpp> -#include <immer/set_transient.hpp> - -#include "mongo/stdx/trusted_hasher.h" -#include "mongo/util/immutable/details/memory_policy.h" - -namespace mongo::immutable { - -/** - * Immutable unordered hash set. - * - * Interfaces that "modify" the hash set are 'const' and returns a new version of the set with - * the modifications applied and leaves the original version untouched. - * - * It is optimized for efficient copies and low memory usage when multiple versions of the set - * exist simultaneously at the expense of regular lookups not being as efficient as the regular - * stdx unordered containers. Suitable for use in code that uses the copy-on-write - * pattern. - * - * Thread-safety: All methods are const, it is safe to perform modifications that result in new - * versions from multiple threads concurrently. - * - * Memory management: Internal memory management is done using reference counting, memory is free'd - * as references to different versions of the set are released. - * - * Multiple modifications can be done efficiently using the 'transient()' interface. - * - * Documentation: 'immer/set.h' and https://sinusoid.es/immer/ - */ -template <class T, - class Hasher = DefaultHasher<T>, - class Eq = absl::container_internal::hash_default_eq<T>> -using unordered_set = immer::set<T, EnsureTrustedHasher<Hasher, T>, Eq, detail::MemoryPolicy>; -} // namespace mongo::immutable diff --git a/src/mongo/util/immutable/vector.h b/src/mongo/util/immutable/vector.h deleted file mode 100644 index b9cc205575d..00000000000 --- a/src/mongo/util/immutable/vector.h +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include <immer/vector.hpp> -#include <immer/vector_transient.hpp> - -#include "mongo/util/immutable/details/memory_policy.h" - -namespace mongo::immutable { - -/** - * Immutable vector. - * - * Interfaces that "modify" the vector are 'const' and return a new version of the vector with - * the modifications applied and leave the original version untouched. - * - * It is optimized for efficient copies and low memory usage when multiple versions of the vector - * exist simultaneously at the expense of regular lookups not being as efficient as the regular - * std::vector implementation. Suitable for use in code that uses the copy-on-write pattern. - * - * Thread-safety: All methods are const, it is safe to perform modifications that result in new - * versions from multiple threads concurrently. - * - * Memory management: Internal memory management is done using reference counting, memory is free'd - * as references to different versions of the vector are released. - * - * Multiple modifications can be done efficiently using the 'transient()' interface. - * - * Documentation: 'immer/vector.h' and https://sinusoid.es/immer/ - */ -template <class T> -using vector = immer::vector<T, detail::MemoryPolicy>; -} // namespace mongo::immutable diff --git a/src/mongo/util/interruptible.h b/src/mongo/util/interruptible.h index 1e9cc070c64..f7a02e971f9 100644 --- a/src/mongo/util/interruptible.h +++ b/src/mongo/util/interruptible.h @@ -107,6 +107,28 @@ protected: * Returns the equivalent of Date_t::now() + waitFor for the InterruptibleBase's clock */ virtual Date_t getExpirationDateForWaitForValue(Milliseconds waitFor) = 0; + + struct IgnoreInterruptsState { + bool ignoreInterrupts; + DeadlineState deadline; + }; + + /** + * Pushes an ignore interruption critical section into the InterruptibleBase. + * Until an associated popIgnoreInterrupts() is invoked, the InterruptibleBase should ignore + * interruptions related to explicit interruption or previously set deadlines. + * + * Note that new deadlines can be set after this is called, which will again introduce the + * possibility of interruption. + * + * Returns state needed to pop interruption. + */ + virtual IgnoreInterruptsState pushIgnoreInterrupts() = 0; + + /** + * Pops the ignored interruption critical section introduced by push. + */ + virtual void popIgnoreInterrupts(IgnoreInterruptsState iis) = 0; }; /** @@ -172,6 +194,44 @@ private: return DeadlineGuard(*this, deadline, error); } + /** + * An interruption guard provides a region where interruption is ignored. + * + * Note that this causes the deadline to be reset to Date_t::max(), but that it can also be + * subsequently reduced in size after the fact. + */ + class IgnoreInterruptionsGuard { + public: + IgnoreInterruptionsGuard(const IgnoreInterruptionsGuard&) = delete; + IgnoreInterruptionsGuard& operator=(const IgnoreInterruptionsGuard&) = delete; + + IgnoreInterruptionsGuard(IgnoreInterruptionsGuard&& other) + : _interruptible(other._interruptible), _oldState(other._oldState) { + other._interruptible = nullptr; + } + + IgnoreInterruptionsGuard& operator=(IgnoreInterruptionsGuard&&) = delete; + + ~IgnoreInterruptionsGuard() { + if (_interruptible) { + _interruptible->popIgnoreInterrupts(_oldState); + } + } + + private: + friend Interruptible; + + explicit IgnoreInterruptionsGuard(Interruptible& interruptible) + : _interruptible(&interruptible), _oldState(_interruptible->pushIgnoreInterrupts()) {} + + Interruptible* _interruptible; + IgnoreInterruptsState _oldState; + }; + + IgnoreInterruptionsGuard makeIgnoreInterruptionsGuard() { + return IgnoreInterruptionsGuard(*this); + } + public: class WaitListener; @@ -246,6 +306,24 @@ public: } /** + * Invokes the passed callback with an interruption guard active. Additionally handles the + * dance of try/catching the invocation and checking checkForInterrupt with the guard inactive + * (to allow a higher level timeout to override a lower level one, or for top level interruption + * to propagate) + */ + template <typename Callback> + decltype(auto) runWithoutInterruptionExceptAtGlobalShutdown(Callback&& cb) { + try { + const auto guard = makeIgnoreInterruptionsGuard(); + return std::forward<Callback>(cb)(); + } catch (const ExceptionForCat<ErrorCategory::ExceededTimeLimitError>&) { + // May throw replacement exception + checkForInterrupt(); + throw; + } + } + + /** * Raises a AssertionException if this operation is in a killed state. */ void checkForInterrupt() { @@ -494,15 +572,7 @@ class Interruptible::NotInterruptible final : public Interruptible { return stdx::cv_status::no_timeout; } - try { - // If the system clock's time_point's compiler-dependent resolution is higher than - // Date_t's milliseconds, it's possible for the conversion from Date_t to time_point - // to overflow and trigger an exception. We catch that here to maintain the noexcept - // contract. - return cv.wait_until(m, deadline.toSystemTimePoint()); - } catch (const ExceptionFor<ErrorCodes::DurationOverflow>& ex) { - return ex.toStatus(); - } + return cv.wait_until(m, deadline.toSystemTimePoint()); } Date_t getDeadline() const override { @@ -513,6 +583,19 @@ class Interruptible::NotInterruptible final : public Interruptible { return Status::OK(); } + // It's invalid to call the deadline or ignore interruption guards on a possibly noop + // Interruptible. + // + // The noop Interruptible should only be invoked as a default arg at the bottom of the call + // stack (with types that won't modify it's invocation) + IgnoreInterruptsState pushIgnoreInterrupts() override { + MONGO_UNREACHABLE; + } + + void popIgnoreInterrupts(IgnoreInterruptsState) override { + MONGO_UNREACHABLE; + } + DeadlineState pushArtificialDeadline(Date_t deadline, ErrorCodes::Error error) override { MONGO_UNREACHABLE; } diff --git a/src/mongo/util/interruptible_test.cpp b/src/mongo/util/interruptible_test.cpp deleted file mode 100644 index 04f3dea56c7..00000000000 --- a/src/mongo/util/interruptible_test.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (C) 2022-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/platform/mutex.h" -#include "mongo/stdx/condition_variable.h" -#include "mongo/unittest/assert_that.h" -#include "mongo/unittest/unittest.h" -#include "mongo/util/interruptible.h" -#include "mongo/util/time_support.h" - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - -namespace mongo { -namespace { - -class InterruptibleTest : public unittest::Test {}; - -TEST_F(InterruptibleTest, NotInterruptibleWaitForConditionFailsWithOverflowError) { - auto notInterruptible = Interruptible::notInterruptible(); - - auto mutex = MONGO_MAKE_LATCH(); - stdx::condition_variable cv; - stdx::unique_lock<Latch> lk(mutex); - - auto overflowDeadline = Date_t::max() - Milliseconds(1); - - ASSERT_THROWS_CODE(notInterruptible->waitForConditionOrInterruptUntil( - cv, lk, overflowDeadline, [] { return false; }), - DBException, - ErrorCodes::DurationOverflow); -} - -} // namespace - -} // namespace mongo diff --git a/src/mongo/util/murmur3.h b/src/mongo/util/murmur3.h deleted file mode 100644 index 9793cf3ae66..00000000000 --- a/src/mongo/util/murmur3.h +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include <third_party/murmurhash3/MurmurHash3.h> - -#include "mongo/base/data_range.h" -#include "mongo/base/data_type_endian.h" -#include "mongo/base/data_view.h" -#include "mongo/base/string_data.h" - -namespace mongo { - -/** - * Wraps the third-party hash function MurmurHash3. Callers should generally prefer this wrapper (or - * one of the other overloads below) rather than calling into the third-party functions directly. - * This interface is intended to be easier to consume safely. - */ -template <int SizeOfOutput> -inline size_t murmur3(StringData str, size_t seed); - -/** - * Template specialization for hashing a 'StringData' to a 32-bit hash code. - */ -template <> -inline size_t murmur3<4>(StringData str, size_t seed) { - char hash[4]; - MurmurHash3_x86_32(str.rawData(), str.size(), seed, &hash); - return ConstDataView(hash).read<LittleEndian<std::uint32_t>>(); -} - -/** - * Template specialization for hashing a 'StringData' to a 64-bit hash code. Returns the first 8 - * bytes of the 128-bit version of MurmurHash, interpreting these 8 bytes as having a little-endian - * byte order. - */ -template <> -inline size_t murmur3<8>(StringData str, size_t seed) { - char hash[16]; - MurmurHash3_x64_128(str.rawData(), str.size(), seed, hash); - return static_cast<size_t>(ConstDataView(hash).read<LittleEndian<std::uint64_t>>()); -} - -/** - * Overload for callers which use a byte-array representation for the data and thus cannot easily - * represent the input as a 'StringData'. - */ -template <int SizeOfOutput> -inline size_t murmur3(ConstDataRange data, size_t seed); - -/** - * Template specialization for hashing a 'ConstDataRange' to a 32-bit hash code. - */ -template <> -inline size_t murmur3<4>(ConstDataRange data, size_t seed) { - char hash[4]; - MurmurHash3_x86_32(data.data(), data.length(), seed, &hash); - return ConstDataView(hash).read<LittleEndian<std::uint32_t>>(); -} - -/** - * Template specialization for hashing a 'ConstDataRange' to a 64-bit hash code. Returns the first 8 - * bytes of the 128-bit version of MurmurHash, interpreting these 8 bytes as having a little-endian - * byte order. - */ -template <> -inline size_t murmur3<8>(ConstDataRange data, size_t seed) { - char hash[16]; - MurmurHash3_x64_128(data.data(), data.length(), seed, hash); - return static_cast<size_t>(ConstDataView(hash).read<LittleEndian<std::uint64_t>>()); -} - -/** - * Writes the full output of the 128-bit version of MurmurHash to the given 'output' array. - */ -inline void murmur3(StringData str, size_t seed, std::array<char, 16>& output) { - MurmurHash3_x64_128(str.rawData(), str.size(), seed, output.data()); -} - -/** - * 128-bit overload where the input is given as a 'ConstDataRange'. - */ -inline void murmur3(ConstDataRange data, size_t seed, std::array<char, 16>& output) { - MurmurHash3_x64_128(data.data(), data.length(), seed, output.data()); -} - -} // namespace mongo diff --git a/src/mongo/util/net/SConscript b/src/mongo/util/net/SConscript index 272cd9740a1..9aec6ba3150 100644 --- a/src/mongo/util/net/SConscript +++ b/src/mongo/util/net/SConscript @@ -33,9 +33,11 @@ env.Library( source=[ "ssl_options.cpp", ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + ], LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/db/server_options_core', - '$BUILD_DIR/mongo/idl/server_parameter', '$BUILD_DIR/mongo/util/options_parser/options_parser', ] ) @@ -62,13 +64,13 @@ env.Library( 'ssl_options_server.idl', ], LIBDEPS=[ + '$BUILD_DIR/mongo/base', 'ssl_options', ], LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/db/auth/auth_options', '$BUILD_DIR/mongo/db/auth/cluster_auth_mode', '$BUILD_DIR/mongo/db/server_options_core', - '$BUILD_DIR/mongo/idl/server_parameter', '$BUILD_DIR/mongo/util/options_parser/options_parser', ] ) @@ -207,8 +209,6 @@ else: env.Library( target='http_client_impl', source=[ - 'http_client_options.idl', - 'http_client_options.cpp', 'http_client_winhttp.cpp' if env.TargetOSIs('windows') else 'http_client_curl.cpp', ], LIBDEPS=[ diff --git a/src/mongo/util/net/hostandport.cpp b/src/mongo/util/net/hostandport.cpp index f351b51b9b9..eb22852a926 100644 --- a/src/mongo/util/net/hostandport.cpp +++ b/src/mongo/util/net/hostandport.cpp @@ -52,13 +52,6 @@ StatusWith<HostAndPort> HostAndPort::parse(StringData text) { return StatusWith<HostAndPort>(result); } -Status validateHostAndPort(const std::string& hostAndPortStr) { - if (hostAndPortStr.empty()) { - return Status::OK(); - } - return HostAndPort::parse(hostAndPortStr).getStatus(); -} - HostAndPort::HostAndPort() : _port(-1) {} HostAndPort::HostAndPort(StringData text) { diff --git a/src/mongo/util/net/hostandport.h b/src/mongo/util/net/hostandport.h index 3c05c5d0c53..369437fb621 100644 --- a/src/mongo/util/net/hostandport.h +++ b/src/mongo/util/net/hostandport.h @@ -45,12 +45,6 @@ class StatusWith; class StringData; /** - * Validate that a string is either empty or is parseable to a HostAndPort. This is intended for use - * as an IDL validator callback. - */ -Status validateHostAndPort(const std::string& hostAndPortStr); - -/** * Name of a process on the network. * * Composed of some name component, followed optionally by a colon and a numeric port. The name diff --git a/src/mongo/util/net/hostname_canonicalization.cpp b/src/mongo/util/net/hostname_canonicalization.cpp index dd31e4b7015..f11ee345480 100644 --- a/src/mongo/util/net/hostname_canonicalization.cpp +++ b/src/mongo/util/net/hostname_canonicalization.cpp @@ -111,6 +111,7 @@ StatusWith<std::vector<std::string>> getHostFQDNs(std::string hostName, std::vector<std::string> getNameInfoErrors; for (shim_addrinfo* p = info; p; p = p->ai_next) { + std::stringstream getNameInfoError; shim_char host[NI_MAXHOST] = {}; if ((err = shim_getnameinfo( p->ai_addr, p->ai_addrlen, host, sizeof(host), nullptr, 0, NI_NAMEREQD)) == 0) { @@ -129,7 +130,6 @@ StatusWith<std::vector<std::string>> getHostFQDNs(std::string hostName, sin_addr = reinterpret_cast<void*>(&addr_in6->sin6_addr); } - std::stringstream getNameInfoError; if (sin_addr) { invariant(inet_ntop(p->ai_family, sin_addr, ip_str, sizeof(ip_str)) != nullptr); getNameInfoError << ip_str; @@ -138,8 +138,8 @@ StatusWith<std::vector<std::string>> getHostFQDNs(std::string hostName, } getNameInfoError << ": \"" << getAddrInfoStrError(err); - getNameInfoErrors.push_back(getNameInfoError.str()); } + getNameInfoErrors.push_back(getNameInfoError.str()); } if (!getNameInfoErrors.empty()) { @@ -150,8 +150,6 @@ StatusWith<std::vector<std::string>> getHostFQDNs(std::string hostName, "errors"_attr = getNameInfoErrors); } - LOGV2_DEBUG(7317600, 4, "Name info: {results}", "Name info", "results"_attr = results); - // Deduplicate the results list std::sort(results.begin(), results.end()); results.erase(std::unique(results.begin(), results.end()), results.end()); diff --git a/src/mongo/util/net/http_client_curl.cpp b/src/mongo/util/net/http_client_curl.cpp index 9db3c06f73f..fa1834c8bde 100644 --- a/src/mongo/util/net/http_client_curl.cpp +++ b/src/mongo/util/net/http_client_curl.cpp @@ -27,8 +27,6 @@ * it in the license file. */ -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork - #include "mongo/platform/basic.h" #include <cstddef> @@ -49,24 +47,22 @@ #include "mongo/db/commands/server_status.h" #include "mongo/executor/connection_pool.h" #include "mongo/executor/connection_pool_stats.h" -#include "mongo/logv2/log.h" #include "mongo/platform/mutex.h" #include "mongo/stdx/unordered_map.h" #include "mongo/transport/transport_layer.h" #include "mongo/util/alarm.h" #include "mongo/util/alarm_runner_background_thread.h" #include "mongo/util/assert_util.h" -#include "mongo/util/bufreader.h" #include "mongo/util/concurrency/thread_pool.h" #include "mongo/util/functional.h" #include "mongo/util/net/hostandport.h" #include "mongo/util/net/http_client.h" -#include "mongo/util/net/http_client_options.h" #include "mongo/util/processinfo.h" #include "mongo/util/strong_weak_finish_line.h" #include "mongo/util/system_clock_source.h" #include "mongo/util/timer.h" + namespace mongo { namespace { @@ -163,40 +159,20 @@ size_t WriteMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data) { */ size_t ReadMemoryCallback(char* buffer, size_t size, size_t nitems, void* instream) { - auto* bufReader = reinterpret_cast<BufReader*>(instream); + auto* cdrc = reinterpret_cast<ConstDataRangeCursor*>(instream); size_t ret = 0; - if (bufReader->remaining() > 0) { - size_t readSize = - std::min(size * nitems, static_cast<unsigned long>(bufReader->remaining())); - auto buf = bufReader->readBytes(readSize); - memcpy(buffer, buf.rawData(), readSize); + if (cdrc->length() > 0) { + size_t readSize = std::min(size * nitems, cdrc->length()); + memcpy(buffer, cdrc->data(), readSize); + invariant(cdrc->advanceNoThrow(readSize).isOK()); ret = readSize; } return ret; } -/** - * Seek into for data to the remote side - */ -size_t SeekMemoryCallback(void* clientp, curl_off_t offset, int origin) { - - // Curl will call this in readrewind but only to reset the stream to the beginning - // In other protocols (like FTP, SSH) or HTTP resumption they may ask for partial buffers which - // we do not support. - if (offset != 0 || origin != SEEK_SET) { - return CURL_SEEKFUNC_CANTSEEK; - } - - auto* bufReader = reinterpret_cast<BufReader*>(clientp); - - bufReader->rewindToStart(); - - return CURL_SEEKFUNC_OK; -} - struct CurlEasyCleanup { void operator()(CURL* handle) { if (handle) { @@ -220,47 +196,6 @@ long longSeconds(Seconds tm) { return static_cast<long>(durationCount<Seconds>(tm)); } - -StringData enumToString(curl_infotype type) { - switch (type) { - case CURLINFO_TEXT: - return "TEXT"_sd; - case CURLINFO_HEADER_IN: - return "HEADER_IN"_sd; - case CURLINFO_HEADER_OUT: - return "HEADER_OUT"_sd; - case CURLINFO_DATA_IN: - return "DATA_IN"_sd; - case CURLINFO_DATA_OUT: - return "DATA_OUT"_sd; - case CURLINFO_SSL_DATA_IN: - return "SSL_DATA_IN"_sd; - case CURLINFO_SSL_DATA_OUT: - return "SSL_DATA_OUT"_sd; - default: - return "unknown"_sd; - } -} - -int curlDebugCallback(CURL* handle, curl_infotype type, char* data, size_t size, void* clientp) { - switch (type) { - case CURLINFO_TEXT: - case CURLINFO_HEADER_IN: - case CURLINFO_HEADER_OUT: - case CURLINFO_DATA_IN: - case CURLINFO_DATA_OUT: - LOGV2_DEBUG(7661901, - 1, - "Curl", - "type"_attr = enumToString(type), - "message"_attr = StringData(data, size)); - [[fallthrough]]; - - default: - return 0; - } -} - CurlEasyHandle createCurlEasyHandle(Protocols protocol) { CurlEasyHandle handle(curl_easy_init()); uassert(ErrorCodes::InternalError, "Curl initialization failed", handle); @@ -291,10 +226,9 @@ CurlEasyHandle createCurlEasyHandle(Protocols protocol) { } // TODO: CURLOPT_EXPECT_100_TIMEOUT_MS? - if (httpClientOptions.verboseLogging.loadRelaxed()) { - curl_easy_setopt(handle.get(), CURLOPT_VERBOSE, 1); - curl_easy_setopt(handle.get(), CURLOPT_DEBUGFUNCTION, curlDebugCallback); - } + // TODO: consider making this configurable, defaults to stderr + // curl_easy_setopt(handle.get(), CURLOPT_VERBOSE, 1); + // curl_easy_setopt(_handle.get(), CURLOPT_DEBUGFUNCTION , ???); return handle; } @@ -696,7 +630,7 @@ private: curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, longSeconds(_connectTimeout)); - BufReader bufReader(cdr.data(), cdr.length()); + ConstDataRangeCursor cdrc(cdr); switch (method) { case HttpMethod::kGET: uassert(ErrorCodes::BadValue, @@ -711,22 +645,16 @@ private: curl_easy_setopt(handle, CURLOPT_POST, 1); curl_easy_setopt(handle, CURLOPT_READFUNCTION, ReadMemoryCallback); - curl_easy_setopt(handle, CURLOPT_READDATA, &bufReader); - curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, (long)bufReader.remaining()); - - curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, SeekMemoryCallback); - curl_easy_setopt(handle, CURLOPT_SEEKDATA, &bufReader); + curl_easy_setopt(handle, CURLOPT_READDATA, &cdrc); + curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, (long)cdrc.length()); break; case HttpMethod::kPUT: curl_easy_setopt(handle, CURLOPT_POST, 0); curl_easy_setopt(handle, CURLOPT_PUT, 1); curl_easy_setopt(handle, CURLOPT_READFUNCTION, ReadMemoryCallback); - curl_easy_setopt(handle, CURLOPT_READDATA, &bufReader); - curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (long)bufReader.remaining()); - - curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, SeekMemoryCallback); - curl_easy_setopt(handle, CURLOPT_SEEKDATA, &bufReader); + curl_easy_setopt(handle, CURLOPT_READDATA, &cdrc); + curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (long)cdrc.length()); break; default: MONGO_UNREACHABLE; diff --git a/src/mongo/util/net/http_client_options.cpp b/src/mongo/util/net/http_client_options.cpp deleted file mode 100644 index 7e96be222ac..00000000000 --- a/src/mongo/util/net/http_client_options.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/util/net/http_client_options.h" - -namespace mongo { - -HttpClientOptions httpClientOptions; - -} diff --git a/src/mongo/util/net/http_client_options.h b/src/mongo/util/net/http_client_options.h deleted file mode 100644 index ebf4611677a..00000000000 --- a/src/mongo/util/net/http_client_options.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include "mongo/platform/atomic_word.h" - -namespace mongo { -struct HttpClientOptions { - /** - * Boolean flag that indicates whether verbose logging for http clients should be enabled. - * - * Note: only affects new handles. This means that if connection pooling is in use, this will - * not take affect on existing connections. - */ - AtomicWord<bool> verboseLogging; -}; - -extern HttpClientOptions httpClientOptions; -} // namespace mongo diff --git a/src/mongo/util/net/http_client_options.idl b/src/mongo/util/net/http_client_options.idl deleted file mode 100644 index b53b5e5541b..00000000000 --- a/src/mongo/util/net/http_client_options.idl +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (C) 2023-present MongoDB, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the Server Side Public License, version 1, -# as published by MongoDB, Inc. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# Server Side Public License for more details. -# -# You should have received a copy of the Server Side Public License -# along with this program. If not, see -# <http://www.mongodb.com/licensing/server-side-public-license>. -# -# As a special exception, the copyright holders give permission to link the -# code of portions of this program with the OpenSSL library under certain -# conditions as described in each individual source file and distribute -# linked combinations including the program with the OpenSSL library. You -# must comply with the Server Side Public License in all respects for -# all of the code used other than as permitted herein. If you modify file(s) -# with this exception, you may extend this exception to your version of the -# file(s), but you are not obligated to do so. If you do not wish to do so, -# delete this exception statement from your version. If you delete this -# exception statement from all source files in the program, then also delete -# it in the license file. - -global: - cpp_namespace: "mongo" - cpp_includes: - - "mongo/util/net/http_client_options.h" - -server_parameters: - httpVerboseLogging: - description: Boolean flag that indicates whether verbose logging for http clients should be enabled. - set_at: [ startup, runtime ] - default: false - cpp_varname: "httpClientOptions.verboseLogging" diff --git a/src/mongo/util/net/ocsp/ocsp_manager.cpp b/src/mongo/util/net/ocsp/ocsp_manager.cpp index b8d0b2683dd..1524d1da560 100644 --- a/src/mongo/util/net/ocsp/ocsp_manager.cpp +++ b/src/mongo/util/net/ocsp/ocsp_manager.cpp @@ -68,6 +68,7 @@ void OCSPManager::start(ServiceContext* service) { void OCSPManager::shutdown(ServiceContext* service) { get(service)->_pool->shutdown(); + getOCSPManager(service).reset(); } OCSPManager::OCSPManager() { diff --git a/src/mongo/util/net/openssl_init.cpp b/src/mongo/util/net/openssl_init.cpp index 5ef31f1212b..89e5a1c4498 100644 --- a/src/mongo/util/net/openssl_init.cpp +++ b/src/mongo/util/net/openssl_init.cpp @@ -46,12 +46,6 @@ #include <stack> #include <vector> -#if OPENSSL_VERSION_NUMBER > 0x30000000L -#include <openssl/provider.h> -#endif - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork - namespace mongo { namespace { @@ -150,54 +144,21 @@ private: } }; -#if OPENSSL_VERSION_NUMBER > 0x30000000L -#define _SUPPORT_FIPS 1 - -OSSL_PROVIDER* fipsProvider; -OSSL_PROVIDER* baseProvider; - -void initFIPS() { - // OpenSSL 3 has a different FIPS design then previous OpenSSL. To load FIPS, we use the FIPS - // algorithm provider which we load into the "default" library context. - fipsProvider = OSSL_PROVIDER_load(NULL, "fips"); - if (fipsProvider == NULL) { - LOGV2_FATAL_NOTRACE( - 7585801, - "Failed to load OpenSSL 3 FIPS provider. OpenSSL was not compiled with FIPS support.", - "error"_attr = SSLManagerInterface::getSSLErrorMessage(ERR_get_error())); - } - - // Base provide has non-cryptographic algorihms (like encoding/decoding keys) - baseProvider = OSSL_PROVIDER_load(NULL, "base"); - if (baseProvider == NULL) { - LOGV2_FATAL_NOTRACE(7585802, - "Failed to load OpenSSL 3 Base provider", - "error"_attr = - SSLManagerInterface::getSSLErrorMessage(ERR_get_error())); - } -} -#elif defined(MONGO_CONFIG_HAVE_FIPS_MODE_SET) - -#define _SUPPORT_FIPS 1 - -void initFIPS() { +void setupFIPS() { +// Turn on FIPS mode if requested, OPENSSL_FIPS must be defined by the OpenSSL headers +#if defined(MONGO_CONFIG_HAVE_FIPS_MODE_SET) int status = FIPS_mode_set(1); if (!status) { - LOGV2_FATAL_NOTRACE(23173, - "Can't activate FIPS mode", - "error"_attr = - SSLManagerInterface::getSSLErrorMessage(ERR_get_error())); + LOGV2_FATAL(23173, + "can't activate FIPS mode: {error}", + "Can't activate FIPS mode", + "error"_attr = SSLManagerInterface::getSSLErrorMessage(ERR_get_error())); + fassertFailedNoTrace(16703); } -} -#endif - -void setupFIPS() { -// Turn on FIPS mode if requested, OPENSSL_FIPS must be defined by the OpenSSL headers -#if defined(_SUPPORT_FIPS) - initFIPS(); LOGV2(23172, "FIPS 140-2 mode activated"); #else - LOGV2_FATAL_NOTRACE(23174, "this version of mongodb was not compiled with FIPS support"); + LOGV2_FATAL(23174, "this version of mongodb was not compiled with FIPS support"); + fassertFailedNoTrace(17089); #endif } diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp index c784c3792d6..2f4a6fb713f 100644 --- a/src/mongo/util/net/ssl_manager.cpp +++ b/src/mongo/util/net/ssl_manager.cpp @@ -38,7 +38,6 @@ #include <string> #include <vector> -#include "mongo/base/data_view.h" #include "mongo/base/init.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/client/internal_auth.h" @@ -780,6 +779,7 @@ bool SSLConfiguration::isClusterMember(StringData subjectName) const { void SSLConfiguration::getServerStatusBSON(BSONObjBuilder* security) const { security->append("SSLServerSubjectName", _serverSubjectName.toString()); + security->appendBool("SSLServerHasCertificateAuthority", hasCA); security->appendDate("SSLServerCertificateExpirationDate", serverCertificateExpirationDate); } @@ -1048,7 +1048,7 @@ StatusWith<DERToken> DERToken::parse(ConstDataRange cdr, size_t* outLength) { derLength = ConstDataView(lengthBuffer.data()).read<BigEndian<uint64_t>>(); } else { // Length is <= 127 bytes, i.e. short form of length - derLength = ConstDataView(&initialLengthByte).read<uint8_t>(); + derLength = initialLengthByte; } // This is the total length of the TLV and all data diff --git a/src/mongo/util/net/ssl_manager_apple.cpp b/src/mongo/util/net/ssl_manager_apple.cpp index f3e0f4d6652..ec77dc17722 100644 --- a/src/mongo/util/net/ssl_manager_apple.cpp +++ b/src/mongo/util/net/ssl_manager_apple.cpp @@ -1369,15 +1369,20 @@ SSLManagerApple::SSLManagerApple(const SSLParams& params, bool isServer) } } - // If the user has specified --setParameter tlsUseSystemCA=true, then no params.sslCAFile nor - // params.sslClusterCAFile will be defined, and the SSL Manager will fall back to the System CA. if (!params.sslCAFile.empty()) { auto ca = uassertStatusOK(loadPEM(params.sslCAFile, "", kLoadPEMStripKeys)); _clientCA = std::move(ca); + _sslConfiguration.hasCA = _clientCA && ::CFArrayGetCount(_clientCA.get()); + } + + if (!params.sslCertificateSelector.empty() || !params.sslClusterCertificateSelector.empty()) { + // By using the system keychain, we acknowledge it exists. + _sslConfiguration.hasCA = true; } if (!_clientCA) { - // No explicit CA was specified, use the Keychain CA explicitly + // No explicit CA was specified, use the Keychain CA explicitly on client connects, + // even though we're going to pretend it doesn't exist on server. ::CFArrayRef certs = nullptr; uassertOSStatusOK(SecTrustCopyAnchorCertificates(&certs)); _clientCA.reset(certs); @@ -1552,6 +1557,17 @@ Future<SSLPeerInfo> SSLManagerApple::parseAndValidatePeerCertificate( recordTLSVersion(tlsVersionStatus.getValue(), hostForLogging); + /* While we always have a system CA via the Keychain, + * we'll pretend not to in terms of validation if the server + * was started using a PEM file (legacy mode). + * + * When a certificate selector is used, we'll override hasCA to true + * so that the validation path runs anyway. + */ + if (!_sslConfiguration.hasCA && isSSLServer) { + return Future<SSLPeerInfo>::makeReady(SSLPeerInfo(sniName)); + } + const auto badCert = [&](StringData msg, bool warn = false) -> Future<SSLPeerInfo> { if (warn) { LOGV2_WARNING(23209, @@ -1576,7 +1592,7 @@ Future<SSLPeerInfo> SSLManagerApple::parseAndValidatePeerCertificate( return SSLPeerInfo(sniName); } else { if (status == ::errSecSuccess) { - return badCert(str::stream() << "No SSL certificate provided by peer: " + return badCert(str::stream() << "no SSL certificate provided by peer: " << stringFromOSStatus(status), _weakValidation); } else { @@ -1654,14 +1670,11 @@ Future<SSLPeerInfo> SSLManagerApple::parseAndValidatePeerCertificate( return swPeerSubjectName.getStatus(); } const auto peerSubjectName = std::move(swPeerSubjectName.getValue()); - // The cipher will be presented as a number. - ::SSLCipherSuite cipher; - uassertOSStatusOK(::SSLGetNegotiatedCipher(ssl, &cipher)); - - LOGV2_INFO(6723803, - "Accepted TLS connection from peer", - "peerSubjectName"_attr = peerSubjectName, - "cipher"_attr = cipher); + LOGV2_DEBUG(23207, + 2, + "Accepted TLS connection from peer: {peerSubjectName}", + "Accepted TLS connection from peer", + "peerSubjectName"_attr = peerSubjectName); // Server side. if (remoteHost.empty()) { @@ -1874,26 +1887,8 @@ MONGO_INITIALIZER_WITH_PREREQUISITES(SSLManager, ("EndStartupOptionHandling")) kMongoDBRolesOID = ::CFStringCreateWithCString( nullptr, mongodbRolesOID.identifier.c_str(), ::kCFStringEncodingUTF8); - // TODO SERVER-67419 This retry logic is a workaround; reconsider this approach after - // investigation. - constexpr int kMaxRetries = 10; if (!isSSLServer || (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled)) { - for (int i = 0; i < kMaxRetries; i++) { - try { - theSSLManagerCoordinator = new SSLManagerCoordinator(); - return; - } catch (const ExceptionFor<ErrorCodes::InvalidSSLConfiguration>& e) { - bool isRetriableError = nullptr != strstr(e.what(), "No keychain is available."); - if (!isRetriableError || i == kMaxRetries - 1) { - // Rethrow if a different error or we fail on final iteration - throw; - } - LOGV2_INFO(6741800, - "Caught exception during apple SSLManagerCoordinator creation, retrying", - "try"_attr = i, - "error"_attr = e.what()); - } - } + theSSLManagerCoordinator = new SSLManagerCoordinator(); } } diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp index 48461e4120d..f2ddee148c7 100644 --- a/src/mongo/util/net/ssl_manager_openssl.cpp +++ b/src/mongo/util/net/ssl_manager_openssl.cpp @@ -123,7 +123,12 @@ struct X509StackDeleter { } } }; -using UniqueStackOfX509 = std::unique_ptr<STACK_OF(X509), X509StackDeleter>; + +// If we have an X509 Stack that is owned by an internal SSL Object, we need to use this +// deleter. +struct X509StackDeleterNoOp { + void operator()(STACK_OF(X509) * chain) {} +}; // Modulus for Diffie-Hellman parameter 'ffdhe3072' defined in RFC 7919 constexpr std::array<std::uint8_t, 384> ffdhe3072_p = { @@ -319,6 +324,25 @@ X509* X509_OBJECT_get0_X509(const X509_OBJECT* a) { return a->data.x509; } +using UniqueVerifiedChainPolyfill = std::unique_ptr<STACK_OF(X509), X509StackDeleter>; + +STACK_OF(X509) * SSL_get0_verified_chain(SSL* s) { + auto* store = SSL_CTX_get_cert_store(SSL_get_SSL_CTX(s)); + UniqueX509 peer(SSL_get_peer_certificate(s)); + auto* peerChain = SSL_get_peer_cert_chain(s); + + UniqueX509StoreCtx ctx(X509_STORE_CTX_new()); + if (!X509_STORE_CTX_init(ctx.get(), store, peer.get(), peerChain)) { + return nullptr; + } + + if (X509_verify_cert(ctx.get()) <= 0) { + return nullptr; + } + + return X509_STORE_CTX_get1_chain(ctx.get()); +} + const OCSP_CERTID* OCSP_SINGLERESP_get0_id(const OCSP_SINGLERESP* single) { return single->certId; } @@ -351,23 +375,14 @@ static ASN1OID tlsFeatureOID("1.3.6.1.5.5.7.1.24", "tlsfeature", "TLS Feature"); static int const NID_tlsfeature = OBJ_create(tlsFeatureOID.identifier.c_str(), tlsFeatureOID.shortDescription.c_str(), tlsFeatureOID.longDescription.c_str()); -#endif - -UniqueStackOfX509 SSLgetVerifiedChain(SSL* s) { - auto* store = SSL_CTX_get_cert_store(SSL_get_SSL_CTX(s)); - auto* peerChain = SSL_get_peer_cert_chain(s); - UniqueX509 peer(SSL_get_peer_certificate(s)); - UniqueX509StoreCtx ctx(X509_STORE_CTX_new()); - if (!X509_STORE_CTX_init(ctx.get(), store, peer.get(), peerChain)) { - return nullptr; - } +#else +using UniqueVerifiedChainPolyfill = std::unique_ptr<STACK_OF(X509), X509StackDeleterNoOp>; - if (X509_verify_cert(ctx.get()) <= 0) { - return nullptr; - } +#endif - return UniqueStackOfX509(X509_STORE_CTX_get1_chain(ctx.get())); +UniqueVerifiedChainPolyfill SSLgetVerifiedChain(SSL* s) { + return UniqueVerifiedChainPolyfill(SSL_get0_verified_chain(s)); } SSLX509Name convertX509ToSSLX509Name(X509_NAME* x509Name) { @@ -641,7 +656,7 @@ std::vector<std::vector<unsigned char>> convertStackOfX509ToDERVec(STACK_OF(X509 } struct OCSPCacheKey { - OCSPCacheKey(UniqueX509 cert, SSL_CTX* context, UniqueStackOfX509 intermediateCerts) + OCSPCacheKey(UniqueX509 cert, SSL_CTX* context, UniqueVerifiedChainPolyfill intermediateCerts) : peerCert(std::move(cert)), context(context), intermediateCerts(std::move(intermediateCerts)), @@ -2539,8 +2554,6 @@ Status SSLManagerOpenSSL::initSSLContext(SSL_CTX* context, } } - // If the user has specified --setParameter tlsUseSystemCA=true, then no params.sslCAFile nor - // params.sslClusterCAFile will be defined, and the SSL Manager will fall back to the System CA. std::string cafile = params.sslCAFile; if (direction == ConnectionDirection::kIncoming && !params.sslClusterCAFile.empty()) { cafile = params.sslClusterCAFile; @@ -2955,6 +2968,7 @@ Status SSLManagerOpenSSL::_setupCA(SSL_CTX* context, const std::string& caFile) // Set SSL to require peer (client) certificate verification // if a certificate is presented SSL_CTX_set_verify(context, SSL_VERIFY_PEER, &SSLManagerOpenSSL::verify_cb); + _sslConfiguration.hasCA = true; return Status::OK(); } @@ -2979,7 +2993,7 @@ Status SSLManagerOpenSSL::_setupSystemCA(SSL_CTX* context) { << "(default certificate file: " << X509_get_default_cert_file() << ", " << "default certificate path: " << X509_get_default_cert_dir() << ")"}; } - SSL_CTX_set_verify(context, SSL_VERIFY_PEER, &SSLManagerOpenSSL::verify_cb); + return Status::OK(); } @@ -3219,6 +3233,9 @@ Future<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate( recordTLSVersion(tlsVersionStatus.getValue(), hostForLogging); + if (!_sslConfiguration.hasCA && isSSLServer) + return SSLPeerInfo(sni); + UniqueX509 peerCert(SSL_get_peer_certificate(conn)); if (nullptr == peerCert) { // no certificate presented by peer @@ -3271,11 +3288,11 @@ Future<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate( // TODO: check optional cipher restriction, using cert. auto peerSubject = getCertificateSubjectX509Name(peerCert.get()); - const auto cipher = SSL_get_current_cipher(conn); - LOGV2_INFO(6723801, - "Accepted TLS connection from peer", - "peerSubject"_attr = peerSubject, - "cipher"_attr = SSL_CIPHER_get_name(cipher)); + LOGV2_DEBUG(23229, + 2, + "Accepted TLS connection from peer: {peerSubject}", + "Accepted TLS connection from peer", + "peerSubject"_attr = peerSubject); StatusWith<stdx::unordered_set<RoleName>> swPeerCertificateRoles = _parsePeerRoles(peerCert.get()); diff --git a/src/mongo/util/net/ssl_manager_test.cpp b/src/mongo/util/net/ssl_manager_test.cpp index 913c8ba983b..1dd6585f7c1 100644 --- a/src/mongo/util/net/ssl_manager_test.cpp +++ b/src/mongo/util/net/ssl_manager_test.cpp @@ -780,5 +780,6 @@ TEST(SSLManager, InitContextNoSanWarning) { ASSERT_FALSE(isSanWarningWritten(getCapturedTextFormatLogMessages())); } + } // namespace } // namespace mongo diff --git a/src/mongo/util/net/ssl_manager_windows.cpp b/src/mongo/util/net/ssl_manager_windows.cpp index 7169e1eef1f..adf86ae847f 100644 --- a/src/mongo/util/net/ssl_manager_windows.cpp +++ b/src/mongo/util/net/ssl_manager_windows.cpp @@ -1284,9 +1284,11 @@ Status SSLManagerWindows::_loadCertificates(const SSLParams& params) { _clientCertificates[0] = std::get<0>(_clusterPEMCertificate).get(); } - // If the user has specified --setParameter tlsUseSystemCA=true, then no params.sslCAFile nor - // params.sslClusterCAFile will be defined, and the SSL Manager will fall back to the System CA. if (!params.sslCAFile.empty()) { + // SChannel always has a CA even when the user does not specify one + // The openssl implementations uses this to decide if it wants to do certificate validation + // on the server side. + _sslConfiguration.hasCA = true; auto swChain = readCertChains(params.sslCAFile, params.sslCRLFile); if (!swChain.isOK()) { @@ -1349,8 +1351,10 @@ Status SSLManagerWindows::_loadCertificates(const SSLParams& params) { if (!params.sslCAFile.empty()) { LOGV2_WARNING(23271, "Mixing certs from the system certificate store and PEM files. This may " - "produce unexpected results."); + "produced unexpected results."); } + + _sslConfiguration.hasCA = true; } if (_sslCertificate) { @@ -2003,6 +2007,9 @@ Future<SSLPeerInfo> SSLManagerWindows::parseAndValidatePeerCertificate( recordTLSVersion(tlsVersionStatus.getValue(), hostForLogging); + if (!_sslConfiguration.hasCA && isSSLServer) + return Future<SSLPeerInfo>::makeReady(SSLPeerInfo(sni)); + SECURITY_STATUS ss = QueryContextAttributes(ssl, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &cert); if (ss == SEC_E_NO_CREDENTIALS) { // no certificate presented by peer @@ -2062,19 +2069,10 @@ Future<SSLPeerInfo> SSLManagerWindows::parseAndValidatePeerCertificate( return Future<SSLPeerInfo>::makeReady(SSLPeerInfo(sni)); } - SecPkgContext_CipherInfo cipherInfo; - SECURITY_STATUS ssCipher = QueryContextAttributes(ssl, SECPKG_ATTR_CIPHER_INFO, &cipherInfo); - if (ssCipher != SEC_E_OK) { - return Status(ErrorCodes::SSLHandshakeFailed, - str::stream() - << "QueryContextAttributes for connection info failed with" << ssCipher); - } - const auto cipher = std::wstring(cipherInfo.szCipherSuite); - - LOGV2_INFO(6723802, - "Accepted TLS connection from peer", - "peerSubjectName"_attr = peerSubjectName, - "cipher"_attr = toUtf8String(cipher)); + LOGV2_DEBUG(23270, + 2, + "Accepted TLS connection from peer: {peerSubjectName}", + "peerSubjectName"_attr = peerSubjectName); // If this is a server and client and server certificate are the same, log a warning. if (remoteHost.empty() && _sslConfiguration.serverSubjectName() == peerSubjectName) { diff --git a/src/mongo/util/net/ssl_options.h b/src/mongo/util/net/ssl_options.h index 13f7303704e..e58bedcd076 100644 --- a/src/mongo/util/net/ssl_options.h +++ b/src/mongo/util/net/ssl_options.h @@ -93,7 +93,6 @@ struct SSLParams { bool sslFIPSMode = false; // --sslFIPSMode bool sslAllowInvalidCertificates = false; // --sslAllowInvalidCertificates bool sslAllowInvalidHostnames = false; // --sslAllowInvalidHostnames - bool sslUseSystemCA = false; // --setParameter tlsUseSystemCA bool disableNonSSLConnectionLogging = false; // --setParameter disableNonSSLConnectionLogging=true bool disableNonSSLConnectionLoggingSet = false; diff --git a/src/mongo/util/net/ssl_options_server.cpp b/src/mongo/util/net/ssl_options_server.cpp index eb67d49e2c3..4612993df07 100644 --- a/src/mongo/util/net/ssl_options_server.cpp +++ b/src/mongo/util/net/ssl_options_server.cpp @@ -29,7 +29,6 @@ #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kControl -#include "mongo/base/error_codes.h" #include "mongo/platform/basic.h" #include "mongo/util/net/ssl_options.h" @@ -193,27 +192,14 @@ MONGO_STARTUP_OPTIONS_POST(SSLServerOptions)(InitializerContext*) { const auto clusterAuthMode = serverGlobalParams.startupClusterAuthMode; if (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled) { - uassert(ErrorCodes::InvalidOptions, - "Specifying a tlsClusterCAFile requires a tlsCAFile also be specified. See " - "https://dochub.mongodb.org/core/mongod" - "#std-option-mongod.--tlsClusterCAFile for details.", - sslGlobalParams.sslClusterCAFile.empty() || !sslGlobalParams.sslCAFile.empty()); - uassert(ErrorCodes::InvalidOptions, - "The use of both a CA File and the System Certificate store is not supported.", - !sslGlobalParams.sslUseSystemCA || sslGlobalParams.sslCAFile.empty()); - uassert(ErrorCodes::InvalidOptions, - "The use of TLS without specifying a chain of trust is no longer supported. See " - "https://jira.mongodb.org/browse/SERVER-72839 for details.", - sslGlobalParams.sslUseSystemCA || !sslGlobalParams.sslCAFile.empty()); - if (!sslGlobalParams.sslCRLFile.empty() && sslGlobalParams.sslCAFile.empty()) { - uasserted(ErrorCodes::BadValue, - "Specifying a tlsCRLFile requires a tlsCAFile also be specified."); - } bool usingCertifiateSelectors = params.count("net.tls.certificateSelector"); if (sslGlobalParams.sslPEMKeyFile.size() == 0 && !usingCertifiateSelectors) { uasserted(ErrorCodes::BadValue, "need tlsCertificateKeyFile or certificateSelector when TLS is enabled"); } + if (!sslGlobalParams.sslCRLFile.empty() && sslGlobalParams.sslCAFile.empty()) { + uasserted(ErrorCodes::BadValue, "need tlsCAFile with tlsCRLFile"); + } std::string sslCANotFoundError( "No TLS certificate validation can be performed since" diff --git a/src/mongo/util/net/ssl_options_server.idl b/src/mongo/util/net/ssl_options_server.idl index d69af991a11..58b05893a36 100644 --- a/src/mongo/util/net/ssl_options_server.idl +++ b/src/mongo/util/net/ssl_options_server.idl @@ -40,13 +40,6 @@ global: imports: - "mongo/idl/basic_types.idl" -server_parameters: - tlsUseSystemCA: - description: "Use System CA for certificate verification" - set_at: startup - cpp_varname: "sslGlobalParams.sslUseSystemCA" - default: false - configs: "net.tls.tlsOnNormalPorts": description: "Use TLS on configured ports" diff --git a/src/mongo/util/net/ssl_parameters.idl b/src/mongo/util/net/ssl_parameters.idl index 9b3222159bb..c0cc5ca2c63 100644 --- a/src/mongo/util/net/ssl_parameters.idl +++ b/src/mongo/util/net/ssl_parameters.idl @@ -87,7 +87,7 @@ server_parameters: when fetching OCSP Responses for peer certificate set_at: startup cpp_vartype: int - default: 4 + default: 5 cpp_varname: "gTLSOCSPVerifyTimeoutSecs" validator: gte: 1 diff --git a/src/mongo/util/net/ssl_types.h b/src/mongo/util/net/ssl_types.h index e129b549056..6f859ee01aa 100644 --- a/src/mongo/util/net/ssl_types.h +++ b/src/mongo/util/net/ssl_types.h @@ -122,6 +122,7 @@ public: SSLX509Name clientSubjectName; Date_t serverCertificateExpirationDate; + bool hasCA = false; private: SSLX509Name _serverSubjectName; diff --git a/src/mongo/util/pin_code_segments.cpp b/src/mongo/util/pin_code_segments.cpp deleted file mode 100644 index 8d685d86d1d..00000000000 --- a/src/mongo/util/pin_code_segments.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kControl - -#include <elf.h> -#include <link.h> -#include <sys/mman.h> -#include <vector> - -#include "mongo/db/initialize_server_global_state.h" -#include "mongo/db/initialize_server_global_state_gen.h" -#include "mongo/logv2/log.h" -#include "mongo/util/hex.h" -#include "mongo/util/pin_code_segments_params_gen.h" - -namespace mongo { -namespace { - -using ElfWEhdr = ElfW(Ehdr); -using ElfWHalf = ElfW(Half); -using ElfWPhdr = ElfW(Phdr); - -struct CodeSegment { - void* addr; - size_t memSize; -}; - -int appendCodeSegments(dl_phdr_info* info, size_t size, void* data) { - auto segments = reinterpret_cast<std::vector<CodeSegment>*>(data); - for (ElfWHalf i = 0; i < info->dlpi_phnum; ++i) { - const ElfWPhdr& phdr(info->dlpi_phdr[i]); - if (phdr.p_type != PT_LOAD) { - // Code segments must be of LOAD type. - continue; - } - const auto f = phdr.p_flags; - if (!(f & PF_R)) { - // Code segments have read permissions so exclude segments that do not. - continue; - } - if (!(f & PF_X)) { - // Code segments have execute permissions so exclude segments that do not. - continue; - } - if (f & PF_W) { - // Code segments don't have write permissions so exclude segments that do. - continue; - } - segments->push_back( - {reinterpret_cast<void*>(info->dlpi_addr + phdr.p_vaddr), phdr.p_memsz}); - } - return 0; -} - -std::vector<CodeSegment> getCodeSegments() { - std::vector<CodeSegment> segments; - dl_iterate_phdr(appendCodeSegments, &segments); - return segments; -} - -MONGO_INITIALIZER(PinCodeSegments)(InitializerContext*) { - if (!gLockCodeSegmentsInMemory) { - return; - } - LOGV2(7394303, "Pinning code segments"); - size_t lockedMemSize = 0; - auto codeSegments = getCodeSegments(); - for (auto&& segment : codeSegments) { - if (mlock(segment.addr, segment.memSize) != 0) { - auto ec = lastSystemError(); - LOGV2_FATAL( - 7394301, - "Failed to lock code segment, ensure system ulimits are properly configured", - "error"_attr = errorMessage(ec), - "address"_attr = unsignedHex(reinterpret_cast<uintptr_t>(segment.addr)), - "memSize"_attr = segment.memSize, - "memLockedSoFar"_attr = lockedMemSize); - } - lockedMemSize += segment.memSize; - } - LOGV2(7394302, - "Successfully locked code segments into memory", - "numSegments"_attr = codeSegments.size(), - "totalLockedMemSize"_attr = lockedMemSize); -} - -} // namespace -} // namespace mongo diff --git a/src/mongo/util/pin_code_segments_params.idl b/src/mongo/util/pin_code_segments_params.idl deleted file mode 100644 index 2686a22a60a..00000000000 --- a/src/mongo/util/pin_code_segments_params.idl +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (C) 2023-present MongoDB, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the Server Side Public License, version 1, -# as published by MongoDB, Inc. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# Server Side Public License for more details. -# -# You should have received a copy of the Server Side Public License -# along with this program. If not, see -# <http://www.mongodb.com/licensing/server-side-public-license>. -# -# As a special exception, the copyright holders give permission to link the -# code of portions of this program with the OpenSSL library under certain -# conditions as described in each individual source file and distribute -# linked combinations including the program with the OpenSSL library. You -# must comply with the Server Side Public License in all respects for -# all of the code used other than as permitted herein. If you modify file(s) -# with this exception, you may extend this exception to your version of the -# file(s), but you are not obligated to do so. If you do not wish to do so, -# delete this exception statement from your version. If you delete this -# exception statement from all source files in the program, then also delete -# it in the license file. -# - -global: - cpp_namespace: "mongo" - -server_parameters: - lockCodeSegmentsInMemory: - description: >- - When enabled, the server will attempt to call mlock() on all memory ranges corresponding - to the process code segments (including shared libraries) on startup. If any call to mlock() - fails, the server will terminate. When disabled, the server will not attempt to pin any code - segments in memory. This feature is only available on Linux. - set_at: startup - cpp_vartype: bool - cpp_varname: gLockCodeSegmentsInMemory - default: false diff --git a/src/mongo/util/processinfo.h b/src/mongo/util/processinfo.h index 361276d5bf3..942ffc54136 100644 --- a/src/mongo/util/processinfo.h +++ b/src/mongo/util/processinfo.h @@ -113,13 +113,6 @@ public: } /** - * Get the number of CPU sockets - */ - static unsigned getNumCpuSockets() { - return sysInfo().numCpuSockets; - } - - /** * Get the number of cores available. Make a best effort to get the cores for this process. * If that information is not available, get the total number of CPUs. */ @@ -149,16 +142,6 @@ public: } /** - * Get the number of NUMA nodes if NUMA is enabled, or 1 otherwise. - */ - static unsigned long getNumNumaNodes() { - if (sysInfo().hasNuma) { - return sysInfo().numNumaNodes; - } - return 1; - } - - /** * Determine if we need to workaround slow msync performance on Illumos/Solaris */ static bool preferMsyncOverFSync() { @@ -197,11 +180,9 @@ private: unsigned long long memLimit; unsigned numCores; unsigned numPhysicalCores; - unsigned numCpuSockets; unsigned long long pageSize; std::string cpuArch; bool hasNuma; - unsigned numNumaNodes; BSONObj _extraStats; // On non-Solaris (ie, Linux, Darwin, *BSD) kernels, prefer msync. @@ -217,10 +198,8 @@ private: memLimit(0), numCores(0), numPhysicalCores(0), - numCpuSockets(0), pageSize(0), hasNuma(false), - numNumaNodes(0), preferMsyncOverFSync(true) { // populate SystemInfo during construction collectSystemInfo(); @@ -251,6 +230,8 @@ private: ProcessId _pid; + static bool checkNumaEnabled(); + static const SystemInfo& sysInfo() { static ProcessInfo::SystemInfo systemInfo; return systemInfo; diff --git a/src/mongo/util/processinfo_freebsd.cpp b/src/mongo/util/processinfo_freebsd.cpp index fbd49a6d4b9..d13b0295e92 100644 --- a/src/mongo/util/processinfo_freebsd.cpp +++ b/src/mongo/util/processinfo_freebsd.cpp @@ -92,6 +92,10 @@ int getSysctlByNameWithDefault<std::string>(const char* sysctlName, return 0; } +bool ProcessInfo::checkNumaEnabled() { + return false; +} + int ProcessInfo::getVirtualMemorySize() { kvm_t* kd = NULL; int cnt = 0; @@ -160,7 +164,7 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { pageSize = static_cast<unsigned long long>(sysconf(_SC_PAGESIZE)); - hasNuma = false; + hasNuma = checkNumaEnabled(); } void ProcessInfo::getExtraInfo(BSONObjBuilder& info) {} diff --git a/src/mongo/util/processinfo_linux.cpp b/src/mongo/util/processinfo_linux.cpp index 7793011e901..37e3d2ea3fc 100644 --- a/src/mongo/util/processinfo_linux.cpp +++ b/src/mongo/util/processinfo_linux.cpp @@ -407,30 +407,9 @@ public: } /** - * count the number of processor packages - */ - static int getNumCpuSockets() { - std::set<std::string> socketIds; - - CpuInfoParser cpuInfoParser{ - { - {"physical id", [&](const std::string& value) { socketIds.insert(value); }}, - }, - []() {}}; - cpuInfoParser.run(); - - // On ARM64, the "physical id" field is unpopulated, causing there to be 0 sockets found. In - // this case, we default to 1. - return std::max(socketIds.size(), 1ul); - } - - /** * Get some details about the CPU */ - static void getCpuInfo(int& procCount, - std::string& modelString, - std::string& freq, - std::string& features) { + static void getCpuInfo(int& procCount, std::string& freq, std::string& features) { procCount = 0; @@ -442,7 +421,6 @@ public: {"features", [&](const std::string& value) { features = value; }}, #else {"processor", [&](const std::string& value) { procCount++; }}, - {"model name", [&](const std::string& value) { modelString = value; }}, {"cpu MHz", [&](const std::string& value) { freq = value; }}, {"flags", [&](const std::string& value) { features = value; }}, #endif @@ -668,49 +646,6 @@ void ProcessInfo::getExtraInfo(BSONObjBuilder& info) { appendNumber("voluntary_context_switches", ru.ru_nvcsw); appendNumber("involuntary_context_switches", ru.ru_nivcsw); - - LinuxProc p(_pid); - - // Append the number of thread in use - appendNumber("threads", p._nlwp); -} - -/** - * If the process is running with (cc)NUMA enabled, return the number of NUMA nodes. Else, return 0. - */ -unsigned long countNumaNodes() { - bool hasMultipleNodes = false; - bool hasNumaMaps = false; - - try { - hasMultipleNodes = boost::filesystem::exists("/sys/devices/system/node/node1"); - hasNumaMaps = boost::filesystem::exists("/proc/self/numa_maps"); - - if (hasMultipleNodes && hasNumaMaps) { - // proc is populated with numa entries - - // read the second column of first line to determine numa state - // ('default' = enabled, 'interleave' = disabled). Logic from version.cpp's warnings. - std::string line = - LinuxSysHelper::readLineFromFile("/proc/self/numa_maps").append(" \0"); - size_t pos = line.find(' '); - if (pos != std::string::npos && - line.substr(pos + 1, 10).find("interleave") == std::string::npos) { - // interleave not found, count NUMA nodes by finding the highest numbered node file - unsigned long i = 2; - while (boost::filesystem::exists( - std::string(str::stream() << "/sys/devices/system/node/node" << i++))) - ; - return i - 1; - } - } - } catch (boost::filesystem::filesystem_error& e) { - LOGV2(23340, - "WARNING: Cannot detect if NUMA interleaving is enabled. Failed to probe", - "path"_attr = e.path1().string(), - "reason"_attr = e.code().message()); - } - return 0; } /** @@ -719,15 +654,13 @@ unsigned long countNumaNodes() { void ProcessInfo::SystemInfo::collectSystemInfo() { utsname unameData; std::string distroName, distroVersion; - std::string cpuString, cpuFreq, cpuFeatures; + std::string cpuFreq, cpuFeatures; int cpuCount; int physicalCores; - int cpuSockets; std::string verSig = LinuxSysHelper::readLineFromFile("/proc/version_signature"); - LinuxSysHelper::getCpuInfo(cpuCount, cpuString, cpuFreq, cpuFeatures); + LinuxSysHelper::getCpuInfo(cpuCount, cpuFreq, cpuFeatures); LinuxSysHelper::getNumPhysicalCores(physicalCores); - cpuSockets = LinuxSysHelper::getNumCpuSockets(); LinuxSysHelper::getLinuxDistro(distroName, distroVersion); if (uname(&unameData) == -1) { @@ -744,12 +677,9 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { memLimit = LinuxSysHelper::getMemorySizeLimit(); addrSize = sizeof(void*) * CHAR_BIT; numCores = cpuCount; - numPhysicalCores = physicalCores; - numCpuSockets = cpuSockets; pageSize = static_cast<unsigned long long>(sysconf(_SC_PAGESIZE)); cpuArch = unameData.machine; - numNumaNodes = countNumaNodes(); - hasNuma = numNumaNodes; + hasNuma = checkNumaEnabled(); BSONObjBuilder bExtra; bExtra.append("versionString", LinuxSysHelper::readLineFromFile("/proc/version")); @@ -769,16 +699,49 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { bExtra.append("versionSignature", verSig); bExtra.append("kernelVersion", unameData.release); - bExtra.append("cpuString", cpuString); bExtra.append("cpuFrequencyMHz", cpuFreq); bExtra.append("cpuFeatures", cpuFeatures); bExtra.append("pageSize", static_cast<long long>(pageSize)); bExtra.append("numPages", static_cast<int>(sysconf(_SC_PHYS_PAGES))); bExtra.append("maxOpenFiles", static_cast<int>(sysconf(_SC_OPEN_MAX))); + bExtra.append("physicalCores", physicalCores); appendMountInfo(bExtra); _extraStats = bExtra.obj(); } +/** + * Determine if the process is running with (cc)NUMA + */ +bool ProcessInfo::checkNumaEnabled() { + bool hasMultipleNodes = false; + bool hasNumaMaps = false; + + try { + hasMultipleNodes = boost::filesystem::exists("/sys/devices/system/node/node1"); + hasNumaMaps = boost::filesystem::exists("/proc/self/numa_maps"); + } catch (boost::filesystem::filesystem_error& e) { + LOGV2(23340, + "WARNING: Cannot detect if NUMA interleaving is enabled. Failed to probe", + "path"_attr = e.path1().string(), + "reason"_attr = e.code().message()); + return false; + } + + if (hasMultipleNodes && hasNumaMaps) { + // proc is populated with numa entries + + // read the second column of first line to determine numa state + // ('default' = enabled, 'interleave' = disabled). Logic from version.cpp's warnings. + std::string line = LinuxSysHelper::readLineFromFile("/proc/self/numa_maps").append(" \0"); + size_t pos = line.find(' '); + if (pos != std::string::npos && + line.substr(pos + 1, 10).find("interleave") == std::string::npos) + // interleave not found; + return true; + } + return false; +} + } // namespace mongo diff --git a/src/mongo/util/processinfo_openbsd.cpp b/src/mongo/util/processinfo_openbsd.cpp index 071b26b8521..40ad2fc1a70 100644 --- a/src/mongo/util/processinfo_openbsd.cpp +++ b/src/mongo/util/processinfo_openbsd.cpp @@ -96,6 +96,10 @@ int getSysctlByIDWithDefault<std::string>(const int* sysctlID, return 0; } +bool ProcessInfo::checkNumaEnabled() { + return false; +} + int ProcessInfo::getVirtualMemorySize() { kvm_t* kd = NULL; int cnt = 0; @@ -177,7 +181,7 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { pageSize = static_cast<unsigned long long>(sysconf(_SC_PAGESIZE)); - hasNuma = false; + hasNuma = checkNumaEnabled(); } void ProcessInfo::getExtraInfo(BSONObjBuilder& info) {} diff --git a/src/mongo/util/processinfo_osx.cpp b/src/mongo/util/processinfo_osx.cpp index 0cd0cbb4cf6..cb42b570513 100644 --- a/src/mongo/util/processinfo_osx.cpp +++ b/src/mongo/util/processinfo_osx.cpp @@ -182,11 +182,9 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { memSize = getSysctlByName<NumberVal>("hw.memsize"); memLimit = memSize; numCores = getSysctlByName<NumberVal>("hw.ncpu"); // includes hyperthreading cores - numPhysicalCores = getSysctlByName<NumberVal>("machdep.cpu.core_count"); - numCpuSockets = getSysctlByName<NumberVal>("hw.packages"); pageSize = static_cast<unsigned long long>(sysconf(_SC_PAGESIZE)); cpuArch = getSysctlByName<std::string>("hw.machine"); - hasNuma = false; + hasNuma = checkNumaEnabled(); BSONObjBuilder bExtra; bExtra.append("versionString", getSysctlByName<std::string>("kern.version")); @@ -196,6 +194,8 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { "nfsAsync", static_cast<int>(getSysctlByName<NumberVal>("vfs.generic.nfs.client.allow_async"))); bExtra.append("model", getSysctlByName<std::string>("hw.model")); + bExtra.append("physicalCores", + static_cast<int>(getSysctlByName<NumberVal>("machdep.cpu.core_count"))); bExtra.append( "cpuFrequencyMHz", static_cast<int>((getSysctlByName<NumberVal>("hw.cpufrequency") / (1000 * 1000)))); @@ -206,4 +206,8 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { _extraStats = bExtra.obj(); } +bool ProcessInfo::checkNumaEnabled() { + return false; +} + } // namespace mongo diff --git a/src/mongo/util/processinfo_solaris.cpp b/src/mongo/util/processinfo_solaris.cpp index 10f97f9406d..95598132526 100644 --- a/src/mongo/util/processinfo_solaris.cpp +++ b/src/mongo/util/processinfo_solaris.cpp @@ -129,33 +129,6 @@ void ProcessInfo::getExtraInfo(BSONObjBuilder& info) { info.appendNumber("page_faults", static_cast<long long>(p.prusage.pr_majf)); } -bool checkNumaEnabled() { - lgrp_cookie_t cookie = lgrp_init(LGRP_VIEW_OS); - - if (cookie == LGRP_COOKIE_NONE) { - auto ec = lastSystemError(); - LOGV2_WARNING(23362, - "lgrp_init failed: {errnoWithDescription}", - "errnoWithDescription"_attr = errorMessage(ec)); - return false; - } - - ON_BLOCK_EXIT([&] { lgrp_fini(cookie); }); - - int groups = lgrp_nlgrps(cookie); - - if (groups == -1) { - auto ec = lastSystemError(); - LOGV2_WARNING(23363, - "lgrp_nlgrps failed: {errnoWithDescription}", - "errnoWithDescription"_attr = errorMessage(ec)); - return false; - } - - // NUMA machines have more then 1 locality group - return groups > 1; -} - /** * Save a BSON obj representing the host system's details */ @@ -225,4 +198,29 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { _extraStats = bExtra.obj(); } +bool ProcessInfo::checkNumaEnabled() { + lgrp_cookie_t cookie = lgrp_init(LGRP_VIEW_OS); + + if (cookie == LGRP_COOKIE_NONE) { + LOGV2_WARNING(23362, + "lgrp_init failed: {errnoWithDescription}", + "errnoWithDescription"_attr = errnoWithDescription()); + return false; + } + + ON_BLOCK_EXIT([&] { lgrp_fini(cookie); }); + + int groups = lgrp_nlgrps(cookie); + + if (groups == -1) { + LOGV2_WARNING(23363, + "lgrp_nlgrps failed: {errnoWithDescription}", + "errnoWithDescription"_attr = errnoWithDescription()); + return false; + } + + // NUMA machines have more then 1 locality group + return groups > 1; +} + } // namespace mongo diff --git a/src/mongo/util/processinfo_test.cpp b/src/mongo/util/processinfo_test.cpp index ab35a6b7374..050835f97ac 100644 --- a/src/mongo/util/processinfo_test.cpp +++ b/src/mongo/util/processinfo_test.cpp @@ -33,30 +33,13 @@ #include <iostream> #include <vector> -#include "mongo/bson/bsonobj.h" -#include "mongo/bson/bsonobjbuilder.h" #include "mongo/unittest/unittest.h" #include "mongo/util/processinfo.h" using boost::optional; +using mongo::ProcessInfo; -namespace mongo { - -namespace { -using StringMap = std::map<std::string, uint64_t>; - -StringMap toStringMap(BSONObj& obj) { - StringMap map; - - for (const auto& e : obj) { - map[e.fieldName()] = e.numberLong(); - } - - return map; -} - -#define ASSERT_KEY(_key) ASSERT_TRUE(stringMap.find(_key) != stringMap.end()); - +namespace mongo_test { TEST(ProcessInfo, SysInfoIsInitialized) { ProcessInfo processInfo; if (processInfo.supported()) { @@ -64,20 +47,6 @@ TEST(ProcessInfo, SysInfoIsInitialized) { } } -TEST(FTDCProcSysInfo, TestSysInfo) { - auto sysInfo = ProcessInfo(); - BSONObjBuilder builder; - sysInfo.appendSystemDetails(builder); - - BSONObj obj = builder.obj(); - auto stringMap = toStringMap(obj); - ASSERT_KEY("extra"); - - BSONObj extra = obj.getObjectField("extra"); - stringMap = toStringMap(extra); - ASSERT_KEY("cpuString"); -} - TEST(ProcessInfo, GetNumAvailableCores) { #if defined(__APPLE__) || defined(__linux__) || (defined(__sun) && defined(__SVR4)) || \ defined(_WIN32) @@ -90,5 +59,4 @@ TEST(ProcessInfo, GetNumAvailableCores) { TEST(ProcessInfo, GetNumCoresReturnsNonZeroNumberOfProcessors) { ASSERT_GREATER_THAN(ProcessInfo::getNumCores(), 0u); } -} // namespace -} // namespace mongo +} // namespace mongo_test diff --git a/src/mongo/util/processinfo_unknown.cpp b/src/mongo/util/processinfo_unknown.cpp index c57516eaeac..1011c4dbfef 100644 --- a/src/mongo/util/processinfo_unknown.cpp +++ b/src/mongo/util/processinfo_unknown.cpp @@ -51,6 +51,10 @@ int ProcessInfo::getResidentSize() { return -1; } +bool ProcessInfo::checkNumaEnabled() { + return false; +} + void ProcessInfo::SystemInfo::collectSystemInfo() {} void ProcessInfo::getExtraInfo(BSONObjBuilder& info) {} diff --git a/src/mongo/util/processinfo_windows.cpp b/src/mongo/util/processinfo_windows.cpp index 5f3c0514949..5cf7edbdee2 100644 --- a/src/mongo/util/processinfo_windows.cpp +++ b/src/mongo/util/processinfo_windows.cpp @@ -39,7 +39,6 @@ #include "mongo/logv2/log.h" #include "mongo/util/processinfo.h" -#include "mongo/util/text.h" namespace mongo { @@ -92,27 +91,13 @@ LpiRecords getLogicalProcessorInformationRecords() { return lpiRecords; } -struct ParsedProcessorInfo { - int physicalCoreCount; - int numaNodeCount; - int processorPackageCount; -}; - -ParsedProcessorInfo getProcessorInfo() { - ParsedProcessorInfo ppi{0, 0, 0}; +int getPhysicalCores() { + int processorCoreCount = 0; for (auto&& lpi : getLogicalProcessorInformationRecords()) { - switch (lpi.Relationship) { - case RelationProcessorCore: - ppi.physicalCoreCount++; - break; - case RelationNumaNode: - ppi.numaNodeCount++; - break; - case RelationProcessorPackage: - ppi.processorPackageCount++; - } + if (lpi.Relationship == RelationProcessorCore) + processorCoreCount++; } - return ppi; + return processorCoreCount; } } // namespace @@ -249,43 +234,6 @@ bool getFileVersion(const char* filePath, DWORD& fileVersionMS, DWORD& fileVersi return true; } -std::string getCpuString() { - // get descriptive CPU string from registry - HKEY hKey; - LPCWSTR cpuKey = L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; - LPCWSTR valueName = L"ProcessorNameString"; - std::string cpuString; - - // Open the CPU key in the Windows Registry - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, cpuKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { - ScopeGuard guard([hKey] { RegCloseKey(hKey); }); - WCHAR cpuModel[128]; - DWORD bufferSize = sizeof(cpuModel); - - // Retrieve the value of ProcessorNameString - if (RegQueryValueEx(hKey, - valueName, - nullptr, - nullptr, - reinterpret_cast<LPBYTE>(cpuModel), - &bufferSize) == ERROR_SUCCESS) { - cpuString = toUtf8String(cpuModel); - } else { - auto ec = lastSystemError(); - LOGV2_WARNING(7663101, - "Failed to retrieve CPU model name from the registry", - "error"_attr = errorMessage(ec)); - } - - // Close the registry key - } else { - auto ec = lastSystemError(); - LOGV2_WARNING( - 7663102, "Failed to open CPU key in the registry", "error"_attr = errorMessage(ec)); - } - return cpuString; -} - void ProcessInfo::SystemInfo::collectSystemInfo() { BSONObjBuilder bExtra; std::stringstream verstr; @@ -297,19 +245,10 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { GetNativeSystemInfo(&ntsysinfo); addrSize = (ntsysinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? 64 : 32); numCores = ntsysinfo.dwNumberOfProcessors; - auto ppi = getProcessorInfo(); - numPhysicalCores = ppi.physicalCoreCount; - numCpuSockets = ppi.processorPackageCount; - hasNuma = ppi.numaNodeCount > 1; - numNumaNodes = ppi.numaNodeCount; + numPhysicalCores = getPhysicalCores(); pageSize = static_cast<unsigned long long>(ntsysinfo.dwPageSize); bExtra.append("pageSize", static_cast<long long>(pageSize)); - - std::string cpuString = getCpuString(); - if (cpuString != nullptr) { - bExtra.append("cpuString", cpuString); - } - + bExtra.append("physicalCores", static_cast<int>(numPhysicalCores)); // get memory info mse.dwLength = sizeof(mse); @@ -413,7 +352,21 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { osType = "Windows"; osVersion = verstr.str(); + hasNuma = checkNumaEnabled(); _extraStats = bExtra.obj(); } + +bool ProcessInfo::checkNumaEnabled() { + DWORD numaNodeCount = 0; + for (auto&& lpi : getLogicalProcessorInformationRecords()) { + if (lpi.Relationship == RelationNumaNode) + // Non-NUMA systems report a single record of this type. + ++numaNodeCount; + } + + // For non-NUMA machines, the count is 1 + return numaNodeCount > 1; +} + } // namespace mongo diff --git a/src/mongo/util/procparser.cpp b/src/mongo/util/procparser.cpp index a9067b27899..599608c9cc8 100644 --- a/src/mongo/util/procparser.cpp +++ b/src/mongo/util/procparser.cpp @@ -857,110 +857,5 @@ Status parseProcVMStatFile(StringData filename, return parseProcVMStat(keys, swString.getValue(), builder); } -// Here is an example of the type of string it supports: -// -// > cat /proc/pressure/<cpu|io|memory> -// some avg10=0.00 avg60=0.00 avg300=0.14 total=1434509127 -// full avg10=0.00 avg60=0.00 avg300=0.14 total=1035574668 -// -// Note: /proc/pressure/cpu only has 'some' entry -// -Status parseProcPressure(StringData data, BSONObjBuilder* builder) { - using string_split_iterator = boost::split_iterator<StringData::const_iterator>; - - // Split the file by lines. - // token_compress_on means the iterator skips over consecutive '\n'. - for (string_split_iterator lineIt = string_split_iterator( - data.begin(), - data.end(), - boost::token_finder([](char c) { return c == '\n'; }, boost::token_compress_on)); - lineIt != string_split_iterator(); - ++lineIt) { - - StringData line((*lineIt).begin(), (*lineIt).end()); - - // Split the line by spaces and equal signs since these are the delimiters for pressure - // files. token_compress_on means the iterator skips over consecutive ' '. This is needed - // for every line. - string_split_iterator partIt = - string_split_iterator(line.begin(), - line.end(), - boost::token_finder([](char c) { return c == ' ' || c == '='; }, - boost::token_compress_on)); - - // Skip processing this line if we do not have a key. - if (partIt == string_split_iterator()) { - continue; - } - - StringData time((*partIt).begin(), (*partIt).end()); - - ++partIt; - - // Skip processing this line if we only have a key, and no arguments. - if (partIt == string_split_iterator()) { - continue; - } - - // Share of time is either 'some' or 'full'. - if (time != kPressureSomeTime && time != kPressureFullTime) { - return Status(ErrorCodes::FailedToParse, "Couldn't find the share of time"); - } - - // Lookup for 'total' token in the parts. - auto totalIt = std::find_if(partIt, string_split_iterator(), [](const auto& vec) { - return StringData(vec.begin(), vec.end()) == "total"_sd; - }); - - // If 'total' token is not found on the row return an error. - if (totalIt == string_split_iterator()) { - return Status(ErrorCodes::NoSuchKey, "Failed to find 'total' token"); - } - - StringData totalToken((*totalIt).begin(), (*totalIt).end()); - - ++totalIt; - - if (totalIt == string_split_iterator()) { - return Status(ErrorCodes::FailedToParse, "No value found for 'total' token"); - } - - StringData stringValue((*totalIt).begin(), (*totalIt).end()); - - double value; - - if (!NumberParser{}(stringValue, &value).isOK()) { - return Status(ErrorCodes::FailedToParse, - str::stream() << "Couldn't parse '" << stringValue << "' to number"); - } - - *builder << time << BSON("totalMicros" << value); - } - - return Status::OK(); -} - -// Example of BSONObjBuilder created: -// cpu : { -// some : { -// totalMicros : ... -// }, -// fulll: { -// totalMicros : ... -// } -// } -Status parseProcPressureFile(StringData key, StringData filename, BSONObjBuilder* builder) { - auto swString = readFileAsString(filename); - if (!swString.isOK()) { - return swString.getStatus(); - } - - BSONObjBuilder sub(builder->subobjStart(key)); - Status status = parseProcPressure(swString.getValue(), &sub); - sub.doneFast(); - - return status; -} - } // namespace procparser } // namespace mongo diff --git a/src/mongo/util/procparser.h b/src/mongo/util/procparser.h index 2e6eca39211..55f6c3c296f 100644 --- a/src/mongo/util/procparser.h +++ b/src/mongo/util/procparser.h @@ -159,23 +159,5 @@ Status parseProcVMStatFile(StringData filename, BSONObjBuilder* builder); -static const StringData kPressureSomeTime = "some"_sd; -static const StringData kPressureFullTime = "full"_sd; - -/** - * Read a string matching /proc/pressure/<cpu|io|memory> format and write the specified keys in - * builder. - * - * keys - list of keys to check for in the data and output its value. - * data - string to parsee - * builder - BSON output - */ -Status parseProcPressure(StringData data, BSONObjBuilder* builder); - -/** - * Read from file, and write the specified keys in builder. - */ -Status parseProcPressureFile(StringData key, StringData filename, BSONObjBuilder* builder); - } // namespace procparser } // namespace mongo diff --git a/src/mongo/util/procparser_test.cpp b/src/mongo/util/procparser_test.cpp index 259243264ef..04edee43380 100644 --- a/src/mongo/util/procparser_test.cpp +++ b/src/mongo/util/procparser_test.cpp @@ -34,14 +34,12 @@ #include "mongo/util/procparser.h" #include <boost/filesystem.hpp> -#include <fcntl.h> #include <map> #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/logv2/log.h" #include "mongo/unittest/unittest.h" -#include "mongo/util/processinfo.h" namespace mongo { @@ -82,25 +80,6 @@ StringMap toNestedStringMap(BSONObj& obj) { return map; } -bool isPSISupported(StringData filename) { - int fd = open(filename.toString().c_str(), 0); - if (fd == -1) { - return false; - } - ScopeGuard scopedGuard([fd] { close(fd); }); - - std::array<char, 1> buf; - - while (read(fd, buf.data(), buf.size()) == -1) { - auto ec = lastPosixError(); - if (ec == posixError(EOPNOTSUPP)) { - return false; - } - ASSERT_EQ(ec, posixError(EINTR)); - } - return true; -} - #define ASSERT_KEY(_key) ASSERT_TRUE(stringMap.find(_key) != stringMap.end()); #define ASSERT_NO_KEY(_key) ASSERT_TRUE(stringMap.find(_key) == stringMap.end()); #define ASSERT_KEY_AND_VALUE(_key, _value) ASSERT_EQUALS(stringMap.at(_key), _value); @@ -134,11 +113,6 @@ bool isPSISupported(StringData filename) { ASSERT_OK(procparser::parseProcVMStat(_keys, _x, &builder)); \ auto obj = builder.obj(); \ auto stringMap = toStringMap(obj); -#define ASSERT_PARSE_PRESSURE(_x) \ - BSONObjBuilder builder; \ - ASSERT_OK(procparser::parseProcPressure(_x, &builder)); \ - auto obj = builder.obj(); \ - auto stringMap = toStringMap(obj); TEST(FTDCProcStat, TestStat) { @@ -904,112 +878,5 @@ TEST(FTDCProcVMStat, TestLocalNonExistentVMStat) { ASSERT_NOT_OK(procparser::parseProcVMStatFile("/proc/does_not_exist", keys, &builder)); } -TEST(FTDCProcPressure, TestSuccess) { - // Normal cases - { - ASSERT_PARSE_PRESSURE( - "some avg10=0.10 avg60=6.50 avg300=1.00 total=14\nfull avg10=2.30 " - "avg60=0.00 avg300=0.14 total=10"); - ASSERT(obj["some"]["totalMicros"].Double() == 14); - - ASSERT(obj["full"]["totalMicros"].Double() == 10); - } - { - ASSERT_PARSE_PRESSURE("some avg10=0.10 avg60=6.50 avg300=1.00 total=14"); - ASSERT(obj["some"]["totalMicros"].Double() == 14); - - ASSERT(!obj["full"]); - } - { - ASSERT_PARSE_PRESSURE( - "some avg10=0.10 avg60=6.50 avg300=1.00 total=14\nfull avg10=2.30 " - "avg60=0.00 avg300=0.14 total=10"); - ASSERT(obj["some"]["totalMicros"].Double() == 14); - - ASSERT(obj["full"]["totalMicros"].Double() == 10); - } -} - -TEST(FTDCProcPressure, TestFailure) { - // Failure cases - { - BSONObjBuilder builder; - ASSERT_NOT_OK(procparser::parseProcPressureFile("", "", &builder)); - } - - { - BSONObjBuilder builder; - ASSERT_NOT_OK( - procparser::parseProcPressureFile("cpu", "/proc/non-existent-file", &builder)); - } - - // 'total' is not found in the data given. - { - BSONObjBuilder builder; - ASSERT_NOT_OK( - procparser::parseProcPressure("some avg10=0.10 avg60=6.50 avg300=1.00", &builder)); - } - - // 'total' is not found in one of the rows. - { - BSONObjBuilder builder; - ASSERT_NOT_OK( - procparser::parseProcPressure("some avg10=0.10 avg60=6.50 avg300=1.00\nfull avg10=2.30 " - "avg60=0.00 avg300=0.14 total=10", - &builder)); - } - - // 'total' is not given a valid number value. - { - BSONObjBuilder builder; - ASSERT_NOT_OK(procparser::parseProcPressure( - "some avg10=0.10 avg60=6.50 avg300=1.00 total=invalid", &builder)); - } -} - -TEST(FTDCProcPressure, TestLocalPressureInfo) { - if (isPSISupported("/proc/pressure/cpu")) { - BSONObjBuilder builder; - - ASSERT_OK(procparser::parseProcPressureFile("cpu", "/proc/pressure/cpu", &builder)); - - BSONObj obj = builder.obj(); - ASSERT(obj.hasField("cpu")); - ASSERT(obj["cpu"]["some"]); - ASSERT(obj["cpu"]["some"]["totalMicros"]); - - // After linux kernel 5.13, /proc/pressure/cpu includes 'full' filled with 0. - ASSERT(!obj["cpu"]["full"] || obj["cpu"]["full"]["totalMicros"].Double() == 0); - } - - if (isPSISupported("/proc/pressure/memory")) { - BSONObjBuilder builder; - - ASSERT_OK(procparser::parseProcPressureFile("memory", "/proc/pressure/memory", &builder)); - - BSONObj obj = builder.obj(); - ASSERT(obj.hasField("memory")); - ASSERT(obj["memory"]["some"]); - ASSERT(obj["memory"]["some"]["totalMicros"]); - - ASSERT(obj["memory"]["full"]); - ASSERT(obj["memory"]["full"]["totalMicros"]); - } - - if (isPSISupported("/proc/pressure/io")) { - BSONObjBuilder builder; - - ASSERT_OK(procparser::parseProcPressureFile("io", "/proc/pressure/io", &builder)); - - BSONObj obj = builder.obj(); - ASSERT(obj.hasField("io")); - ASSERT(obj["io"]["some"]); - ASSERT(obj["io"]["some"]["totalMicros"]); - - ASSERT(obj["io"]["full"]); - ASSERT(obj["io"]["full"]["totalMicros"]); - } -} - } // namespace } // namespace mongo diff --git a/src/mongo/util/scoped_unlock.h b/src/mongo/util/scoped_unlock.h deleted file mode 100644 index af9b04b9e0b..00000000000 --- a/src/mongo/util/scoped_unlock.h +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (C) 2022-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include "mongo/stdx/mutex.h" -#include "mongo/util/assert_util.h" - - -namespace mongo { -/** - * RAII object that unlocks a unique_lock type on construction, and relocks it on destruction. The - * unique_lock must be locked when it is given to ScopedUnlock. - */ -template <typename T> -class ScopedUnlock { -public: - /** - * Construct a new Scoped Unlock object. - * Unique_locks passed into this constructor must be locked, or an invariant failure will be - * thrown. - */ - explicit ScopedUnlock(stdx::unique_lock<T>& lock) : _lock(lock) { - invariant(_lock.owns_lock(), "Locks in ScopedUnlock must be locked on initialization."); - _lock.unlock(); - } - - ~ScopedUnlock() { - if (!_dismissed) { - _lock.lock(); - } - } - - ScopedUnlock(const ScopedUnlock&) = delete; - ScopedUnlock(ScopedUnlock&&) = delete; - ScopedUnlock& operator=(const ScopedUnlock&) = delete; - ScopedUnlock& operator=(ScopedUnlock&&) = delete; - - /** A dismissed ScopedUnlock does not lock on destruction. */ - void dismiss() noexcept { - _dismissed = true; - } - -private: - stdx::unique_lock<T>& _lock; - bool _dismissed = false; -}; - -} // namespace mongo diff --git a/src/mongo/util/scoped_unlock_test.cpp b/src/mongo/util/scoped_unlock_test.cpp deleted file mode 100644 index 20090e0961c..00000000000 --- a/src/mongo/util/scoped_unlock_test.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (C) 2022-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/util/scoped_unlock.h" - -#include "mongo/platform/mutex.h" -#include "mongo/unittest/death_test.h" -#include "mongo/unittest/unittest.h" - -namespace mongo { -namespace { -TEST(ScopedUnlockTest, Relocked) { - Mutex mutex; - stdx::unique_lock lk(mutex); - - { ScopedUnlock scopedUnlock(lk); } - - ASSERT(lk.owns_lock()) << "ScopedUnlock should relock on destruction"; -} - -TEST(ScopedUnlockTest, Unlocked) { - Mutex mutex; - stdx::unique_lock<Mutex> lk(mutex); - - ScopedUnlock scopedUnlock(lk); - - ASSERT_FALSE(lk.owns_lock()) << "ScopedUnlock should unlock on construction"; -} - -TEST(ScopedUnlockTest, Dismissed) { - Mutex mutex; - stdx::unique_lock<Mutex> lk(mutex); - - { - ScopedUnlock scopedUnlock(lk); - scopedUnlock.dismiss(); - } - - ASSERT_FALSE(lk.owns_lock()) << "ScopedUnlock should not relock on destruction if dismissed"; -} - -DEATH_TEST(ScopedUnlockTest, - InitUnlocked, - "Locks in ScopedUnlock must be locked on initialization.") { - Mutex mutex; - stdx::unique_lock<Mutex> lk(mutex); - lk.unlock(); - - ScopedUnlock scopedUnlock(lk); -} -} // namespace -} // namespace mongo diff --git a/src/mongo/util/shared_buffer_fragment.h b/src/mongo/util/shared_buffer_fragment.h index d94acd096aa..05f453c9f60 100644 --- a/src/mongo/util/shared_buffer_fragment.h +++ b/src/mongo/util/shared_buffer_fragment.h @@ -32,7 +32,6 @@ #include "mongo/util/shared_buffer.h" #include <functional> -#include <vector> namespace mongo { @@ -44,7 +43,7 @@ class SharedBufferFragment { public: SharedBufferFragment() : _offset(0), _size(0) {} explicit SharedBufferFragment(SharedBuffer buffer, size_t size) - : SharedBufferFragment(std::move(buffer), 0, size) {} + : _buffer(std::move(buffer)), _offset(0), _size(size) {} explicit SharedBufferFragment(SharedBuffer buffer, ptrdiff_t offset, size_t size) : _buffer(std::move(buffer)), _offset(offset), _size(size) {} @@ -87,14 +86,10 @@ private: size_t _size; }; + /** * Builder of SharedBufferFragment where multiple fragments are using different parts of the same - * underlying buffer or multiple buffers. Can only build one fragment at a time. - * - * Warning: This builder will hold references to all allocated buffers and will not release them - * until freeUnused() is called. Memory is not reused. This means that failing to call this function - * will result in an unbounded amount of memory usage for the lifetime of the builder. Even after - * this builder is destructed, SharedBufferFragments can prevent memory from being freed. + * underlying buffer. Can only build one fragment at a time */ class SharedBufferFragmentBuilder { public: @@ -104,9 +99,6 @@ public: size_t blockSize, GrowStrategy growStrategy = DoubleGrowStrategy(kDefaultMaxBlockSize)) : _offset(0), _blockSize(blockSize), _growStrategy(growStrategy) {} - SharedBufferFragmentBuilder(SharedBufferFragmentBuilder&& other) = default; - SharedBufferFragmentBuilder& operator=(SharedBufferFragmentBuilder&& other) = default; - struct ConstantGrowStrategy { size_t operator()(size_t current) const { return current; @@ -127,20 +119,13 @@ public: // May only be called if we are not currently building a fragment SharedBufferFragmentBuilder& start(size_t initialSize) { invariant(!_inUse); - if (!_buffer.isShared()) { - // Since there are no fragments sharing with this buffer, we can reset the offset to 0 - // to reuse unused space. - _offset = 0; - } - if (_buffer.capacity() < (_offset + initialSize)) { - // If the capacity is 0, this is our initial allocation and we should not use the grow - // strategy. + // If capacity is 0, then this is our initial allocation and we should not use the grow + // strategy if (_buffer.capacity() > 0) _blockSize = _growStrategy(_blockSize); - size_t allocSize = std::max(_blockSize, initialSize); - _buffer = _alloc(std::move(_buffer), allocSize); + _buffer = SharedBuffer::allocate(allocSize); _offset = 0; } _inUse = true; @@ -153,18 +138,18 @@ public: invariant(_inUse); auto currentCapacity = capacity(); if (currentCapacity < size) { - // If the capacity is 0, this is our initial allocation and we should not use the grow - // strategy. - if (currentCapacity > 0) { - _blockSize = _growStrategy(_blockSize); - } + _blockSize = _growStrategy(_blockSize); size_t allocSize = std::max(_blockSize, size); - if (_buffer) { - _buffer = _realloc(std::move(_buffer), _offset, currentCapacity, allocSize); - } else { - _buffer = _alloc(std::move(_buffer), allocSize); - } + // If nothing else is using the internal buffer it would be safe to use realloc. But as + // this potentially is a large buffer realloc would need copy all of it as it doesn't + // know how much is actually used. So we create a new buffer in all cases and reset the + // offset to 0. We only need to copy the memory of the fragment we are currently + // building. + auto newBuffer = SharedBuffer::allocate(allocSize); + if (_buffer) + memcpy(newBuffer.get(), _buffer.get() + _offset, currentCapacity); + _buffer = std::move(newBuffer); _offset = 0; } } @@ -206,38 +191,13 @@ public: return _inUse; } - // Returns the memory used by all allocated buffers that are being tracked. - size_t memUsage() { - return _memUsage; - } - - // Frees all unreferenced buffers except for the most recently allocated one. The caller must - // ensure that no references to any shared buffers remain to maintain useful memory usage - // information. - void freeUnused(); - private: - SharedBuffer _alloc(SharedBuffer&& existing, size_t allocSize) { - return _realloc(std::move(existing), 0, 0, allocSize); - } - - SharedBuffer _realloc(SharedBuffer&& existing, - size_t offset, - size_t existingSize, - size_t newSize); - - // The current working buffer of this builder. SharedBuffer _buffer; ptrdiff_t _offset; size_t _blockSize; GrowStrategy _growStrategy; bool _inUse{false}; - - // This is a list of old buffers that may still be in use by other fragments. Counts towards - // total memory usage and buffers must be freed by calling using freeUnused() when buffers are - // no longer needed. - std::vector<SharedBuffer> _activeBuffers; - size_t _memUsage = 0; }; + } // namespace mongo diff --git a/src/mongo/util/shared_buffer_fragment_builder.cpp b/src/mongo/util/shared_buffer_fragment_builder.cpp deleted file mode 100644 index 06dbda2352b..00000000000 --- a/src/mongo/util/shared_buffer_fragment_builder.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (C) 2024-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/util/shared_buffer_fragment.h" - -namespace mongo { - -void SharedBufferFragmentBuilder::freeUnused() { - if (_activeBuffers.empty()) { - return; - } - - // Normally all buffers are expected to no longer be shared and can be freed immediately, - // however, the last buffer may still be shared with the owning SharedBufferFragmentBuilder. - auto it = std::remove_if( - _activeBuffers.begin(), _activeBuffers.end(), [](auto&& buf) { return !buf.isShared(); }); - _activeBuffers.erase(it, _activeBuffers.end()); - - // Recalculate memory used by active buffers. - size_t remaining = _buffer.capacity(); - for (auto&& buf : _activeBuffers) { - remaining += buf.capacity(); - } - _memUsage = remaining; -} - -SharedBuffer SharedBufferFragmentBuilder::_realloc(SharedBuffer&& existing, - size_t offset, - size_t existingSize, - size_t newSize) { - // If nothing else is using the internal buffer it would be safe to use realloc. But as - // this potentially is a large buffer realloc would need copy all of it as it doesn't - // know how much is actually used. So we create a new buffer in all cases - auto newBuffer = SharedBuffer::allocate(newSize); - _memUsage += newBuffer.capacity(); - - // When existingSize is 0 we may be in an initial alloc(). - if (existing && existingSize) { - memcpy(newBuffer.get(), existing.get() + offset, existingSize); - } - - // If this buffer is actively used somewhere, we'll need to keep a reference to it for - // tracking memory usage since there may be other fragments that are also holding onto a - // reference. Otherwise, we let it get freed. Callers will have to take care to clean up - // these shared references regularly using freeUnused(). - if (existing.isShared()) { - _activeBuffers.push_back(std::move(existing)); - } else { - _memUsage -= existing.capacity(); - } - return newBuffer; -} -} // namespace mongo diff --git a/src/mongo/util/shared_buffer_test.cpp b/src/mongo/util/shared_buffer_test.cpp deleted file mode 100644 index 7d0eefac43d..00000000000 --- a/src/mongo/util/shared_buffer_test.cpp +++ /dev/null @@ -1,426 +0,0 @@ -/** - * Copyright (C) 2019-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/base/string_data.h" -#include "mongo/util/shared_buffer.h" -#include "mongo/util/shared_buffer_fragment.h" - -#include "mongo/unittest/unittest.h" - -namespace mongo { -namespace { - -using SharedBufferTest = unittest::Test; - -TEST_F(SharedBufferTest, ReallocOrCopyNull) { - SharedBuffer buf; - ASSERT_EQ(buf.capacity(), 0u); - ASSERT(!buf); - ASSERT(!buf.isShared()); - buf.reallocOrCopy(10); - ASSERT(buf); - ASSERT(!buf.isShared()); - ASSERT_EQ(buf.capacity(), 10u); -} - -TEST_F(SharedBufferTest, ReallocOrCopyNullShared) { - // null SharedBuffers are never considered "shared", even when copied. - SharedBuffer buf; - const SharedBuffer sharer = buf; - ASSERT_EQ(buf.capacity(), 0u); - ASSERT(!buf); - ASSERT(!buf.isShared()); - buf.reallocOrCopy(10); - ASSERT(buf); - ASSERT(!buf.isShared()); - ASSERT_EQ(buf.capacity(), 10u); - ASSERT_EQ(sharer.capacity(), 0u); -} - -SharedBuffer makeBuffer() { - SharedBuffer buf = SharedBuffer::allocate(4); - memcpy(buf.get(), "foo", 4); - return buf; -} - -TEST_F(SharedBufferTest, ReallocOrCopyGrow) { - SharedBuffer buf = makeBuffer(); - ASSERT_EQ(buf.capacity(), 4u); - ASSERT(buf); - ASSERT(!buf.isShared()); - buf.reallocOrCopy(10); - ASSERT(buf); - ASSERT(!buf.isShared()); - ASSERT_EQ(buf.capacity(), 10u); - ASSERT_EQ("foo"_sd, buf.get()); -} - -TEST_F(SharedBufferTest, ReallocOrCopyGrowShared) { - SharedBuffer buf = makeBuffer(); - const SharedBuffer sharer = buf; - ASSERT_EQ(buf.capacity(), 4u); - ASSERT(buf); - ASSERT(buf.isShared()); - buf.reallocOrCopy(10); - ASSERT(buf); - ASSERT(!buf.isShared()); - ASSERT_EQ(buf.capacity(), 10u); - ASSERT_EQ(sharer.capacity(), 4u); - ASSERT_EQ("foo"_sd, buf.get()); - ASSERT_EQ("foo"_sd, sharer.get()); - ASSERT_NE(buf.get(), sharer.get()); -} - -TEST_F(SharedBufferTest, ReallocOrCopyShrink) { - SharedBuffer buf = makeBuffer(); - ASSERT_EQ(buf.capacity(), 4u); - ASSERT(buf); - ASSERT(!buf.isShared()); - // The buffer is already at least 1 byte. - buf.reallocOrCopy(1); - ASSERT(buf); - // We copy it anyway. - ASSERT(!buf.isShared()); - ASSERT_EQ(buf.capacity(), 1u); - ASSERT_EQ('f', buf.get()[0]); -} - -TEST_F(SharedBufferTest, ReallocOrCopyShrinkShared) { - SharedBuffer buf = makeBuffer(); - const SharedBuffer sharer = buf; - ASSERT_EQ(buf.capacity(), 4u); - ASSERT(buf); - ASSERT(buf.isShared()); - // The buffer is already at least 1 byte. - buf.reallocOrCopy(1); - ASSERT(buf); - // We copy it anyway. - ASSERT(!buf.isShared()); - ASSERT_EQ(buf.capacity(), 1u); - ASSERT_EQ(sharer.capacity(), 4u); - ASSERT_EQ('f', buf.get()[0]); - ASSERT_EQ("foo"_sd, sharer.get()); - ASSERT_NE(buf.get(), sharer.get()); -} - -TEST_F(SharedBufferTest, SharedBufferFragmentBuilder) { - constexpr size_t kBlockSize = 16; - SharedBufferFragmentBuilder builder(kBlockSize); - - auto verifyFragment = [](const SharedBufferFragment& fragment, uint8_t expected) { - for (size_t i = 0; i < fragment.size(); ++i) - ASSERT(memcmp(fragment.get() + i, &expected, 1) == 0); - }; - - builder.start(kBlockSize / 2); - ASSERT_EQ(builder.capacity(), kBlockSize); - uint8_t one = 1; - memset(builder.get(), one, kBlockSize / 2); - auto fragment1 = builder.finish(kBlockSize / 2); - ASSERT_EQ(fragment1.size(), kBlockSize / 2); - verifyFragment(fragment1, one); - - builder.start(kBlockSize / 2); - ASSERT_EQ(builder.capacity(), kBlockSize / 2); - // We can use less than we ask for - uint8_t two = 2; - memset(builder.get(), two, kBlockSize / 4); - auto fragment2 = builder.finish(kBlockSize / 4); - ASSERT_EQ(fragment2.size(), kBlockSize / 4); - // Buffers should not overlap and be next to each other - ASSERT_EQ(fragment1.get() + fragment1.size(), fragment2.get()); - verifyFragment(fragment2, two); - - // Verify that anything written is transfered when we grow - builder.start(builder.capacity()); - ASSERT_EQ(builder.capacity(), kBlockSize / 4); - uint8_t three = 3; - size_t written = kBlockSize / 4; - // Write current capacity - memset(builder.get(), three, written); - builder.grow(kBlockSize); - // Write the rest - memset(builder.get() + written, three, builder.capacity() - written); - auto fragment3 = builder.finish(kBlockSize); - for (size_t i = 0; i < (kBlockSize / 4); ++i) - ASSERT(memcmp(fragment3.get() + i, &three, 1) == 0); - ASSERT_EQ(builder.capacity(), kBlockSize); - verifyFragment(fragment3, three); - - builder.start(builder.capacity()); - auto ptr = builder.get(); - builder.discard(); - builder.start(builder.capacity()); - ASSERT_EQ(builder.get(), ptr); - - // No buffers should have been overwritten by others - verifyFragment(fragment1, one); - verifyFragment(fragment2, two); - verifyFragment(fragment3, three); -} - -TEST_F(SharedBufferTest, ManualFreeSharedBufferFragmentMemUsage1) { - constexpr size_t kBlockSize = 16; - SharedBufferFragmentBuilder builder(kBlockSize, - SharedBufferFragmentBuilder::DoubleGrowStrategy( - SharedBufferFragmentBuilder::kDefaultMaxBlockSize)); - - { - builder.start(kBlockSize / 2); - ASSERT_EQ(kBlockSize, builder.capacity()); - auto fragment1 = builder.finish(kBlockSize / 2); - ASSERT_EQ(kBlockSize / 2, fragment1.size()); - ASSERT_EQ(kBlockSize, builder.memUsage()); - - builder.start(kBlockSize / 2); - ASSERT_EQ(kBlockSize / 2, builder.capacity()); - auto fragment2 = builder.finish(kBlockSize / 2); - ASSERT_EQ(kBlockSize / 2, fragment2.size()); - ASSERT_EQ(kBlockSize, builder.memUsage()); - } - - ASSERT_EQ(kBlockSize, builder.memUsage()); - builder.freeUnused(); - // At a mimimum we will have a buffer of at least one block size in use. - ASSERT_EQ(kBlockSize, builder.memUsage()); -} - -TEST_F(SharedBufferTest, ManualFreeSharedBufferFragmentMemUsage2) { - constexpr size_t kBlockSize = 16; - SharedBufferFragmentBuilder builder(kBlockSize, - SharedBufferFragmentBuilder::DoubleGrowStrategy( - SharedBufferFragmentBuilder::kDefaultMaxBlockSize)); - - // This test allocates fragments on a block boundaries. - { - builder.start(kBlockSize); - ASSERT_EQ(kBlockSize, builder.capacity()); - auto fragment1 = builder.finish(kBlockSize); - ASSERT_EQ(kBlockSize, fragment1.size()); - ASSERT_EQ(kBlockSize, builder.memUsage()); - - builder.start(kBlockSize); - // Second buffer is allocated with twice the capacity. - ASSERT_EQ(2 * kBlockSize, builder.capacity()); - auto fragment2 = builder.finish(kBlockSize); - ASSERT_EQ(kBlockSize, fragment2.size()); - ASSERT_EQ(3 * kBlockSize, builder.memUsage()); - } - - ASSERT_EQ(3 * kBlockSize, builder.memUsage()); - builder.freeUnused(); - // Remaining buffer is the last allocated block of 2x block size; - ASSERT_EQ(2 * kBlockSize, builder.memUsage()); -} - -TEST_F(SharedBufferTest, ManualFreeSharedBufferFragmentMemUsage3) { - constexpr size_t kBlockSize = 16; - SharedBufferFragmentBuilder builder(kBlockSize, - SharedBufferFragmentBuilder::DoubleGrowStrategy( - SharedBufferFragmentBuilder::kDefaultMaxBlockSize)); - - size_t expectedMem = 0; - ASSERT_EQ(expectedMem, builder.memUsage()); - - // This test allocates a fragment that isn't on a block boundary. - { - builder.start(1); - ASSERT_EQ(kBlockSize, builder.capacity()); - auto fragment1 = builder.finish(1); - ASSERT_EQ(1, fragment1.size()); - ASSERT_EQ(kBlockSize, builder.memUsage()); - - builder.start(kBlockSize); - // We will realloc with double the size. - ASSERT_EQ(2 * kBlockSize, builder.capacity()); - auto fragment2 = builder.finish(kBlockSize); - ASSERT_EQ(kBlockSize, fragment2.size()); - // We have one buffer of size kBlockSize and another of 2x. - ASSERT_EQ(3 * kBlockSize, builder.memUsage()); - } - - ASSERT_EQ(3 * kBlockSize, builder.memUsage()); - builder.freeUnused(); - // The last buffer we allocated was 2x the block size, so this is what remains. - ASSERT_EQ(2 * kBlockSize, builder.memUsage()); -} - -TEST_F(SharedBufferTest, ManualFreeSharedBufferFragmentMemUsageGrow) { - constexpr size_t kBlockSize = 16; - SharedBufferFragmentBuilder builder(kBlockSize, - SharedBufferFragmentBuilder::DoubleGrowStrategy( - SharedBufferFragmentBuilder::kDefaultMaxBlockSize)); - { - builder.start(1); - ASSERT_EQ(kBlockSize, builder.capacity()); - auto fragment1 = builder.finish(1); - ASSERT_EQ(1, fragment1.size()); - ASSERT_EQ(kBlockSize, builder.memUsage()); - - builder.start(1); - ASSERT_EQ(kBlockSize - 1, builder.capacity()); - // We will realloc a buffer of 2x the block size. - builder.grow(kBlockSize); - auto fragment2 = builder.finish(kBlockSize); - ASSERT_EQ(kBlockSize, fragment2.size()); - ASSERT_EQ(3 * kBlockSize, builder.memUsage()); - } - - ASSERT_EQ(3 * kBlockSize, builder.memUsage()); - builder.freeUnused(); - // The last buffer we allocated was 2x the block size, so this is what remains. - ASSERT_EQ(2 * kBlockSize, builder.memUsage()); -} - -TEST_F(SharedBufferTest, ManualFreeSharedBufferFragmentMemReUse) { - constexpr size_t kBlockSize = 16; - SharedBufferFragmentBuilder builder(kBlockSize, - SharedBufferFragmentBuilder::DoubleGrowStrategy( - SharedBufferFragmentBuilder::kDefaultMaxBlockSize)); - - { - builder.start(kBlockSize); - ASSERT_EQ(kBlockSize, builder.capacity()); - auto fragment1 = builder.finish(kBlockSize); - ASSERT_EQ(kBlockSize, fragment1.size()); - ASSERT_EQ(kBlockSize, builder.memUsage()); - } - - { - // Expect that there is no increase in memory usage when we allocate a new buffer of the - // same size. - builder.start(kBlockSize); - ASSERT_EQ(kBlockSize, builder.capacity()); - auto fragment2 = builder.finish(kBlockSize); - ASSERT_EQ(kBlockSize, fragment2.size()); - ASSERT_EQ(kBlockSize, builder.memUsage()); - } - - { - // Expect that the memory usage only doubles and no more when we allocate a new buffer - // greater than the current capacity. - builder.start(2 * kBlockSize); - ASSERT_EQ(2 * kBlockSize, builder.capacity()); - auto fragment = builder.finish(2 * kBlockSize); - ASSERT_EQ(2 * kBlockSize, builder.memUsage()); - } -} - -TEST_F(SharedBufferTest, ManualFreeSharedBufferFragmentLotsOfGrows) { - constexpr size_t kBlockSize = 16; - SharedBufferFragmentBuilder builder(kBlockSize, - SharedBufferFragmentBuilder::DoubleGrowStrategy( - SharedBufferFragmentBuilder::kDefaultMaxBlockSize)); - - // Ensure that when we grow and we are the exclusive user of the builder, we don't need to - // allocate new buffers. - { - builder.start(kBlockSize); - ASSERT_EQ(kBlockSize, builder.capacity()); - ASSERT_EQ(kBlockSize, builder.memUsage()); - - builder.grow(2 * kBlockSize); - ASSERT_EQ(2 * kBlockSize, builder.capacity()); - ASSERT_EQ(2 * kBlockSize, builder.memUsage()); - - builder.grow(3 * kBlockSize); - // We double the buffer size internally every time we realloc. - ASSERT_EQ(4 * kBlockSize, builder.capacity()); - ASSERT_EQ(4 * kBlockSize, builder.memUsage()); - - builder.grow(4 * kBlockSize); - ASSERT_EQ(4 * kBlockSize, builder.capacity()); - ASSERT_EQ(4 * kBlockSize, builder.memUsage()); - - builder.grow(5 * kBlockSize); - ASSERT_EQ(8 * kBlockSize, builder.capacity()); - ASSERT_EQ(8 * kBlockSize, builder.memUsage()); - - auto fragment1 = builder.finish(5 * kBlockSize); - ASSERT_EQ(fragment1.size(), 5 * kBlockSize); - - // If we start a new fragment, we expect that it's capacity is what remains in the buffer. - builder.start(kBlockSize); - ASSERT_EQ(3 * kBlockSize, builder.capacity()); - ASSERT_EQ(8 * kBlockSize, builder.memUsage()); - - auto fragment2 = builder.finish(kBlockSize); - ASSERT_EQ(kBlockSize, fragment2.size()); - } - - // This has no effect on memory usage since the last allocated buffer is 8x the block size. We - // can't reclaim the unused space right now, but the next fragment we build should be able to. - builder.freeUnused(); - ASSERT_EQ(2 * kBlockSize, builder.capacity()); - ASSERT_EQ(8 * kBlockSize, builder.memUsage()); - - { - builder.start(kBlockSize); - // Since there are no active fragments, we expect to have the full capacity of the - // underlying buffer. - ASSERT_EQ(8 * kBlockSize, builder.capacity()); - ASSERT_EQ(8 * kBlockSize, builder.memUsage()); - - builder.grow(8 * kBlockSize); - ASSERT_EQ(8 * kBlockSize, builder.capacity()); - ASSERT_EQ(8 * kBlockSize, builder.memUsage()); - - auto fragment1 = builder.finish(8 * kBlockSize); - ASSERT_EQ(fragment1.size(), 8 * kBlockSize); - } - - builder.freeUnused(); - ASSERT_EQ(8 * kBlockSize, builder.memUsage()); -} - -TEST_F(SharedBufferTest, ManyUnusedBuffers) { - constexpr size_t kBlockSize = 16; - SharedBufferFragmentBuilder builder( - kBlockSize, SharedBufferFragmentBuilder::DoubleGrowStrategy(kBlockSize)); - - { - // Create many fragments, stop using them them all at once, and expect freeUnused to reclaim - // all but one. - std::vector<SharedBufferFragment> fragments; - for (int i = 0; i < 128; i++) { - builder.start(kBlockSize); - ASSERT_EQ(kBlockSize, builder.capacity()); - fragments.emplace_back(builder.finish(kBlockSize)); - ASSERT_EQ(kBlockSize * (i + 1), builder.memUsage()); - } - } - - builder.freeUnused(); - ASSERT_EQ(kBlockSize, builder.memUsage()); -} - -} // namespace -} // namespace mongo diff --git a/src/mongo/util/signal_handlers_synchronous.cpp b/src/mongo/util/signal_handlers_synchronous.cpp index 59e2e2ef923..8f052ea134b 100644 --- a/src/mongo/util/signal_handlers_synchronous.cpp +++ b/src/mongo/util/signal_handlers_synchronous.cpp @@ -65,8 +65,6 @@ namespace mongo { namespace { -using namespace fmt::literals; - #if defined(_WIN32) const char* strsignal(int signalNum) { // should only see SIGABRT on windows @@ -193,38 +191,22 @@ private: stdx::mutex MallocFreeOStreamGuard::_streamMutex; // NOLINT thread_local int MallocFreeOStreamGuard::terminateDepth = 0; -void logNoRecursion(StringData message) { - // If we were within a log call when we hit a signal, don't call back into the logging - // subsystem. - if (logv2::loggingInProgress()) { - logv2::signalSafeWriteToStderr(message); - } else { - LOGV2_FATAL_CONTINUE(6384300, "Writing fatal message", "message"_attr = message); - } -} - // must hold MallocFreeOStreamGuard to call void writeMallocFreeStreamToLog() { - mallocFreeOStream << "\n"; - logNoRecursion(mallocFreeOStream.str()); + LOGV2_FATAL_OPTIONS( + 4757800, + logv2::LogOptions(logv2::FatalMode::kContinue, logv2::LogTruncation::Disabled), + "{message}", + "Writing fatal message", + "message"_attr = mallocFreeOStream.str()); mallocFreeOStream.rewind(); } // must hold MallocFreeOStreamGuard to call -void printStackTraceNoRecursion() { - if (logv2::loggingInProgress()) { - printStackTrace(mallocFreeOStream); - writeMallocFreeStreamToLog(); - } else { - printStackTrace(); - } -} - -// must hold MallocFreeOStreamGuard to call void printSignalAndBacktrace(int signalNum) { - mallocFreeOStream << "Got signal: " << signalNum << " (" << strsignal(signalNum) << ")."; + mallocFreeOStream << "Got signal: " << signalNum << " (" << strsignal(signalNum) << ").\n"; writeMallocFreeStreamToLog(); - printStackTraceNoRecursion(); + printStackTrace(); } // this will be called in certain c++ error cases, for example if there are two active @@ -240,7 +222,7 @@ void myTerminate() { mallocFreeOStream << " No exception is active"; } writeMallocFreeStreamToLog(); - printStackTraceNoRecursion(); + printStackTrace(); breakpoint(); endProcessWithSignal(SIGABRT); } @@ -259,16 +241,22 @@ void myInvalidParameterHandler(const wchar_t* expression, const wchar_t* file, unsigned int line, uintptr_t pReserved) { - - logNoRecursion( - "Invalid parameter detected in function {} in {} at line {} with expression '{}'\n"_format( - toUtf8String(function), toUtf8String(file), line, toUtf8String(expression))); + LOGV2_FATAL_CONTINUE( + 23815, + "Invalid parameter detected in function {function} in {file} at line {line} " + "with expression '{expression}'", + "Invalid parameter detected", + "function"_attr = toUtf8String(function), + "file"_attr = toUtf8String(file), + "line"_attr = line, + "expression"_attr = toUtf8String(expression)); abruptQuit(SIGABRT); } void myPureCallHandler() { - logNoRecursion("Pure call handler invoked. Immediate exit due to invalid pure call\n"); + LOGV2_FATAL_CONTINUE(23818, + "Pure call handler invoked. Immediate exit due to invalid pure call"); abruptQuit(SIGABRT); } @@ -357,9 +345,9 @@ void setupSynchronousSignalHandlers() { void reportOutOfMemoryErrorAndExit() { MallocFreeOStreamGuard lk{}; - mallocFreeOStream << "out of memory."; + mallocFreeOStream << "out of memory.\n"; writeMallocFreeStreamToLog(); - printStackTraceNoRecursion(); + printStackTrace(); quickExit(EXIT_ABRUPT); } diff --git a/src/mongo/util/stacktrace.cpp b/src/mongo/util/stacktrace.cpp index a1c3c69a39a..b9b15b7410a 100644 --- a/src/mongo/util/stacktrace.cpp +++ b/src/mongo/util/stacktrace.cpp @@ -117,21 +117,3 @@ void logBacktraceObject(const BSONObj& bt, StackTraceSink* sink, bool withHumanR } } // namespace mongo::stack_trace_detail - -void mongo::StackTrace::log(bool withHumanReadable) const { - if (hasError()) { - LOGV2_ERROR(31430, "Error collecting stack trace", "error"_attr = _error); - } - - StackTraceSink* logv2Sink = nullptr; - stack_trace_detail::logBacktraceObject(_stacktrace, logv2Sink, withHumanReadable); -} - -void mongo::StackTrace::sink(StackTraceSink* sink, bool withHumanReadable) const { - using namespace fmt::literals; - if (hasError()) { - *sink << fmt::format(FMT_STRING("Error collecting stack trace: {}"), _error); - } - - stack_trace_detail::logBacktraceObject(_stacktrace, sink, withHumanReadable); -} diff --git a/src/mongo/util/stacktrace.h b/src/mongo/util/stacktrace.h index fdfce44af08..e36a0aea03b 100644 --- a/src/mongo/util/stacktrace.h +++ b/src/mongo/util/stacktrace.h @@ -39,8 +39,6 @@ #include "mongo/base/string_data.h" #include "mongo/bson/bsonobj.h" #include "mongo/config.h" -#include "mongo/util/future.h" -#include "mongo/util/synchronized_value.h" /** * All-thread backtrace is only implemented on Linux. Even on Linux, it's only AS-safe @@ -93,46 +91,6 @@ private: std::string& _s; }; -/** - * A `StackTrace` object also encapsulates any errors encountered while attaining stacktrace - * information. Oddly, a `StackTrace` object can be in an error state (`hasError` returns true) and - * have non-empty stacktrace information via `getBSONRepresentation`. It is legal to call - * `getBSONRepresentation` even when in an error state. - * - * Likewise, it is always safe to call `log` or `sink`, regardless of error state. Those output - * methods will write out any errors along with any available stacktrace information. - * - * Disabling log truncation is strongly recommended when logging a BSONObj returned from - * `getBSONRepresentation` by hand. - */ -class StackTrace { -public: - explicit StackTrace(BSONObj stacktrace) : _stacktrace(stacktrace) {} - - StackTrace(BSONObj stacktrace, std::string error) - : _stacktrace(stacktrace), _error(std::move(error)) {} - - void log(bool withHumanReadable = true) const; - - void sink(StackTraceSink* sink, bool withHumanReadable = true) const; - - BSONObj getBSONRepresentation() const { - return _stacktrace; - } - - bool hasError() const { - return !_error.empty(); - } - - std::string getError() const { - return _error; - } - -private: - BSONObj _stacktrace; - std::string _error; -}; - namespace stack_trace_detail { /** * A utility for uint64_t <=> uppercase hex string conversions. It @@ -185,64 +143,6 @@ private: void logBacktraceObject(const BSONObj& bt, StackTraceSink* sink, bool withHumanReadable); -/** - * Multiple waiters can register their interest in the next stack trace, - * by calling `waiter()` and obtaining a waiter object, which will block - * in its destructor until the next stack trace completes. - * On the "producer side", each stack trace collection calls `notifier()` - * to become that next stack trace. It will signal the end of the collection - * by destroying the notifier object returned by that `notifier` call. - */ -class PrintAllStacksSession { -public: - /** Notifies observers on its destruction. */ - class Notifier { - public: - explicit Notifier(std::unique_ptr<SharedPromise<void>> prom) : _prom{std::move(prom)} {} - ~Notifier() { - if (_prom) - _prom->emplaceValue(); - } - - Notifier(Notifier&&) = default; - Notifier& operator=(Notifier&&) = default; - - private: - std::unique_ptr<SharedPromise<void>> _prom; - }; - - /** Blocks in its destructor waiting for a session to complete. */ - class Waiter { - public: - explicit Waiter(SharedSemiFuture<void> fut) : _fut{std::move(fut)} {} - ~Waiter() { - if (_fut.valid()) - _fut.get(); - } - - Waiter(Waiter&&) = default; - Waiter& operator=(Waiter&&) = default; - - private: - SharedSemiFuture<void> _fut; - }; - - Notifier notifier() { - // Consume and retain the current SharedPromise from _promise. - return Notifier{std::exchange(**_promise, {})}; - } - - Waiter waiter() { - auto updateGuard = _promise.synchronize(); - if (!*updateGuard) - *updateGuard = std::make_unique<SharedPromise<void>>(); - return Waiter{(*updateGuard)->getFuture()}; - } - -private: - synchronized_value<std::unique_ptr<SharedPromise<void>>> _promise; -}; - } // namespace stack_trace_detail #ifndef _WIN32 @@ -379,7 +279,6 @@ size_t rawBacktrace(void** addrs, size_t capacity); void printStackTrace(StackTraceSink& sink); void printStackTrace(std::ostream& os); void printStackTrace(); -StackTrace getStackTrace(); #if defined(MONGO_STACKTRACE_CAN_DUMP_ALL_THREADS) @@ -404,9 +303,6 @@ void markAsStackTraceProcessingThread(); void printAllThreadStacks(); void printAllThreadStacks(StackTraceSink& sink); -/** Calls `printAllThreadStacks` and blocks until it is completed. */ -void printAllThreadStacksBlocking(); - #endif // defined(MONGO_STACKTRACE_CAN_DUMP_ALL_THREADS) } // namespace mongo diff --git a/src/mongo/util/stacktrace_posix.cpp b/src/mongo/util/stacktrace_posix.cpp index 2bbfd8867bd..2bf807ed4ae 100644 --- a/src/mongo/util/stacktrace_posix.cpp +++ b/src/mongo/util/stacktrace_posix.cpp @@ -97,6 +97,9 @@ struct Options { // Add the processInfo block bool withProcessInfo = true; + // Add "human readable" breakdown when dumping stack. 1 line per frame. + bool withHumanReadable = true; + // only include the somap entries relevant to the backtrace bool trimSoMap = true; @@ -421,7 +424,7 @@ private: * analysis tool. For example, on Linux it contains a subobject named "somap", describing * the objects referenced in the "b" fields of the "backtrace" list. */ -StackTrace getStackTraceImpl(const Options& options) { +void printStackTraceImpl(const Options& options, StackTraceSink* sink = nullptr) { using namespace fmt::literals; std::string err; BSONObjBuilder bob; @@ -438,9 +441,16 @@ StackTrace getStackTraceImpl(const Options& options) { appendStackTraceObject(&bob, iteration, options); #endif - return StackTrace(bob.obj(), err); + if (!err.empty()) { + if (sink) { + *sink << fmt::format(FMT_STRING("Error collecting stack trace: {}"), err); + } + LOGV2_ERROR(31430, "Error collecting stack trace", "error"_attr = err); + } + stack_trace_detail::logBacktraceObject(bob.done(), sink, options.withHumanReadable); } + } // namespace } // namespace stack_trace_detail @@ -464,17 +474,10 @@ const StackTraceAddressMetadata& StackTraceAddressMetadataGenerator::load(void* return _meta; } -StackTrace getStackTrace() { - stack_trace_detail::Options options{}; - options.rawAddress = true; - return getStackTraceImpl(options); -} - void printStackTrace(StackTraceSink& sink) { stack_trace_detail::Options options{}; options.rawAddress = true; - const bool withHumanReadable = true; - getStackTraceImpl(options).sink(&sink, withHumanReadable); + stack_trace_detail::printStackTraceImpl(options, &sink); } void printStackTrace(std::ostream& os) { @@ -485,8 +488,7 @@ void printStackTrace(std::ostream& os) { void printStackTrace() { stack_trace_detail::Options options{}; options.rawAddress = true; - const bool withHumanReadable = true; - getStackTraceImpl(options).log(withHumanReadable); + stack_trace_detail::printStackTraceImpl(options, nullptr); } } // namespace mongo diff --git a/src/mongo/util/stacktrace_test.cpp b/src/mongo/util/stacktrace_test.cpp index 6c6d2f36366..2a9ad180c28 100644 --- a/src/mongo/util/stacktrace_test.cpp +++ b/src/mongo/util/stacktrace_test.cpp @@ -645,64 +645,6 @@ TEST_F(PrintAllThreadStacksTest, Go_200_Threads) { doPrintAllThreadStacks(200); } -TEST_F(PrintAllThreadStacksTest, SessionBasic) { - stack_trace_detail::PrintAllStacksSession session; - - auto waiter = boost::make_optional(session.waiter()); - stdx::thread producer{[&] { auto notifier = session.notifier(); }}; - waiter = {}; - producer.join(); -} - -TEST_F(PrintAllThreadStacksTest, SessionProducerConsumers) { - synchronized_value<std::string> values; - stack_trace_detail::PrintAllStacksSession session; - - struct PromiseFuture { - SharedPromise<void> promise; - SharedSemiFuture<void> future; - }; - - std::vector<PromiseFuture> promisesFutures(2); - - for (auto& pf : promisesFutures) { - pf.future = pf.promise.getFuture(); - } - - std::vector<stdx::thread> consumers; - for (auto& pf : promisesFutures) { - consumers.emplace_back([&, p = &pf.promise] { - auto waiter = boost::make_optional(session.waiter()); - p->emplaceValue(); - waiter = {}; - values->push_back('3'); - }); - } - - // This does not enforce ordering between push_back('3') above and the push_back calls below if - // PrintAllStacksSession::waiter/notifier don't. - for (auto& pf : promisesFutures) { - pf.future.wait(); - } - - stdx::thread producer{[&] { - values->push_back('1'); - auto notifier = boost::make_optional(session.notifier()); - values->push_back('2'); - notifier = {}; - values->push_back('3'); - }}; - - for (auto& consumer : consumers) { - consumer.join(); - } - - producer.join(); - - auto guard = values.synchronize(); - ASSERT(*guard == "12333") << *guard; -} - #endif // defined(MONGO_STACKTRACE_CAN_DUMP_ALL_THREADS) #if defined(MONGO_CONFIG_USE_LIBUNWIND) || defined(MONGO_CONFIG_HAVE_EXECINFO_BACKTRACE) diff --git a/src/mongo/util/stacktrace_threads.cpp b/src/mongo/util/stacktrace_threads.cpp index 51581c4dc9b..d7157d0e212 100644 --- a/src/mongo/util/stacktrace_threads.cpp +++ b/src/mongo/util/stacktrace_threads.cpp @@ -324,7 +324,6 @@ class State { public: void printStacks(StackTraceSink& sink); void printStacks(); - void printAllThreadStacksBlocking(); /** * We need signals for two purpposes in the stack tracing system. @@ -383,7 +382,6 @@ private: int _signal = 0; std::atomic<int> _processingTid = -1; // NOLINT std::atomic<StackCollectionOperation*> _stackCollection = nullptr; // NOLINT - PrintAllStacksSession _printAllStacksSession; MONGO_STATIC_ASSERT(decltype(_processingTid)::is_always_lock_free); MONGO_STATIC_ASSERT(decltype(_stackCollection)::is_always_lock_free); @@ -525,16 +523,10 @@ void State::printStacks() { LOGV2(31426, "===== multithread stacktrace session end ====="); } }; - LogEmitter emitter; - auto notifier = _printAllStacksSession.notifier(); printToEmitter(emitter); } -void State::printAllThreadStacksBlocking() { - auto waiter = _printAllStacksSession.waiter(); - kill(getpid(), _signal); // The SignalHandler thread calls printAllThreadStacks. -} void State::printToEmitter(AbstractEmitter& emitter) { std::vector<ThreadBacktrace> messageStorage; @@ -674,7 +666,6 @@ void initialize(int signal) { } } // namespace - } // namespace stack_trace_detail @@ -686,10 +677,6 @@ void printAllThreadStacks() { stack_trace_detail::stateSingleton->printStacks(); } -void printAllThreadStacksBlocking() { - stack_trace_detail::stateSingleton->printAllThreadStacksBlocking(); -} - void setupStackTraceSignalAction(int signal) { stack_trace_detail::initialize(signal); } diff --git a/src/mongo/util/stacktrace_windows.cpp b/src/mongo/util/stacktrace_windows.cpp index f34d616aa6a..99ceea9e937 100644 --- a/src/mongo/util/stacktrace_windows.cpp +++ b/src/mongo/util/stacktrace_windows.cpp @@ -66,6 +66,11 @@ namespace { const size_t kPathBufferSize = 1024; +struct Options { + bool withHumanReadable = false; + bool rawAddress = false; +}; + // On Windows the symbol handler must be initialized at process startup and cleaned up at shutdown. // This class wraps up that logic and gives access to the process handle associated with the // symbol handler. Because access to the symbol handler API is not thread-safe, it also provides @@ -207,11 +212,14 @@ struct TraceItem { std::pair<std::string, size_t> symbol; }; -void appendTrace(BSONObjBuilder* bob, const std::vector<TraceItem>& traceList) { +void appendTrace(BSONObjBuilder* bob, + const std::vector<TraceItem>& traceList, + const Options& options) { auto bt = BSONArrayBuilder(bob->subarrayStart("backtrace")); for (const auto& item : traceList) { auto o = BSONObjBuilder(bt.subobjStart()); - o.append("a", stack_trace_detail::Hex(item.address)); + if (options.rawAddress) + o.append("a", stack_trace_detail::Hex(item.address)); if (!item.module.empty()) o.append("module", item.module); if (!item.source.first.empty()) { @@ -286,51 +294,59 @@ std::vector<TraceItem> makeTraceList(CONTEXT& context) { return traceList; } -StackTrace getStackTraceImpl(CONTEXT& context) { - std::vector<TraceItem> traceList = makeTraceList(context); - if (traceList.empty()) { - return StackTrace(BSONObj(), ""); - } - +void printTraceList(const std::vector<TraceItem>& traceList, + StackTraceSink* sink, + const Options& options) { + using namespace fmt::literals; + if (traceList.empty()) + return; BSONObjBuilder bob; - appendTrace(&bob, traceList); - return StackTrace(bob.obj(), ""); + appendTrace(&bob, traceList, options); + stack_trace_detail::logBacktraceObject(bob.done(), sink, options.withHumanReadable); } -} // namespace -StackTrace getStackTrace() { +/** `sink` can be nullptr to emit structured logs instead of writing to a sink. */ +void printWindowsStackTraceImpl(CONTEXT& context, StackTraceSink* sink) { + Options options{}; + options.withHumanReadable = true; + options.rawAddress = true; + printTraceList(makeTraceList(context), sink, options); +} + +void printWindowsStackTraceImpl(StackTraceSink* sink) { CONTEXT context; memset(&context, 0, sizeof(context)); context.ContextFlags = CONTEXT_CONTROL; RtlCaptureContext(&context); - - return getStackTraceImpl(context); + printWindowsStackTraceImpl(context, sink); } +} // namespace + void printWindowsStackTrace(CONTEXT& context, StackTraceSink& sink) { - getStackTraceImpl(context).sink(&sink); + printWindowsStackTraceImpl(context, &sink); } void printWindowsStackTrace(CONTEXT& context, std::ostream& os) { OstreamStackTraceSink sink{os}; - printWindowsStackTrace(context, sink); + printWindowsStackTraceImpl(context, &sink); } void printWindowsStackTrace(CONTEXT& context) { - getStackTraceImpl(context).log(); + printWindowsStackTraceImpl(context, nullptr); } void printStackTrace(StackTraceSink& sink) { - getStackTrace().sink(&sink); + printWindowsStackTraceImpl(&sink); } void printStackTrace(std::ostream& os) { OstreamStackTraceSink sink{os}; - printStackTrace(sink); + printWindowsStackTraceImpl(&sink); } void printStackTrace() { - getStackTrace().log(); + printWindowsStackTraceImpl(nullptr); } } // namespace mongo diff --git a/src/mongo/util/str_escape.cpp b/src/mongo/util/str_escape.cpp index c42a916bab0..d191fb92252 100644 --- a/src/mongo/util/str_escape.cpp +++ b/src/mongo/util/str_escape.cpp @@ -37,82 +37,45 @@ namespace mongo::str { namespace { constexpr char kHexChar[] = "0123456789abcdef"; -// Appends the bytes in the range [begin, end) to the output buffer, -// which can either be a fmt::memory_buffer, or a std::string. -template <typename Buffer, typename Iterator> -void appendBuffer(Buffer& buffer, Iterator begin, Iterator end) { - buffer.append(begin, end); -} - // 'singleHandler' Function to write a valid single byte UTF-8 sequence with desired escaping. // 'invalidByteHandler' Function to write a byte of invalid UTF-8 encoding // 'twoEscaper' Function to write a valid two byte UTF-8 sequence with desired escaping, for C1 // control codes. -// 'maxLength' Max length to write into output buffer; A value of std::string::npos means unbounded. -// An escape sequence will not be written if appending the entire sequence will exceed this limit. -// 'wouldWrite' Output to contain the total bytes that would have been written to the buffer if no -// size limit is in place. -// // All these functions take a function object as their first parameter to perform the // writing of any escaped data. This function expects the number of handled bytes as its first // parameter and the corresponding escaped string as the second. They are templates to they can be // inlined. -template <typename Buffer, - typename SingleByteHandler, - typename InvalidByteHandler, - typename TwoByteEscaper> -void escape(Buffer& buffer, +template <typename SingleByteHandler, typename InvalidByteHandler, typename TwoByteEscaper> +void escape(fmt::memory_buffer& buffer, StringData str, SingleByteHandler singleHandler, InvalidByteHandler invalidByteHandler, - TwoByteEscaper twoEscaper, - size_t maxLength, - size_t* wouldWrite) { - // The range [inFirst, it) contains input that does not need to be escaped and that has not been + TwoByteEscaper twoEscaper) { + // The range [begin, it) contains input that does not need to be escaped and that has not been // written to output yet. - // The range [it, inLast) contains remaining input to scan. 'inFirst' is pointing to the - // beginning of the input that has not yet been written to 'escaped'. 'it' is pointing to the - // beginning of the unicode code point we're currently processing in the while-loop below. - // 'inLast' is the end of the input sequence. - auto inFirst = str.begin(); - auto inLast = str.end(); - auto it = inFirst; - size_t cap = maxLength; - size_t total = 0; + // The range [it end) contains remaining input to scan 'begin' is pointing to the beginning of + // the input that has not yet been written to 'escaped'. + // 'it' is pointing to the beginning of the unicode code point we're currently processing in the + // while-loop below. 'end' is the end of the input sequence. + auto begin = str.begin(); + auto it = str.begin(); + auto end = str.end(); // Writes an escaped sequence to output after flushing pending input that does not need to be // escaped. 'it' is assumed to be at the beginning of the input sequence represented by the // escaped data. // 'numHandled' the number of bytes of unescaped data being written escaped in 'escapeSequence' auto flushAndWrite = [&](size_t numHandled, StringData escapeSequence) { - // Appends the range [wFirst, wLast) to the output if the result is within the max length. - // 'canTruncate' controls the behavior if appending the entire range would exceed the limit. - // If true, this appends input up to the length limit. Otherwise, none is appended. - auto boundedWrite = [&](auto wFirst, auto wLast, bool canTruncate) { - size_t len = std::distance(wFirst, wLast); - total += len; - if (maxLength != std::string::npos) { - if (len > cap) { - if (!canTruncate) { - cap = 0; - } - len = cap; - } - cap -= len; - } - appendBuffer(buffer, wFirst, wFirst + len); - }; - // Flush range of unmodified input - boundedWrite(inFirst, it, true); - inFirst = it + numHandled; + buffer.append(begin, it); + begin = it + numHandled; // Write escaped data - boundedWrite(escapeSequence.begin(), escapeSequence.end(), false); + buffer.append(escapeSequence.rawData(), escapeSequence.rawData() + escapeSequence.size()); }; auto isValidCodePoint = [&](auto pos, int len) { - return std::distance(pos, inLast) >= len && + return std::distance(pos, end) >= len && std::all_of(pos + 1, pos + len, [](uint8_t c) { return (c >> 6) == 0b10; }); }; @@ -135,7 +98,7 @@ void escape(Buffer& buffer, auto writeInvalid = [&](uint8_t c) { invalidByteHandler(flushAndWrite, c); }; - while (it != inLast) { + while (it != end) { uint8_t c = *it; bool bit7 = (c >> 7) & 1; if (MONGO_likely(!bit7)) { @@ -193,15 +156,10 @@ void escape(Buffer& buffer, } } // Write last block - flushAndWrite(0, {}); - if (wouldWrite) { - *wouldWrite = total; - } + buffer.append(begin, it); } } // namespace - -template <typename Buffer> -void escapeForTextCommon(Buffer& buffer, StringData str, size_t maxLength, size_t* wouldWrite) { +void escapeForText(fmt::memory_buffer& buffer, StringData str) { auto singleByteHandler = [](const auto& writer, uint8_t unescaped) { switch (unescaped) { case '\0': @@ -329,26 +287,16 @@ void escapeForTextCommon(Buffer& buffer, StringData str, size_t maxLength, size_ str, std::move(singleByteHandler), std::move(invalidByteHandler), - std::move(twoByteEscaper), - maxLength, - wouldWrite); + std::move(twoByteEscaper)); } -void escapeForText(fmt::memory_buffer& buffer, - StringData str, - size_t maxLength, - size_t* wouldWrite) { - escapeForTextCommon(buffer, str, maxLength, wouldWrite); +std::string escapeForText(StringData str) { + fmt::memory_buffer buffer; + escapeForText(buffer, str); + return fmt::to_string(buffer); } -std::string escapeForText(StringData str, size_t maxLength, size_t* wouldWrite) { - std::string buffer; - escapeForTextCommon(buffer, str, maxLength, wouldWrite); - return buffer; -} - -template <typename Buffer> -void escapeForJSONCommon(Buffer& buffer, StringData str, size_t maxLength, size_t* wouldWrite) { +void escapeForJSON(fmt::memory_buffer& buffer, StringData str) { auto singleByteHandler = [](const auto& writer, uint8_t unescaped) { switch (unescaped) { case '\0': @@ -479,21 +427,11 @@ void escapeForJSONCommon(Buffer& buffer, StringData str, size_t maxLength, size_ str, std::move(singleByteHandler), std::move(invalidByteHandler), - std::move(twoByteEscaper), - maxLength, - wouldWrite); + std::move(twoByteEscaper)); } - -void escapeForJSON(fmt::memory_buffer& buffer, - StringData str, - size_t maxLength, - size_t* wouldWrite) { - escapeForJSONCommon(buffer, str, maxLength, wouldWrite); -} - -std::string escapeForJSON(StringData str, size_t maxLength, size_t* wouldWrite) { - std::string buffer; - escapeForJSONCommon(buffer, str, maxLength, wouldWrite); - return buffer; +std::string escapeForJSON(StringData str) { + fmt::memory_buffer buffer; + escapeForJSON(buffer, str); + return fmt::to_string(buffer); } } // namespace mongo::str diff --git a/src/mongo/util/str_escape.h b/src/mongo/util/str_escape.h index 2d82e5697cd..47fe3d30060 100644 --- a/src/mongo/util/str_escape.h +++ b/src/mongo/util/str_escape.h @@ -35,75 +35,9 @@ #include <string> namespace mongo::str { +void escapeForText(fmt::memory_buffer& buffer, StringData str); +std::string escapeForText(StringData str); -/** - * Escapes the special characters in 'str' for use as printable text. - * - * The backslash (`\`) character is escaped with another backslash, yielding the - * 2-character sequence {`\`, `\`}. - * - * The single-byte control characters (octets 0x00-0x1f, 0x7f) are generally escaped - * using the format "\xHH", where the 2 `H` characters are replaced by the 2 hex digits - * of the octet. For instance, the octet 0x7f would yield the sequence: {`\`, `x`, `7`, `f`}. - * Exemptions to this rule are the following octets, which are escaped using C-style escape - * sequences: - * 0x00 -> {`\`, `0`} - * 0x07 -> {`\`, `a`} - * 0x08 -> {`\`, `b`} - * 0x09 -> {`\`, `t`} - * 0x0a -> {`\`, `n`} - * 0x0b -> {`\`, `v`} - * 0x0c -> {`\`, `f`} - * 0x0d -> {`\`, `r`} - * 0x1b -> {`\`, `e`} - * - * The two-byte UTF-8 sequences between 0xC280 (U+0080) and 0xC29F (U+009F), inclusive, are - * also escaped as they are considered control characters. The escape sequence for these has - * the format: "\xC2\xHH", where the 2 `H` characters are replaced by the 2 hex digits of the - * second octet. - * - * Invalid bytes found are replaced with the escape sequence following the format: "\xHH", - * similar to how single-byte control characters are escaped. - * - * This writes the escaped output to 'buffer', and stops writing when either the output - * length reaches the 'maxLength', or if appending the next escape sequence will cause the - * output to exceed 'maxLength'. A 'maxLength' value of std::string::npos means unbounded. - * - * The 'wouldWrite' output is updated to contain the total bytes that would have been written - * if there was no length limit. - */ -void escapeForText(fmt::memory_buffer& buffer, - StringData str, - size_t maxLength = std::string::npos, - size_t* wouldWrite = nullptr); -std::string escapeForText(StringData str, - size_t maxLength = std::string::npos, - size_t* wouldWrite = nullptr); - -/** - * Escapes the special characters in 'str' for use in JSON. - * - * This differs from escapeForText in that the double-quote character (`"`) is escaped - * with a backslash, yielding the 2-character sequence {`\`, `"`}. - * - * The general format of the escape sequences for single-byte control characters becomes - * "\u00HH", where the 2 `H` characters are replaced by the 2 hex digits of the octet. - * For example, the octet 0x7f would yield the sequence: {`\`, `u`, `0`, `0`, `7`, `f`}. - * The list of octets escaped using C-style escape sequences is also shortened to: - * 0x08 -> {`\`, `b`} - * 0x09 -> {`\`, `t`} - * 0x0a -> {`\`, `n`} - * 0x0c -> {`\`, `f`} - * 0x0d -> {`\`, `r`} - * For two-byte control characters, the format of the escape sequence becomes "\uc2HH", - * where the 2 `H` characters are replaced by the 2 hex digits of the second octet. - * Invalid bytes found are replaced with the sequence: "\ufffd". - */ -void escapeForJSON(fmt::memory_buffer& buffer, - StringData str, - size_t maxLength = std::string::npos, - size_t* wouldWrite = nullptr); -std::string escapeForJSON(StringData str, - size_t maxLength = std::string::npos, - size_t* wouldWrite = nullptr); +void escapeForJSON(fmt::memory_buffer& buffer, StringData str); +std::string escapeForJSON(StringData str); } // namespace mongo::str diff --git a/src/mongo/util/string_map.h b/src/mongo/util/string_map.h index ee3bf8da366..2285a3d630b 100644 --- a/src/mongo/util/string_map.h +++ b/src/mongo/util/string_map.h @@ -33,7 +33,6 @@ #include <absl/container/flat_hash_set.h> #include "mongo/base/string_data.h" -#include "mongo/stdx/trusted_hasher.h" #include "mongo/util/assert_util.h" namespace mongo { @@ -119,11 +118,4 @@ using StringDataMap = absl::flat_hash_map<StringData, V, StringMapHasher, String using StringDataSet = absl::flat_hash_set<StringData, StringMapHasher, StringMapEq>; -// StringMapHasher is a trusted hasher, no need to wrap in a secondary layer of hashing when used in -// stdx unordered containers. -template <> -struct IsTrustedHasher<StringMapHasher, std::string> : std::true_type {}; -template <> -struct IsTrustedHasher<StringMapHasher, StringData> : std::true_type {}; - } // namespace mongo diff --git a/src/mongo/util/tick_source_bm.cpp b/src/mongo/util/tick_source_bm.cpp deleted file mode 100644 index ef6381b753d..00000000000 --- a/src/mongo/util/tick_source_bm.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (C) 2022-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/util/duration.h" -#include "mongo/util/system_tick_source.h" -#include "mongo/util/tick_source.h" -#include <benchmark/benchmark.h> - - -namespace mongo { - -static void BM_getTicks(benchmark::State& state) { - auto tickSource = SystemTickSource::get(); - for (auto _ : state) { - benchmark::DoNotOptimize(tickSource->getTicks()); - } - state.SetItemsProcessed(state.iterations()); -} - -BENCHMARK(BM_getTicks); - -} // namespace mongo diff --git a/src/mongo/util/uuid.h b/src/mongo/util/uuid.h index ce10692d1de..078f658d07c 100644 --- a/src/mongo/util/uuid.h +++ b/src/mongo/util/uuid.h @@ -160,11 +160,6 @@ public: return _uuid >= rhs._uuid; } - template <typename H> - friend H AbslHashValue(H h, const UUID& uuid) { - return H::combine(std::move(h), uuid._uuid); - } - /** * Returns true only if the UUID is the RFC 4122 variant, v4 (random). */ diff --git a/src/mongo/util/version/releases.h.tpl b/src/mongo/util/version/releases.h.tpl index ddab494047d..701266bc71f 100644 --- a/src/mongo/util/version/releases.h.tpl +++ b/src/mongo/util/version/releases.h.tpl @@ -242,24 +242,6 @@ constexpr StringData toString(FeatureCompatibilityVersion v) { return findExtended(v).second; } -inline int majorVersion(FeatureCompatibilityVersion v) { - auto str = toString(v); - auto pos = str.rfind('.'); - if (pos == std::string::npos) { - return -1; - } - return stoi(str.substr(pos-1, 1).toString()); -} - -inline int minorVersion(FeatureCompatibilityVersion v) { - auto str = toString(v); - auto pos = str.rfind('.'); - if (pos == std::string::npos) { - return -1; - } - return stoi(str.substr(pos+1, 1).toString()); -} - /** * Pointers to nodes of the extended table that represent numbered software versions. * Other FCV enum members, such as those representing transitions, are excluded. diff --git a/src/mongo/util/version/releases.yml b/src/mongo/util/version/releases.yml index 3babf32c3f7..ca829d039c9 100644 --- a/src/mongo/util/version/releases.yml +++ b/src/mongo/util/version/releases.yml @@ -24,27 +24,6 @@ longTermSupportReleases: - "4.4" - "5.0" -# List of stable MongoDB versions since 2.0 that have been EOL'd. -# Entries to this section will also stop them from running as "old" versions in -# multiversion testing across all branches. This is because there is no need to -# run multiversion testing against EOL versions. -eolVersions: - - "2.0" - - "2.2" - - "2.4" - - "2.6" - - "3.0" # 2018-02 - - "3.2" # 2018-09 - - "3.4" # 2020-01 - - "3.6" # 2021-04 - - "4.0" # 2022-04 -# - "4.2" # 2023-04 -# - "4.4" # 2024-02 -# - "5.0" # 2024-10 - - "5.1" # 2022-01 - - "5.2" # 2022-04 - - "5.3" # 2022-07 - # Optional. # Using this special override extends FCV constant generation down to the previous value of # lastLTS. This is intended to ease the transition to a new lastLTS by extending the lifetime |
