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