diff --git a/emper/io/GlobalIoContext.hpp b/emper/io/GlobalIoContext.hpp index 7dbf3114ec7d113a6c94f586f7498ad37dd32a7a..d819ba8e2865c46bf457be43d26f5973fae0ff13 100644 --- a/emper/io/GlobalIoContext.hpp +++ b/emper/io/GlobalIoContext.hpp @@ -20,9 +20,12 @@ class GlobalIoContext : public IoContext { friend class RecvFuture; private: - GlobalIoContext(Runtime& runtime, workerid_t workerCount) : IoContext(runtime, workerCount) { + GlobalIoContext(Runtime& runtime, workerid_t workerCount, unsigned unboundedIowMax) + : IoContext(runtime, workerCount, unboundedIowMax) { worker = nullptr; } + GlobalIoContext(Runtime& runtime, workerid_t workerCount) + : GlobalIoContext(runtime, workerCount, IoContext::getDefaultUnboundedIowMax()) {} enum class PointerTags : uint16_t { Future, Callback, IoContext, TerminationEvent }; diff --git a/emper/io/IoContext.cpp b/emper/io/IoContext.cpp index 56c4c1e92bfeaa601864474a241f4b66d006e4a4..b183abf03dacf14729c6693566f1e991d0e68983 100644 --- a/emper/io/IoContext.cpp +++ b/emper/io/IoContext.cpp @@ -10,10 +10,12 @@ #include <unistd.h> #include <algorithm> +#include <array> #include <atomic> // for atomic, __atomic_base #include <cassert> // for assert #include <cerrno> // for errno, ECANCELED, EBUSY, EAGAIN, EINTR #include <cstring> // for memset +#include <optional> #include <ostream> // for basic_osteram::operator<<, operator<< #include <string> #include <vector> @@ -25,11 +27,14 @@ #include "Emper.hpp" // for DEBUG, IO_URING_SQPOLL #include "Runtime.hpp" #include "emper-common.h" +#include "emper-config.h" #include "io/Future.hpp" #include "io/GlobalIoContext.hpp" #include "io/Stats.hpp" // for Stats, nanoseconds #include "io/SubmitActor.hpp" +#include "lib/LinuxVersion.hpp" #include "lib/TaggedPtr.hpp" +#include "lib/env.hpp" using emper::lib::TaggedPtr; @@ -495,7 +500,8 @@ template auto IoContext::reapCompletionsLocked<CallerEnvironment::ANYWHERE, IoContext::CQE_BATCH_COUNT>(Fiber **contiunations) -> unsigned; -IoContext::IoContext(Runtime &runtime, size_t uring_entries) : runtime(runtime) { +IoContext::IoContext(Runtime &runtime, size_t uring_entries, unsigned unboundedIowMax) + : runtime(runtime) { struct io_uring_params params; memset(¶ms, 0, sizeof(params)); @@ -579,6 +585,26 @@ IoContext::IoContext(Runtime &runtime, size_t uring_entries) : runtime(runtime) } } + // Limit the number of unbounded iow thread created by this io_uring + // By default the number of unbounded iow worker threads is RLIMIT_NPROC + // which may be excessive. + // IORING_REGISTER_IOWQ_MAX_WORKERS sets a maximum value for bounded and unbounded + // worker creation per NUMA node and thread. + // See: + // https://blog.cloudflare.com/missing-manuals-io_uring-worker-pool/ + if (unboundedIowMax) { + if (EMPER_LINUX_LT("5.15")) DIE_MSG("can not set unbounded iow max on linux < 5.15"); + + std::array<unsigned int, 2> maxWorkerConfig = { + 0, // do not change bounded worker count + unboundedIowMax, + }; + + if (unlikely(io_uring_register_iowq_max_workers(&ring, maxWorkerConfig.data()) < 0)) + DIE_MSG_ERRNO("setting the max unbounded io worker count to " << unboundedIowMax + << " failed"); + } + // reserve space for a full SQ preparedSqes.reserve(*this->ring.sq.kring_entries); @@ -613,6 +639,17 @@ IoContext::~IoContext() { delete submitter; } +auto IoContext::getDefaultUnboundedIowMax() -> unsigned { + unsigned unboundedIowMax = EMPER_IO_UNBOUNDED_IOW_MAX; + + static const std::string unboundedIowMaxEnvVarName = "EMPER_UNBOUNDED_IOW_MAX"; + auto unboundedIowMaxEnv = + emper::lib::env::getUnsignedFromEnv<unsigned>(unboundedIowMaxEnvVarName); + if (unboundedIowMaxEnv) return unboundedIowMaxEnv.value(); + + return unboundedIowMax; +} + auto IoContext::getSqHead() const -> unsigned { return *ring.sq.khead; } auto IoContext::getSqTail() const -> unsigned { return *ring.sq.ktail; } auto IoContext::getSqEntries() const -> unsigned { return *ring.sq.kring_entries; } diff --git a/emper/io/IoContext.hpp b/emper/io/IoContext.hpp index 53e2a05b5e9a6071d61e592927b6d42c622138af..a877a7488b1a6f393f21b825b62a352cc5ecaef4 100644 --- a/emper/io/IoContext.hpp +++ b/emper/io/IoContext.hpp @@ -302,10 +302,22 @@ class IoContext : public Logger<LogSubsystem::IO> { } public: - IoContext(Runtime &runtime, size_t uring_entries); - IoContext(Runtime &runtime) : IoContext(runtime, EMPER_IO_WORKER_URING_ENTRIES){}; + IoContext(Runtime &runtime, size_t uring_entries, unsigned unboundedIowMax); + IoContext(Runtime &runtime) + : IoContext(runtime, EMPER_IO_WORKER_URING_ENTRIES, IoContext::getDefaultUnboundedIowMax()){}; ~IoContext(); + /** + * @brief get the default maximum count of unbounded io worker threads + * + * The default is 0 (unbounded), which can be overwritten during + * compile time (-Dio_unbouned_iow_max) or runtime using the + * environment variable EMPER_UNBOUNDED_IOW_MAX. + * + * @return the default maximum count of unbounded io worker threads + */ + static auto getDefaultUnboundedIowMax() -> unsigned; + /** * @brief get IoContext of the current worker * diff --git a/emper/lib/env.cpp b/emper/lib/env.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a6c065f66a52cbbd3da87af5f0bbcccfa2079140 --- /dev/null +++ b/emper/lib/env.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright © 2021-2022 Florian Fischer +#include "lib/env.hpp" + +#include <ostream> +#include <string> + +#include "Common.hpp" + +namespace emper::lib::env { + +auto getBoolFromEnv(const std::string& key) -> std::optional<bool> { + char* envVar = std::getenv(key.c_str()); + if (!envVar) { + return std::nullopt; + } + + std::string envStr(envVar); + if (envStr == "true") { + return true; + } + + if (envStr == "false") { + return false; + } + + DIE_MSG(key << " has invalid value: " << envStr << " (expected true or false)"); + // Should never be reached. + return false; +} +} // namespace emper::lib::env diff --git a/emper/lib/env.hpp b/emper/lib/env.hpp index d03784beb10caf45a0ae8f17189884345881202f..1179253da971193d0bb7da885e23ea730526b802 100644 --- a/emper/lib/env.hpp +++ b/emper/lib/env.hpp @@ -1,31 +1,21 @@ // SPDX-License-Identifier: LGPL-3.0-or-later -// Copyright © 2021 Florian Fischer +// Copyright © 2021-2022 Florian Fischer #pragma once #include <cinttypes> +#include <cstdlib> +#include <iterator> +#include <memory> +#include <optional> +#include <ostream> #include <string> +#include <typeinfo> -#include "Debug.hpp" +#include "Common.hpp" namespace emper::lib::env { -static auto getBoolFromEnv(const std::string& key) -> std::optional<bool> { - char* envVar = std::getenv(key.c_str()); - if (!envVar) { - return std::nullopt; - } - - std::string envStr(envVar); - if (envStr == "true") { - return true; - } - - if (envStr == "false") { - return false; - } - - DIE_MSG(key << " has invalid value: " << envStr << " (expected true or false)"); -} +auto getBoolFromEnv(const std::string& key) -> std::optional<bool>; template <typename unsigned_type> static auto getUnsignedFromEnv(const std::string& key) -> std::optional<unsigned_type> { diff --git a/emper/lib/meson.build b/emper/lib/meson.build index 0fd7650423bd2f515dec7203102b84dd053c4ed1..2ee763b692c54979677ffea7bc96faa7c8c1b883 100644 --- a/emper/lib/meson.build +++ b/emper/lib/meson.build @@ -1,5 +1,6 @@ emper_cpp_sources += files( 'DebugUtil.cpp', + 'env.cpp', 'LinuxVersion.cpp', 'util.cpp', ) diff --git a/meson.build b/meson.build index 8928df3906f7fe17e07b3720cb2d359643fd2c87..18d2799983d044f9922f528dfb125f1fd64c5bd7 100644 --- a/meson.build +++ b/meson.build @@ -13,7 +13,7 @@ thread_dep = dependency('threads') conf_data = configuration_data() use_bundled_deps = get_option('use_bundled_deps') -liburing_version = '2.0' +liburing_version = '2.1' uring_dep = dependency('liburing', version: liburing_version, required: use_bundled_deps == 'never') if not uring_dep.found() or use_bundled_deps == 'always' message('liburing ' + liburing_version + ' not found in system, using liburing subproject') @@ -157,6 +157,7 @@ io_bool_options = [ io_raw_options = [ 'worker_uring_entries', + 'unbounded_iow_max', 'lockless_memory_order', ] diff --git a/meson_options.txt b/meson_options.txt index ecca5993f419bc395510b2dc768e88911464bf2e..9b3a0d15ac7db33017e0eb9390b18c6027d08467 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -176,6 +176,12 @@ option( value: 16, description: 'Number of entries in each worker io_uring' ) +option( + 'io_unbounded_iow_max', + type: 'integer', + value: 0, + description: 'Set the maximum number of unbounded io worker threads created per io_uring' +) option( 'io_uring_sq_poller', type: 'combo', diff --git a/subprojects/liburing.wrap b/subprojects/liburing.wrap index 3fc0f9b24c1115599f28d6c1b1e823e99513bb27..b3c66d527072ad67744860bf46cd98ca18f1ba43 100644 --- a/subprojects/liburing.wrap +++ b/subprojects/liburing.wrap @@ -1,11 +1,12 @@ [wrap-file] -directory = liburing-liburing-2.0 -source_url = https://github.com/axboe/liburing/archive/refs/tags/liburing-2.0.tar.gz -source_filename = liburing-2.0.tar.gz -source_hash = ca069ecc4aa1baf1031bd772e4e97f7e26dfb6bb733d79f70159589b22ab4dc0 -patch_filename = liburing_2.0-1_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/liburing_2.0-1/get_patch -patch_hash = c63ac1a5b5ffbc088dd772878f694c5c0e502d4e4c58d262f0272ae40e648d3b +directory = liburing-liburing-2.1 +source_url = https://github.com/axboe/liburing/archive/refs/tags/liburing-2.1.tar.gz +source_filename = liburing-2.1.tar.gz +source_hash = f1e0500cb3934b0b61c5020c3999a973c9c93b618faff1eba75aadc95bb03e07 +patch_filename = liburing_2.1-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/liburing_2.1-1/get_patch +patch_hash = aaeb2be1f5eacf4f41e0537aa02154486e0729a3395d2e832de7657ab0b56290 [provide] uring = uring +