From 7b7f7535f41e4cdc05a7bab3edb58a6fb32adc5d Mon Sep 17 00:00:00 2001 From: Florian Fischer <florian.fischer@muhq.space> Date: Fri, 14 May 2021 13:43:11 +0200 Subject: [PATCH] implement a builder pattern for Runtime This new builder pattern in addition to a more powerful Runtime constructor allows the user to pass additional new worker hooks. This is useful for example if an applications wants to initialize thread local variables on each worker. Current code does not need any modification and has no semantic changes. Future code can use the new RuntimeBuilder class to construct more sophisticated Runtime objects. --- emper/Runtime.cpp | 33 +++++++------------ emper/Runtime.hpp | 18 +++++++++-- emper/RuntimeBuilder.hpp | 70 ++++++++++++++++++++++++++++++++++++++++ emper/lib/env.hpp | 25 ++++++++++++++ 4 files changed, 122 insertions(+), 24 deletions(-) create mode 100644 emper/RuntimeBuilder.hpp diff --git a/emper/Runtime.cpp b/emper/Runtime.cpp index bb5e1ef8..c246bbc6 100644 --- a/emper/Runtime.cpp +++ b/emper/Runtime.cpp @@ -72,7 +72,9 @@ RuntimeStrategyFactory& Runtime::DEFAULT_STRATEGY = using emper::io::GlobalIoContext; using emper::io::IoContext; -Runtime::Runtime(workerid_t workerCount, RuntimeStrategyFactory& strategyFactory, unsigned int seed) +Runtime::Runtime(workerid_t workerCount, const std::vector<NewWorkerHook>& newWorkerHooks, + bool pinWorkers, workerid_t pinningOffset, RuntimeStrategyFactory& strategyFactory, + unsigned int seed) : workerCount(workerCount), workerLatch(workerCount), firstWorkerThreadExitLatch(workerCount), @@ -116,6 +118,9 @@ Runtime::Runtime(workerid_t workerCount, RuntimeStrategyFactory& strategyFactory fromAnywhereStats = nullptr; } + // transfere newWorkerHooks + for (const auto& f : newWorkerHooks) this->newWorkerHooks.push_back(f); + // initialize the global IoContext if a completer is used if constexpr (emper::IO && emper::IO_COMPLETER_BEHAVIOR != emper::IoCompleterBehavior::none) { // The global io_uring needs at least workerCount entries in its SQ because @@ -130,21 +135,9 @@ Runtime::Runtime(workerid_t workerCount, RuntimeStrategyFactory& strategyFactory } } - bool pinWorkers = shouldPinWorkers(); - - // Core id we start the worker pinning - workerid_t pinningOffset = 0; - char* pinningOffsetEnv = std::getenv("EMPER_PINNING_OFFSET"); - if (pinningOffsetEnv) { - if (!pinWorkers) { - DIE_MSG("EMPER_PIN_WORKERS=false and EMPER_PINNING_OFFSET are mutual exclusive"); - } - - int pinningOffsetInt = std::stoi(pinningOffsetEnv); - if (pinningOffsetInt > UINT8_MAX) { - DIE_MSG("Pinning offset " << pinningOffsetInt << " to big for its datatype"); - } - pinningOffset = static_cast<workerid_t>(pinningOffsetInt); + // Check if the pinning settings are sound + if (pinningOffset && !pinWorkers) { + DIE_MSG("pinningOffset and not pinning workers are mutually exclusive"); } for (workerid_t i = 0; i < workerCount; ++i) { @@ -298,13 +291,9 @@ auto Runtime::workerLoop(Worker* worker) -> void* { } auto Runtime::getDefaultWorkerCount() -> workerid_t { - char* workerCountEnv = std::getenv("EMPER_WORKER_COUNT"); + auto workerCountEnv = emper::lib::env::getUnsignedFromEnv<workerid_t>("EMPER_WORKER_COUNT"); if (workerCountEnv) { - int workerCountInt = std::stoi(workerCountEnv); - if (workerCountInt > UINT8_MAX) { - DIE_MSG("Worker count " << workerCountInt << " to big for its datatype"); - } - return static_cast<workerid_t>(workerCountInt); + return workerCountEnv.value(); } // The CPU count reported by sysconf(_SC_NPROCESSORS_ONLN), sysconf(_SC_NPROCESSORS_CONF) diff --git a/emper/Runtime.hpp b/emper/Runtime.hpp index c3467559..14ae319c 100644 --- a/emper/Runtime.hpp +++ b/emper/Runtime.hpp @@ -31,6 +31,7 @@ #include "sleep_strategy/WorkerSleepStrategy.hpp" enum class LogSubsystem; +class RuntimeBuilder; class ContextManager; class Dispatcher; class Fiber; @@ -59,13 +60,16 @@ using emper::io::IoContext; using emper::sleep_strategy::WorkerSleepStrategy; class Runtime : public Logger<LogSubsystem::RUNTI> { + public: + using NewWorkerHook = std::function<void(workerid_t)>; + private: static std::mutex currentRuntimeMutex; static Runtime* currentRuntime; const workerid_t workerCount; - std::vector<std::function<void(workerid_t)>> newWorkerHooks; + std::vector<NewWorkerHook> newWorkerHooks; Latch workerLatch; Latch firstWorkerThreadExitLatch; @@ -110,6 +114,10 @@ class Runtime : public Logger<LogSubsystem::RUNTI> { return emper::lib::env::getBoolFromEnv("EMPER_PIN_WORKERS").value_or(true); } + static auto getDefaultPinningOffset() -> workerid_t { + return emper::lib::env::getUnsignedFromEnv<workerid_t>("EMPER_PINNING_OFFSET").value_or(0); + } + protected: void addNewWorkerHook(const std::function<void(workerid_t)>& hook) { newWorkerHooks.push_back(hook); @@ -157,7 +165,12 @@ class Runtime : public Logger<LogSubsystem::RUNTI> { : Runtime(getDefaultWorkerCount(), strategyFactory) {} Runtime(workerid_t workerCount, RuntimeStrategyFactory& strategyFactory, - unsigned int seed = std::random_device()()); + unsigned int seed = std::random_device()()) + : Runtime(workerCount, std::vector<NewWorkerHook>(), shouldPinWorkers(), + getDefaultPinningOffset(), strategyFactory, seed) {} + + Runtime(workerid_t workerCount, const std::vector<NewWorkerHook>& newWorkerHooks, bool pinWorkers, + workerid_t pinningOffset, RuntimeStrategyFactory& strategyFactory, unsigned int seed); ~Runtime(); @@ -216,6 +229,7 @@ class Runtime : public Logger<LogSubsystem::RUNTI> { friend class AbstractWorkStealingScheduler; template <LogSubsystem> friend class Blockable; + friend RuntimeBuilder; friend ContextManager; friend Scheduler; friend Dispatcher; diff --git a/emper/RuntimeBuilder.hpp b/emper/RuntimeBuilder.hpp new file mode 100644 index 00000000..5e3e8b2e --- /dev/null +++ b/emper/RuntimeBuilder.hpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright © 2021 Florian Fischer +#pragma once + +#include <optional> +#include <vector> + +#include "Runtime.hpp" +#include "emper-common.h" + +class RuntimeBuilder { + private: + workerid_t workerCount = 0; + + std::vector<Runtime::NewWorkerHook> newWorkerHooks; + + RuntimeStrategyFactory* strategyFactory = nullptr; + + std::optional<bool> pinWorkers = std::nullopt; + std::optional<workerid_t> pinningOffset = std::nullopt; + + std::optional<unsigned> seed = std::nullopt; + + public: + inline auto withWorkerCount(workerid_t workerCount) -> RuntimeBuilder& { + this->workerCount = workerCount; + return *this; + }; + + inline auto newWorkerHook(const Runtime::NewWorkerHook& hook) -> RuntimeBuilder& { + newWorkerHooks.push_back(hook); + return *this; + }; + + inline auto withPinWorkers(bool pinWorkers) -> RuntimeBuilder& { + this->pinWorkers = pinWorkers; + return *this; + }; + + inline auto withPinningOffset(workerid_t pinningOffset) -> RuntimeBuilder& { + this->pinningOffset = pinningOffset; + return *this; + }; + + inline auto withStrategyFactory(RuntimeStrategyFactory* factory) -> RuntimeBuilder& { + this->strategyFactory = factory; + return *this; + }; + + inline auto withSeed(unsigned seed) -> RuntimeBuilder& { + this->seed = seed; + return *this; + }; + + inline auto build() -> Runtime { + auto workerCount = + this->workerCount != 0 ? this->workerCount : Runtime::getDefaultWorkerCount(); + + auto* strategyFactory = + this->strategyFactory != nullptr ? this->strategyFactory : &Runtime::DEFAULT_STRATEGY; + + auto pinWorkers = this->pinWorkers.value_or(Runtime::shouldPinWorkers()); + + auto pinningOffset = this->pinningOffset.value_or(Runtime::getDefaultPinningOffset()); + + auto seed = this->seed.value_or(std::random_device()()); + + return Runtime(workerCount, newWorkerHooks, pinWorkers, pinningOffset, *strategyFactory, seed); + } +}; diff --git a/emper/lib/env.hpp b/emper/lib/env.hpp index 65b6c4fc..4d97aaec 100644 --- a/emper/lib/env.hpp +++ b/emper/lib/env.hpp @@ -2,6 +2,7 @@ // Copyright © 2021 Florian Fischer #pragma once +#include <cinttypes> #include <string> #include "Debug.hpp" @@ -27,4 +28,28 @@ static auto getBoolFromEnv(const std::string&& key) -> std::optional<bool> { DIE_MSG(key << " has invalid value: " << envStr << " (expected true or false)"); } +template <typename unsigned_type> +static auto getUnsignedFromEnv(const std::string&& key) -> std::optional<unsigned_type> { + DBG("parse " << key << " environment variable"); + char* envVar = std::getenv(key.c_str()); + if (!envVar) { + return std::nullopt; + } + + std::string envStr(envVar); + char* last; + + uintmax_t num = std::strtoumax(envStr.c_str(), &last, 10); + if (last != &envStr[0] + envStr.size()) { + DIE_MSG(key << " has invalid value: " << envStr << " (expected base-10 number)"); + } + + const unsigned_type t_max = std::numeric_limits<unsigned_type>::max(); + if (num > static_cast<uintmax_t>(t_max)) { + DIE_MSG(key << " is to big: " << envStr << " (type '" << typeid(t_max).name() + << "' max: " << t_max << ")"); + } + return static_cast<unsigned_type>(num); +} + } // namespace emper::lib::env -- GitLab