From 825f64a1090e4f2f85494052c3d9b6cd09613cbd Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Fri, 8 Dec 2023 15:52:12 +0100 Subject: [PATCH 01/98] Fix major bug with sending multiple HttpConnectionService messages --- src/services/network/http/HttpConnectionService.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/network/http/HttpConnectionService.cpp b/src/services/network/http/HttpConnectionService.cpp index 6dafb1c..9e18d5b 100644 --- a/src/services/network/http/HttpConnectionService.cpp +++ b/src/services/network/http/HttpConnectionService.cpp @@ -131,12 +131,12 @@ Ichor::Task Ichor::HttpConnectionService::sendAsync(Ichor:: lg.unlock(); INTERNAL_DEBUG("Outbox {}", next.route); - ScopeGuard const coroutineGuard{[this, &event, &lg]() { + ScopeGuard const coroutineGuard{[this, event = next.event, &lg]() { // use service id 0 to ensure event gets run, even if service is stopped. Otherwise, the coroutine will never complete. // Similarly, use priority 0 to ensure these events run before any dependency changes, otherwise the service might be destroyed // before we can finish all the coroutines. - _queue->pushPrioritisedEvent(0u, 0u, [&event]() { - event.set(); + _queue->pushPrioritisedEvent(0u, 0u, [event]() { + event->set(); }); lg.lock(); _outbox.pop_front(); From e60d611167805c7b794155382a1eb688c9ff2245 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Fri, 8 Dec 2023 15:54:02 +0100 Subject: [PATCH 02/98] Expand etcd example --- examples/etcd_example/UsingEtcdService.h | 45 ++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/examples/etcd_example/UsingEtcdService.h b/examples/etcd_example/UsingEtcdService.h index a6d8966..5b2aee5 100644 --- a/examples/etcd_example/UsingEtcdService.h +++ b/examples/etcd_example/UsingEtcdService.h @@ -12,8 +12,10 @@ class UsingEtcdV2Service final { public: UsingEtcdV2Service(IService *self, IEventQueue *queue, ILogger *logger, IEtcd *EtcdV2Service) { ICHOR_LOG_INFO(logger, "UsingEtcdV2Service started"); - queue->pushEvent(0, [logger, EtcdV2Service, queue, self]() -> AsyncGenerator { - auto ret = co_await EtcdV2Service->put("test", "2"); + + // standard put/get + queue->pushEvent(0, [logger, EtcdV2Service]() -> AsyncGenerator { + auto ret = co_await EtcdV2Service->put("test", "2", 10); // put with a TTL of 10 seconds if(ret) { ICHOR_LOG_INFO(logger, "Successfully put key/value into etcd"); auto storedVal = co_await EtcdV2Service->get("test"); @@ -26,6 +28,45 @@ class UsingEtcdV2Service final { ICHOR_LOG_ERROR(logger, "Error putting key/value into etcd: {}", ret.error()); } + co_return IchorBehaviour::DONE; + }); + + // wait for update and set + queue->pushEvent(0, [logger, EtcdV2Service, queue, self]() -> AsyncGenerator { + auto ret = co_await EtcdV2Service->put("watch", "3", 10); // set value + if(!ret) { + std::terminate(); + } + + // update the "test" key in 250 ms + auto start = std::chrono::steady_clock::now(); + queue->pushEvent(0, [EtcdV2Service, start]() -> AsyncGenerator { + auto now = std::chrono::steady_clock::now(); + while(now - start < std::chrono::milliseconds(250)) { + co_yield IchorBehaviour::DONE; + now = std::chrono::steady_clock::now(); + } + + auto ret = co_await EtcdV2Service->put("watch", "4", 10); // update value, this should trigger the watch + if(!ret) { + std::terminate(); + } + + co_return IchorBehaviour::DONE; + }); + + // wait for a reply, this blocks this async event (hence why we need to update the key in another async event). + // Note that this does not block the thread, just this event. + auto getReply = co_await EtcdV2Service->get("watch", false, false, true, {}); + if(!getReply) { + throw std::runtime_error(""); + } + + if(getReply.value().node->value != "4") { + std::terminate(); + } + ICHOR_LOG_ERROR(logger, "Successfully got a value when watching 'watch'", ret.error()); + queue->pushEvent(self->getServiceId()); co_return IchorBehaviour::DONE; From 7eaa5e6f7fdf2740e67a4c43f218ee607f37d110 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Wed, 13 Dec 2023 12:21:37 +0100 Subject: [PATCH 03/98] Add auth management to Etcd V2 implementation * Change every usage of std::optional to tl::optional where possible, because that allows using the [[nodiscard]] attribute on the class, giving off extra warnings. * Change HttpHeaders to an unordered_map, as that makes more sense in how to use them * Add base64, as the Etcd V2 auth uses HTTP Basic Authentication * Update to latest glaze, fixes a misaligned address load. --- CMakeLists.txt | 24 +- cloc.sh | 4 +- examples/common/TestMsgGlazeSerializer.h | 17 +- examples/common/lyra.hpp | 2 +- examples/etcd_example/UsingEtcdService.h | 8 +- examples/http_example/UsingHttpService.h | 2 +- .../http_ping_pong/PingMsgJsonSerializer.h | 17 +- examples/http_ping_pong/PingService.h | 8 +- examples/serializer_example/README.md | 2 +- examples/tcp_example/UsingTcpService.h | 10 +- examples/websocket_example/UsingWsService.h | 11 +- include/base64/base64.h | 35 + include/ichor/Callbacks.h | 6 +- include/ichor/DependencyManager.h | 18 +- .../coroutines/AsyncGeneratorPromiseBase.h | 6 +- include/ichor/coroutines/Task.h | 30 +- .../ConstructorInjectionService.h | 8 +- .../DependencyRegister.h | 8 +- include/ichor/events/ContinuableEvent.h | 4 +- include/ichor/events/InternalEvents.h | 6 +- include/ichor/glaze.h | 13 + include/ichor/services/etcd/EtcdV2Service.h | 44 +- include/ichor/services/etcd/IEtcd.h | 229 +- .../io/SharedOverThreadsAsyncFileIO.h | 2 +- .../ichor/services/network/http/HttpCommon.h | 16 +- .../network/http/HttpConnectionService.h | 4 +- .../network/http/IHttpConnectionService.h | 2 +- include/ichor/services/redis/IRedis.h | 85 +- .../services/serialization/ISerializer.h | 4 +- include/ichor/stl/AsyncSingleThreadedMutex.h | 8 +- include/tl/expected.h | 6 +- include/tl/optional.h | 2062 +++++++++++++++++ src/base64/base64.cpp | 132 ++ src/ichor/DependencyManager.cpp | 2 +- src/services/etcd/EtcdV2Service.cpp | 918 ++++++-- .../network/http/HttpConnectionService.cpp | 6 +- src/services/network/http/HttpHostService.cpp | 6 +- test/TestServices/EtcdUsingService.h | 190 +- test/TestServices/HttpThreadService.h | 2 +- 39 files changed, 3653 insertions(+), 304 deletions(-) create mode 100644 include/base64/base64.h create mode 100644 include/ichor/glaze.h create mode 100644 include/tl/optional.h create mode 100644 src/base64/base64.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 93c792c..0f2cbb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,10 +94,11 @@ file(GLOB_RECURSE ICHOR_METRICS_SOURCES ${ICHOR_TOP_DIR}/src/services/metrics/*. file(GLOB_RECURSE ICHOR_TIMER_SOURCES ${ICHOR_TOP_DIR}/src/services/timer/*.cpp) file(GLOB_RECURSE ICHOR_OPTIONAL_HIREDIS_SOURCES ${ICHOR_TOP_DIR}/src/services/redis/*.cpp) file(GLOB_RECURSE ICHOR_IO_SOURCES ${ICHOR_TOP_DIR}/src/services/io/*.cpp) +file(GLOB_RECURSE ICHOR_BASE64_SOURCES ${ICHOR_TOP_DIR}/src/base64/*.cpp) file(GLOB SPDLOG_SOURCES ${ICHOR_EXTERNAL_DIR}/spdlog/src/*.cpp) -add_library(ichor ${FMT_SOURCES} ${ICHOR_FRAMEWORK_SOURCES} ${ICHOR_LOGGING_SOURCES} ${ICHOR_NETWORK_SOURCES} ${ICHOR_METRICS_SOURCES} ${ICHOR_TIMER_SOURCES} ${ICHOR_IO_SOURCES}) +add_library(ichor ${FMT_SOURCES} ${ICHOR_FRAMEWORK_SOURCES} ${ICHOR_LOGGING_SOURCES} ${ICHOR_NETWORK_SOURCES} ${ICHOR_METRICS_SOURCES} ${ICHOR_TIMER_SOURCES} ${ICHOR_IO_SOURCES} ${ICHOR_BASE64_SOURCES}) if(ICHOR_ENABLE_INTERNAL_DEBUGGING) target_compile_definitions(ichor PUBLIC ICHOR_ENABLE_INTERNAL_DEBUGGING) @@ -133,8 +134,8 @@ if(ICHOR_AARCH64) endif() if(NOT WIN32) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wshadow -Wnon-virtual-dtor -Wno-unused-variable -Wno-long-long -Wno-unused-parameter -Wnull-dereference -pedantic -Wformat -Wformat-security -Wcast-align -Woverloaded-virtual ") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -ftemplate-backtrace-limit=0 ") + target_compile_options(ichor PUBLIC -Wall -Wextra -Wshadow -Wnon-virtual-dtor -Wno-unused-variable -Wno-long-long -Wno-unused-parameter -Wnull-dereference -pedantic -Wformat -Wformat-security -Wcast-align -Woverloaded-virtual) + target_compile_options(ichor PUBLIC -pthread -ftemplate-backtrace-limit=0) if(ICHOR_USE_HARDENING AND (CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)) # Enable runtime iterator debug checks (like checking two different iterators from different containers) target_compile_definitions(ichor PUBLIC _GLIBCXX_ASSERTIONS _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC _ITERATOR_DEBUG_LEVEL=2 _LIBCPP_DEBUG=1 _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG) @@ -158,12 +159,14 @@ endif() # gcc added support for mold in version 12, before that, the flag is ignored. # Note that with older versions of mold and early versions of gcc 12, issues with the tests have been observed. Please upgrade to the latest if you notice any issues. -if(ICHOR_USE_MOLD) +if(ICHOR_USE_MOLD AND NOT WIN32) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=mold ") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=mold ") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fuse-ld=mold ") endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wduplicated-cond -Wduplicated-branches -Wlogical-op ") + target_compile_options(ichor PUBLIC -Wduplicated-cond -Wduplicated-branches -Wlogical-op) endif() if(WIN32) @@ -308,7 +311,8 @@ endif() # Gcc 12.1 and 12.2 have bugs that prevent compilation with Werror at -O2 and higher: # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107138 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329 -if((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_VERSION VERSION_LESS "12") AND NOT WIN32) +# 12.3 has a bug where [[maybe_unused]] in glaze is somehow ignored. +if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT WIN32) target_compile_options(ichor PRIVATE "-Werror") #prevent externally compiled sources to error out on warnings endif() @@ -471,7 +475,13 @@ install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/sole" # source directory DESTINATION "include/ichor/external" # target directory FILES_MATCHING # install only matched files PATTERN "*.h" # select header files - ) +) + +install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/base64" # source directory + DESTINATION "include/ichor/external" # target directory + FILES_MATCHING # install only matched files + PATTERN "*.h" # select header files +) install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/tl" # source directory DESTINATION "include/ichor/external" # target directory diff --git a/cloc.sh b/cloc.sh index 322877e..1eaddd7 100755 --- a/cloc.sh +++ b/cloc.sh @@ -1,4 +1,4 @@ #!/bin/bash -cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=etcd,etcd_example,tl,sole,backward,gsm_enc --exclude-content=LYRA_LYRA_HPP --by-file +cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64 --exclude-content=LYRA_LYRA_HPP --by-file echo "" -cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=etcd,etcd_example,tl,sole,backward,gsm_enc --exclude-content=LYRA_LYRA_HPP +cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64 --exclude-content=LYRA_LYRA_HPP diff --git a/examples/common/TestMsgGlazeSerializer.h b/examples/common/TestMsgGlazeSerializer.h index 6ed0e74..373a61d 100644 --- a/examples/common/TestMsgGlazeSerializer.h +++ b/examples/common/TestMsgGlazeSerializer.h @@ -2,18 +2,7 @@ #include #include "TestMsg.h" - -// Glaze uses different conventions than Ichor, ignore them to prevent being spammed by warnings -#if defined( __GNUC__ ) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wsign-conversion" -# pragma GCC diagnostic ignored "-Wshadow" -# pragma GCC diagnostic ignored "-Wconversion" -#endif -#include -#if defined( __GNUC__ ) -# pragma GCC diagnostic pop -#endif +#include using namespace Ichor; @@ -35,14 +24,14 @@ class TestMsgGlazeSerializer final : public ISerializer { return buf; } - std::optional deserialize(std::vector &&stream) final { + tl::optional deserialize(std::vector &&stream) final { TestMsg msg; auto err = glz::read_json(msg, stream); if(err) { fmt::print("Glaze error {} at {}\n", (int)err.ec, err.location); fmt::print("json {}\n", (char*)stream.data()); - return std::nullopt; + return tl::nullopt; } return msg; diff --git a/examples/common/lyra.hpp b/examples/common/lyra.hpp index e805b7c..fc19559 100644 --- a/examples/common/lyra.hpp +++ b/examples/common/lyra.hpp @@ -247,7 +247,7 @@ struct is_specialization_of, Primary> : std::true_type # ifdef __has_include # if __has_include() && __cplusplus >= 201703L # include -# define LYRA_CONFIG_OPTIONAL_TYPE std::optional +# define LYRA_CONFIG_OPTIONAL_TYPE tl::optional # endif # endif #endif diff --git a/examples/etcd_example/UsingEtcdService.h b/examples/etcd_example/UsingEtcdService.h index 5b2aee5..27421df 100644 --- a/examples/etcd_example/UsingEtcdService.h +++ b/examples/etcd_example/UsingEtcdService.h @@ -15,7 +15,7 @@ class UsingEtcdV2Service final { // standard put/get queue->pushEvent(0, [logger, EtcdV2Service]() -> AsyncGenerator { - auto ret = co_await EtcdV2Service->put("test", "2", 10); // put with a TTL of 10 seconds + auto ret = co_await EtcdV2Service->put("test", "2", 10u); // put with a TTL of 10 seconds if(ret) { ICHOR_LOG_INFO(logger, "Successfully put key/value into etcd"); auto storedVal = co_await EtcdV2Service->get("test"); @@ -33,7 +33,7 @@ class UsingEtcdV2Service final { // wait for update and set queue->pushEvent(0, [logger, EtcdV2Service, queue, self]() -> AsyncGenerator { - auto ret = co_await EtcdV2Service->put("watch", "3", 10); // set value + auto ret = co_await EtcdV2Service->put("watch", "3", 10u); // set value if(!ret) { std::terminate(); } @@ -47,8 +47,8 @@ class UsingEtcdV2Service final { now = std::chrono::steady_clock::now(); } - auto ret = co_await EtcdV2Service->put("watch", "4", 10); // update value, this should trigger the watch - if(!ret) { + auto ret2 = co_await EtcdV2Service->put("watch", "4", 10u); // update value, this should trigger the watch + if(!ret2) { std::terminate(); } diff --git a/examples/http_example/UsingHttpService.h b/examples/http_example/UsingHttpService.h index 4cf5aec..710f6eb 100644 --- a/examples/http_example/UsingHttpService.h +++ b/examples/http_example/UsingHttpService.h @@ -89,7 +89,7 @@ class UsingHttpService final : public AdvancedService { AsyncGenerator sendTestRequest(std::vector &&toSendMsg) { ICHOR_LOG_INFO(_logger, "sendTestRequest"); - std::vector headers{HttpHeader{"Content-Type", "application/json"}}; + unordered_map headers{{"Content-Type", "application/json"}}; auto response = co_await _connectionService->sendAsync(HttpMethod::post, "/test", std::move(headers), std::move(toSendMsg)); if(_serializer == nullptr) { diff --git a/examples/http_ping_pong/PingMsgJsonSerializer.h b/examples/http_ping_pong/PingMsgJsonSerializer.h index 887af19..cad2431 100644 --- a/examples/http_ping_pong/PingMsgJsonSerializer.h +++ b/examples/http_ping_pong/PingMsgJsonSerializer.h @@ -2,18 +2,7 @@ #include #include "PingMsg.h" - -// Glaze uses different conventions than Ichor, ignore them to prevent being spammed by warnings -#if defined( __GNUC__ ) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wsign-conversion" -# pragma GCC diagnostic ignored "-Wshadow" -# pragma GCC diagnostic ignored "-Wconversion" -#endif -#include -#if defined( __GNUC__ ) -# pragma GCC diagnostic pop -#endif +#include using namespace Ichor; @@ -33,14 +22,14 @@ class PingMsgJsonSerializer final : public ISerializer { buf.push_back('\0'); return buf; } - std::optional deserialize(std::vector &&stream) final { + tl::optional deserialize(std::vector &&stream) final { PingMsg msg; auto err = glz::read_json(msg, stream); if(err) { fmt::print("Glaze error {} at {}\n", (int)err.ec, err.location); fmt::print("json {}\n", (char*)stream.data()); - return std::nullopt; + return tl::nullopt; } return msg; diff --git a/examples/http_ping_pong/PingService.h b/examples/http_ping_pong/PingService.h index a8d065a..48642c2 100644 --- a/examples/http_ping_pong/PingService.h +++ b/examples/http_ping_pong/PingService.h @@ -102,13 +102,13 @@ class PingService final : public AdvancedService { friend DependencyRegister; - Task> sendTestRequest(std::vector &&toSendMsg) { - std::vector headers{HttpHeader{"Content-Type", "application/json"}}; + Task> sendTestRequest(std::vector &&toSendMsg) { + unordered_map headers{{"Content-Type", "application/json"}}; auto response = co_await _connectionService->sendAsync(HttpMethod::post, "/ping", std::move(headers), std::move(toSendMsg)); if(_serializer == nullptr) { // we're stopping, gotta bail. - co_return std::optional{}; + co_return tl::optional{}; } if(response.status == HttpStatus::ok) { @@ -116,7 +116,7 @@ class PingService final : public AdvancedService { co_return msg; } else { ICHOR_LOG_ERROR(_logger, "Received status {}", (int)response.status); - co_return std::optional{}; + co_return tl::optional{}; } } diff --git a/examples/serializer_example/README.md b/examples/serializer_example/README.md index 772397f..62e2287 100644 --- a/examples/serializer_example/README.md +++ b/examples/serializer_example/README.md @@ -17,7 +17,7 @@ public: return std::vector{}; } - std::optional deserialize(std::vector &&stream) final { + tl::optional deserialize(std::vector &&stream) final { // deserialize and return a MyMsg with the right id return MyMsg{}; } diff --git a/examples/tcp_example/UsingTcpService.h b/examples/tcp_example/UsingTcpService.h index e267d30..6d2a1c8 100644 --- a/examples/tcp_example/UsingTcpService.h +++ b/examples/tcp_example/UsingTcpService.h @@ -26,7 +26,10 @@ class UsingTcpService final : public AdvancedService { ICHOR_LOG_INFO(_logger, "UsingTcpService started"); _dataEventRegistration = GetThreadLocalManager().registerEventHandler(this, this); _failureEventRegistration = GetThreadLocalManager().registerEventHandler(this, this); - _connectionService->sendAsync(_serializer->serialize(TestMsg{11, "hello"})); + auto ret = _connectionService->sendAsync(_serializer->serialize(TestMsg{11, "hello"})); + if(!ret) { + ICHOR_LOG_ERROR(_logger, "start() send error: {}", (int)ret.error()); + } co_return {}; } @@ -74,7 +77,10 @@ class UsingTcpService final : public AdvancedService { AsyncGenerator handleEvent(FailedSendMessageEvent const &evt) { ICHOR_LOG_INFO(_logger, "Failed to send message id {}, retrying", evt.msgId); - _connectionService->sendAsync(std::move(evt.data)); + auto ret = _connectionService->sendAsync(std::move(evt.data)); + if(!ret) { + ICHOR_LOG_ERROR(_logger, "handleEvent() send error: {}", (int)ret.error()); + } co_return {}; } diff --git a/examples/websocket_example/UsingWsService.h b/examples/websocket_example/UsingWsService.h index 4500b98..6cb4623 100644 --- a/examples/websocket_example/UsingWsService.h +++ b/examples/websocket_example/UsingWsService.h @@ -26,7 +26,11 @@ class UsingWsService final : public AdvancedService { ICHOR_LOG_INFO(_logger, "UsingWsService started"); _dataEventRegistration = GetThreadLocalManager().registerEventHandler(this, this); _failureEventRegistration = GetThreadLocalManager().registerEventHandler(this, this); - _connectionService->sendAsync(_serializer->serialize(TestMsg{11, "hello"})); + auto ret = _connectionService->sendAsync(_serializer->serialize(TestMsg{11, "hello"})); + if(!ret) { + ICHOR_LOG_ERROR(_logger, "start() send error: {}", (int)ret.error()); + } + co_return {}; } @@ -80,7 +84,10 @@ class UsingWsService final : public AdvancedService { AsyncGenerator handleEvent(FailedSendMessageEvent const &evt) { ICHOR_LOG_INFO(_logger, "Failed to send message id {}, retrying", evt.msgId); - _connectionService->sendAsync(std::move(evt.data)); + auto ret = _connectionService->sendAsync(std::move(evt.data)); + if(!ret) { + ICHOR_LOG_ERROR(_logger, "handleEvent() send error: {}", (int)ret.error()); + } co_return {}; } diff --git a/include/base64/base64.h b/include/base64/base64.h new file mode 100644 index 0000000..8f4a5d5 --- /dev/null +++ b/include/base64/base64.h @@ -0,0 +1,35 @@ +/* + base64.cpp and base64.h + + Copyright (C) 2004-2017 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#pragma once + +#include +#include + +// minor C++ modifications, not entirely original source +std::string base64_encode(unsigned char const* , uint64_t len); +std::string base64_decode(std::string const& s); diff --git a/include/ichor/Callbacks.h b/include/ichor/Callbacks.h index 684de0c..d2d713e 100644 --- a/include/ichor/Callbacks.h +++ b/include/ichor/Callbacks.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -13,14 +13,14 @@ namespace Ichor { class [[nodiscard]] EventCallbackInfo final { public: uint64_t listeningServiceId; - std::optional filterServiceId; + tl::optional filterServiceId; std::function(Event const &)> callback; }; class [[nodiscard]] EventInterceptInfo final { public: uint64_t listeningServiceId; - std::optional filterEventId; + tl::optional filterEventId; std::function preIntercept; std::function postIntercept; }; diff --git a/include/ichor/DependencyManager.h b/include/ichor/DependencyManager.h index e8156f0..76f9beb 100644 --- a/include/ichor/DependencyManager.h +++ b/include/ichor/DependencyManager.h @@ -211,8 +211,8 @@ namespace Ichor { for (auto const &[interfaceHash, registration] : depRegistry->_registrations) { if(interfaceHash == typeNameHash()) { - auto const &props = std::get>(registration); - requests.emplace_back(0, mgr->serviceId(), INTERNAL_EVENT_PRIORITY, std::get(registration), props.has_value() ? &props.value() : std::optional{}); + auto const &props = std::get>(registration); + requests.emplace_back(0, mgr->serviceId(), INTERNAL_EVENT_PRIORITY, std::get(registration), props.has_value() ? &props.value() : tl::optional{}); } } } @@ -284,7 +284,7 @@ namespace Ichor { /// \param impl class that is registering handler /// \param targetServiceId optional service id to filter registering for, if empty, receive all events of type EventT /// \return RAII handler, removes registration upon destruction - EventHandlerRegistration registerEventHandler(Impl *impl, IService *self, std::optional targetServiceId = {}) { + EventHandlerRegistration registerEventHandler(Impl *impl, IService *self, tl::optional targetServiceId = {}) { #ifdef ICHOR_USE_HARDENING if(this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? std::terminate(); @@ -390,7 +390,7 @@ namespace Ichor { /// Get IService by local ID /// \param id service id /// \return optional - [[nodiscard]] std::optional getIService(uint64_t id) const noexcept { + [[nodiscard]] tl::optional getIService(uint64_t id) const noexcept { #ifdef ICHOR_USE_HARDENING if(this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? std::terminate(); @@ -408,7 +408,7 @@ namespace Ichor { /// Get IService by global ID, much slower than getting by local ID /// \param id service uuid /// \return optional - [[nodiscard]] std::optional getIService(sole::uuid id) const noexcept { + [[nodiscard]] tl::optional getIService(sole::uuid id) const noexcept { #ifdef ICHOR_USE_HARDENING if(this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? std::terminate(); @@ -427,7 +427,7 @@ namespace Ichor { template - [[nodiscard]] std::optional> getService(uint64_t id) const noexcept { + [[nodiscard]] tl::optional> getService(uint64_t id) const noexcept { #ifdef ICHOR_USE_HARDENING if(this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? std::terminate(); @@ -514,7 +514,7 @@ namespace Ichor { /// Gets the class name of the implementation for given serviceId /// \param serviceId /// \return nullopt if serviceId does not exist, name of implementation class otherwise - [[nodiscard]] std::optional getImplementationNameFor(uint64_t serviceId) const noexcept; + [[nodiscard]] tl::optional getImplementationNameFor(uint64_t serviceId) const noexcept; [[nodiscard]] IEventQueue& getEventQueue() const noexcept; @@ -548,8 +548,8 @@ namespace Ichor { logAddService(cmpMgr->serviceId()); for (auto const &[key, registration] : cmpMgr->getDependencyRegistry()->_registrations) { - auto const &props = std::get>(registration); - _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), priority, std::get(registration), props.has_value() ? &props.value() : std::optional{}); + auto const &props = std::get>(registration); + _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), priority, std::get(registration), props.has_value() ? &props.value() : tl::optional{}); } auto event_priority = std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, priority); diff --git a/include/ichor/coroutines/AsyncGeneratorPromiseBase.h b/include/ichor/coroutines/AsyncGeneratorPromiseBase.h index 33d1e1b..7392bda 100644 --- a/include/ichor/coroutines/AsyncGeneratorPromiseBase.h +++ b/include/ichor/coroutines/AsyncGeneratorPromiseBase.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include #include @@ -140,7 +140,7 @@ namespace Ichor::Detail { #ifdef ICHOR_USE_HARDENING DependencyManager *_dmAtTimeOfCreation{_local_dm}; #endif - std::optional _hasSuspended{}; + tl::optional _hasSuspended{}; private: static thread_local uint64_t _idCounter; @@ -228,7 +228,7 @@ namespace Ichor::Detail { _finished = true; } - std::optional _currentValue{}; + tl::optional _currentValue{}; bool _finished{}; std::shared_ptr _destroyed; }; diff --git a/include/ichor/coroutines/Task.h b/include/ichor/coroutines/Task.h index b4a6b2e..5e56038 100644 --- a/include/ichor/coroutines/Task.h +++ b/include/ichor/coroutines/Task.h @@ -19,7 +19,7 @@ namespace Ichor namespace Detail { - class TaskPromiseBase + class [[nodiscard]] TaskPromiseBase { friend struct final_awaitable; @@ -68,7 +68,7 @@ namespace Ichor }; template - class TaskPromise final : public TaskPromiseBase + class [[nodiscard]] TaskPromise final : public TaskPromiseBase { public: @@ -87,7 +87,7 @@ namespace Ichor } } - Task get_return_object() noexcept; + [[nodiscard]] Task get_return_object() noexcept; void unhandled_exception() noexcept { ::new (static_cast(std::addressof(m_exception))) std::exception_ptr( @@ -109,7 +109,7 @@ namespace Ichor m_resultType = result_type::value; } - T& result() & { + [[nodiscard]] T& result() & { if (m_resultType == result_type::exception) { std::rethrow_exception(m_exception); } @@ -130,7 +130,7 @@ namespace Ichor T, T&&>; - rvalue_type result() && { + [[nodiscard]] rvalue_type result() && { if (m_resultType == result_type::exception) { std::rethrow_exception(m_exception); } @@ -154,12 +154,12 @@ namespace Ichor }; template<> - class TaskPromise : public TaskPromiseBase { + class [[nodiscard]] TaskPromise : public TaskPromiseBase { public: TaskPromise() noexcept = default; - Task get_return_object() noexcept; + [[nodiscard]] Task get_return_object() noexcept; void return_void() noexcept {} @@ -181,12 +181,12 @@ namespace Ichor }; template - class TaskPromise : public TaskPromiseBase { + class [[nodiscard]] TaskPromise : public TaskPromiseBase { public: TaskPromise() noexcept = default; - Task get_return_object() noexcept; + [[nodiscard]] Task get_return_object() noexcept; void unhandled_exception() noexcept { m_exception = std::current_exception(); @@ -196,7 +196,7 @@ namespace Ichor m_value = std::addressof(value); } - T& result() { + [[nodiscard]] T& result() { if (m_exception) { std::rethrow_exception(m_exception); } @@ -297,7 +297,7 @@ namespace Ichor return m_coroutine && m_coroutine.done(); } - auto operator co_await() const & noexcept { + [[nodiscard]] auto operator co_await() const & noexcept { struct awaitable : awaitable_base { using awaitable_base::awaitable_base; @@ -313,7 +313,7 @@ namespace Ichor return awaitable{ m_coroutine }; } - auto operator co_await() const && noexcept { + [[nodiscard]] auto operator co_await() const && noexcept { struct awaitable : awaitable_base { using awaitable_base::awaitable_base; @@ -348,16 +348,16 @@ namespace Ichor namespace Detail { template - Task TaskPromise::get_return_object() noexcept { + [[nodiscard]] Task TaskPromise::get_return_object() noexcept { return Task{ std::coroutine_handle::from_promise(*this) }; } - inline Task TaskPromise::get_return_object() noexcept { + [[nodiscard]] inline Task TaskPromise::get_return_object() noexcept { return Task{ std::coroutine_handle::from_promise(*this) }; } template - Task TaskPromise::get_return_object() noexcept { + [[nodiscard]] Task TaskPromise::get_return_object() noexcept { return Task{ std::coroutine_handle::from_promise(*this) }; } } diff --git a/include/ichor/dependency_management/ConstructorInjectionService.h b/include/ichor/dependency_management/ConstructorInjectionService.h index aead6a4..5ec2a76 100644 --- a/include/ichor/dependency_management/ConstructorInjectionService.h +++ b/include/ichor/dependency_management/ConstructorInjectionService.h @@ -146,7 +146,7 @@ namespace Ichor { class ConstructorInjectionService : public IService { public: ConstructorInjectionService(DependencyRegister ®, Properties props) noexcept : IService(), _properties(std::move(props)), _serviceId(Detail::_serviceIdCounter.fetch_add(1, std::memory_order_relaxed)), _servicePriority(INTERNAL_EVENT_PRIORITY), _serviceGid(sole::uuid4()), _serviceState(ServiceState::INSTALLED) { - registerDependenciesSpecialSauce(reg, std::optional>()); + registerDependenciesSpecialSauce(reg, tl::optional>()); } ~ConstructorInjectionService() noexcept override { @@ -160,11 +160,11 @@ namespace Ichor { ConstructorInjectionService& operator=(ConstructorInjectionService&&) noexcept = default; template - void registerDependenciesSpecialSauce(DependencyRegister ®, std::optional> = {}) { + void registerDependenciesSpecialSauce(DependencyRegister ®, tl::optional> = {}) { (reg.registerDependencyConstructor>(this), ...); } template - void createServiceSpecialSauce(std::optional> = {}) { + void createServiceSpecialSauce(tl::optional> = {}) { try { new (buf) T(std::get(_deps[typeNameHash>()])...); } catch (std::exception const &) { @@ -230,7 +230,7 @@ namespace Ichor { INTERNAL_DEBUG("internal_start service {}:{} state {} -> {}", getServiceId(), typeName(), getState(), ServiceState::STARTING); _serviceState = ServiceState::STARTING; - createServiceSpecialSauce(std::optional>()); + createServiceSpecialSauce(tl::optional>()); INTERNAL_DEBUG("internal_start service {}:{} state {} -> {}", getServiceId(), typeName(), getState(), ServiceState::INJECTING); _serviceState = ServiceState::INJECTING; diff --git a/include/ichor/dependency_management/DependencyRegister.h b/include/ichor/dependency_management/DependencyRegister.h index 7d629e6..c8ba0d7 100644 --- a/include/ichor/dependency_management/DependencyRegister.h +++ b/include/ichor/dependency_management/DependencyRegister.h @@ -3,13 +3,13 @@ #include #include #include -#include +#include #include namespace Ichor { struct DependencyRegister final { template Impl> - void registerDependency(Impl *svc, bool required, std::optional props = {}) { + void registerDependency(Impl *svc, bool required, tl::optional props = {}) { static_assert(!std::is_same_v, "Impl and interface need to be separate classes"); static_assert(!DerivedTemplated, "Interface needs to be a non-service class."); // Some weird bug in MSVC @@ -44,9 +44,9 @@ namespace Ichor { Dependency{typeNameHash(), true, 0}, std::function, IService&)>{[svc](NeverNull dep, IService& isvc){ svc->template addDependencyInstance(reinterpret_cast(dep.get()), &isvc); }}, std::function, IService&)>{[svc](NeverNull dep, IService& isvc){ svc->template removeDependencyInstance(reinterpret_cast(dep.get()), &isvc); }}, - std::optional{})); + tl::optional{})); } - unordered_map, IService&)>, std::function, IService&)>, std::optional>> _registrations; + unordered_map, IService&)>, std::function, IService&)>, tl::optional>> _registrations; }; } diff --git a/include/ichor/events/ContinuableEvent.h b/include/ichor/events/ContinuableEvent.h index b73f756..a56d0d6 100644 --- a/include/ichor/events/ContinuableEvent.h +++ b/include/ichor/events/ContinuableEvent.h @@ -2,7 +2,7 @@ #include #include -#include +#include namespace Ichor { struct ContinuableEvent final : public Event { @@ -32,4 +32,4 @@ namespace Ichor { static constexpr uint64_t TYPE = typeNameHash(); static constexpr std::string_view NAME = typeName(); }; -} \ No newline at end of file +} diff --git a/include/ichor/events/InternalEvents.h b/include/ichor/events/InternalEvents.h index 91cae8b..8176af2 100644 --- a/include/ichor/events/InternalEvents.h +++ b/include/ichor/events/InternalEvents.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include namespace Ichor { /// When a service has succesfully started, this event gets added to inject it into other services @@ -29,12 +29,12 @@ namespace Ichor { /// When a new service gets created that requests dependencies, each dependency it requests adds this event struct DependencyRequestEvent final : public Event { - explicit DependencyRequestEvent(uint64_t _id, uint64_t _originatingService, uint64_t _priority, Dependency _dependency, std::optional _properties) noexcept : + explicit DependencyRequestEvent(uint64_t _id, uint64_t _originatingService, uint64_t _priority, Dependency _dependency, tl::optional _properties) noexcept : Event(TYPE, NAME, _id, _originatingService, _priority), dependency(_dependency), properties{_properties} {} ~DependencyRequestEvent() final = default; Dependency dependency; - std::optional properties; + tl::optional properties; static constexpr uint64_t TYPE = typeNameHash(); static constexpr std::string_view NAME = typeName(); }; diff --git a/include/ichor/glaze.h b/include/ichor/glaze.h new file mode 100644 index 0000000..bb088ea --- /dev/null +++ b/include/ichor/glaze.h @@ -0,0 +1,13 @@ +#pragma once + +// Glaze uses different conventions than Ichor, ignore them to prevent being spammed by warnings +#if defined( __GNUC__ ) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wshadow" +# pragma GCC diagnostic ignored "-Wconversion" +#endif +#include +#if defined( __GNUC__ ) +# pragma GCC diagnostic pop +#endif diff --git a/include/ichor/services/etcd/EtcdV2Service.h b/include/ichor/services/etcd/EtcdV2Service.h index 8da63b7..faf60df 100644 --- a/include/ichor/services/etcd/EtcdV2Service.h +++ b/include/ichor/services/etcd/EtcdV2Service.h @@ -27,21 +27,40 @@ namespace Ichor { EtcdV2Service(DependencyRegister ®, Properties props); ~EtcdV2Service() final = default; - Task> put(std::string_view key, std::string_view value, std::optional previous_value, std::optional previous_index, std::optional previous_exists, std::optional ttl_second, bool refresh, bool dir, bool in_order) final; - Task> put(std::string_view key, std::string_view value, std::optional ttl_second, bool refresh) final; - Task> put(std::string_view key, std::string_view value, std::optional ttl_second) final; - Task> put(std::string_view key, std::string_view value) final; + [[nodiscard]] Task> put(std::string_view key, std::string_view value, tl::optional previous_value, tl::optional previous_index, tl::optional previous_exists, tl::optional ttl_second, bool refresh, bool dir, bool in_order) final; + [[nodiscard]] Task> put(std::string_view key, std::string_view value, tl::optional ttl_second, bool refresh) final; + [[nodiscard]] Task> put(std::string_view key, std::string_view value, tl::optional ttl_second) final; + [[nodiscard]] Task> put(std::string_view key, std::string_view value) final; - Task> get(std::string_view key, bool recursive, bool sorted, bool watch, std::optional watchIndex) final; - Task> get(std::string_view key) final; - Task> del(std::string_view key, bool recursive, bool dir) final; + [[nodiscard]] Task> get(std::string_view key, bool recursive, bool sorted, bool watch, tl::optional watchIndex) final; + [[nodiscard]] Task> get(std::string_view key) final; + [[nodiscard]] Task> del(std::string_view key, bool recursive, bool dir) final; /// Not implemented, the leader statistics have variable key names in the response which is harder to decode with glaze. /// \return Always returns EtcdError::JSON_PARSE_ERROR - Task> leaderStatistics() final; - Task> selfStatistics() final; - Task> storeStatistics() final; - Task> version() final; - Task> health() final; + [[nodiscard]] Task> leaderStatistics() final; + [[nodiscard]] Task> selfStatistics() final; + [[nodiscard]] Task> storeStatistics() final; + [[nodiscard]] Task> version() final; + [[nodiscard]] Task> health() final; + [[nodiscard]] Task> authStatus() final; + [[nodiscard]] Task> enableAuth() final; + [[nodiscard]] Task> disableAuth() final; + [[nodiscard]] Task> getUsers() final; + [[nodiscard]] Task> getUserDetails(std::string_view user) final; + void setAuthentication(std::string_view user, std::string_view pass) final; + void clearAuthentication() final; + [[nodiscard]] tl::optional getAuthenticationUser() const final; + [[nodiscard]] Task> createUser(std::string_view user, std::string_view pass) final; + [[nodiscard]] Task> grantUserRoles(std::string_view user, std::vector roles) final; + [[nodiscard]] Task> revokeUserRoles(std::string_view user, std::vector roles) final; + [[nodiscard]] Task> updateUserPassword(std::string_view user, std::string_view pass) final; + [[nodiscard]] Task> deleteUser(std::string_view user) final; + [[nodiscard]] Task> getRoles() final; + [[nodiscard]] Task> getRole(std::string_view role) final; + [[nodiscard]] Task> createRole(std::string_view role, std::vector read_permissions, std::vector write_permissions) final; + [[nodiscard]] Task> grantRolePermissions(std::string_view role, std::vector read_permissions, std::vector write_permissions) final; + [[nodiscard]] Task> revokeRolePermissions(std::string_view role, std::vector read_permissions, std::vector write_permissions) final; + [[nodiscard]] Task> deleteRole(std::string_view role) final; private: Task> start() final; @@ -62,5 +81,6 @@ namespace Ichor { IHttpConnectionService* _mainConn{}; IClientFactory *_clientFactory{}; std::stack _connRequests{}; + tl::optional _auth; }; } diff --git a/include/ichor/services/etcd/IEtcd.h b/include/ichor/services/etcd/IEtcd.h index 29344c0..6c4c18f 100644 --- a/include/ichor/services/etcd/IEtcd.h +++ b/include/ichor/services/etcd/IEtcd.h @@ -2,7 +2,8 @@ #include #include -#include +#include // Glaze does not support tl::optional +#include #include #include #include @@ -13,6 +14,13 @@ namespace Ichor { JSON_PARSE_ERROR, TIMEOUT, CONNECTION_CLOSED_PREMATURELY_TRY_AGAIN, + ROOT_USER_NOT_YET_CREATED, + REMOVING_ROOT_NOT_ALLOWED_WITH_AUTH_ENABLED, + NO_AUTHENTICATION_SET, + UNAUTHORIZED, + NOT_FOUND, + DUPLICATE_PERMISSION_OR_REVOKING_NON_EXISTENT, + CANNOT_DELETE_ROOT_WHILE_AUTH_IS_ENABLED, QUITTING }; @@ -102,6 +110,43 @@ namespace Ichor { std::string etcdcluster; }; + struct EtcdKvReply final { + std::vector read; + std::vector write; + }; + + struct EtcdPermissionsReply final { + EtcdKvReply kv; + }; + + struct EtcdRoleReply final { + std::string role; + EtcdPermissionsReply permissions; + std::optional grant; + std::optional revoke; + }; + + struct EtcdRolesReply final { + std::vector roles; + }; + + struct EtcdUserReply final { + std::string user; + std::optional password; + std::vector roles; + std::optional> grant; + std::optional> revoke; + }; + + struct EtcdUpdateUserReply final { + std::string user; + std::optional> roles; + }; + + struct EtcdUsersReply final { + std::optional> users; + }; + class IEtcd { public: /** @@ -118,7 +163,7 @@ namespace Ichor { * @param in_order set to true to change to a POST request and create in-order keys, most commonly used for queues. * @return Either the EtcdReply or an EtcdError */ - virtual Task> put(std::string_view key, std::string_view value, std::optional previous_value, std::optional previous_index, std::optional previous_exists, std::optional ttl_seconds, bool refresh, bool dir, bool in_order) = 0; + [[nodiscard]] virtual Task> put(std::string_view key, std::string_view value, tl::optional previous_value, tl::optional previous_index, tl::optional previous_exists, tl::optional ttl_seconds, bool refresh, bool dir, bool in_order) = 0; /** * Create or update operation * @@ -128,7 +173,7 @@ namespace Ichor { * @param refresh set to true to only refresh the TTL or value * @return Either the EtcdReply or an EtcdError */ - virtual Task> put(std::string_view key, std::string_view value, std::optional ttl_seconds, bool refresh) = 0; + [[nodiscard]] virtual Task> put(std::string_view key, std::string_view value, tl::optional ttl_seconds, bool refresh) = 0; /** * Create or update operation * @@ -137,7 +182,7 @@ namespace Ichor { * @param ttl_second optional: how many seconds should this key last * @return Either the EtcdReply or an EtcdError */ - virtual Task> put(std::string_view key, std::string_view value, std::optional ttl_seconds) = 0; + [[nodiscard]] virtual Task> put(std::string_view key, std::string_view value, tl::optional ttl_seconds) = 0; /** * Create or update operation * @@ -145,7 +190,7 @@ namespace Ichor { * @param value value to set to * @return Either the EtcdReply or an EtcdError */ - virtual Task> put(std::string_view key, std::string_view value) = 0; + [[nodiscard]] virtual Task> put(std::string_view key, std::string_view value) = 0; /** * Get value for key operation @@ -155,7 +200,7 @@ namespace Ichor { * @param sorted Set to true if * @return Either the EtcdReply or an EtcdError */ - virtual Task> get(std::string_view key, bool recursive, bool sorted, bool watch, std::optional watchIndex) = 0; + [[nodiscard]] virtual Task> get(std::string_view key, bool recursive, bool sorted, bool watch, tl::optional watchIndex) = 0; /** * Get value for key operation @@ -163,7 +208,7 @@ namespace Ichor { * @param key * @return Either the EtcdReply or an EtcdError */ - virtual Task> get(std::string_view key) = 0; + [[nodiscard]] virtual Task> get(std::string_view key) = 0; /** * Delete key operation @@ -173,37 +218,173 @@ namespace Ichor { * @param dir set to true if key is a directory * @return Either the EtcdReply or an EtcdError */ - virtual Task> del(std::string_view key, bool recursive, bool dir) = 0; + [[nodiscard]] virtual Task> del(std::string_view key, bool recursive, bool dir) = 0; /** * Unimplemented * @return */ - virtual Task> leaderStatistics() = 0; + [[nodiscard]] virtual Task> leaderStatistics() = 0; /** * Get statistics of the etcd server that we're connected to * @return */ - virtual Task> selfStatistics() = 0; + [[nodiscard]] virtual Task> selfStatistics() = 0; /** * Get the store statistics of the etcd server that we're connected to * @return */ - virtual Task> storeStatistics() = 0; + [[nodiscard]] virtual Task> storeStatistics() = 0; /** * Get the version of the etcd server that we're connected to * @return */ - virtual Task> version() = 0; + [[nodiscard]] virtual Task> version() = 0; /** * Get the health status of the etcd server that we're connected to * @return */ - virtual Task> health() = 0; + [[nodiscard]] virtual Task> health() = 0; + + /** + * Get auth status + * @return true if auth has been enabled + */ + [[nodiscard]] virtual Task> authStatus() = 0; + + /** + * Enable auth. Requires root user to have been created and for the service to authenticate as the root user. + * @return true if successful + */ + [[nodiscard]] virtual Task> enableAuth() = 0; + + /** + * Disable auth. Requires root user to have been created and for the service to authenticate as the root user. + * @return + */ + [[nodiscard]] virtual Task> disableAuth() = 0; + + /** + * Get a list of all users details + * @return + */ + [[nodiscard]] virtual Task> getUsers() = 0; + + /** + * Get user details for given user + * @param user + * @return + */ + [[nodiscard]] virtual Task> getUserDetails(std::string_view user) = 0; + + /** + * Sets the authentication to use for each request. Caution: might store password in memory. + * @param user + * @param password + */ + virtual void setAuthentication(std::string_view user, std::string_view password) = 0; + + /** + * Clears used authentication + */ + virtual void clearAuthentication() = 0; + + /** + * Gets the user used with the current authentication + */ + [[nodiscard]] virtual tl::optional getAuthenticationUser() const = 0; + + /** + * Create user + * @param user + * @param pass + * @return full user details + */ + [[nodiscard]] virtual Task> createUser(std::string_view user, std::string_view pass) = 0; + + /** + * Grant roles to user + * @param user + * @param roles list of roles for this user + * @return updated full user details + */ + [[nodiscard]] virtual Task> grantUserRoles(std::string_view user, std::vector roles) = 0; + + /** + * Revoke roles of user + * @param user + * @param roles list of roles for this user + * @return updated full user details + */ + [[nodiscard]] virtual Task> revokeUserRoles(std::string_view user, std::vector roles) = 0; + + /** + * Revoke roles of user + * @param user + * @param pass + * @return updated full user details + */ + [[nodiscard]] virtual Task> updateUserPassword(std::string_view user, std::string_view pass) = 0; + + /** + * Delete user + * @param user + * @return + */ + [[nodiscard]] virtual Task> deleteUser(std::string_view user) = 0; + + /** + * Get details for all roles + * @return + */ + [[nodiscard]] virtual Task> getRoles() = 0; + + /** + * Get details for specific role + * @param user + * @return + */ + [[nodiscard]] virtual Task> getRole(std::string_view role) = 0; + + /** + * Create role + * @param role name + * @param read_permissions + * @param write_permissions + * @return full role details + */ + [[nodiscard]] virtual Task> createRole(std::string_view role, std::vector read_permissions, std::vector write_permissions) = 0; + + /** + * Grant role permissions + * @param role name + * @param read_permissions + * @param write_permissions + * @return updated full role details + */ + [[nodiscard]] virtual Task> grantRolePermissions(std::string_view role, std::vector read_permissions, std::vector write_permissions) = 0; + + /** + * Revoke role permissions + * @param role name + * @param read_permissions + * @param write_permissions + * @return updated full role details + */ + [[nodiscard]] virtual Task> revokeRolePermissions(std::string_view role, std::vector read_permissions, std::vector write_permissions) = 0; + + /** + * Delete role + * @param role + * @return + */ + [[nodiscard]] virtual Task> deleteRole(std::string_view role) = 0; + + @@ -225,12 +406,30 @@ struct fmt::formatter { switch(state) { - case Ichor::EtcdError::JSON_PARSE_ERROR: - return fmt::format_to(ctx.out(), "JSON_PARSE_ERROR"); case Ichor::EtcdError::HTTP_RESPONSE_ERROR: return fmt::format_to(ctx.out(), "HTTP_RESPONSE_ERROR"); + case Ichor::EtcdError::JSON_PARSE_ERROR: + return fmt::format_to(ctx.out(), "JSON_PARSE_ERROR"); case Ichor::EtcdError::TIMEOUT: return fmt::format_to(ctx.out(), "TIMEOUT"); + case Ichor::EtcdError::CONNECTION_CLOSED_PREMATURELY_TRY_AGAIN: + return fmt::format_to(ctx.out(), "CONNECTION_CLOSED_PREMATURELY_TRY_AGAIN"); + case Ichor::EtcdError::ROOT_USER_NOT_YET_CREATED: + return fmt::format_to(ctx.out(), "ROOT_USER_NOT_YET_CREATED"); + case Ichor::EtcdError::REMOVING_ROOT_NOT_ALLOWED_WITH_AUTH_ENABLED: + return fmt::format_to(ctx.out(), "REMOVING_ROOT_NOT_ALLOWED_WITH_AUTH_ENABLED"); + case Ichor::EtcdError::NO_AUTHENTICATION_SET: + return fmt::format_to(ctx.out(), "NO_AUTHENTICATION_SET"); + case Ichor::EtcdError::UNAUTHORIZED: + return fmt::format_to(ctx.out(), "UNAUTHORIZED"); + case Ichor::EtcdError::NOT_FOUND: + return fmt::format_to(ctx.out(), "NOT_FOUND"); + case Ichor::EtcdError::DUPLICATE_PERMISSION_OR_REVOKING_NON_EXISTENT: + return fmt::format_to(ctx.out(), "DUPLICATE_PERMISSION_OR_REVOKING_NON_EXISTENT"); + case Ichor::EtcdError::CANNOT_DELETE_ROOT_WHILE_AUTH_IS_ENABLED: + return fmt::format_to(ctx.out(), "CANNOT_DELETE_ROOT_WHILE_AUTH_IS_ENABLED"); + case Ichor::EtcdError::QUITTING: + return fmt::format_to(ctx.out(), "QUITTING"); default: return fmt::format_to(ctx.out(), "error, please file a bug in Ichor"); } diff --git a/include/ichor/services/io/SharedOverThreadsAsyncFileIO.h b/include/ichor/services/io/SharedOverThreadsAsyncFileIO.h index c430677..ae2848a 100644 --- a/include/ichor/services/io/SharedOverThreadsAsyncFileIO.h +++ b/include/ichor/services/io/SharedOverThreadsAsyncFileIO.h @@ -32,6 +32,6 @@ namespace Ichor { static std::mutex _io_mutex; static std::queue> _evts; bool _should_stop{}; - std::optional _io_thread; + tl::optional _io_thread; }; } diff --git a/include/ichor/services/network/http/HttpCommon.h b/include/ichor/services/network/http/HttpCommon.h index cd8843d..8711905 100644 --- a/include/ichor/services/network/http/HttpCommon.h +++ b/include/ichor/services/network/http/HttpCommon.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include namespace Ichor { @@ -181,27 +181,19 @@ namespace Ichor { network_connect_timeout_error = 599 }; - struct HttpHeader { - std::string name{}; - std::string value{}; - - HttpHeader() noexcept = default; - HttpHeader(std::string_view _name, std::string_view _value) noexcept : name(_name), value(_value) {} - }; - struct HttpRequest { std::vector body; HttpMethod method; std::string route; std::string_view address; - std::vector headers; + unordered_map headers; }; struct HttpResponse { bool error; HttpStatus status; - std::optional contentType; + tl::optional contentType; std::vector body; - std::vector headers; + unordered_map headers; }; } diff --git a/include/ichor/services/network/http/HttpConnectionService.h b/include/ichor/services/network/http/HttpConnectionService.h index 9f40aca..8146ec7 100644 --- a/include/ichor/services/network/http/HttpConnectionService.h +++ b/include/ichor/services/network/http/HttpConnectionService.h @@ -24,7 +24,7 @@ namespace Ichor { std::string_view route; AsyncManualResetEvent* event; HttpResponse* response; - std::vector* headers; + unordered_map* headers; std::vector* body; }; } @@ -34,7 +34,7 @@ namespace Ichor { HttpConnectionService(DependencyRegister ®, Properties props); ~HttpConnectionService() final = default; - Task sendAsync(HttpMethod method, std::string_view route, std::vector &&headers, std::vector&& msg) final; + Task sendAsync(HttpMethod method, std::string_view route, unordered_map &&headers, std::vector&& msg) final; Task close() final; diff --git a/include/ichor/services/network/http/IHttpConnectionService.h b/include/ichor/services/network/http/IHttpConnectionService.h index 5a1d008..48b7edc 100644 --- a/include/ichor/services/network/http/IHttpConnectionService.h +++ b/include/ichor/services/network/http/IHttpConnectionService.h @@ -14,7 +14,7 @@ namespace Ichor { * @param msg Usually json, ignored for GET requests * @return response */ - virtual Task sendAsync(HttpMethod method, std::string_view route, std::vector &&headers, std::vector&& msg) = 0; + virtual Task sendAsync(HttpMethod method, std::string_view route, unordered_map &&headers, std::vector&& msg) = 0; /** * Close the connection diff --git a/include/ichor/services/redis/IRedis.h b/include/ichor/services/redis/IRedis.h index 2662d71..4abc047 100644 --- a/include/ichor/services/redis/IRedis.h +++ b/include/ichor/services/redis/IRedis.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include namespace Ichor { @@ -11,10 +11,10 @@ namespace Ichor { }; struct RedisSetOptions { - std::optional EX; - std::optional PX; - std::optional EXAT; - std::optional PXAT; + tl::optional EX; + tl::optional PX; + tl::optional EXAT; + tl::optional PXAT; bool NX{}; bool XX{}; bool KEEPTTL{}; @@ -29,7 +29,7 @@ namespace Ichor { struct RedisGetReply { // null if key not found - std::optional value; + tl::optional value; }; struct RedisIntegerReply { @@ -99,6 +99,79 @@ namespace Ichor { /// \return coroutine with the value of the key after decrement virtual Task> decrBy(std::string_view key, int64_t decr) = 0; + /// Returns the length of the string + /// \param key + /// \return coroutine with the length of the value stored for the key +// virtual Task> strlen(std::string_view key, int64_t decr) = 0; + +// strlen +// multi +// exec +// discard +// getrange +// setrange +// append (multi?) +// append (multi?) +// bgsave +// bgrewriteaof +// lastsave +// client info +// dbsize +// acl setuser +// acl deluser +// acl users +// acl whoami +// acl save +// acl load +// acl log +// acl list +// acl getuser +// acl genpass +// config get +// config resetstat +// config rewrite +// config set +// exists +// expire +// expireat +// keys +// lpush +// lindex +// lset +// lrem +// lrange +// lpop +// lpos +// llen +// linsert +// mget +// mset +// move +// persist +// pexpire +// rename +// rpop +// rpush +// sort +// sort_ro +// select (db) +// sadd +// scard +// sdiff +// sinter +// sismember +// smismember +// smembers +// smove +// spop +// srem +// sunioin +// touch +// ttl +// type +// unlink +// wait? +// waitaof protected: ~IRedis() = default; }; diff --git a/include/ichor/services/serialization/ISerializer.h b/include/ichor/services/serialization/ISerializer.h index 71cedb1..27a3c3e 100644 --- a/include/ichor/services/serialization/ISerializer.h +++ b/include/ichor/services/serialization/ISerializer.h @@ -10,9 +10,9 @@ namespace Ichor { class ISerializer { public: virtual std::vector serialize(T const &obj) = 0; - virtual std::optional deserialize(std::vector &&stream) = 0; + virtual tl::optional deserialize(std::vector &&stream) = 0; protected: ~ISerializer() = default; }; -} \ No newline at end of file +} diff --git a/include/ichor/stl/AsyncSingleThreadedMutex.h b/include/ichor/stl/AsyncSingleThreadedMutex.h index ec87761..5e4477b 100644 --- a/include/ichor/stl/AsyncSingleThreadedMutex.h +++ b/include/ichor/stl/AsyncSingleThreadedMutex.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include // Mutex that prevents access between coroutines on the same thread @@ -56,7 +56,7 @@ namespace Ichor { co_return AsyncSingleThreadedLockGuard{*this}; } - std::optional non_blocking_lock() { + tl::optional non_blocking_lock() { #if defined(ICHOR_USE_HARDENING) || defined(ICHOR_ENABLE_INTERNAL_DEBUGGING) if(!_thread_id) { _thread_id = std::this_thread::get_id(); @@ -66,7 +66,7 @@ namespace Ichor { #endif if(_locked) { - return std::nullopt; + return tl::nullopt; } _locked = true; @@ -92,7 +92,7 @@ namespace Ichor { bool _locked{}; std::queue> _evts{}; #if defined(ICHOR_USE_HARDENING) || defined(ICHOR_ENABLE_INTERNAL_DEBUGGING) - std::optional _thread_id; + tl::optional _thread_id; #endif }; } diff --git a/include/tl/expected.h b/include/tl/expected.h index bfb3832..d88b590 100644 --- a/include/tl/expected.h +++ b/include/tl/expected.h @@ -117,7 +117,7 @@ struct is_trivially_copy_constructible> : std::false_type {}; #endif namespace tl { - template class expected; + template class [[nodiscard]] expected; #ifndef TL_MONOSTATE_INPLACE_MUTEX #define TL_MONOSTATE_INPLACE_MUTEX @@ -129,7 +129,7 @@ namespace tl { static constexpr in_place_t in_place{}; #endif - template class unexpected { + template class [[nodiscard]] unexpected { public: static_assert(!std::is_same::value, "E must not be void"); @@ -1233,7 +1233,7 @@ template struct is_nothrow_swappable : std::true_type {}; /// has been destroyed. The initialization state of the contained object is /// tracked by the expected object. template - class expected : private detail::expected_move_assign_base, + class [[nodiscard]] expected : private detail::expected_move_assign_base, private detail::expected_delete_ctor_base, private detail::expected_delete_assign_base, private detail::expected_default_ctor_base { diff --git a/include/tl/optional.h b/include/tl/optional.h new file mode 100644 index 0000000..826e16b --- /dev/null +++ b/include/tl/optional.h @@ -0,0 +1,2062 @@ + +/// +// optional - An implementation of tl::optional with extensions +// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama) +// +// Documentation available at https://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#ifndef TL_OPTIONAL_HPP +#define TL_OPTIONAL_HPP + +#define TL_OPTIONAL_VERSION_MAJOR 1 +#define TL_OPTIONAL_VERSION_MINOR 1 +#define TL_OPTIONAL_VERSION_PATCH 0 + +#include +#include +#include +#include +#include + +#if (defined(_MSC_VER) && _MSC_VER == 1900) +#define TL_OPTIONAL_MSVC2015 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC55 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions +#define TL_OPTIONAL_NO_CONSTRR + +// GCC < 5 doesn't support some standard C++11 type traits +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::has_trivial_copy_constructor::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::has_trivial_copy_assign::value + +// This one will be different for GCC 5.7 if it's ever supported +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value + +// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks std::vector +// for non-copyable types +#elif (defined(__GNUC__) && __GNUC__ < 8 && \ + !defined(__clang__)) +#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +namespace tl { + namespace detail { + template + struct is_trivially_copy_constructible : std::is_trivially_copy_constructible{}; +#ifdef _GLIBCXX_VECTOR + template + struct is_trivially_copy_constructible> + : std::is_trivially_copy_constructible{}; +#endif + } +} +#endif + +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + tl::detail::is_trivially_copy_constructible::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable::value +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value +#else +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::is_trivially_copy_constructible::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable::value +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value +#endif + +#if __cplusplus > 201103L +#define TL_OPTIONAL_CXX14 +#endif + +// constexpr implies const in C++11, not C++14 +#if (__cplusplus == 201103L || defined(TL_OPTIONAL_MSVC2015) || \ + defined(TL_OPTIONAL_GCC49)) +#define TL_OPTIONAL_11_CONSTEXPR +#else +#define TL_OPTIONAL_11_CONSTEXPR constexpr +#endif + +namespace tl { +#ifndef TL_MONOSTATE_INPLACE_MUTEX +#define TL_MONOSTATE_INPLACE_MUTEX +/// Used to represent an optional with no data; essentially a bool + class monostate {}; + +/// A tag type to tell optional to construct its value in-place + struct in_place_t { + explicit in_place_t() = default; + }; +/// A tag to tell optional to construct its value in-place + static constexpr in_place_t in_place{}; +#endif + + template class [[nodiscard]] optional; + + namespace detail { +#ifndef TL_TRAITS_MUTEX +#define TL_TRAITS_MUTEX +// C++14-style aliases for brevity + template using remove_const_t = typename std::remove_const::type; + template + using remove_reference_t = typename std::remove_reference::type; + template using decay_t = typename std::decay::type; + template + using enable_if_t = typename std::enable_if::type; + template + using conditional_t = typename std::conditional::type; + +// std::conjunction from C++17 + template struct conjunction : std::true_type {}; + template struct conjunction : B {}; + template + struct conjunction + : std::conditional, B>::type {}; + +#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L +#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#endif + +// In C++11 mode, there's an issue in libc++'s std::mem_fn +// which results in a hard-error when using it in a noexcept expression +// in some cases. This is a check to workaround the common failing case. +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND + template struct is_pointer_to_non_const_member_func : std::false_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; + +template struct is_const_or_const_ref : std::false_type{}; +template struct is_const_or_const_ref : std::true_type{}; +template struct is_const_or_const_ref : std::true_type{}; +#endif + +// std::invoke from C++17 +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround + template ::value + && is_const_or_const_ref::value)>, +#endif + typename = enable_if_t>::value>, + int = 0> + constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); + } + + template >::value>> + constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); + } + +// std::invoke_result from C++17 + template struct invoke_result_impl; + + template + struct invoke_result_impl< + F, decltype(detail::invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = decltype(detail::invoke(std::declval(), std::declval()...)); + }; + + template + using invoke_result = invoke_result_impl; + + template + using invoke_result_t = typename invoke_result::type; + +#if defined(_MSC_VER) && _MSC_VER <= 1900 + // TODO make a version which works with MSVC 2015 +template struct is_swappable : std::true_type {}; + +template struct is_nothrow_swappable : std::true_type {}; +#else +// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept + namespace swap_adl_tests { +// if swap ADL finds this then it would call std::swap otherwise (same +// signature) + struct tag {}; + + template tag swap(T &, T &); + template tag swap(T (&a)[N], T (&b)[N]); + +// helper functions to test if an unqualified swap is possible, and if it +// becomes std::swap + template std::false_type can_swap(...) noexcept(false); + template (), std::declval()))> + std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), + std::declval()))); + + template std::false_type uses_std(...); + template + std::is_same(), std::declval())), tag> + uses_std(int); + + template + struct is_std_swap_noexcept + : std::integral_constant::value && + std::is_nothrow_move_assignable::value> {}; + + template + struct is_std_swap_noexcept : is_std_swap_noexcept {}; + + template + struct is_adl_swap_noexcept + : std::integral_constant(0))> {}; + } // namespace swap_adl_tests + + template + struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || + (std::is_move_assignable::value && + std::is_move_constructible::value))> {}; + + template + struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype( + detail::swap_adl_tests::uses_std(0))::value || + is_swappable::value)> {}; + + template + struct is_nothrow_swappable + : std::integral_constant< + bool, + is_swappable::value && + ((decltype(detail::swap_adl_tests::uses_std(0))::value + &&detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> { + }; +#endif +#endif + +// std::void_t from C++17 + template struct voider { using type = void; }; + template using void_t = typename voider::type; + +// Trait for checking if a type is a tl::optional + template struct is_optional_impl : std::false_type {}; + template struct is_optional_impl> : std::true_type {}; + template using is_optional = is_optional_impl>; + +// Change void to tl::monostate + template + using fixup_void = conditional_t::value, monostate, U>; + + template > + using get_map_return = optional>>; + +// Check if invoking F for some Us returns void + template struct returns_void_impl; + template + struct returns_void_impl>, U...> + : std::is_void> {}; + template + using returns_void = returns_void_impl; + + template + using enable_if_ret_void = enable_if_t::value>; + + template + using disable_if_ret_void = enable_if_t::value>; + + template + using enable_forward_value = + detail::enable_if_t::value && + !std::is_same, in_place_t>::value && + !std::is_same, detail::decay_t>::value>; + + template + using enable_from_other = detail::enable_if_t< + std::is_constructible::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value>; + + template + using enable_assign_forward = detail::enable_if_t< + !std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && std::is_assignable::value>; + + template + using enable_assign_from_other = detail::enable_if_t< + std::is_constructible::value && + std::is_assignable::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_assignable &>::value && + !std::is_assignable &&>::value && + !std::is_assignable &>::value && + !std::is_assignable &&>::value>; + +// The storage base manages the actual storage, and correctly propagates +// trivial destruction from T. This case is for when T is not trivially +// destructible. + template ::value> + struct optional_storage_base { + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + : m_dummy(), m_has_value(false) {} + + template + TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) + : m_value(std::forward(u)...), m_has_value(true) {} + + ~optional_storage_base() { + if (m_has_value) { + m_value.~T(); + m_has_value = false; + } + } + + struct dummy {}; + union { + dummy m_dummy; + T m_value; + }; + + bool m_has_value; + }; + +// This case is for when T is trivially destructible. + template struct optional_storage_base { + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + : m_dummy(), m_has_value(false) {} + + template + TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) + : m_value(std::forward(u)...), m_has_value(true) {} + + // No destructor, so this class is trivially destructible + + struct dummy {}; + union { + dummy m_dummy; + T m_value; + }; + + bool m_has_value = false; + }; + +// This base class provides some handy member functions which can be used in +// further derived classes + template struct optional_operations_base : optional_storage_base { + using optional_storage_base::optional_storage_base; + + void hard_reset() noexcept { + get().~T(); + this->m_has_value = false; + } + + template void construct(Args &&... args) { + new (std::addressof(this->m_value)) T(std::forward(args)...); + this->m_has_value = true; + } + + template void assign(Opt &&rhs) { + if (this->has_value()) { + if (rhs.has_value()) { + this->m_value = std::forward(rhs).get(); + } else { + this->m_value.~T(); + this->m_has_value = false; + } + } + + else if (rhs.has_value()) { + construct(std::forward(rhs).get()); + } + } + + bool has_value() const { return this->m_has_value; } + + TL_OPTIONAL_11_CONSTEXPR T &get() & { return this->m_value; } + TL_OPTIONAL_11_CONSTEXPR const T &get() const & { return this->m_value; } + TL_OPTIONAL_11_CONSTEXPR T &&get() && { return std::move(this->m_value); } +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr const T &&get() const && { return std::move(this->m_value); } +#endif + }; + +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T is trivially copy constructible + template + struct optional_copy_base : optional_operations_base { + using optional_operations_base::optional_operations_base; + }; + +// This specialization is for when T is not trivially copy constructible + template + struct optional_copy_base : optional_operations_base { + using optional_operations_base::optional_operations_base; + + optional_copy_base() = default; + optional_copy_base(const optional_copy_base &rhs) + : optional_operations_base() { + if (rhs.has_value()) { + this->construct(rhs.get()); + } else { + this->m_has_value = false; + } + } + + optional_copy_base(optional_copy_base &&rhs) = default; + optional_copy_base &operator=(const optional_copy_base &rhs) = default; + optional_copy_base &operator=(optional_copy_base &&rhs) = default; + }; + +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_constructible. We +// have to make do with a non-trivial move constructor even if T is trivially +// move constructible +#ifndef TL_OPTIONAL_GCC49 + template ::value> + struct optional_move_base : optional_copy_base { + using optional_copy_base::optional_copy_base; + }; +#else + template struct optional_move_base; +#endif + template struct optional_move_base : optional_copy_base { + using optional_copy_base::optional_copy_base; + + optional_move_base() = default; + optional_move_base(const optional_move_base &rhs) = default; + + optional_move_base(optional_move_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value) { + if (rhs.has_value()) { + this->construct(std::move(rhs.get())); + } else { + this->m_has_value = false; + } + } + optional_move_base &operator=(const optional_move_base &rhs) = default; + optional_move_base &operator=(optional_move_base &&rhs) = default; + }; + +// This class manages conditionally having a trivial copy assignment operator + template + struct optional_copy_assign_base : optional_move_base { + using optional_move_base::optional_move_base; + }; + + template + struct optional_copy_assign_base : optional_move_base { + using optional_move_base::optional_move_base; + + optional_copy_assign_base() = default; + optional_copy_assign_base(const optional_copy_assign_base &rhs) = default; + + optional_copy_assign_base(optional_copy_assign_base &&rhs) = default; + optional_copy_assign_base &operator=(const optional_copy_assign_base &rhs) { + this->assign(rhs); + return *this; + } + optional_copy_assign_base & + operator=(optional_copy_assign_base &&rhs) = default; + }; + +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_assignable. We have +// to make do with a non-trivial move assignment operator even if T is trivially +// move assignable +#ifndef TL_OPTIONAL_GCC49 + template ::value + &&std::is_trivially_move_constructible::value + &&std::is_trivially_move_assignable::value> + struct optional_move_assign_base : optional_copy_assign_base { + using optional_copy_assign_base::optional_copy_assign_base; + }; +#else + template struct optional_move_assign_base; +#endif + + template + struct optional_move_assign_base : optional_copy_assign_base { + using optional_copy_assign_base::optional_copy_assign_base; + + optional_move_assign_base() = default; + optional_move_assign_base(const optional_move_assign_base &rhs) = default; + + optional_move_assign_base(optional_move_assign_base &&rhs) = default; + + optional_move_assign_base & + operator=(const optional_move_assign_base &rhs) = default; + + optional_move_assign_base & + operator=(optional_move_assign_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + return *this; + } + }; + +// optional_delete_ctor_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible + template ::value, + bool EnableMove = std::is_move_constructible::value> + struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = default; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; + }; + + template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = delete; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; + }; + + template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = delete; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = default; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; + }; + + template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = delete; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = delete; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; + }; + +// optional_delete_assign_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible + assignable + template ::value && + std::is_copy_assignable::value), + bool EnableMove = (std::is_move_constructible::value && + std::is_move_assignable::value)> + struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = default; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = default; + }; + + template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = default; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = delete; + }; + + template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = delete; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = default; + }; + + template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = delete; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = delete; + }; + + } // namespace detail + +/// A tag type to represent an empty optional + struct nullopt_t { + struct do_not_use {}; + constexpr explicit nullopt_t(do_not_use, do_not_use) noexcept {} + }; +/// Represents an empty optional + static constexpr nullopt_t nullopt{nullopt_t::do_not_use{}, + nullopt_t::do_not_use{}}; + + class bad_optional_access : public std::exception { + public: + bad_optional_access() = default; + const char *what() const noexcept { return "Optional has no value"; } + }; + +/// An optional object is an object that contains the storage for another +/// object and manages the lifetime of this contained object, if any. The +/// contained object may be initialized after the optional object has been +/// initialized, and may be destroyed before the optional object has been +/// destroyed. The initialization state of the contained object is tracked by +/// the optional object. + template + class [[nodiscard]] optional : private detail::optional_move_assign_base, + private detail::optional_delete_ctor_base, + private detail::optional_delete_assign_base { + using base = detail::optional_move_assign_base; + + static_assert(!std::is_same::value, + "instantiation of optional with in_place_t is ill-formed"); + static_assert(!std::is_same, nullopt_t>::value, + "instantiation of optional with nullopt_t is ill-formed"); + + public: +// The different versions for C++14 and 11 are needed because deduced return +// types are not SFINAE-safe. This provides better support for things like +// generic lambdas. C.f. +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } + + template constexpr auto and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template constexpr auto and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#else + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } + + template + constexpr detail::invoke_result_t and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr detail::invoke_result_t and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto map(F &&f) const & { + return optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto map(F &&f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto transform(F&& f) const & { + return optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto transform(F&& f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const & { + return optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + + /// Calls `f` if the optional is empty + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + return has_value() ? *this : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) && { + return has_value() ? std::move(*this) : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) const & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) const & { + return has_value() ? *this : std::forward(f)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template * = nullptr> + optional or_else(F &&f) const && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional or_else(F &&f) const && { + return has_value() ? std::move(*this) : std::forward(f)(); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise returns `u`. + template U map_or(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template U map_or(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise calls + /// `u` and returns the result. + template + detail::invoke_result_t map_or_else(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } +#endif + + /// Returns `u` if `*this` has a value, otherwise an empty optional. + template + constexpr optional::type> conjunction(U &&u) const { + using result = optional>; + return has_value() ? result{u} : result{nullopt}; + } + + /// Returns `rhs` if `*this` is empty, otherwise the current value. + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) & { + return has_value() ? *this : rhs; + } + + constexpr optional disjunction(const optional &rhs) const & { + return has_value() ? *this : rhs; + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) && { + return has_value() ? std::move(*this) : rhs; + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(const optional &rhs) const && { + return has_value() ? std::move(*this) : rhs; + } +#endif + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) & { + return has_value() ? *this : std::move(rhs); + } + + constexpr optional disjunction(optional &&rhs) const & { + return has_value() ? *this : std::move(rhs); + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) && { + return has_value() ? std::move(*this) : std::move(rhs); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(optional &&rhs) const && { + return has_value() ? std::move(*this) : std::move(rhs); + } +#endif + + /// Takes the value out of the optional, leaving it empty + optional take() { + optional ret = std::move(*this); + reset(); + return ret; + } + + using value_type = T; + + /// Constructs an optional that does not contain a value. + constexpr optional() noexcept = default; + + constexpr optional(nullopt_t) noexcept {} + + /// Copy constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(const optional &rhs) = default; + + /// Move constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(optional &&rhs) = default; + + /// Constructs the stored value in-place using the given arguments. + template + constexpr explicit optional( + detail::enable_if_t::value, in_place_t>, + Args &&... args) + : base(in_place, std::forward(args)...) {} + + template + TL_OPTIONAL_11_CONSTEXPR explicit optional( + detail::enable_if_t &, + Args &&...>::value, + in_place_t>, + std::initializer_list il, Args &&... args) { + this->construct(il, std::forward(args)...); + } + + /// Constructs the stored value with `u`. + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::enable_forward_value * = nullptr> + constexpr optional(U &&u) : base(in_place, std::forward(u)) {} + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::enable_forward_value * = nullptr> + constexpr explicit optional(U &&u) : base(in_place, std::forward(u)) {} + + /// Converting copy constructor. + template < + class U, detail::enable_from_other * = nullptr, + detail::enable_if_t::value> * = nullptr> + optional(const optional &rhs) { + if (rhs.has_value()) { + this->construct(*rhs); + } + } + + template * = nullptr, + detail::enable_if_t::value> * = + nullptr> + explicit optional(const optional &rhs) { + if (rhs.has_value()) { + this->construct(*rhs); + } + } + + /// Converting move constructor. + template < + class U, detail::enable_from_other * = nullptr, + detail::enable_if_t::value> * = nullptr> + optional(optional &&rhs) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + } + + template < + class U, detail::enable_from_other * = nullptr, + detail::enable_if_t::value> * = nullptr> + explicit optional(optional &&rhs) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + } + + /// Destroys the stored value if there is one. + ~optional() = default; + + /// Assignment to empty. + /// + /// Destroys the current value if there is one. + optional &operator=(nullopt_t) noexcept { + if (has_value()) { + this->m_value.~T(); + this->m_has_value = false; + } + + return *this; + } + + /// Copy assignment. + /// + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + optional &operator=(const optional &rhs) = default; + + /// Move assignment. + /// + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + optional &operator=(optional &&rhs) = default; + + /// Assigns the stored value from `u`, destroying the old value if there was + /// one. + template * = nullptr> + optional &operator=(U &&u) { + if (has_value()) { + this->m_value = std::forward(u); + } else { + this->construct(std::forward(u)); + } + + return *this; + } + + /// Converting copy assignment operator. + /// + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + template * = nullptr> + optional &operator=(const optional &rhs) { + if (has_value()) { + if (rhs.has_value()) { + this->m_value = *rhs; + } else { + this->hard_reset(); + } + } + + else if (rhs.has_value()) { + this->construct(*rhs); + } + + return *this; + } + + // TODO check exception guarantee + /// Converting move assignment operator. + /// + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + template * = nullptr> + optional &operator=(optional &&rhs) { + if (has_value()) { + if (rhs.has_value()) { + this->m_value = std::move(*rhs); + } else { + this->hard_reset(); + } + } + + else if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + + return *this; + } + + /// Constructs the value in-place, destroying the current one if there is + /// one. + template T &emplace(Args &&... args) { + static_assert(std::is_constructible::value, + "T must be constructible with Args"); + + *this = nullopt; + this->construct(std::forward(args)...); + return value(); + } + + template + detail::enable_if_t< + std::is_constructible &, Args &&...>::value, + T &> + emplace(std::initializer_list il, Args &&... args) { + *this = nullopt; + this->construct(il, std::forward(args)...); + return value(); + } + + /// Swaps this optional with the other. + /// + /// If neither optionals have a value, nothing happens. + /// If both have a value, the values are swapped. + /// If one has a value, it is moved to the other and the movee is left + /// valueless. + void + swap(optional &rhs) noexcept(std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value) { + using std::swap; + if (has_value()) { + if (rhs.has_value()) { + swap(**this, *rhs); + } else { + new (std::addressof(rhs.m_value)) T(std::move(this->m_value)); + this->m_value.T::~T(); + } + } else if (rhs.has_value()) { + new (std::addressof(this->m_value)) T(std::move(rhs.m_value)); + rhs.m_value.T::~T(); + } + swap(this->m_has_value, rhs.m_has_value); + } + + /// Returns a pointer to the stored value + constexpr const T *operator->() const { + return std::addressof(this->m_value); + } + + TL_OPTIONAL_11_CONSTEXPR T *operator->() { + return std::addressof(this->m_value); + } + + /// Returns the stored value + TL_OPTIONAL_11_CONSTEXPR T &operator*() & { return this->m_value; } + + constexpr const T &operator*() const & { return this->m_value; } + + TL_OPTIONAL_11_CONSTEXPR T &&operator*() && { + return std::move(this->m_value); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr const T &&operator*() const && { return std::move(this->m_value); } +#endif + + /// Returns whether or not the optional has a value + constexpr bool has_value() const noexcept { return this->m_has_value; } + + constexpr explicit operator bool() const noexcept { + return this->m_has_value; + } + + /// Returns the contained value if there is one, otherwise throws bad_optional_access + TL_OPTIONAL_11_CONSTEXPR T &value() & { + if (has_value()) + return this->m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR const T &value() const & { + if (has_value()) + return this->m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR T &&value() && { + if (has_value()) + return std::move(this->m_value); + throw bad_optional_access(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + TL_OPTIONAL_11_CONSTEXPR const T &&value() const && { + if (has_value()) + return std::move(this->m_value); + throw bad_optional_access(); + } +#endif + + /// Returns the stored value if there is one, otherwise returns `u` + template constexpr T value_or(U &&u) const & { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + template TL_OPTIONAL_11_CONSTEXPR T value_or(U &&u) && { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move constructible and convertible from U"); + return has_value() ? std::move(**this) : static_cast(std::forward(u)); + } + + /// Destroys the stored value if one exists, making the optional empty + void reset() noexcept { + if (has_value()) { + this->m_value.~T(); + this->m_has_value = false; + } + } + }; // namespace tl + +/// Compares two optional objects + template + inline constexpr bool operator==(const optional &lhs, + const optional &rhs) { + return lhs.has_value() == rhs.has_value() && + (!lhs.has_value() || *lhs == *rhs); + } + template + inline constexpr bool operator!=(const optional &lhs, + const optional &rhs) { + return lhs.has_value() != rhs.has_value() || + (lhs.has_value() && *lhs != *rhs); + } + template + inline constexpr bool operator<(const optional &lhs, + const optional &rhs) { + return rhs.has_value() && (!lhs.has_value() || *lhs < *rhs); + } + template + inline constexpr bool operator>(const optional &lhs, + const optional &rhs) { + return lhs.has_value() && (!rhs.has_value() || *lhs > *rhs); + } + template + inline constexpr bool operator<=(const optional &lhs, + const optional &rhs) { + return !lhs.has_value() || (rhs.has_value() && *lhs <= *rhs); + } + template + inline constexpr bool operator>=(const optional &lhs, + const optional &rhs) { + return !rhs.has_value() || (lhs.has_value() && *lhs >= *rhs); + } + +/// Compares an optional to a `nullopt` + template + inline constexpr bool operator==(const optional &lhs, nullopt_t) noexcept { + return !lhs.has_value(); + } + template + inline constexpr bool operator==(nullopt_t, const optional &rhs) noexcept { + return !rhs.has_value(); + } + template + inline constexpr bool operator!=(const optional &lhs, nullopt_t) noexcept { + return lhs.has_value(); + } + template + inline constexpr bool operator!=(nullopt_t, const optional &rhs) noexcept { + return rhs.has_value(); + } + template + inline constexpr bool operator<(const optional &, nullopt_t) noexcept { + return false; + } + template + inline constexpr bool operator<(nullopt_t, const optional &rhs) noexcept { + return rhs.has_value(); + } + template + inline constexpr bool operator<=(const optional &lhs, nullopt_t) noexcept { + return !lhs.has_value(); + } + template + inline constexpr bool operator<=(nullopt_t, const optional &) noexcept { + return true; + } + template + inline constexpr bool operator>(const optional &lhs, nullopt_t) noexcept { + return lhs.has_value(); + } + template + inline constexpr bool operator>(nullopt_t, const optional &) noexcept { + return false; + } + template + inline constexpr bool operator>=(const optional &, nullopt_t) noexcept { + return true; + } + template + inline constexpr bool operator>=(nullopt_t, const optional &rhs) noexcept { + return !rhs.has_value(); + } + +/// Compares the optional with a value. + template + inline constexpr bool operator==(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs == rhs : false; + } + template + inline constexpr bool operator==(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs == *rhs : false; + } + template + inline constexpr bool operator!=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs != rhs : true; + } + template + inline constexpr bool operator!=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs != *rhs : true; + } + template + inline constexpr bool operator<(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs < rhs : true; + } + template + inline constexpr bool operator<(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs < *rhs : false; + } + template + inline constexpr bool operator<=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs <= rhs : true; + } + template + inline constexpr bool operator<=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs <= *rhs : false; + } + template + inline constexpr bool operator>(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs > rhs : false; + } + template + inline constexpr bool operator>(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs > *rhs : true; + } + template + inline constexpr bool operator>=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs >= rhs : false; + } + template + inline constexpr bool operator>=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs >= *rhs : true; + } + + template ::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + void swap(optional &lhs, + optional &rhs) noexcept(noexcept(lhs.swap(rhs))) { + return lhs.swap(rhs); + } + + namespace detail { + struct i_am_secret {}; + } // namespace detail + + template ::value, + detail::decay_t, T>> + inline constexpr optional make_optional(U &&v) { + return optional(std::forward(v)); + } + + template + inline constexpr optional make_optional(Args &&... args) { + return optional(in_place, std::forward(args)...); + } + template + inline constexpr optional make_optional(std::initializer_list il, + Args &&... args) { + return optional(in_place, il, std::forward(args)...); + } + +#if __cplusplus >= 201703L + template optional(T)->optional; +#endif + +/// \exclude + namespace detail { +#ifdef TL_OPTIONAL_CXX14 + template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + constexpr auto optional_map_impl(Opt &&opt, F &&f) { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); + } + + template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + auto optional_map_impl(Opt &&opt, F &&f) { + if (opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return make_optional(monostate{}); + } + + return optional(nullopt); + } +#else + template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto optional_map_impl(Opt &&opt, F &&f) -> optional { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); +} + +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto optional_map_impl(Opt &&opt, F &&f) -> optional { + if (opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return monostate{}; + } + + return nullopt; +} +#endif + } // namespace detail + +/// Specialization for when `T` is a reference. `optional` acts similarly +/// to a `T*`, but provides more operations and shows intent more clearly. + template class optional { + public: +// The different versions for C++14 and 11 are needed because deduced return +// types are not SFINAE-safe. This provides better support for things like +// generic lambdas. C.f. +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template constexpr auto and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template constexpr auto and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } +#endif +#else + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + constexpr detail::invoke_result_t and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr detail::invoke_result_t and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto map(F &&f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto map(F &&f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto transform(F&& f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto transform(F&& f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + /// \group map + /// \synopsis template auto transform(F &&f) &&; + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + + /// Calls `f` if the optional is empty + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + return has_value() ? *this : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) && { + return has_value() ? std::move(*this) : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) const & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) const & { + return has_value() ? *this : std::forward(f)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template * = nullptr> + optional or_else(F &&f) const && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional or_else(F &&f) const && { + return has_value() ? std::move(*this) : std::forward(f)(); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise returns `u` + template U map_or(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template U map_or(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise calls + /// `u` and returns the result. + template + detail::invoke_result_t map_or_else(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } +#endif + + /// Returns `u` if `*this` has a value, otherwise an empty optional. + template + constexpr optional::type> conjunction(U &&u) const { + using result = optional>; + return has_value() ? result{u} : result{nullopt}; + } + + /// Returns `rhs` if `*this` is empty, otherwise the current value. + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) & { + return has_value() ? *this : rhs; + } + + constexpr optional disjunction(const optional &rhs) const & { + return has_value() ? *this : rhs; + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) && { + return has_value() ? std::move(*this) : rhs; + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(const optional &rhs) const && { + return has_value() ? std::move(*this) : rhs; + } +#endif + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) & { + return has_value() ? *this : std::move(rhs); + } + + constexpr optional disjunction(optional &&rhs) const & { + return has_value() ? *this : std::move(rhs); + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) && { + return has_value() ? std::move(*this) : std::move(rhs); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(optional &&rhs) const && { + return has_value() ? std::move(*this) : std::move(rhs); + } +#endif + + /// Takes the value out of the optional, leaving it empty + optional take() { + optional ret = std::move(*this); + reset(); + return ret; + } + + using value_type = T &; + + /// Constructs an optional that does not contain a value. + constexpr optional() noexcept : m_value(nullptr) {} + + constexpr optional(nullopt_t) noexcept : m_value(nullptr) {} + + /// Copy constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(const optional &rhs) noexcept = default; + + /// Move constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(optional &&rhs) = default; + + /// Constructs the stored value with `u`. + template >::value> + * = nullptr> + constexpr optional(U &&u) noexcept : m_value(std::addressof(u)) { + static_assert(std::is_lvalue_reference::value, "U must be an lvalue"); + } + + template + constexpr explicit optional(const optional &rhs) noexcept : optional(*rhs) {} + + /// No-op + ~optional() = default; + + /// Assignment to empty. + /// + /// Destroys the current value if there is one. + optional &operator=(nullopt_t) noexcept { + m_value = nullptr; + return *this; + } + + /// Copy assignment. + /// + /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise + /// resets the stored value in `*this`. + optional &operator=(const optional &rhs) = default; + + /// Rebinds this optional to `u`. + template >::value> + * = nullptr> + optional &operator=(U &&u) { + static_assert(std::is_lvalue_reference::value, "U must be an lvalue"); + m_value = std::addressof(u); + return *this; + } + + /// Converting copy assignment operator. + /// + /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise + /// resets the stored value in `*this`. + template optional &operator=(const optional &rhs) noexcept { + m_value = std::addressof(rhs.value()); + return *this; + } + + /// Rebinds this optional to `u`. + template >::value> + * = nullptr> + optional &emplace(U &&u) noexcept { + return *this = std::forward(u); + } + + void swap(optional &rhs) noexcept { std::swap(m_value, rhs.m_value); } + + /// Returns a pointer to the stored value + constexpr const T *operator->() const noexcept { return m_value; } + + TL_OPTIONAL_11_CONSTEXPR T *operator->() noexcept { return m_value; } + + /// Returns the stored value + TL_OPTIONAL_11_CONSTEXPR T &operator*() noexcept { return *m_value; } + + constexpr const T &operator*() const noexcept { return *m_value; } + + constexpr bool has_value() const noexcept { return m_value != nullptr; } + + constexpr explicit operator bool() const noexcept { + return m_value != nullptr; + } + + /// Returns the contained value if there is one, otherwise throws bad_optional_access + TL_OPTIONAL_11_CONSTEXPR T &value() { + if (has_value()) + return *m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR const T &value() const { + if (has_value()) + return *m_value; + throw bad_optional_access(); + } + + /// Returns the stored value if there is one, otherwise returns `u` + template constexpr T value_or(U &&u) const & noexcept { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// \group value_or + template TL_OPTIONAL_11_CONSTEXPR T value_or(U &&u) && noexcept { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// Destroys the stored value if one exists, making the optional empty + void reset() noexcept { m_value = nullptr; } + + private: + T *m_value; + }; // namespace tl + + + +} // namespace tl + +namespace std { +// TODO SFINAE + template struct hash> { + ::std::size_t operator()(const tl::optional &o) const { + if (!o.has_value()) + return 0; + + return std::hash>()(*o); + } +}; +} // namespace std + +#endif diff --git a/src/base64/base64.cpp b/src/base64/base64.cpp new file mode 100644 index 0000000..6b8a9d6 --- /dev/null +++ b/src/base64/base64.cpp @@ -0,0 +1,132 @@ +/* + base64.cpp and base64.h + + Copyright (C) 2004-2017 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +#if defined( __GNUC__ ) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + +std::string base64_encode(unsigned char const* bytes_to_encode, uint64_t in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = ( char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while(i++ < 3) + ret += '='; + + } + + return ret; + +} + +std::string base64_decode(std::string const& encoded_string) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4]; + unsigned char char_array_3[3]; + std::string ret; + + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = ( char_array_4[0] << 2 ) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +} + +#if defined( __GNUC__ ) +#pragma GCC diagnostic pop +#endif diff --git a/src/ichor/DependencyManager.cpp b/src/ichor/DependencyManager.cpp index 0688777..19b5178 100644 --- a/src/ichor/DependencyManager.cpp +++ b/src/ichor/DependencyManager.cpp @@ -1109,7 +1109,7 @@ Ichor::unordered_map Ichor::DependencyManager return svcs; } -std::optional Ichor::DependencyManager::getImplementationNameFor(uint64_t serviceId) const noexcept { +tl::optional Ichor::DependencyManager::getImplementationNameFor(uint64_t serviceId) const noexcept { if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { if (this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? std::terminate(); diff --git a/src/services/etcd/EtcdV2Service.cpp b/src/services/etcd/EtcdV2Service.cpp index 7c2e591..fc17938 100644 --- a/src/services/etcd/EtcdV2Service.cpp +++ b/src/services/etcd/EtcdV2Service.cpp @@ -14,18 +14,9 @@ #include #include #include - -// Glaze uses different conventions than Ichor, ignore them to prevent being spammed by warnings -#if defined( __GNUC__ ) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wsign-conversion" -# pragma GCC diagnostic ignored "-Wshadow" -# pragma GCC diagnostic ignored "-Wconversion" -#endif -#include -#if defined( __GNUC__ ) -# pragma GCC diagnostic pop -#endif +#include +#include +#include template<> struct fmt::formatter { @@ -132,105 +123,181 @@ struct fmt::formatter { } }; -template <> -struct glz::meta { - using T = Ichor::EtcdReplyNode; - static constexpr auto value = object( - "createdIndex", &T::createdIndex, - "modifiedIndex", &T::modifiedIndex, - "key", &T::key, - "value", &T::value, - "ttl", &T::ttl, - "expiration", &T::expiration, - "dir", &T::dir, - "nodes", &T::nodes - ); -}; - -template <> -struct glz::meta { - using T = Ichor::EtcdReply; - static constexpr auto value = object( - "action", &T::action, - "node", &T::node, - "prevNode", &T::prevNode, - "index", &T::index, - "cause", &T::cause, - "errorCode", &T::errorCode, - "message", &T::message - ); -}; - -template <> -struct glz::meta { - using T = Ichor::EtcdLeaderInfoStats; - static constexpr auto value = object( - "leader", &T::leader, - "uptime", &T::uptime, - "startTime", &T::startTime - ); -}; - -template <> -struct glz::meta { - using T = Ichor::EtcdSelfStats; - static constexpr auto value = object( - "name", &T::name, - "id", &T::id, - "state", &T::state, - "startTime", &T::startTime, - "leaderInfo", &T::leaderInfo, - "recvAppendRequestCnt", &T::recvAppendRequestCnt, - "sendAppendRequestCnt", &T::sendAppendRequestCnt - ); -}; - -template <> -struct glz::meta { - using T = Ichor::EtcdStoreStats; - static constexpr auto value = object( - "compareAndSwapFail", &T::compareAndSwapFail, - "compareAndSwapSuccess", &T::compareAndSwapSuccess, - "compareAndDeleteSuccess", &T::compareAndDeleteSuccess, - "compareAndDeleteFail", &T::compareAndDeleteFail, - "createFail", &T::createFail, - "createSuccess", &T::createSuccess, - "deleteFail", &T::deleteFail, - "deleteSuccess", &T::deleteSuccess, - "expireCount", &T::expireCount, - "getsFail", &T::getsFail, - "getsSuccess", &T::getsSuccess, - "setsFail", &T::setsFail, - "setsSuccess", &T::setsSuccess, - "updateFail", &T::updateFail, - "updateSuccess", &T::updateSuccess, - "watchers", &T::watchers - ); -}; +//template <> +//struct glz::meta { +// using T = Ichor::EtcdReplyNode; +// static constexpr auto value = object( +// "createdIndex", &T::createdIndex, +// "modifiedIndex", &T::modifiedIndex, +// "key", &T::key, +// "value", &T::value, +// "ttl", &T::ttl, +// "expiration", &T::expiration, +// "dir", &T::dir, +// "nodes", &T::nodes +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdReply; +// static constexpr auto value = object( +// "action", &T::action, +// "node", &T::node, +// "prevNode", &T::prevNode, +// "index", &T::index, +// "cause", &T::cause, +// "errorCode", &T::errorCode, +// "message", &T::message +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdLeaderInfoStats; +// static constexpr auto value = object( +// "leader", &T::leader, +// "uptime", &T::uptime, +// "startTime", &T::startTime +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdSelfStats; +// static constexpr auto value = object( +// "name", &T::name, +// "id", &T::id, +// "state", &T::state, +// "startTime", &T::startTime, +// "leaderInfo", &T::leaderInfo, +// "recvAppendRequestCnt", &T::recvAppendRequestCnt, +// "sendAppendRequestCnt", &T::sendAppendRequestCnt +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdStoreStats; +// static constexpr auto value = object( +// "compareAndSwapFail", &T::compareAndSwapFail, +// "compareAndSwapSuccess", &T::compareAndSwapSuccess, +// "compareAndDeleteSuccess", &T::compareAndDeleteSuccess, +// "compareAndDeleteFail", &T::compareAndDeleteFail, +// "createFail", &T::createFail, +// "createSuccess", &T::createSuccess, +// "deleteFail", &T::deleteFail, +// "deleteSuccess", &T::deleteSuccess, +// "expireCount", &T::expireCount, +// "getsFail", &T::getsFail, +// "getsSuccess", &T::getsSuccess, +// "setsFail", &T::setsFail, +// "setsSuccess", &T::setsSuccess, +// "updateFail", &T::updateFail, +// "updateSuccess", &T::updateSuccess, +// "watchers", &T::watchers +// ); +//}; namespace Ichor { struct EtcdHealthReply final { std::string health; }; + struct EtcdEnableAuthReply final { + bool enabled; + }; } -template <> -struct glz::meta { - using T = Ichor::EtcdHealthReply; - static constexpr auto value = object( - "health", &T::health - ); -}; - -template <> -struct glz::meta { - using T = Ichor::EtcdVersionReply; - static constexpr auto value = object( - "etcdserver", &T::etcdserver, - "etcdcluster", &T::etcdcluster - ); -}; +//template <> +//struct glz::meta { +// using T = Ichor::EtcdHealthReply; +// static constexpr auto value = object( +// "health", &T::health +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdEnableAuthReply; +// static constexpr auto value = object( +// "enabled", &T::enabled +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdVersionReply; +// static constexpr auto value = object( +// "etcdserver", &T::etcdserver, +// "etcdcluster", &T::etcdcluster +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdKvReply; +// static constexpr auto value = object( +// "read", &T::read, +// "write", &T::write +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdPermissionsReply; +// static constexpr auto value = object( +// "kv", &T::kv +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdRoleReply; +// static constexpr auto value = object( +// "role", &T::role, +// "permissions", &T::permissions, +// "grant", &T::grant, +// "revoke", &T::revoke +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdRolesReply; +// static constexpr auto value = object( +// "roles", &T::roles +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdUserReply; +// static constexpr auto value = object( +// "user", &T::user, +// "password", &T::password, +// "roles", &T::roles, +// "grant", &T::grant, +// "revoke", &T::revoke +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdUpdateUserReply; +// static constexpr auto value = object( +// "user", &T::user, +// "roles", &T::roles +// ); +//}; +// +//template <> +//struct glz::meta { +// using T = Ichor::EtcdUsersReply; +// static constexpr auto value = object( +// "users", &T::users +// ); +//}; Ichor::EtcdV2Service::EtcdV2Service(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { reg.registerDependency(this, false, getProperties()); @@ -301,7 +368,7 @@ void Ichor::EtcdV2Service::removeDependencyInstance(IClientFactory&, IService&) _clientFactory = nullptr; } -Ichor::Task> Ichor::EtcdV2Service::put(std::string_view key, std::string_view value, std::optional previous_value, std::optional previous_index, std::optional previous_exists, std::optional ttl_second, bool refresh, bool dir, bool in_order) { +Ichor::Task> Ichor::EtcdV2Service::put(std::string_view key, std::string_view value, tl::optional previous_value, tl::optional previous_index, tl::optional previous_exists, tl::optional ttl_second, bool refresh, bool dir, bool in_order) { std::vector msg_buf; fmt::format_to(std::back_inserter(msg_buf), "value={}", value); if(ttl_second) { @@ -323,10 +390,14 @@ Ichor::Task> Ichor::EtcdV2Servi fmt::format_to(std::back_inserter(msg_buf), "&dir=true"); } ICHOR_LOG_TRACE(_logger, "put body {}", std::string_view{(char*)msg_buf.data(), msg_buf.size()}); - std::vector headers{HttpHeader{"Content-Type", "application/x-www-form-urlencoded"}}; + unordered_map headers{{"Content-Type", "application/x-www-form-urlencoded"}}; + + if(_auth) { + headers.emplace("Authorization", *_auth); + } HttpMethod method = in_order ? HttpMethod::post : HttpMethod::put; auto http_reply = co_await _mainConn->sendAsync(method, fmt::format("/v2/keys/{}", key), std::move(headers), std::move(msg_buf)); - ICHOR_LOG_TRACE(_logger, "put json {}", (char*)http_reply.body.data()); + ICHOR_LOG_TRACE(_logger, "put status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : http_reply.body.empty() ? "" : (char*)http_reply.body.data()); if(http_reply.status != HttpStatus::ok && http_reply.status != HttpStatus::created && http_reply.status != HttpStatus::forbidden && http_reply.status != HttpStatus::precondition_failed && http_reply.status != HttpStatus::not_found) { ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", fmt::format("/v2/keys/{}", key), (int)http_reply.status); @@ -338,36 +409,36 @@ Ichor::Task> Ichor::EtcdV2Servi if(err) { ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); - ICHOR_LOG_ERROR(_logger, "json {}", (char*)http_reply.body.data()); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : http_reply.body.empty() ? "" : (char*)http_reply.body.data()); co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); } - for(auto &header : http_reply.headers) { - // TODO refactor headers to unordered_map instead of vector - if(!header.name.empty() && header.name[0] == 'X') { - if (header.name == "X-Etcd-Cluster-Id") { - etcd_reply.x_etcd_cluster_id = header.value; - } - if (header.name == "X-Etcd-Index") { - etcd_reply.x_etcd_index = Ichor::FastAtoiCompare(header.value.c_str()); - } - if (header.name == "X-Raft-Index") { - etcd_reply.x_raft_index = Ichor::FastAtoiCompare(header.value.c_str()); - } - if (header.name == "X-Raft-Term") { - etcd_reply.x_raft_term = Ichor::FastAtoiCompare(header.value.c_str()); - } - } + auto cluster_id = http_reply.headers.find("X-Etcd-Cluster-Id"); + auto etcd_index = http_reply.headers.find("X-Etcd-Index"); + auto raft_index = http_reply.headers.find("X-Raft-Index"); + auto raft_term = http_reply.headers.find("X-Raft-Term"); + + if(cluster_id != end(http_reply.headers)) { + etcd_reply.x_etcd_cluster_id = cluster_id->second; + } + if(etcd_index != end(http_reply.headers)) { + etcd_reply.x_etcd_index = Ichor::FastAtoiCompareu(etcd_index->second.c_str()); + } + if(raft_index != end(http_reply.headers)) { + etcd_reply.x_raft_index = Ichor::FastAtoiCompareu(raft_index->second.c_str()); + } + if(raft_term != end(http_reply.headers)) { + etcd_reply.x_raft_term = Ichor::FastAtoiCompareu(raft_term->second.c_str()); } co_return etcd_reply; } -Ichor::Task> Ichor::EtcdV2Service::put(std::string_view key, std::string_view value, std::optional ttl_second, bool refresh) { +Ichor::Task> Ichor::EtcdV2Service::put(std::string_view key, std::string_view value, tl::optional ttl_second, bool refresh) { return put(key, value, {}, {}, {}, ttl_second, refresh, false, false); } -Ichor::Task> Ichor::EtcdV2Service::put(std::string_view key, std::string_view value, std::optional ttl_second) { +Ichor::Task> Ichor::EtcdV2Service::put(std::string_view key, std::string_view value, tl::optional ttl_second) { return put(key, value, {}, {}, {}, ttl_second, false, false, false); } @@ -375,13 +446,13 @@ Ichor::Task> Ichor::EtcdV2Servi return put(key, value, {}, {}, {}, {}, false, false, false); } -Ichor::Task> Ichor::EtcdV2Service::get(std::string_view key, bool recursive, bool sorted, bool watch, std::optional watchIndex) { +Ichor::Task> Ichor::EtcdV2Service::get(std::string_view key, bool recursive, bool sorted, bool watch, tl::optional watchIndex) { std::string url{fmt::format("/v2/keys/{}?", key)}; // using watches blocks the connection and thus blocks every other call // This implementation requests a new HttpConnection specifically for this and cleans it up afterwards IHttpConnectionService *connToUse = _mainConn; - std::optional connIdToClean{}; + tl::optional connIdToClean{}; ScopeGuard sg{[this, &connIdToClean]() { if(connIdToClean && _clientFactory != nullptr) { _clientFactory->removeConnection(this, *connIdToClean); @@ -407,16 +478,20 @@ Ichor::Task> Ichor::EtcdV2Servi if(watch) { ConnRequest &request = _connRequests.emplace(); connIdToClean = _clientFactory->createNewConnection(this, getProperties()); - ICHOR_LOG_TRACE(_logger, "connIdToClean {}", *connIdToClean); co_await request.event; - ICHOR_LOG_TRACE(_logger, "get url {} - conn {}", url, request.conn == nullptr ? false : true); if(request.conn == nullptr) { co_return tl::unexpected(EtcdError::QUITTING); } connToUse = request.conn; } - auto http_reply = co_await connToUse->sendAsync(HttpMethod::get, url, {}, {}); + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + auto http_reply = co_await connToUse->sendAsync(HttpMethod::get, url, std::move(headers), {}); + ICHOR_LOG_TRACE(_logger, "get status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : http_reply.body.empty() ? "" : (char*)http_reply.body.data()); if(http_reply.status != HttpStatus::ok && http_reply.status != HttpStatus::not_found) { ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); @@ -427,14 +502,12 @@ Ichor::Task> Ichor::EtcdV2Servi co_return tl::unexpected(Ichor::EtcdError::CONNECTION_CLOSED_PREMATURELY_TRY_AGAIN); } - ICHOR_LOG_TRACE(_logger, "get json {}", (char*)http_reply.body.data()); - EtcdReply etcd_reply; auto err = glz::read_json(etcd_reply, http_reply.body); if(err) { ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); - ICHOR_LOG_ERROR(_logger, "json {}", (char*)http_reply.body.data()); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : http_reply.body.empty() ? "" : (char*)http_reply.body.data()); co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); } @@ -456,21 +529,25 @@ Ichor::Task> Ichor::EtcdV2Servi ICHOR_LOG_TRACE(_logger, "del url {}", url); - auto http_reply = co_await _mainConn->sendAsync(HttpMethod::delete_, url, {}, {}); + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::delete_, url, std::move(headers), {}); + ICHOR_LOG_TRACE(_logger, "del status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : http_reply.body.empty() ? "" : (char*)http_reply.body.data()); if(http_reply.status != HttpStatus::ok) { ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); } - ICHOR_LOG_TRACE(_logger, "del json {}", (char*)http_reply.body.data()); - EtcdReply etcd_reply; auto err = glz::read_json(etcd_reply, http_reply.body); if(err) { ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); - ICHOR_LOG_ERROR(_logger, "json {}", (char*)http_reply.body.data()); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : http_reply.body.empty() ? "" : (char*)http_reply.body.data()); co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); } @@ -480,13 +557,14 @@ Ichor::Task> Ichor::EtcdV2Servi Ichor::Task> Ichor::EtcdV2Service::leaderStatistics() { std::string url = fmt::format("/v2/stats/leader"); auto http_reply = co_await _mainConn->sendAsync(HttpMethod::get, url, {}, {}); - ICHOR_LOG_ERROR(_logger, "json {}", (char*)http_reply.body.data()); + ICHOR_LOG_ERROR(_logger, "leaderStatistics json {}", http_reply.body.empty() ? "" : http_reply.body.empty() ? "" : (char*)http_reply.body.data()); co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); } Ichor::Task> Ichor::EtcdV2Service::selfStatistics() { std::string url = fmt::format("/v2/stats/self"); auto http_reply = co_await _mainConn->sendAsync(HttpMethod::get, url, {}, {}); + ICHOR_LOG_TRACE(_logger, "selfStatistics status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : http_reply.body.empty() ? "" : (char*)http_reply.body.data()); if(http_reply.status != HttpStatus::ok) { ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); @@ -498,7 +576,7 @@ Ichor::Task> Ichor::EtcdV2S if(err) { ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); - ICHOR_LOG_ERROR(_logger, "json {}", (char*)http_reply.body.data()); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : http_reply.body.empty() ? "" : (char*)http_reply.body.data()); co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); } @@ -508,6 +586,7 @@ Ichor::Task> Ichor::EtcdV2S Ichor::Task> Ichor::EtcdV2Service::storeStatistics() { std::string url = fmt::format("/v2/stats/store"); auto http_reply = co_await _mainConn->sendAsync(HttpMethod::get, url, {}, {}); + ICHOR_LOG_TRACE(_logger, "storeStatistics status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); if(http_reply.status != HttpStatus::ok) { ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); @@ -519,7 +598,7 @@ Ichor::Task> Ichor::EtcdV2 if(err) { ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); - ICHOR_LOG_ERROR(_logger, "json {}", (char*)http_reply.body.data()); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); } @@ -529,6 +608,7 @@ Ichor::Task> Ichor::EtcdV2 Ichor::Task> Ichor::EtcdV2Service::version() { std::string url = fmt::format("/version"); auto http_reply = co_await _mainConn->sendAsync(HttpMethod::get, url, {}, {}); + ICHOR_LOG_TRACE(_logger, "version status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); if(http_reply.status != HttpStatus::ok) { ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); @@ -540,7 +620,7 @@ Ichor::Task> Ichor::Etcd if(err) { ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); - ICHOR_LOG_ERROR(_logger, "json {}", (char*)http_reply.body.data()); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); } @@ -550,6 +630,7 @@ Ichor::Task> Ichor::Etcd Ichor::Task> Ichor::EtcdV2Service::health() { std::string url = fmt::format("/health"); auto http_reply = co_await _mainConn->sendAsync(HttpMethod::get, url, {}, {}); + ICHOR_LOG_TRACE(_logger, "health status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); if(http_reply.status != HttpStatus::ok) { ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); @@ -561,10 +642,563 @@ Ichor::Task> Ichor::EtcdV2Service::health() if(err) { ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); - ICHOR_LOG_ERROR(_logger, "json {}", (char*)http_reply.body.data()); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); } co_return etcd_reply.health == "true"; } +Ichor::Task> Ichor::EtcdV2Service::authStatus() { + std::string url = fmt::format("/v2/auth/enable"); + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::get, url, {}, {}); + ICHOR_LOG_TRACE(_logger, "authStatus status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status != HttpStatus::ok) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdEnableAuthReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply.enabled; +} + +Ichor::Task> Ichor::EtcdV2Service::enableAuth() { + std::string url = fmt::format("/v2/auth/enable"); + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::put, url, {}, {}); + ICHOR_LOG_TRACE(_logger, "enableAuth status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status == HttpStatus::bad_request) { + co_return tl::unexpected(Ichor::EtcdError::ROOT_USER_NOT_YET_CREATED); + } + + if(http_reply.status != HttpStatus::ok && http_reply.status != HttpStatus::conflict) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + co_return true; +} + +Ichor::Task> Ichor::EtcdV2Service::disableAuth() { + std::string url = fmt::format("/v2/auth/enable"); + + if(!_auth) { + co_return tl::unexpected(Ichor::EtcdError::NO_AUTHENTICATION_SET); + } + + // forced to declare as a variable due to internal compiler errors in gcc <= 12.3.0 + unordered_map headers{{"Authorization", *_auth}}; + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::delete_, url, std::move(headers), {}); + ICHOR_LOG_TRACE(_logger, "disableAuth status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status == HttpStatus::unauthorized) { + co_return tl::unexpected(Ichor::EtcdError::UNAUTHORIZED); + } + + if(http_reply.status != HttpStatus::ok && http_reply.status != HttpStatus::conflict) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + co_return true; +} + +Ichor::Task> Ichor::EtcdV2Service::getUsers() { + std::string url = fmt::format("/v2/auth/users"); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::get, url, std::move(headers), {}); + ICHOR_LOG_TRACE(_logger, "getUsers status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status != HttpStatus::ok) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdUsersReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply; +} + +Ichor::Task> Ichor::EtcdV2Service::getUserDetails(std::string_view user) { + std::string url = fmt::format("/v2/auth/users/{}", user); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::get, url, std::move(headers), {}); + ICHOR_LOG_TRACE(_logger, "getUserDetails status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status != HttpStatus::ok) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdUserReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply; +} + +void Ichor::EtcdV2Service::setAuthentication(std::string_view user, std::string_view pass) { + _auth = fmt::format("{}:{}", user, pass); + _auth = base64_encode(reinterpret_cast(_auth->c_str()), _auth->length()); + _auth = fmt::format("Basic {}", *_auth); +} + +void Ichor::EtcdV2Service::clearAuthentication() { + _auth.reset(); +} + +tl::optional Ichor::EtcdV2Service::getAuthenticationUser() const { + if(!_auth) { + return {}; + } + + auto full_str = base64_decode((*_auth).substr(6)); + auto pos = full_str.find(':'); + return full_str.substr(0, pos); +} + +Ichor::Task> Ichor::EtcdV2Service::createUser(std::string_view user, std::string_view pass) { + std::string url = fmt::format("/v2/auth/users/{}", user); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + EtcdUserReply user_create{}; + user_create.user = user; + user_create.password = pass; + + std::vector buf; + glz::write_json(user_create, buf); + buf.push_back('\0'); + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::put, url, std::move(headers), std::move(buf)); + ICHOR_LOG_TRACE(_logger, "createUser status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status != HttpStatus::ok && http_reply.status != HttpStatus::created) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdUserReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply; +} + +Ichor::Task> Ichor::EtcdV2Service::grantUserRoles(std::string_view user, std::vector roles) { + std::string url = fmt::format("/v2/auth/users/{}", user); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + EtcdUserReply user_create{}; + user_create.user = user; + user_create.grant = std::move(roles); + + std::vector buf; + glz::write_json(user_create, buf); + buf.push_back('\0'); + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::put, url, std::move(headers), std::move(buf)); + ICHOR_LOG_TRACE(_logger, "grantUserRoles status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status != HttpStatus::ok && http_reply.status != HttpStatus::created) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdUpdateUserReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply; +} + +Ichor::Task> Ichor::EtcdV2Service::revokeUserRoles(std::string_view user, std::vector roles) { + std::string url = fmt::format("/v2/auth/users/{}", user); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + EtcdUserReply user_create{}; + user_create.user = user; + user_create.revoke = std::move(roles); + + std::vector buf; + glz::write_json(user_create, buf); + buf.push_back('\0'); + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::put, url, std::move(headers), std::move(buf)); + ICHOR_LOG_TRACE(_logger, "revokeUserRoles status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status != HttpStatus::ok && http_reply.status != HttpStatus::created) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdUpdateUserReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply; +} + +Ichor::Task> Ichor::EtcdV2Service::updateUserPassword(std::string_view user, std::string_view pass) { + std::string url = fmt::format("/v2/auth/users/{}", user); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + EtcdUserReply user_create{}; + user_create.user = user; + user_create.password = pass; + + std::vector buf; + glz::write_json(user_create, buf); + buf.push_back('\0'); + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::put, url, std::move(headers), std::move(buf)); + ICHOR_LOG_TRACE(_logger, "updateUserPassword status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status != HttpStatus::ok && http_reply.status != HttpStatus::created) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdUpdateUserReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply; +} + +Ichor::Task> Ichor::EtcdV2Service::deleteUser(std::string_view user) { + std::string url = fmt::format("/v2/auth/users/{}", user); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::delete_, url, std::move(headers), {}); + ICHOR_LOG_TRACE(_logger, "deleteUser status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status == HttpStatus::unauthorized) { + co_return tl::unexpected(Ichor::EtcdError::UNAUTHORIZED); + } + if(http_reply.status == HttpStatus::forbidden) { + co_return tl::unexpected(Ichor::EtcdError::REMOVING_ROOT_NOT_ALLOWED_WITH_AUTH_ENABLED); + } + if(http_reply.status == HttpStatus::not_found) { + co_return tl::unexpected(Ichor::EtcdError::NOT_FOUND); + } + + if(http_reply.status != HttpStatus::ok) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + co_return {}; +} + +Ichor::Task> Ichor::EtcdV2Service::getRoles() { + std::string url = fmt::format("/v2/auth/roles"); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::get, url, std::move(headers), {}); + ICHOR_LOG_TRACE(_logger, "getRoles status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status == HttpStatus::unauthorized) { + co_return tl::unexpected(Ichor::EtcdError::UNAUTHORIZED); + } + + if(http_reply.status != HttpStatus::ok) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdRolesReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply; +} + +Ichor::Task> Ichor::EtcdV2Service::getRole(std::string_view role) { + std::string url = fmt::format("/v2/auth/roles/{}", role); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::get, url, std::move(headers), {}); + ICHOR_LOG_TRACE(_logger, "getRole status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status == HttpStatus::unauthorized) { + co_return tl::unexpected(Ichor::EtcdError::UNAUTHORIZED); + } + if(http_reply.status == HttpStatus::not_found) { + co_return tl::unexpected(Ichor::EtcdError::NOT_FOUND); + } + + if(http_reply.status != HttpStatus::ok) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdRoleReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply; +} + +Ichor::Task> Ichor::EtcdV2Service::createRole(std::string_view role, std::vector read_permissions, std::vector write_permissions) { + std::string url = fmt::format("/v2/auth/roles/{}", role); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + EtcdRoleReply role_create{}; + role_create.role = role; + role_create.permissions.kv.read = std::move(read_permissions); + role_create.permissions.kv.write = std::move(write_permissions); + + std::vector buf; + glz::write_json(role_create, buf); + buf.push_back('\0'); + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::put, url, std::move(headers), std::move(buf)); + ICHOR_LOG_TRACE(_logger, "createRole status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status == HttpStatus::unauthorized) { + co_return tl::unexpected(Ichor::EtcdError::UNAUTHORIZED); + } + if(http_reply.status == HttpStatus::not_found) { + co_return tl::unexpected(Ichor::EtcdError::NOT_FOUND); + } + if(http_reply.status == HttpStatus::conflict) { + co_return tl::unexpected(Ichor::EtcdError::DUPLICATE_PERMISSION_OR_REVOKING_NON_EXISTENT); + } + + if(http_reply.status != HttpStatus::ok && http_reply.status != HttpStatus::created) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdRoleReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply; +} + +Ichor::Task> Ichor::EtcdV2Service::grantRolePermissions(std::string_view role, std::vector read_permissions, std::vector write_permissions) { + std::string url = fmt::format("/v2/auth/roles/{}", role); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + EtcdRoleReply role_create{}; + role_create.role = role; + EtcdPermissionsReply granted{}; + granted.kv.read = std::move(read_permissions); + granted.kv.write = std::move(write_permissions); + role_create.grant = std::move(granted); + + std::vector buf; + glz::write_json(role_create, buf); + buf.push_back('\0'); + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::put, url, std::move(headers), std::move(buf)); + ICHOR_LOG_TRACE(_logger, "grantRolePermissions status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status == HttpStatus::unauthorized) { + co_return tl::unexpected(Ichor::EtcdError::UNAUTHORIZED); + } + if(http_reply.status == HttpStatus::not_found) { + co_return tl::unexpected(Ichor::EtcdError::NOT_FOUND); + } + if(http_reply.status == HttpStatus::conflict) { + co_return tl::unexpected(Ichor::EtcdError::DUPLICATE_PERMISSION_OR_REVOKING_NON_EXISTENT); + } + + if(http_reply.status != HttpStatus::ok && http_reply.status != HttpStatus::created) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdRoleReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply; +} + +Ichor::Task> Ichor::EtcdV2Service::revokeRolePermissions(std::string_view role, std::vector read_permissions, std::vector write_permissions) { + std::string url = fmt::format("/v2/auth/roles/{}", role); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + EtcdRoleReply role_create{}; + role_create.role = role; + EtcdPermissionsReply revoked{}; + revoked.kv.read = std::move(read_permissions); + revoked.kv.write = std::move(write_permissions); + role_create.revoke = std::move(revoked); + + std::vector buf; + glz::write_json(role_create, buf); + buf.push_back('\0'); + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::put, url, std::move(headers), std::move(buf)); + ICHOR_LOG_TRACE(_logger, "revokeRolePermissions status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status == HttpStatus::unauthorized) { + co_return tl::unexpected(Ichor::EtcdError::UNAUTHORIZED); + } + if(http_reply.status == HttpStatus::not_found) { + co_return tl::unexpected(Ichor::EtcdError::NOT_FOUND); + } + if(http_reply.status == HttpStatus::conflict) { + co_return tl::unexpected(Ichor::EtcdError::DUPLICATE_PERMISSION_OR_REVOKING_NON_EXISTENT); + } + + if(http_reply.status != HttpStatus::ok && http_reply.status != HttpStatus::created) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + EtcdRoleReply etcd_reply; + auto err = glz::read_json(etcd_reply, http_reply.body); + + if(err) { + ICHOR_LOG_ERROR(_logger, "Glaze error {} at {}", err.ec, err.location); + ICHOR_LOG_ERROR(_logger, "json {}", http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + co_return tl::unexpected(Ichor::EtcdError::JSON_PARSE_ERROR); + } + + co_return etcd_reply; +} + +Ichor::Task> Ichor::EtcdV2Service::deleteRole(std::string_view role) { + std::string url = fmt::format("/v2/auth/roles/{}", role); + + unordered_map headers{}; + if(_auth) { + headers.emplace("Authorization", *_auth); + } + + auto http_reply = co_await _mainConn->sendAsync(HttpMethod::delete_, url, std::move(headers), {}); + ICHOR_LOG_TRACE(_logger, "deleteRole status: {}, body: {}", (int)http_reply.status, http_reply.body.empty() ? "" : (char*)http_reply.body.data()); + + if(http_reply.status == HttpStatus::unauthorized) { + co_return tl::unexpected(Ichor::EtcdError::UNAUTHORIZED); + } + if(http_reply.status == HttpStatus::forbidden) { + co_return tl::unexpected(Ichor::EtcdError::CANNOT_DELETE_ROOT_WHILE_AUTH_IS_ENABLED); + } + if(http_reply.status == HttpStatus::not_found) { + co_return tl::unexpected(Ichor::EtcdError::NOT_FOUND); + } + + if(http_reply.status != HttpStatus::ok) { + ICHOR_LOG_ERROR(_logger, "Error on route {}, http status {}", url, (int)http_reply.status); + co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); + } + + co_return {}; +} + diff --git a/src/services/network/http/HttpConnectionService.cpp b/src/services/network/http/HttpConnectionService.cpp index 9e18d5b..117fd10 100644 --- a/src/services/network/http/HttpConnectionService.cpp +++ b/src/services/network/http/HttpConnectionService.cpp @@ -96,7 +96,7 @@ uint64_t Ichor::HttpConnectionService::getPriority() { return _priority.load(std::memory_order_acquire); } -Ichor::Task Ichor::HttpConnectionService::sendAsync(Ichor::HttpMethod method, std::string_view route, std::vector &&headers, std::vector &&msg) { +Ichor::Task Ichor::HttpConnectionService::sendAsync(Ichor::HttpMethod method, std::string_view route, unordered_map &&headers, std::vector &&msg) { if(method == HttpMethod::get && !msg.empty()) { throw std::runtime_error("GET requests cannot have a body."); } @@ -156,7 +156,7 @@ Ichor::Task Ichor::HttpConnectionService::sendAsync(Ichor:: }; for (auto const &header : *next.headers) { - req.set(header.name, header.value); + req.set(header.first, header.second); } req.set(http::field::host, Ichor::any_cast(getProperties()["Address"])); req.prepare_payload(); @@ -221,7 +221,7 @@ Ichor::Task Ichor::HttpConnectionService::sendAsync(Ichor:: next.response->status = (HttpStatus) (int) res.result(); next.response->headers.reserve(static_cast(std::distance(std::begin(res), std::end(res)))); for (auto const &header: res) { - next.response->headers.emplace_back(header.name_string(), header.value()); + next.response->headers.emplace(header.name_string(), header.value()); } // need to use move iterator instead of std::move directly, to prevent leaks. diff --git a/src/services/network/http/HttpHostService.cpp b/src/services/network/http/HttpHostService.cpp index 1d0aa29..16afa98 100644 --- a/src/services/network/http/HttpHostService.cpp +++ b/src/services/network/http/HttpHostService.cpp @@ -304,10 +304,10 @@ void Ichor::HttpHostService::read(tcp::socket socket, net::yield_context yield) ICHOR_LOG_TRACE_ATOMIC(_logger, "New request for {} {}", (int)req.method(), req.target()); - std::vector headers{}; + unordered_map headers{}; headers.reserve(static_cast(std::distance(std::begin(req), std::end(req)))); for (auto const& field : req) { - headers.emplace_back(field.name_string(), field.value()); + headers.emplace(field.name_string(), field.value()); } // rapidjson f.e. expects a null terminator if (!req.body().empty() && *req.body().rbegin() != 0) { @@ -348,7 +348,7 @@ void Ichor::HttpHostService::read(tcp::socket socket, net::yield_context yield) res.set(http::field::content_type, *httpRes.contentType); } for (auto const& header : httpRes.headers) { - res.set(header.value, header.name); + res.set(header.first, header.second); } res.keep_alive(keep_alive); ICHOR_LOG_TRACE_ATOMIC(_logger, "sending http response {} - {}", (int)httpRes.status, diff --git a/test/TestServices/EtcdUsingService.h b/test/TestServices/EtcdUsingService.h index f7baf8a..8c785f0 100644 --- a/test/TestServices/EtcdUsingService.h +++ b/test/TestServices/EtcdUsingService.h @@ -433,7 +433,7 @@ namespace Ichor { } // TTL test { - auto putReply = co_await _etcd->put("ttl_key", "value", {}, {}, {}, 1, false, false, false); + auto putReply = co_await _etcd->put("ttl_key", "value", {}, {}, {}, 1u, false, false, false); if (!putReply) { throw std::runtime_error(""); } @@ -525,6 +525,194 @@ namespace Ichor { throw std::runtime_error("Incorrect etcdcluster value"); } } + // Authentication test + { + auto authReply = co_await _etcd->authStatus(); + if (!authReply) { + throw std::runtime_error("missing authReply"); + } + + if(authReply.value()) { + _etcd->setAuthentication("root", "root"); + + auto disableAuthReply = co_await _etcd->disableAuth(); + if (!disableAuthReply || !disableAuthReply.value()) { + throw std::runtime_error("These tests require that auth is disabled before starting the tests, or that the tests can disable it automatically using root:root"); + } + + } + + auto usersReply = co_await _etcd->getUsers(); + if (!usersReply) { + throw std::runtime_error("missing usersReply"); + } + + if(!usersReply.value().users || std::find_if(usersReply.value().users->begin(), usersReply.value().users->end(), [](EtcdUserReply const &r) { + return r.user == "root"; + }) == usersReply.value().users->end()) { + auto createUserReply = co_await _etcd->createUser("root", "root"); + if (!createUserReply) { + throw std::runtime_error("missing createUserReply"); + } + } + + _etcd->setAuthentication("root", "root"); + if(_etcd->getAuthenticationUser() != "root") { + throw std::runtime_error("Incorrect auth user"); + } + + auto enableAuthReply = co_await _etcd->enableAuth(); + if (!enableAuthReply) { + throw std::runtime_error("missing enableAuthReply"); + } + + if(!enableAuthReply.value()) { + throw std::runtime_error("Could not enable auth"); + } + + authReply = co_await _etcd->authStatus(); + if (!authReply) { + throw std::runtime_error("missing authReply"); + } + + if(!authReply.value()) { + throw std::runtime_error("Expected Auth enabled"); + } + + auto disableAuthReply = co_await _etcd->disableAuth(); + if (!disableAuthReply) { + throw std::runtime_error("missing disableAuthReply"); + } + + if(!disableAuthReply.value()) { + throw std::runtime_error("Could not disable auth"); + } + } + // Create / delete user tests + { + auto usersReply = co_await _etcd->getUsers(); + if (!usersReply) { + throw std::runtime_error("missing usersReply"); + } + + if(!usersReply.value().users || std::find_if(usersReply.value().users->begin(), usersReply.value().users->end(), [](EtcdUserReply const &r) { + return r.user == "test_user"; + }) != usersReply.value().users->end()) { + throw std::runtime_error("test_user already exists"); + } + + auto createUserReply = co_await _etcd->createUser("test_user", "test_user"); + if (!createUserReply) { + throw std::runtime_error("missing createUserReply"); + } + + usersReply = co_await _etcd->getUsers(); + if (!usersReply) { + throw std::runtime_error("missing usersReply"); + } + + if(!usersReply.value().users || std::find_if(usersReply.value().users->begin(), usersReply.value().users->end(), [](EtcdUserReply const &r) { + return r.user == "test_user"; + }) == usersReply.value().users->end()) { + throw std::runtime_error("test_user not created successfully"); + } + + // forced to declare as a variable due to internal compiler errors in gcc <= 12.3.0 + std::vector roles{"test_role"}; + auto grantUserReply = co_await _etcd->grantUserRoles("test_user", roles); + if (!grantUserReply) { + throw std::runtime_error("missing grantUserReply"); + } + + auto revokeUserReply = co_await _etcd->revokeUserRoles("test_user", roles); + if (!revokeUserReply) { + throw std::runtime_error("missing revokeUserReply"); + } + + auto updatePassReply = co_await _etcd->updateUserPassword("test_user", "test_role2"); + if (!updatePassReply) { + throw std::runtime_error("missing updatePassReply"); + } + + auto deleteUserReply = co_await _etcd->deleteUser("test_user"); + if (!deleteUserReply) { + throw std::runtime_error("missing deleteUserReply"); + } + + usersReply = co_await _etcd->getUsers(); + if (!usersReply) { + throw std::runtime_error("missing usersReply"); + } + + if(!usersReply.value().users || std::find_if(usersReply.value().users->begin(), usersReply.value().users->end(), [](EtcdUserReply const &r) { + return r.user == "test_user"; + }) != usersReply.value().users->end()) { + throw std::runtime_error("test_user not deleted successfully"); + } + } + // Create / delete role tests + { + auto rolesReply = co_await _etcd->getRoles(); + if (!rolesReply) { + throw std::runtime_error("missing rolesReply"); + } + + if(std::find_if(rolesReply.value().roles.begin(), rolesReply.value().roles.end(), [](EtcdRoleReply const &r) { + return r.role == "test_role"; + }) != rolesReply.value().roles.end()) { + throw std::runtime_error("test_role already exists"); + } + + // forced to declare as a variable due to internal compiler errors in gcc <= 12.3.0 + std::vector read_permissions{"read_perm"}; + std::vector write_permissions{"write_perm"}; + auto createRoleReply = co_await _etcd->createRole("test_role", read_permissions, write_permissions); + if (!createRoleReply) { + fmt::print("missing createRoleReply {}\n", createRoleReply.error()); + throw std::runtime_error("missing createRoleReply"); + } + + rolesReply = co_await _etcd->getRoles(); + if (!rolesReply) { + throw std::runtime_error("missing rolesReply"); + } + + if(std::find_if(rolesReply.value().roles.begin(), rolesReply.value().roles.end(), [](EtcdRoleReply const &r) { + return r.role == "test_role"; + }) == rolesReply.value().roles.end()) { + throw std::runtime_error("test_role not created successfully"); + } + + read_permissions.clear(); + read_permissions.push_back("read_perm2"); + auto grantRoleReply = co_await _etcd->grantRolePermissions("test_role", read_permissions, {}); + if (!grantRoleReply) { + fmt::print("missing grantRoleReply {}\n", grantRoleReply.error()); + throw std::runtime_error("missing grantRoleReply"); + } + + auto revokeRoleReply = co_await _etcd->revokeRolePermissions("test_role", read_permissions, {}); + if (!revokeRoleReply) { + fmt::print("missing revokeRoleReply {}\n", revokeRoleReply.error()); + throw std::runtime_error("missing revokeRoleReply"); + } + + auto deleteRoleReply = co_await _etcd->deleteRole("test_role"); + if (!deleteRoleReply) { + throw std::runtime_error("missing deleteRoleReply"); + } + + rolesReply = co_await _etcd->getRoles(); + if (!rolesReply) { + throw std::runtime_error("missing rolesReply"); + } + + if(std::find_if(rolesReply.value().roles.begin(), rolesReply.value().roles.end(), [](EtcdRoleReply const &r) { + return r.role == "test_role"; + }) != rolesReply.value().roles.end()) { + throw std::runtime_error("test_role not deleted successfully"); + } + } GetThreadLocalEventQueue().pushEvent(getServiceId()); diff --git a/test/TestServices/HttpThreadService.h b/test/TestServices/HttpThreadService.h index 1a2daa8..2f3b97a 100644 --- a/test/TestServices/HttpThreadService.h +++ b/test/TestServices/HttpThreadService.h @@ -106,7 +106,7 @@ class HttpThreadService final : public AdvancedService { friend DependencyRegister; AsyncGenerator sendTestRequest(std::vector &&toSendMsg) { - std::vector headers{HttpHeader("Content-Type", "application/json")}; + unordered_map headers{{"Content-Type", "application/json"}}; auto response = co_await _connectionService->sendAsync(HttpMethod::post, "/test", std::move(headers), std::move(toSendMsg)); if(_serializer == nullptr) { From 76c52068925fee1cace267491ab19ce18f42119f Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Wed, 13 Dec 2023 12:38:06 +0100 Subject: [PATCH 04/98] Fix Etcd compilation issues on non-gcc compilers, fix createUser response parsing --- include/ichor/services/etcd/EtcdV2Service.h | 2 +- include/ichor/services/etcd/IEtcd.h | 2 +- src/services/etcd/EtcdV2Service.cpp | 334 ++++++++++---------- 3 files changed, 170 insertions(+), 168 deletions(-) diff --git a/include/ichor/services/etcd/EtcdV2Service.h b/include/ichor/services/etcd/EtcdV2Service.h index faf60df..e6823f4 100644 --- a/include/ichor/services/etcd/EtcdV2Service.h +++ b/include/ichor/services/etcd/EtcdV2Service.h @@ -50,7 +50,7 @@ namespace Ichor { void setAuthentication(std::string_view user, std::string_view pass) final; void clearAuthentication() final; [[nodiscard]] tl::optional getAuthenticationUser() const final; - [[nodiscard]] Task> createUser(std::string_view user, std::string_view pass) final; + [[nodiscard]] Task> createUser(std::string_view user, std::string_view pass) final; [[nodiscard]] Task> grantUserRoles(std::string_view user, std::vector roles) final; [[nodiscard]] Task> revokeUserRoles(std::string_view user, std::vector roles) final; [[nodiscard]] Task> updateUserPassword(std::string_view user, std::string_view pass) final; diff --git a/include/ichor/services/etcd/IEtcd.h b/include/ichor/services/etcd/IEtcd.h index 6c4c18f..79b6c35 100644 --- a/include/ichor/services/etcd/IEtcd.h +++ b/include/ichor/services/etcd/IEtcd.h @@ -304,7 +304,7 @@ namespace Ichor { * @param pass * @return full user details */ - [[nodiscard]] virtual Task> createUser(std::string_view user, std::string_view pass) = 0; + [[nodiscard]] virtual Task> createUser(std::string_view user, std::string_view pass) = 0; /** * Grant roles to user diff --git a/src/services/etcd/EtcdV2Service.cpp b/src/services/etcd/EtcdV2Service.cpp index fc17938..68b8efc 100644 --- a/src/services/etcd/EtcdV2Service.cpp +++ b/src/services/etcd/EtcdV2Service.cpp @@ -123,85 +123,86 @@ struct fmt::formatter { } }; -//template <> -//struct glz::meta { -// using T = Ichor::EtcdReplyNode; -// static constexpr auto value = object( -// "createdIndex", &T::createdIndex, -// "modifiedIndex", &T::modifiedIndex, -// "key", &T::key, -// "value", &T::value, -// "ttl", &T::ttl, -// "expiration", &T::expiration, -// "dir", &T::dir, -// "nodes", &T::nodes -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdReply; -// static constexpr auto value = object( -// "action", &T::action, -// "node", &T::node, -// "prevNode", &T::prevNode, -// "index", &T::index, -// "cause", &T::cause, -// "errorCode", &T::errorCode, -// "message", &T::message -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdLeaderInfoStats; -// static constexpr auto value = object( -// "leader", &T::leader, -// "uptime", &T::uptime, -// "startTime", &T::startTime -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdSelfStats; -// static constexpr auto value = object( -// "name", &T::name, -// "id", &T::id, -// "state", &T::state, -// "startTime", &T::startTime, -// "leaderInfo", &T::leaderInfo, -// "recvAppendRequestCnt", &T::recvAppendRequestCnt, -// "sendAppendRequestCnt", &T::sendAppendRequestCnt -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdStoreStats; -// static constexpr auto value = object( -// "compareAndSwapFail", &T::compareAndSwapFail, -// "compareAndSwapSuccess", &T::compareAndSwapSuccess, -// "compareAndDeleteSuccess", &T::compareAndDeleteSuccess, -// "compareAndDeleteFail", &T::compareAndDeleteFail, -// "createFail", &T::createFail, -// "createSuccess", &T::createSuccess, -// "deleteFail", &T::deleteFail, -// "deleteSuccess", &T::deleteSuccess, -// "expireCount", &T::expireCount, -// "getsFail", &T::getsFail, -// "getsSuccess", &T::getsSuccess, -// "setsFail", &T::setsFail, -// "setsSuccess", &T::setsSuccess, -// "updateFail", &T::updateFail, -// "updateSuccess", &T::updateSuccess, -// "watchers", &T::watchers -// ); -//}; +template <> +struct glz::meta { + using T = Ichor::EtcdReplyNode; + static constexpr auto value = object( + "createdIndex", &T::createdIndex, + "modifiedIndex", &T::modifiedIndex, + "key", &T::key, + "value", &T::value, + "ttl", &T::ttl, + "expiration", &T::expiration, + "dir", &T::dir, + "nodes", &T::nodes + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdReply; + static constexpr auto value = object( + "action", &T::action, + "node", &T::node, + "prevNode", &T::prevNode, + "index", &T::index, + "cause", &T::cause, + "errorCode", &T::errorCode, + "message", &T::message + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdLeaderInfoStats; + static constexpr auto value = object( + "leader", &T::leader, + "uptime", &T::uptime, + "startTime", &T::startTime + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdSelfStats; + static constexpr auto value = object( + "name", &T::name, + "id", &T::id, + "state", &T::state, + "startTime", &T::startTime, + "leaderInfo", &T::leaderInfo, + "recvAppendRequestCnt", &T::recvAppendRequestCnt, + "sendAppendRequestCnt", &T::sendAppendRequestCnt + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdStoreStats; + static constexpr auto value = object( + "compareAndSwapFail", &T::compareAndSwapFail, + "compareAndSwapSuccess", &T::compareAndSwapSuccess, + "compareAndDeleteSuccess", &T::compareAndDeleteSuccess, + "compareAndDeleteFail", &T::compareAndDeleteFail, + "createFail", &T::createFail, + "createSuccess", &T::createSuccess, + "deleteFail", &T::deleteFail, + "deleteSuccess", &T::deleteSuccess, + "expireCount", &T::expireCount, + "getsFail", &T::getsFail, + "getsSuccess", &T::getsSuccess, + "setsFail", &T::setsFail, + "setsSuccess", &T::setsSuccess, + "updateFail", &T::updateFail, + "updateSuccess", &T::updateSuccess, + "watchers", &T::watchers + ); +}; namespace Ichor { struct EtcdHealthReply final { std::string health; + std::optional reason; }; struct EtcdEnableAuthReply final { bool enabled; @@ -209,95 +210,96 @@ namespace Ichor { } -//template <> -//struct glz::meta { -// using T = Ichor::EtcdHealthReply; -// static constexpr auto value = object( -// "health", &T::health -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdEnableAuthReply; -// static constexpr auto value = object( -// "enabled", &T::enabled -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdVersionReply; -// static constexpr auto value = object( -// "etcdserver", &T::etcdserver, -// "etcdcluster", &T::etcdcluster -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdKvReply; -// static constexpr auto value = object( -// "read", &T::read, -// "write", &T::write -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdPermissionsReply; -// static constexpr auto value = object( -// "kv", &T::kv -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdRoleReply; -// static constexpr auto value = object( -// "role", &T::role, -// "permissions", &T::permissions, -// "grant", &T::grant, -// "revoke", &T::revoke -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdRolesReply; -// static constexpr auto value = object( -// "roles", &T::roles -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdUserReply; -// static constexpr auto value = object( -// "user", &T::user, -// "password", &T::password, -// "roles", &T::roles, -// "grant", &T::grant, -// "revoke", &T::revoke -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdUpdateUserReply; -// static constexpr auto value = object( -// "user", &T::user, -// "roles", &T::roles -// ); -//}; -// -//template <> -//struct glz::meta { -// using T = Ichor::EtcdUsersReply; -// static constexpr auto value = object( -// "users", &T::users -// ); -//}; +template <> +struct glz::meta { + using T = Ichor::EtcdHealthReply; + static constexpr auto value = object( + "health", &T::health, + "reason", &T::reason + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdEnableAuthReply; + static constexpr auto value = object( + "enabled", &T::enabled + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdVersionReply; + static constexpr auto value = object( + "etcdserver", &T::etcdserver, + "etcdcluster", &T::etcdcluster + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdKvReply; + static constexpr auto value = object( + "read", &T::read, + "write", &T::write + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdPermissionsReply; + static constexpr auto value = object( + "kv", &T::kv + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdRoleReply; + static constexpr auto value = object( + "role", &T::role, + "permissions", &T::permissions, + "grant", &T::grant, + "revoke", &T::revoke + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdRolesReply; + static constexpr auto value = object( + "roles", &T::roles + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdUserReply; + static constexpr auto value = object( + "user", &T::user, + "password", &T::password, + "roles", &T::roles, + "grant", &T::grant, + "revoke", &T::revoke + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdUpdateUserReply; + static constexpr auto value = object( + "user", &T::user, + "roles", &T::roles + ); +}; + +template <> +struct glz::meta { + using T = Ichor::EtcdUsersReply; + static constexpr auto value = object( + "users", &T::users + ); +}; Ichor::EtcdV2Service::EtcdV2Service(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { reg.registerDependency(this, false, getProperties()); @@ -788,7 +790,7 @@ tl::optional Ichor::EtcdV2Service::getAuthenticationUser() const { return full_str.substr(0, pos); } -Ichor::Task> Ichor::EtcdV2Service::createUser(std::string_view user, std::string_view pass) { +Ichor::Task> Ichor::EtcdV2Service::createUser(std::string_view user, std::string_view pass) { std::string url = fmt::format("/v2/auth/users/{}", user); unordered_map headers{}; @@ -812,7 +814,7 @@ Ichor::Task> Ichor::EtcdV2S co_return tl::unexpected(Ichor::EtcdError::HTTP_RESPONSE_ERROR); } - EtcdUserReply etcd_reply; + EtcdUpdateUserReply etcd_reply; auto err = glz::read_json(etcd_reply, http_reply.body); if(err) { From 25ee59b9de93a82b9ecc0c077dadd2f23eb638dc Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sat, 23 Dec 2023 21:53:10 +0100 Subject: [PATCH 05/98] Make Any noexcept --- include/ichor/stl/Any.h | 32 +++++++++++-------- ...ultipleSeparateDependencyRequestsService.h | 8 +++++ 2 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 test/TestServices/MultipleSeparateDependencyRequestsService.h diff --git a/include/ichor/stl/Any.h b/include/ichor/stl/Any.h index 6179ab7..b61dd67 100644 --- a/include/ichor/stl/Any.h +++ b/include/ichor/stl/Any.h @@ -5,13 +5,14 @@ #include #include -// Differs from std::any by not needing RTTI (no typeid()) +// Differs from std::any by not needing RTTI (no typeid()) and still able to compare types +// e.g my_any_var.type_hash() == Ichor::typeNameHash() // Probably doesn't work in some situations where std::any would, as compiler support is missing. namespace Ichor { struct bad_any_cast final : public std::bad_cast { - bad_any_cast(std::string_view type, std::string_view cast) : _error() { + bad_any_cast(std::string_view type, std::string_view cast) { _error = "Bad any_cast. Expected "; _error += type; _error += " but got request to cast to "; @@ -23,13 +24,11 @@ namespace Ichor { std::string _error; }; - using createValueFnPtr = void*(*)(void*); - using deleteValueFnPtr = void(*)(void*); + using createValueFnPtr = void*(*)(void*) noexcept; + using deleteValueFnPtr = void(*)(void*) noexcept; struct any final { - any() noexcept { - - } + any() noexcept = default; any(const any& o) : _size(o._size), _ptr(o._createFn(o._ptr)), _typeHash(o._typeHash), _hasValue(o._hasValue), _createFn(o._createFn), _deleteFn(o._deleteFn), _typeName(o._typeName) { @@ -40,7 +39,7 @@ namespace Ichor { o._ptr = nullptr; } - any& operator=(const any& o) { + any& operator=(const any& o) noexcept { if(this == &o) { return *this; } @@ -75,12 +74,17 @@ namespace Ichor { return *this; } - ~any() { + ~any() noexcept { reset(); } + bool operator==(const any& other) const noexcept + { + return _typeHash == other._typeHash && _hasValue && _ptr == other._ptr; + } + template - std::decay& emplace(Args&&... args) + std::decay& emplace(Args&&... args) noexcept { static_assert(std::is_copy_constructible_v, "Template argument must be copy constructible."); static_assert(!std::is_pointer_v, "Template argument must not be a pointer"); @@ -91,10 +95,10 @@ namespace Ichor { _ptr = new T(std::forward(args)...); _typeHash = typeNameHash>(); _hasValue = true; - _createFn = [](void *value) -> void* { + _createFn = [](void *value) noexcept -> void* { return new T(*reinterpret_cast(value)); }; - _deleteFn = [](void *value) { + _deleteFn = [](void *value) noexcept { delete static_cast(value); }; _typeName = typeName>(); @@ -102,7 +106,7 @@ namespace Ichor { return *reinterpret_cast*>(_ptr); } - void reset() { + void reset() noexcept { if(_hasValue) { _deleteFn(_ptr); _hasValue = false; @@ -182,7 +186,7 @@ namespace Ichor { } template - any make_any(Args&&... args) { + any make_any(Args&&... args) noexcept { any a; a.template emplace(std::forward(args)...); return a; diff --git a/test/TestServices/MultipleSeparateDependencyRequestsService.h b/test/TestServices/MultipleSeparateDependencyRequestsService.h new file mode 100644 index 0000000..ec702f4 --- /dev/null +++ b/test/TestServices/MultipleSeparateDependencyRequestsService.h @@ -0,0 +1,8 @@ +// +// Created by oipo on 23-12-23. +// + +#ifndef ICHOR_MULTIPLESEPARATEDEPENDENCYREQUESTSSERVICE_H +#define ICHOR_MULTIPLESEPARATEDEPENDENCYREQUESTSSERVICE_H + +#endif //ICHOR_MULTIPLESEPARATEDEPENDENCYREQUESTSSERVICE_H From b09f34e9dcad15abf96c4bda2d919e2526411618 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sat, 23 Dec 2023 21:54:16 +0100 Subject: [PATCH 06/98] Remove redundant usage of Filter{...} in examples --- docs/01-GettingStarted.md | 2 +- docs/02-DependencyInjection.md | 2 +- include/ichor/services/logging/LoggerFactory.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/01-GettingStarted.md b/docs/01-GettingStarted.md index 92e0a3a..3aca59a 100644 --- a/docs/01-GettingStarted.md +++ b/docs/01-GettingStarted.md @@ -462,7 +462,7 @@ struct LoggerFactory final { Properties props{}; // Filter is a special property in Ichor, if this is detected, it gets checked before asking another service if they're interested in it. // In this case, we apply a filter specifically so that the requesting service id is the only one that will match. - props.template emplace<>("Filter", Ichor::make_any(Filter{ServiceIdFilterEntry{evt.originatingService}})); + props.template emplace<>("Filter", Ichor::make_any(ServiceIdFilterEntry{evt.originatingService})); auto *newLogger = _dm->createServiceManager(std::move(props)); _loggers.emplace(evt.originatingService, newLogger); } diff --git a/docs/02-DependencyInjection.md b/docs/02-DependencyInjection.md index f0633ca..dc83c0c 100644 --- a/docs/02-DependencyInjection.md +++ b/docs/02-DependencyInjection.md @@ -115,4 +115,4 @@ private: [Cody Morterud's blog](https://www.codymorterud.com/design/2018/09/07/dependency-injection-cpp.html) -[C++Now 2019: Kris Jusiak “Dependency Injection - a 25-dollar term for a 5-cent concept”](https://www.youtube.com/watch?v=yVogS4NbL6U) \ No newline at end of file +[C++Now 2019: Kris Jusiak “Dependency Injection - a 25-dollar term for a 5-cent concept”](https://www.youtube.com/watch?v=yVogS4NbL6U) diff --git a/include/ichor/services/logging/LoggerFactory.h b/include/ichor/services/logging/LoggerFactory.h index 8c5e1bd..05a7d00 100644 --- a/include/ichor/services/logging/LoggerFactory.h +++ b/include/ichor/services/logging/LoggerFactory.h @@ -65,7 +65,7 @@ namespace Ichor { } Properties props{}; - props.template emplace<>("Filter", Ichor::make_any(Filter{ServiceIdFilterEntry{evt.originatingService}})); + props.template emplace<>("Filter", Ichor::make_any(ServiceIdFilterEntry{evt.originatingService})); props.template emplace<>("LogLevel", Ichor::make_any(requestedLevel)); auto newLogger = GetThreadLocalManager().template createServiceManager(std::move(props)); _loggers.emplace(evt.originatingService, newLogger); From e0ab6488d8a86f1b0098de39e824030afdb3af11 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sun, 24 Dec 2023 18:59:17 +0100 Subject: [PATCH 07/98] Add to_string() to Any, by default requires values to have an fmt::formatter Also expose size and type_name --- examples/tracker_example/TrackerService.h | 2 +- include/ichor/Common.h | 28 ++++++-- include/ichor/Enums.h | 63 ++++++------------ include/ichor/Filter.h | 12 ++++ include/ichor/stl/Any.h | 79 +++++++++++++++++++---- src/services/network/ws/WsHostService.cpp | 4 +- test/StlTests.cpp | 43 ++++++++++++ 7 files changed, 169 insertions(+), 62 deletions(-) diff --git a/examples/tracker_example/TrackerService.h b/examples/tracker_example/TrackerService.h index 28d7e0c..32b58d8 100644 --- a/examples/tracker_example/TrackerService.h +++ b/examples/tracker_example/TrackerService.h @@ -60,7 +60,7 @@ class TrackerService final { auto newProps = *evt.properties.value(); // `Filter` is a magic keyword that Ichor uses to determine if this service is global or if Ichor should use its filtering logic. // In this case, we tell Ichor to only insert this service if the requesting service has a matching scope - newProps.emplace("Filter", Ichor::make_any(Filter{ScopeFilterEntry{scope}})); + newProps.emplace("Filter", Ichor::make_any(ScopeFilterEntry{scope})); _scopedRuntimeServices.emplace(scope, GetThreadLocalManager().createServiceManager(std::move(newProps))); } diff --git a/include/ichor/Common.h b/include/ichor/Common.h index 29b881f..6f695a7 100644 --- a/include/ichor/Common.h +++ b/include/ichor/Common.h @@ -4,10 +4,12 @@ #include #include #include +#include #ifdef ICHOR_USE_ABSEIL #include #include +#include #include #else #include @@ -123,6 +125,13 @@ namespace Ichor { class Eq = std::equal_to, class Allocator = std::allocator> using unordered_set = absl::flat_hash_set; + template < + class Key, + class T, + class Hash = std::hash, + class Eq = std::less, + class Allocator = std::allocator>> + using unordered_multimap = absl::btree_multimap; #else template < class Key, @@ -137,6 +146,13 @@ namespace Ichor { class Eq = std::equal_to, class Allocator = std::allocator> using unordered_set = std::unordered_set; + template < + class Key, + class T, + class Hash = std::hash, + class Eq = std::equal_to, + class Allocator = std::allocator>> + using unordered_multimap = std::unordered_multimap; #endif using Properties = unordered_map; @@ -144,18 +160,18 @@ namespace Ichor { inline constexpr bool PreventOthersHandling = false; inline constexpr bool AllowOthersHandling = true; - // Code from https://stackoverflow.com/a/73078442/1460998 - // converts a string to an integer with little error checking. Only use if you're very sure that the string is actually a number. - static inline int64_t FastAtoiCompare(const char* str) - { + /// Code from https://stackoverflow.com/a/73078442/1460998 + /// converts a string to an integer with little error checking. Only use if you're very sure that the string is actually a number. + static inline int64_t FastAtoiCompare(const char* str) noexcept { int64_t val = 0; uint8_t x; while ((x = uint8_t(*str++ - '0')) <= 9) val = val * 10 + x; return val; } - static inline uint64_t FastAtoiCompareu(const char* str) - { + /// Code from https://stackoverflow.com/a/73078442/1460998 + /// converts a string to an unsigned integer with little error checking. Only use if you're very sure that the string is actually a number. + static inline uint64_t FastAtoiCompareu(const char* str) noexcept { uint64_t val = 0; uint8_t x; while ((x = uint8_t(*str++ - '0')) <= 9) val = val * 10 + x; diff --git a/include/ichor/Enums.h b/include/ichor/Enums.h index 05c0855..af4e2e9 100644 --- a/include/ichor/Enums.h +++ b/include/ichor/Enums.h @@ -52,8 +52,7 @@ namespace Ichor // // [C] - Consumer performs this transition // [P] - Producer performs this transition - enum class state - { + enum class state { value_not_ready_consumer_active, value_not_ready_consumer_suspended, value_ready_producer_active, @@ -88,18 +87,14 @@ namespace Ichor } template <> -struct fmt::formatter -{ - constexpr auto parse(format_parse_context& ctx) - { +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { return ctx.end(); } template - auto format(const Ichor::ServiceState& state, FormatContext& ctx) - { - switch(state) - { + auto format(const Ichor::ServiceState& state, FormatContext& ctx) { + switch(state) { case Ichor::ServiceState::UNINSTALLED: return fmt::format_to(ctx.out(), "UNINSTALLED"); case Ichor::ServiceState::INSTALLED: @@ -121,18 +116,14 @@ struct fmt::formatter }; template <> -struct fmt::formatter -{ - constexpr auto parse(format_parse_context& ctx) - { +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { return ctx.end(); } template - auto format(const Ichor::state& state, FormatContext& ctx) - { - switch(state) - { + auto format(const Ichor::state& state, FormatContext& ctx) { + switch(state) { case Ichor::state::value_not_ready_consumer_active: return fmt::format_to(ctx.out(), "value_not_ready_consumer_active"); case Ichor::state::value_not_ready_consumer_suspended: @@ -150,18 +141,14 @@ struct fmt::formatter }; template <> -struct fmt::formatter -{ - constexpr auto parse(format_parse_context& ctx) - { +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { return ctx.end(); } template - auto format(const Ichor::Detail::DependencyChange& change, FormatContext& ctx) - { - switch(change) - { + auto format(const Ichor::Detail::DependencyChange& change, FormatContext& ctx) { + switch(change) { case Ichor::Detail::DependencyChange::NOT_FOUND: return fmt::format_to(ctx.out(), "NOT_FOUND"); case Ichor::Detail::DependencyChange::FOUND: @@ -177,18 +164,14 @@ struct fmt::formatter }; template <> -struct fmt::formatter -{ - constexpr auto parse(format_parse_context& ctx) - { +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { return ctx.end(); } template - auto format(const Ichor::StartBehaviour& change, FormatContext& ctx) - { - switch(change) - { + auto format(const Ichor::StartBehaviour& change, FormatContext& ctx) { + switch(change) { case Ichor::StartBehaviour::DONE: return fmt::format_to(ctx.out(), "DONE"); case Ichor::StartBehaviour::STARTED: @@ -202,18 +185,14 @@ struct fmt::formatter }; template <> -struct fmt::formatter -{ - constexpr auto parse(format_parse_context& ctx) - { +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { return ctx.end(); } template - auto format(const Ichor::LogLevel& change, FormatContext& ctx) - { - switch(change) - { + auto format(const Ichor::LogLevel& change, FormatContext& ctx) { + switch(change) { case Ichor::LogLevel::LOG_TRACE: return fmt::format_to(ctx.out(), "LOG_TRACE"); case Ichor::LogLevel::LOG_DEBUG: diff --git a/include/ichor/Filter.h b/include/ichor/Filter.h index 600feea..06eee0c 100644 --- a/include/ichor/Filter.h +++ b/include/ichor/Filter.h @@ -81,3 +81,15 @@ namespace Ichor { std::shared_ptr _templatedFilter; }; } + +template <> +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { + return ctx.end(); + } + + template + auto format(const Ichor::Filter& change, FormatContext& ctx) { + return fmt::format_to(ctx.out(), ""); + } +}; diff --git a/include/ichor/stl/Any.h b/include/ichor/stl/Any.h index b61dd67..084f6e5 100644 --- a/include/ichor/stl/Any.h +++ b/include/ichor/stl/Any.h @@ -4,14 +4,15 @@ #include #include #include +#include // Differs from std::any by not needing RTTI (no typeid()) and still able to compare types // e.g my_any_var.type_hash() == Ichor::typeNameHash() +// Also supports to_string() on the underlying value T, if there exists an fmt::formatter. // Probably doesn't work in some situations where std::any would, as compiler support is missing. namespace Ichor { - struct bad_any_cast final : public std::bad_cast - { + struct bad_any_cast final : public std::bad_cast { bad_any_cast(std::string_view type, std::string_view cast) { _error = "Bad any_cast. Expected "; _error += type; @@ -26,15 +27,18 @@ namespace Ichor { using createValueFnPtr = void*(*)(void*) noexcept; using deleteValueFnPtr = void(*)(void*) noexcept; + using formatValueFnPt = std::string(*)(void*) noexcept; + /// Type-safe container for single values for any copy constructible type. Differs from std::any by not requiring RTTI for any operation, as well as supplying things like size, type_name and to_string() functions. + /// By default, requires the type to have an existing fmt::formatter. Use Ichor::make_unformattable_any if it is not feasible to implement one. struct any final { any() noexcept = default; - any(const any& o) : _size(o._size), _ptr(o._createFn(o._ptr)), _typeHash(o._typeHash), _hasValue(o._hasValue), _createFn(o._createFn), _deleteFn(o._deleteFn), _typeName(o._typeName) { + any(const any& o) : _size(o._size), _ptr(o._createFn(o._ptr)), _typeHash(o._typeHash), _hasValue(o._hasValue), _createFn(o._createFn), _deleteFn(o._deleteFn), _formatFn(o._formatFn), _typeName(o._typeName) { } - any(any&& o) noexcept : _size(o._size), _ptr(o._ptr), _typeHash(o._typeHash), _hasValue(o._hasValue), _createFn(o._createFn), _deleteFn(o._deleteFn), _typeName(o._typeName) { + any(any&& o) noexcept : _size(o._size), _ptr(o._ptr), _typeHash(o._typeHash), _hasValue(o._hasValue), _createFn(o._createFn), _deleteFn(o._deleteFn), _formatFn(o._formatFn), _typeName(o._typeName) { o._hasValue = false; o._ptr = nullptr; } @@ -52,6 +56,7 @@ namespace Ichor { _hasValue = o._hasValue; _createFn = o._createFn; _deleteFn = o._deleteFn; + _formatFn = o._formatFn; _typeName = o._typeName; return *this; @@ -66,6 +71,7 @@ namespace Ichor { _hasValue = o._hasValue; _createFn = o._createFn; _deleteFn = o._deleteFn; + _formatFn = o._formatFn; _typeName = o._typeName; o._hasValue = false; @@ -91,6 +97,32 @@ namespace Ichor { reset(); + _size = sizeof(T); + _ptr = new T(std::forward(args)...); + _typeHash = typeNameHash>(); + _hasValue = true; + _createFn = [](void *value) noexcept -> void* { + return new T(*reinterpret_cast(value)); + }; + _deleteFn = [](void *value) noexcept { + delete static_cast(value); + }; + _formatFn = [](void *value) noexcept { + return fmt::format("{}", *static_cast(value)); + }; + _typeName = typeName>(); + + return *reinterpret_cast*>(_ptr); + } + + template + std::decay& emplace_unformattable(Args&&... args) noexcept + { + static_assert(std::is_copy_constructible_v, "Template argument must be copy constructible."); + static_assert(!std::is_pointer_v, "Template argument must not be a pointer"); + + reset(); + _size = sizeof(T); _ptr = new T(std::forward(args)...); _typeHash = typeNameHash>(); @@ -110,9 +142,18 @@ namespace Ichor { if(_hasValue) { _deleteFn(_ptr); _hasValue = false; + _ptr = nullptr; } } + [[nodiscard]] std::string to_string() const noexcept { + if(_formatFn == nullptr || _ptr == nullptr) { + return "Unprintable value"; + } + + return _formatFn(_ptr); + } + [[nodiscard]] bool has_value() const noexcept { return _hasValue; } @@ -121,8 +162,16 @@ namespace Ichor { return _typeHash; } + [[nodiscard]] std::string_view type_name() const noexcept { + return _typeName; + } + + [[nodiscard]] std::size_t get_size() const noexcept { + return _size; + } + template - ValueType any_cast() + [[nodiscard]] ValueType any_cast() { using Up = typename std::remove_pointer_t>; static_assert((std::is_reference_v || std::is_copy_constructible_v), "Template argument must be a reference or CopyConstructible type"); @@ -134,7 +183,7 @@ namespace Ichor { } template - ValueType any_cast() const + [[nodiscard]] ValueType any_cast() const { using Up = typename std::remove_pointer_t>; static_assert((std::is_reference_v || std::is_copy_constructible_v), "Template argument must be a reference or CopyConstructible type"); @@ -146,7 +195,7 @@ namespace Ichor { } template - ValueType any_cast_ptr() const + [[nodiscard]] ValueType any_cast_ptr() const { using Up = typename std::remove_pointer_t>; static_assert((std::is_pointer_v ), "Template argument must be a pointer type"); @@ -164,31 +213,39 @@ namespace Ichor { bool _hasValue{}; createValueFnPtr _createFn{}; deleteValueFnPtr _deleteFn{}; + formatValueFnPt _formatFn{}; std::string_view _typeName{}; }; template - ValueType any_cast(const any& a) + [[nodiscard]] ValueType any_cast(const any& a) { return a.template any_cast(); } template - ValueType any_cast(any& a) + [[nodiscard]] ValueType any_cast(any& a) { return a.template any_cast(); } template - ValueType any_cast(any const *a) + [[nodiscard]] ValueType any_cast(any const *a) { return a->template any_cast_ptr(); } template - any make_any(Args&&... args) noexcept { + [[nodiscard]] any make_any(Args&&... args) noexcept { any a; a.template emplace(std::forward(args)...); return a; } + + template + [[nodiscard]] any make_unformattable_any(Args&&... args) noexcept { + any a; + a.template emplace_unformattable(std::forward(args)...); + return a; + } } diff --git a/src/services/network/ws/WsHostService.cpp b/src/services/network/ws/WsHostService.cpp index 55e9b10..6dee566 100644 --- a/src/services/network/ws/WsHostService.cpp +++ b/src/services/network/ws/WsHostService.cpp @@ -129,7 +129,7 @@ Ichor::AsyncGenerator Ichor::WsHostService::handleEvent(I auto connection = GetThreadLocalManager().createServiceManager(Properties{ {"WsHostServiceId", Ichor::make_any(getServiceId())}, - {"Socket", Ichor::make_any(evt._socket)}, + {"Socket", Ichor::make_unformattable_any(evt._socket)}, {"Filter", Ichor::make_any(Filter{ClientConnectionFilter{}})} }); _connections.push_back(connection); @@ -148,7 +148,7 @@ uint64_t Ichor::WsHostService::getPriority() { void Ichor::WsHostService::fail(beast::error_code ec, const char *what) { ICHOR_LOG_ERROR(_logger, "Boost.BEAST fail: {}, {}", what, ec.message()); INTERNAL_DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! push {}", getServiceId()); - _queue->pushPrioritisedEvent(getServiceId(), 1000, [this]() { + _queue->pushPrioritisedEvent(getServiceId(), INTERNAL_EVENT_PRIORITY, [this]() { INTERNAL_DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! _startStopEvent set {}", getServiceId()); _startStopEvent.set(); }); diff --git a/test/StlTests.cpp b/test/StlTests.cpp index ad21d3e..3dd0c2e 100644 --- a/test/StlTests.cpp +++ b/test/StlTests.cpp @@ -6,12 +6,36 @@ using namespace Ichor; +struct nonmoveable { + nonmoveable() = default; + nonmoveable(const nonmoveable&) = default; + nonmoveable(nonmoveable&&) = delete; + nonmoveable& operator=(const nonmoveable&) = default; + nonmoveable& operator=(nonmoveable&&) = delete; +}; + +template <> +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { + return ctx.end(); + } + + template + auto format(const nonmoveable& change, FormatContext& ctx) { + return fmt::format_to(ctx.out(), "nonmoveable"); + } +}; + TEST_CASE("STL Tests") { SECTION("Any basics") { auto someInt = make_any(5u); REQUIRE_NOTHROW(any_cast(someInt)); REQUIRE(any_cast(someInt) == 5u); + REQUIRE(someInt.to_string() == "5"); + REQUIRE(someInt.get_size() == sizeof(uint64_t)); + REQUIRE(someInt.type_hash() == typeNameHash()); + REQUIRE(someInt.type_name() == typeName()); REQUIRE_THROWS_MATCHES(any_cast(someInt), bad_any_cast, ExceptionMatcher()); REQUIRE_THROWS_MATCHES(any_cast(someInt), bad_any_cast, ExceptionMatcher()); REQUIRE_THROWS_MATCHES(any_cast(someInt), bad_any_cast, ExceptionMatcher()); @@ -23,6 +47,10 @@ TEST_CASE("STL Tests") { auto someString = make_any("test"); REQUIRE_NOTHROW(any_cast(someString)); REQUIRE(any_cast(someString) == "test"); + REQUIRE(someString.to_string() == "test"); + REQUIRE(someString.get_size() == sizeof(std::string)); + REQUIRE(someString.type_hash() == typeNameHash()); + REQUIRE(someString.type_name() == typeName()); REQUIRE_THROWS_MATCHES(any_cast(someString), bad_any_cast, ExceptionMatcher()); REQUIRE_THROWS_MATCHES(any_cast(someString), bad_any_cast, ExceptionMatcher()); REQUIRE_THROWS_MATCHES(any_cast(someString), bad_any_cast, ExceptionMatcher()); @@ -37,15 +65,21 @@ TEST_CASE("STL Tests") { REQUIRE_NOTHROW(any_cast(someMovedFloat)); REQUIRE(any_cast(someMovedFloat) == 4.5f); REQUIRE_THROWS_MATCHES(any_cast(someString), bad_any_cast, ExceptionMatcher()); + REQUIRE(someMovedFloat.to_string() == "4.5"); + REQUIRE(someString.to_string() == "Unprintable value"); auto someCopiedInt = someInt; REQUIRE_NOTHROW(any_cast(someCopiedInt)); REQUIRE(any_cast(someCopiedInt) == 10u); REQUIRE(any_cast(someInt) == 10u); + REQUIRE(someInt.to_string() == "10"); + REQUIRE(someCopiedInt.to_string() == "10"); auto someMovedInt = std::move(someInt); REQUIRE_NOTHROW(any_cast(someMovedInt)); REQUIRE(any_cast(someMovedInt) == 10u); + REQUIRE(someMovedInt.to_string() == "10"); + REQUIRE(someInt.to_string() == "Unprintable value"); REQUIRE_THROWS_MATCHES(any_cast(someInt), bad_any_cast, ExceptionMatcher()); const auto someConstInt = make_any(12); @@ -58,12 +92,21 @@ TEST_CASE("STL Tests") { REQUIRE_NOTHROW(any_cast(someCopiedFromConstInt)); REQUIRE(any_cast(someCopiedFromConstInt) == 12); + const auto someNonmoveable = make_any(); + REQUIRE_NOTHROW(any_cast(someNonmoveable)); + REQUIRE(someNonmoveable.to_string() == "nonmoveable"); + auto someMovedNonMoveable = std::move(someNonmoveable); + REQUIRE(someMovedNonMoveable.to_string() == "nonmoveable"); + any noneAny; REQUIRE_THROWS_MATCHES(any_cast(noneAny), bad_any_cast, ExceptionMatcher()); + REQUIRE(noneAny.to_string() == "Unprintable value"); auto movedNoneAny = std::move(noneAny); REQUIRE_THROWS_MATCHES(any_cast(noneAny), bad_any_cast, ExceptionMatcher()); REQUIRE_THROWS_MATCHES(any_cast(movedNoneAny), bad_any_cast, ExceptionMatcher()); + REQUIRE(movedNoneAny.to_string() == "Unprintable value"); + REQUIRE(noneAny.to_string() == "Unprintable value"); } From 4da2a19287ceaa678f5a868c07e9610765461091 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sun, 24 Dec 2023 20:34:07 +0100 Subject: [PATCH 08/98] Update sole to latest --- include/sole/sole.h | 210 ++++++++++++++++++++++++++++++-------------- 1 file changed, 143 insertions(+), 67 deletions(-) diff --git a/include/sole/sole.h b/include/sole/sole.h index 98fc886..8199622 100644 --- a/include/sole/sole.h +++ b/include/sole/sole.h @@ -43,6 +43,8 @@ * - rlyeh ~~ listening to Hedon Cries / Until The Sun Goes up */ +// Note: modified to fit Ichor's warning flags (except sign conversion) and added fmt::formatter. + ////////////////////////////////////////////////////////////////////////////////////// #pragma once @@ -52,16 +54,25 @@ #include #include +#if (!defined(WIN32) && !defined(_WIN32) && !defined(__WIN32)) || defined(__CYGWIN__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + // public API -#define SOLE_VERSION "1.0.1" /* (2017/05/16): Improve UUID4 and base62 performance; fix warnings +#define SOLE_VERSION "1.0.5" /* (2022/07/15): Fix #42 (clang: __thread vs threadlocal) +#define SOLE_VERSION "1.0.4" // (2022/04/09): Fix potential threaded issues (fix #18, PR #39) and a socket leak (fix #38) +#define SOLE_VERSION "1.0.3" // (2022/01/17): Merge fixes by @jasonwinterpixel(emscripten) + @jj-tetraquark(get_any_mac) +#define SOLE_VERSION "1.0.2" // (2021/03/16): Merge speed improvements by @vihangm +#define SOLE_VERSION "1.0.1" // (2017/05/16): Improve UUID4 and base62 performance; fix warnings #define SOLE_VERSION "1.0.0" // (2016/02/03): Initial semver adherence; Switch to header-only; Remove warnings */ namespace sole { // 128-bit basic UUID type that allows comparison and sorting. // Use .str() for printing and .pretty() for pretty printing. - // Also, ostream friendly. + // Also, ostream friendly. struct uuid { uint64_t ab; @@ -71,9 +82,9 @@ namespace sole bool operator!=( const uuid &other ) const; bool operator <( const uuid &other ) const; - [[nodiscard]] std::string pretty() const; - [[nodiscard]] std::string base62() const; - [[nodiscard]] std::string str() const; + std::string pretty() const; + std::string base62() const; + std::string str() const; template inline friend ostream &operator<<( ostream &os, const uuid &self ) { @@ -84,9 +95,9 @@ namespace sole // Generators uuid uuid0(); // UUID v0, pro: unique; cons: MAC revealed, pid revealed, predictable. uuid uuid1(); // UUID v1, pro: unique; cons: MAC revealed, predictable. - uuid uuid4(); // UUID v4, pros: anonymous, fast; con: uuids "can cla.h> + uuid uuid4(); // UUID v4, pros: anonymous, fast; con: uuids "can clash" - // Rebuilders + // Rebuilders a.k.a. From String uuid rebuild( uint64_t ab, uint64_t cd ); uuid rebuild( const std::string &uustr ); } @@ -132,7 +143,7 @@ namespace std { #include #include -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32)) && !defined(__CYGWIN__) # include # include # include @@ -161,6 +172,7 @@ namespace std { # define _osx _yes #elif defined(__linux__) # include +# include # include # include # include @@ -169,6 +181,9 @@ namespace std { # include # define _linux _yes #else //elif defined(__unix__) +# if defined(__EMSCRIPTEN__) +# define _emscripten _yes +# endif # if defined(__VMS) # include # include @@ -250,6 +265,12 @@ namespace std #define _melse _yes #endif +#ifdef _emscripten +#define _emselse _no +#else +#define _emselse _yes +#endif + #define _yes(...) __VA_ARGS__ #define _no(...) @@ -273,7 +294,7 @@ namespace sole { try { // Taken from parameter //std::string locale; // = "es-ES", "Chinese_China.936", "en_US.UTF8", etc... - std::time_t t = static_cast(timestamp_secs); + std::time_t t = timestamp_secs; std::tm tm; _msvc( localtime_s( &tm, &t ); @@ -306,7 +327,7 @@ namespace sole { uint64_t c = (cd >> 32); uint64_t d = (cd & 0xFFFFFFFF); - int version = static_cast(b & 0xF000) >> 12; + int version = (b & 0xF000) >> 12; uint64_t timestamp = ((b & 0x0FFF) << 48 ) | (( b >> 16 ) << 32) | a; // in 100ns units ss << "version=" << (version) << ','; @@ -327,26 +348,32 @@ namespace sole { } inline std::string uuid::str() const { - std::stringstream ss; - ss << std::hex << std::nouppercase << std::setfill('0'); + char uustr[] = "00000000-0000-0000-0000-000000000000"; + constexpr char encode[] = "0123456789abcdef"; - auto a = static_cast(ab >> 32); - auto b = static_cast(ab & 0xFFFFFFFF); - auto c = static_cast(cd >> 32); - auto d = static_cast(cd & 0xFFFFFFFF); + size_t bit = 15; + for( size_t i = 0; i < 18; i++ ) { + if( i == 8 || i == 13 ) { + continue; + } + uustr[i] = encode[ab>>4*bit&0x0f]; + bit--; + } - ss << std::setw(8) << (a) << '-'; - ss << std::setw(4) << (b >> 16) << '-'; - ss << std::setw(4) << (b & 0xFFFF) << '-'; - ss << std::setw(4) << (c >> 16 ) << '-'; - ss << std::setw(4) << (c & 0xFFFF); - ss << std::setw(8) << d; + bit = 15; + for( size_t i = 18; i < 36; i++ ) { + if( i == 18 || i == 23 ) { + continue; + } + uustr[i] = encode[cd>>4*bit&0x0f]; + bit--; + } - return ss.str(); + return std::string(uustr); } inline std::string uuid::base62() const { - uint64_t base62len = 10 + 26 + 26; + int base62len = 10 + 26 + 26; const char base62[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -432,7 +459,7 @@ namespace sole { return 0; } ) - _lelse( _belse( // if not linux, if not bsd... valid for apple/win32 + _lelse( _belse( _emselse ( // if not linux, if not bsd, if not emscripten... valid for apple/win32 inline int clock_gettime( int /*clk_id*/, struct timespec* t ) { struct timeval now; int rv = gettimeofday(&now, NULL); @@ -441,7 +468,7 @@ namespace sole { t->tv_nsec = now.tv_usec * 1000; return 0; } - )) + ))) ////////////////////////////////////////////////////////////////////////////////////// // Timestamp and MAC interfaces @@ -453,16 +480,17 @@ namespace sole { // Convert to 100-nanosecond intervals uint64_t uuid_time; - uuid_time = static_cast(tp.tv_sec * 10000000); - uuid_time = uuid_time + static_cast(tp.tv_nsec / 100); - uuid_time = uuid_time + offset; + uuid_time = tp.tv_sec * 10000000; + uuid_time = uuid_time + (tp.tv_nsec / 100); // If the clock looks like it went backwards, or is the same, increment it. static uint64_t last_uuid_time = 0; - if( last_uuid_time > uuid_time ) - last_uuid_time = uuid_time; + if( last_uuid_time >= uuid_time ) + uuid_time = ++last_uuid_time; else - last_uuid_time = ++uuid_time; + last_uuid_time = uuid_time; + + uuid_time = uuid_time + offset; return uuid_time; } @@ -559,7 +587,7 @@ namespace sole { int alen = sdl->sdl_alen; if (ap && alen > 0) { - _node.resize( static_cast(alen) ); + _node.resize( alen ); std::memcpy(_node.data(), ap, _node.size() ); foundAdapter = true; break; @@ -572,19 +600,32 @@ namespace sole { }) _linux({ - struct ifreq ifr; - - int s = socket(PF_INET, SOCK_DGRAM, 0); - if (s == -1) return _no("cannot open socket") false; - - std::strcpy(ifr.ifr_name, "eth0"); - int rc = ioctl(s, SIOCGIFHWADDR, &ifr); - close(s); - if (rc < 0) return _no("cannot get MAC address") false; - struct sockaddr* sa = reinterpret_cast(&ifr.ifr_addr); - _node.resize( sizeof(sa->sa_data) ); - std::memcpy(_node.data(), sa->sa_data, _node.size() ); + struct ifaddrs* ifaphead; + if (getifaddrs(&ifaphead) == -1) return _no("cannot get network adapter list") false; + + bool foundAdapter = false; + for (struct ifaddrs* ifap = ifaphead; ifap; ifap = ifap->ifa_next) + { + struct ifreq ifr; + int s = socket(PF_INET, SOCK_DGRAM, 0); + if (s == -1) continue; + + if (std::strcmp("lo", ifap->ifa_name) == 0) { close(s); continue;} // loopback address is zero + + std::strcpy(ifr.ifr_name, ifap->ifa_name); + int rc = ioctl(s, SIOCGIFHWADDR, &ifr); + close(s); + if (rc < 0) continue; + struct sockaddr* sa = reinterpret_cast(&ifr.ifr_addr); + _node.resize( sizeof(sa->sa_data) ); + std::memcpy(_node.data(), sa->sa_data, _node.size() ); + foundAdapter = true; + break; + } + freeifaddrs(ifaphead); + if (!foundAdapter) return _no("cannot determine MAC address (no suitable network adapter found)") false; return true; + }) _unix({ @@ -633,13 +674,12 @@ namespace sole { inline uuid uuid4() { static thread_local std::random_device rd; - static thread_local std::mt19937 rng(rd()); - static thread_local std::uniform_int_distribution dist(0, std::numeric_limits::max()); + static thread_local std::uniform_int_distribution dist(0, (uint64_t)(~0)); uuid my; - my.ab = dist(rng); - my.cd = dist(rng); + my.ab = dist(rd); + my.cd = dist(rd); my.ab = (my.ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL; my.cd = (my.cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL; @@ -656,7 +696,7 @@ namespace sole { uint32_t time_low = ns100_intervals & 0xffffffff; uint16_t time_mid = (ns100_intervals >> 32) & 0xffff; uint16_t time_hi_version = (ns100_intervals >> 48) & 0xfff; - uint8_t clock_seq_low = static_cast(clock_seq & 0xff); + uint8_t clock_seq_low = clock_seq & 0xff; uint8_t clock_seq_hi_variant = (clock_seq >> 8) & 0x3f; uuid u; @@ -678,7 +718,7 @@ namespace sole { // Set the version number. enum { version = 1 }; - upper_ &= ~0xf000u; + upper_ &= ~0xf000; upper_ |= version << 12; return u; @@ -687,15 +727,15 @@ namespace sole { inline uuid uuid0() { // Number of 100-ns intervals since Unix epoch time uint64_t ns100_intervals = get_time( 0 ); - uint64_t pid = _windows( _getpid() ) _welse( static_cast(getpid()) ); + uint64_t pid = _windows( _getpid() ) _welse( getpid() ); uint16_t pid16 = (uint16_t)( pid & 0xffff ); // 16-bits max uint64_t mac = get_any_mac48(); // 48-bits max uint32_t time_low = ns100_intervals & 0xffffffff; uint16_t time_mid = (ns100_intervals >> 32) & 0xffff; uint16_t time_hi_version = (ns100_intervals >> 48) & 0xfff; - uint8_t pid_low = static_cast(pid16 & 0xff); - uint8_t pid_hi = static_cast((pid16 >> 8) & 0xff); + uint8_t pid_low = pid16 & 0xff; + uint8_t pid_hi = (pid16 >> 8) & 0xff; uuid u; uint64_t &upper_ = u.ab; @@ -712,7 +752,7 @@ namespace sole { // Set the version number. enum { version = 0 }; - upper_ &= ~0xf000u; + upper_ &= ~0xf000; upper_ |= version << 12; return u; @@ -725,19 +765,17 @@ namespace sole { } inline uuid rebuild( const std::string &uustr ) { - char sep; - uint64_t a,b,c,d,e; uuid u = { 0, 0 }; auto idx = uustr.find_first_of("-"); if( idx != std::string::npos ) { // single separator, base62 notation if( uustr.find_first_of("-",idx+1) == std::string::npos ) { auto rebase62 = [&]( const char *input, size_t limit ) -> uint64_t { - uint64_t base62len = 10 + 26 + 26; + int base62len = 10 + 26 + 26; auto strpos = []( char ch ) -> size_t { - if( ch >= 'a' ) return static_cast(ch - 'a' + 10 + 26); - if( ch >= 'A' ) return static_cast(ch - 'A' + 10); - return static_cast(ch - '0'); + if( ch >= 'a' ) return ch - 'a' + 10 + 26; + if( ch >= 'A' ) return ch - 'A' + 10; + return ch - '0'; }; uint64_t res = strpos( input[0] ); for( size_t i = 1; i < limit; ++i ) @@ -749,11 +787,24 @@ namespace sole { } // else classic hex notation else { - std::stringstream ss( uustr ); - if( ss >> std::hex >> a >> sep >> b >> sep >> c >> sep >> d >> sep >> e ) { - if( ss.eof() ) { - u.ab = (a << 32) | (b << 16) | c; - u.cd = (d << 48) | e; + if (uustr[8] == '-' || uustr[13] == '-' || uustr[18] == '-' || uustr[23] == '-') { + auto decode = []( char ch ) -> size_t { + if( 'f' >= ch && ch >= 'a' ) return ch - 'a' + 10; + if( 'F' >= ch && ch >= 'A' ) return ch - 'A' + 10; + if( '9' >= ch && ch >= '0' ) return ch - '0'; + return 0; + }; + for( size_t i = 0; i < 18; i++ ) { + if( i == 8 || i == 13 ) { + continue; + } + u.ab = u.ab<<4 | decode(uustr[i]); + } + for( size_t i = 19; i < 36; i++ ) { + if( i == 23 ) { + continue; + } + u.cd = u.cd<<4 | decode(uustr[i]); } } } @@ -890,6 +941,12 @@ int main() { run::benchmark(uuid1, "v1"); run::benchmark(uuid4, "v4"); + auto uustr = uuid4().str(); + run::benchmark([=]() { sole::rebuild( uustr ); }, "rebuild"); + + auto uuid = uuid4(); + run::benchmark([=]() { uuid.str(); }, "str"); + run::verify(uuid4); // use fastest implementation // run::tests(uuid0); // not applicable @@ -897,4 +954,23 @@ int main() { run::tests(uuid4); } -#endif \ No newline at end of file +#endif + + +#if (!defined(WIN32) && !defined(_WIN32) && !defined(__WIN32)) || defined(__CYGWIN__) +#pragma GCC diagnostic pop +#endif + +#include + +template <> +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { + return ctx.end(); + } + + template + auto format(const sole::uuid& change, FormatContext& ctx) { + return fmt::format_to(ctx.out(), "{}", change.str()); + } +}; From c68b6001c7d6ef00e2450a3f7870e5c62869bc75 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sun, 24 Dec 2023 22:10:19 +0100 Subject: [PATCH 09/98] Remove multimap, different definition between absl and std --- include/ichor/Common.h | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/include/ichor/Common.h b/include/ichor/Common.h index 6f695a7..3bed6b8 100644 --- a/include/ichor/Common.h +++ b/include/ichor/Common.h @@ -9,7 +9,6 @@ #ifdef ICHOR_USE_ABSEIL #include #include -#include #include #else #include @@ -125,13 +124,6 @@ namespace Ichor { class Eq = std::equal_to, class Allocator = std::allocator> using unordered_set = absl::flat_hash_set; - template < - class Key, - class T, - class Hash = std::hash, - class Eq = std::less, - class Allocator = std::allocator>> - using unordered_multimap = absl::btree_multimap; #else template < class Key, @@ -146,13 +138,6 @@ namespace Ichor { class Eq = std::equal_to, class Allocator = std::allocator> using unordered_set = std::unordered_set; - template < - class Key, - class T, - class Hash = std::hash, - class Eq = std::equal_to, - class Allocator = std::allocator>> - using unordered_multimap = std::unordered_multimap; #endif using Properties = unordered_map; From 4290ddce1b4d793d9ec638217a374154ef534521 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sun, 24 Dec 2023 22:25:14 +0100 Subject: [PATCH 10/98] Add introspection example and expanded ways to introspect dependencies --- .github/workflows/cmake.yml | 2 +- .github/workflows/coverage.yml | 4 +- build.sh | 1 + examples/CMakeLists.txt | 5 + examples/common/DebugService.h | 2 +- .../IntrospectionService.h | 50 ++++++++ examples/introspection_example/README.md | 61 ++++++++++ examples/introspection_example/main.cpp | 38 ++++++ include/ichor/DependencyManager.h | 87 ++++++-------- .../ConstructorInjectionService.h | 16 ++- .../ichor/dependency_management/Dependency.h | 3 +- .../DependencyLifecycleManager.h | 4 +- .../DependencyManagerLifecycleManager.h | 4 +- .../DependencyRegister.h | 4 +- .../IServiceInterestedLifecycleManager.h | 2 +- .../dependency_management/InternalService.h | 13 ++- .../dependency_management/LifecycleManager.h | 2 +- .../QueueLifecycleManager.h | 4 +- include/sole/sole.h | 1 + quickbuild.sh | 1 + src/ichor/DependencyManager.cpp | 109 ++++++++++++++++-- test/CoroutineTests.cpp | 20 ++-- test/TestServices/StopsInAsyncStartService.h | 2 +- 23 files changed, 340 insertions(+), 95 deletions(-) create mode 100644 examples/introspection_example/IntrospectionService.h create mode 100644 examples/introspection_example/README.md create mode 100644 examples/introspection_example/main.cpp diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 2689ab9..d3b879d 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -56,5 +56,5 @@ jobs: - name: Examples working-directory: ${{github.workspace}}/bin - run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_tracker_example && ../bin/ichor_websocket_example && ../bin/ichor_websocket_example -t4 && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example + run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_tracker_example && ../bin/ichor_websocket_example && ../bin/ichor_websocket_example -t4 && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example && ../bin/ichor_introspection_example diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 52003f3..d5bad63 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -13,7 +13,7 @@ concurrency: jobs: build: - runs-on: self-hosted + runs-on: coverage steps: - uses: actions/checkout@v3 @@ -35,7 +35,7 @@ jobs: - name: Examples working-directory: ${{github.workspace}}/bin - run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_tracker_example && ../bin/ichor_websocket_example && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example + run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_tracker_example && ../bin/ichor_websocket_example && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example && ../bin/ichor_introspection_example - name: coverage run: lcov --directory . --capture --output-file coverage.info && lcov --remove coverage.info '/usr/*' '/opt/*' '${{github.workspace}}/external/*' --output-file coverage.info && lcov --list coverage.info && codecov -f coverage.info diff --git a/build.sh b/build.sh index e987ea4..619aacf 100755 --- a/build.sh +++ b/build.sh @@ -58,6 +58,7 @@ run_examples () ../bin/ichor_tcp_example || exit 1 ../bin/ichor_timer_example || exit 1 ../bin/ichor_tracker_example || exit 1 + ../bin/ichor_introspection_example || exit 1 ../bin/ichor_websocket_example || exit 1 ../bin/ichor_websocket_example -t4 || exit 1 ../bin/ichor_yielding_timer_example || exit 1 diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 37c9bce..e47802b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -8,6 +8,11 @@ add_executable(ichor_minimal_example ${EXAMPLE_SOURCES}) target_link_libraries(ichor_minimal_example ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(ichor_minimal_example ichor) +file(GLOB_RECURSE EXAMPLE_SOURCES ${ICHOR_TOP_DIR}/examples/introspection_example/*.cpp) +add_executable(ichor_introspection_example ${EXAMPLE_SOURCES}) +target_link_libraries(ichor_introspection_example ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(ichor_introspection_example ichor) + file(GLOB_RECURSE EXAMPLE_SOURCES ${ICHOR_TOP_DIR}/examples/tracker_example/*.cpp) add_executable(ichor_tracker_example ${EXAMPLE_SOURCES}) target_link_libraries(ichor_tracker_example ${CMAKE_THREAD_LIBS_INIT}) diff --git a/examples/common/DebugService.h b/examples/common/DebugService.h index 9640f83..e6eafc0 100644 --- a/examples/common/DebugService.h +++ b/examples/common/DebugService.h @@ -19,7 +19,7 @@ class DebugService final : public AdvancedService { Task> start() final { auto &_timer = _timerFactory->createTimer(); _timer.setCallback([this]() { - auto svcs = dm.getServiceInfo(); + auto svcs = dm.getAllServices(); for(auto &[id, svc] : svcs) { ICHOR_LOG_INFO(_logger, "Svc {}:{} {}", svc->getServiceId(), svc->getServiceName(), svc->getServiceState()); } diff --git a/examples/introspection_example/IntrospectionService.h b/examples/introspection_example/IntrospectionService.h new file mode 100644 index 0000000..39c7859 --- /dev/null +++ b/examples/introspection_example/IntrospectionService.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include +#include + +using namespace Ichor; + +class IntrospectionService final { +public: + IntrospectionService(IService *self, IEventQueue *queue, DependencyManager *dm, ILogger *logger, ITimerFactory *) : _logger(logger) { + ICHOR_LOG_INFO(_logger, "IntrospectionService started"); + + auto svcs = dm->getAllServices(); + + for(auto &[svcId, svc] : svcs) { + ICHOR_LOG_INFO(_logger, "Service {}:{}, guid {} priority {} state {}", svc->getServiceId(), svc->getServiceName(), svc->getServiceGid(), svc->getServicePriority(), svc->getServiceState()); + + ICHOR_LOG_INFO(_logger, "\tProperties:"); + for(auto &[key, val] : svc->getProperties()) { + ICHOR_LOG_INFO(_logger, "\t\tProperty {} value {} size {} type {}", key, val.to_string(), val.get_size(), val.type_name()); + } + + auto deps = dm->getDependencyRequestsForService(svc->getServiceId()); + ICHOR_LOG_INFO(_logger, "\tDependencies:"); + for(auto &dep : deps) { + ICHOR_LOG_INFO(_logger, "\t\tDependency {} required {} satisfied {}", dep.interfaceName, dep.required, dep.satisfied); + } + auto dependants = dm->getDependentsForService(svc->getServiceId()); + ICHOR_LOG_INFO(_logger, "\tUsed by:"); + for(auto &dep : dependants) { + ICHOR_LOG_INFO(_logger, "\t\tDependant {}:{}", dep->getServiceId(), dep->getServiceName()); + } + ICHOR_LOG_INFO(_logger, ""); + + } + + // TODO set logger of other service + + queue->pushEvent(self->getServiceId()); + } + ~IntrospectionService() { + ICHOR_LOG_INFO(_logger, "IntrospectionService stopped"); + } + +private: + ILogger *_logger; +}; diff --git a/examples/introspection_example/README.md b/examples/introspection_example/README.md new file mode 100644 index 0000000..97b3722 --- /dev/null +++ b/examples/introspection_example/README.md @@ -0,0 +1,61 @@ +# Introspection Example + +This example showcases Ichor's capability introspect services and properties, without knowing their type directly. Each service contains metadata about its Id/Guid, whether it is started, what its properties are and so on. + +Introspecting allows for monitoring and controlling individual services. In this example, all services are iterated and their metadata printed to stdout. It also shows how to change logger settings that belong to a specific service. + +Example output: + +```text +Service 1:Ichor::IEventQueue, guid 45571cc5-f381-42a3-8e29-529a43d5a164 priority 1000 state INSTALLED + Properties: + Dependencies: + Used by: + Dependant 7:Ichor::TimerFactory + Dependant 5:IntrospectionService + +Service 2:Ichor::DependencyManager, guid 4bef6ae7-89ac-4ab4-9bed-6b31bf752066 priority 1000 state INSTALLED + Properties: + Dependencies: + Used by: + Dependant 5:IntrospectionService + +Service 3:Ichor::LoggerFactory, guid 65eb5090-ac98-4f1e-a486-4da196200830 priority 1000 state ACTIVE + Properties: + Property DefaultLogLevel value LOG_INFO size 4 type Ichor::LogLevel + Dependencies: + Dependency Ichor::IFrameworkLogger required false satisfied 0 + Used by: + +Service 4:Ichor::TimerFactoryFactory, guid 3614ba22-7058-4722-bf85-b34370621ef3 priority 1000 state ACTIVE + Properties: + Dependencies: + Used by: + +Service 5:IntrospectionService, guid ccc4887f-46a7-438a-b04d-3c7fb84c0a8c priority 1000 state STARTING + Properties: + Dependencies: + Dependency Ichor::ITimerFactory required true satisfied 0 + Dependency Ichor::ILogger required true satisfied 0 + Dependency Ichor::DependencyManager required true satisfied 0 + Dependency Ichor::IEventQueue required true satisfied 0 + Dependency Ichor::IService required true satisfied 0 + Used by: + +Service 6:Ichor::CoutLogger, guid 0c3c83e7-55fb-4d10-8c82-705bb2e1d139 priority 1000 state ACTIVE + Properties: + Property LogLevel value LOG_INFO size 4 type Ichor::LogLevel + Property Filter value size 16 type Ichor::Filter + Dependencies: + Used by: + Dependant 5:IntrospectionService + +Service 7:Ichor::TimerFactory, guid 90f45076-a7bb-4d09-8416-c7590654c746 priority 1000 state ACTIVE + Properties: + Property requestingSvcId value 5 size 8 type unsigned long + Dependencies: + Dependency Ichor::IEventQueue required true satisfied 0 + Used by: + Dependant 5:IntrospectionService + +``` diff --git a/examples/introspection_example/main.cpp b/examples/introspection_example/main.cpp new file mode 100644 index 0000000..099338d --- /dev/null +++ b/examples/introspection_example/main.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include "IntrospectionService.h" + +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. +#ifdef ICHOR_USE_SPDLOG +#include +#define LOGGER_TYPE SpdlogLogger +#else +#include +#define LOGGER_TYPE CoutLogger +#endif + +#include +#include + +using namespace Ichor; +using namespace std::string_literals; + +int main(int argc, char *argv[]) { + std::locale::global(std::locale("en_US.UTF-8")); + + auto start = std::chrono::steady_clock::now(); + auto queue = std::make_unique(); + auto &dm = queue->createManager(); +#ifdef ICHOR_USE_SPDLOG + dm.createServiceManager(); +#endif + dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); + dm.createServiceManager(); + dm.createServiceManager(); + queue->start(CaptureSigInt); + auto end = std::chrono::steady_clock::now(); + fmt::print("{} ran for {:L} µs\n", argv[0], std::chrono::duration_cast(end-start).count()); + + return 0; +} diff --git a/include/ichor/DependencyManager.h b/include/ichor/DependencyManager.h index 76f9beb..6669417 100644 --- a/include/ichor/DependencyManager.h +++ b/include/ichor/DependencyManager.h @@ -32,7 +32,7 @@ namespace Ichor { // Moved here from InternalEvents.h to prevent circular includes /// Used to prevent modifying the _services container while iterating over it through f.e. DependencyOnline() struct InsertServiceEvent final : public Event { - InsertServiceEvent(uint64_t _id, uint64_t _originatingService, uint64_t _priority, std::unique_ptr _mgr) noexcept : Event(TYPE, NAME, _id, _originatingService, _priority), mgr(std::move(_mgr)) {} + InsertServiceEvent(uint64_t _id, ServiceIdType _originatingService, uint64_t _priority, std::unique_ptr _mgr) noexcept : Event(TYPE, NAME, _id, _originatingService, _priority), mgr(std::move(_mgr)) {} ~InsertServiceEvent() final = default; std::unique_ptr mgr; @@ -41,23 +41,23 @@ namespace Ichor { }; struct EventWaiter final { - explicit EventWaiter(uint64_t _waitingSvcId, uint64_t _eventType) : waitingSvcId(_waitingSvcId), eventType(_eventType) { + explicit EventWaiter(ServiceIdType _waitingSvcId, uint64_t _eventType) : waitingSvcId(_waitingSvcId), eventType(_eventType) { events.emplace_back(_eventType, std::make_unique()); } EventWaiter(const EventWaiter &) = delete; EventWaiter(EventWaiter &&o) noexcept = default; std::vector>> events{}; - uint64_t waitingSvcId; + ServiceIdType waitingSvcId; uint64_t eventType; uint32_t count{1}; // default of 1, to be decremented in event completion/error }; - class DependencyManager final { + class [[nodiscard]] DependencyManager final { private: explicit DependencyManager(IEventQueue *eventQueue); public: - // DANGEROUS COPY, EFFECTIVELY MAKES A NEW MANAGER AND STARTS OVER!! - // Only implemented so that the manager can be easily used in STL containers before anything is using it. + /// DANGEROUS COPY, EFFECTIVELY MAKES A NEW MANAGER AND STARTS OVER!! + /// Only implemented so that the manager can be easily used in STL containers before anything is using it. [[deprecated("DANGEROUS COPY, EFFECTIVELY MAKES A NEW MANAGER AND STARTS OVER!! The moved-from manager cannot be registered with a CommunicationChannel, or UB occurs.")]] DependencyManager(const DependencyManager& other) : _eventQueue(other._eventQueue) { if(other._started.load(std::memory_order_acquire)) [[unlikely]] { @@ -129,7 +129,7 @@ namespace Ichor { #if (!defined(WIN32) && !defined(_WIN32) && !defined(__WIN32)) || defined(__CYGWIN__) requires Derived #endif - AsyncGenerator pushPrioritisedEventAsync(uint64_t originatingServiceId, uint64_t priority, bool coalesce, Args&&... args) { + Task pushPrioritisedEventAsync(ServiceIdType originatingServiceId, uint64_t priority, bool coalesce, Args&&... args) { #ifdef ICHOR_USE_HARDENING if(this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? std::terminate(); @@ -165,7 +165,7 @@ namespace Ichor { } uint64_t eventId = _eventQueue->getNextEventId(); - _eventQueue->pushEventInternal(priority, std::unique_ptr{new EventT(std::forward(eventId), std::forward(originatingServiceId), std::forward(priority), std::forward(args)...)}); + _eventQueue->pushEventInternal(priority, std::unique_ptr{new EventT(std::forward(eventId), std::forward(originatingServiceId), std::forward(priority), std::forward(args)...)}); auto it = _eventWaiters.emplace(eventId, EventWaiter(originatingServiceId, EventT::TYPE)); INTERNAL_DEBUG("pushPrioritisedEventAsync {}:{} {} waiting {} {}", eventId, typeName(), originatingServiceId, it.first->second.count, it.first->second.events.size()); co_await *it.first->second.events.begin()->second.get(); @@ -284,7 +284,7 @@ namespace Ichor { /// \param impl class that is registering handler /// \param targetServiceId optional service id to filter registering for, if empty, receive all events of type EventT /// \return RAII handler, removes registration upon destruction - EventHandlerRegistration registerEventHandler(Impl *impl, IService *self, tl::optional targetServiceId = {}) { + EventHandlerRegistration registerEventHandler(Impl *impl, IService *self, tl::optional targetServiceId = {}) { #ifdef ICHOR_USE_HARDENING if(this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? std::terminate(); @@ -390,44 +390,16 @@ namespace Ichor { /// Get IService by local ID /// \param id service id /// \return optional - [[nodiscard]] tl::optional getIService(uint64_t id) const noexcept { -#ifdef ICHOR_USE_HARDENING - if(this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? - std::terminate(); - } -#endif - - auto svc = _services.find(id); + [[nodiscard]] tl::optional> getIService(ServiceIdType id) const noexcept; - if(svc == _services.end()) { - return {}; - } - - return svc->second->getIService(); - } /// Get IService by global ID, much slower than getting by local ID /// \param id service uuid /// \return optional - [[nodiscard]] tl::optional getIService(sole::uuid id) const noexcept { -#ifdef ICHOR_USE_HARDENING - if(this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? - std::terminate(); - } -#endif - auto svc = std::find_if(_services.begin(), _services.end(), [&id](const std::pair> &svcPair) { - return svcPair.second->getIService()->getServiceGid() == id; - }); - - if(svc == _services.end()) { - return {}; - } - - return svc->second->getIService(); - } + [[nodiscard]] tl::optional> getIService(sole::uuid id) const noexcept; template - [[nodiscard]] tl::optional> getService(uint64_t id) const noexcept { + [[nodiscard]] tl::optional> getService(ServiceIdType id) const noexcept { #ifdef ICHOR_USE_HARDENING if(this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? std::terminate(); @@ -476,10 +448,12 @@ namespace Ichor { return ret; } - template /// Get all services by given template interface type, regardless of state + /// Do not use in coroutines or other threads. + /// Not thread-safe. /// \tparam Interface interface to search for /// \return list of found services + template [[nodiscard]] std::vector> getAllServicesOfType() noexcept { #ifdef ICHOR_USE_HARDENING if(this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? @@ -502,20 +476,25 @@ namespace Ichor { return ret; } + [[nodiscard]] std::vector getDependencyRequestsForService(ServiceIdType svcId) const noexcept; + [[nodiscard]] std::vector> getDependentsForService(ServiceIdType svcId) const noexcept; + /// Returns a list of currently known services and their status. - /// Do not use in coroutines or other threads - /// NOT thread-safe!! + /// Do not use in coroutines or other threads. + /// Not thread-safe. /// \return map of [serviceId, service] - [[nodiscard]] unordered_map getServiceInfo() const noexcept; + [[nodiscard]] unordered_map> getAllServices() const noexcept; - // Mainly useful for tests + /// Blocks until the queue is empty or the specified timeout has passed. + /// Mainly useful for tests void runForOrQueueEmpty(std::chrono::milliseconds ms = 100ms) const noexcept; - /// Gets the class name of the implementation for given serviceId + /// Convenience function. Gets the class name of the implementation for given serviceId. /// \param serviceId /// \return nullopt if serviceId does not exist, name of implementation class otherwise - [[nodiscard]] tl::optional getImplementationNameFor(uint64_t serviceId) const noexcept; + [[nodiscard]] tl::optional getImplementationNameFor(ServiceIdType serviceId) const noexcept; + /// Gets the associated event queue [[nodiscard]] IEventQueue& getEventQueue() const noexcept; /// Async method to wait until a service is started @@ -646,7 +625,7 @@ namespace Ichor { /// \tparam Interfaces /// \param id template - void logAddService(uint64_t id) { + void logAddService(ServiceIdType id) { if(_logger != nullptr && _logger->getLogLevel() <= LogLevel::LOG_DEBUG) { fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "added ServiceManager<{}, {}, ", typeName(), typeName()); @@ -661,7 +640,7 @@ namespace Ichor { /// \tparam Interface /// \param id template - void logAddService(uint64_t id) { + void logAddService(ServiceIdType id) { if(_logger != nullptr && _logger->getLogLevel() <= LogLevel::LOG_DEBUG) { ICHOR_LOG_DEBUG(_logger, "added ServiceManager<{}, {}> {}", typeName(), typeName(), id); } @@ -671,7 +650,7 @@ namespace Ichor { /// \tparam Impl /// \param id template - void logAddService(uint64_t id) { + void logAddService(ServiceIdType id) { if(_logger != nullptr && _logger->getLogLevel() <= LogLevel::LOG_DEBUG) { ICHOR_LOG_DEBUG(_logger, "added ServiceManager<{}> {}", typeName(), id); } @@ -693,20 +672,20 @@ namespace Ichor { /// Check if there is a coroutine for the given serviceId that is still waiting on something /// \param serviceId /// \return - [[nodiscard]] bool existingCoroutineFor(uint64_t serviceId) const noexcept; + [[nodiscard]] bool existingCoroutineFor(ServiceIdType serviceId) const noexcept; /// Coroutine based method to wait for a service to have finished with either DependencyOfflineEvent or StopServiceEvent /// \param serviceId /// \param eventType /// \return - Task waitForService(uint64_t serviceId, uint64_t eventType) noexcept; + Task waitForService(ServiceIdType serviceId, uint64_t eventType) noexcept; /// Counterpart for waitForService, runs all waiting coroutines for specified event /// \param serviceId /// \param eventType /// \param eventName /// \return - bool finishWaitingService(uint64_t serviceId, uint64_t eventType, [[maybe_unused]] std::string_view eventName) noexcept; + bool finishWaitingService(ServiceIdType serviceId, uint64_t eventType, [[maybe_unused]] std::string_view eventName) noexcept; - unordered_map> _services{}; // key = service id + unordered_map> _services{}; // key = service id unordered_map> _dependencyRequestTrackers{}; // key = interface name hash unordered_map> _dependencyUndoRequestTrackers{}; // key = interface name hash unordered_map> _completionCallbacks{}; // key = listening service id + event type diff --git a/include/ichor/dependency_management/ConstructorInjectionService.h b/include/ichor/dependency_management/ConstructorInjectionService.h index 5ec2a76..c538728 100644 --- a/include/ichor/dependency_management/ConstructorInjectionService.h +++ b/include/ichor/dependency_management/ConstructorInjectionService.h @@ -166,8 +166,20 @@ namespace Ichor { template void createServiceSpecialSauce(tl::optional> = {}) { try { - new (buf) T(std::get(_deps[typeNameHash>()])...); - } catch (std::exception const &) { + new(buf) T(std::get(_deps[typeNameHash>()])...); + } catch (fmt::format_error const &e) { + fmt::print("User error in service {}:{}: \"{}\"\n", getServiceId(), getServiceName(), e.what()); + std::terminate(); + } catch (std::exception const &e) { + fmt::print("Std exception while starting svc {}:{} : \"{}\".\n\nLikely user error, but printing extra information anyway: Stored dependencies:\n", e.what(), getServiceId(), getServiceName()); + for(auto &[key, _] : _deps) { + fmt::print("{} ", key); + } + fmt::print("\n"); + + fmt::print("Requested deps:\n"); + (fmt::print("{}:{} ", typeNameHash>(), typeName>()), ...); + fmt::print("\n"); std::terminate(); } } diff --git a/include/ichor/dependency_management/Dependency.h b/include/ichor/dependency_management/Dependency.h index 1ddc8f8..9a3c00f 100644 --- a/include/ichor/dependency_management/Dependency.h +++ b/include/ichor/dependency_management/Dependency.h @@ -4,7 +4,7 @@ namespace Ichor { struct Dependency { - Dependency(uint64_t _interfaceNameHash, bool _required, uint64_t _satisfied) noexcept : interfaceNameHash(_interfaceNameHash), required(_required), satisfied(_satisfied) {} + Dependency(uint64_t _interfaceNameHash, std::string_view _interfaceName, bool _required, uint64_t _satisfied) noexcept : interfaceNameHash(_interfaceNameHash), interfaceName(_interfaceName), required(_required), satisfied(_satisfied) {} Dependency(const Dependency &other) noexcept = default; Dependency(Dependency &&other) noexcept = default; Dependency& operator=(const Dependency &other) noexcept = default; @@ -14,6 +14,7 @@ namespace Ichor { } uint64_t interfaceNameHash; + std::string_view interfaceName; bool required; uint64_t satisfied; }; diff --git a/include/ichor/dependency_management/DependencyLifecycleManager.h b/include/ichor/dependency_management/DependencyLifecycleManager.h index a6da598..01ddc57 100644 --- a/include/ichor/dependency_management/DependencyLifecycleManager.h +++ b/include/ichor/dependency_management/DependencyLifecycleManager.h @@ -19,7 +19,7 @@ namespace Ichor::Detail { ~DependencyLifecycleManager() final { INTERNAL_DEBUG("destroying {}, id {}", typeName(), _service.getServiceId()); for(auto const &dep : _dependencies._dependencies) { - GetThreadLocalEventQueue().template pushPrioritisedEvent(_service.getServiceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY, Dependency{dep.interfaceNameHash, dep.required, dep.satisfied}, getProperties()); + GetThreadLocalEventQueue().template pushPrioritisedEvent(_service.getServiceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY, dep, getProperties()); } } @@ -28,7 +28,7 @@ namespace Ichor::Detail { static std::unique_ptr> create(Properties&& properties, InterfacesList_t) { std::vector interfaces{}; interfaces.reserve(sizeof...(Interfaces)); - (interfaces.emplace_back(typeNameHash(), false, false),...); + (interfaces.emplace_back(typeNameHash(), typeName(), false, false),...); return std::make_unique>(std::move(interfaces), std::move(properties)); } diff --git a/include/ichor/dependency_management/DependencyManagerLifecycleManager.h b/include/ichor/dependency_management/DependencyManagerLifecycleManager.h index 2dde882..90b8887 100644 --- a/include/ichor/dependency_management/DependencyManagerLifecycleManager.h +++ b/include/ichor/dependency_management/DependencyManagerLifecycleManager.h @@ -8,7 +8,7 @@ namespace Ichor::Detail { class DependencyManagerLifecycleManager final : public ILifecycleManager { public: explicit DependencyManagerLifecycleManager(DependencyManager *dm) : _dm(dm) { - _interfaces.emplace_back(typeNameHash(), false, false); + _interfaces.emplace_back(typeNameHash(), typeName(), false, false); } ~DependencyManagerLifecycleManager() final = default; @@ -134,6 +134,6 @@ namespace Ichor::Detail { ServiceState _state{ServiceState::ACTIVE}; unordered_set _serviceIdsOfDependees; // services that depend on this service std::vector _interfaces; - InternalService _service; + InternalService _service; }; } diff --git a/include/ichor/dependency_management/DependencyRegister.h b/include/ichor/dependency_management/DependencyRegister.h index c8ba0d7..b1ec49c 100644 --- a/include/ichor/dependency_management/DependencyRegister.h +++ b/include/ichor/dependency_management/DependencyRegister.h @@ -23,7 +23,7 @@ namespace Ichor { } _registrations.emplace(typeNameHash(), std::make_tuple( - Dependency{typeNameHash(), required, 0}, + Dependency{typeNameHash(), typeName(), required, 0}, std::function, IService&)>{[svc](NeverNull dep, IService& isvc){ svc->addDependencyInstance(*reinterpret_cast(dep.get()), isvc); }}, std::function, IService&)>{[svc](NeverNull dep, IService& isvc){ svc->removeDependencyInstance(*reinterpret_cast(dep.get()), isvc); }}, std::move(props))); @@ -41,7 +41,7 @@ namespace Ichor { } _registrations.emplace(typeNameHash(), std::make_tuple( - Dependency{typeNameHash(), true, 0}, + Dependency{typeNameHash(), typeName(), true, 0}, std::function, IService&)>{[svc](NeverNull dep, IService& isvc){ svc->template addDependencyInstance(reinterpret_cast(dep.get()), &isvc); }}, std::function, IService&)>{[svc](NeverNull dep, IService& isvc){ svc->template removeDependencyInstance(reinterpret_cast(dep.get()), &isvc); }}, tl::optional{})); diff --git a/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h b/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h index 88b45f5..b1b3900 100644 --- a/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h +++ b/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h @@ -7,7 +7,7 @@ namespace Ichor::Detail { class IServiceInterestedLifecycleManager final : public ILifecycleManager { public: IServiceInterestedLifecycleManager(IService *self) : _self(self) { - _interfaces.emplace_back(typeNameHash(), false, false); + _interfaces.emplace_back(typeNameHash(), typeName(), false, false); } ~IServiceInterestedLifecycleManager() final = default; diff --git a/include/ichor/dependency_management/InternalService.h b/include/ichor/dependency_management/InternalService.h index d64ab40..22de960 100644 --- a/include/ichor/dependency_management/InternalService.h +++ b/include/ichor/dependency_management/InternalService.h @@ -4,6 +4,7 @@ namespace Ichor::Detail { + template class InternalService : public IService { public: InternalService() noexcept : IService(), _properties(), _serviceId(Detail::_serviceIdCounter.fetch_add(1, std::memory_order_relaxed)), _servicePriority(INTERNAL_EVENT_PRIORITY), _serviceGid(sole::uuid4()), _serviceState(ServiceState::INSTALLED) { @@ -34,7 +35,7 @@ namespace Ichor::Detail { /// Name of the user-specified service (e.g. CoutFrameworkLogger) /// \return [[nodiscard]] std::string_view getServiceName() const noexcept final { - return typeName(); + return typeName(); } [[nodiscard]] ServiceState getServiceState() const noexcept final { @@ -68,11 +69,11 @@ namespace Ichor::Detail { /// \return true if started [[nodiscard]] Task internal_start(DependencyInfo *_dependencies) { if(_serviceState != ServiceState::INSTALLED || (_dependencies != nullptr && !_dependencies->allSatisfied())) { - INTERNAL_DEBUG("internal_start service {}:{} state {} dependencies {} {}", getServiceId(), typeName(), getState(), _dependencies->size(), _dependencies->allSatisfied()); + INTERNAL_DEBUG("internal_start service {}:{} state {} dependencies {} {}", getServiceId(), typeName(), getState(), _dependencies->size(), _dependencies->allSatisfied()); co_return {}; } - INTERNAL_DEBUG("internal_start service {}:{} state {} -> {}", getServiceId(), typeName(), getState(), ServiceState::INJECTING); + INTERNAL_DEBUG("internal_start service {}:{} state {} -> {}", getServiceId(), typeName(), getState(), ServiceState::INJECTING); _serviceState = ServiceState::INJECTING; co_return {}; @@ -86,7 +87,7 @@ namespace Ichor::Detail { } #endif - INTERNAL_DEBUG("internal_stop service {}:{} state {} -> {}", getServiceId(), typeName(), getState(), ServiceState::INSTALLED); + INTERNAL_DEBUG("internal_stop service {}:{} state {} -> {}", getServiceId(), typeName(), getState(), ServiceState::INSTALLED); _serviceState = ServiceState::INSTALLED; co_return {}; @@ -97,7 +98,7 @@ namespace Ichor::Detail { return false; } - INTERNAL_DEBUG("internalSetInjected service {}:{} state {} -> {}", getServiceId(), typeName(), getState(), ServiceState::ACTIVE); + INTERNAL_DEBUG("internalSetInjected service {}:{} state {} -> {}", getServiceId(), typeName(), getState(), ServiceState::ACTIVE); _serviceState = ServiceState::ACTIVE; return true; } @@ -107,7 +108,7 @@ namespace Ichor::Detail { return false; } - INTERNAL_DEBUG("internalSetUninjected service {}:{} state {} -> {}", getServiceId(), typeName(), getState(), ServiceState::UNINJECTING); + INTERNAL_DEBUG("internalSetUninjected service {}:{} state {} -> {}", getServiceId(), typeName(), getState(), ServiceState::UNINJECTING); _serviceState = ServiceState::UNINJECTING; return true; } diff --git a/include/ichor/dependency_management/LifecycleManager.h b/include/ichor/dependency_management/LifecycleManager.h index b357279..260fd1b 100644 --- a/include/ichor/dependency_management/LifecycleManager.h +++ b/include/ichor/dependency_management/LifecycleManager.h @@ -29,7 +29,7 @@ namespace Ichor::Detail { static std::unique_ptr> create(Properties&& properties, InterfacesList_t) { std::vector interfaces{}; interfaces.reserve(sizeof...(Interfaces)); - (interfaces.emplace_back(typeNameHash(), false, false),...); + (interfaces.emplace_back(typeNameHash(), typeName(), false, false),...); return std::make_unique>(std::move(interfaces), std::forward(properties)); } diff --git a/include/ichor/dependency_management/QueueLifecycleManager.h b/include/ichor/dependency_management/QueueLifecycleManager.h index 374e5a1..3ab4748 100644 --- a/include/ichor/dependency_management/QueueLifecycleManager.h +++ b/include/ichor/dependency_management/QueueLifecycleManager.h @@ -9,7 +9,7 @@ namespace Ichor::Detail { class QueueLifecycleManager final : public ILifecycleManager { public: explicit QueueLifecycleManager(IEventQueue *q) : _q(q) { - _interfaces.emplace_back(typeNameHash(), false, false); + _interfaces.emplace_back(typeNameHash(), typeName(), false, false); } ~QueueLifecycleManager() final = default; @@ -135,6 +135,6 @@ namespace Ichor::Detail { ServiceState _state{ServiceState::ACTIVE}; unordered_set _serviceIdsOfDependees; // services that depend on this service std::vector _interfaces; - InternalService _service; + InternalService _service; }; } diff --git a/include/sole/sole.h b/include/sole/sole.h index 8199622..d84320f 100644 --- a/include/sole/sole.h +++ b/include/sole/sole.h @@ -57,6 +57,7 @@ #if (!defined(WIN32) && !defined(_WIN32) && !defined(__WIN32)) || defined(__CYGWIN__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wconversion" #endif // public API diff --git a/quickbuild.sh b/quickbuild.sh index b1de8f2..8be2d4b 100755 --- a/quickbuild.sh +++ b/quickbuild.sh @@ -84,6 +84,7 @@ if [[ $RUN_EXAMPLES -eq 1 ]]; then ../bin/ichor_tcp_example || exit 1 ../bin/ichor_timer_example || exit 1 ../bin/ichor_tracker_example || exit 1 + ../bin/ichor_introspection_example || exit 1 ../bin/ichor_websocket_example || exit 1 ../bin/ichor_websocket_example -t4 || exit 1 ../bin/ichor_yielding_timer_example || exit 1 diff --git a/src/ichor/DependencyManager.cpp b/src/ichor/DependencyManager.cpp index 19b5178..cff5cde 100644 --- a/src/ichor/DependencyManager.cpp +++ b/src/ichor/DependencyManager.cpp @@ -901,7 +901,7 @@ void Ichor::DependencyManager::stop() { Ichor::Detail::_local_dm = nullptr; } -bool Ichor::DependencyManager::existingCoroutineFor(uint64_t serviceId) const noexcept { +bool Ichor::DependencyManager::existingCoroutineFor(ServiceIdType serviceId) const noexcept { auto existingCoroutineEvent = std::find_if(_scopedEvents.begin(), _scopedEvents.end(), [serviceId](const std::pair> &t) { if(t.second->type == StartServiceEvent::TYPE) { return t.second->originatingService == serviceId || static_cast(t.second.get())->serviceId == serviceId; @@ -922,7 +922,7 @@ bool Ichor::DependencyManager::existingCoroutineFor(uint64_t serviceId) const no return existingCoroutineEvent != _scopedEvents.end(); } -Ichor::Task Ichor::DependencyManager::waitForService(uint64_t serviceId, uint64_t eventType) noexcept { +Ichor::Task Ichor::DependencyManager::waitForService(ServiceIdType serviceId, uint64_t eventType) noexcept { if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { if (this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? std::terminate(); @@ -942,7 +942,7 @@ Ichor::Task Ichor::DependencyManager::waitForService(uint64_t serviceId, u co_return; } -bool Ichor::DependencyManager::finishWaitingService(uint64_t serviceId, uint64_t eventType, [[maybe_unused]] std::string_view eventName) noexcept { +bool Ichor::DependencyManager::finishWaitingService(ServiceIdType serviceId, uint64_t eventType, [[maybe_unused]] std::string_view eventName) noexcept { bool ret{}; auto waiter = _dependencyWaiters.find(serviceId); std::vector> evts{}; @@ -1095,21 +1095,116 @@ void Ichor::DependencyManager::runForOrQueueEmpty(std::chrono::milliseconds ms) } } -Ichor::unordered_map Ichor::DependencyManager::getServiceInfo() const noexcept { +/// Get IService by local ID +/// \param id service id +/// \return optional +[[nodiscard]] tl::optional> Ichor::DependencyManager::getIService(uint64_t id) const noexcept { if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { if (this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? std::terminate(); } } - unordered_map svcs{}; - for(auto &[svcId, svc] : _services) { + auto svc = _services.find(id); + + if(svc == _services.end()) { + return {}; + } + + return svc->second->getIService(); +} + +tl::optional> Ichor::DependencyManager::getIService(sole::uuid id) const noexcept { + if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { + if (this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? + std::terminate(); + } + } + + auto svc = std::find_if(_services.begin(), _services.end(), [&id](const std::pair> &svcPair) { + return svcPair.second->getIService()->getServiceGid() == id; + }); + + if(svc == _services.end()) { + return {}; + } + + return svc->second->getIService(); +} + +std::vector Ichor::DependencyManager::getDependencyRequestsForService(ServiceIdType svcId) const noexcept { + if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { + if (this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? + std::terminate(); + } + } + + auto svc = _services.find(svcId); + + if(svc == _services.end()) { + return {}; + } + + auto reg = svc->second->getDependencyRegistry(); + + if(reg == nullptr) { + return {}; + } + + std::vector ret; + + for(auto &r : reg->_registrations) { + ret.emplace_back(std::get(r.second)); + } + + return ret; +} + +std::vector> Ichor::DependencyManager::getDependentsForService(ServiceIdType svcId) const noexcept { + if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { + if (this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? + std::terminate(); + } + } + + auto svc = _services.find(svcId); + + if(svc == _services.end()) { + return {}; + } + + auto deps = svc->second->getDependees(); + + std::vector> ret; + + for(auto &dep : deps) { + auto isvc = getIService(dep); + + if(!isvc) { + continue; + } + + ret.emplace_back(*isvc); + } + + return ret; +} + +Ichor::unordered_map> Ichor::DependencyManager::getAllServices() const noexcept { + if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { + if (this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? + std::terminate(); + } + } + + unordered_map> svcs{}; + for(auto const &[svcId, svc] : _services) { svcs.emplace(svcId, svc->getIService()); } return svcs; } -tl::optional Ichor::DependencyManager::getImplementationNameFor(uint64_t serviceId) const noexcept { +tl::optional Ichor::DependencyManager::getImplementationNameFor(ServiceIdType serviceId) const noexcept { if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { if (this != Detail::_local_dm) [[unlikely]] { // are we on the right thread? std::terminate(); diff --git a/test/CoroutineTests.cpp b/test/CoroutineTests.cpp index 10f9947..31e1264 100644 --- a/test/CoroutineTests.cpp +++ b/test/CoroutineTests.cpp @@ -312,23 +312,23 @@ TEST_CASE("CoroutineTests") { REQUIRE(services.empty()); - auto svcs = dm.getServiceInfo(); + auto svcs = dm.getAllServices(); REQUIRE(svcs.size() == 4); - REQUIRE(svcs[svcId]->getServiceState() == Ichor::ServiceState::STARTING); + REQUIRE(svcs.find(svcId)->second->getServiceState() == Ichor::ServiceState::STARTING); _evt->set(); - REQUIRE(svcs[svcId]->getServiceState() == Ichor::ServiceState::INJECTING); + REQUIRE(svcs.find(svcId)->second->getServiceState() == Ichor::ServiceState::INJECTING); }); dm.runForOrQueueEmpty(); queue->pushEvent(0, [&dm = dm, svcId]() { - auto svcs = dm.getServiceInfo(); + auto svcs = dm.getAllServices(); - REQUIRE(svcs[svcId]->getServiceState() == Ichor::ServiceState::INSTALLED); + REQUIRE(svcs.find(svcId)->second->getServiceState() == Ichor::ServiceState::INSTALLED); auto services = dm.getStartedServices(); @@ -362,15 +362,15 @@ TEST_CASE("CoroutineTests") { REQUIRE(services.empty()); - auto svcs = dm.getServiceInfo(); + auto svcs = dm.getAllServices(); REQUIRE(svcs.size() == 4); - REQUIRE(svcs[svcId]->getServiceState() == Ichor::ServiceState::STOPPING); + REQUIRE(svcs.find(svcId)->second->getServiceState() == Ichor::ServiceState::STOPPING); _evt->set(); - REQUIRE(svcs[svcId]->getServiceState() == Ichor::ServiceState::INSTALLED); + REQUIRE(svcs.find(svcId)->second->getServiceState() == Ichor::ServiceState::INSTALLED); }); dm.runForOrQueueEmpty(); @@ -380,10 +380,10 @@ TEST_CASE("CoroutineTests") { REQUIRE(services.empty()); - auto svcs = dm.getServiceInfo(); + auto svcs = dm.getAllServices(); REQUIRE(svcs.size() == 4); - REQUIRE(svcs[svcId]->getServiceState() == Ichor::ServiceState::INSTALLED); + REQUIRE(svcs.find(svcId)->second->getServiceState() == Ichor::ServiceState::INSTALLED); dm.getEventQueue().pushEvent(0); }); diff --git a/test/TestServices/StopsInAsyncStartService.h b/test/TestServices/StopsInAsyncStartService.h index 6ac42a0..c82d00a 100644 --- a/test/TestServices/StopsInAsyncStartService.h +++ b/test/TestServices/StopsInAsyncStartService.h @@ -11,7 +11,7 @@ namespace Ichor { ~StopsInAsyncStartService() final = default; Task> start() final { - co_await GetThreadLocalManager().pushPrioritisedEventAsync(getServiceId(), 50, false, getServiceId()).begin(); + co_await GetThreadLocalManager().pushPrioritisedEventAsync(getServiceId(), 50, false, getServiceId()); GetThreadLocalEventQueue().pushEvent(getServiceId()); co_return {}; From ed613e61e44b0e1dd8dc1612ba707ae6b95f4fc7 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Tue, 26 Dec 2023 13:43:01 +0100 Subject: [PATCH 11/98] Add non-atomic reference counted smart pointer to STL --- CMakeLists.txt | 5 +- cloc.sh | 4 +- include/ichor/DependencyManager.h | 5 +- include/ichor/Enums.h | 5 +- include/ichor/Filter.h | 3 +- include/ichor/coroutines/AsyncGenerator.h | 37 ++- .../coroutines/AsyncGeneratorPromiseBase.h | 18 +- include/ichor/services/logging/SpdlogLogger.h | 9 +- .../services/network/http/HttpHostService.h | 1 + include/ichor/services/network/ws/WsEvents.h | 2 +- include/ichor/stl/Any.h | 6 +- include/ichor/stl/ReferenceCountedPointer.h | 285 ++++++++++++++++++ src/ichor/DependencyManager.cpp | 17 +- src/services/logging/SpdlogLogger.cpp | 2 +- src/services/logging/SpdlogSharedService.cpp | 2 +- test/ServicesTests.cpp | 5 + test/StlTests.cpp | 178 ++++++++++- 17 files changed, 538 insertions(+), 46 deletions(-) create mode 100644 include/ichor/stl/ReferenceCountedPointer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f2cbb1..db2075b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -239,7 +239,7 @@ endif() if(ICHOR_USE_THREAD_SANITIZER) target_compile_options(ichor PUBLIC -fsanitize=thread -fno-omit-frame-pointer) - target_link_options(ichor PUBLIC -fsanitize=thread -static-libtsan) + target_link_options(ichor PUBLIC -fsanitize=thread) # clang on OSX doesn't accept -no-pie for some reason if(NOT APPLE) @@ -248,6 +248,9 @@ if(ICHOR_USE_THREAD_SANITIZER) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_options(ichor PUBLIC -no-pie) + target_link_options(ichor PUBLIC -static-libtsan) + else() + target_link_options(ichor PUBLIC -static-libsan) endif() if(ICHOR_USE_BOOST_BEAST) diff --git a/cloc.sh b/cloc.sh index 1eaddd7..33fe1ca 100755 --- a/cloc.sh +++ b/cloc.sh @@ -1,4 +1,4 @@ #!/bin/bash -cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64 --exclude-content=LYRA_LYRA_HPP --by-file +cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64,ctre --exclude-content=LYRA_LYRA_HPP --by-file echo "" -cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64 --exclude-content=LYRA_LYRA_HPP +cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64,ctre --exclude-content=LYRA_LYRA_HPP diff --git a/include/ichor/DependencyManager.h b/include/ichor/DependencyManager.h index 6669417..5127f21 100644 --- a/include/ichor/DependencyManager.h +++ b/include/ichor/DependencyManager.h @@ -18,6 +18,7 @@ #include #include #include +#include using namespace std::chrono_literals; @@ -657,7 +658,7 @@ namespace Ichor { } void handleEventCompletion(Event const &evt); - [[nodiscard]] uint64_t broadcastEvent(std::shared_ptr &evt); + [[nodiscard]] uint64_t broadcastEvent(ReferenceCountedPointer &evt); /// Sets the communication channel. Only to be used from inside the CommunicationChannel class itself. /// \param channel void setCommunicationChannel(NeverNull channel); @@ -693,7 +694,7 @@ namespace Ichor { unordered_map> _eventCallbacks{}; // key = event id unordered_map> _eventInterceptors{}; // key = event id unordered_map> _scopedGenerators{}; // key = promise id - unordered_map> _scopedEvents{}; // key = promise id + unordered_map> _scopedEvents{}; // key = promise id unordered_map _eventWaiters{}; // key = event id unordered_map _dependencyWaiters{}; // key = event id IEventQueue *_eventQueue; diff --git a/include/ichor/Enums.h b/include/ichor/Enums.h index af4e2e9..34116ab 100644 --- a/include/ichor/Enums.h +++ b/include/ichor/Enums.h @@ -53,6 +53,7 @@ namespace Ichor // [C] - Consumer performs this transition // [P] - Producer performs this transition enum class state { + unknown, value_not_ready_consumer_active, value_not_ready_consumer_suspended, value_ready_producer_active, @@ -124,6 +125,8 @@ struct fmt::formatter { template auto format(const Ichor::state& state, FormatContext& ctx) { switch(state) { + case Ichor::state::unknown: + return fmt::format_to(ctx.out(), "unknown"); case Ichor::state::value_not_ready_consumer_active: return fmt::format_to(ctx.out(), "value_not_ready_consumer_active"); case Ichor::state::value_not_ready_consumer_suspended: @@ -135,7 +138,7 @@ struct fmt::formatter { case Ichor::state::cancelled: return fmt::format_to(ctx.out(), "cancelled"); default: - return fmt::format_to(ctx.out(), "error, please file a bug in Ichor"); + return fmt::format_to(ctx.out(), "error, please file a bug in Ichor, val: {}", (int)state); } } }; diff --git a/include/ichor/Filter.h b/include/ichor/Filter.h index 06eee0c..ea7fd46 100644 --- a/include/ichor/Filter.h +++ b/include/ichor/Filter.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace Ichor { @@ -78,7 +79,7 @@ namespace Ichor { return _templatedFilter->compareTo(manager); } - std::shared_ptr _templatedFilter; + ReferenceCountedPointer _templatedFilter; }; } diff --git a/include/ichor/coroutines/AsyncGenerator.h b/include/ichor/coroutines/AsyncGenerator.h index 5104f6d..095b5a5 100644 --- a/include/ichor/coroutines/AsyncGenerator.h +++ b/include/ichor/coroutines/AsyncGenerator.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace Ichor { template @@ -101,7 +102,7 @@ namespace Ichor { std::coroutine_handle<> _producerCoroutine; public: - state _initialState; + state _initialState{}; }; template @@ -184,13 +185,13 @@ namespace Ichor { public: AsyncGeneratorBeginOperation(std::nullptr_t) noexcept - : AsyncGeneratorAdvanceOperation(nullptr) - { + : AsyncGeneratorAdvanceOperation(nullptr) { + INTERNAL_COROUTINE_DEBUG("AsyncGeneratorBeginOperation<{}>(nullptr) {}", typeName(), _initialState); } AsyncGeneratorBeginOperation(handle_type producerCoroutine) noexcept - : AsyncGeneratorAdvanceOperation(producerCoroutine.promise(), producerCoroutine) - { + : AsyncGeneratorAdvanceOperation(producerCoroutine.promise(), producerCoroutine) { + INTERNAL_COROUTINE_DEBUG("AsyncGeneratorBeginOperation<{}>(producerCoroutine) {} {}", typeName(), _initialState, _promise->_state); } ~AsyncGeneratorBeginOperation() final = default; @@ -201,6 +202,7 @@ namespace Ichor { } [[nodiscard]] bool get_finished() const noexcept final { + INTERNAL_COROUTINE_DEBUG("AsyncGeneratorBeginOperation<{}>::get_finished {} {}", typeName(), _promise == nullptr, _promise == nullptr ? false : _promise->finished()); return _promise == nullptr || _promise->finished(); } @@ -217,6 +219,9 @@ namespace Ichor { } [[nodiscard]] state get_promise_state() const noexcept final { + if(_promise == nullptr) { + return state::unknown; + } return _promise->_state; } @@ -346,20 +351,18 @@ namespace Ichor { using iterator = Detail::AsyncGeneratorIterator; AsyncGenerator() noexcept - : IGenerator(), _coroutine(nullptr), _destroyed() - { + : IGenerator(), _coroutine(nullptr), _destroyed() { INTERNAL_COROUTINE_DEBUG("AsyncGenerator<{}>()", typeName()); } explicit AsyncGenerator(promise_type& promise) noexcept - : IGenerator(), _coroutine(std::coroutine_handle::from_promise(promise)), _destroyed(promise.get_destroyed()) - { - INTERNAL_COROUTINE_DEBUG("AsyncGenerator<{}>(promise_type& promise) {}", typeName(), _coroutine.promise().get_id()); + : IGenerator(), _coroutine(std::coroutine_handle::from_promise(promise)), _destroyed(promise.get_destroyed()) { + INTERNAL_COROUTINE_DEBUG("AsyncGenerator<{}>(promise_type& promise) {} {}", typeName(), _coroutine.promise().get_id(), *_destroyed); } AsyncGenerator(AsyncGenerator&& other) noexcept : IGenerator(), _coroutine(std::move(other._coroutine)), _destroyed(other._destroyed) { - INTERNAL_COROUTINE_DEBUG("AsyncGenerator<{}>(AsyncGenerator&& other) {}", typeName(), _coroutine.promise().get_id()); + INTERNAL_COROUTINE_DEBUG("AsyncGenerator<{}>(AsyncGenerator&& other) {} {}", typeName(), _coroutine.promise().get_id(), *_destroyed); other._coroutine = nullptr; if(_coroutine != nullptr) { // Assume we're moving because an iterator has not finished and has suspended @@ -368,7 +371,7 @@ namespace Ichor { } ~AsyncGenerator() final { -// INTERNAL_COROUTINE_DEBUG("~AsyncGenerator<{}>() {} {} {}", typeName(), *_destroyed, _coroutine == nullptr, !(*_destroyed) && _coroutine != nullptr ? _coroutine.promise().get_id() : 0); + INTERNAL_COROUTINE_DEBUG("~AsyncGenerator<{}>() {} {} {}", typeName(), *_destroyed, _coroutine == nullptr, !(*_destroyed) && _coroutine != nullptr ? _coroutine.promise().get_id() : 0); if (!(*_destroyed) && _coroutine) { if (_coroutine.promise().request_cancellation()) { _coroutine.destroy(); @@ -378,7 +381,7 @@ namespace Ichor { } AsyncGenerator& operator=(AsyncGenerator&& other) noexcept { - INTERNAL_COROUTINE_DEBUG("operator=(AsyncGenerator<{}>&& other) {}", typeName(), other._coroutine.promise().get_id()); + INTERNAL_COROUTINE_DEBUG("operator=(AsyncGenerator<{}>&& other) {} {} {}", typeName(), other._coroutine.promise().get_id(), *_destroyed, *other._destroyed); AsyncGenerator temp(std::move(other)); swap(temp); if(_coroutine != nullptr) { @@ -392,6 +395,7 @@ namespace Ichor { AsyncGenerator& operator=(const AsyncGenerator&) = delete; [[nodiscard]] auto begin() noexcept { + INTERNAL_COROUTINE_DEBUG("AsyncGenerator<{}>::begin() {} {}", typeName(), *_destroyed, _coroutine == nullptr); if ((*_destroyed) || !_coroutine) { return Detail::AsyncGeneratorBeginOperation{ nullptr }; } @@ -400,6 +404,7 @@ namespace Ichor { } [[nodiscard]] std::unique_ptr begin_interface() noexcept final { + INTERNAL_COROUTINE_DEBUG("AsyncGenerator<{}>::begin_interface() {} {}", typeName(), *_destroyed, _coroutine == nullptr); if ((*_destroyed) || !_coroutine) { return std::make_unique>(nullptr); } @@ -412,10 +417,12 @@ namespace Ichor { } [[nodiscard]] bool done() const noexcept final { + INTERNAL_COROUTINE_DEBUG("AsyncGenerator<{}>::done() {} {}", typeName(), *_destroyed, _coroutine == nullptr ? state::unknown : _coroutine.promise()._state); return (*_destroyed) || _coroutine == nullptr || _coroutine.done(); } void swap(AsyncGenerator& other) noexcept { + INTERNAL_COROUTINE_DEBUG("AsyncGenerator<{}>::swap() {} {} {}", typeName(), *_destroyed, _coroutine.promise()._state, *other._destroyed); using std::swap; swap(_coroutine, other._coroutine); swap(_destroyed, other._destroyed); @@ -423,16 +430,18 @@ namespace Ichor { template requires (!std::is_same_v) [[nodiscard]] U& get_value() noexcept { + INTERNAL_COROUTINE_DEBUG("AsyncGenerator<{}>::get_value() {} {} {}", typeName(), typeName(), *_destroyed, _coroutine.promise()._state); return _coroutine.promise().value(); } void set_priority(uint64_t priority) noexcept { + INTERNAL_COROUTINE_DEBUG("AsyncGenerator<{}>::set_priority({}) {} {}", typeName(), priority, *_destroyed, _coroutine.promise()._state); _coroutine.promise().set_priority(priority); } private: std::coroutine_handle _coroutine; - std::shared_ptr _destroyed; + ReferenceCountedPointer _destroyed; }; template diff --git a/include/ichor/coroutines/AsyncGeneratorPromiseBase.h b/include/ichor/coroutines/AsyncGeneratorPromiseBase.h index 7392bda..2f2e6b4 100644 --- a/include/ichor/coroutines/AsyncGeneratorPromiseBase.h +++ b/include/ichor/coroutines/AsyncGeneratorPromiseBase.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace Ichor { template @@ -55,7 +56,7 @@ namespace Ichor::Detail { AsyncGeneratorPromiseBase& operator=(const AsyncGeneratorPromiseBase& other) = delete; std::suspend_always initial_suspend() const noexcept { - INTERNAL_COROUTINE_DEBUG("AsyncGeneratorPromiseBase::initial_suspend {}", _id); + INTERNAL_COROUTINE_DEBUG("AsyncGeneratorPromiseBase::initial_suspend {} {}", _id, _state); return {}; } @@ -179,9 +180,10 @@ namespace Ichor::Detail { public: AsyncGeneratorPromise() noexcept : _destroyed(new bool(false)) { - + INTERNAL_COROUTINE_DEBUG("AsyncGeneratorPromise<{}>() {}", typeName(), *_destroyed); } ~AsyncGeneratorPromise() final { + INTERNAL_COROUTINE_DEBUG("~AsyncGeneratorPromise<{}>()", typeName()); *_destroyed = true; }; @@ -208,7 +210,8 @@ namespace Ichor::Detail { return _finished; } - std::shared_ptr& get_destroyed() noexcept { + ReferenceCountedPointer& get_destroyed() noexcept { + INTERNAL_COROUTINE_DEBUG("AsyncGeneratorPromise<{}>::get_destroyed {}, {}", typeName(), _id, *_destroyed); return _destroyed; } @@ -230,7 +233,7 @@ namespace Ichor::Detail { tl::optional _currentValue{}; bool _finished{}; - std::shared_ptr _destroyed; + ReferenceCountedPointer _destroyed; }; template<> @@ -238,9 +241,10 @@ namespace Ichor::Detail { { public: AsyncGeneratorPromise() noexcept : _destroyed(new bool(false)) { - + INTERNAL_COROUTINE_DEBUG("AsyncGeneratorPromise<>()"); } ~AsyncGeneratorPromise() final { + INTERNAL_COROUTINE_DEBUG("~AsyncGeneratorPromise<>()"); *_destroyed = true; }; @@ -254,7 +258,7 @@ namespace Ichor::Detail { return _finished; } - std::shared_ptr& get_destroyed() noexcept { + ReferenceCountedPointer& get_destroyed() noexcept { return _destroyed; } @@ -265,7 +269,7 @@ namespace Ichor::Detail { } bool _finished{}; - std::shared_ptr _destroyed; + ReferenceCountedPointer _destroyed; }; } diff --git a/include/ichor/services/logging/SpdlogLogger.h b/include/ichor/services/logging/SpdlogLogger.h index 9828255..82be776 100644 --- a/include/ichor/services/logging/SpdlogLogger.h +++ b/include/ichor/services/logging/SpdlogLogger.h @@ -2,15 +2,12 @@ #ifdef ICHOR_USE_SPDLOG -#include #include #include #include #include - -namespace spdlog { - class logger; -} +#include +#include namespace Ichor { class SpdlogLogger final : public ILogger, public AdvancedService { @@ -35,7 +32,7 @@ namespace Ichor { friend DependencyRegister; - std::shared_ptr _logger{}; + ReferenceCountedPointer _logger{}; LogLevel _level{LogLevel::LOG_TRACE}; ISpdlogSharedService* _sharedService{}; }; diff --git a/include/ichor/services/network/http/HttpHostService.h b/include/ichor/services/network/http/HttpHostService.h index 32d7523..152c4bb 100644 --- a/include/ichor/services/network/http/HttpHostService.h +++ b/include/ichor/services/network/http/HttpHostService.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace beast = boost::beast; // from namespace http = beast::http; // from diff --git a/include/ichor/services/network/ws/WsEvents.h b/include/ichor/services/network/ws/WsEvents.h index 8e05e30..e55f9e3 100644 --- a/include/ichor/services/network/ws/WsEvents.h +++ b/include/ichor/services/network/ws/WsEvents.h @@ -23,4 +23,4 @@ namespace Ichor { }; } -#endif \ No newline at end of file +#endif diff --git a/include/ichor/stl/Any.h b/include/ichor/stl/Any.h index 084f6e5..8fc4ae5 100644 --- a/include/ichor/stl/Any.h +++ b/include/ichor/stl/Any.h @@ -44,7 +44,7 @@ namespace Ichor { } any& operator=(const any& o) noexcept { - if(this == &o) { + if(this == &o) [[unlikely]] { return *this; } @@ -63,6 +63,10 @@ namespace Ichor { } any& operator=(any&& o) noexcept { + if(this == &o) [[unlikely]] { + return *this; + } + reset(); _size = o._size; diff --git a/include/ichor/stl/ReferenceCountedPointer.h b/include/ichor/stl/ReferenceCountedPointer.h new file mode 100644 index 0000000..7ab2b01 --- /dev/null +++ b/include/ichor/stl/ReferenceCountedPointer.h @@ -0,0 +1,285 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Ichor { + + template + class ReferenceCountedPointer; + + template + concept Constructible = std::is_constructible_v; + + +#ifdef ICHOR_ENABLE_INTERNAL_DEBUGGING + extern std::atomic _rfpCounter; +#define RFP_ID _id, +#else +#define RFP_ID +#endif + + + + namespace Detail { + struct [[nodiscard]] ReferenceCountedPointerBase { + ReferenceCountedPointerBase(const ReferenceCountedPointerBase &) = delete; + ReferenceCountedPointerBase(ReferenceCountedPointerBase &&) = default; + ReferenceCountedPointerBase &operator=(const ReferenceCountedPointerBase &) = delete; + ReferenceCountedPointerBase &operator=(ReferenceCountedPointerBase &&) = default; + virtual ~ReferenceCountedPointerBase() noexcept = default; + NeverNull ptr; + uint64_t useCount{}; + + protected: + ReferenceCountedPointerBase(void* _ptr, uint64_t _useCount) noexcept : ptr(_ptr), useCount(_useCount) { + } + }; + + template + struct [[nodiscard]] ReferenceCountedPointerDeleter final : public ReferenceCountedPointerBase { + ReferenceCountedPointerDeleter(const ReferenceCountedPointerDeleter &) = delete; + ReferenceCountedPointerDeleter(ReferenceCountedPointerDeleter &&) = default; + ReferenceCountedPointerDeleter &operator=(const ReferenceCountedPointerDeleter &) = delete; + ReferenceCountedPointerDeleter &operator=(ReferenceCountedPointerDeleter &&) = default; + + template + explicit ReferenceCountedPointerDeleter(U *p, DeleterType fn) noexcept : ReferenceCountedPointerBase(p, 1), deleteFn(fn) { + + } + + ~ReferenceCountedPointerDeleter() noexcept final { + deleteFn(ptr); + } + + DeleterType deleteFn; + }; + } + + /// Non-atomic reference counted pointer + template + class [[nodiscard]] ReferenceCountedPointer final { + public: + constexpr ReferenceCountedPointer() noexcept = default; + ReferenceCountedPointer(const ReferenceCountedPointer &o) noexcept : _ptr(o._ptr) { + _ptr->useCount++; + } + template requires Constructible + ReferenceCountedPointer(const ReferenceCountedPointer &o) noexcept : _ptr(o._ptr) { + INTERNAL_DEBUG("ReferenceCountedPointer<{}>(const ReferenceCountedPointer<{}> &o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + _ptr->useCount++; + } + ReferenceCountedPointer(ReferenceCountedPointer &&o) noexcept : _ptr(o._ptr) { + o._ptr = nullptr; + } + template requires Constructible + ReferenceCountedPointer(ReferenceCountedPointer &&o) noexcept : _ptr(o._ptr) { + INTERNAL_DEBUG("ReferenceCountedPointer<{}>(ReferenceCountedPointer<{}> &&o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + o._ptr = nullptr; + } + + explicit ReferenceCountedPointer(T* p) : _ptr(new Detail::ReferenceCountedPointerDeleter(p, [](void *ptr) { delete static_cast(ptr); })) { + INTERNAL_DEBUG("ReferenceCountedPointer<{}>(T* p) {} {}", typeName(), RFP_ID _ptr == nullptr); + } + + template requires Constructible + explicit ReferenceCountedPointer(U* p) : _ptr(new Detail::ReferenceCountedPointerDeleter(p, [](void *ptr) { delete static_cast(ptr); })) { + INTERNAL_DEBUG("ReferenceCountedPointer<{}>({}* p) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + } + template requires Constructible + explicit ReferenceCountedPointer(U&&... args) : _ptr(new Detail::ReferenceCountedPointerDeleter(new T(std::forward(args)...), [](void *ptr) { delete static_cast(ptr); })) { + if constexpr (std::is_same_v) { + static_assert(std::is_same_v); + INTERNAL_DEBUG("ReferenceCountedPointer<{}>(U&&... args) {} {} bool! {}", typeName(), RFP_ID _ptr == nullptr, *static_cast(_ptr->ptr.get())); + } else { + INTERNAL_DEBUG("ReferenceCountedPointer<{}>(U&&... args) {} {} {}", typeName(), RFP_ID _ptr == nullptr, sizeof(T)); + } + } + template + ReferenceCountedPointer(std::unique_ptr unique) : _ptr(new Detail::ReferenceCountedPointerDeleter(unique.get(), [deleter = unique.get_deleter()](void *ptr) { deleter(static_cast(ptr)); })) { + INTERNAL_DEBUG("ReferenceCountedPointer<{}>(unique) {} {}", typeName(), RFP_ID _ptr == nullptr); + unique.release(); + } + + ~ReferenceCountedPointer() noexcept { + INTERNAL_DEBUG("~ReferenceCountedPointer<{}>() {} {}", typeName(), RFP_ID _ptr == nullptr); + decrement(); + } + + ReferenceCountedPointer& operator=(const ReferenceCountedPointer &o) noexcept { + if(this == &o) [[unlikely]] { + return *this; + } + + decrement(); + _ptr = o._ptr; + _ptr->useCount++; + + return *this; + } + + template requires Constructible + ReferenceCountedPointer& operator=(const ReferenceCountedPointer &o) noexcept { + INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator=(const ReferenceCountedPointer<{}> &o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + decrement(); + _ptr = o._ptr; + _ptr->useCount++; + + return *this; + } + + ReferenceCountedPointer& operator=(ReferenceCountedPointer &&o) noexcept { + if(this == &o) [[unlikely]] { + return *this; + } + + decrement(); + _ptr = o._ptr; + o._ptr = nullptr; + + return *this; + } + + template requires Constructible + ReferenceCountedPointer& operator=(ReferenceCountedPointer &&o) noexcept { + INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator=(ReferenceCountedPointer<{}> &&o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + decrement(); + _ptr = o._ptr; + o._ptr = nullptr; + + return *this; + } + + template requires Constructible + ReferenceCountedPointer& operator=(U *p) noexcept { + INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator=({}* p) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + decrement(); + _ptr = new Detail::ReferenceCountedPointerDeleter(p, [](void *ptr) { delete static_cast(ptr); }); + + return *this; + } + + template + ReferenceCountedPointer& operator=(std::unique_ptr unique) noexcept { + INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator=(unique) {} {}", typeName(), RFP_ID _ptr == nullptr); + decrement(); + _ptr = new Detail::ReferenceCountedPointerDeleter(unique.get(), [deleter = unique.get_deleter()](void *ptr) { deleter(static_cast(ptr)); }); + unique.release(); + + return *this; + } + + ReferenceCountedPointer& operator=(decltype(nullptr)) noexcept { + INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator=(nullptr) {} {}", typeName(), RFP_ID _ptr == nullptr); + decrement(); + _ptr = nullptr; + + return *this; + } + + [[nodiscard]] T& operator*() const noexcept { + if constexpr (std::is_same_v) { + INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator*() {} {} bool! {}", typeName(), RFP_ID _ptr == nullptr, *static_cast(_ptr->ptr.get())); + } else { + INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator*() {} {}", typeName(), RFP_ID _ptr == nullptr); + } + if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { + if (_ptr == nullptr) [[unlikely]] { + std::terminate(); + } + } + return *static_cast(_ptr->ptr.get()); + } + + [[nodiscard]] T* operator->() const noexcept { + INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator->() {} {}", typeName(), RFP_ID _ptr == nullptr); + if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { + if (_ptr == nullptr) [[unlikely]] { + std::terminate(); + } + } + return static_cast(_ptr->ptr.get()); + } + + // TODO enabling this function causes function overloading to fail for ReferenceCountedPointer(const &ReferenceCountedPointer o). + // causing it to construct a new bool from a ReferenceCountedPointer instead of the underlying bool. + // Maybe try https://www.artima.com/articles/the-safe-bool-idiom ? +// [[nodiscard]] explicit operator bool() const noexcept { +// INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator bool() {} {}", typeName(), RFP_ID _ptr == nullptr); +// return _ptr != nullptr; +// } + + [[nodiscard]] bool operator==(decltype(nullptr)) const noexcept { + INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator==(nullptr) {} {}", typeName(), RFP_ID _ptr == nullptr); + return _ptr == nullptr; + } + + template + [[nodiscard]] bool operator==(const ReferenceCountedPointer &o) const noexcept { + INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator==(const ReferenceCountedPointer<{}> &) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + return _ptr == o._ptr; + } + + [[nodiscard]] uint64_t use_count() const noexcept { + if(_ptr == nullptr) { + return 0; + } + + return _ptr->useCount; + } + + [[nodiscard]] bool has_value() const noexcept { + return _ptr != nullptr; + } + + [[nodiscard]] T* get() const noexcept { + INTERNAL_DEBUG("ReferenceCountedPointer<{}> get() {} {}", typeName(), RFP_ID _ptr == nullptr); + if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { + if (_ptr == nullptr) [[unlikely]] { + std::terminate(); + } + } + return static_cast(_ptr->ptr.get()); + } + + void swap(ReferenceCountedPointer &o) noexcept { + auto *ptr = _ptr; + _ptr = o._ptr; + o._ptr = ptr; + } + + private: + void decrement() const noexcept { + if(_ptr != nullptr) { + _ptr->useCount--; + if(_ptr->useCount == 0) { + delete _ptr; + } + } + } + + Detail::ReferenceCountedPointerBase *_ptr{}; +#ifdef ICHOR_ENABLE_INTERNAL_DEBUGGING + uint64_t _id{_rfpCounter.fetch_add(1, std::memory_order_relaxed)}; +#endif + + template + friend class ReferenceCountedPointer; + }; + + template + ReferenceCountedPointer make_reference_counted(Args&&... args) { + return ReferenceCountedPointer(std::forward(args)...); + } +} + +namespace std { + template + inline void swap(Ichor::ReferenceCountedPointer& a, Ichor::ReferenceCountedPointer& b) noexcept { + a.swap(b); + } +} diff --git a/src/ichor/DependencyManager.cpp b/src/ichor/DependencyManager.cpp index cff5cde..148bc4c 100644 --- a/src/ichor/DependencyManager.cpp +++ b/src/ichor/DependencyManager.cpp @@ -17,6 +17,9 @@ #endif std::atomic Ichor::DependencyManager::_managerIdCounter = 0; +#ifdef ICHOR_ENABLE_INTERNAL_DEBUGGING +std::atomic Ichor::_rfpCounter = 0; +#endif Ichor::unordered_set Ichor::Detail::emptyDependencies{}; thread_local Ichor::DependencyManager *Ichor::Detail::_local_dm = nullptr; @@ -63,7 +66,7 @@ void Ichor::DependencyManager::start() { } void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) { - std::shared_ptr evt{std::move(uniqueEvt)}; + ReferenceCountedPointer evt{std::move(uniqueEvt)}; ICHOR_LOG_TRACE(_logger, "evt id {} type {} has {} prio", evt->id, evt->name, evt->priority); bool allowProcessing = true; @@ -147,7 +150,7 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) } _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent - _scopedEvents.emplace(it.get_promise_id(), std::make_shared(_eventQueue->getNextEventId(), serviceId, INTERNAL_DEPENDENCY_EVENT_PRIORITY)); + _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), serviceId, INTERNAL_DEPENDENCY_EVENT_PRIORITY)); } else if(it.get_value() == StartBehaviour::STARTED) { _eventQueue->pushPrioritisedEvent(serviceId, INTERNAL_DEPENDENCY_EVENT_PRIORITY); } @@ -205,7 +208,7 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) allDependeesFinished = false; _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent - _scopedEvents.emplace(it.get_promise_id(), std::make_shared(_eventQueue->getNextEventId(), serviceId, INTERNAL_DEPENDENCY_EVENT_PRIORITY, depOfflineEvt->originatingService)); + _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), serviceId, INTERNAL_DEPENDENCY_EVENT_PRIORITY, depOfflineEvt->originatingService)); continue; } @@ -326,7 +329,7 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) if(!it.get_finished()) { _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent - _scopedEvents.emplace(it.get_promise_id(), std::make_shared(_eventQueue->getNextEventId(), cmpMgr->serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY)); + _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), cmpMgr->serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY)); } else if(it.get_value() == StartBehaviour::STARTED) { _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY); } @@ -357,7 +360,7 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) if(!it.get_finished()) { _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent - _scopedEvents.emplace(it.get_promise_id(), std::make_shared(_eventQueue->getNextEventId(), cmpMgr->serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY)); + _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), cmpMgr->serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY)); } else if(it.get_value() == StartBehaviour::STARTED) { _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY); } @@ -902,7 +905,7 @@ void Ichor::DependencyManager::stop() { } bool Ichor::DependencyManager::existingCoroutineFor(ServiceIdType serviceId) const noexcept { - auto existingCoroutineEvent = std::find_if(_scopedEvents.begin(), _scopedEvents.end(), [serviceId](const std::pair> &t) { + auto existingCoroutineEvent = std::find_if(_scopedEvents.begin(), _scopedEvents.end(), [serviceId](const std::pair> &t) { if(t.second->type == StartServiceEvent::TYPE) { return t.second->originatingService == serviceId || static_cast(t.second.get())->serviceId == serviceId; } @@ -1019,7 +1022,7 @@ void Ichor::DependencyManager::handleEventCompletion(Ichor::Event const &evt) { callback->second(evt); } -uint64_t Ichor::DependencyManager::broadcastEvent(std::shared_ptr &evt) { +uint64_t Ichor::DependencyManager::broadcastEvent(Ichor::ReferenceCountedPointer &evt) { auto registeredListeners = _eventCallbacks.find(evt->type); if(registeredListeners == end(_eventCallbacks)) { diff --git a/src/services/logging/SpdlogLogger.cpp b/src/services/logging/SpdlogLogger.cpp index d9aa0c4..fb04790 100644 --- a/src/services/logging/SpdlogLogger.cpp +++ b/src/services/logging/SpdlogLogger.cpp @@ -58,7 +58,7 @@ Ichor::LogLevel Ichor::SpdlogLogger::getLogLevel() const { Ichor::Task> Ichor::SpdlogLogger::start() { auto const &sinks = _sharedService->getSinks(); - _logger = std::make_shared("multi_sink", sinks.begin(), sinks.end()); + _logger = make_reference_counted("multi_sink", sinks.begin(), sinks.end()); #ifndef ICHOR_REMOVE_SOURCE_NAMES_FROM_LOGGING _logger->set_pattern("[%C-%m-%d %H:%M:%S.%e] [%s:%#] [%L] %v"); diff --git a/src/services/logging/SpdlogSharedService.cpp b/src/services/logging/SpdlogSharedService.cpp index 1459d13..c391ed4 100644 --- a/src/services/logging/SpdlogSharedService.cpp +++ b/src/services/logging/SpdlogSharedService.cpp @@ -9,7 +9,7 @@ Ichor::Task> Ichor::SpdlogSharedService::s auto console_sink = std::make_shared(); auto time_since_epoch = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); - auto file_sink = make_shared(fmt::format("logs/log-{}-{}.txt", GetThreadLocalManager().getId(), time_since_epoch.count()), true); + auto file_sink = std::make_shared(fmt::format("logs/log-{}-{}.txt", GetThreadLocalManager().getId(), time_since_epoch.count()), true); _sinks = std::vector>{}; _sinks.emplace_back(std::move(console_sink)); diff --git a/test/ServicesTests.cpp b/test/ServicesTests.cpp index 009873f..73861a5 100644 --- a/test/ServicesTests.cpp +++ b/test/ServicesTests.cpp @@ -323,6 +323,11 @@ TEST_CASE("ServicesTests") { dm.runForOrQueueEmpty(); + // the qemu setup used in build.sh is not fast enough to have the test pass. +#ifdef ICHOR_AARCH64 + std::this_thread::sleep_for(std::chrono::milliseconds(500)); +#endif + queue->pushEvent(0, [&]() { REQUIRE(dm.getServiceCount() == 4); diff --git a/test/StlTests.cpp b/test/StlTests.cpp index 3dd0c2e..104c00e 100644 --- a/test/StlTests.cpp +++ b/test/StlTests.cpp @@ -2,11 +2,13 @@ #include #include #include +#include +#include #include "TestServices/UselessService.h" using namespace Ichor; -struct nonmoveable { +struct nonmoveable final { nonmoveable() = default; nonmoveable(const nonmoveable&) = default; nonmoveable(nonmoveable&&) = delete; @@ -14,6 +16,14 @@ struct nonmoveable { nonmoveable& operator=(nonmoveable&&) = delete; }; +struct noncopyable final { + noncopyable() = default; + noncopyable(const noncopyable&) = delete; + noncopyable(noncopyable&&) = default; + noncopyable& operator=(const noncopyable&) = delete; + noncopyable& operator=(noncopyable&&) = default; +}; + template <> struct fmt::formatter { constexpr auto parse(format_parse_context& ctx) { @@ -26,6 +36,21 @@ struct fmt::formatter { } }; +struct parent_test_class { + +}; + +struct rc_test_class final : parent_test_class { + rc_test_class(int _i, float _f, noncopyable) : i(_i), f(_f) {} + rc_test_class(const rc_test_class&) = default; + rc_test_class(rc_test_class&&) = delete; + rc_test_class& operator=(const rc_test_class&) = default; + rc_test_class& operator=(rc_test_class&&) = delete; + + int i; + float f; +}; + TEST_CASE("STL Tests") { SECTION("Any basics") { @@ -163,4 +188,155 @@ TEST_CASE("STL Tests") { REQUIRE(*p == 121); delete p; } + + SECTION("ReferenceCountedPointer tests") { + { + ReferenceCountedPointer i; + REQUIRE(i.use_count() == 0); + REQUIRE(!i.has_value()); + i = new int(5); + REQUIRE(i.use_count() == 1); + REQUIRE(*i == 5); + REQUIRE(*i.get() == 5); + REQUIRE(i.has_value()); + } + { + ReferenceCountedPointer i{new int(6)}; + REQUIRE(i.use_count() == 1); + REQUIRE(i.has_value()); + i = new int(5); + REQUIRE(i.use_count() == 1); + REQUIRE(i.has_value()); + REQUIRE(*i == 5); + REQUIRE(*i.get() == 5); + } + bool deleted{}; + { + auto deleter = [&deleted](int *ptr){ deleted = true; delete ptr; }; + std::unique_ptr unique(new int(5), deleter); + ReferenceCountedPointer tc{std::move(unique)}; + REQUIRE(tc.use_count() == 1); + REQUIRE(*tc == 5); + REQUIRE(!unique); + } + REQUIRE(deleted); + deleted = false; + { + auto deleter = [&deleted](int *ptr){ deleted = true; delete ptr; }; + std::unique_ptr unique(new int(5), deleter); + ReferenceCountedPointer tc; + tc = std::move(unique); + REQUIRE(tc.use_count() == 1); + REQUIRE(*tc == 5); + REQUIRE(!unique); + } + REQUIRE(deleted); + { + ReferenceCountedPointer tc = std::make_unique(5); + REQUIRE(tc.use_count() == 1); + REQUIRE(*tc == 5); + } + { + ReferenceCountedPointer tc = Ichor::make_reference_counted(5); + REQUIRE(tc.use_count() == 1); + REQUIRE(*tc == 5); + tc = nullptr; + REQUIRE(tc.use_count() == 0); + REQUIRE(!tc.has_value()); + } + { + ReferenceCountedPointer tc; + REQUIRE(tc.use_count() == 0); + tc = std::make_unique(5); + REQUIRE(tc.use_count() == 1); + REQUIRE(*tc == 5); + } + { + ReferenceCountedPointer tc = Ichor::make_reference_counted(5, 5.0f, noncopyable{}); + REQUIRE(tc.use_count() == 1); + REQUIRE(tc->i == 5); + REQUIRE(tc->f == 5.0f); + ReferenceCountedPointer tc2{tc}; + REQUIRE(tc.use_count() == 2); + REQUIRE(tc2.use_count() == 2); + ReferenceCountedPointer tc3; + REQUIRE(tc3.use_count() == 0); + tc3 = tc; + REQUIRE(tc.use_count() == 3); + REQUIRE(tc2.use_count() == 3); + REQUIRE(tc3.use_count() == 3); + } + { + ReferenceCountedPointer tc{new rc_test_class(5, 5.0f, noncopyable{})}; + REQUIRE(tc.use_count() == 1); + tc = Ichor::make_reference_counted(5, 5.0f, noncopyable{}); + REQUIRE(tc.use_count() == 1); + } + { + ReferenceCountedPointer tc = Ichor::make_reference_counted(5, 5.0f, noncopyable{}); + REQUIRE(tc.use_count() == 1); + } + { + ReferenceCountedPointer i{new int(7)}; + ReferenceCountedPointer i2{i}; + REQUIRE(i.use_count() == 2); + REQUIRE(i2.use_count() == 2); + REQUIRE(*i == 7); + REQUIRE(*i2 == 7); + i = new int(5); + REQUIRE(i.use_count() == 1); + REQUIRE(i2.use_count() == 1); + REQUIRE(*i == 5); + REQUIRE(*i2 == 7); + } + { + ReferenceCountedPointer i{new int(7)}; + ReferenceCountedPointer i2; + REQUIRE(i2.use_count() == 0); + i2 = i; + REQUIRE(i.use_count() == 2); + REQUIRE(i2.use_count() == 2); + REQUIRE(*i == 7); + REQUIRE(*i2 == 7); + i = new int(5); + REQUIRE(i.use_count() == 1); + REQUIRE(i2.use_count() == 1); + REQUIRE(*i == 5); + REQUIRE(*i2 == 7); + } + { + ReferenceCountedPointer i{new int(7)}; + ReferenceCountedPointer i2{std::move(i)}; + REQUIRE(!i.has_value()); + REQUIRE(i2.has_value()); + REQUIRE(i.use_count() == 0); + REQUIRE(i2.use_count() == 1); + REQUIRE(*i2 == 7); + i = new int(5); + REQUIRE(i.has_value()); + REQUIRE(i2.has_value()); + REQUIRE(i.use_count() == 1); + REQUIRE(i2.use_count() == 1); + REQUIRE(*i == 5); + REQUIRE(*i2 == 7); + } + { + ReferenceCountedPointer i{new int(7)}; + ReferenceCountedPointer i2{}; + REQUIRE(i2.use_count() == 0); + i2 = std::move(i); + REQUIRE(!i.has_value()); + REQUIRE(i2.has_value()); + REQUIRE(i.use_count() == 0); + REQUIRE(i2.use_count() == 1); + REQUIRE(*i2 == 7); + i = new int(5); + REQUIRE(i.has_value()); + REQUIRE(i2.has_value()); + REQUIRE(i.use_count() == 1); + REQUIRE(i2.use_count() == 1); + REQUIRE(*i == 5); + REQUIRE(*i2 == 7); + } + } } From 08f79a675ff946799bd2aef0a081159085fc80e8 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Wed, 27 Dec 2023 15:13:46 +0100 Subject: [PATCH 12/98] Add security related linker flags when using hardening --- CMakeLists.txt | 2 ++ build.sh | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index db2075b..4829291 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -285,6 +285,8 @@ elseif(ICHOR_USE_HARDENING) endif() target_compile_definitions(ichor PUBLIC ICHOR_USE_HARDENING) + + target_link_options(ichor PUBLIC -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now) endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") diff --git a/build.sh b/build.sh index 619aacf..dfb16a2 100755 --- a/build.sh +++ b/build.sh @@ -79,16 +79,19 @@ if [[ $DOCKER -eq 1 ]]; then docker build -f ../Dockerfile -t ichor . || exit 1 docker run -v $(pwd)/../:/opt/ichor/src -it ichor || exit 1 run_examples + docker run -v $(pwd)/../:/opt/ichor/src -it ichor "rm -rf /opt/ichor/src/bin/* /opt/ichor/src/build/*" || exit 1 rm -rf ./* ../bin/* docker build -f ../Dockerfile-musl -t ichor-musl . || exit 1 docker run -v $(pwd)/../:/opt/ichor/src -it ichor-musl || exit 1 run_examples + docker run -v $(pwd)/../:/opt/ichor/src -it ichor-musl "rm -rf /opt/ichor/src/bin/* /opt/ichor/src/build/*" || exit 1 rm -rf ./* ../bin/* docker build -f ../Dockerfile-asan -t ichor-asan . || exit 1 docker run -v $(pwd)/../:/opt/ichor/src -it ichor-asan || exit 1 run_examples + docker run -v $(pwd)/../:/opt/ichor/src -it ichor-asan "rm -rf /opt/ichor/src/bin/* /opt/ichor/src/build/*" || exit 1 # tsan is purposefully not run automatically, because it usually contains false positives. @@ -101,7 +104,7 @@ if [[ $DOCKER -eq 1 ]]; then FILES=/opt/ichor/src/bin/* for f in \$FILES; do if [[ "\$f" != *"Redis"* ]] && [[ "\$f" != *"benchmark"* ]] && [[ "\$f" != *"minimal"* ]] && [[ "\$f" != *"ping"* ]] && [[ "\$f" != *"etcd"* ]] && [[ "\$f" != *"Etcd"* ]] && [[ "\$f" != *"pong"* ]] && [[ "\$f" != *"Started"* ]] && [[ "\$f" != *".sh" ]] && [[ -x "\$f" ]] && [[ ! -d "\$f" ]] ; then - echo "Running \$f" + echo "Running \${f}" \$f || exit 1 fi done From fdc8a406887225480c690eca53fae6bb0855f0ae Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Thu, 28 Dec 2023 20:00:36 +0100 Subject: [PATCH 13/98] Allow requesting multiple dependencies with the same interface --- .../IntrospectionService.h | 2 +- .../dependency_management/DependencyInfo.h | 4 +- .../DependencyLifecycleManager.h | 18 ++++--- .../DependencyRegister.h | 14 +----- .../dependency_management/ILifecycleManager.h | 10 ++-- include/ichor/stl/Any.h | 12 +++++ .../dependency_management/DependencyInfo.cpp | 19 +++++-- src/services/network/ws/WsHostService.cpp | 4 +- test/DependencyManagerTests.cpp | 46 +++++++++++++++++ ...ultipleSeparateDependencyRequestsService.h | 50 ++++++++++++++++--- .../TestServices/RegistrationCheckerService.h | 33 ++---------- 11 files changed, 140 insertions(+), 72 deletions(-) diff --git a/examples/introspection_example/IntrospectionService.h b/examples/introspection_example/IntrospectionService.h index 39c7859..22dc0ea 100644 --- a/examples/introspection_example/IntrospectionService.h +++ b/examples/introspection_example/IntrospectionService.h @@ -20,7 +20,7 @@ class IntrospectionService final { ICHOR_LOG_INFO(_logger, "\tProperties:"); for(auto &[key, val] : svc->getProperties()) { - ICHOR_LOG_INFO(_logger, "\t\tProperty {} value {} size {} type {}", key, val.to_string(), val.get_size(), val.type_name()); + ICHOR_LOG_INFO(_logger, "\t\tProperty {} value {} size {} type {}", key, val, val.get_size(), val.type_name()); } auto deps = dm->getDependencyRequestsForService(svc->getServiceId()); diff --git a/include/ichor/dependency_management/DependencyInfo.h b/include/ichor/dependency_management/DependencyInfo.h index e3564c7..6c8f630 100644 --- a/include/ichor/dependency_management/DependencyInfo.h +++ b/include/ichor/dependency_management/DependencyInfo.h @@ -34,10 +34,10 @@ namespace Ichor { bool contains(const Dependency &dependency) const noexcept; [[nodiscard]] - std::vector::const_iterator find(const Dependency &dependency) const noexcept; + std::vector::const_iterator find(const Dependency &dependency, bool satisfied) const noexcept; [[nodiscard]] - std::vector::iterator find(const Dependency &dependency) noexcept; + std::vector::iterator find(const Dependency &dependency, bool satisfied) noexcept; [[nodiscard]] size_t size() const noexcept; diff --git a/include/ichor/dependency_management/DependencyLifecycleManager.h b/include/ichor/dependency_management/DependencyLifecycleManager.h index 01ddc57..f1cf568 100644 --- a/include/ichor/dependency_management/DependencyLifecycleManager.h +++ b/include/ichor/dependency_management/DependencyLifecycleManager.h @@ -40,9 +40,11 @@ namespace Ichor::Detail { std::vector().begin())> ret; for(auto const &interface : dependentService->getInterfaces()) { - auto dep = _dependencies.find(interface); + auto dep = _dependencies.find(interface, !online); + INTERNAL_DEBUG("interestedInDependency() svc {}:{} {} dependent {}:{}", serviceId(), implementationName(), getServiceState(), dependentService->serviceId(), dependentService->implementationName()); if (dep == _dependencies.end()) { + INTERNAL_DEBUG("interestedInDependency() not found"); continue; } @@ -203,7 +205,7 @@ namespace Ichor::Detail { /// \param keyOfInterfaceToInject /// \param serviceIdOfOther /// \param fn - void insertSelfInto(uint64_t keyOfInterfaceToInject, uint64_t serviceIdOfOther, std::function, IService&)> &fn) final { + void insertSelfInto(uint64_t keyOfInterfaceToInject, ServiceIdType serviceIdOfOther, std::function, IService&)> &fn) final { INTERNAL_DEBUG("insertSelfInto() svc {} telling svc {} to add us", serviceId(), serviceIdOfOther); if constexpr (sizeof...(IFaces) > 0) { insertSelfInto2(keyOfInterfaceToInject, fn); @@ -245,7 +247,7 @@ namespace Ichor::Detail { /// \param keyOfInterfaceToInject /// \param serviceIdOfOther /// \param fn - void removeSelfInto(uint64_t keyOfInterfaceToInject, uint64_t serviceIdOfOther, std::function, IService&)> &fn) final { + void removeSelfInto(uint64_t keyOfInterfaceToInject, ServiceIdType serviceIdOfOther, std::function, IService&)> &fn) final { INTERNAL_DEBUG("removeSelfInto() svc {} telling svc {} to remove us", serviceId(), serviceIdOfOther); if constexpr (sizeof...(IFaces) > 0) { insertSelfInto2(keyOfInterfaceToInject, fn); @@ -255,12 +257,12 @@ namespace Ichor::Detail { } [[nodiscard]] - unordered_set &getDependencies() noexcept final { + unordered_set &getDependencies() noexcept final { return _serviceIdsOfInjectedDependencies; } [[nodiscard]] - unordered_set &getDependees() noexcept final { + unordered_set &getDependees() noexcept final { return _serviceIdsOfDependees; } @@ -292,7 +294,7 @@ namespace Ichor::Detail { return typeNameHash(); } - [[nodiscard]] uint64_t serviceId() const noexcept final { + [[nodiscard]] ServiceIdType serviceId() const noexcept final { return _service.getServiceId(); } @@ -333,7 +335,7 @@ namespace Ichor::Detail { DependencyRegister _registry; DependencyInfo _dependencies; ServiceType _service; - unordered_set _serviceIdsOfInjectedDependencies; // Services that this service depends on. - unordered_set _serviceIdsOfDependees; // services that depend on this service + unordered_set _serviceIdsOfInjectedDependencies; // Services that this service depends on. + unordered_set _serviceIdsOfDependees; // services that depend on this service }; } diff --git a/include/ichor/dependency_management/DependencyRegister.h b/include/ichor/dependency_management/DependencyRegister.h index b1ec49c..daf7a10 100644 --- a/include/ichor/dependency_management/DependencyRegister.h +++ b/include/ichor/dependency_management/DependencyRegister.h @@ -4,7 +4,6 @@ #include #include #include -#include namespace Ichor { struct DependencyRegister final { @@ -16,11 +15,6 @@ namespace Ichor { #if (!defined(WIN32) && !defined(_WIN32) && !defined(__WIN32)) || defined(__CYGWIN__) static_assert(ImplementsDependencyInjection, "Impl needs to implement the ImplementsDependencyInjection concept"); #endif - if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { - if (_registrations.contains(typeNameHash())) [[unlikely]] { - throw std::runtime_error("Already registered interface"); - } - } _registrations.emplace(typeNameHash(), std::make_tuple( Dependency{typeNameHash(), typeName(), required, 0}, @@ -34,12 +28,6 @@ namespace Ichor { static_assert(!std::is_same_v, "Impl and interface need to be separate classes"); static_assert(!DerivedTemplated, "Interface needs to be a non-service class."); - if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { - if (_registrations.contains(typeNameHash())) [[unlikely]] { - throw std::runtime_error("Already registered interface"); - } - } - _registrations.emplace(typeNameHash(), std::make_tuple( Dependency{typeNameHash(), typeName(), true, 0}, std::function, IService&)>{[svc](NeverNull dep, IService& isvc){ svc->template addDependencyInstance(reinterpret_cast(dep.get()), &isvc); }}, @@ -47,6 +35,6 @@ namespace Ichor { tl::optional{})); } - unordered_map, IService&)>, std::function, IService&)>, tl::optional>> _registrations; + std::unordered_multimap, IService&)>, std::function, IService&)>, tl::optional>> _registrations; }; } diff --git a/include/ichor/dependency_management/ILifecycleManager.h b/include/ichor/dependency_management/ILifecycleManager.h index af21880..1a40fe1 100644 --- a/include/ichor/dependency_management/ILifecycleManager.h +++ b/include/ichor/dependency_management/ILifecycleManager.h @@ -16,8 +16,8 @@ namespace Ichor { // iterators come from interestedInDependency() and have to be moved as using coroutines might end up clearing it. virtual AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector().begin())> iterators) = 0; virtual AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector().begin())> iterators) = 0; - [[nodiscard]] virtual unordered_set &getDependencies() noexcept = 0; - [[nodiscard]] virtual unordered_set &getDependees() noexcept = 0; + [[nodiscard]] virtual unordered_set &getDependencies() noexcept = 0; + [[nodiscard]] virtual unordered_set &getDependees() noexcept = 0; [[nodiscard]] virtual AsyncGenerator start() = 0; [[nodiscard]] virtual AsyncGenerator stop() = 0; [[nodiscard]] virtual bool setInjected() = 0; @@ -32,11 +32,11 @@ namespace Ichor { [[nodiscard]] virtual const std::vector& getInterfaces() const noexcept = 0; [[nodiscard]] virtual Properties const & getProperties() const noexcept = 0; [[nodiscard]] virtual DependencyRegister const * getDependencyRegistry() const noexcept = 0; - virtual void insertSelfInto(uint64_t keyOfInterfaceToInject, uint64_t serviceIdOfOther, std::function, IService&)>&) = 0; - virtual void removeSelfInto(uint64_t keyOfInterfaceToInject, uint64_t serviceIdOfOther, std::function, IService&)>&) = 0; + virtual void insertSelfInto(uint64_t keyOfInterfaceToInject, ServiceIdType serviceIdOfOther, std::function, IService&)>&) = 0; + virtual void removeSelfInto(uint64_t keyOfInterfaceToInject, ServiceIdType serviceIdOfOther, std::function, IService&)>&) = 0; protected: - static Task waitForService(uint64_t serviceId, uint64_t eventType) noexcept; + static Task waitForService(ServiceIdType serviceId, uint64_t eventType) noexcept; }; } diff --git a/include/ichor/stl/Any.h b/include/ichor/stl/Any.h index 8fc4ae5..563a010 100644 --- a/include/ichor/stl/Any.h +++ b/include/ichor/stl/Any.h @@ -253,3 +253,15 @@ namespace Ichor { return a; } } + +template <> +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { + return ctx.end(); + } + + template + auto format(const Ichor::any& change, FormatContext& ctx) { + return fmt::format_to(ctx.out(), "{}", change.to_string()); + } +}; diff --git a/src/ichor/dependency_management/DependencyInfo.cpp b/src/ichor/dependency_management/DependencyInfo.cpp index 44ae0ae..bc5de2d 100644 --- a/src/ichor/dependency_management/DependencyInfo.cpp +++ b/src/ichor/dependency_management/DependencyInfo.cpp @@ -1,4 +1,5 @@ #include +#include #include namespace Ichor { @@ -24,7 +25,7 @@ namespace Ichor { } void DependencyInfo::addDependency(Dependency dependency) { - _dependencies.emplace_back(dependency); + _dependencies.emplace_back(std::move(dependency)); } void DependencyInfo::removeDependency(const Dependency &dependency) { @@ -37,13 +38,21 @@ namespace Ichor { } [[nodiscard]] - std::vector::const_iterator DependencyInfo::find(const Dependency &dependency) const noexcept { - return std::find_if(begin(), end(), [&dependency](auto const& dep) noexcept { return dep.interfaceNameHash == dependency.interfaceNameHash; }); + std::vector::const_iterator DependencyInfo::find(const Dependency &dependency, bool satisfied) const noexcept { + INTERNAL_DEBUG("const find() size {}", size()); + return std::find_if(begin(), end(), [&dependency, satisfied](Dependency const& dep) noexcept { + INTERNAL_DEBUG("const find() {}:{}, {}:{}", dep.interfaceNameHash, dependency.interfaceNameHash, dep.satisfied, satisfied); + return dep.interfaceNameHash == dependency.interfaceNameHash && dep.satisfied == satisfied; + }); } [[nodiscard]] - std::vector::iterator DependencyInfo::find(const Dependency &dependency) noexcept { - return std::find_if(begin(), end(), [&dependency](auto const& dep) noexcept { return dep.interfaceNameHash == dependency.interfaceNameHash; }); + std::vector::iterator DependencyInfo::find(const Dependency &dependency, bool satisfied) noexcept { + INTERNAL_DEBUG("find() size {}", size()); + return std::find_if(begin(), end(), [&dependency, satisfied](Dependency const& dep) noexcept { + INTERNAL_DEBUG("find() {}:{}, {}:{}", dep.interfaceNameHash, dependency.interfaceNameHash, dep.satisfied, satisfied); + return dep.interfaceNameHash == dependency.interfaceNameHash && dep.satisfied == satisfied; + }); } [[nodiscard]] diff --git a/src/services/network/ws/WsHostService.cpp b/src/services/network/ws/WsHostService.cpp index 6dee566..6be3c80 100644 --- a/src/services/network/ws/WsHostService.cpp +++ b/src/services/network/ws/WsHostService.cpp @@ -15,7 +15,7 @@ class ClientConnectionFilter final { // that are created for incoming connections. If the "Address" key is present in the dependency registration properties, we skip it. // Currently only used for WsHostService [[nodiscard]] bool matches(Ichor::ILifecycleManager const &manager) const noexcept { - auto *reg = manager.getDependencyRegistry(); + auto const *reg = manager.getDependencyRegistry(); if(reg == nullptr) { return true; @@ -130,7 +130,7 @@ Ichor::AsyncGenerator Ichor::WsHostService::handleEvent(I auto connection = GetThreadLocalManager().createServiceManager(Properties{ {"WsHostServiceId", Ichor::make_any(getServiceId())}, {"Socket", Ichor::make_unformattable_any(evt._socket)}, - {"Filter", Ichor::make_any(Filter{ClientConnectionFilter{}})} + {"Filter", Ichor::make_any(ClientConnectionFilter{})} }); _connections.push_back(connection); diff --git a/test/DependencyManagerTests.cpp b/test/DependencyManagerTests.cpp index 8d2bbce..a5402c0 100644 --- a/test/DependencyManagerTests.cpp +++ b/test/DependencyManagerTests.cpp @@ -3,8 +3,40 @@ #include #include "TestServices/UselessService.h" #include "TestServices/RegistrationCheckerService.h" +#include "TestServices/MultipleSeparateDependencyRequestsService.h" #include "Common.h" +class ScopeFilter final { +public: + explicit ScopeFilter(std::string _scope) : scope(std::move(_scope)) {} + + [[nodiscard]] bool matches(ILifecycleManager const &manager) const noexcept { + for(auto const &[interfaceHash, depTuple] : manager.getDependencyRegistry()->_registrations) { + if(interfaceHash != typeNameHash()) { + continue; + } + + auto &props = std::get>(depTuple); + + if(props) { + for (auto const &[key, prop] : *props) { + fmt::print("depprop {} {}\n", key, prop); + + if(key == "scope" && Ichor::any_cast(prop) == scope) { + fmt::print("ScopeFilter matches {}:{}\n", manager.serviceId(), manager.implementationName()); + return true; + } + } + } + } + + fmt::print("ScopeFilter does not match {}:{}\n", manager.serviceId(), manager.implementationName()); + return false; + } + + std::string scope; +}; + TEST_CASE("DependencyManager") { SECTION("DependencyManager", "QuitOnQuitEvent") { auto queue = std::make_unique(); @@ -44,6 +76,20 @@ TEST_CASE("DependencyManager") { REQUIRE_FALSE(dm.isRunning()); } + SECTION("DependencyManager", "Check Multiple Registrations Different Properties") { + auto queue = std::make_unique(); + auto &dm = queue->createManager(); + + dm.createServiceManager(); + dm.createServiceManager(); + dm.createServiceManager(Properties{{"Filter", Ichor::make_any(ScopeFilter{"scope_one"})}}); + dm.createServiceManager(Properties{{"Filter", Ichor::make_any(ScopeFilter{"scope_two"})}}); + dm.createServiceManager(Properties{{"Filter", Ichor::make_any(ScopeFilter{"scope_three"})}}); + queue->start(CaptureSigInt); + + REQUIRE_FALSE(dm.isRunning()); + } + SECTION("DependencyManager", "Get services functions") { auto queue = std::make_unique(); auto &dm = queue->createManager(); diff --git a/test/TestServices/MultipleSeparateDependencyRequestsService.h b/test/TestServices/MultipleSeparateDependencyRequestsService.h index ec702f4..ef3ba92 100644 --- a/test/TestServices/MultipleSeparateDependencyRequestsService.h +++ b/test/TestServices/MultipleSeparateDependencyRequestsService.h @@ -1,8 +1,46 @@ -// -// Created by oipo on 23-12-23. -// +#pragma once -#ifndef ICHOR_MULTIPLESEPARATEDEPENDENCYREQUESTSSERVICE_H -#define ICHOR_MULTIPLESEPARATEDEPENDENCYREQUESTSSERVICE_H +#include "UselessService.h" +#include -#endif //ICHOR_MULTIPLESEPARATEDEPENDENCYREQUESTSSERVICE_H +using namespace Ichor; + +struct INotUsed { + +}; + +struct MultipleSeparateDependencyRequestsService final : public AdvancedService { + MultipleSeparateDependencyRequestsService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { + reg.registerDependency(this, false, Properties{{"scope", Ichor::make_any("scope_one")}}); + reg.registerDependency(this, true, Properties{{"scope", Ichor::make_any("scope_one")}}); + reg.registerDependency(this, true, Properties{{"scope", Ichor::make_any("scope_two")}}); + } + ~MultipleSeparateDependencyRequestsService() final = default; + + Task> start() final { + fmt::print("multiple start\n"); + if(_depCount == 2) { + GetThreadLocalEventQueue().pushEvent(getServiceId()); + } + co_return {}; + } + + void addDependencyInstance(IUselessService&, IService&) { + _depCount++; + + fmt::print("multiple requests: {}\n", _depCount); + } + + void removeDependencyInstance(IUselessService&, IService&) { + } + + void addDependencyInstance(INotUsed&, IService&) { + throw std::runtime_error("INotUsed should never be injected"); + } + + void removeDependencyInstance(INotUsed&, IService&) { + throw std::runtime_error("INotUsed should never be injected"); + } + + uint64_t _depCount{}; +}; diff --git a/test/TestServices/RegistrationCheckerService.h b/test/TestServices/RegistrationCheckerService.h index 7f7bbe4..4c7afb2 100644 --- a/test/TestServices/RegistrationCheckerService.h +++ b/test/TestServices/RegistrationCheckerService.h @@ -7,40 +7,12 @@ using namespace Ichor; struct RegistrationCheckerService final : public AdvancedService { RegistrationCheckerService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - bool threwException{}; - reg.registerDependency(this, false); - - try { - reg.registerDependency(this, false); - } catch (const std::runtime_error &e) { - threwException = true; - } - - if(!threwException) { - throw std::runtime_error("Should have thrown exception"); - } - - threwException = false; - - try { - reg.registerDependency(this, false); - } catch (const std::runtime_error &e) { - threwException = true; - } - - if(!threwException) { - throw std::runtime_error("Should have thrown exception"); - } - - _executedTests = true; + reg.registerDependency(this, false); } ~RegistrationCheckerService() final = default; Task> start() final { - if(!_executedTests) { - throw std::runtime_error("Should have executed tests"); - } if(_depCount == 2) { GetThreadLocalEventQueue().pushEvent(getServiceId()); } @@ -50,6 +22,8 @@ struct RegistrationCheckerService final : public AdvancedService(getServiceId()); } @@ -58,6 +32,5 @@ struct RegistrationCheckerService final : public AdvancedService Date: Thu, 28 Dec 2023 20:03:20 +0100 Subject: [PATCH 14/98] Rename tracker example to factory example --- examples/CMakeLists.txt | 8 ++++---- .../FactoryService.h} | 10 +++++----- .../{tracker_example => factory_example}/README.md | 4 ++-- .../RuntimeCreatedService.h | 0 .../{tracker_example => factory_example}/TestService.h | 0 examples/{tracker_example => factory_example}/main.cpp | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) rename examples/{tracker_example/TrackerService.h => factory_example/FactoryService.h} (95%) rename examples/{tracker_example => factory_example}/README.md (85%) rename examples/{tracker_example => factory_example}/RuntimeCreatedService.h (100%) rename examples/{tracker_example => factory_example}/TestService.h (100%) rename examples/{tracker_example => factory_example}/main.cpp (95%) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e47802b..9fbced7 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -13,10 +13,10 @@ add_executable(ichor_introspection_example ${EXAMPLE_SOURCES}) target_link_libraries(ichor_introspection_example ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(ichor_introspection_example ichor) -file(GLOB_RECURSE EXAMPLE_SOURCES ${ICHOR_TOP_DIR}/examples/tracker_example/*.cpp) -add_executable(ichor_tracker_example ${EXAMPLE_SOURCES}) -target_link_libraries(ichor_tracker_example ${CMAKE_THREAD_LIBS_INIT}) -target_link_libraries(ichor_tracker_example ichor) +file(GLOB_RECURSE EXAMPLE_SOURCES ${ICHOR_TOP_DIR}/examples/factory_example/*.cpp) +add_executable(ichor_factory_example ${EXAMPLE_SOURCES}) +target_link_libraries(ichor_factory_example ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(ichor_factory_example ichor) if(NOT WIN32 AND NOT APPLE) file(GLOB_RECURSE EXAMPLE_SOURCES ${ICHOR_TOP_DIR}/examples/tcp_example/*.cpp) diff --git a/examples/tracker_example/TrackerService.h b/examples/factory_example/FactoryService.h similarity index 95% rename from examples/tracker_example/TrackerService.h rename to examples/factory_example/FactoryService.h index 32b58d8..41624c7 100644 --- a/examples/tracker_example/TrackerService.h +++ b/examples/factory_example/FactoryService.h @@ -23,14 +23,14 @@ class ScopeFilterEntry final { std::string scope; }; -class TrackerService final { +class FactoryService final { public: - TrackerService(DependencyManager *dm, IService *self, ILogger *logger) : _self(self), _logger(logger) { - ICHOR_LOG_INFO(_logger, "TrackerService started"); + FactoryService(DependencyManager *dm, IService *self, ILogger *logger) : _self(self), _logger(logger) { + ICHOR_LOG_INFO(_logger, "FactoryService started"); _trackerRegistration = dm->registerDependencyTracker(this, self); } - ~TrackerService() { - ICHOR_LOG_INFO(_logger, "TrackerService stopped"); + ~FactoryService() { + ICHOR_LOG_INFO(_logger, "FactoryService stopped"); } private: diff --git a/examples/tracker_example/README.md b/examples/factory_example/README.md similarity index 85% rename from examples/tracker_example/README.md rename to examples/factory_example/README.md index c122525..13820b8 100644 --- a/examples/tracker_example/README.md +++ b/examples/factory_example/README.md @@ -1,4 +1,4 @@ -# Tracker Example +# Factory Example This example shows how to 'track' dependency requests. Specifically, this is most commonly used to create factories. @@ -9,4 +9,4 @@ When a service is created normally, that dependency is considered a globally ava This demonstrates the following concepts: * Fulfilling a dependency per-request, instead of globally -* How to use the advanced `filter` feature to create a per-request dependency +* How to use the `filter` feature to create a per-request dependency diff --git a/examples/tracker_example/RuntimeCreatedService.h b/examples/factory_example/RuntimeCreatedService.h similarity index 100% rename from examples/tracker_example/RuntimeCreatedService.h rename to examples/factory_example/RuntimeCreatedService.h diff --git a/examples/tracker_example/TestService.h b/examples/factory_example/TestService.h similarity index 100% rename from examples/tracker_example/TestService.h rename to examples/factory_example/TestService.h diff --git a/examples/tracker_example/main.cpp b/examples/factory_example/main.cpp similarity index 95% rename from examples/tracker_example/main.cpp rename to examples/factory_example/main.cpp index 91ebfe4..4aa25ef 100644 --- a/examples/tracker_example/main.cpp +++ b/examples/factory_example/main.cpp @@ -1,5 +1,5 @@ #include "TestService.h" -#include "TrackerService.h" +#include "FactoryService.h" #include "RuntimeCreatedService.h" #include #include @@ -30,7 +30,7 @@ int main(int argc, char *argv[]) { dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); dm.createServiceManager(Properties{{"scope", Ichor::make_any("one"s)}}); dm.createServiceManager(Properties{{"scope", Ichor::make_any("two"s)}}); - dm.createServiceManager(); + dm.createServiceManager(); queue->start(CaptureSigInt); auto end = std::chrono::steady_clock::now(); fmt::print("{} ran for {:L} µs\n", argv[0], std::chrono::duration_cast(end-start).count()); From b015df7cac8807331a14ed5b7cd0fadae94af867 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Thu, 28 Dec 2023 22:27:58 +0100 Subject: [PATCH 15/98] Refactor requesting multiple of same dependency --- .github/workflows/cmake.yml | 2 +- .github/workflows/coverage.yml | 2 +- CMakeLists.txt | 4 + benchmarks/coroutine_benchmark/TestService.h | 4 +- benchmarks/event_benchmark/TestService.h | 4 +- benchmarks/serializer_benchmark/TestService.h | 4 +- benchmarks/start_benchmark/TestService.h | 2 +- .../start_stop_benchmark/StartStopService.h | 6 +- benchmarks/start_stop_benchmark/TestService.h | 2 +- build.sh | 2 +- docs/02-DependencyInjection.md | 30 ++++- docs/Outdated-Fundamentals.md | 4 +- examples/common/DebugService.h | 4 +- examples/factory_example/TestService.h | 4 +- examples/http_example/UsingHttpService.h | 8 +- examples/http_ping_pong/PingService.h | 8 +- .../IntrospectionService.h | 2 +- .../OptionalService.h | 2 +- .../optional_dependency_example/TestService.h | 4 +- examples/realtime_example/OptionalService.h | 2 +- examples/realtime_example/TestService.h | 4 +- examples/tcp_example/UsingTcpService.h | 6 +- examples/websocket_example/UsingWsService.h | 6 +- include/ichor/Common.h | 15 +++ include/ichor/Enums.h | 17 +-- include/ichor/Filter.h | 13 +- include/ichor/coroutines/Task.h | 2 +- .../ichor/dependency_management/Dependency.h | 47 +++++++- .../DependencyLifecycleManager.h | 33 +++--- .../DependencyManagerLifecycleManager.h | 8 +- .../DependencyRegister.h | 6 +- .../dependency_management/ILifecycleManager.h | 6 +- .../IServiceInterestedLifecycleManager.h | 8 +- .../dependency_management/LifecycleManager.h | 8 +- .../QueueLifecycleManager.h | 8 +- include/ichor/glaze.h | 107 +++++++++++++++++ include/ichor/services/etcd/IEtcd.h | 4 +- include/ichor/services/io/IAsyncFileIO.h | 2 +- .../ichor/services/logging/LoggerFactory.h | 2 +- .../ichor/services/network/ClientFactory.h | 2 +- .../services/network/IConnectionService.h | 2 +- .../ichor/services/network/http/HttpCommon.h | 5 +- include/ichor/services/redis/IRedis.h | 2 +- include/ichor/stl/ConditionVariable.h | 2 +- include/ichor/stl/ReferenceCountedPointer.h | 50 ++++---- quickbuild.sh | 2 +- src/ichor/DependencyManager.cpp | 2 +- .../dependency_management/DependencyInfo.cpp | 10 +- src/services/etcd/EtcdV2Service.cpp | 111 +----------------- src/services/logging/SpdlogLogger.cpp | 2 +- .../metrics/EventStatisticsService.cpp | 4 +- src/services/network/AsioContextService.cpp | 2 +- .../network/http/HttpConnectionService.cpp | 4 +- src/services/network/http/HttpHostService.cpp | 4 +- .../network/tcp/TcpConnectionService.cpp | 4 +- src/services/network/tcp/TcpHostService.cpp | 4 +- .../network/ws/WsConnectionService.cpp | 6 +- src/services/network/ws/WsHostService.cpp | 4 +- src/services/redis/HiRedisService.cpp | 6 +- src/services/timer/TimerFactoryFactory.cpp | 2 +- test/GettingStartedTests.cpp | 2 +- test/ServicesTests.cpp | 73 +++++++++++- test/TestServices/AsyncUsingTimerService.h | 4 +- .../DependencyOfflineWhileStartingService.h | 2 +- .../DependencyOnlineWhileStoppingService.h | 2 +- test/TestServices/DependencyService.h | 16 +-- test/TestServices/EtcdUsingService.h | 2 +- test/TestServices/FailOnStartService.h | 2 +- test/TestServices/HttpThreadService.h | 6 +- test/TestServices/ICountService.h | 10 ++ test/TestServices/MixingInterfacesService.h | 4 +- ...ultipleSeparateDependencyRequestsService.h | 6 +- .../QuitOnStartWithDependenciesService.h | 2 +- test/TestServices/RedisUsingService.h | 2 +- .../TestServices/RegistrationCheckerService.h | 4 +- test/TestServices/RequestsLoggingService.h | 2 +- test/TestServices/RequiredMultipleService.h | 40 +++++++ 77 files changed, 504 insertions(+), 307 deletions(-) create mode 100644 test/TestServices/ICountService.h create mode 100644 test/TestServices/RequiredMultipleService.h diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index d3b879d..89fc401 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -56,5 +56,5 @@ jobs: - name: Examples working-directory: ${{github.workspace}}/bin - run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_tracker_example && ../bin/ichor_websocket_example && ../bin/ichor_websocket_example -t4 && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example && ../bin/ichor_introspection_example + run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_factory_example && ../bin/ichor_websocket_example && ../bin/ichor_websocket_example -t4 && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example && ../bin/ichor_introspection_example diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d5bad63..4191699 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -35,7 +35,7 @@ jobs: - name: Examples working-directory: ${{github.workspace}}/bin - run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_tracker_example && ../bin/ichor_websocket_example && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example && ../bin/ichor_introspection_example + run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_factory_example && ../bin/ichor_websocket_example && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example && ../bin/ichor_introspection_example - name: coverage run: lcov --directory . --capture --output-file coverage.info && lcov --remove coverage.info '/usr/*' '/opt/*' '${{github.workspace}}/external/*' --output-file coverage.info && lcov --list coverage.info && codecov -f coverage.info diff --git a/CMakeLists.txt b/CMakeLists.txt index 4829291..47d2d8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ option(ICHOR_BUILD_TESTING "Build tests" ON) option(ICHOR_ENABLE_INTERNAL_DEBUGGING "Add verbose logging of Ichor internals" OFF) option(ICHOR_ENABLE_INTERNAL_COROUTINE_DEBUGGING "Add verbose logging of Ichor coroutine internals" OFF) option(ICHOR_ENABLE_INTERNAL_IO_DEBUGGING "Add verbose logging of Ichor I/O internals" OFF) +option(ICHOR_ENABLE_INTERNAL_STL_DEBUGGING "Add verbose logging of Ichor STL" OFF) option(ICHOR_BUILD_COVERAGE "Build ichor with coverage" OFF) option(ICHOR_USE_SPDLOG "Use spdlog as framework logging implementation" OFF) option(ICHOR_USE_BOOST_BEAST "Add boost asio and boost BEAST as dependencies" OFF) @@ -109,6 +110,9 @@ endif() if(ICHOR_ENABLE_INTERNAL_IO_DEBUGGING) target_compile_definitions(ichor PUBLIC ICHOR_ENABLE_INTERNAL_IO_DEBUGGING) endif() +if(ICHOR_ENABLE_INTERNAL_STL_DEBUGGING) + target_compile_definitions(ichor PUBLIC ICHOR_ENABLE_INTERNAL_STL_DEBUGGING) +endif() if(ICHOR_USE_SPDLOG) target_compile_definitions(ichor PUBLIC SPDLOG_COMPILED_LIB SPDLOG_NO_EXCEPTIONS SPDLOG_FMT_EXTERNAL SPDLOG_DISABLE_DEFAULT_LOGGER SPDLOG_NO_ATOMIC_LEVELS ICHOR_USE_SPDLOG SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) diff --git a/benchmarks/coroutine_benchmark/TestService.h b/benchmarks/coroutine_benchmark/TestService.h index 4ffd222..1356feb 100644 --- a/benchmarks/coroutine_benchmark/TestService.h +++ b/benchmarks/coroutine_benchmark/TestService.h @@ -27,8 +27,8 @@ struct UselessEvent final : public Event { class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~TestService() final = default; diff --git a/benchmarks/event_benchmark/TestService.h b/benchmarks/event_benchmark/TestService.h index b124317..a426cbc 100644 --- a/benchmarks/event_benchmark/TestService.h +++ b/benchmarks/event_benchmark/TestService.h @@ -25,8 +25,8 @@ struct UselessEvent final : public Event { class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~TestService() final = default; diff --git a/benchmarks/serializer_benchmark/TestService.h b/benchmarks/serializer_benchmark/TestService.h index c7a5364..c1037d5 100644 --- a/benchmarks/serializer_benchmark/TestService.h +++ b/benchmarks/serializer_benchmark/TestService.h @@ -20,8 +20,8 @@ extern uint64_t sizeof_test; class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency>(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency>(this, DependencyFlags::REQUIRED); } ~TestService() final = default; diff --git a/benchmarks/start_benchmark/TestService.h b/benchmarks/start_benchmark/TestService.h index a067065..499c79e 100644 --- a/benchmarks/start_benchmark/TestService.h +++ b/benchmarks/start_benchmark/TestService.h @@ -16,7 +16,7 @@ using namespace Ichor; class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~TestService() final = default; diff --git a/benchmarks/start_stop_benchmark/StartStopService.h b/benchmarks/start_stop_benchmark/StartStopService.h index e92ddfa..81528c4 100644 --- a/benchmarks/start_stop_benchmark/StartStopService.h +++ b/benchmarks/start_stop_benchmark/StartStopService.h @@ -17,9 +17,9 @@ using namespace Ichor; class StartStopService final : public AdvancedService { public: StartStopService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~StartStopService() final = default; diff --git a/benchmarks/start_stop_benchmark/TestService.h b/benchmarks/start_stop_benchmark/TestService.h index d248e1d..ce93a56 100644 --- a/benchmarks/start_stop_benchmark/TestService.h +++ b/benchmarks/start_stop_benchmark/TestService.h @@ -13,7 +13,7 @@ struct ITestService { class TestService final : public ITestService, public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~TestService() final = default; diff --git a/build.sh b/build.sh index dfb16a2..6b564f3 100755 --- a/build.sh +++ b/build.sh @@ -57,7 +57,7 @@ run_examples () ../bin/ichor_serializer_example || exit 1 ../bin/ichor_tcp_example || exit 1 ../bin/ichor_timer_example || exit 1 - ../bin/ichor_tracker_example || exit 1 + ../bin/ichor_factory_example || exit 1 ../bin/ichor_introspection_example || exit 1 ../bin/ichor_websocket_example || exit 1 ../bin/ichor_websocket_example -t4 || exit 1 diff --git a/docs/02-DependencyInjection.md b/docs/02-DependencyInjection.md index dc83c0c..8b14063 100644 --- a/docs/02-DependencyInjection.md +++ b/docs/02-DependencyInjection.md @@ -71,8 +71,8 @@ class MyService final : public AdvancedService { public: // Creating instances of a service includes properties, and these need to be stored in the parent class. Be careful to move them each time and don't use the props variable but instead call getProperties(), if you need them. MyService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); // Request ILogger as a required dependency (the start function will only be called if all required dependencies have at least 1 instance available) - reg.registerDependency(this, false); // Request IOptionalService as an optional dependency. This does not influence the start and stop functions. + reg.registerDependency(this, DependencyFlags::REQUIRED); // Request ILogger as a required dependency (the start function will only be called if all required dependencies have at least 1 instance available) + reg.registerDependency(this, DependencyFlags::NONE); // Request IOptionalService as an optional dependency. This does not influence the start and stop functions. } ~MyService() final = default; @@ -109,6 +109,32 @@ private: }; ``` +## Multiple Requests Of Same Type + +There are two ways to request dependencies of the same type. Note that this requires using the AdvancedService method of requestion dependencies. + +The easiest is to specify the `ALLOW_MULTIPLE` flag for the dependency: + +```c++ +MyService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { + reg.registerDependency(this, DependencyFlags::ALLOW_MULTIPLE); // optional dependency + reg.registerDependency(this, DependencyFlags(DependencyFlags::REQUIRED | DependencyFlags::ALLOW_MULTIPLE)); // required dependency +} +``` + +This can also be combined with the `REQUIRED` flag, although as soon as one service is injected of the request type, it is considered satisfied and may start the service. Similarly, the service is only stopped once all services of the requested type are removed. + +The second way is to duplicate the request for the number of times you want: + +```c++ +MyService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { + // MyService now needs exactly 3 services before it starts. + reg.registerDependency(this, DependencyFlags::REQUIRE); + reg.registerDependency(this, DependencyFlags::REQUIRE); + reg.registerDependency(this, DependencyFlags::REQUIRE); +} +``` + ## Extra Resources [How to Use C++ Dependency Injection to Write Maintainable Software - Francesco Zoffoli CppCon 2022](https://www.youtube.com/watch?v=l6Y9PqyK1Mc) diff --git a/docs/Outdated-Fundamentals.md b/docs/Outdated-Fundamentals.md index e066632..3eb5781 100644 --- a/docs/Outdated-Fundamentals.md +++ b/docs/Outdated-Fundamentals.md @@ -25,8 +25,8 @@ struct ISomeDependency{}; struct ISomeOptionalDependency{}; struct DependencyService final : public Service { DependencyService(DependencyRegister ®, Properties props, DependencyManager *) : Service(std::move(props), mng) { - reg.registerDependency(this, true /* required dependency */); - reg.registerDependency(this, false /* optional dependency */); + reg.registerDependency(this, DependencyFlags::REQUIRED /* required dependency */); + reg.registerDependency(this, DependencyFlags::NONE /* optional dependency */); } ~DependencyService() final = default; diff --git a/examples/common/DebugService.h b/examples/common/DebugService.h index e6eafc0..fd71ae1 100644 --- a/examples/common/DebugService.h +++ b/examples/common/DebugService.h @@ -11,8 +11,8 @@ using namespace Ichor; class DebugService final : public AdvancedService { public: DebugService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true, Properties{{"LogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED, Properties{{"LogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~DebugService() final = default; private: diff --git a/examples/factory_example/TestService.h b/examples/factory_example/TestService.h index ffa2bf7..59b7a17 100644 --- a/examples/factory_example/TestService.h +++ b/examples/factory_example/TestService.h @@ -11,8 +11,8 @@ using namespace Ichor; class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true, getProperties()); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED, getProperties()); } ~TestService() final = default; diff --git a/examples/http_example/UsingHttpService.h b/examples/http_example/UsingHttpService.h index 710f6eb..f240bcf 100644 --- a/examples/http_example/UsingHttpService.h +++ b/examples/http_example/UsingHttpService.h @@ -17,10 +17,10 @@ using namespace Ichor; class UsingHttpService final : public AdvancedService { public: UsingHttpService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency>(this, true); - reg.registerDependency(this, true, getProperties()); // using getProperties here prevents us refactoring this class to constructor injection - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency>(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED, getProperties()); // using getProperties here prevents us refactoring this class to constructor injection + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~UsingHttpService() final = default; diff --git a/examples/http_ping_pong/PingService.h b/examples/http_ping_pong/PingService.h index 48642c2..bb4c375 100644 --- a/examples/http_ping_pong/PingService.h +++ b/examples/http_ping_pong/PingService.h @@ -18,10 +18,10 @@ using namespace Ichor; class PingService final : public AdvancedService { public: PingService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true, Properties{{"LogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); // using customer properties here prevents us refactoring this class to constructor injection - reg.registerDependency>(this, true); - reg.registerDependency(this, true, getProperties()); // using getProperties here prevents us refactoring this class to constructor injection - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED, Properties{{"LogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); // using customer properties here prevents us refactoring this class to constructor injection + reg.registerDependency>(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED, getProperties()); // using getProperties here prevents us refactoring this class to constructor injection + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~PingService() final = default; diff --git a/examples/introspection_example/IntrospectionService.h b/examples/introspection_example/IntrospectionService.h index 22dc0ea..2bc77b4 100644 --- a/examples/introspection_example/IntrospectionService.h +++ b/examples/introspection_example/IntrospectionService.h @@ -26,7 +26,7 @@ class IntrospectionService final { auto deps = dm->getDependencyRequestsForService(svc->getServiceId()); ICHOR_LOG_INFO(_logger, "\tDependencies:"); for(auto &dep : deps) { - ICHOR_LOG_INFO(_logger, "\t\tDependency {} required {} satisfied {}", dep.interfaceName, dep.required, dep.satisfied); + ICHOR_LOG_INFO(_logger, "\t\tDependency {} required {} satisfied {}", dep.interfaceName, dep.flags, dep.satisfied); } auto dependants = dm->getDependentsForService(svc->getServiceId()); ICHOR_LOG_INFO(_logger, "\tUsed by:"); diff --git a/examples/optional_dependency_example/OptionalService.h b/examples/optional_dependency_example/OptionalService.h index 667c945..7bb1d91 100644 --- a/examples/optional_dependency_example/OptionalService.h +++ b/examples/optional_dependency_example/OptionalService.h @@ -13,7 +13,7 @@ struct IOptionalService { class OptionalService final : public IOptionalService, public AdvancedService { public: OptionalService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~OptionalService() final = default; diff --git a/examples/optional_dependency_example/TestService.h b/examples/optional_dependency_example/TestService.h index 4205bee..1ff5d01 100644 --- a/examples/optional_dependency_example/TestService.h +++ b/examples/optional_dependency_example/TestService.h @@ -10,8 +10,8 @@ using namespace Ichor; class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, false); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::ALLOW_MULTIPLE); } ~TestService() final = default; diff --git a/examples/realtime_example/OptionalService.h b/examples/realtime_example/OptionalService.h index 667c945..7bb1d91 100644 --- a/examples/realtime_example/OptionalService.h +++ b/examples/realtime_example/OptionalService.h @@ -13,7 +13,7 @@ struct IOptionalService { class OptionalService final : public IOptionalService, public AdvancedService { public: OptionalService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~OptionalService() final = default; diff --git a/examples/realtime_example/TestService.h b/examples/realtime_example/TestService.h index 870e7dc..79038a5 100644 --- a/examples/realtime_example/TestService.h +++ b/examples/realtime_example/TestService.h @@ -28,8 +28,8 @@ struct ExecuteTaskEvent final : public Event { class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, false); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::NONE); } ~TestService() final = default; diff --git a/examples/tcp_example/UsingTcpService.h b/examples/tcp_example/UsingTcpService.h index 6d2a1c8..5cf05f1 100644 --- a/examples/tcp_example/UsingTcpService.h +++ b/examples/tcp_example/UsingTcpService.h @@ -15,9 +15,9 @@ using namespace Ichor; class UsingTcpService final : public AdvancedService { public: UsingTcpService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency>(this, true); - reg.registerDependency(this, true, getProperties()); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency>(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED, getProperties()); } ~UsingTcpService() final = default; diff --git a/examples/websocket_example/UsingWsService.h b/examples/websocket_example/UsingWsService.h index 6cb4623..178f923 100644 --- a/examples/websocket_example/UsingWsService.h +++ b/examples/websocket_example/UsingWsService.h @@ -15,9 +15,9 @@ using namespace Ichor; class UsingWsService final : public AdvancedService { public: UsingWsService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency>(this, true); - reg.registerDependency(this, true, getProperties()); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency>(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED, getProperties()); } ~UsingWsService() final = default; diff --git a/include/ichor/Common.h b/include/ichor/Common.h index 3bed6b8..2021cdf 100644 --- a/include/ichor/Common.h +++ b/include/ichor/Common.h @@ -33,6 +33,12 @@ static constexpr bool DO_INTERNAL_IO_DEBUG = true; static constexpr bool DO_INTERNAL_IO_DEBUG = false; #endif +#ifdef ICHOR_ENABLE_INTERNAL_STL_DEBUGGING +static constexpr bool DO_INTERNAL_STL_DEBUG = true; +#else +static constexpr bool DO_INTERNAL_STL_DEBUG = false; +#endif + #ifdef ICHOR_USE_HARDENING static constexpr bool DO_HARDENING = true; #else @@ -66,6 +72,15 @@ static_assert(true, "") } \ static_assert(true, "") +#define INTERNAL_STL_DEBUG(...) \ + if constexpr(DO_INTERNAL_STL_DEBUG) { \ + fmt::print("[{:L}] ", std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); \ + fmt::print("[{}:{}] ", __FILE__, __LINE__); \ + fmt::print(__VA_ARGS__); \ + fmt::print("\n"); \ + } \ +static_assert(true, "") + // GNU C Library contains defines in sys/sysmacros.h. However, for compatibility reasons, this header is included in sys/types.h. Which is used by std. #undef major #undef minor diff --git a/include/ichor/Enums.h b/include/ichor/Enums.h index 34116ab..024aa42 100644 --- a/include/ichor/Enums.h +++ b/include/ichor/Enums.h @@ -1,11 +1,12 @@ #pragma once #include +#include namespace Ichor { namespace Detail { - enum class DependencyChange { + enum class DependencyChange : uint_fast16_t { NOT_FOUND, FOUND, FOUND_AND_START_ME, @@ -13,7 +14,7 @@ namespace Ichor }; } - enum class ServiceState { + enum class ServiceState : uint_fast16_t { UNINSTALLED, INSTALLED, INJECTING, @@ -23,7 +24,7 @@ namespace Ichor STOPPING, }; - enum class LogLevel { + enum class LogLevel : uint_fast16_t { LOG_TRACE, LOG_DEBUG, LOG_INFO, @@ -52,7 +53,7 @@ namespace Ichor // // [C] - Consumer performs this transition // [P] - Producer performs this transition - enum class state { + enum class state : uint_fast16_t { unknown, value_not_ready_consumer_active, value_not_ready_consumer_suspended, @@ -65,23 +66,23 @@ namespace Ichor struct Empty{}; constexpr static Empty empty = {}; - enum class StartBehaviour { + enum class StartBehaviour : uint_fast16_t { DONE, STARTED, STOPPED }; - enum class WaitError { + enum class WaitError : uint_fast16_t { QUITTING }; - enum class StartError { + enum class StartError : uint_fast16_t { FAILED }; // Necessary to prevent excessive events on the queue. // Every async call using this will end up adding an event to the queue on co_return/co_yield. - enum class IchorBehaviour { + enum class IchorBehaviour : uint_fast16_t { DONE }; diff --git a/include/ichor/Filter.h b/include/ichor/Filter.h index ea7fd46..6c2a931 100644 --- a/include/ichor/Filter.h +++ b/include/ichor/Filter.h @@ -49,7 +49,7 @@ namespace Ichor { template class TemplatedFilter final : public ITemplatedFilter { public: - TemplatedFilter(T&& _entries) noexcept : entries(std::forward(_entries)) {} + TemplatedFilter(T&& _entries) noexcept : entry(std::forward(_entries)) {} ~TemplatedFilter() noexcept final = default; TemplatedFilter(const TemplatedFilter&) = default; @@ -58,16 +58,17 @@ namespace Ichor { TemplatedFilter& operator=(TemplatedFilter&&) noexcept = default; [[nodiscard]] bool compareTo(ILifecycleManager const &manager) const noexcept final { - return entries.matches(manager); + return entry.matches(manager); } - T entries; + private: + T entry; }; class Filter final { public: - template - Filter(T&&... entries) noexcept : _templatedFilter(new TemplatedFilter(std::forward(entries)...)) {} + template + Filter(T&& entries) noexcept : _templatedFilter(new TemplatedFilter(std::forward(entries))) {} Filter(Filter&) = default; Filter(const Filter&) = default; @@ -90,7 +91,7 @@ struct fmt::formatter { } template - auto format(const Ichor::Filter& change, FormatContext& ctx) { + auto format(const Ichor::Filter&, FormatContext& ctx) { return fmt::format_to(ctx.out(), ""); } }; diff --git a/include/ichor/coroutines/Task.h b/include/ichor/coroutines/Task.h index 5e56038..85cf4ec 100644 --- a/include/ichor/coroutines/Task.h +++ b/include/ichor/coroutines/Task.h @@ -142,7 +142,7 @@ namespace Ichor private: - enum class result_type { empty, value, exception }; + enum class result_type : uint_fast16_t { empty, value, exception }; result_type m_resultType = result_type::empty; diff --git a/include/ichor/dependency_management/Dependency.h b/include/ichor/dependency_management/Dependency.h index 9a3c00f..c46db89 100644 --- a/include/ichor/dependency_management/Dependency.h +++ b/include/ichor/dependency_management/Dependency.h @@ -3,19 +3,60 @@ #include namespace Ichor { + + enum DependencyFlags : uint_fast16_t { + NONE = 0, + REQUIRED = 1, + ALLOW_MULTIPLE = 2 + }; + struct Dependency { - Dependency(uint64_t _interfaceNameHash, std::string_view _interfaceName, bool _required, uint64_t _satisfied) noexcept : interfaceNameHash(_interfaceNameHash), interfaceName(_interfaceName), required(_required), satisfied(_satisfied) {} + Dependency(uint64_t _interfaceNameHash, std::string_view _interfaceName, DependencyFlags _flags, uint64_t _satisfied) noexcept : interfaceNameHash(_interfaceNameHash), interfaceName(_interfaceName), flags(_flags), satisfied(_satisfied) {} Dependency(const Dependency &other) noexcept = default; Dependency(Dependency &&other) noexcept = default; Dependency& operator=(const Dependency &other) noexcept = default; Dependency& operator=(Dependency &&other) noexcept = default; bool operator==(const Dependency &other) const noexcept { - return interfaceNameHash == other.interfaceNameHash && required == other.required; + return interfaceNameHash == other.interfaceNameHash && flags == other.flags; } uint64_t interfaceNameHash; std::string_view interfaceName; - bool required; + DependencyFlags flags; uint64_t satisfied; }; } + + + +template <> +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { + return ctx.end(); + } + + template + auto format(const Ichor::DependencyFlags& flags, FormatContext& ctx) { + if(flags == Ichor::DependencyFlags::NONE) { + return fmt::format_to(ctx.out(), "NONE"); + } + + std::string intermediate{}; + if((flags & Ichor::DependencyFlags::REQUIRED) == Ichor::DependencyFlags::REQUIRED) { + fmt::format_to(std::back_inserter(intermediate), "REQUIRED"); + } + if((flags & Ichor::DependencyFlags::ALLOW_MULTIPLE) == Ichor::DependencyFlags::ALLOW_MULTIPLE) { + if(intermediate.empty()) { + fmt::format_to(std::back_inserter(intermediate), "ALLOW_MULTIPLE"); + } else { + fmt::format_to(std::back_inserter(intermediate), "|ALLOW_MULTIPLE"); + } + } + + if(intermediate.empty()) { + return fmt::format_to(ctx.out(), "Unknown value. Please file a bug in Ichor.");; + } + + return fmt::format_to(ctx.out(), "{}", intermediate); + } +}; diff --git a/include/ichor/dependency_management/DependencyLifecycleManager.h b/include/ichor/dependency_management/DependencyLifecycleManager.h index f1cf568..515a677 100644 --- a/include/ichor/dependency_management/DependencyLifecycleManager.h +++ b/include/ichor/dependency_management/DependencyLifecycleManager.h @@ -28,16 +28,16 @@ namespace Ichor::Detail { static std::unique_ptr> create(Properties&& properties, InterfacesList_t) { std::vector interfaces{}; interfaces.reserve(sizeof...(Interfaces)); - (interfaces.emplace_back(typeNameHash(), typeName(), false, false),...); + (interfaces.emplace_back(typeNameHash(), typeName(), DependencyFlags::NONE, false),...); return std::make_unique>(std::move(interfaces), std::move(properties)); } - std::vector().begin())> interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { + std::vector interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { if((online && _serviceIdsOfInjectedDependencies.contains(dependentService->serviceId())) || (!online && !_serviceIdsOfInjectedDependencies.contains(dependentService->serviceId()))) { return {}; } - std::vector().begin())> ret; + std::vector ret; for(auto const &interface : dependentService->getInterfaces()) { auto dep = _dependencies.find(interface, !online); @@ -48,13 +48,13 @@ namespace Ichor::Detail { continue; } - ret.push_back(dep); + ret.push_back(&(*dep)); } return ret; } - AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector().begin())> iterators) final { + AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) final { INTERNAL_DEBUG("dependencyOnline() svc {}:{} {} dependent {}:{}", serviceId(), implementationName(), getServiceState(), dependentService->serviceId(), dependentService->implementationName()); if constexpr (DO_INTERNAL_DEBUG) { for (auto id: _serviceIdsOfInjectedDependencies) { @@ -67,7 +67,8 @@ namespace Ichor::Detail { auto interested = DependencyChange::NOT_FOUND; - for(auto &dep : iterators) { + for(auto *dep : deps) { + INTERNAL_DEBUG("dependencyOnline() dep {} {} {}", dep->interfaceName, dep->satisfied, dep->flags); if(dep->satisfied == 0) { interested = DependencyChange::FOUND; } @@ -95,7 +96,7 @@ namespace Ichor::Detail { co_return StartBehaviour::DONE; } - AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector().begin())> iterators) final { + AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) final { INTERNAL_DEBUG("dependencyOffline() svc {}:{} {} dependent {}:{}", serviceId(), implementationName(), getServiceState(), dependentService->serviceId(), dependentService->implementationName()); auto interested = DependencyChange::NOT_FOUND; StartBehaviour ret = StartBehaviour::DONE; @@ -109,7 +110,7 @@ namespace Ichor::Detail { } } - for(auto &dep : iterators) { + for(auto &dep : deps) { // dependency should not be marked as unsatisfied if there is at least one other of the same type present dep->satisfied--; _serviceIdsOfInjectedDependencies.erase(dependentService->serviceId()); @@ -120,12 +121,12 @@ namespace Ichor::Detail { } #endif - if(dep->required && dep->satisfied == 0 && (getServiceState() == ServiceState::STARTING || getServiceState() == ServiceState::INJECTING)) { - INTERNAL_DEBUG("{}:{}:{} dependencyOffline waitForService {} {} {} {}", serviceId(), _service.getServiceName(), getServiceState(), interested, dep->satisfied, dep->required, getDependees().size()); + if((dep->flags & DependencyFlags::REQUIRED) && dep->satisfied == 0 && (getServiceState() == ServiceState::STARTING || getServiceState() == ServiceState::INJECTING)) { + INTERNAL_DEBUG("{}:{}:{} dependencyOffline waitForService {} {} {} {}", serviceId(), _service.getServiceName(), getServiceState(), interested, dep->satisfied, dep->flags, getDependees().size()); co_await waitForService(serviceId(), DependencyOnlineEvent::TYPE); } - if (dep->required && dep->satisfied == 0 && interested != DependencyChange::FOUND_AND_STOP_ME && getServiceState() == ServiceState::ACTIVE) { + if ((dep->flags & DependencyFlags::REQUIRED) && dep->satisfied == 0 && interested != DependencyChange::FOUND_AND_STOP_ME && getServiceState() == ServiceState::ACTIVE) { INTERNAL_DEBUG("{}:{}:{} dependencyOffline stopping {}", serviceId(), _service.getServiceName(), getServiceState(), interested); GetThreadLocalEventQueue().template pushPrioritisedEvent(serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY - 1); @@ -165,19 +166,19 @@ namespace Ichor::Detail { interested = DependencyChange::FOUND; } - if(dep->required && dep->satisfied == 0 && (getServiceState() == ServiceState::UNINJECTING || getServiceState() == ServiceState::STOPPING)) { - INTERNAL_DEBUG("{}:{}:{} dependencyOffline waitForService {} {} {} {}", serviceId(), _service.getServiceName(), getServiceState(), interested, dep->satisfied, dep->required, getDependees().size()); + if((dep->flags & DependencyFlags::REQUIRED) && dep->satisfied == 0 && (getServiceState() == ServiceState::UNINJECTING || getServiceState() == ServiceState::STOPPING)) { + INTERNAL_DEBUG("{}:{}:{} dependencyOffline waitForService {} {} {} {}", serviceId(), _service.getServiceName(), getServiceState(), interested, dep->satisfied, dep->flags, getDependees().size()); co_await waitForService(serviceId(), StopServiceEvent::TYPE); } #ifdef ICHOR_USE_HARDENING - if(dep->required && dep->satisfied == 0 && getServiceState() >= ServiceState::INJECTING) [[unlikely]] { - INTERNAL_DEBUG("{}:{}:{} dependencyOffline terminating {} {} {} {}", serviceId(), _service.getServiceName(), getServiceState(), interested, dep->satisfied, dep->required, getDependees().size()); + if((dep->flags & DependencyFlags::REQUIRED) && dep->satisfied == 0 && getServiceState() >= ServiceState::INJECTING) [[unlikely]] { + INTERNAL_DEBUG("{}:{}:{} dependencyOffline terminating {} {} {} {}", serviceId(), _service.getServiceName(), getServiceState(), interested, dep->satisfied, dep->flags, getDependees().size()); std::terminate(); } #endif - INTERNAL_DEBUG("{}:{}:{} dependencyOffline interested {} {} {}", serviceId(), _service.getServiceName(), getServiceState(), interested, dep->satisfied, dep->required); + INTERNAL_DEBUG("{}:{}:{} dependencyOffline interested {} {} {}", serviceId(), _service.getServiceName(), getServiceState(), interested, dep->satisfied, dep->flags); removeSelfIntoDoubleDispatch(dep->interfaceNameHash, dependentService); } diff --git a/include/ichor/dependency_management/DependencyManagerLifecycleManager.h b/include/ichor/dependency_management/DependencyManagerLifecycleManager.h index 90b8887..daf42e2 100644 --- a/include/ichor/dependency_management/DependencyManagerLifecycleManager.h +++ b/include/ichor/dependency_management/DependencyManagerLifecycleManager.h @@ -8,22 +8,22 @@ namespace Ichor::Detail { class DependencyManagerLifecycleManager final : public ILifecycleManager { public: explicit DependencyManagerLifecycleManager(DependencyManager *dm) : _dm(dm) { - _interfaces.emplace_back(typeNameHash(), typeName(), false, false); + _interfaces.emplace_back(typeNameHash(), typeName(), DependencyFlags::NONE, false); } ~DependencyManagerLifecycleManager() final = default; - std::vector().begin())> interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { + std::vector interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { return {}; } - AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector().begin())> iterators) final { + AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) final { // this function should never be called std::terminate(); co_return StartBehaviour::DONE; } - AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector().begin())> iterators) final { + AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) final { // this function should never be called std::terminate(); co_return StartBehaviour::DONE; diff --git a/include/ichor/dependency_management/DependencyRegister.h b/include/ichor/dependency_management/DependencyRegister.h index daf7a10..d8f1ad1 100644 --- a/include/ichor/dependency_management/DependencyRegister.h +++ b/include/ichor/dependency_management/DependencyRegister.h @@ -8,7 +8,7 @@ namespace Ichor { struct DependencyRegister final { template Impl> - void registerDependency(Impl *svc, bool required, tl::optional props = {}) { + void registerDependency(Impl *svc, DependencyFlags flags, tl::optional props = {}) { static_assert(!std::is_same_v, "Impl and interface need to be separate classes"); static_assert(!DerivedTemplated, "Interface needs to be a non-service class."); // Some weird bug in MSVC @@ -17,7 +17,7 @@ namespace Ichor { #endif _registrations.emplace(typeNameHash(), std::make_tuple( - Dependency{typeNameHash(), typeName(), required, 0}, + Dependency{typeNameHash(), typeName(), flags, 0}, std::function, IService&)>{[svc](NeverNull dep, IService& isvc){ svc->addDependencyInstance(*reinterpret_cast(dep.get()), isvc); }}, std::function, IService&)>{[svc](NeverNull dep, IService& isvc){ svc->removeDependencyInstance(*reinterpret_cast(dep.get()), isvc); }}, std::move(props))); @@ -29,7 +29,7 @@ namespace Ichor { static_assert(!DerivedTemplated, "Interface needs to be a non-service class."); _registrations.emplace(typeNameHash(), std::make_tuple( - Dependency{typeNameHash(), typeName(), true, 0}, + Dependency{typeNameHash(), typeName(), DependencyFlags::REQUIRED, 0}, std::function, IService&)>{[svc](NeverNull dep, IService& isvc){ svc->template addDependencyInstance(reinterpret_cast(dep.get()), &isvc); }}, std::function, IService&)>{[svc](NeverNull dep, IService& isvc){ svc->template removeDependencyInstance(reinterpret_cast(dep.get()), &isvc); }}, tl::optional{})); diff --git a/include/ichor/dependency_management/ILifecycleManager.h b/include/ichor/dependency_management/ILifecycleManager.h index 1a40fe1..78565b1 100644 --- a/include/ichor/dependency_management/ILifecycleManager.h +++ b/include/ichor/dependency_management/ILifecycleManager.h @@ -12,10 +12,10 @@ namespace Ichor { class ILifecycleManager { public: virtual ~ILifecycleManager() = default; - virtual std::vector().begin())> interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept = 0; + virtual std::vector interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept = 0; // iterators come from interestedInDependency() and have to be moved as using coroutines might end up clearing it. - virtual AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector().begin())> iterators) = 0; - virtual AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector().begin())> iterators) = 0; + virtual AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) = 0; + virtual AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) = 0; [[nodiscard]] virtual unordered_set &getDependencies() noexcept = 0; [[nodiscard]] virtual unordered_set &getDependees() noexcept = 0; [[nodiscard]] virtual AsyncGenerator start() = 0; diff --git a/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h b/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h index b1b3900..8709df3 100644 --- a/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h +++ b/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h @@ -7,21 +7,21 @@ namespace Ichor::Detail { class IServiceInterestedLifecycleManager final : public ILifecycleManager { public: IServiceInterestedLifecycleManager(IService *self) : _self(self) { - _interfaces.emplace_back(typeNameHash(), typeName(), false, false); + _interfaces.emplace_back(typeNameHash(), typeName(), DependencyFlags::NONE, false); } ~IServiceInterestedLifecycleManager() final = default; - std::vector().begin())> interestedInDependency(ILifecycleManager *, bool) noexcept final { + std::vector interestedInDependency(ILifecycleManager *, bool) noexcept final { // this function should never be called std::terminate(); } - AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector().begin())> iterators) final { + AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) final { // this function should never be called std::terminate(); } - AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector().begin())> iterators) final { + AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) final { // this function should never be called std::terminate(); } diff --git a/include/ichor/dependency_management/LifecycleManager.h b/include/ichor/dependency_management/LifecycleManager.h index 260fd1b..57ed36a 100644 --- a/include/ichor/dependency_management/LifecycleManager.h +++ b/include/ichor/dependency_management/LifecycleManager.h @@ -29,22 +29,22 @@ namespace Ichor::Detail { static std::unique_ptr> create(Properties&& properties, InterfacesList_t) { std::vector interfaces{}; interfaces.reserve(sizeof...(Interfaces)); - (interfaces.emplace_back(typeNameHash(), typeName(), false, false),...); + (interfaces.emplace_back(typeNameHash(), typeName(), DependencyFlags::NONE, false),...); return std::make_unique>(std::move(interfaces), std::forward(properties)); } - std::vector().begin())> interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { + std::vector interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { return {}; } - AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector().begin())> iterators) final { + AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) final { // this function should never be called std::terminate(); co_return StartBehaviour::DONE; } - AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector().begin())> iterators) final { + AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) final { // this function should never be called std::terminate(); co_return StartBehaviour::DONE; diff --git a/include/ichor/dependency_management/QueueLifecycleManager.h b/include/ichor/dependency_management/QueueLifecycleManager.h index 3ab4748..ddc6c83 100644 --- a/include/ichor/dependency_management/QueueLifecycleManager.h +++ b/include/ichor/dependency_management/QueueLifecycleManager.h @@ -9,22 +9,22 @@ namespace Ichor::Detail { class QueueLifecycleManager final : public ILifecycleManager { public: explicit QueueLifecycleManager(IEventQueue *q) : _q(q) { - _interfaces.emplace_back(typeNameHash(), typeName(), false, false); + _interfaces.emplace_back(typeNameHash(), typeName(), DependencyFlags::NONE, false); } ~QueueLifecycleManager() final = default; - std::vector().begin())> interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { + std::vector interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { return {}; } - AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector().begin())> iterators) final { + AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) final { // this function should never be called std::terminate(); co_return StartBehaviour::DONE; } - AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector().begin())> iterators) final { + AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) final { // this function should never be called std::terminate(); co_return StartBehaviour::DONE; diff --git a/include/ichor/glaze.h b/include/ichor/glaze.h index bb088ea..a9580e9 100644 --- a/include/ichor/glaze.h +++ b/include/ichor/glaze.h @@ -11,3 +11,110 @@ #if defined( __GNUC__ ) # pragma GCC diagnostic pop #endif + + + +template<> +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.end(); + } + + template + auto format(const glz::error_code& input, FormatContext& ctx) -> decltype(ctx.out()) { + switch(input) { + case glz::error_code::none: + return fmt::format_to(ctx.out(), "glz::error_code::none"); + case glz::error_code::no_read_input: + return fmt::format_to(ctx.out(), "glz::error_code::no_read_input"); + case glz::error_code::data_must_be_null_terminated: + return fmt::format_to(ctx.out(), "glz::error_code::data_must_be_null_terminated"); + case glz::error_code::parse_number_failure: + return fmt::format_to(ctx.out(), "glz::error_code::parse_number_failure"); + case glz::error_code::expected_brace: + return fmt::format_to(ctx.out(), "glz::error_code::expected_brace"); + case glz::error_code::expected_bracket: + return fmt::format_to(ctx.out(), "glz::error_code::expected_bracket"); + case glz::error_code::expected_quote: + return fmt::format_to(ctx.out(), "glz::error_code::expected_quote"); + case glz::error_code::exceeded_static_array_size: + return fmt::format_to(ctx.out(), "glz::error_code::exceeded_static_array_size"); + case glz::error_code::unexpected_end: + return fmt::format_to(ctx.out(), "glz::error_code::unexpected_end"); + case glz::error_code::expected_end_comment: + return fmt::format_to(ctx.out(), "glz::error_code::expected_end_comment"); + case glz::error_code::syntax_error: + return fmt::format_to(ctx.out(), "glz::error_code::syntax_error"); + case glz::error_code::key_not_found: + return fmt::format_to(ctx.out(), "glz::error_code::key_not_found"); + case glz::error_code::unexpected_enum: + return fmt::format_to(ctx.out(), "glz::error_code::unexpected_enum"); + case glz::error_code::attempt_member_func_read: + return fmt::format_to(ctx.out(), "glz::error_code::attempt_member_func_read"); + case glz::error_code::attempt_read_hidden: + return fmt::format_to(ctx.out(), "glz::error_code::attempt_read_hidden"); + case glz::error_code::invalid_nullable_read: + return fmt::format_to(ctx.out(), "glz::error_code::invalid_nullable_read"); + case glz::error_code::invalid_variant_object: + return fmt::format_to(ctx.out(), "glz::error_code::invalid_variant_object"); + case glz::error_code::invalid_variant_array: + return fmt::format_to(ctx.out(), "glz::error_code::invalid_variant_array"); + case glz::error_code::invalid_variant_string: + return fmt::format_to(ctx.out(), "glz::error_code::invalid_variant_string"); + case glz::error_code::no_matching_variant_type: + return fmt::format_to(ctx.out(), "glz::error_code::no_matching_variant_type"); + case glz::error_code::expected_true_or_false: + return fmt::format_to(ctx.out(), "glz::error_code::expected_true_or_false"); + case glz::error_code::unknown_key: + return fmt::format_to(ctx.out(), "glz::error_code::unknown_key"); + case glz::error_code::invalid_flag_input: + return fmt::format_to(ctx.out(), "glz::error_code::invalid_flag_input"); + case glz::error_code::invalid_escape: + return fmt::format_to(ctx.out(), "glz::error_code::invalid_escape"); + case glz::error_code::u_requires_hex_digits: + return fmt::format_to(ctx.out(), "glz::error_code::u_requires_hex_digits"); + case glz::error_code::file_extension_not_supported: + return fmt::format_to(ctx.out(), "glz::error_code::file_extension_not_supported"); + case glz::error_code::could_not_determine_extension: + return fmt::format_to(ctx.out(), "glz::error_code::could_not_determine_extension"); + case glz::error_code::seek_failure: + return fmt::format_to(ctx.out(), "glz::error_code::seek_failure"); + case glz::error_code::unicode_escape_conversion_failure: + return fmt::format_to(ctx.out(), "glz::error_code::unicode_escape_conversion_failure"); + case glz::error_code::file_open_failure: + return fmt::format_to(ctx.out(), "glz::error_code::file_open_failure"); + case glz::error_code::file_include_error: + return fmt::format_to(ctx.out(), "glz::error_code::file_include_error"); + case glz::error_code::dump_int_error: + return fmt::format_to(ctx.out(), "glz::error_code::dump_int_error"); + case glz::error_code::get_nonexistent_json_ptr: + return fmt::format_to(ctx.out(), "glz::error_code::get_nonexistent_json_ptr"); + case glz::error_code::get_wrong_type: + return fmt::format_to(ctx.out(), "glz::error_code::get_wrong_type"); + case glz::error_code::cannot_be_referenced: + return fmt::format_to(ctx.out(), "glz::error_code::cannot_be_referenced"); + case glz::error_code::invalid_get: + return fmt::format_to(ctx.out(), "glz::error_code::invalid_get"); + case glz::error_code::invalid_get_fn: + return fmt::format_to(ctx.out(), "glz::error_code::invalid_get_fn"); + case glz::error_code::invalid_call: + return fmt::format_to(ctx.out(), "glz::error_code::invalid_call"); + case glz::error_code::invalid_partial_key: + return fmt::format_to(ctx.out(), "glz::error_code::invalid_partial_key"); + case glz::error_code::name_mismatch: + return fmt::format_to(ctx.out(), "glz::error_code::name_mismatch"); + case glz::error_code::array_element_not_found: + return fmt::format_to(ctx.out(), "glz::error_code::array_element_not_found"); + case glz::error_code::elements_not_convertible_to_design: + return fmt::format_to(ctx.out(), "glz::error_code::elements_not_convertible_to_design"); + case glz::error_code::unknown_distribution: + return fmt::format_to(ctx.out(), "glz::error_code::unknown_distribution"); + case glz::error_code::invalid_distribution_elements: + return fmt::format_to(ctx.out(), "glz::error_code::invalid_distribution_elements"); + case glz::error_code::missing_key: + return fmt::format_to(ctx.out(), "glz::error_code::missing_key"); + default: + return fmt::format_to(ctx.out(), "glz::error_code:: ??? report bug!"); + } + } +}; diff --git a/include/ichor/services/etcd/IEtcd.h b/include/ichor/services/etcd/IEtcd.h index 79b6c35..4ab84d3 100644 --- a/include/ichor/services/etcd/IEtcd.h +++ b/include/ichor/services/etcd/IEtcd.h @@ -9,7 +9,7 @@ #include namespace Ichor { - enum class EtcdError : uint64_t { + enum class EtcdError : uint_fast16_t { HTTP_RESPONSE_ERROR, JSON_PARSE_ERROR, TIMEOUT, @@ -24,7 +24,7 @@ namespace Ichor { QUITTING }; - enum class EtcdErrorCodes : uint64_t { + enum class EtcdErrorCodes : uint_fast16_t { KEY_DOES_NOT_EXIST = 100, COMPARE_AND_SWAP_FAILED = 101, NOT_A_FILE = 102, diff --git a/include/ichor/services/io/IAsyncFileIO.h b/include/ichor/services/io/IAsyncFileIO.h index f4aa12a..ef447bc 100644 --- a/include/ichor/services/io/IAsyncFileIO.h +++ b/include/ichor/services/io/IAsyncFileIO.h @@ -7,7 +7,7 @@ #include namespace Ichor { - enum class FileIOError { + enum class FileIOError : uint_fast16_t { FAILED, FILE_DOES_NOT_EXIST, NO_PERMISSION, diff --git a/include/ichor/services/logging/LoggerFactory.h b/include/ichor/services/logging/LoggerFactory.h index 05a7d00..94a1613 100644 --- a/include/ichor/services/logging/LoggerFactory.h +++ b/include/ichor/services/logging/LoggerFactory.h @@ -17,7 +17,7 @@ namespace Ichor { class LoggerFactory final : public ILoggerFactory, public AdvancedService> { public: LoggerFactory(DependencyRegister ®, Properties props) : AdvancedService>(std::move(props)) { - reg.registerDependency(this, false); + reg.registerDependency(this, DependencyFlags::NONE); auto logLevelProp = AdvancedService>::getProperties().find("DefaultLogLevel"); if(logLevelProp != end(AdvancedService>::getProperties())) { diff --git a/include/ichor/services/network/ClientFactory.h b/include/ichor/services/network/ClientFactory.h index f0ce8e5..428bf16 100644 --- a/include/ichor/services/network/ClientFactory.h +++ b/include/ichor/services/network/ClientFactory.h @@ -13,7 +13,7 @@ namespace Ichor { class ClientFactory final : public IClientFactory, public AdvancedService> { public: ClientFactory(DependencyRegister ®, Properties properties) : AdvancedService>(std::move(properties)), _connections{} { - reg.registerDependency(this, false, AdvancedService>::getProperties()); + reg.registerDependency(this, DependencyFlags::NONE, AdvancedService>::getProperties()); } ~ClientFactory() final = default; diff --git a/include/ichor/services/network/IConnectionService.h b/include/ichor/services/network/IConnectionService.h index 1dc9165..95ad531 100644 --- a/include/ichor/services/network/IConnectionService.h +++ b/include/ichor/services/network/IConnectionService.h @@ -4,7 +4,7 @@ #include namespace Ichor { - enum class SendErrorReason { + enum class SendErrorReason : uint_fast16_t { QUITTING }; diff --git a/include/ichor/services/network/http/HttpCommon.h b/include/ichor/services/network/http/HttpCommon.h index 8711905..ebc62b2 100644 --- a/include/ichor/services/network/http/HttpCommon.h +++ b/include/ichor/services/network/http/HttpCommon.h @@ -5,7 +5,7 @@ namespace Ichor { // Copied/modified from Boost.BEAST - enum class HttpMethod { + enum class HttpMethod : uint_fast16_t { /** An unknown method. This value indicates that the request method string is not @@ -110,8 +110,7 @@ namespace Ichor { }; // Copied/modified from Boost.BEAST - enum class HttpStatus - { + enum class HttpStatus : uint_fast16_t { continue_ = 100, switching_protocols = 101, diff --git a/include/ichor/services/redis/IRedis.h b/include/ichor/services/redis/IRedis.h index 4abc047..cb59e9b 100644 --- a/include/ichor/services/redis/IRedis.h +++ b/include/ichor/services/redis/IRedis.h @@ -36,7 +36,7 @@ namespace Ichor { int64_t value; }; - enum class RedisError : uint64_t { + enum class RedisError : uint_fast16_t { DISCONNECTED }; diff --git a/include/ichor/stl/ConditionVariable.h b/include/ichor/stl/ConditionVariable.h index 8f8ae39..8c856b9 100644 --- a/include/ichor/stl/ConditionVariable.h +++ b/include/ichor/stl/ConditionVariable.h @@ -12,7 +12,7 @@ namespace Ichor { #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__APPLE__) || defined(ICHOR_MUSL)) && !defined(__CYGWIN__) using ConditionVariable = std::condition_variable_any; #else - enum class cv_status { no_timeout, timeout }; + enum class cv_status : uint_fast16_t { no_timeout, timeout }; struct ConditionVariable final { explicit ConditionVariable() noexcept { diff --git a/include/ichor/stl/ReferenceCountedPointer.h b/include/ichor/stl/ReferenceCountedPointer.h index 7ab2b01..020c71c 100644 --- a/include/ichor/stl/ReferenceCountedPointer.h +++ b/include/ichor/stl/ReferenceCountedPointer.h @@ -16,7 +16,7 @@ namespace Ichor { concept Constructible = std::is_constructible_v; -#ifdef ICHOR_ENABLE_INTERNAL_DEBUGGING +#ifdef ICHOR_ENABLE_INTERNAL_STL_DEBUGGING extern std::atomic _rfpCounter; #define RFP_ID _id, #else @@ -70,7 +70,7 @@ namespace Ichor { } template requires Constructible ReferenceCountedPointer(const ReferenceCountedPointer &o) noexcept : _ptr(o._ptr) { - INTERNAL_DEBUG("ReferenceCountedPointer<{}>(const ReferenceCountedPointer<{}> &o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}>(const ReferenceCountedPointer<{}> &o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); _ptr->useCount++; } ReferenceCountedPointer(ReferenceCountedPointer &&o) noexcept : _ptr(o._ptr) { @@ -78,35 +78,35 @@ namespace Ichor { } template requires Constructible ReferenceCountedPointer(ReferenceCountedPointer &&o) noexcept : _ptr(o._ptr) { - INTERNAL_DEBUG("ReferenceCountedPointer<{}>(ReferenceCountedPointer<{}> &&o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}>(ReferenceCountedPointer<{}> &&o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); o._ptr = nullptr; } explicit ReferenceCountedPointer(T* p) : _ptr(new Detail::ReferenceCountedPointerDeleter(p, [](void *ptr) { delete static_cast(ptr); })) { - INTERNAL_DEBUG("ReferenceCountedPointer<{}>(T* p) {} {}", typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}>(T* p) {} {}", typeName(), RFP_ID _ptr == nullptr); } template requires Constructible explicit ReferenceCountedPointer(U* p) : _ptr(new Detail::ReferenceCountedPointerDeleter(p, [](void *ptr) { delete static_cast(ptr); })) { - INTERNAL_DEBUG("ReferenceCountedPointer<{}>({}* p) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}>({}* p) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); } template requires Constructible explicit ReferenceCountedPointer(U&&... args) : _ptr(new Detail::ReferenceCountedPointerDeleter(new T(std::forward(args)...), [](void *ptr) { delete static_cast(ptr); })) { if constexpr (std::is_same_v) { static_assert(std::is_same_v); - INTERNAL_DEBUG("ReferenceCountedPointer<{}>(U&&... args) {} {} bool! {}", typeName(), RFP_ID _ptr == nullptr, *static_cast(_ptr->ptr.get())); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}>(U&&... args) {} {} bool! {}", typeName(), RFP_ID _ptr == nullptr, *static_cast(_ptr->ptr.get())); } else { - INTERNAL_DEBUG("ReferenceCountedPointer<{}>(U&&... args) {} {} {}", typeName(), RFP_ID _ptr == nullptr, sizeof(T)); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}>(U&&... args) {} {} {}", typeName(), RFP_ID _ptr == nullptr, sizeof(T)); } } template ReferenceCountedPointer(std::unique_ptr unique) : _ptr(new Detail::ReferenceCountedPointerDeleter(unique.get(), [deleter = unique.get_deleter()](void *ptr) { deleter(static_cast(ptr)); })) { - INTERNAL_DEBUG("ReferenceCountedPointer<{}>(unique) {} {}", typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}>(unique) {} {}", typeName(), RFP_ID _ptr == nullptr); unique.release(); } ~ReferenceCountedPointer() noexcept { - INTERNAL_DEBUG("~ReferenceCountedPointer<{}>() {} {}", typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("~ReferenceCountedPointer<{}>() {} {}", typeName(), RFP_ID _ptr == nullptr); decrement(); } @@ -124,7 +124,7 @@ namespace Ichor { template requires Constructible ReferenceCountedPointer& operator=(const ReferenceCountedPointer &o) noexcept { - INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator=(const ReferenceCountedPointer<{}> &o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> operator=(const ReferenceCountedPointer<{}> &o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); decrement(); _ptr = o._ptr; _ptr->useCount++; @@ -146,7 +146,7 @@ namespace Ichor { template requires Constructible ReferenceCountedPointer& operator=(ReferenceCountedPointer &&o) noexcept { - INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator=(ReferenceCountedPointer<{}> &&o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> operator=(ReferenceCountedPointer<{}> &&o) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); decrement(); _ptr = o._ptr; o._ptr = nullptr; @@ -156,7 +156,7 @@ namespace Ichor { template requires Constructible ReferenceCountedPointer& operator=(U *p) noexcept { - INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator=({}* p) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> operator=({}* p) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); decrement(); _ptr = new Detail::ReferenceCountedPointerDeleter(p, [](void *ptr) { delete static_cast(ptr); }); @@ -165,7 +165,7 @@ namespace Ichor { template ReferenceCountedPointer& operator=(std::unique_ptr unique) noexcept { - INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator=(unique) {} {}", typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> operator=(unique) {} {}", typeName(), RFP_ID _ptr == nullptr); decrement(); _ptr = new Detail::ReferenceCountedPointerDeleter(unique.get(), [deleter = unique.get_deleter()](void *ptr) { deleter(static_cast(ptr)); }); unique.release(); @@ -174,7 +174,7 @@ namespace Ichor { } ReferenceCountedPointer& operator=(decltype(nullptr)) noexcept { - INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator=(nullptr) {} {}", typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> operator=(nullptr) {} {}", typeName(), RFP_ID _ptr == nullptr); decrement(); _ptr = nullptr; @@ -183,11 +183,11 @@ namespace Ichor { [[nodiscard]] T& operator*() const noexcept { if constexpr (std::is_same_v) { - INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator*() {} {} bool! {}", typeName(), RFP_ID _ptr == nullptr, *static_cast(_ptr->ptr.get())); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> operator*() {} {} bool! {}", typeName(), RFP_ID _ptr == nullptr, *static_cast(_ptr->ptr.get())); } else { - INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator*() {} {}", typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> operator*() {} {}", typeName(), RFP_ID _ptr == nullptr); } - if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { + if constexpr (DO_INTERNAL_STL_DEBUG || DO_HARDENING) { if (_ptr == nullptr) [[unlikely]] { std::terminate(); } @@ -196,8 +196,8 @@ namespace Ichor { } [[nodiscard]] T* operator->() const noexcept { - INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator->() {} {}", typeName(), RFP_ID _ptr == nullptr); - if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> operator->() {} {}", typeName(), RFP_ID _ptr == nullptr); + if constexpr (DO_INTERNAL_STL_DEBUG || DO_HARDENING) { if (_ptr == nullptr) [[unlikely]] { std::terminate(); } @@ -209,18 +209,18 @@ namespace Ichor { // causing it to construct a new bool from a ReferenceCountedPointer instead of the underlying bool. // Maybe try https://www.artima.com/articles/the-safe-bool-idiom ? // [[nodiscard]] explicit operator bool() const noexcept { -// INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator bool() {} {}", typeName(), RFP_ID _ptr == nullptr); +// INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> operator bool() {} {}", typeName(), RFP_ID _ptr == nullptr); // return _ptr != nullptr; // } [[nodiscard]] bool operator==(decltype(nullptr)) const noexcept { - INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator==(nullptr) {} {}", typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> operator==(nullptr) {} {}", typeName(), RFP_ID _ptr == nullptr); return _ptr == nullptr; } template [[nodiscard]] bool operator==(const ReferenceCountedPointer &o) const noexcept { - INTERNAL_DEBUG("ReferenceCountedPointer<{}> operator==(const ReferenceCountedPointer<{}> &) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> operator==(const ReferenceCountedPointer<{}> &) {} {}", typeName(), typeName(), RFP_ID _ptr == nullptr); return _ptr == o._ptr; } @@ -237,8 +237,8 @@ namespace Ichor { } [[nodiscard]] T* get() const noexcept { - INTERNAL_DEBUG("ReferenceCountedPointer<{}> get() {} {}", typeName(), RFP_ID _ptr == nullptr); - if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { + INTERNAL_STL_DEBUG("ReferenceCountedPointer<{}> get() {} {}", typeName(), RFP_ID _ptr == nullptr); + if constexpr (DO_INTERNAL_STL_DEBUG || DO_HARDENING) { if (_ptr == nullptr) [[unlikely]] { std::terminate(); } @@ -263,7 +263,7 @@ namespace Ichor { } Detail::ReferenceCountedPointerBase *_ptr{}; -#ifdef ICHOR_ENABLE_INTERNAL_DEBUGGING +#ifdef ICHOR_ENABLE_INTERNAL_STL_DEBUGGING uint64_t _id{_rfpCounter.fetch_add(1, std::memory_order_relaxed)}; #endif diff --git a/quickbuild.sh b/quickbuild.sh index 8be2d4b..f9fad86 100755 --- a/quickbuild.sh +++ b/quickbuild.sh @@ -83,7 +83,7 @@ if [[ $RUN_EXAMPLES -eq 1 ]]; then ../bin/ichor_serializer_example || exit 1 ../bin/ichor_tcp_example || exit 1 ../bin/ichor_timer_example || exit 1 - ../bin/ichor_tracker_example || exit 1 + ../bin/ichor_factory_example || exit 1 ../bin/ichor_introspection_example || exit 1 ../bin/ichor_websocket_example || exit 1 ../bin/ichor_websocket_example -t4 || exit 1 diff --git a/src/ichor/DependencyManager.cpp b/src/ichor/DependencyManager.cpp index 148bc4c..ef293cd 100644 --- a/src/ichor/DependencyManager.cpp +++ b/src/ichor/DependencyManager.cpp @@ -17,7 +17,7 @@ #endif std::atomic Ichor::DependencyManager::_managerIdCounter = 0; -#ifdef ICHOR_ENABLE_INTERNAL_DEBUGGING +#ifdef ICHOR_ENABLE_INTERNAL_STL_DEBUGGING std::atomic Ichor::_rfpCounter = 0; #endif Ichor::unordered_set Ichor::Detail::emptyDependencies{}; diff --git a/src/ichor/dependency_management/DependencyInfo.cpp b/src/ichor/dependency_management/DependencyInfo.cpp index bc5de2d..0b0f05c 100644 --- a/src/ichor/dependency_management/DependencyInfo.cpp +++ b/src/ichor/dependency_management/DependencyInfo.cpp @@ -28,10 +28,6 @@ namespace Ichor { _dependencies.emplace_back(std::move(dependency)); } - void DependencyInfo::removeDependency(const Dependency &dependency) { - std::erase_if(_dependencies, [&dependency](auto const& dep) noexcept { return dep.interfaceNameHash == dependency.interfaceNameHash; }); - } - [[nodiscard]] bool DependencyInfo::contains(const Dependency &dependency) const noexcept { return end() != std::find_if(begin(), end(), [&dependency](auto const& dep) noexcept { return dep.interfaceNameHash == dependency.interfaceNameHash; }); @@ -42,7 +38,7 @@ namespace Ichor { INTERNAL_DEBUG("const find() size {}", size()); return std::find_if(begin(), end(), [&dependency, satisfied](Dependency const& dep) noexcept { INTERNAL_DEBUG("const find() {}:{}, {}:{}", dep.interfaceNameHash, dependency.interfaceNameHash, dep.satisfied, satisfied); - return dep.interfaceNameHash == dependency.interfaceNameHash && dep.satisfied == satisfied; + return dep.interfaceNameHash == dependency.interfaceNameHash && ((dep.flags & DependencyFlags::ALLOW_MULTIPLE) == DependencyFlags::ALLOW_MULTIPLE || (satisfied && dep.satisfied > 0) || (!satisfied && dep.satisfied == 0)); }); } @@ -51,7 +47,7 @@ namespace Ichor { INTERNAL_DEBUG("find() size {}", size()); return std::find_if(begin(), end(), [&dependency, satisfied](Dependency const& dep) noexcept { INTERNAL_DEBUG("find() {}:{}, {}:{}", dep.interfaceNameHash, dependency.interfaceNameHash, dep.satisfied, satisfied); - return dep.interfaceNameHash == dependency.interfaceNameHash && dep.satisfied == satisfied; + return dep.interfaceNameHash == dependency.interfaceNameHash && ((dep.flags & DependencyFlags::ALLOW_MULTIPLE) == DependencyFlags::ALLOW_MULTIPLE || (satisfied && dep.satisfied > 0) || (!satisfied && dep.satisfied == 0)); }); } @@ -68,7 +64,7 @@ namespace Ichor { [[nodiscard]] bool DependencyInfo::allSatisfied() const noexcept { return std::all_of(begin(), end(), [](auto const &dep) { - return dep.satisfied > 0 || !dep.required; + return dep.satisfied > 0 || (dep.flags & DependencyFlags::REQUIRED) == 0; }); } } diff --git a/src/services/etcd/EtcdV2Service.cpp b/src/services/etcd/EtcdV2Service.cpp index 68b8efc..e72d899 100644 --- a/src/services/etcd/EtcdV2Service.cpp +++ b/src/services/etcd/EtcdV2Service.cpp @@ -18,111 +18,6 @@ #include #include -template<> -struct fmt::formatter { - constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { - return ctx.end(); - } - - template - auto format(const glz::error_code& input, FormatContext& ctx) -> decltype(ctx.out()) { - switch(input) { - case glz::error_code::none: - return fmt::format_to(ctx.out(), "glz::error_code::none"); - case glz::error_code::no_read_input: - return fmt::format_to(ctx.out(), "glz::error_code::no_read_input"); - case glz::error_code::data_must_be_null_terminated: - return fmt::format_to(ctx.out(), "glz::error_code::data_must_be_null_terminated"); - case glz::error_code::parse_number_failure: - return fmt::format_to(ctx.out(), "glz::error_code::parse_number_failure"); - case glz::error_code::expected_brace: - return fmt::format_to(ctx.out(), "glz::error_code::expected_brace"); - case glz::error_code::expected_bracket: - return fmt::format_to(ctx.out(), "glz::error_code::expected_bracket"); - case glz::error_code::expected_quote: - return fmt::format_to(ctx.out(), "glz::error_code::expected_quote"); - case glz::error_code::exceeded_static_array_size: - return fmt::format_to(ctx.out(), "glz::error_code::exceeded_static_array_size"); - case glz::error_code::unexpected_end: - return fmt::format_to(ctx.out(), "glz::error_code::unexpected_end"); - case glz::error_code::expected_end_comment: - return fmt::format_to(ctx.out(), "glz::error_code::expected_end_comment"); - case glz::error_code::syntax_error: - return fmt::format_to(ctx.out(), "glz::error_code::syntax_error"); - case glz::error_code::key_not_found: - return fmt::format_to(ctx.out(), "glz::error_code::key_not_found"); - case glz::error_code::unexpected_enum: - return fmt::format_to(ctx.out(), "glz::error_code::unexpected_enum"); - case glz::error_code::attempt_member_func_read: - return fmt::format_to(ctx.out(), "glz::error_code::attempt_member_func_read"); - case glz::error_code::attempt_read_hidden: - return fmt::format_to(ctx.out(), "glz::error_code::attempt_read_hidden"); - case glz::error_code::invalid_nullable_read: - return fmt::format_to(ctx.out(), "glz::error_code::invalid_nullable_read"); - case glz::error_code::invalid_variant_object: - return fmt::format_to(ctx.out(), "glz::error_code::invalid_variant_object"); - case glz::error_code::invalid_variant_array: - return fmt::format_to(ctx.out(), "glz::error_code::invalid_variant_array"); - case glz::error_code::invalid_variant_string: - return fmt::format_to(ctx.out(), "glz::error_code::invalid_variant_string"); - case glz::error_code::no_matching_variant_type: - return fmt::format_to(ctx.out(), "glz::error_code::no_matching_variant_type"); - case glz::error_code::expected_true_or_false: - return fmt::format_to(ctx.out(), "glz::error_code::expected_true_or_false"); - case glz::error_code::unknown_key: - return fmt::format_to(ctx.out(), "glz::error_code::unknown_key"); - case glz::error_code::invalid_flag_input: - return fmt::format_to(ctx.out(), "glz::error_code::invalid_flag_input"); - case glz::error_code::invalid_escape: - return fmt::format_to(ctx.out(), "glz::error_code::invalid_escape"); - case glz::error_code::u_requires_hex_digits: - return fmt::format_to(ctx.out(), "glz::error_code::u_requires_hex_digits"); - case glz::error_code::file_extension_not_supported: - return fmt::format_to(ctx.out(), "glz::error_code::file_extension_not_supported"); - case glz::error_code::could_not_determine_extension: - return fmt::format_to(ctx.out(), "glz::error_code::could_not_determine_extension"); - case glz::error_code::seek_failure: - return fmt::format_to(ctx.out(), "glz::error_code::seek_failure"); - case glz::error_code::unicode_escape_conversion_failure: - return fmt::format_to(ctx.out(), "glz::error_code::unicode_escape_conversion_failure"); - case glz::error_code::file_open_failure: - return fmt::format_to(ctx.out(), "glz::error_code::file_open_failure"); - case glz::error_code::file_include_error: - return fmt::format_to(ctx.out(), "glz::error_code::file_include_error"); - case glz::error_code::dump_int_error: - return fmt::format_to(ctx.out(), "glz::error_code::dump_int_error"); - case glz::error_code::get_nonexistent_json_ptr: - return fmt::format_to(ctx.out(), "glz::error_code::get_nonexistent_json_ptr"); - case glz::error_code::get_wrong_type: - return fmt::format_to(ctx.out(), "glz::error_code::get_wrong_type"); - case glz::error_code::cannot_be_referenced: - return fmt::format_to(ctx.out(), "glz::error_code::cannot_be_referenced"); - case glz::error_code::invalid_get: - return fmt::format_to(ctx.out(), "glz::error_code::invalid_get"); - case glz::error_code::invalid_get_fn: - return fmt::format_to(ctx.out(), "glz::error_code::invalid_get_fn"); - case glz::error_code::invalid_call: - return fmt::format_to(ctx.out(), "glz::error_code::invalid_call"); - case glz::error_code::invalid_partial_key: - return fmt::format_to(ctx.out(), "glz::error_code::invalid_partial_key"); - case glz::error_code::name_mismatch: - return fmt::format_to(ctx.out(), "glz::error_code::name_mismatch"); - case glz::error_code::array_element_not_found: - return fmt::format_to(ctx.out(), "glz::error_code::array_element_not_found"); - case glz::error_code::elements_not_convertible_to_design: - return fmt::format_to(ctx.out(), "glz::error_code::elements_not_convertible_to_design"); - case glz::error_code::unknown_distribution: - return fmt::format_to(ctx.out(), "glz::error_code::unknown_distribution"); - case glz::error_code::invalid_distribution_elements: - return fmt::format_to(ctx.out(), "glz::error_code::invalid_distribution_elements"); - case glz::error_code::missing_key: - return fmt::format_to(ctx.out(), "glz::error_code::missing_key"); - default: - return fmt::format_to(ctx.out(), "glz::error_code:: ??? report bug!"); - } - } -}; - template <> struct glz::meta { using T = Ichor::EtcdReplyNode; @@ -302,9 +197,9 @@ struct glz::meta { }; Ichor::EtcdV2Service::EtcdV2Service(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, false, getProperties()); - reg.registerDependency(this, true, getProperties()); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::NONE, getProperties()); + reg.registerDependency(this, DependencyFlags(DependencyFlags::REQUIRED | DependencyFlags::ALLOW_MULTIPLE), getProperties()); + reg.registerDependency(this, DependencyFlags::REQUIRED); } Ichor::Task> Ichor::EtcdV2Service::start() { diff --git a/src/services/logging/SpdlogLogger.cpp b/src/services/logging/SpdlogLogger.cpp index fb04790..f4ac51b 100644 --- a/src/services/logging/SpdlogLogger.cpp +++ b/src/services/logging/SpdlogLogger.cpp @@ -5,7 +5,7 @@ #include Ichor::SpdlogLogger::SpdlogLogger(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); auto logLevelProp = getProperties().find("LogLevel"); if(logLevelProp != end(getProperties())) { diff --git a/src/services/metrics/EventStatisticsService.cpp b/src/services/metrics/EventStatisticsService.cpp index d710645..a7bc5a5 100644 --- a/src/services/metrics/EventStatisticsService.cpp +++ b/src/services/metrics/EventStatisticsService.cpp @@ -3,8 +3,8 @@ #include Ichor::EventStatisticsService::EventStatisticsService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } Ichor::Task> Ichor::EventStatisticsService::start() { diff --git a/src/services/network/AsioContextService.cpp b/src/services/network/AsioContextService.cpp index c3c4468..38a2e2d 100644 --- a/src/services/network/AsioContextService.cpp +++ b/src/services/network/AsioContextService.cpp @@ -16,7 +16,7 @@ Ichor::AsioContextService::AsioContextService(DependencyRegister ®, Propertie } } - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } Ichor::AsioContextService::~AsioContextService() { diff --git a/src/services/network/http/HttpConnectionService.cpp b/src/services/network/http/HttpConnectionService.cpp index 117fd10..78e5db4 100644 --- a/src/services/network/http/HttpConnectionService.cpp +++ b/src/services/network/http/HttpConnectionService.cpp @@ -8,8 +8,8 @@ #include Ichor::HttpConnectionService::HttpConnectionService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } Ichor::Task> Ichor::HttpConnectionService::start() { diff --git a/src/services/network/http/HttpHostService.cpp b/src/services/network/http/HttpHostService.cpp index 16afa98..4b66c23 100644 --- a/src/services/network/http/HttpHostService.cpp +++ b/src/services/network/http/HttpHostService.cpp @@ -6,8 +6,8 @@ #include Ichor::HttpHostService::HttpHostService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } Ichor::Task> Ichor::HttpHostService::start() { diff --git a/src/services/network/tcp/TcpConnectionService.cpp b/src/services/network/tcp/TcpConnectionService.cpp index 01104a7..bba92d3 100644 --- a/src/services/network/tcp/TcpConnectionService.cpp +++ b/src/services/network/tcp/TcpConnectionService.cpp @@ -10,8 +10,8 @@ #include Ichor::TcpConnectionService::TcpConnectionService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)), _socket(-1), _attempts(), _priority(INTERNAL_EVENT_PRIORITY), _quit() { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } Ichor::Task> Ichor::TcpConnectionService::start() { diff --git a/src/services/network/tcp/TcpHostService.cpp b/src/services/network/tcp/TcpHostService.cpp index fcce701..35e8422 100644 --- a/src/services/network/tcp/TcpHostService.cpp +++ b/src/services/network/tcp/TcpHostService.cpp @@ -12,8 +12,8 @@ #include Ichor::TcpHostService::TcpHostService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)), _socket(-1), _bindFd(), _priority(INTERNAL_EVENT_PRIORITY), _quit() { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } Ichor::Task> Ichor::TcpHostService::start() { diff --git a/src/services/network/ws/WsConnectionService.cpp b/src/services/network/ws/WsConnectionService.cpp index bfa4731..dcdba6f 100644 --- a/src/services/network/ws/WsConnectionService.cpp +++ b/src/services/network/ws/WsConnectionService.cpp @@ -26,10 +26,10 @@ void setup_stream(std::shared_ptr>& ws) } Ichor::WsConnectionService::WsConnectionService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); if(getProperties().contains("WsHostServiceId")) { - reg.registerDependency(this, true, + reg.registerDependency(this, DependencyFlags::REQUIRED, Properties{{"Filter", Ichor::make_any(ServiceIdFilterEntry{Ichor::any_cast(getProperties()["WsHostServiceId"])})}}); } } diff --git a/src/services/network/ws/WsHostService.cpp b/src/services/network/ws/WsHostService.cpp index 6be3c80..8779c76 100644 --- a/src/services/network/ws/WsHostService.cpp +++ b/src/services/network/ws/WsHostService.cpp @@ -34,8 +34,8 @@ class ClientConnectionFilter final { }; Ichor::WsHostService::WsHostService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } Ichor::Task> Ichor::WsHostService::start() { diff --git a/src/services/redis/HiRedisService.cpp b/src/services/redis/HiRedisService.cpp index 1897eb8..95172b8 100644 --- a/src/services/redis/HiRedisService.cpp +++ b/src/services/redis/HiRedisService.cpp @@ -93,9 +93,9 @@ namespace Ichor { } Ichor::HiredisService::HiredisService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } Ichor::Task> Ichor::HiredisService::start() { diff --git a/src/services/timer/TimerFactoryFactory.cpp b/src/services/timer/TimerFactoryFactory.cpp index d4c1993..a7f4499 100644 --- a/src/services/timer/TimerFactoryFactory.cpp +++ b/src/services/timer/TimerFactoryFactory.cpp @@ -6,7 +6,7 @@ class Ichor::TimerFactory final : public Ichor::ITimerFactory, public Ichor::AdvancedService { public: TimerFactory(Ichor::DependencyRegister ®, Ichor::Properties props) : Ichor::AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); _requestingSvcId = Ichor::any_cast(getProperties()["requestingSvcId"]); } ~TimerFactory() final = default; diff --git a/test/GettingStartedTests.cpp b/test/GettingStartedTests.cpp index 44415d1..a206169 100644 --- a/test/GettingStartedTests.cpp +++ b/test/GettingStartedTests.cpp @@ -22,7 +22,7 @@ struct IMyDependencyService {}; struct MyDependencyService final : public IMyDependencyService, public Ichor::AdvancedService { MyDependencyService(Ichor::DependencyRegister ®, Ichor::Properties props) : Ichor::AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~MyDependencyService() final = default; diff --git a/test/ServicesTests.cpp b/test/ServicesTests.cpp index 73861a5..15cc522 100644 --- a/test/ServicesTests.cpp +++ b/test/ServicesTests.cpp @@ -8,6 +8,7 @@ #include "TestServices/AddEventHandlerDuringEventHandlingService.h" #include "TestServices/RequestsLoggingService.h" #include "TestServices/ConstructorInjectionTestServices.h" +#include "TestServices/RequiredMultipleService.h" #include #include #include @@ -108,7 +109,7 @@ TEST_CASE("ServicesTests") { dm.createServiceManager(); dm.createServiceManager(); secondUselessServiceId = dm.createServiceManager()->getServiceId(); - dm.createServiceManager, ICountService>(); + dm.createServiceManager, ICountService>(); queue->start(CaptureSigInt); }); @@ -143,6 +144,72 @@ TEST_CASE("ServicesTests") { REQUIRE_FALSE(dm.isRunning()); } + SECTION("Multiple required dependencies, service starts on first and stops when everything uninjected") { + auto queue = std::make_unique(); + auto &dm = queue->createManager(); + uint64_t firstUselessServiceId{}; + uint64_t secondUselessServiceId{}; + + std::thread t([&]() { + dm.createServiceManager(); + firstUselessServiceId = dm.createServiceManager()->getServiceId(); + dm.createServiceManager(); + queue->start(CaptureSigInt); + }); + + waitForRunning(dm); + + dm.runForOrQueueEmpty(); + + queue->pushEvent(0, [&]() { + auto services = dm.getStartedServices(); + + REQUIRE(services.size() == 1); + REQUIRE(services[0]->isRunning()); + REQUIRE(services[0]->getSvcCount() == 1); + + secondUselessServiceId = dm.createServiceManager()->getServiceId(); + }); + + dm.runForOrQueueEmpty(); + + queue->pushEvent(0, [&]() { + auto services = dm.getStartedServices(); + + REQUIRE(services.size() == 1); + REQUIRE(services[0]->isRunning()); + REQUIRE(services[0]->getSvcCount() == 2); + + dm.getEventQueue().pushEvent(0, firstUselessServiceId); + }); + + dm.runForOrQueueEmpty(); + + queue->pushEvent(0, [&]() { + auto services = dm.getStartedServices(); + + REQUIRE(services.size() == 1); + REQUIRE(services[0]->isRunning()); + REQUIRE(services[0]->getSvcCount() == 1); + + dm.getEventQueue().pushEvent(0, secondUselessServiceId); + }); + + dm.runForOrQueueEmpty(); + + queue->pushEvent(0, [&]() { + auto services = dm.getStartedServices(); + + REQUIRE(services.size() == 0); + + dm.getEventQueue().pushEvent(0); + }); + + t.join(); + + REQUIRE_FALSE(dm.isRunning()); + } + SECTION("Optional dependencies") { auto queue = std::make_unique(); auto &dm = queue->createManager(); @@ -152,7 +219,7 @@ TEST_CASE("ServicesTests") { dm.createServiceManager(); dm.createServiceManager(); secondUselessServiceId = dm.createServiceManager()->getServiceId(); - dm.createServiceManager, ICountService>(); + dm.createServiceManager, ICountService>(); queue->start(CaptureSigInt); }); @@ -299,7 +366,7 @@ TEST_CASE("ServicesTests") { std::thread t([&]() { dm.createServiceManager, ILoggerFactory>(); - dm.createServiceManager, ICountService>(); + dm.createServiceManager, ICountService>(); auto service = dm.createServiceManager(); svcId = service->getServiceId(); static_assert(std::is_same_v>, ""); diff --git a/test/TestServices/AsyncUsingTimerService.h b/test/TestServices/AsyncUsingTimerService.h index f118b5e..9b67aa9 100644 --- a/test/TestServices/AsyncUsingTimerService.h +++ b/test/TestServices/AsyncUsingTimerService.h @@ -9,8 +9,8 @@ using namespace Ichor; class AsyncUsingTimerService final : public AdvancedService { public: AsyncUsingTimerService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~AsyncUsingTimerService() final = default; diff --git a/test/TestServices/DependencyOfflineWhileStartingService.h b/test/TestServices/DependencyOfflineWhileStartingService.h index eb4706f..9b1bf63 100644 --- a/test/TestServices/DependencyOfflineWhileStartingService.h +++ b/test/TestServices/DependencyOfflineWhileStartingService.h @@ -11,7 +11,7 @@ namespace Ichor { struct DependencyOfflineWhileStartingService final : public IDependencyOfflineWhileStartingService, public AdvancedService { DependencyOfflineWhileStartingService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~DependencyOfflineWhileStartingService() final = default; diff --git a/test/TestServices/DependencyOnlineWhileStoppingService.h b/test/TestServices/DependencyOnlineWhileStoppingService.h index 1465854..755ee86 100644 --- a/test/TestServices/DependencyOnlineWhileStoppingService.h +++ b/test/TestServices/DependencyOnlineWhileStoppingService.h @@ -11,7 +11,7 @@ namespace Ichor { struct DependencyOnlineWhileStoppingService final : public IDependencyOnlineWhileStoppingService, public AdvancedService { DependencyOnlineWhileStoppingService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~DependencyOnlineWhileStoppingService() final = default; diff --git a/test/TestServices/DependencyService.h b/test/TestServices/DependencyService.h index 211a2cc..f71c755 100644 --- a/test/TestServices/DependencyService.h +++ b/test/TestServices/DependencyService.h @@ -1,20 +1,14 @@ #pragma once #include "UselessService.h" +#include "ICountService.h" using namespace Ichor; -struct ICountService { - [[nodiscard]] virtual uint64_t getSvcCount() const noexcept = 0; - [[nodiscard]] virtual bool isRunning() const noexcept = 0; -protected: - ~ICountService() = default; -}; - -template -struct DependencyService final : public ICountService, public AdvancedService> { - DependencyService(DependencyRegister ®, Properties props) : AdvancedService>(std::move(props)) { - reg.registerDependency(this, required); +template +struct DependencyService final : public ICountService, public AdvancedService> { + DependencyService(DependencyRegister ®, Properties props) : AdvancedService>(std::move(props)) { + reg.registerDependency(this, flags); } ~DependencyService() final = default; Task> start() final { diff --git a/test/TestServices/EtcdUsingService.h b/test/TestServices/EtcdUsingService.h index 8c785f0..482fdb4 100644 --- a/test/TestServices/EtcdUsingService.h +++ b/test/TestServices/EtcdUsingService.h @@ -7,7 +7,7 @@ namespace Ichor { struct EtcdUsingService final : public AdvancedService { EtcdUsingService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } Task> start() final { diff --git a/test/TestServices/FailOnStartService.h b/test/TestServices/FailOnStartService.h index 2d25f08..671f2cb 100644 --- a/test/TestServices/FailOnStartService.h +++ b/test/TestServices/FailOnStartService.h @@ -32,7 +32,7 @@ namespace Ichor { struct FailOnStartWithDependenciesService final : public IFailOnStartService, public AdvancedService { FailOnStartWithDependenciesService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~FailOnStartWithDependenciesService() final = default; diff --git a/test/TestServices/HttpThreadService.h b/test/TestServices/HttpThreadService.h index 2f3b97a..2d3bd85 100644 --- a/test/TestServices/HttpThreadService.h +++ b/test/TestServices/HttpThreadService.h @@ -21,9 +21,9 @@ extern std::thread::id dmThreadId; class HttpThreadService final : public AdvancedService { public: HttpThreadService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency>(this, true); - reg.registerDependency(this, true, getProperties()); - reg.registerDependency(this, true); + reg.registerDependency>(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED, getProperties()); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~HttpThreadService() final = default; diff --git a/test/TestServices/ICountService.h b/test/TestServices/ICountService.h new file mode 100644 index 0000000..04d3d46 --- /dev/null +++ b/test/TestServices/ICountService.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +struct ICountService { + [[nodiscard]] virtual uint64_t getSvcCount() const noexcept = 0; + [[nodiscard]] virtual bool isRunning() const noexcept = 0; +protected: + ~ICountService() = default; +}; diff --git a/test/TestServices/MixingInterfacesService.h b/test/TestServices/MixingInterfacesService.h index 0385704..596f31e 100644 --- a/test/TestServices/MixingInterfacesService.h +++ b/test/TestServices/MixingInterfacesService.h @@ -58,8 +58,8 @@ struct Handle { struct CheckMixService final : public ICountService, public AdvancedService { CheckMixService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, false); - reg.registerDependency(this, false); + reg.registerDependency(this, DependencyFlags::ALLOW_MULTIPLE); + reg.registerDependency(this, DependencyFlags::ALLOW_MULTIPLE); } static void check(IMixOne &one, IMixTwo &two) { diff --git a/test/TestServices/MultipleSeparateDependencyRequestsService.h b/test/TestServices/MultipleSeparateDependencyRequestsService.h index ef3ba92..1e36b25 100644 --- a/test/TestServices/MultipleSeparateDependencyRequestsService.h +++ b/test/TestServices/MultipleSeparateDependencyRequestsService.h @@ -11,9 +11,9 @@ struct INotUsed { struct MultipleSeparateDependencyRequestsService final : public AdvancedService { MultipleSeparateDependencyRequestsService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, false, Properties{{"scope", Ichor::make_any("scope_one")}}); - reg.registerDependency(this, true, Properties{{"scope", Ichor::make_any("scope_one")}}); - reg.registerDependency(this, true, Properties{{"scope", Ichor::make_any("scope_two")}}); + reg.registerDependency(this, DependencyFlags::NONE, Properties{{"scope", Ichor::make_any("scope_one")}}); + reg.registerDependency(this, DependencyFlags::REQUIRED, Properties{{"scope", Ichor::make_any("scope_one")}}); + reg.registerDependency(this, DependencyFlags::REQUIRED, Properties{{"scope", Ichor::make_any("scope_two")}}); } ~MultipleSeparateDependencyRequestsService() final = default; diff --git a/test/TestServices/QuitOnStartWithDependenciesService.h b/test/TestServices/QuitOnStartWithDependenciesService.h index 1368e56..e09428b 100644 --- a/test/TestServices/QuitOnStartWithDependenciesService.h +++ b/test/TestServices/QuitOnStartWithDependenciesService.h @@ -6,7 +6,7 @@ using namespace Ichor; struct QuitOnStartWithDependenciesService final : public AdvancedService { QuitOnStartWithDependenciesService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~QuitOnStartWithDependenciesService() final = default; Task> start() final { diff --git a/test/TestServices/RedisUsingService.h b/test/TestServices/RedisUsingService.h index 4074c49..31da9d6 100644 --- a/test/TestServices/RedisUsingService.h +++ b/test/TestServices/RedisUsingService.h @@ -6,7 +6,7 @@ namespace Ichor { struct RedisUsingService final : public AdvancedService { RedisUsingService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } Task> start() final { diff --git a/test/TestServices/RegistrationCheckerService.h b/test/TestServices/RegistrationCheckerService.h index 4c7afb2..269e476 100644 --- a/test/TestServices/RegistrationCheckerService.h +++ b/test/TestServices/RegistrationCheckerService.h @@ -7,8 +7,8 @@ using namespace Ichor; struct RegistrationCheckerService final : public AdvancedService { RegistrationCheckerService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, false); - reg.registerDependency(this, false); + reg.registerDependency(this, DependencyFlags::NONE); + reg.registerDependency(this, DependencyFlags::NONE); } ~RegistrationCheckerService() final = default; diff --git a/test/TestServices/RequestsLoggingService.h b/test/TestServices/RequestsLoggingService.h index 2c8a622..59dc92b 100644 --- a/test/TestServices/RequestsLoggingService.h +++ b/test/TestServices/RequestsLoggingService.h @@ -9,7 +9,7 @@ namespace Ichor { struct RequestsLoggingService final : public IRequestsLoggingService, public AdvancedService { RequestsLoggingService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~RequestsLoggingService() = default; diff --git a/test/TestServices/RequiredMultipleService.h b/test/TestServices/RequiredMultipleService.h new file mode 100644 index 0000000..0406550 --- /dev/null +++ b/test/TestServices/RequiredMultipleService.h @@ -0,0 +1,40 @@ +#pragma once + +#include "UselessService.h" +#include "ICountService.h" + +using namespace Ichor; + +struct RequiredMultipleService final : public ICountService, public AdvancedService { + RequiredMultipleService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { + reg.registerDependency(this, DependencyFlags(DependencyFlags::REQUIRED | DependencyFlags::ALLOW_MULTIPLE)); + } + ~RequiredMultipleService() final = default; + Task> start() final { + running = true; + co_return {}; + } + Task stop() final { + running = false; + co_return; + } + + void addDependencyInstance(IUselessService&, IService&) { + svcCount++; + } + + void removeDependencyInstance(IUselessService&, IService&) { + svcCount--; + } + + [[nodiscard]] uint64_t getSvcCount() const noexcept final { + return svcCount; + } + + [[nodiscard]] bool isRunning() const noexcept final { + return running; + } + + uint64_t svcCount{}; + bool running{}; +}; From c1fa3c43b6abc5235d31e26268c4ce2562ff201e Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Thu, 28 Dec 2023 22:38:16 +0100 Subject: [PATCH 16/98] Add extra test for multiple required deps --- .../dependency_management/DependencyInfo.cpp | 2 +- test/ServicesTests.cpp | 62 +++++++++++++++++++ test/TestServices/RequiredMultipleService.h | 35 +++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/ichor/dependency_management/DependencyInfo.cpp b/src/ichor/dependency_management/DependencyInfo.cpp index 0b0f05c..81148b3 100644 --- a/src/ichor/dependency_management/DependencyInfo.cpp +++ b/src/ichor/dependency_management/DependencyInfo.cpp @@ -63,7 +63,7 @@ namespace Ichor { [[nodiscard]] bool DependencyInfo::allSatisfied() const noexcept { - return std::all_of(begin(), end(), [](auto const &dep) { + return std::all_of(begin(), end(), [](auto const &dep) noexcept { return dep.satisfied > 0 || (dep.flags & DependencyFlags::REQUIRED) == 0; }); } diff --git a/test/ServicesTests.cpp b/test/ServicesTests.cpp index 15cc522..c3e1ba2 100644 --- a/test/ServicesTests.cpp +++ b/test/ServicesTests.cpp @@ -210,6 +210,68 @@ TEST_CASE("ServicesTests") { REQUIRE_FALSE(dm.isRunning()); } + SECTION("Multiple required dependencies, service starts on all and stops when everything uninjected") { + auto queue = std::make_unique(); + auto &dm = queue->createManager(); + uint64_t firstUselessServiceId{}; + uint64_t secondUselessServiceId{}; + + std::thread t([&]() { + dm.createServiceManager(); + firstUselessServiceId = dm.createServiceManager()->getServiceId(); + dm.createServiceManager(); + queue->start(CaptureSigInt); + }); + + waitForRunning(dm); + + dm.runForOrQueueEmpty(); + + queue->pushEvent(0, [&]() { + auto services = dm.getStartedServices(); + + REQUIRE(services.size() == 0); + + secondUselessServiceId = dm.createServiceManager()->getServiceId(); + }); + + dm.runForOrQueueEmpty(); + + queue->pushEvent(0, [&]() { + auto services = dm.getStartedServices(); + + REQUIRE(services.size() == 1); + REQUIRE(services[0]->isRunning()); + REQUIRE(services[0]->getSvcCount() == 2); + + dm.getEventQueue().pushEvent(0, firstUselessServiceId); + }); + + dm.runForOrQueueEmpty(); + + queue->pushEvent(0, [&]() { + auto services = dm.getStartedServices(); + + REQUIRE(services.size() == 0); + + dm.getEventQueue().pushEvent(0, secondUselessServiceId); + }); + + dm.runForOrQueueEmpty(); + + queue->pushEvent(0, [&]() { + auto services = dm.getStartedServices(); + + REQUIRE(services.size() == 0); + + dm.getEventQueue().pushEvent(0); + }); + + t.join(); + + REQUIRE_FALSE(dm.isRunning()); + } + SECTION("Optional dependencies") { auto queue = std::make_unique(); auto &dm = queue->createManager(); diff --git a/test/TestServices/RequiredMultipleService.h b/test/TestServices/RequiredMultipleService.h index 0406550..1bf893b 100644 --- a/test/TestServices/RequiredMultipleService.h +++ b/test/TestServices/RequiredMultipleService.h @@ -38,3 +38,38 @@ struct RequiredMultipleService final : public ICountService, public AdvancedServ uint64_t svcCount{}; bool running{}; }; + +struct RequiredMultipleService2 final : public ICountService, public AdvancedService { + RequiredMultipleService2(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); + } + ~RequiredMultipleService2() final = default; + Task> start() final { + running = true; + co_return {}; + } + Task stop() final { + running = false; + co_return; + } + + void addDependencyInstance(IUselessService&, IService&) { + svcCount++; + } + + void removeDependencyInstance(IUselessService&, IService&) { + svcCount--; + } + + [[nodiscard]] uint64_t getSvcCount() const noexcept final { + return svcCount; + } + + [[nodiscard]] bool isRunning() const noexcept final { + return running; + } + + uint64_t svcCount{}; + bool running{}; +}; From 93b655b02b5e6b90e8f9d4b5a93b5a9036b01579 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Fri, 5 Jan 2024 23:32:55 +0100 Subject: [PATCH 17/98] Fix compiling with position indepedent code --- CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 47d2d8f..4aadf29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -263,9 +263,10 @@ if(ICHOR_USE_THREAD_SANITIZER) endif() endif() -if(NOT DEFINED ICHOR_USE_SANITIZERS AND NOT DEFINED ICHOR_USE_THREAD_SANITIZER) +if(NOT WIN32 AND NOT ICHOR_USE_SANITIZERS AND NOT ICHOR_USE_THREAD_SANITIZER) # see https://github.com/google/sanitizers/issues/856 - target_compile_options(ichor PUBLIC -fPIE) + target_compile_options(ichor PUBLIC -fpie) + target_link_options(ichor PUBLIC -pie) endif() if(WIN32 AND ICHOR_USE_HARDENING) @@ -567,4 +568,9 @@ if(ICHOR_BUILD_TESTING) endif() endif() + if(NOT WIN32 AND NOT ICHOR_USE_SANITIZERS AND NOT ICHOR_USE_THREAD_SANITIZER) + target_compile_options(Catch2 PUBLIC -fpie) + target_link_options(Catch2 PUBLIC -pie) + endif() + endif() From 55e5a994a90285012a3b445c6bace1b94a49b686 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Fri, 5 Jan 2024 23:33:13 +0100 Subject: [PATCH 18/98] Fix realtime example when SMT missing --- examples/realtime_example/GlobalRealtimeSettings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/realtime_example/GlobalRealtimeSettings.cpp b/examples/realtime_example/GlobalRealtimeSettings.cpp index 8db6a2c..b6b1720 100644 --- a/examples/realtime_example/GlobalRealtimeSettings.cpp +++ b/examples/realtime_example/GlobalRealtimeSettings.cpp @@ -73,6 +73,7 @@ GlobalRealtimeSettings::GlobalRealtimeSettings() { } } } else { + reenable_smt = false; fmt::print("SMT missing\n"); } From af2bf9fe60cc3ee8126ca030c25bd1a2cf8d1764 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sat, 6 Jan 2024 14:02:42 +0100 Subject: [PATCH 19/98] Fix realtime example and remove sanitizers for musl --- CMakeLists.txt | 4 ++-- Dockerfile-musl-aarch64 | 2 +- examples/realtime_example/TestService.h | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4aadf29..cd5f8ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ option(ICHOR_ENABLE_INTERNAL_STL_DEBUGGING "Add verbose logging of Ichor STL" OF option(ICHOR_BUILD_COVERAGE "Build ichor with coverage" OFF) option(ICHOR_USE_SPDLOG "Use spdlog as framework logging implementation" OFF) option(ICHOR_USE_BOOST_BEAST "Add boost asio and boost BEAST as dependencies" OFF) -option(ICHOR_USE_SANITIZERS "Enable sanitizers, catching potential errors but slowing down compilation and execution speed" ON) +cmake_dependent_option(ICHOR_USE_SANITIZERS "Enable sanitizers, catching potential errors but slowing down compilation and execution speed" ON "NOT ICHOR_MUSL" OFF) cmake_dependent_option(ICHOR_USE_THREAD_SANITIZER "Enable thread sanitizer, catching potential threading errors but slowing down compilation and execution speed. Cannot be combined with ICHOR_USE_SANITIZERS" OFF "NOT WIN32" OFF) option(ICHOR_USE_UGLY_HACK_EXCEPTION_CATCHING "Enable an ugly hack on gcc to enable debugging the point where exceptions are thrown. Useful for debugging boost asio/beast backtraces." OFF) option(ICHOR_REMOVE_SOURCE_NAMES "Remove compiling source file names and line numbers when logging." OFF) @@ -193,7 +193,7 @@ else() elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "X86_64_AVX512") target_compile_options(ichor PUBLIC -march=x86-64-v4) elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "MODERN_ARM_GENERIC") - target_compile_options(ichor PUBLIC -march=armv8-a) + target_compile_options(ichor PUBLIC -march=armv8-a+simd) endif() endif() diff --git a/Dockerfile-musl-aarch64 b/Dockerfile-musl-aarch64 index e90c260..ff492d1 100644 --- a/Dockerfile-musl-aarch64 +++ b/Dockerfile-musl-aarch64 @@ -44,4 +44,4 @@ WORKDIR /opt/ichor/build ENTRYPOINT ["/bin/sh", "-c"] -CMD ["cd /opt/ichor/build && cmake -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=0 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_MUSL=1 -DICHOR_AARCH64=1 /opt/ichor/src && make -j$(nproc)"] +CMD ["cd /opt/ichor/build && cmake -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_MUSL=1 -DICHOR_AARCH64=1 -DICHOR_ARCH_OPTIMIZATION=MODERN_ARM_GENERIC /opt/ichor/src && make -j$(nproc)"] diff --git a/examples/realtime_example/TestService.h b/examples/realtime_example/TestService.h index 79038a5..c6cb2a2 100644 --- a/examples/realtime_example/TestService.h +++ b/examples/realtime_example/TestService.h @@ -29,13 +29,13 @@ class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { reg.registerDependency(this, DependencyFlags::REQUIRED); - reg.registerDependency(this, DependencyFlags::NONE); + reg.registerDependency(this, DependencyFlags::ALLOW_MULTIPLE); } ~TestService() final = default; private: Task> start() final { - ICHOR_LOG_INFO(_logger, "TestService started with dependency"); + ICHOR_LOG_INFO(_logger, "TestService started with dependency {}", _injectionCount); _started = true; _eventHandlerRegistration = GetThreadLocalManager().registerEventHandler(this, this); if(_injectionCount == 2) { @@ -61,7 +61,7 @@ class TestService final : public AdvancedService { } void addDependencyInstance(IOptionalService&, IService &isvc) { - ICHOR_LOG_INFO(_logger, "Inserted IOptionalService svcid {}", isvc.getServiceId()); + ICHOR_LOG_INFO(_logger, "Inserted IOptionalService svcid {} {}", isvc.getServiceId(), _injectionCount); _injectionCount++; if(_started && _injectionCount == 2) { From e7beb64ee7d5b780b1a3a86a9fb2e5665d41803c Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sat, 6 Jan 2024 16:06:20 +0100 Subject: [PATCH 20/98] Fix compilation on OSX, apple has different linker flags --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd5f8ac..27c5a49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -291,7 +291,11 @@ elseif(ICHOR_USE_HARDENING) target_compile_definitions(ichor PUBLIC ICHOR_USE_HARDENING) - target_link_options(ichor PUBLIC -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now) + if(NOT APPLE) + target_link_options(ichor PUBLIC -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now) + else() + target_link_options(ichor PUBLIC -Wl,-z,nodlopen -Wl,-bind_at_load) + endif() endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") From 2e946acc5ec13f24db84fe1a4eaa33a293fe0c76 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sat, 6 Jan 2024 16:07:02 +0100 Subject: [PATCH 21/98] Fix fix the last fix (typo in apple linker flags) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 27c5a49..bd9a8f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -294,7 +294,7 @@ elseif(ICHOR_USE_HARDENING) if(NOT APPLE) target_link_options(ichor PUBLIC -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now) else() - target_link_options(ichor PUBLIC -Wl,-z,nodlopen -Wl,-bind_at_load) + target_link_options(ichor PUBLIC -Wl,-bind_at_load) endif() endif() From ea067e54bc33b808b719a895ab123ddfaf8737f2 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sun, 7 Jan 2024 13:00:56 +0100 Subject: [PATCH 22/98] Fix bug in calculating averages that threw away metrics --- .../services/metrics/EventStatisticsService.h | 2 +- .../metrics/EventStatisticsService.cpp | 35 +++++++++++-------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/include/ichor/services/metrics/EventStatisticsService.h b/include/ichor/services/metrics/EventStatisticsService.h index 9d7bac7..acd9db5 100644 --- a/include/ichor/services/metrics/EventStatisticsService.h +++ b/include/ichor/services/metrics/EventStatisticsService.h @@ -54,7 +54,7 @@ namespace Ichor { void addDependencyInstance(ITimerFactory &factory, IService &); void removeDependencyInstance(ITimerFactory &factory, IService&); - AsyncGenerator handleEvent(); + void calculateAverage(); Task> start() final; Task stop() final; diff --git a/src/services/metrics/EventStatisticsService.cpp b/src/services/metrics/EventStatisticsService.cpp index a7bc5a5..24781d2 100644 --- a/src/services/metrics/EventStatisticsService.cpp +++ b/src/services/metrics/EventStatisticsService.cpp @@ -18,29 +18,37 @@ Ichor::Task> Ichor::EventStatisticsService _averagingIntervalMs = 500; } +// fmt::print("evt stats {}:{} {} {}\n", getServiceId(), getServiceName(), _showStatisticsOnStop, _averagingIntervalMs); + auto &timer = _timerFactory->createTimer(); timer.setChronoInterval(std::chrono::milliseconds(_averagingIntervalMs)); timer.setCallbackAsync([this]() -> AsyncGenerator { - return handleEvent(); + calculateAverage(); + co_return IchorBehaviour::DONE; }); _interceptorRegistration = GetThreadLocalManager().registerEventInterceptor(this, this); timer.startTimer(); + // Try to get stopped after all other default priority services, to catch more events. We'll probably still miss services with a lower priority, but alas... + this->setServicePriority(1'001); + co_return {}; } Ichor::Task Ichor::EventStatisticsService::stop() { _interceptorRegistration.reset(); + if(_logger == nullptr) { + std::terminate(); + } + + uint64_t total_occ{}; + if(_showStatisticsOnStop) { // handle last bit of stored statistics by emulating a handleEvent call - auto gen = handleEvent(); - auto it = gen.begin(); - while(!gen.done() && !it.get_finished()) { - it = gen.begin(); - } + calculateAverage(); for(auto &[key, statistics] : _averagedStatistics) { if(statistics.empty()) { @@ -51,9 +59,11 @@ Ichor::Task Ichor::EventStatisticsService::stop() { auto max = std::max_element(begin(statistics), end(statistics), [](const AveragedStatisticEntry &a, const AveragedStatisticEntry &b){return a.maxProcessingTimeRequired < b.maxProcessingTimeRequired; })->maxProcessingTimeRequired; auto avg = std::accumulate(begin(statistics), end(statistics), 0L, [](int64_t i, const AveragedStatisticEntry &entry) -> int64_t { return i + entry.avgProcessingTimeRequired; }) / static_cast(statistics.size()); auto occ = std::accumulate(begin(statistics), end(statistics), 0L, [](int64_t i, const AveragedStatisticEntry &entry){ return i + entry.occurrences; }); + total_occ += occ; ICHOR_LOG_ERROR(_logger, "Dm {:L} Event type {} occurred {:L} times, min/max/avg processing: {:L}/{:L}/{:L} ns", GetThreadLocalManager().getId(), _eventTypeToNameMapper[key], occ, min, max, avg); } + ICHOR_LOG_ERROR(_logger, "Dm {:L} total events caught: {}", GetThreadLocalManager().getId(), total_occ); } co_return; @@ -93,7 +103,7 @@ void Ichor::EventStatisticsService::addDependencyInstance(ILogger &logger, IServ _logger = &logger; } -void Ichor::EventStatisticsService::removeDependencyInstance(ILogger &, IService&) { +void Ichor::EventStatisticsService::removeDependencyInstance(ILogger &, IService &) { _logger = nullptr; } @@ -101,23 +111,23 @@ void Ichor::EventStatisticsService::addDependencyInstance(ITimerFactory &factory _timerFactory = &factory; } -void Ichor::EventStatisticsService::removeDependencyInstance(ITimerFactory &factory, IService&) { +void Ichor::EventStatisticsService::removeDependencyInstance(ITimerFactory &, IService &) { _timerFactory = nullptr; } -Ichor::AsyncGenerator Ichor::EventStatisticsService::handleEvent() { +void Ichor::EventStatisticsService::calculateAverage() { int64_t now = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); decltype(_recentEventStatistics) newVec{}; newVec.swap(_recentEventStatistics); _recentEventStatistics.clear(); for(auto &[key, statistics] : newVec) { - auto avgStatistics = _averagedStatistics.find(key); - if(statistics.empty()) { continue; } + auto avgStatistics = _averagedStatistics.find(key); + auto min = std::min_element(begin(statistics), end(statistics), [](const StatisticEntry &a, const StatisticEntry &b){return a.processingTimeRequired < b.processingTimeRequired; })->processingTimeRequired; auto max = std::max_element(begin(statistics), end(statistics), [](const StatisticEntry &a, const StatisticEntry &b){return a.processingTimeRequired < b.processingTimeRequired; })->processingTimeRequired; auto avg = std::accumulate(begin(statistics), end(statistics), 0L, [](int64_t i, const StatisticEntry &entry){ return i + entry.processingTimeRequired; }) / static_cast(statistics.size()); @@ -127,10 +137,7 @@ Ichor::AsyncGenerator Ichor::EventStatisticsService::hand } else { avgStatistics->second.emplace_back(now, min, max, avg, statistics.size()); } - - co_yield {}; } - co_return {}; } const Ichor::unordered_map> &Ichor::EventStatisticsService::getRecentStatistics() const noexcept { From 13d4ff6f6b32f5ca4ed7647e60b81e87ddd6ec09 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sun, 7 Jan 2024 13:50:29 +0100 Subject: [PATCH 23/98] Support setting priority lower than normal to ensure services start in a particular order --- examples/event_statistics_example/main.cpp | 11 ++- examples/tcp_example/main.cpp | 11 ++- include/ichor/DependencyManager.h | 32 ++++---- .../ichor/services/logging/LoggerFactory.h | 4 +- .../ichor/services/network/ClientFactory.h | 14 ++-- .../services/timer/TimerFactoryFactory.h | 12 ++- src/ichor/DependencyManager.cpp | 82 ++++++++++--------- .../metrics/EventStatisticsService.cpp | 2 +- src/services/timer/TimerFactoryFactory.cpp | 21 ++++- 9 files changed, 115 insertions(+), 74 deletions(-) diff --git a/examples/event_statistics_example/main.cpp b/examples/event_statistics_example/main.cpp index 2ca7d79..8fe3967 100644 --- a/examples/event_statistics_example/main.cpp +++ b/examples/event_statistics_example/main.cpp @@ -25,13 +25,16 @@ int main(int argc, char *argv[]) { auto start = std::chrono::steady_clock::now(); auto queue = std::make_unique(); auto &dm = queue->createManager(); + + uint64_t priorityToEnsureStartingFirst = 51; + #ifdef ICHOR_USE_SPDLOG - dm.createServiceManager(); + dm.createServiceManager(Properties{}, priorityToEnsureStartingFirst); #endif - dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); - dm.createServiceManager(Properties{{"ShowStatisticsOnStop", make_any(true)}}); + dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}, priorityToEnsureStartingFirst); + dm.createServiceManager(Properties{{"ShowStatisticsOnStop", make_any(true)}}, priorityToEnsureStartingFirst); dm.createServiceManager(); - dm.createServiceManager(); + dm.createServiceManager(Properties{}, priorityToEnsureStartingFirst); queue->start(CaptureSigInt); auto end = std::chrono::steady_clock::now(); fmt::print("{} ran for {:L} µs\n", argv[0], std::chrono::duration_cast(end-start).count()); diff --git a/examples/tcp_example/main.cpp b/examples/tcp_example/main.cpp index c5eaa54..3f4d7d8 100644 --- a/examples/tcp_example/main.cpp +++ b/examples/tcp_example/main.cpp @@ -27,14 +27,17 @@ int main(int argc, char *argv[]) { auto start = std::chrono::steady_clock::now(); auto queue = std::make_unique(); auto &dm = queue->createManager(); + + uint64_t priorityToEnsureHostStartingFirst = 51; + #ifdef ICHOR_USE_SPDLOG - dm.createServiceManager(); + dm.createServiceManager(Properties{}, priorityToEnsureHostStartingFirst); #endif - dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); + dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}, priorityToEnsureHostStartingFirst); dm.createServiceManager>(); - dm.createServiceManager(Properties{{"Address", Ichor::make_any("127.0.0.1"s)}, {"Port", Ichor::make_any(static_cast(8001))}}); + dm.createServiceManager(Properties{{"Address", Ichor::make_any("127.0.0.1"s)}, {"Port", Ichor::make_any(static_cast(8001))}}, priorityToEnsureHostStartingFirst); dm.createServiceManager, IClientFactory>(); - dm.createServiceManager(); + dm.createServiceManager(Properties{}, priorityToEnsureHostStartingFirst); dm.createServiceManager(Properties{{"Address", Ichor::make_any("127.0.0.1"s)}, {"Port", Ichor::make_any(static_cast(8001))}}); queue->start(CaptureSigInt); auto end = std::chrono::steady_clock::now(); diff --git a/include/ichor/DependencyManager.h b/include/ichor/DependencyManager.h index 5127f21..530dde5 100644 --- a/include/ichor/DependencyManager.h +++ b/include/ichor/DependencyManager.h @@ -88,7 +88,7 @@ namespace Ichor { requires ImplementsAll #endif NeverNull createServiceManager(Properties&& properties, uint64_t priority = INTERNAL_EVENT_PRIORITY) { - return internalCreateServiceManager(std::move(properties), priority); + return internalCreateServiceManager(std::forward(properties), priority); } template @@ -213,7 +213,7 @@ namespace Ichor { for (auto const &[interfaceHash, registration] : depRegistry->_registrations) { if(interfaceHash == typeNameHash()) { auto const &props = std::get>(registration); - requests.emplace_back(0, mgr->serviceId(), INTERNAL_EVENT_PRIORITY, std::get(registration), props.has_value() ? &props.value() : tl::optional{}); + requests.emplace_back(0, mgr->serviceId(), std::min(mgr->getPriority(), INTERNAL_DEPENDENCY_EVENT_PRIORITY), std::get(registration), props.has_value() ? &props.value() : tl::optional{}); } } } @@ -520,37 +520,39 @@ namespace Ichor { static_assert(!std::is_default_constructible_v, "Cannot have a dependencies constructor and a default constructor simultaneously."); static_assert(!RequestsProperties, "Cannot have a dependencies constructor and a properties constructor simultaneously."); auto cmpMgr = Detail::DependencyLifecycleManager::template create<>(std::forward(properties), InterfacesList); + auto serviceId = cmpMgr->serviceId(); if constexpr (sizeof...(Interfaces) > 0) { static_assert(!ListContainsInterface::value, "IFrameworkLogger cannot have any dependencies"); } - logAddService(cmpMgr->serviceId()); + logAddService(serviceId); + + auto event_priority = std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, priority); for (auto const &[key, registration] : cmpMgr->getDependencyRegistry()->_registrations) { auto const &props = std::get>(registration); - _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), priority, std::get(registration), props.has_value() ? &props.value() : tl::optional{}); + _eventQueue->pushPrioritisedEvent(serviceId, event_priority, std::get(registration), props.has_value() ? &props.value() : tl::optional{}); } - auto event_priority = std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, priority); - _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), event_priority, cmpMgr->serviceId()); - cmpMgr->getService().setServicePriority(priority); if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { - if (_services.contains(cmpMgr->serviceId())) [[unlikely]] { + if (_services.contains(serviceId)) [[unlikely]] { std::terminate(); } } Impl* impl = &cmpMgr->getService(); // Can't directly emplace mgr into _services as that would result into modifying the container while iterating. - _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), INTERNAL_INSERT_SERVICE_EVENT_PRIORITY, std::move(cmpMgr)); + _eventQueue->pushPrioritisedEvent(serviceId, std::min(INTERNAL_INSERT_SERVICE_EVENT_PRIORITY, priority), std::move(cmpMgr)); + _eventQueue->pushPrioritisedEvent(serviceId, event_priority, serviceId); return impl; } else { static_assert(!(std::is_default_constructible_v && RequestsProperties), "Cannot have a properties constructor and a default constructor simultaneously."); auto cmpMgr = Detail::LifecycleManager::template create<>(std::forward(properties), InterfacesList); + auto serviceId = cmpMgr->serviceId(); if constexpr (sizeof...(Interfaces) > 0) { if constexpr (ListContainsInterface::value) { @@ -561,19 +563,19 @@ namespace Ichor { cmpMgr->getService().setServicePriority(priority); - logAddService(cmpMgr->serviceId()); - - auto event_priority = std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, priority); - _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), event_priority, cmpMgr->serviceId()); + logAddService(serviceId); if constexpr (DO_INTERNAL_DEBUG || DO_HARDENING) { - if (_services.contains(cmpMgr->serviceId())) [[unlikely]] { + if (_services.contains(serviceId)) [[unlikely]] { std::terminate(); } } Impl* impl = &cmpMgr->getService(); - _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), INTERNAL_INSERT_SERVICE_EVENT_PRIORITY, std::move(cmpMgr)); + _eventQueue->pushPrioritisedEvent(serviceId, std::min(INTERNAL_INSERT_SERVICE_EVENT_PRIORITY, priority), std::move(cmpMgr)); + + auto event_priority = std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, priority); + _eventQueue->pushPrioritisedEvent(serviceId, event_priority, serviceId); return impl; } diff --git a/include/ichor/services/logging/LoggerFactory.h b/include/ichor/services/logging/LoggerFactory.h index 94a1613..f60fd70 100644 --- a/include/ichor/services/logging/LoggerFactory.h +++ b/include/ichor/services/logging/LoggerFactory.h @@ -67,7 +67,7 @@ namespace Ichor { Properties props{}; props.template emplace<>("Filter", Ichor::make_any(ServiceIdFilterEntry{evt.originatingService})); props.template emplace<>("LogLevel", Ichor::make_any(requestedLevel)); - auto newLogger = GetThreadLocalManager().template createServiceManager(std::move(props)); + auto newLogger = GetThreadLocalManager().template createServiceManager(std::move(props), evt.priority); _loggers.emplace(evt.originatingService, newLogger); } else { ICHOR_LOG_TRACE(_logger, "svcid {} already has logger", evt.originatingService); @@ -77,6 +77,8 @@ namespace Ichor { void handleDependencyUndoRequest(AlwaysNull, DependencyUndoRequestEvent const &evt) { auto service = _loggers.find(evt.originatingService); if(service != end(_loggers)) { + // since loggers only do things upon requests and the requesting service is already stopped, there is no harm in using a lower priority. + GetThreadLocalEventQueue().template pushEvent(AdvancedService>::getServiceId(), service->second->getServiceId()); // + 11 because the first stop triggers a dep offline event and inserts a new stop with 10 higher priority. GetThreadLocalEventQueue().template pushPrioritisedEvent(AdvancedService>::getServiceId(), INTERNAL_EVENT_PRIORITY + 11, service->second->getServiceId()); diff --git a/include/ichor/services/network/ClientFactory.h b/include/ichor/services/network/ClientFactory.h index 428bf16..43805bc 100644 --- a/include/ichor/services/network/ClientFactory.h +++ b/include/ichor/services/network/ClientFactory.h @@ -12,7 +12,7 @@ namespace Ichor { template class ClientFactory final : public IClientFactory, public AdvancedService> { public: - ClientFactory(DependencyRegister ®, Properties properties) : AdvancedService>(std::move(properties)), _connections{} { + ClientFactory(DependencyRegister ®, Properties properties) : AdvancedService>(std::move(properties)) { reg.registerDependency(this, DependencyFlags::NONE, AdvancedService>::getProperties()); } ~ClientFactory() final = default; @@ -27,12 +27,12 @@ namespace Ichor { if(existingConnections == _connections.end()) { unordered_map newMap; - newMap.emplace(count, GetThreadLocalManager().template createServiceManager(std::move(properties))); + newMap.emplace(count, GetThreadLocalManager().template createServiceManager(std::move(properties), requestingSvc->getServicePriority())); _connections.emplace(requestingSvc->getServiceId(), std::move(newMap)); return count; } - existingConnections->second.emplace(requestingSvc->getServiceId(), GetThreadLocalManager().template createServiceManager(std::move(properties))); + existingConnections->second.emplace(requestingSvc->getServiceId(), GetThreadLocalManager().template createServiceManager(std::move(properties), requestingSvc->getServicePriority())); return count; } @@ -95,7 +95,7 @@ namespace Ichor { newProps.emplace("Filter", Ichor::make_any(ServiceIdFilterEntry{evt.originatingService})); unordered_map newMap; - newMap.emplace(_connectionCounter++, GetThreadLocalManager().template createServiceManager(std::move(newProps))); + newMap.emplace(_connectionCounter++, GetThreadLocalManager().template createServiceManager(std::move(newProps), evt.priority)); _connections.emplace(evt.originatingService, std::move(newMap)); } } @@ -139,11 +139,11 @@ namespace Ichor { } - void addDependencyInstance(ILogger &logger, IService &isvc) { + void addDependencyInstance(ILogger &logger, IService &) { _logger = &logger; } - void removeDependencyInstance(ILogger &logger, IService &isvc) { + void removeDependencyInstance(ILogger &, IService &) { _logger = nullptr; } @@ -152,7 +152,7 @@ namespace Ichor { ILogger *_logger{}; uint64_t _connectionCounter{}; - unordered_map> _connections; + unordered_map> _connections{}; DependencyTrackerRegistration _trackerRegistration{}; EventHandlerRegistration _unrecoverableErrorRegistration{}; }; diff --git a/include/ichor/services/timer/TimerFactoryFactory.h b/include/ichor/services/timer/TimerFactoryFactory.h index 3f2de86..e776904 100644 --- a/include/ichor/services/timer/TimerFactoryFactory.h +++ b/include/ichor/services/timer/TimerFactoryFactory.h @@ -6,6 +6,16 @@ #include namespace Ichor { + namespace Detail { + struct InternalTimerFactory { + [[nodiscard]] virtual ServiceIdType _getServiceId() const noexcept = 0; + virtual void stopAllTimers() noexcept = 0; + + protected: + ~InternalTimerFactory() = default; + }; + } + // Oh god, we're turning into java /// This class creates timer factories for requesting services, providing the requesting services' serviceId to the factory/timers class TimerFactoryFactory final : public AdvancedService { @@ -23,6 +33,6 @@ namespace Ichor { friend DependencyManager; DependencyTrackerRegistration _trackerRegistration{}; - unordered_map _factories; + unordered_map _factories; }; } diff --git a/src/ichor/DependencyManager.cpp b/src/ichor/DependencyManager.cpp index ef293cd..dc285d5 100644 --- a/src/ichor/DependencyManager.cpp +++ b/src/ichor/DependencyManager.cpp @@ -150,9 +150,9 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) } _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent - _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), serviceId, INTERNAL_DEPENDENCY_EVENT_PRIORITY)); + _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), serviceId, std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority))); } else if(it.get_value() == StartBehaviour::STARTED) { - _eventQueue->pushPrioritisedEvent(serviceId, INTERNAL_DEPENDENCY_EVENT_PRIORITY); + _eventQueue->pushPrioritisedEvent(serviceId, std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority)); } } handleEventCompletion(*depOnlineEvt); @@ -271,17 +271,23 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) bool allServicesStopped{true}; + uint64_t lowest_priority{evt->priority}; + for (auto const &[key, possibleManager]: _services) { if (possibleManager->getServiceState() == ServiceState::ACTIVE) { auto &dependees = possibleManager->getDependees(); - for(auto &serviceId : dependees) { - _eventQueue->pushPrioritisedEvent(_quitEvt->originatingService, INTERNAL_DEPENDENCY_EVENT_PRIORITY, - serviceId); + auto priority = std::max(possibleManager->getPriority(), INTERNAL_DEPENDENCY_EVENT_PRIORITY); + + if(priority > lowest_priority) { + lowest_priority = priority; + } + + for(auto const &serviceId : dependees) { + _eventQueue->pushPrioritisedEvent(_quitEvt->originatingService, priority, serviceId); } - _eventQueue->pushPrioritisedEvent(_quitEvt->originatingService, INTERNAL_DEPENDENCY_EVENT_PRIORITY, - possibleManager->serviceId()); + _eventQueue->pushPrioritisedEvent(_quitEvt->originatingService, priority, possibleManager->serviceId()); } if(possibleManager->getServiceState() != ServiceState::INSTALLED) { @@ -307,7 +313,7 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) _eventQueue->quit(); } else { // slowly increase priority every time it fails, as some services rely on custom priorities when stopping - _eventQueue->pushPrioritisedEvent(_quitEvt->originatingService, std::max(INTERNAL_EVENT_PRIORITY + 1, evt->priority + 10)); + _eventQueue->pushPrioritisedEvent(_quitEvt->originatingService, std::max(INTERNAL_EVENT_PRIORITY + 1, lowest_priority + 1)); } // quit event cannot be used in async manner, so no need to handle error/completion } @@ -329,41 +335,43 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) if(!it.get_finished()) { _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent - _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), cmpMgr->serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY)); + _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), cmpMgr->serviceId(), std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority))); } else if(it.get_value() == StartBehaviour::STARTED) { - _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY); + _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority)); } } // loop over all services, check if cmpMgr is interested in the active ones and inject them if so for (auto &[key, mgr] : _services) { - if (mgr->getServiceState() == ServiceState::ACTIVE) { - auto depIts = cmpMgr->interestedInDependency(mgr.get(), true); + if (mgr->getServiceState() != ServiceState::ACTIVE) { + continue; + } - if(depIts.empty()) { - continue; - } + auto depIts = cmpMgr->interestedInDependency(mgr.get(), true); - auto const filterProp = mgr->getProperties().find("Filter"); - const Filter *filter = nullptr; - if (filterProp != cend(mgr->getProperties())) { - filter = Ichor::any_cast(&filterProp->second); - } + if(depIts.empty()) { + continue; + } - if (filter != nullptr && !filter->compareTo(*cmpMgr.get())) { - continue; - } + auto const filterProp = mgr->getProperties().find("Filter"); + const Filter *filter = nullptr; + if (filterProp != cend(mgr->getProperties())) { + filter = Ichor::any_cast(&filterProp->second); + } - auto gen = cmpMgr->dependencyOnline(mgr.get(), std::move(depIts)); - auto it = gen.begin(); + if (filter != nullptr && !filter->compareTo(*cmpMgr.get())) { + continue; + } - if(!it.get_finished()) { - _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); - // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent - _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), cmpMgr->serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY)); - } else if(it.get_value() == StartBehaviour::STARTED) { - _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY); - } + auto gen = cmpMgr->dependencyOnline(mgr.get(), std::move(depIts)); + auto it = gen.begin(); + + if(!it.get_finished()) { + _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); + // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent + _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), cmpMgr->serviceId(), std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority))); + } else if(it.get_value() == StartBehaviour::STARTED) { + _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority)); } } } @@ -539,7 +547,7 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) INTERNAL_DEBUG("StartServiceEvent finished {}:{} {}", toStartService->serviceId(), toStartService->implementationName(), it.get_promise_id()); - _eventQueue->pushPrioritisedEvent(toStartService->serviceId(), INTERNAL_DEPENDENCY_EVENT_PRIORITY); + _eventQueue->pushPrioritisedEvent(toStartService->serviceId(), std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority)); handleEventCompletion(*startServiceEvt); } break; @@ -711,7 +719,7 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) INTERNAL_DEBUG("Finishing handling StartServiceEvent {} {} {} {}", origEvt->id, origEvt->priority, origEvt->originatingService, origEvt->serviceId); - _eventQueue->pushPrioritisedEvent(origEvt->serviceId, INTERNAL_COROUTINE_EVENT_PRIORITY); + _eventQueue->pushPrioritisedEvent(origEvt->serviceId, std::min(INTERNAL_COROUTINE_EVENT_PRIORITY, evt->priority)); handleEventCompletion(*origEvt); } else if(origEvtIt->second->type == StopServiceEvent::TYPE) { auto origEvt = static_cast(origEvtIt->second.get()); @@ -747,7 +755,7 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) // The dependee went offline during the async handling of the original // DependencyOnlineEvent. Add a proper DependencyOfflineEvent to handle that. // That is, originatingService points to the dependee, not the original service that went offline. - _eventQueue->pushPrioritisedEvent(origEvt->originatingService, INTERNAL_COROUTINE_EVENT_PRIORITY); + _eventQueue->pushPrioritisedEvent(origEvt->originatingService, std::min(INTERNAL_COROUTINE_EVENT_PRIORITY, evt->priority)); } handleEventCompletion(*origEvt); } else if(origEvtIt->second->type == ContinuableDependencyOfflineEvent::TYPE) { @@ -762,7 +770,7 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) } // The dependee of originatingOfflineServiceId went offline during the async handling of the original // DependencyOfflineEvent. Add a proper DependencyOfflineEvent to handle that. - _eventQueue->pushPrioritisedEvent(origEvt->originatingService, INTERNAL_COROUTINE_EVENT_PRIORITY); + _eventQueue->pushPrioritisedEvent(origEvt->originatingService, std::min(INTERNAL_COROUTINE_EVENT_PRIORITY, evt->priority)); finishWaitingService(origEvt->originatingService, StopServiceEvent::TYPE, StopServiceEvent::NAME); } @@ -786,7 +794,7 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) INTERNAL_DEBUG("originatingOfflineService found waiting service {} {}", origEvt->originatingOfflineServiceId, serviceIt->second->getServiceState()); #endif // Service needs to be stopped to complete the sequence - _eventQueue->pushPrioritisedEvent(origEvt->originatingOfflineServiceId, INTERNAL_COROUTINE_EVENT_PRIORITY, origEvt->originatingOfflineServiceId); + _eventQueue->pushPrioritisedEvent(origEvt->originatingOfflineServiceId, std::min(INTERNAL_COROUTINE_EVENT_PRIORITY, evt->priority), origEvt->originatingOfflineServiceId); } handleEventCompletion(*origEvt); diff --git a/src/services/metrics/EventStatisticsService.cpp b/src/services/metrics/EventStatisticsService.cpp index 24781d2..246f0ef 100644 --- a/src/services/metrics/EventStatisticsService.cpp +++ b/src/services/metrics/EventStatisticsService.cpp @@ -44,7 +44,7 @@ Ichor::Task Ichor::EventStatisticsService::stop() { std::terminate(); } - uint64_t total_occ{}; + int64_t total_occ{}; if(_showStatisticsOnStop) { // handle last bit of stored statistics by emulating a handleEvent call diff --git a/src/services/timer/TimerFactoryFactory.cpp b/src/services/timer/TimerFactoryFactory.cpp index a7f4499..eb066f5 100644 --- a/src/services/timer/TimerFactoryFactory.cpp +++ b/src/services/timer/TimerFactoryFactory.cpp @@ -3,7 +3,7 @@ #include #include -class Ichor::TimerFactory final : public Ichor::ITimerFactory, public Ichor::AdvancedService { +class Ichor::TimerFactory final : public Ichor::ITimerFactory, public Detail::InternalTimerFactory, public Ichor::AdvancedService { public: TimerFactory(Ichor::DependencyRegister ®, Ichor::Properties props) : Ichor::AdvancedService(std::move(props)) { reg.registerDependency(this, DependencyFlags::REQUIRED); @@ -39,6 +39,16 @@ class Ichor::TimerFactory final : public Ichor::ITimerFactory, public Ichor::Adv _queue = nullptr; } + ServiceIdType _getServiceId() const noexcept final { + return getServiceId(); + } + + void stopAllTimers() noexcept final { + for(auto& timer : _timers) { + timer->stopTimer(); + } + } + static std::atomic _timerIdCounter; std::vector> _timers; Ichor::IEventQueue* _queue{}; @@ -64,7 +74,7 @@ void Ichor::TimerFactoryFactory::handleDependencyRequest(AlwaysNull(Properties{{"requestingSvcId", Ichor::make_any(evt.originatingService)}})); + _factories.emplace(evt.originatingService, Ichor::GetThreadLocalManager().createServiceManager(Properties{{"requestingSvcId", Ichor::make_any(evt.originatingService)}}, evt.priority)); } void Ichor::TimerFactoryFactory::handleDependencyUndoRequest(AlwaysNull, const DependencyUndoRequestEvent &evt) { @@ -74,8 +84,11 @@ void Ichor::TimerFactoryFactory::handleDependencyUndoRequest(AlwaysNull(getServiceId(), factory->second->getServiceId()); + factory->second->stopAllTimers(); + + // because we manually tell the factory to stop all timers, stopping the factory itself isn't a high priority action anymore. + GetThreadLocalEventQueue().pushEvent(getServiceId(), factory->second->_getServiceId()); // + 11 because the first stop triggers a dep offline event and inserts a new stop with 10 higher priority. - GetThreadLocalEventQueue().pushPrioritisedEvent(getServiceId(), INTERNAL_EVENT_PRIORITY + 11, factory->second->getServiceId()); + GetThreadLocalEventQueue().pushPrioritisedEvent(getServiceId(), INTERNAL_EVENT_PRIORITY + 11, factory->second->_getServiceId()); _factories.erase(factory); } From 42cb4a39b2f08ab963fa5925861b2d460e399861 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Sun, 7 Jan 2024 14:21:22 +0100 Subject: [PATCH 24/98] Refactor boost services into their own directory --- CMakeLists.txt | 8 ++++++-- examples/etcd_example/main.cpp | 4 ++-- examples/http_example/main.cpp | 4 ++-- examples/http_ping_pong/ping.cpp | 4 ++-- examples/http_ping_pong/pong.cpp | 4 ++-- examples/realtime_example/GlobalRealtimeSettings.cpp | 1 + examples/websocket_example/main.cpp | 4 ++-- .../network/{ => boost}/AsioContextService.h | 0 .../network/{http => boost}/HttpConnectionService.h | 6 +----- .../network/{http => boost}/HttpHostService.h | 6 +----- .../network/{ws => boost}/WsConnectionService.h | 6 +----- .../services/network/{ws => boost}/WsHostService.h | 8 ++------ .../ichor/services/network/uv/UvConnectionService.h | 8 -------- include/ichor/services/network/uv/UvHostService.h | 8 -------- .../network/{ => boost}/AsioContextService.cpp | 6 +----- .../{http => boost}/HttpConnectionService.cpp | 6 +----- .../network/{http => boost}/HttpHostService.cpp | 6 +----- .../network/{ws => boost}/WsConnectionService.cpp | 6 +----- src/services/network/{ws => boost}/WsHostService.cpp | 8 ++------ src/services/network/json/BoostJsonSrc.cpp | 12 ------------ src/services/network/uv/UvConnectionService.cpp | 4 ---- src/services/network/uv/UvHostService.cpp | 4 ---- test/EtcdTests.cpp | 4 ++-- test/HttpTests.cpp | 6 +++--- 24 files changed, 33 insertions(+), 100 deletions(-) rename include/ichor/services/network/{ => boost}/AsioContextService.h (100%) rename include/ichor/services/network/{http => boost}/HttpConnectionService.h (96%) rename include/ichor/services/network/{http => boost}/HttpHostService.h (97%) rename include/ichor/services/network/{ws => boost}/WsConnectionService.h (96%) rename include/ichor/services/network/{ws => boost}/WsHostService.h (93%) delete mode 100644 include/ichor/services/network/uv/UvConnectionService.h delete mode 100644 include/ichor/services/network/uv/UvHostService.h rename src/services/network/{ => boost}/AsioContextService.cpp (98%) rename src/services/network/{http => boost}/HttpConnectionService.cpp (99%) rename src/services/network/{http => boost}/HttpHostService.cpp (99%) rename src/services/network/{ws => boost}/WsConnectionService.cpp (99%) rename src/services/network/{ws => boost}/WsHostService.cpp (98%) delete mode 100644 src/services/network/json/BoostJsonSrc.cpp delete mode 100644 src/services/network/uv/UvConnectionService.cpp delete mode 100644 src/services/network/uv/UvHostService.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bd9a8f9..eb47eff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,7 +90,8 @@ set(FMT_SOURCES ${ICHOR_EXTERNAL_DIR}/fmt/src/format.cc ${ICHOR_EXTERNAL_DIR}/fm file(GLOB_RECURSE ICHOR_FRAMEWORK_SOURCES ${ICHOR_TOP_DIR}/src/ichor/*.cpp) file(GLOB_RECURSE ICHOR_OPTIONAL_ETCD_SOURCES ${ICHOR_TOP_DIR}/src/services/etcd/*.cpp) file(GLOB_RECURSE ICHOR_LOGGING_SOURCES ${ICHOR_TOP_DIR}/src/services/logging/*.cpp) -file(GLOB_RECURSE ICHOR_NETWORK_SOURCES ${ICHOR_TOP_DIR}/src/services/network/*.cpp) +file(GLOB_RECURSE ICHOR_TCP_SOURCES ${ICHOR_TOP_DIR}/src/services/network/tcp/*.cpp) +file(GLOB_RECURSE ICHOR_BOOST_BEAST_SOURCES ${ICHOR_TOP_DIR}/src/services/network/boost/*.cpp) file(GLOB_RECURSE ICHOR_METRICS_SOURCES ${ICHOR_TOP_DIR}/src/services/metrics/*.cpp) file(GLOB_RECURSE ICHOR_TIMER_SOURCES ${ICHOR_TOP_DIR}/src/services/timer/*.cpp) file(GLOB_RECURSE ICHOR_OPTIONAL_HIREDIS_SOURCES ${ICHOR_TOP_DIR}/src/services/redis/*.cpp) @@ -99,7 +100,7 @@ file(GLOB_RECURSE ICHOR_BASE64_SOURCES ${ICHOR_TOP_DIR}/src/base64/*.cpp) file(GLOB SPDLOG_SOURCES ${ICHOR_EXTERNAL_DIR}/spdlog/src/*.cpp) -add_library(ichor ${FMT_SOURCES} ${ICHOR_FRAMEWORK_SOURCES} ${ICHOR_LOGGING_SOURCES} ${ICHOR_NETWORK_SOURCES} ${ICHOR_METRICS_SOURCES} ${ICHOR_TIMER_SOURCES} ${ICHOR_IO_SOURCES} ${ICHOR_BASE64_SOURCES}) +add_library(ichor ${FMT_SOURCES} ${ICHOR_FRAMEWORK_SOURCES} ${ICHOR_LOGGING_SOURCES} ${ICHOR_TCP_SOURCES} ${ICHOR_METRICS_SOURCES} ${ICHOR_TIMER_SOURCES} ${ICHOR_IO_SOURCES} ${ICHOR_BASE64_SOURCES}) if(ICHOR_ENABLE_INTERNAL_DEBUGGING) target_compile_definitions(ichor PUBLIC ICHOR_ENABLE_INTERNAL_DEBUGGING) @@ -366,6 +367,9 @@ endif() if(ICHOR_USE_SPDLOG) target_sources(ichor PRIVATE ${SPDLOG_SOURCES}) endif() +if(ICHOR_USE_BOOST_BEAST) + target_sources(ichor PRIVATE ${ICHOR_BOOST_BEAST_SOURCES}) +endif() if(ICHOR_USE_SDEVENT) find_package(PkgConfig REQUIRED) diff --git a/examples/etcd_example/main.cpp b/examples/etcd_example/main.cpp index 69e130c..7934bf3 100644 --- a/examples/etcd_example/main.cpp +++ b/examples/etcd_example/main.cpp @@ -3,8 +3,8 @@ #include #include #include -#include -#include +#include +#include #include // Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. diff --git a/examples/http_example/main.cpp b/examples/http_example/main.cpp index 279bdd3..3efe3d7 100644 --- a/examples/http_example/main.cpp +++ b/examples/http_example/main.cpp @@ -4,8 +4,8 @@ #include #include #include -#include -#include +#include +#include #include #include diff --git a/examples/http_ping_pong/ping.cpp b/examples/http_ping_pong/ping.cpp index 81b9e60..a79fb6c 100644 --- a/examples/http_ping_pong/ping.cpp +++ b/examples/http_ping_pong/ping.cpp @@ -3,8 +3,8 @@ #include "../common/lyra.hpp" #include #include -#include -#include +#include +#include #include #include #include diff --git a/examples/http_ping_pong/pong.cpp b/examples/http_ping_pong/pong.cpp index 8841c25..2493538 100644 --- a/examples/http_ping_pong/pong.cpp +++ b/examples/http_ping_pong/pong.cpp @@ -3,8 +3,8 @@ #include "../common/lyra.hpp" #include #include -#include -#include +#include +#include #include #include diff --git a/examples/realtime_example/GlobalRealtimeSettings.cpp b/examples/realtime_example/GlobalRealtimeSettings.cpp index b6b1720..a8e701d 100644 --- a/examples/realtime_example/GlobalRealtimeSettings.cpp +++ b/examples/realtime_example/GlobalRealtimeSettings.cpp @@ -81,6 +81,7 @@ GlobalRealtimeSettings::GlobalRealtimeSettings() { if(setpriority(PRIO_PROCESS, 0, -20) != 0) { fmt::print("setpriority failed\n"); } + fmt::print("finished settings\n"); #endif } diff --git a/examples/websocket_example/main.cpp b/examples/websocket_example/main.cpp index 5e922e3..8bc6386 100644 --- a/examples/websocket_example/main.cpp +++ b/examples/websocket_example/main.cpp @@ -4,8 +4,8 @@ #include #include #include -#include -#include +#include +#include #include #include diff --git a/include/ichor/services/network/AsioContextService.h b/include/ichor/services/network/boost/AsioContextService.h similarity index 100% rename from include/ichor/services/network/AsioContextService.h rename to include/ichor/services/network/boost/AsioContextService.h diff --git a/include/ichor/services/network/http/HttpConnectionService.h b/include/ichor/services/network/boost/HttpConnectionService.h similarity index 96% rename from include/ichor/services/network/http/HttpConnectionService.h rename to include/ichor/services/network/boost/HttpConnectionService.h index 8146ec7..a360861 100644 --- a/include/ichor/services/network/http/HttpConnectionService.h +++ b/include/ichor/services/network/boost/HttpConnectionService.h @@ -1,9 +1,7 @@ #pragma once -#ifdef ICHOR_USE_BOOST_BEAST - #include -#include +#include #include #include #include @@ -73,5 +71,3 @@ namespace Ichor { IEventQueue *_queue; }; } - -#endif diff --git a/include/ichor/services/network/http/HttpHostService.h b/include/ichor/services/network/boost/HttpHostService.h similarity index 97% rename from include/ichor/services/network/http/HttpHostService.h rename to include/ichor/services/network/boost/HttpHostService.h index 152c4bb..0aa88fc 100644 --- a/include/ichor/services/network/http/HttpHostService.h +++ b/include/ichor/services/network/boost/HttpHostService.h @@ -1,9 +1,7 @@ #pragma once -#ifdef ICHOR_USE_BOOST_BEAST - #include -#include +#include #include #include #include @@ -84,5 +82,3 @@ namespace Ichor { IEventQueue *_queue; }; } - -#endif diff --git a/include/ichor/services/network/ws/WsConnectionService.h b/include/ichor/services/network/boost/WsConnectionService.h similarity index 96% rename from include/ichor/services/network/ws/WsConnectionService.h rename to include/ichor/services/network/boost/WsConnectionService.h index b12d927..29a78a4 100644 --- a/include/ichor/services/network/ws/WsConnectionService.h +++ b/include/ichor/services/network/boost/WsConnectionService.h @@ -1,10 +1,8 @@ #pragma once -#ifdef ICHOR_USE_BOOST_BEAST - #include #include -#include +#include #include #include #include @@ -71,5 +69,3 @@ namespace Ichor { IEventQueue *_queue; }; } - -#endif diff --git a/include/ichor/services/network/ws/WsHostService.h b/include/ichor/services/network/boost/WsHostService.h similarity index 93% rename from include/ichor/services/network/ws/WsHostService.h rename to include/ichor/services/network/boost/WsHostService.h index b6ecdab..71e544c 100644 --- a/include/ichor/services/network/ws/WsHostService.h +++ b/include/ichor/services/network/boost/WsHostService.h @@ -1,12 +1,10 @@ #pragma once -#ifdef ICHOR_USE_BOOST_BEAST - #include #include -#include +#include +#include #include -#include #include #include #include @@ -57,5 +55,3 @@ namespace Ichor { IEventQueue *_queue; }; } - -#endif diff --git a/include/ichor/services/network/uv/UvConnectionService.h b/include/ichor/services/network/uv/UvConnectionService.h deleted file mode 100644 index 1b7beb7..0000000 --- a/include/ichor/services/network/uv/UvConnectionService.h +++ /dev/null @@ -1,8 +0,0 @@ -// -// Created by oipo on 07-08-20. -// - -#ifndef CPPELIX_UVCONNECTIONSERVICE_H -#define CPPELIX_UVCONNECTIONSERVICE_H - -#endif //CPPELIX_UVCONNECTIONSERVICE_H diff --git a/include/ichor/services/network/uv/UvHostService.h b/include/ichor/services/network/uv/UvHostService.h deleted file mode 100644 index 259d9f2..0000000 --- a/include/ichor/services/network/uv/UvHostService.h +++ /dev/null @@ -1,8 +0,0 @@ -// -// Created by oipo on 07-08-20. -// - -#ifndef CPPELIX_UVHOSTSERVICE_H -#define CPPELIX_UVHOSTSERVICE_H - -#endif //CPPELIX_UVHOSTSERVICE_H diff --git a/src/services/network/AsioContextService.cpp b/src/services/network/boost/AsioContextService.cpp similarity index 98% rename from src/services/network/AsioContextService.cpp rename to src/services/network/boost/AsioContextService.cpp index 38a2e2d..cb37aa2 100644 --- a/src/services/network/AsioContextService.cpp +++ b/src/services/network/boost/AsioContextService.cpp @@ -1,7 +1,5 @@ -#ifdef ICHOR_USE_BOOST_BEAST - #include -#include +#include #include #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32)) && !defined(__CYGWIN__) #include @@ -135,5 +133,3 @@ bool Ichor::AsioContextService::fibersShouldStop() const noexcept { uint64_t Ichor::AsioContextService::threadCount() const noexcept { return _threads; } - -#endif diff --git a/src/services/network/http/HttpConnectionService.cpp b/src/services/network/boost/HttpConnectionService.cpp similarity index 99% rename from src/services/network/http/HttpConnectionService.cpp rename to src/services/network/boost/HttpConnectionService.cpp index 78e5db4..b454b08 100644 --- a/src/services/network/http/HttpConnectionService.cpp +++ b/src/services/network/boost/HttpConnectionService.cpp @@ -1,8 +1,6 @@ -#ifdef ICHOR_USE_BOOST_BEAST - #include #include -#include +#include #include #include #include @@ -370,5 +368,3 @@ void Ichor::HttpConnectionService::connect(tcp::endpoint endpoint, net::yield_co _connected.store(true, std::memory_order_release); _connecting.store(false, std::memory_order_release); } - -#endif diff --git a/src/services/network/http/HttpHostService.cpp b/src/services/network/boost/HttpHostService.cpp similarity index 99% rename from src/services/network/http/HttpHostService.cpp rename to src/services/network/boost/HttpHostService.cpp index 4b66c23..65c16bf 100644 --- a/src/services/network/http/HttpHostService.cpp +++ b/src/services/network/boost/HttpHostService.cpp @@ -1,7 +1,5 @@ -#ifdef ICHOR_USE_BOOST_BEAST - #include -#include +#include #include #include @@ -436,5 +434,3 @@ void Ichor::HttpHostService::sendInternal(std::shared_ptr -#include +#include #include #include #include @@ -373,5 +371,3 @@ void Ichor::WsConnectionService::read(net::yield_context &yield) { INTERNAL_DEBUG("read stopped WsConnectionService {}", getServiceId()); // fmt::print("{}:{} read done\n", getServiceId(), getServiceName()); } - -#endif diff --git a/src/services/network/ws/WsHostService.cpp b/src/services/network/boost/WsHostService.cpp similarity index 98% rename from src/services/network/ws/WsHostService.cpp rename to src/services/network/boost/WsHostService.cpp index 8779c76..e1df1b2 100644 --- a/src/services/network/ws/WsHostService.cpp +++ b/src/services/network/boost/WsHostService.cpp @@ -1,9 +1,7 @@ -#ifdef ICHOR_USE_BOOST_BEAST - #include #include -#include -#include +#include +#include #include #include #include @@ -204,5 +202,3 @@ void Ichor::WsHostService::listen(tcp::endpoint endpoint, net::yield_context yie _startStopEvent.set(); }); } - -#endif diff --git a/src/services/network/json/BoostJsonSrc.cpp b/src/services/network/json/BoostJsonSrc.cpp deleted file mode 100644 index 8fbba15..0000000 --- a/src/services/network/json/BoostJsonSrc.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#ifdef ICHOR_USE_BOOST_JSON -#pragma GCC diagnostic push -#ifndef __clang__ -#pragma GCC diagnostic ignored "-Wduplicated-branches" -#pragma GCC diagnostic ignored "-Wstringop-overflow" -#endif -#pragma GCC diagnostic ignored "-Wcast-align" -#pragma GCC diagnostic ignored "-Wdeprecated-copy" -#pragma GCC diagnostic ignored "-Warray-bounds" -#include -#pragma GCC diagnostic pop -#endif diff --git a/src/services/network/uv/UvConnectionService.cpp b/src/services/network/uv/UvConnectionService.cpp deleted file mode 100644 index 568434b..0000000 --- a/src/services/network/uv/UvConnectionService.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// -// Created by oipo on 07-08-20. -// - diff --git a/src/services/network/uv/UvHostService.cpp b/src/services/network/uv/UvHostService.cpp deleted file mode 100644 index 568434b..0000000 --- a/src/services/network/uv/UvHostService.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// -// Created by oipo on 07-08-20. -// - diff --git a/test/EtcdTests.cpp b/test/EtcdTests.cpp index 40873c7..79cad6b 100644 --- a/test/EtcdTests.cpp +++ b/test/EtcdTests.cpp @@ -4,9 +4,9 @@ #include #include #include -#include +#include +#include #include -#include #include #include "TestServices/EtcdUsingService.h" #include "Common.h" diff --git a/test/HttpTests.cpp b/test/HttpTests.cpp index c7236ff..1f5cca9 100644 --- a/test/HttpTests.cpp +++ b/test/HttpTests.cpp @@ -4,9 +4,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include #include From 56923a8986139a74b8365d7aa18b447f0d45a6cb Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Mon, 8 Jan 2024 13:52:52 +0100 Subject: [PATCH 25/98] Improve performance for starting services with dependencies and interfaces --- include/ichor/ConstevalHash.h | 4 +- include/ichor/DependencyManager.h | 4 +- .../DependencyLifecycleManager.h | 69 ++++++++----- .../DependencyManagerLifecycleManager.h | 15 +-- .../dependency_management/ILifecycleManager.h | 5 +- .../IServiceInterestedLifecycleManager.h | 16 +-- .../dependency_management/LifecycleManager.h | 21 ++-- .../QueueLifecycleManager.h | 15 +-- src/ichor/DependencyManager.cpp | 99 ++++++++++--------- 9 files changed, 147 insertions(+), 101 deletions(-) diff --git a/include/ichor/ConstevalHash.h b/include/ichor/ConstevalHash.h index d714126..f51214f 100644 --- a/include/ichor/ConstevalHash.h +++ b/include/ichor/ConstevalHash.h @@ -122,6 +122,8 @@ static consteval uint64_t consteval_wyhash(const T *key, uint64_t len, uint64_t } namespace Ichor { + using NameHashType = uint64_t; + template [[nodiscard]] consteval auto typeName() { #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) @@ -139,7 +141,7 @@ namespace Ichor { } template - [[nodiscard]] consteval auto typeNameHash() { + [[nodiscard]] consteval NameHashType typeNameHash() { std::string_view name = typeName(); return consteval_wyhash(name.data(), name.size(), 0); } diff --git a/include/ichor/DependencyManager.h b/include/ichor/DependencyManager.h index 530dde5..d36592b 100644 --- a/include/ichor/DependencyManager.h +++ b/include/ichor/DependencyManager.h @@ -689,8 +689,8 @@ namespace Ichor { bool finishWaitingService(ServiceIdType serviceId, uint64_t eventType, [[maybe_unused]] std::string_view eventName) noexcept; unordered_map> _services{}; // key = service id - unordered_map> _dependencyRequestTrackers{}; // key = interface name hash - unordered_map> _dependencyUndoRequestTrackers{}; // key = interface name hash + unordered_map> _dependencyRequestTrackers{}; // key = interface name hash + unordered_map> _dependencyUndoRequestTrackers{}; // key = interface name hash unordered_map> _completionCallbacks{}; // key = listening service id + event type unordered_map> _errorCallbacks{}; // key = listening service id + event type unordered_map> _eventCallbacks{}; // key = event id diff --git a/include/ichor/dependency_management/DependencyLifecycleManager.h b/include/ichor/dependency_management/DependencyLifecycleManager.h index 515a677..8d86c5e 100644 --- a/include/ichor/dependency_management/DependencyLifecycleManager.h +++ b/include/ichor/dependency_management/DependencyLifecycleManager.h @@ -32,19 +32,20 @@ namespace Ichor::Detail { return std::make_unique>(std::move(interfaces), std::move(properties)); } - std::vector interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { - if((online && _serviceIdsOfInjectedDependencies.contains(dependentService->serviceId())) || (!online && !_serviceIdsOfInjectedDependencies.contains(dependentService->serviceId()))) { - return {}; - } - + std::vector interestedInDependencyGoingOffline(ILifecycleManager *dependentService) noexcept final { std::vector ret; + if(!_serviceIdsOfInjectedDependencies.contains(dependentService->serviceId())) { + fmt::print("interestedInDependencyGoingOffline() svc {}:{} already injected\n", serviceId(), implementationName()); + return ret; + } + for(auto const &interface : dependentService->getInterfaces()) { - auto dep = _dependencies.find(interface, !online); - INTERNAL_DEBUG("interestedInDependency() svc {}:{} {} dependent {}:{}", serviceId(), implementationName(), getServiceState(), dependentService->serviceId(), dependentService->implementationName()); + auto dep = _dependencies.find(interface, true); + INTERNAL_DEBUG("interestedInDependencyGoingOffline() svc {}:{} {} dependent {}:{}", serviceId(), implementationName(), getServiceState(), dependentService->serviceId(), dependentService->implementationName()); - if (dep == _dependencies.end()) { - INTERNAL_DEBUG("interestedInDependency() not found"); + if(dep == _dependencies.end()) { + INTERNAL_DEBUG("interestedInDependencyGoingOffline() not found"); continue; } @@ -54,8 +55,14 @@ namespace Ichor::Detail { return ret; } - AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) final { + StartBehaviour dependencyOnline(NeverNull dependentService) final { INTERNAL_DEBUG("dependencyOnline() svc {}:{} {} dependent {}:{}", serviceId(), implementationName(), getServiceState(), dependentService->serviceId(), dependentService->implementationName()); + + if(_serviceIdsOfInjectedDependencies.contains(dependentService->serviceId())) { + fmt::print("dependencyOnline() svc {}:{} already injected\n", serviceId(), implementationName()); + return StartBehaviour::DONE; + } + if constexpr (DO_INTERNAL_DEBUG) { for (auto id: _serviceIdsOfInjectedDependencies) { INTERNAL_DEBUG("dependency: {}", id); @@ -67,7 +74,13 @@ namespace Ichor::Detail { auto interested = DependencyChange::NOT_FOUND; - for(auto *dep : deps) { + for(auto const &interface : dependentService->getInterfaces()) { + auto dep = _dependencies.find(interface, false); + + if(dep == _dependencies.end()) { + continue; + } + INTERNAL_DEBUG("dependencyOnline() dep {} {} {}", dep->interfaceName, dep->satisfied, dep->flags); if(dep->satisfied == 0) { interested = DependencyChange::FOUND; @@ -79,25 +92,20 @@ namespace Ichor::Detail { } if(interested == DependencyChange::FOUND && getServiceState() <= ServiceState::INSTALLED && _dependencies.allSatisfied()) { - StartBehaviour ret = co_await _service.internal_start(nullptr); // we already checked the dependencies, pass in nullptr; - - if(ret == StartBehaviour::STOPPED) { - co_return StartBehaviour::STOPPED; - } - -#ifdef ICHOR_USE_HARDENING - if(getServiceState() != ServiceState::INJECTING) [[unlikely]] { - std::terminate(); - } -#endif - co_return StartBehaviour::STARTED; + return StartBehaviour::STARTED; } - co_return StartBehaviour::DONE; + return StartBehaviour::DONE; } AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) final { INTERNAL_DEBUG("dependencyOffline() svc {}:{} {} dependent {}:{}", serviceId(), implementationName(), getServiceState(), dependentService->serviceId(), dependentService->implementationName()); + + if(!_serviceIdsOfInjectedDependencies.contains(dependentService->serviceId())) { + fmt::print("dependencyOffline() svc {}:{} not injected\n", serviceId(), implementationName()); + co_return StartBehaviour::DONE; + } + auto interested = DependencyChange::NOT_FOUND; StartBehaviour ret = StartBehaviour::DONE; @@ -110,7 +118,7 @@ namespace Ichor::Detail { } } - for(auto &dep : deps) { + for(auto *dep : deps) { // dependency should not be marked as unsatisfied if there is at least one other of the same type present dep->satisfied--; _serviceIdsOfInjectedDependencies.erase(dependentService->serviceId()); @@ -267,6 +275,17 @@ namespace Ichor::Detail { return _serviceIdsOfDependees; } + [[nodiscard]] + AsyncGenerator startAfterDependencyOnline() final { + auto startBehaviour = co_await _service.internal_start(nullptr); + + if(startBehaviour == StartBehaviour::DONE) { + co_return StartBehaviour::STARTED; + } + + co_return startBehaviour; + } + [[nodiscard]] AsyncGenerator start() final { co_return co_await _service.internal_start(&_dependencies); diff --git a/include/ichor/dependency_management/DependencyManagerLifecycleManager.h b/include/ichor/dependency_management/DependencyManagerLifecycleManager.h index daf42e2..d05048e 100644 --- a/include/ichor/dependency_management/DependencyManagerLifecycleManager.h +++ b/include/ichor/dependency_management/DependencyManagerLifecycleManager.h @@ -13,20 +13,17 @@ namespace Ichor::Detail { ~DependencyManagerLifecycleManager() final = default; - std::vector interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { + std::vector interestedInDependencyGoingOffline(ILifecycleManager *dependentService) noexcept final { return {}; } - AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) final { - // this function should never be called - std::terminate(); - co_return StartBehaviour::DONE; + StartBehaviour dependencyOnline(NeverNull dependentService) final { + return StartBehaviour::DONE; } AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) final { // this function should never be called std::terminate(); - co_return StartBehaviour::DONE; } [[nodiscard]] @@ -39,6 +36,12 @@ namespace Ichor::Detail { return _serviceIdsOfDependees; } + [[nodiscard]] + AsyncGenerator startAfterDependencyOnline() final { + // this function should never be called + std::terminate(); + } + [[nodiscard]] AsyncGenerator start() final { co_return {}; diff --git a/include/ichor/dependency_management/ILifecycleManager.h b/include/ichor/dependency_management/ILifecycleManager.h index 78565b1..2f9d715 100644 --- a/include/ichor/dependency_management/ILifecycleManager.h +++ b/include/ichor/dependency_management/ILifecycleManager.h @@ -12,12 +12,13 @@ namespace Ichor { class ILifecycleManager { public: virtual ~ILifecycleManager() = default; - virtual std::vector interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept = 0; + virtual std::vector interestedInDependencyGoingOffline(ILifecycleManager *dependentService) noexcept = 0; + virtual StartBehaviour dependencyOnline(NeverNull dependentService) = 0; // iterators come from interestedInDependency() and have to be moved as using coroutines might end up clearing it. - virtual AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) = 0; virtual AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) = 0; [[nodiscard]] virtual unordered_set &getDependencies() noexcept = 0; [[nodiscard]] virtual unordered_set &getDependees() noexcept = 0; + [[nodiscard]] virtual AsyncGenerator startAfterDependencyOnline() = 0; [[nodiscard]] virtual AsyncGenerator start() = 0; [[nodiscard]] virtual AsyncGenerator stop() = 0; [[nodiscard]] virtual bool setInjected() = 0; diff --git a/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h b/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h index 8709df3..fb5ab9c 100644 --- a/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h +++ b/include/ichor/dependency_management/IServiceInterestedLifecycleManager.h @@ -11,14 +11,12 @@ namespace Ichor::Detail { } ~IServiceInterestedLifecycleManager() final = default; - std::vector interestedInDependency(ILifecycleManager *, bool) noexcept final { - // this function should never be called - std::terminate(); + std::vector interestedInDependencyGoingOffline(ILifecycleManager *dependentService) noexcept final { + return {}; } - AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) final { - // this function should never be called - std::terminate(); + StartBehaviour dependencyOnline(NeverNull dependentService) final { + return StartBehaviour::DONE; } AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) final { @@ -38,6 +36,12 @@ namespace Ichor::Detail { std::terminate(); } + [[nodiscard]] + AsyncGenerator startAfterDependencyOnline() final { + // this function should never be called + std::terminate(); + } + [[nodiscard]] AsyncGenerator start() final { // this function should never be called diff --git a/include/ichor/dependency_management/LifecycleManager.h b/include/ichor/dependency_management/LifecycleManager.h index 57ed36a..ec5e961 100644 --- a/include/ichor/dependency_management/LifecycleManager.h +++ b/include/ichor/dependency_management/LifecycleManager.h @@ -33,21 +33,17 @@ namespace Ichor::Detail { return std::make_unique>(std::move(interfaces), std::forward(properties)); } - - std::vector interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { + std::vector interestedInDependencyGoingOffline(ILifecycleManager *dependentService) noexcept final { return {}; } - AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) final { - // this function should never be called - std::terminate(); - co_return StartBehaviour::DONE; + StartBehaviour dependencyOnline(NeverNull dependentService) final { + return StartBehaviour::DONE; } AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) final { // this function should never be called std::terminate(); - co_return StartBehaviour::DONE; } [[nodiscard]] @@ -60,6 +56,17 @@ namespace Ichor::Detail { return _serviceIdsOfDependees; } + [[nodiscard]] + AsyncGenerator startAfterDependencyOnline() final { + auto startBehaviour = co_await _service.internal_start(nullptr); + + if(startBehaviour == StartBehaviour::DONE) { + co_return StartBehaviour::STARTED; + } + + co_return startBehaviour; + } + [[nodiscard]] AsyncGenerator start() final { co_return co_await _service.internal_start(nullptr); diff --git a/include/ichor/dependency_management/QueueLifecycleManager.h b/include/ichor/dependency_management/QueueLifecycleManager.h index ddc6c83..5a42cb1 100644 --- a/include/ichor/dependency_management/QueueLifecycleManager.h +++ b/include/ichor/dependency_management/QueueLifecycleManager.h @@ -14,20 +14,17 @@ namespace Ichor::Detail { ~QueueLifecycleManager() final = default; - std::vector interestedInDependency(ILifecycleManager *dependentService, bool online) noexcept final { + std::vector interestedInDependencyGoingOffline(ILifecycleManager *dependentService) noexcept final { return {}; } - AsyncGenerator dependencyOnline(NeverNull dependentService, std::vector deps) final { - // this function should never be called - std::terminate(); - co_return StartBehaviour::DONE; + StartBehaviour dependencyOnline(NeverNull dependentService) final { + return StartBehaviour::DONE; } AsyncGenerator dependencyOffline(NeverNull dependentService, std::vector deps) final { // this function should never be called std::terminate(); - co_return StartBehaviour::DONE; } [[nodiscard]] @@ -40,6 +37,12 @@ namespace Ichor::Detail { return _serviceIdsOfDependees; } + [[nodiscard]] + AsyncGenerator startAfterDependencyOnline() final { + // this function should never be called + std::terminate(); + } + [[nodiscard]] AsyncGenerator start() final { co_return {}; diff --git a/src/ichor/DependencyManager.cpp b/src/ichor/DependencyManager.cpp index dc285d5..7ae0806 100644 --- a/src/ichor/DependencyManager.cpp +++ b/src/ichor/DependencyManager.cpp @@ -120,41 +120,46 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) finishWaitingService(depOnlineEvt->originatingService, DependencyOnlineEvent::TYPE, DependencyOnlineEvent::NAME); - auto const filterProp = manager->getProperties().find("Filter"); - const Filter *filter = nullptr; - if (filterProp != cend(manager->getProperties())) { - filter = Ichor::any_cast(&filterProp->second); - } + if(!manager->getInterfaces().empty()) { + auto const filterProp = manager->getProperties().find("Filter"); + const Filter *filter = nullptr; + if (filterProp != cend(manager->getProperties())) { + filter = Ichor::any_cast(&filterProp->second); + } - for (auto const &[serviceId, possibleDependentLifecycleManager] : _services) { - auto depIts = possibleDependentLifecycleManager->interestedInDependency(manager.get(), true); + for (auto const &[serviceId, possibleDependentLifecycleManager] : _services) { + if (serviceId == depOnlineEvt->originatingService || (filter != nullptr && !filter->compareTo(*possibleDependentLifecycleManager))) { + continue; + } - if(depIts.empty()) { - continue; - } + auto startBehaviour = possibleDependentLifecycleManager->dependencyOnline(manager.get()); - if (serviceId == depOnlineEvt->originatingService || (filter != nullptr && !filter->compareTo(*possibleDependentLifecycleManager))) { - continue; - } + INTERNAL_DEBUG("DependencyOnlineEvent {} interested service is {} startBehaviour {}", evt->id, serviceId, startBehaviour); - auto gen = possibleDependentLifecycleManager->dependencyOnline(manager.get(), std::move(depIts)); - auto it = gen.begin(); + if(startBehaviour == StartBehaviour::DONE) { + continue; + } - INTERNAL_DEBUG("DependencyOnlineEvent {} interested service is {} {} {}", evt->id, serviceId, it.get_promise_id(), it.get_finished()); + auto gen = possibleDependentLifecycleManager->startAfterDependencyOnline(); + auto it = gen.begin(); - if(!it.get_finished()) { - if constexpr (DO_INTERNAL_DEBUG) { - if (!it.get_has_suspended()) [[unlikely]] { - std::terminate(); + INTERNAL_DEBUG("DependencyOnlineEvent {} interested service is {} {} {}", evt->id, serviceId, it.get_promise_id(), it.get_finished()); + + if(!it.get_finished()) { + if constexpr (DO_INTERNAL_DEBUG) { + if (!it.get_has_suspended()) [[unlikely]] { + std::terminate(); + } } + _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); + // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent + _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), serviceId, std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority))); + } else if(it.get_value() == StartBehaviour::STARTED) { + _eventQueue->pushPrioritisedEvent(serviceId, std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority)); } - _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); - // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent - _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), serviceId, std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority))); - } else if(it.get_value() == StartBehaviour::STARTED) { - _eventQueue->pushPrioritisedEvent(serviceId, std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority)); } } + handleEventCompletion(*depOnlineEvt); } break; @@ -189,7 +194,7 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) } } - auto depIts = depIt->second->interestedInDependency(manager.get(), false); + auto depIts = depIt->second->interestedInDependencyGoingOffline(manager.get()); if(depIts.empty()) { continue; @@ -326,30 +331,26 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) // If a service requests IService, we interpret it to mean a reference to itself, not just all services in existence. Detail::IServiceInterestedLifecycleManager selfMgr{cmpMgr->getIService()}; - auto selfDepIts = cmpMgr->interestedInDependency(&selfMgr, true); - - if(!selfDepIts.empty()) { - auto gen = cmpMgr->dependencyOnline(&selfMgr, std::move(selfDepIts)); - auto it = gen.begin(); - - if(!it.get_finished()) { - _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); - // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent - _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), cmpMgr->serviceId(), std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority))); - } else if(it.get_value() == StartBehaviour::STARTED) { - _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority)); + { + auto startBehaviour = cmpMgr->dependencyOnline(&selfMgr); + + if(startBehaviour == StartBehaviour::STARTED) { + auto gen = cmpMgr->startAfterDependencyOnline(); + auto it = gen.begin(); + + if(!it.get_finished()) { + _scopedGenerators.emplace(it.get_promise_id(), std::make_unique>(std::move(gen))); + // create new event that will be inserted upon finish of coroutine in ContinuableStartEvent + _scopedEvents.emplace(it.get_promise_id(), Ichor::make_reference_counted(_eventQueue->getNextEventId(), cmpMgr->serviceId(), std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority))); + } else if(it.get_value() == StartBehaviour::STARTED) { + _eventQueue->pushPrioritisedEvent(cmpMgr->serviceId(), std::min(INTERNAL_DEPENDENCY_EVENT_PRIORITY, evt->priority)); + } } } // loop over all services, check if cmpMgr is interested in the active ones and inject them if so for (auto &[key, mgr] : _services) { - if (mgr->getServiceState() != ServiceState::ACTIVE) { - continue; - } - - auto depIts = cmpMgr->interestedInDependency(mgr.get(), true); - - if(depIts.empty()) { + if (mgr->getServiceState() != ServiceState::ACTIVE || mgr->getInterfaces().empty()) { continue; } @@ -363,7 +364,13 @@ void Ichor::DependencyManager::processEvent(std::unique_ptr &&uniqueEvt) continue; } - auto gen = cmpMgr->dependencyOnline(mgr.get(), std::move(depIts)); + auto startBehaviour = cmpMgr->dependencyOnline(mgr.get()); + + if(startBehaviour == StartBehaviour::DONE) { + continue; + } + + auto gen = cmpMgr->startAfterDependencyOnline(); auto it = gen.begin(); if(!it.get_finished()) { From 8f2fe055bbea2f95f4914385ff83d2190e2b1204 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Mon, 8 Jan 2024 13:53:13 +0100 Subject: [PATCH 26/98] Fix nullptr issue --- test/DependencyManagerTests.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/DependencyManagerTests.cpp b/test/DependencyManagerTests.cpp index a5402c0..480a656 100644 --- a/test/DependencyManagerTests.cpp +++ b/test/DependencyManagerTests.cpp @@ -11,6 +11,11 @@ class ScopeFilter final { explicit ScopeFilter(std::string _scope) : scope(std::move(_scope)) {} [[nodiscard]] bool matches(ILifecycleManager const &manager) const noexcept { + if(manager.getDependencyRegistry() == nullptr) { + fmt::print("ScopeFilter does not match, missing registry {}:{}\n", manager.serviceId(), manager.implementationName()); + return false; + } + for(auto const &[interfaceHash, depTuple] : manager.getDependencyRegistry()->_registrations) { if(interfaceHash != typeNameHash()) { continue; From 4ec8acda99db15cb7429be95efedc505917fdc39 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Mon, 8 Jan 2024 13:53:25 +0100 Subject: [PATCH 27/98] Fix clang-tidy warning --- src/services/network/tcp/TcpConnectionService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/network/tcp/TcpConnectionService.cpp b/src/services/network/tcp/TcpConnectionService.cpp index bba92d3..218d295 100644 --- a/src/services/network/tcp/TcpConnectionService.cpp +++ b/src/services/network/tcp/TcpConnectionService.cpp @@ -63,7 +63,7 @@ Ichor::Task> Ichor::TcpConnectionService:: } } - auto ip = ::inet_ntoa(address.sin_addr); + auto *ip = ::inet_ntoa(address.sin_addr); ICHOR_LOG_TRACE(_logger, "Starting TCP connection for {}:{}", ip, ::ntohs(address.sin_port)); } From afc83dfff44e5e537f7a12f1f65434fd625bb477 Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Mon, 8 Jan 2024 14:57:59 +0100 Subject: [PATCH 28/98] Update benchmarks README.md --- benchmarks/README.md | 131 ++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/benchmarks/README.md b/benchmarks/README.md index 20e6bc2..3b314aa 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -6,76 +6,77 @@ Listed numbers are for the multi-threaded runs where contention is likely. | Compile
Options | coroutines | events | start | start & stop | |-------------------------------------------|:--------------------:|------------------------:|------------------------:|---------------------:| -| std containers
std alloc | 976,216 µs
6MB | 1,942,373 µs
5765MB | 20,156,161 µs
572MB | 2,563,558 µs
6MB | -| std containers
mimalloc | 894,269 µs
7MB | 833,478 µs
4334MB | 13,795,744 µs
479MB | 1,774,788 µs
7MB | -| std containers
mimalloc, no hardening | 868,806 µs
7MB | 802,718 µs
4360MB | 13,731,578 µs
532MB | 1,863,568 µs
7MB | -| absl containers
std alloc | 1,425,984 µs
8MB | 1,404,642 µs
3726MB | 11,868,876 µs
466MB | 2,596,978 µs
8MB | -| absl containers
mimalloc | 940,348 µs
8MB | 875,581 µs
3255MB | 7,598,533 µs
509MB | 2,279,161 µs
8MB | +| std containers
std alloc | 1,074,712 µs
6MB | 1,770,993 µs
5765MB | 25,772,273 µs
572MB | 2,078,400 µs
6MB | +| std containers
mimalloc | 787,779 µs
7MB | 808,082 µs
4334MB | 23,768,086 µs
479MB | 1,652,661 µs
7MB | +| std containers
mimalloc, no hardening | 753,784 µs
7MB | 816,374 µs
4360MB | 23,924,781 µs
532MB | 1,646,869 µs
7MB | +| absl containers
std alloc | 1,232,790 µs
8MB | 1,184,407 µs
3726MB | 21,649,222 µs
466MB | 2,333,392 µs
8MB | +| absl containers
mimalloc | 1,059,809 µs
8MB | 830,994 µs
3255MB | 21,477,940 µs
509MB | 2,090,964 µs
8MB | The most interesting observation here is that disabling hardening does not bring any performance gains larger than run-to-run variance. Detailed data: ```text -../std_std/ichor_coroutine_benchmark single threaded ran for 526,229 µs with 5,767,168 peak memory usage 9,501,566 coroutines/s -../std_std/ichor_coroutine_benchmark multi threaded ran for 976,216 µs with 6,029,312 peak memory usage 40,974,538 coroutines/s -../std_std/ichor_event_benchmark single threaded ran for 1,133,371 µs with 646,184,960 peak memory usage 4,411,618 events/s -../std_std/ichor_event_benchmark multi threaded ran for 1,942,373 µs with 5,764,808,704 peak memory usage 20,593,366 events/s -../std_std/ichor_serializer_benchmark single threaded glaze ran for 201,452 µs with 6,029,312 peak memory usage 769 MB/s -../std_std/ichor_serializer_benchmark multi threaded glaze ran for 240,475 µs with 6,291,456 peak memory usage 5,156 MB/s -../std_std/ichor_start_benchmark single threaded advanced injection ran for 5,116,410 µs with 47,321,088 peak memory usage -../std_std/ichor_start_benchmark multi threaded advanced injection ran for 12,272,126 µs with 357,158,912 peak memory usage -../std_std/ichor_start_benchmark single threaded constructor injection ran for 7,323,145 µs with 357,158,912 peak memory usage -../std_std/ichor_start_benchmark multi threaded constructor injection ran for 20,156,161 µs with 572,489,728 peak memory usage -../std_std/ichor_start_stop_benchmark single threaded ran for 1,424,174 µs with 5,767,168 peak memory usage 702,161 start & stop /s -../std_std/ichor_start_stop_benchmark multi threaded ran for 2,563,558 µs with 6,029,312 peak memory usage 3,120,662 start & stop /s -../std_mimalloc/ichor_coroutine_benchmark single threaded ran for 410,605 µs with 6,815,744 peak memory usage 12,177,153 coroutines/s -../std_mimalloc/ichor_coroutine_benchmark multi threaded ran for 894,269 µs with 6,815,744 peak memory usage 44,729,270 coroutines/s -../std_mimalloc/ichor_event_benchmark single threaded ran for 629,212 µs with 568,590,336 peak memory usage 7,946,447 events/s -../std_mimalloc/ichor_event_benchmark multi threaded ran for 833,478 µs with 4,334,026,752 peak memory usage 47,991,668 events/s -../std_mimalloc/ichor_serializer_benchmark single threaded glaze ran for 180,443 µs with 6,815,744 peak memory usage 858 MB/s -../std_mimalloc/ichor_serializer_benchmark multi threaded glaze ran for 212,152 µs with 6,815,744 peak memory usage 5,844 MB/s -../std_mimalloc/ichor_start_benchmark single threaded advanced injection ran for 3,578,056 µs with 46,907,392 peak memory usage -../std_mimalloc/ichor_start_benchmark multi threaded advanced injection ran for 8,041,827 µs with 367,214,592 peak memory usage -../std_mimalloc/ichor_start_benchmark single threaded constructor injection ran for 5,215,533 µs with 367,214,592 peak memory usage -../std_mimalloc/ichor_start_benchmark multi threaded constructor injection ran for 13,795,744 µs with 478,502,912 peak memory usage -../std_mimalloc/ichor_start_stop_benchmark single threaded ran for 1,107,564 µs with 6,815,744 peak memory usage 902,882 start & stop /s -../std_mimalloc/ichor_start_stop_benchmark multi threaded ran for 1,774,788 µs with 6,815,744 peak memory usage 4,507,580 start & stop /s -../std_no_hardening/ichor_coroutine_benchmark single threaded ran for 388,710 µs with 6,815,744 peak memory usage 12,863,059 coroutines/s -../std_no_hardening/ichor_coroutine_benchmark multi threaded ran for 868,806 µs with 6,815,744 peak memory usage 46,040,197 coroutines/s -../std_no_hardening/ichor_event_benchmark single threaded ran for 610,706 µs with 568,328,192 peak memory usage 8,187,245 events/s -../std_no_hardening/ichor_event_benchmark multi threaded ran for 802,718 µs with 4,360,241,152 peak memory usage 49,830,700 events/s -../std_no_hardening/ichor_serializer_benchmark single threaded glaze ran for 158,830 µs with 6,553,600 peak memory usage 975 MB/s -../std_no_hardening/ichor_serializer_benchmark multi threaded glaze ran for 178,391 µs with 6,815,744 peak memory usage 6,951 MB/s -../std_no_hardening/ichor_start_benchmark single threaded advanced injection ran for 3,850,709 µs with 45,064,192 peak memory usage -../std_no_hardening/ichor_start_benchmark multi threaded advanced injection ran for 8,232,102 µs with 346,742,784 peak memory usage -../std_no_hardening/ichor_start_benchmark single threaded constructor injection ran for 5,432,727 µs with 346,742,784 peak memory usage -../std_no_hardening/ichor_start_benchmark multi threaded constructor injection ran for 13,731,578 µs with 531,845,120 peak memory usage -../std_no_hardening/ichor_start_stop_benchmark single threaded ran for 1,042,270 µs with 6,553,600 peak memory usage 959,444 start & stop /s -../std_no_hardening/ichor_start_stop_benchmark multi threaded ran for 1,863,568 µs with 6,815,744 peak memory usage 4,292,840 start & stop /s -../absl_std/ichor_coroutine_benchmark single threaded ran for 568,563 µs with 7,077,888 peak memory usage 8,794,100 coroutines/s -../absl_std/ichor_coroutine_benchmark multi threaded ran for 1,425,984 µs with 7,602,176 peak memory usage 28,050,805 coroutines/s -../absl_std/ichor_event_benchmark single threaded ran for 584,022 µs with 420,478,976 peak memory usage 8,561,321 events/s -../absl_std/ichor_event_benchmark multi threaded ran for 1,404,642 µs with 3,726,376,960 peak memory usage 28,477,006 events/s -../absl_std/ichor_serializer_benchmark single threaded glaze ran for 206,556 µs with 7,340,032 peak memory usage 750 MB/s -../absl_std/ichor_serializer_benchmark multi threaded glaze ran for 213,755 µs with 7,864,320 peak memory usage 5,801 MB/s -../absl_std/ichor_start_benchmark single threaded advanced injection ran for 5,491,052 µs with 40,632,320 peak memory usage -../absl_std/ichor_start_benchmark multi threaded advanced injection ran for 7,553,260 µs with 307,494,912 peak memory usage -../absl_std/ichor_start_benchmark single threaded constructor injection ran for 7,460,216 µs with 307,494,912 peak memory usage -../absl_std/ichor_start_benchmark multi threaded constructor injection ran for 11,868,876 µs with 466,481,152 peak memory usage -../absl_std/ichor_start_stop_benchmark single threaded ran for 1,442,448 µs with 7,340,032 peak memory usage 693,265 start & stop /s -../absl_std/ichor_start_stop_benchmark multi threaded ran for 2,596,978 µs with 7,602,176 peak memory usage 3,080,503 start & stop /s -../absl_mimalloc/ichor_coroutine_benchmark single threaded ran for 434,036 µs with 7,864,320 peak memory usage 11,519,781 coroutines/s -../absl_mimalloc/ichor_coroutine_benchmark multi threaded ran for 940,348 µs with 7,864,320 peak memory usage 42,537,443 coroutines/s -../absl_mimalloc/ichor_event_benchmark single threaded ran for 509,016 µs with 417,333,248 peak memory usage 9,822,873 events/s -../absl_mimalloc/ichor_event_benchmark multi threaded ran for 875,581 µs with 3,255,042,048 peak memory usage 45,683,951 events/s -../absl_mimalloc/ichor_serializer_benchmark single threaded glaze ran for 186,982 µs with 7,864,320 peak memory usage 828 MB/s -../absl_mimalloc/ichor_serializer_benchmark multi threaded glaze ran for 212,994 µs with 7,864,320 peak memory usage 5,821 MB/s -../absl_mimalloc/ichor_start_benchmark single threaded advanced injection ran for 4,480,755 µs with 42,156,032 peak memory usage -../absl_mimalloc/ichor_start_benchmark multi threaded advanced injection ran for 5,288,896 µs with 314,568,704 peak memory usage -../absl_mimalloc/ichor_start_benchmark single threaded constructor injection ran for 6,119,402 µs with 314,568,704 peak memory usage -../absl_mimalloc/ichor_start_benchmark multi threaded constructor injection ran for 7,598,533 µs with 508,850,176 peak memory usage -../absl_mimalloc/ichor_start_stop_benchmark single threaded ran for 1,178,222 µs with 7,864,320 peak memory usage 848,736 start & stop /s -../absl_mimalloc/ichor_start_stop_benchmark multi threaded ran for 2,279,161 µs with 7,864,320 peak memory usage 3,510,063 start & stop /s +../std_std/ichor_coroutine_benchmark single threaded ran for 529,094 µs with 6,029,312 peak memory usage 9,450,116 coroutines/s +../std_std/ichor_coroutine_benchmark multi threaded ran for 1,074,712 µs with 6,291,456 peak memory usage 37,219,273 coroutines/s +../std_std/ichor_event_benchmark single threaded ran for 1,213,448 µs with 645,660,672 peak memory usage 4,120,489 events/s +../std_std/ichor_event_benchmark multi threaded ran for 1,770,993 µs with 5,765,070,848 peak memory usage 22,586,198 events/s +../std_std/ichor_serializer_benchmark single threaded glaze ran for 178,634 µs with 6,029,312 peak memory usage 867 MB/s +../std_std/ichor_serializer_benchmark multi threaded glaze ran for 188,159 µs with 6,291,456 peak memory usage 6,590 MB/s +../std_std/ichor_start_benchmark single threaded advanced injection ran for 4,692,610 µs with 44,564,480 peak memory usage +../std_std/ichor_start_benchmark multi threaded advanced injection ran for 24,380,374 µs with 349,761,536 peak memory usage +../std_std/ichor_start_benchmark single threaded constructor injection ran for 5,466,481 µs with 349,761,536 peak memory usage +../std_std/ichor_start_benchmark multi threaded constructor injection ran for 25,772,273 µs with 444,768,256 peak memory usage +../std_std/ichor_start_stop_benchmark single threaded ran for 1,389,655 µs with 6,029,312 peak memory usage 719,603 start & stop /s +../std_std/ichor_start_stop_benchmark multi threaded ran for 2,078,400 µs with 6,553,600 peak memory usage 3,849,114 start & stop /s +../std_mimalloc/ichor_coroutine_benchmark single threaded ran for 376,194 µs with 6,815,744 peak memory usage 13,291,014 coroutines/s +../std_mimalloc/ichor_coroutine_benchmark multi threaded ran for 787,779 µs with 6,815,744 peak memory usage 50,775,661 coroutines/s +../std_mimalloc/ichor_event_benchmark single threaded ran for 625,455 µs with 568,590,336 peak memory usage 7,994,180 events/s +../std_mimalloc/ichor_event_benchmark multi threaded ran for 808,082 µs with 4,334,026,752 peak memory usage 49,499,926 events/s +../std_mimalloc/ichor_serializer_benchmark single threaded glaze ran for 161,354 µs with 6,815,744 peak memory usage 960 MB/s +../std_mimalloc/ichor_serializer_benchmark multi threaded glaze ran for 175,916 µs with 6,815,744 peak memory usage 7,048 MB/s +../std_mimalloc/ichor_start_benchmark single threaded advanced injection ran for 3,697,897 µs with 42,467,328 peak memory usage +../std_mimalloc/ichor_start_benchmark multi threaded advanced injection ran for 22,956,881 µs with 328,323,072 peak memory usage +../std_mimalloc/ichor_start_benchmark single threaded constructor injection ran for 4,214,905 µs with 340,119,552 peak memory usage +../std_mimalloc/ichor_start_benchmark multi threaded constructor injection ran for 23,768,086 µs with 432,386,048 peak memory usage +../std_mimalloc/ichor_start_stop_benchmark single threaded ran for 1,010,664 µs with 6,815,744 peak memory usage 989,448 start & stop /s +../std_mimalloc/ichor_start_stop_benchmark multi threaded ran for 1,652,661 µs with 6,815,744 peak memory usage 4,840,678 start & stop /s +../std_no_hardening/ichor_coroutine_benchmark single threaded ran for 365,134 µs with 6,815,744 peak memory usage 13,693,602 coroutines/s +../std_no_hardening/ichor_coroutine_benchmark multi threaded ran for 753,784 µs with 6,815,744 peak memory usage 53,065,599 coroutines/s +../std_no_hardening/ichor_event_benchmark single threaded ran for 608,248 µs with 568,590,336 peak memory usage 8,220,331 events/s +../std_no_hardening/ichor_event_benchmark multi threaded ran for 816,374 µs with 4,334,026,752 peak memory usage 48,997,150 events/s +../std_no_hardening/ichor_serializer_benchmark single threaded glaze ran for 158,739 µs with 6,815,744 peak memory usage 976 MB/s +../std_no_hardening/ichor_serializer_benchmark multi threaded glaze ran for 165,208 µs with 6,815,744 peak memory usage 7,505 MB/s +../std_no_hardening/ichor_start_benchmark single threaded advanced injection ran for 3,688,129 µs with 42,205,184 peak memory usage +../std_no_hardening/ichor_start_benchmark multi threaded advanced injection ran for 22,908,667 µs with 329,244,672 peak memory usage +../std_no_hardening/ichor_start_benchmark single threaded constructor injection ran for 4,208,535 µs with 341,041,152 peak memory usage +../std_no_hardening/ichor_start_benchmark multi threaded constructor injection ran for 23,924,781 µs with 432,885,760 peak memory usage +../std_no_hardening/ichor_start_stop_benchmark single threaded ran for 955,847 µs with 6,815,744 peak memory usage 1,046,192 start & stop /s +../std_no_hardening/ichor_start_stop_benchmark multi threaded ran for 1,646,869 µs with 6,815,744 peak memory usage 4,857,702 start & stop /s +../absl_std/ichor_coroutine_benchmark single threaded ran for 537,344 µs with 7,077,888 peak memory usage 9,305,026 coroutines/s +../absl_std/ichor_coroutine_benchmark multi threaded ran for 1,232,790 µs with 7,340,032 peak memory usage 32,446,726 coroutines/s +../absl_std/ichor_event_benchmark single threaded ran for 592,075 µs with 420,478,976 peak memory usage 8,444,876 events/s +../absl_std/ichor_event_benchmark multi threaded ran for 1,184,407 µs with 3,725,852,672 peak memory usage 33,772,174 events/s +../absl_std/ichor_serializer_benchmark single threaded glaze ran for 179,488 µs with 7,340,032 peak memory usage 863 MB/s +../absl_std/ichor_serializer_benchmark multi threaded glaze ran for 191,568 µs with 7,602,176 peak memory usage 6,472 MB/s +../absl_std/ichor_start_benchmark single threaded advanced injection ran for 4,000,142 µs with 40,632,320 peak memory usage +../absl_std/ichor_start_benchmark multi threaded advanced injection ran for 21,589,190 µs with 302,379,008 peak memory usage +../absl_std/ichor_start_benchmark single threaded constructor injection ran for 4,695,022 µs with 302,379,008 peak memory usage +../absl_std/ichor_start_benchmark multi threaded constructor injection ran for 21,649,222 µs with 372,146,176 peak memory usage +../absl_std/ichor_start_stop_benchmark single threaded ran for 1,324,208 µs with 7,340,032 peak memory usage 755,168 start & stop /s +../absl_std/ichor_start_stop_benchmark multi threaded ran for 2,333,392 µs with 7,602,176 peak memory usage 3,428,485 start & stop /s +../absl_mimalloc/ichor_coroutine_benchmark single threaded ran for 433,608 µs with 7,864,320 peak memory usage 11,531,152 coroutines/s +../absl_mimalloc/ichor_coroutine_benchmark multi threaded ran for 1,059,809 µs with 8,126,464 peak memory usage 37,742,649 coroutines/s +../absl_mimalloc/ichor_event_benchmark single threaded ran for 509,442 µs with 417,333,248 peak memory usage 9,814,659 events/s +../absl_mimalloc/ichor_event_benchmark multi threaded ran for 830,994 µs with 3,203,661,824 peak memory usage 48,135,124 events/s +../absl_mimalloc/ichor_serializer_benchmark single threaded glaze ran for 174,560 µs with 7,864,320 peak memory usage 887 MB/s +../absl_mimalloc/ichor_serializer_benchmark multi threaded glaze ran for 232,321 µs with 7,864,320 peak memory usage 5,337 MB/s +../absl_mimalloc/ichor_start_benchmark single threaded advanced injection ran for 3,921,122 µs with 41,680,896 peak memory usage +../absl_mimalloc/ichor_start_benchmark multi threaded advanced injection ran for 21,500,235 µs with 313,102,336 peak memory usage +../absl_mimalloc/ichor_start_benchmark single threaded constructor injection ran for 4,492,021 µs with 323,657,728 peak memory usage +../absl_mimalloc/ichor_start_benchmark multi threaded constructor injection ran for 21,477,940 µs with 404,377,600 peak memory usage +../absl_mimalloc/ichor_start_stop_benchmark single threaded ran for 1,066,336 µs with 7,864,320 peak memory usage 937,790 start & stop /s +../absl_mimalloc/ichor_start_stop_benchmark multi threaded ran for 2,090,964 µs with 8,126,464 peak memory usage 3,825,986 start & stop /s + ``` From 4105be8a239b4fff7b60919969787ba2835f626a Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Tue, 9 Jan 2024 10:01:55 +0100 Subject: [PATCH 29/98] Always use ankerl's unordered_dense maps as it is one of the best implementations. Also add some Raspberry Pi benchmark results. This means we can remove abseil as its only remaining use for Ichor is the btree multimap. However, users can still opt to create their own event queue implementation with abseil. --- .github/workflows/cmake.yml | 5 +- CMakeLists.txt | 63 +- Dockerfile-musl-aarch64-bench | 11 + benchmark.sh | 22 +- benchmarks/README.md | 137 +- build.sh | 6 +- cloc.sh | 4 +- docs/01-GettingStarted.md | 9 - docs/03-CMakeOptions.md | 4 - .../optional_dependency_example/TestService.h | 2 +- include/ankerl/unordered_dense.h | 2032 +++++++++++++++++ include/ichor/Callbacks.h | 7 - include/ichor/Common.h | 48 +- include/ichor/DependencyManager.h | 5 +- .../DependencyLifecycleManager.h | 2 +- include/ichor/event_queues/MultimapQueue.h | 8 - include/ichor/event_queues/SdeventQueue.h | 8 - include/ichor/stl/Any.h | 6 +- src/ichor/DependencyManager.cpp | 16 +- 19 files changed, 2194 insertions(+), 201 deletions(-) create mode 100644 Dockerfile-musl-aarch64-bench create mode 100644 include/ankerl/unordered_dense.h diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 89fc401..ae8c6a0 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -19,7 +19,6 @@ jobs: matrix: compiler: [/opt/gcc-11.3/bin/g++, clang++] spdlog: [ON, OFF] - absl: [ON, OFF] opts: [Debug, Release] sanitizer: [asan] #tsan not yet supported due to missing sanitizer implementation in GCC 10, 11. As well as false positives. See: # - https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=80b4ce1a5190ebe764b1009afae57dcef45f92c2 @@ -36,13 +35,13 @@ jobs: if: ${{matrix.sanitizer == 'asan'}} # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: CXX=${{matrix.compiler}} cmake -GNinja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.opts}} -DICHOR_USE_SPDLOG=${{matrix.spdlog}} -DICHOR_USE_ABSEIL=${{matrix.absl}} -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SDEVENT=ON -DICHOR_USE_MOLD=OFF + run: CXX=${{matrix.compiler}} cmake -GNinja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.opts}} -DICHOR_USE_SPDLOG=${{matrix.spdlog}} -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SDEVENT=ON -DICHOR_USE_MOLD=OFF - name: "Configure CMake (spdlog:${{matrix.spdlog}}, tsan)" if: ${{matrix.sanitizer == 'tsan'}} # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: CXX=${{matrix.compiler}} cmake -GNinja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.opts}} -DICHOR_USE_SPDLOG=${{matrix.spdlog}} -DICHOR_USE_ABSEIL=${{matrix.absl}} -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SDEVENT=ON -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_THREAD_SANITIZER=ON -DICHOR_USE_MOLD=OFF + run: CXX=${{matrix.compiler}} cmake -GNinja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.opts}} -DICHOR_USE_SPDLOG=${{matrix.spdlog}} -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SDEVENT=ON -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_THREAD_SANITIZER=ON -DICHOR_USE_MOLD=OFF - name: Build # Build your program with the given configuration diff --git a/CMakeLists.txt b/CMakeLists.txt index eb47eff..c80669c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,6 @@ option(ICHOR_USE_UGLY_HACK_EXCEPTION_CATCHING "Enable an ugly hack on gcc to ena option(ICHOR_REMOVE_SOURCE_NAMES "Remove compiling source file names and line numbers when logging." OFF) cmake_dependent_option(ICHOR_USE_MOLD "Use mold when linking, recommended to use with gcc 12+ or clang" OFF "NOT WIN32" OFF) cmake_dependent_option(ICHOR_USE_SDEVENT "Add sd-event based queue/integration" OFF "NOT WIN32" OFF) -option(ICHOR_USE_ABSEIL "Use abseil provided classes where applicable" OFF) option(ICHOR_DISABLE_RTTI "Disable RTTI. Reduces memory usage, disables dynamic_cast<>()" ON) option(ICHOR_USE_HARDENING "Uses compiler-specific flags which add stack protection and similar features, as well as adding safety checks in Ichor itself." ON) cmake_dependent_option(ICHOR_USE_MIMALLOC "Use mimalloc for significant performance improvements" ON "NOT ICHOR_USE_SANITIZERS AND NOT ICHOR_MUSL" OFF) @@ -80,7 +79,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") endif() set(ICHOR_ARCH_OPTIMIZATION OFF CACHE STRING "Tell compiler to optimize for target") -set_property(CACHE ICHOR_ARCH_OPTIMIZATION PROPERTY STRINGS OFF NATIVE X86_64 X86_64_SSE4 X86_64_AVX2 X86_64_AVX512 MODERN_ARM_GENERIC) +set_property(CACHE ICHOR_ARCH_OPTIMIZATION PROPERTY STRINGS OFF NATIVE X86_64 X86_64_SSE4 X86_64_AVX2 X86_64_AVX512 MODERN_ARM_GENERIC RASPBERRY_PI_ONE RASPBERRY_PI_TWO RASPBERRY_PI_THREE RASPBERRY_PI_FOUR RASPBERRY_PI_FIVE) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND ICHOR_RUN_CLANG_TIDY) set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=*,-llvmlibc-*,-readability-function-cognitive-complexity,-altera-*,-modernize-use-trailing-return-type,-concurrency-mt-unsafe,-fuchsia-default-arguments-calls,-android-*,-readability-identifier-length,-clang-analyzer-optin.cplusplus.UninitializedObject") @@ -194,7 +193,37 @@ else() elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "X86_64_AVX512") target_compile_options(ichor PUBLIC -march=x86-64-v4) elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "MODERN_ARM_GENERIC") - target_compile_options(ichor PUBLIC -march=armv8-a+simd) + target_compile_options(ichor PUBLIC -march=armv8-a) + elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "RASPBERRY_PI_ONE") + if(ICHOR_AARCH64) + target_compile_options(ichor PUBLIC -march=armv6zk -mcpu=arm1176jzf-s) + else() + target_compile_options(ichor PUBLIC -march=armv6zk -mcpu=arm1176jzf-s -mfpu=vfp) + endif() + elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "RASPBERRY_PI_TWO") + if(ICHOR_AARCH64) + target_compile_options(ichor PUBLIC -march=armv7-a -mcpu=cortex-a7) + else() + target_compile_options(ichor PUBLIC -march=armv7-a -mcpu=cortex-a7 -mfpu=neon-vfpv4) + endif() + elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "RASPBERRY_PI_THREE") + if(ICHOR_AARCH64) + target_compile_options(ichor PUBLIC -march=armv8-a+crc -mcpu=cortex-a53) + else() + target_compile_options(ichor PUBLIC -march=armv8-a+crc -mcpu=cortex-a53 -mfpu=crypto-neon-fp-armv8) + endif() + elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "RASPBERRY_PI_FOUR") + if(ICHOR_AARCH64) + target_compile_options(ichor PUBLIC -march=armv8-a+crc -mcpu=cortex-a72) + else() + target_compile_options(ichor PUBLIC -march=armv8-a+crc -mcpu=cortex-a72 -mfpu=crypto-neon-fp-armv8) + endif() + elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "RASPBERRY_PI_FIVE") + if(ICHOR_AARCH64) + target_compile_options(ichor PUBLIC -march=armv8-a+crc+crypto -mcpu=cortex-a76) + else() + target_compile_options(ichor PUBLIC -march=armv8-a+crc+crypto -mcpu=cortex-a76 -mfpu=crypto-neon-fp-armv8) + endif() endif() endif() @@ -267,7 +296,11 @@ endif() if(NOT WIN32 AND NOT ICHOR_USE_SANITIZERS AND NOT ICHOR_USE_THREAD_SANITIZER) # see https://github.com/google/sanitizers/issues/856 target_compile_options(ichor PUBLIC -fpie) - target_link_options(ichor PUBLIC -pie) + if(ICHOR_MUSL) + target_link_options(ichor PUBLIC -static-pie -static) + else() + target_link_options(ichor PUBLIC -pie) + endif() endif() if(WIN32 AND ICHOR_USE_HARDENING) @@ -358,12 +391,6 @@ if(ICHOR_USE_MIMALLOC) endif() endif() -if(ICHOR_USE_ABSEIL) - find_package(absl REQUIRED) - target_link_libraries(ichor PUBLIC absl::flat_hash_map absl::flat_hash_set absl::btree absl::hash) - target_compile_definitions(ichor PUBLIC ICHOR_USE_ABSEIL) -endif() - if(ICHOR_USE_SPDLOG) target_sources(ichor PRIVATE ${SPDLOG_SOURCES}) endif() @@ -418,14 +445,14 @@ if(NOT WIN32 AND NOT APPLE) target_link_libraries(ichor PUBLIC -ldl -lrt) endif() -target_include_directories(ichor PUBLIC - $ - $) - target_include_directories(ichor PUBLIC $ $) +target_include_directories(ichor PUBLIC + $ + $) + if(ICHOR_USE_SPDLOG) target_include_directories(ichor PUBLIC $ @@ -501,11 +528,17 @@ install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/base64" # source directory PATTERN "*.h" # select header files ) +install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/ankerl" # source directory + DESTINATION "include/ichor/external" # target directory + FILES_MATCHING # install only matched files + PATTERN "*.h" # select header files +) + install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/tl" # source directory DESTINATION "include/ichor/external" # target directory FILES_MATCHING # install only matched files PATTERN "*.h" # select header files - ) +) install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/backward" # source directory DESTINATION "include/ichor/external" # target directory diff --git a/Dockerfile-musl-aarch64-bench b/Dockerfile-musl-aarch64-bench new file mode 100644 index 0000000..7566483 --- /dev/null +++ b/Dockerfile-musl-aarch64-bench @@ -0,0 +1,11 @@ +FROM arm64v8/alpine:3.17 + +RUN apk update +RUN apk add gcc g++ build-base cmake git wget make nano sed linux-headers perl +RUN mkdir -p /opt/ichor/build + +WORKDIR /opt/ichor/build + +ENTRYPOINT ["/bin/sh", "-c"] + +CMD ["cd /opt/ichor/build && cmake -DCMAKE_BUILD_TYPE=Release -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_MUSL=1 -DICHOR_AARCH64=1 -DICHOR_USE_HARDENING=0 -DICHOR_ARCH_OPTIMIZATION=RASPBERRY_PI_FOUR /opt/ichor/src && make -j$(nproc)"] diff --git a/benchmark.sh b/benchmark.sh index d67f100..002e53d 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -52,37 +52,25 @@ run_benchmarks () } if [ $REBUILD -eq 1 ]; then - rm -rf ../std_std ../std_mimalloc ../std_no_hardening ../absl_std ../absl_mimalloc - mkdir -p ../std_std ../std_mimalloc ../std_no_hardening ../absl_std ../absl_mimalloc + rm -rf ../std_std ../std_mimalloc ../std_no_hardening + mkdir -p ../std_std ../std_mimalloc ../std_no_hardening rm -rf ./* ../bin/* - cmake -GNinja -DICHOR_USE_ABSEIL=OFF -DICHOR_USE_MIMALLOC=OFF -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 + cmake -GNinja -DICHOR_USE_MIMALLOC=OFF -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 ninja || exit 1 cp ../bin/*benchmark ../std_std || exit 1 rm -rf ./* ../bin/* - cmake -GNinja -DICHOR_USE_ABSEIL=OFF -DICHOR_USE_MIMALLOC=ON -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 + cmake -GNinja -DICHOR_USE_MIMALLOC=ON -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 ninja || exit 1 cp ../bin/*benchmark ../std_mimalloc || exit 1 rm -rf ./* ../bin/* - cmake -GNinja -DICHOR_USE_ABSEIL=OFF -DICHOR_USE_MIMALLOC=ON -DICHOR_USE_HARDENING=OFF -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 + cmake -GNinja -DICHOR_USE_MIMALLOC=ON -DICHOR_USE_HARDENING=OFF -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 ninja || exit 1 cp ../bin/*benchmark ../std_no_hardening || exit 1 - - rm -rf ./* ../bin/* - cmake -GNinja -DICHOR_USE_ABSEIL=ON -DICHOR_USE_MIMALLOC=OFF -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 - ninja || exit 1 - cp ../bin/*benchmark ../absl_std || exit 1 - - rm -rf ./* ../bin/* - cmake -GNinja -DICHOR_USE_ABSEIL=ON -DICHOR_USE_MIMALLOC=ON -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 - ninja || exit 1 - cp ../bin/*benchmark ../absl_mimalloc || exit 1 fi run_benchmarks ../std_std run_benchmarks ../std_mimalloc run_benchmarks ../std_no_hardening -run_benchmarks ../absl_std -run_benchmarks ../absl_mimalloc diff --git a/benchmarks/README.md b/benchmarks/README.md index 3b314aa..0c0a782 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -1,83 +1,78 @@ # Preliminary benchmark results These benchmarks are mainly used to identify bottlenecks, not to showcase the performance of the framework. Proper throughput and latency benchmarks are TBD. -Setup: AMD 7950X, 6000MHz@CL38 RAM, ubuntu 22.04, gcc 11.3 +Setup: AMD 7950X, 6000MHz@CL38 RAM, ubuntu 22.04, gcc 11.3. Listed numbers are for the multi-threaded runs where contention is likely. -| Compile
Options | coroutines | events | start | start & stop | -|-------------------------------------------|:--------------------:|------------------------:|------------------------:|---------------------:| -| std containers
std alloc | 1,074,712 µs
6MB | 1,770,993 µs
5765MB | 25,772,273 µs
572MB | 2,078,400 µs
6MB | -| std containers
mimalloc | 787,779 µs
7MB | 808,082 µs
4334MB | 23,768,086 µs
479MB | 1,652,661 µs
7MB | -| std containers
mimalloc, no hardening | 753,784 µs
7MB | 816,374 µs
4360MB | 23,924,781 µs
532MB | 1,646,869 µs
7MB | -| absl containers
std alloc | 1,232,790 µs
8MB | 1,184,407 µs
3726MB | 21,649,222 µs
466MB | 2,333,392 µs
8MB | -| absl containers
mimalloc | 1,059,809 µs
8MB | 830,994 µs
3255MB | 21,477,940 µs
509MB | 2,090,964 µs
8MB | +| Compile Options | coroutines | events | start | start & stop | +|------------------------|:-------------------:|------------------------:|------------------------:|---------------------:| +| std alloc | 929,438 µs
6MB | 1,790,241 µs
5765MB | 20,659,654 µs
411MB | 1,963,093 µs
6MB | +| mimalloc | 884,401 µs
7MB | 818,337 µs
4361MB | 20,633,802 µs
408MB | 1,629,301 µs
7MB | +| mimalloc, no hardening | 768,800 µs
7MB | 800,319 µs
4480MB | 20,573,036 µs
407MB | 1,522,690 µs
7MB | + +Raspberry Pi Model 4B, Debian 12 Bookworm, gcc 12.2. +Listed numbers are for the multi-threaded runs where contention is likely. Note that the benchmarks use 8 threads and the Pi Model 4B only has 4 cores. + +| Compile Options | coroutines | single-threaded events | start | start & stop | +|------------------------|:---------------------:|-----------------------:|-------------------------:|----------------------:| +| mimalloc | 11,166,479 µs
6MB | 6,249,102 µs
565MB | 160,831,445 µs
407MB | 25,342,246 µs
6MB | The most interesting observation here is that disabling hardening does not bring any performance gains larger than run-to-run variance. -Detailed data: +Detailed data 7950X: ```text -../std_std/ichor_coroutine_benchmark single threaded ran for 529,094 µs with 6,029,312 peak memory usage 9,450,116 coroutines/s -../std_std/ichor_coroutine_benchmark multi threaded ran for 1,074,712 µs with 6,291,456 peak memory usage 37,219,273 coroutines/s -../std_std/ichor_event_benchmark single threaded ran for 1,213,448 µs with 645,660,672 peak memory usage 4,120,489 events/s -../std_std/ichor_event_benchmark multi threaded ran for 1,770,993 µs with 5,765,070,848 peak memory usage 22,586,198 events/s -../std_std/ichor_serializer_benchmark single threaded glaze ran for 178,634 µs with 6,029,312 peak memory usage 867 MB/s -../std_std/ichor_serializer_benchmark multi threaded glaze ran for 188,159 µs with 6,291,456 peak memory usage 6,590 MB/s -../std_std/ichor_start_benchmark single threaded advanced injection ran for 4,692,610 µs with 44,564,480 peak memory usage -../std_std/ichor_start_benchmark multi threaded advanced injection ran for 24,380,374 µs with 349,761,536 peak memory usage -../std_std/ichor_start_benchmark single threaded constructor injection ran for 5,466,481 µs with 349,761,536 peak memory usage -../std_std/ichor_start_benchmark multi threaded constructor injection ran for 25,772,273 µs with 444,768,256 peak memory usage -../std_std/ichor_start_stop_benchmark single threaded ran for 1,389,655 µs with 6,029,312 peak memory usage 719,603 start & stop /s -../std_std/ichor_start_stop_benchmark multi threaded ran for 2,078,400 µs with 6,553,600 peak memory usage 3,849,114 start & stop /s -../std_mimalloc/ichor_coroutine_benchmark single threaded ran for 376,194 µs with 6,815,744 peak memory usage 13,291,014 coroutines/s -../std_mimalloc/ichor_coroutine_benchmark multi threaded ran for 787,779 µs with 6,815,744 peak memory usage 50,775,661 coroutines/s -../std_mimalloc/ichor_event_benchmark single threaded ran for 625,455 µs with 568,590,336 peak memory usage 7,994,180 events/s -../std_mimalloc/ichor_event_benchmark multi threaded ran for 808,082 µs with 4,334,026,752 peak memory usage 49,499,926 events/s -../std_mimalloc/ichor_serializer_benchmark single threaded glaze ran for 161,354 µs with 6,815,744 peak memory usage 960 MB/s -../std_mimalloc/ichor_serializer_benchmark multi threaded glaze ran for 175,916 µs with 6,815,744 peak memory usage 7,048 MB/s -../std_mimalloc/ichor_start_benchmark single threaded advanced injection ran for 3,697,897 µs with 42,467,328 peak memory usage -../std_mimalloc/ichor_start_benchmark multi threaded advanced injection ran for 22,956,881 µs with 328,323,072 peak memory usage -../std_mimalloc/ichor_start_benchmark single threaded constructor injection ran for 4,214,905 µs with 340,119,552 peak memory usage -../std_mimalloc/ichor_start_benchmark multi threaded constructor injection ran for 23,768,086 µs with 432,386,048 peak memory usage -../std_mimalloc/ichor_start_stop_benchmark single threaded ran for 1,010,664 µs with 6,815,744 peak memory usage 989,448 start & stop /s -../std_mimalloc/ichor_start_stop_benchmark multi threaded ran for 1,652,661 µs with 6,815,744 peak memory usage 4,840,678 start & stop /s -../std_no_hardening/ichor_coroutine_benchmark single threaded ran for 365,134 µs with 6,815,744 peak memory usage 13,693,602 coroutines/s -../std_no_hardening/ichor_coroutine_benchmark multi threaded ran for 753,784 µs with 6,815,744 peak memory usage 53,065,599 coroutines/s -../std_no_hardening/ichor_event_benchmark single threaded ran for 608,248 µs with 568,590,336 peak memory usage 8,220,331 events/s -../std_no_hardening/ichor_event_benchmark multi threaded ran for 816,374 µs with 4,334,026,752 peak memory usage 48,997,150 events/s -../std_no_hardening/ichor_serializer_benchmark single threaded glaze ran for 158,739 µs with 6,815,744 peak memory usage 976 MB/s -../std_no_hardening/ichor_serializer_benchmark multi threaded glaze ran for 165,208 µs with 6,815,744 peak memory usage 7,505 MB/s -../std_no_hardening/ichor_start_benchmark single threaded advanced injection ran for 3,688,129 µs with 42,205,184 peak memory usage -../std_no_hardening/ichor_start_benchmark multi threaded advanced injection ran for 22,908,667 µs with 329,244,672 peak memory usage -../std_no_hardening/ichor_start_benchmark single threaded constructor injection ran for 4,208,535 µs with 341,041,152 peak memory usage -../std_no_hardening/ichor_start_benchmark multi threaded constructor injection ran for 23,924,781 µs with 432,885,760 peak memory usage -../std_no_hardening/ichor_start_stop_benchmark single threaded ran for 955,847 µs with 6,815,744 peak memory usage 1,046,192 start & stop /s -../std_no_hardening/ichor_start_stop_benchmark multi threaded ran for 1,646,869 µs with 6,815,744 peak memory usage 4,857,702 start & stop /s -../absl_std/ichor_coroutine_benchmark single threaded ran for 537,344 µs with 7,077,888 peak memory usage 9,305,026 coroutines/s -../absl_std/ichor_coroutine_benchmark multi threaded ran for 1,232,790 µs with 7,340,032 peak memory usage 32,446,726 coroutines/s -../absl_std/ichor_event_benchmark single threaded ran for 592,075 µs with 420,478,976 peak memory usage 8,444,876 events/s -../absl_std/ichor_event_benchmark multi threaded ran for 1,184,407 µs with 3,725,852,672 peak memory usage 33,772,174 events/s -../absl_std/ichor_serializer_benchmark single threaded glaze ran for 179,488 µs with 7,340,032 peak memory usage 863 MB/s -../absl_std/ichor_serializer_benchmark multi threaded glaze ran for 191,568 µs with 7,602,176 peak memory usage 6,472 MB/s -../absl_std/ichor_start_benchmark single threaded advanced injection ran for 4,000,142 µs with 40,632,320 peak memory usage -../absl_std/ichor_start_benchmark multi threaded advanced injection ran for 21,589,190 µs with 302,379,008 peak memory usage -../absl_std/ichor_start_benchmark single threaded constructor injection ran for 4,695,022 µs with 302,379,008 peak memory usage -../absl_std/ichor_start_benchmark multi threaded constructor injection ran for 21,649,222 µs with 372,146,176 peak memory usage -../absl_std/ichor_start_stop_benchmark single threaded ran for 1,324,208 µs with 7,340,032 peak memory usage 755,168 start & stop /s -../absl_std/ichor_start_stop_benchmark multi threaded ran for 2,333,392 µs with 7,602,176 peak memory usage 3,428,485 start & stop /s -../absl_mimalloc/ichor_coroutine_benchmark single threaded ran for 433,608 µs with 7,864,320 peak memory usage 11,531,152 coroutines/s -../absl_mimalloc/ichor_coroutine_benchmark multi threaded ran for 1,059,809 µs with 8,126,464 peak memory usage 37,742,649 coroutines/s -../absl_mimalloc/ichor_event_benchmark single threaded ran for 509,442 µs with 417,333,248 peak memory usage 9,814,659 events/s -../absl_mimalloc/ichor_event_benchmark multi threaded ran for 830,994 µs with 3,203,661,824 peak memory usage 48,135,124 events/s -../absl_mimalloc/ichor_serializer_benchmark single threaded glaze ran for 174,560 µs with 7,864,320 peak memory usage 887 MB/s -../absl_mimalloc/ichor_serializer_benchmark multi threaded glaze ran for 232,321 µs with 7,864,320 peak memory usage 5,337 MB/s -../absl_mimalloc/ichor_start_benchmark single threaded advanced injection ran for 3,921,122 µs with 41,680,896 peak memory usage -../absl_mimalloc/ichor_start_benchmark multi threaded advanced injection ran for 21,500,235 µs with 313,102,336 peak memory usage -../absl_mimalloc/ichor_start_benchmark single threaded constructor injection ran for 4,492,021 µs with 323,657,728 peak memory usage -../absl_mimalloc/ichor_start_benchmark multi threaded constructor injection ran for 21,477,940 µs with 404,377,600 peak memory usage -../absl_mimalloc/ichor_start_stop_benchmark single threaded ran for 1,066,336 µs with 7,864,320 peak memory usage 937,790 start & stop /s -../absl_mimalloc/ichor_start_stop_benchmark multi threaded ran for 2,090,964 µs with 8,126,464 peak memory usage 3,825,986 start & stop /s - +../std_std/ichor_coroutine_benchmark single threaded ran for 540,057 µs with 6,029,312 peak memory usage 9,258,281 coroutines/s +../std_std/ichor_coroutine_benchmark multi threaded ran for 929,438 µs with 6,291,456 peak memory usage 43,036,759 coroutines/s +../std_std/ichor_event_benchmark single threaded ran for 1,152,095 µs with 645,922,816 peak memory usage 4,339,919 events/s +../std_std/ichor_event_benchmark multi threaded ran for 1,790,241 µs with 5,764,808,704 peak memory usage 22,343,360 events/s +../std_std/ichor_serializer_benchmark single threaded glaze ran for 177,602 µs with 6,029,312 peak memory usage 872 MB/s +../std_std/ichor_serializer_benchmark multi threaded glaze ran for 187,264 µs with 6,291,456 peak memory usage 6,621 MB/s +../std_std/ichor_start_benchmark single threaded advanced injection ran for 3,233,842 µs with 41,537,536 peak memory usage +../std_std/ichor_start_benchmark multi threaded advanced injection ran for 20,744,909 µs with 323,313,664 peak memory usage +../std_std/ichor_start_benchmark single threaded constructor injection ran for 3,539,603 µs with 323,313,664 peak memory usage +../std_std/ichor_start_benchmark multi threaded constructor injection ran for 20,659,654 µs with 411,222,016 peak memory usage +../std_std/ichor_start_stop_benchmark single threaded ran for 1,330,531 µs with 6,029,312 peak memory usage 751,579 start & stop /s +../std_std/ichor_start_stop_benchmark multi threaded ran for 1,963,093 µs with 6,291,456 peak memory usage 4,075,201 start & stop /s +../std_mimalloc/ichor_coroutine_benchmark single threaded ran for 392,161 µs with 6,815,744 peak memory usage 12,749,865 coroutines/s +../std_mimalloc/ichor_coroutine_benchmark multi threaded ran for 884,401 µs with 6,815,744 peak memory usage 45,228,352 coroutines/s +../std_mimalloc/ichor_event_benchmark single threaded ran for 613,459 µs with 568,590,336 peak memory usage 8,150,503 events/s +../std_mimalloc/ichor_event_benchmark multi threaded ran for 818,337 µs with 4,360,503,296 peak memory usage 48,879,618 events/s +../std_mimalloc/ichor_serializer_benchmark single threaded glaze ran for 164,604 µs with 6,815,744 peak memory usage 941 MB/s +../std_mimalloc/ichor_serializer_benchmark multi threaded glaze ran for 169,835 µs with 6,815,744 peak memory usage 7,301 MB/s +../std_mimalloc/ichor_start_benchmark single threaded advanced injection ran for 3,199,495 µs with 40,370,176 peak memory usage +../std_mimalloc/ichor_start_benchmark multi threaded advanced injection ran for 20,534,538 µs with 310,624,256 peak memory usage +../std_mimalloc/ichor_start_benchmark single threaded constructor injection ran for 3,447,383 µs with 310,624,256 peak memory usage +../std_mimalloc/ichor_start_benchmark multi threaded constructor injection ran for 20,633,802 µs with 408,350,720 peak memory usage +../std_mimalloc/ichor_start_stop_benchmark single threaded ran for 1,020,544 µs with 6,815,744 peak memory usage 979,869 start & stop /s +../std_mimalloc/ichor_start_stop_benchmark multi threaded ran for 1,629,301 µs with 6,815,744 peak memory usage 4,910,081 start & stop /s +../std_no_hardening/ichor_coroutine_benchmark single threaded ran for 354,885 µs with 6,815,744 peak memory usage 14,089,071 coroutines/s +../std_no_hardening/ichor_coroutine_benchmark multi threaded ran for 768,800 µs with 6,815,744 peak memory usage 52,029,136 coroutines/s +../std_no_hardening/ichor_event_benchmark single threaded ran for 587,957 µs with 568,590,336 peak memory usage 8,504,023 events/s +../std_no_hardening/ichor_event_benchmark multi threaded ran for 800,319 µs with 4,479,516,672 peak memory usage 49,980,070 events/s +../std_no_hardening/ichor_serializer_benchmark single threaded glaze ran for 150,209 µs with 6,815,744 peak memory usage 1,031 MB/s +../std_no_hardening/ichor_serializer_benchmark multi threaded glaze ran for 168,869 µs with 6,815,744 peak memory usage 7,342 MB/s +../std_no_hardening/ichor_start_benchmark single threaded advanced injection ran for 3,147,797 µs with 40,370,176 peak memory usage +../std_no_hardening/ichor_start_benchmark multi threaded advanced injection ran for 20,622,724 µs with 310,763,520 peak memory usage +../std_no_hardening/ichor_start_benchmark single threaded constructor injection ran for 3,370,720 µs with 322,035,712 peak memory usage +../std_no_hardening/ichor_start_benchmark multi threaded constructor injection ran for 20,573,036 µs with 406,839,296 peak memory usage +../std_no_hardening/ichor_start_stop_benchmark single threaded ran for 914,654 µs with 6,815,744 peak memory usage 1,093,309 start & stop /s +../std_no_hardening/ichor_start_stop_benchmark multi threaded ran for 1,522,690 µs with 6,815,744 peak memory usage 5,253,859 start & stop /s +``` +Detailed data Raspberry Pi 4B: +```text +../bin/ichor_coroutine_benchmark single threaded ran for 5,260,358 µs with 3,641,344 peak memory usage 950,505 coroutines/s +../bin/ichor_coroutine_benchmark multi threaded ran for 11,166,479 µs with 5,623,808 peak memory usage 3,582,149 coroutines/s +../bin/ichor_event_benchmark single threaded ran for 6,249,102 µs with 565,805,056 peak memory usage 800,114 events/s +# multithreaded event benchmark skipped due to not having enough memory +../bin/ichor_serializer_benchmark single threaded glaze ran for 1,578,962 µs with 2,486,272 peak memory usage 98 MB/s +../bin/ichor_serializer_benchmark multi threaded glaze ran for 3,083,435 µs with 5,722,112 peak memory usage 402 MB/s +../bin/ichor_start_benchmark single threaded advanced injection ran for 20,680,096 µs with 37,597,184 peak memory usage +../bin/ichor_start_benchmark multi threaded advanced injection ran for 162,277,929 µs with 308,760,576 peak memory usage +../bin/ichor_start_benchmark single threaded constructor injection ran for 20,891,884 µs with 308,760,576 peak memory usage +../bin/ichor_start_benchmark multi threaded constructor injection ran for 160,831,445 µs with 407,212,032 peak memory usage +../bin/ichor_start_stop_benchmark single threaded ran for 12,108,020 µs with 4,055,040 peak memory usage 82,589 start & stop /s +../bin/ichor_start_stop_benchmark multi threaded ran for 25,342,246 µs with 5,763,072 peak memory usage 315,678 start & stop /s ``` Realtime example on a vanilla linux: @@ -101,7 +96,7 @@ Some HTTP benchmarks with different frameworks ### Ichor + Boost.BEAST ```bash -$ mkdir build && cd build && cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DICHOR_USE_ABSEIL=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SANITIZERS=OFF .. && ninja +$ mkdir build && cd build && cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SANITIZERS=OFF .. && ninja $ ulimit -n 65535 && ../bin/ichor_pong_example -s & $ wrk -c2 -t1 -d60s --latency -s ../benchmarks/wrk.lua http://localhost:8001/ping Running 1m test @ http://localhost:8001/ping diff --git a/build.sh b/build.sh index 6b564f3..e862494 100755 --- a/build.sh +++ b/build.sh @@ -116,19 +116,19 @@ fi for i in ${!ccompilers[@]}; do rm -rf ./* ../bin/* - CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=ON -DICHOR_USE_ABSEIL=ON -DICHOR_ENABLE_INTERNAL_DEBUGGING=OFF -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 + CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=ON -DICHOR_ENABLE_INTERNAL_DEBUGGING=OFF -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 ninja || exit 1 ninja test || exit 1 run_examples rm -rf ./* ../bin/* - CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=ON -DICHOR_USE_ABSEIL=OFF -DICHOR_ENABLE_INTERNAL_DEBUGGING=ON -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SPDLOG=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 + CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=ON -DICHOR_ENABLE_INTERNAL_DEBUGGING=ON -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SPDLOG=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 ninja || exit 1 ninja test || exit 1 run_examples rm -rf ./* ../bin/* - CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_ABSEIL=ON -DICHOR_ENABLE_INTERNAL_DEBUGGING=OFF -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 + CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_ENABLE_INTERNAL_DEBUGGING=OFF -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 ninja || exit 1 ninja test || exit 1 run_examples diff --git a/cloc.sh b/cloc.sh index 33fe1ca..6645350 100755 --- a/cloc.sh +++ b/cloc.sh @@ -1,4 +1,4 @@ #!/bin/bash -cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64,ctre --exclude-content=LYRA_LYRA_HPP --by-file +cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64,ctre,ankerl --exclude-content=LYRA_LYRA_HPP --by-file echo "" -cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64,ctre --exclude-content=LYRA_LYRA_HPP +cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64,ctre,ankerl --exclude-content=LYRA_LYRA_HPP diff --git a/docs/01-GettingStarted.md b/docs/01-GettingStarted.md index 3aca59a..924368b 100644 --- a/docs/01-GettingStarted.md +++ b/docs/01-GettingStarted.md @@ -55,15 +55,6 @@ Ubuntu 22.04: sudo apt install libboost1.74-all-dev libssl-dev ``` -If using abseil: -Ubuntu 20.04: -Not available on apt. Please compile manually. - -Ubuntu 22.04: -``` -sudo apt install libabsl-dev -``` - #### Windows Install MSVC 17.4 or newer. Open Ichor in MSVC and configure CMake according to your wishes. Build and install and you should find an `out` directory in Ichor's top level directory. diff --git a/docs/03-CMakeOptions.md b/docs/03-CMakeOptions.md index fa6371d..d4e765b 100644 --- a/docs/03-CMakeOptions.md +++ b/docs/03-CMakeOptions.md @@ -65,10 +65,6 @@ Usage with gcc 12+ is technically possible, but it might throw off [Catch2 unit Enables the use of the [sdevent event queue](../include/ichor/event_queues/SdeventQueue.h). Requires having sdevent headers and libraries installed on your system to compile. -## ICHOR_USE_ABSEIL (optional dependency) - -Enables the use of the abseil containers in Ichor. Requires having abseil headers and libraries installed on your system to compile. - ## ICHOR_USE_HIREDIS (optional dependency) Enables the use of the Hiredis library and exposes the Redis service. Requires having Hiredis libraries and headers installed on your system to compile. diff --git a/examples/optional_dependency_example/TestService.h b/examples/optional_dependency_example/TestService.h index 1ff5d01..0b3840e 100644 --- a/examples/optional_dependency_example/TestService.h +++ b/examples/optional_dependency_example/TestService.h @@ -17,7 +17,7 @@ class TestService final : public AdvancedService { private: Task> start() final { - ICHOR_LOG_INFO(_logger, "TestService started with dependency"); + ICHOR_LOG_INFO(_logger, "TestService {} started with dependency", getServiceId()); _started = true; if(_injectionCount == 2) { GetThreadLocalEventQueue().pushEvent(getServiceId()); diff --git a/include/ankerl/unordered_dense.h b/include/ankerl/unordered_dense.h new file mode 100644 index 0000000..8f17f65 --- /dev/null +++ b/include/ankerl/unordered_dense.h @@ -0,0 +1,2032 @@ +///////////////////////// ankerl::unordered_dense::{map, set} ///////////////////////// + +// A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion. +// Version 4.4.0 +// https://github.com/martinus/unordered_dense +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2022-2023 Martin Leitner-Ankerl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ANKERL_UNORDERED_DENSE_H +#define ANKERL_UNORDERED_DENSE_H + +// see https://semver.org/spec/v2.0.0.html +#define ANKERL_UNORDERED_DENSE_VERSION_MAJOR 4 // NOLINT(cppcoreguidelines-macro-usage) incompatible API changes +#define ANKERL_UNORDERED_DENSE_VERSION_MINOR 4 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible functionality +#define ANKERL_UNORDERED_DENSE_VERSION_PATCH 0 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes + +// API versioning with inline namespace, see https://www.foonathan.net/2018/11/inline-namespaces/ + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) v##major##_##minor##_##patch +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT(major, minor, patch) ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) +#define ANKERL_UNORDERED_DENSE_NAMESPACE \ + ANKERL_UNORDERED_DENSE_VERSION_CONCAT( \ + ANKERL_UNORDERED_DENSE_VERSION_MAJOR, ANKERL_UNORDERED_DENSE_VERSION_MINOR, ANKERL_UNORDERED_DENSE_VERSION_PATCH) + +#if defined(_MSVC_LANG) +# define ANKERL_UNORDERED_DENSE_CPP_VERSION _MSVC_LANG +#else +# define ANKERL_UNORDERED_DENSE_CPP_VERSION __cplusplus +#endif + +#if defined(__GNUC__) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PACK(decl) decl __attribute__((__packed__)) +#elif defined(_MSC_VER) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PACK(decl) __pragma(pack(push, 1)) decl __pragma(pack(pop)) +#endif + +// exceptions +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) +# define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 1 // NOLINT(cppcoreguidelines-macro-usage) +#else +# define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 0 // NOLINT(cppcoreguidelines-macro-usage) +#endif +#ifdef _MSC_VER +# define ANKERL_UNORDERED_DENSE_NOINLINE __declspec(noinline) +#else +# define ANKERL_UNORDERED_DENSE_NOINLINE __attribute__((noinline)) +#endif + +// defined in unordered_dense.cpp +#if !defined(ANKERL_UNORDERED_DENSE_EXPORT) +# define ANKERL_UNORDERED_DENSE_EXPORT +#endif + +#if ANKERL_UNORDERED_DENSE_CPP_VERSION < 201703L +# error ankerl::unordered_dense requires C++17 or higher +#else +# include // for array +# include // for uint64_t, uint32_t, uint8_t, UINT64_C +# include // for size_t, memcpy, memset +# include // for equal_to, hash +# include // for initializer_list +# include // for pair, distance +# include // for numeric_limits +# include // for allocator, allocator_traits, shared_ptr +# include // for optional +# include // for out_of_range +# include // for basic_string +# include // for basic_string_view, hash +# include // for forward_as_tuple +# include // for enable_if_t, declval, conditional_t, ena... +# include // for forward, exchange, pair, as_const, piece... +# include // for vector +# if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() == 0 +# include // for abort +# endif + +# if defined(__has_include) +# if __has_include() +# define ANKERL_UNORDERED_DENSE_PMR std::pmr // NOLINT(cppcoreguidelines-macro-usage) +# include // for polymorphic_allocator +# elif __has_include() +# define ANKERL_UNORDERED_DENSE_PMR std::experimental::pmr // NOLINT(cppcoreguidelines-macro-usage) +# include // for polymorphic_allocator +# endif +# endif + +# if defined(_MSC_VER) && defined(_M_X64) +# include +# pragma intrinsic(_umul128) +# endif + +# if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) +# define ANKERL_UNORDERED_DENSE_LIKELY(x) __builtin_expect(x, 1) // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_UNLIKELY(x) __builtin_expect(x, 0) // NOLINT(cppcoreguidelines-macro-usage) +# else +# define ANKERL_UNORDERED_DENSE_LIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_UNLIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) +# endif + +namespace ankerl::unordered_dense { + inline namespace ANKERL_UNORDERED_DENSE_NAMESPACE { + + namespace detail { + +# if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() + +// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other +// inlinings more difficult. Throws are also generally the slow path. + [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_key_not_found() { + throw std::out_of_range("ankerl::unordered_dense::map::at(): key not found"); + } + [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_bucket_overflow() { + throw std::overflow_error("ankerl::unordered_dense: reached max bucket size, cannot increase size"); + } + [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_too_many_elements() { + throw std::out_of_range("ankerl::unordered_dense::map::replace(): too many elements"); + } + +# else + + [[noreturn]] inline void on_error_key_not_found() { + abort(); +} +[[noreturn]] inline void on_error_bucket_overflow() { + abort(); +} +[[noreturn]] inline void on_error_too_many_elements() { + abort(); +} + +# endif + + } // namespace detail + +// hash /////////////////////////////////////////////////////////////////////// + +// This is a stripped-down implementation of wyhash: https://github.com/wangyi-fudan/wyhash +// No big-endian support (because different values on different machines don't matter), +// hardcodes seed and the secret, reformats the code, and clang-tidy fixes. + namespace detail::wyhash { + + inline void mum(uint64_t* a, uint64_t* b) { +# if defined(__SIZEOF_INT128__) + __uint128_t r = *a; + r *= *b; + *a = static_cast(r); + *b = static_cast(r >> 64U); +# elif defined(_MSC_VER) && defined(_M_X64) + *a = _umul128(*a, *b, b); +# else + uint64_t ha = *a >> 32U; + uint64_t hb = *b >> 32U; + uint64_t la = static_cast(*a); + uint64_t lb = static_cast(*b); + uint64_t hi{}; + uint64_t lo{}; + uint64_t rh = ha * hb; + uint64_t rm0 = ha * lb; + uint64_t rm1 = hb * la; + uint64_t rl = la * lb; + uint64_t t = rl + (rm0 << 32U); + auto c = static_cast(t < rl); + lo = t + (rm1 << 32U); + c += static_cast(lo < t); + hi = rh + (rm0 >> 32U) + (rm1 >> 32U) + c; + *a = lo; + *b = hi; +# endif + } + +// multiply and xor mix function, aka MUM + [[nodiscard]] inline auto mix(uint64_t a, uint64_t b) -> uint64_t { + mum(&a, &b); + return a ^ b; + } + +// read functions. WARNING: we don't care about endianness, so results are different on big endian! + [[nodiscard]] inline auto r8(const uint8_t* p) -> uint64_t { + uint64_t v{}; + std::memcpy(&v, p, 8U); + return v; + } + + [[nodiscard]] inline auto r4(const uint8_t* p) -> uint64_t { + uint32_t v{}; + std::memcpy(&v, p, 4); + return v; + } + +// reads 1, 2, or 3 bytes + [[nodiscard]] inline auto r3(const uint8_t* p, size_t k) -> uint64_t { + return (static_cast(p[0]) << 16U) | (static_cast(p[k >> 1U]) << 8U) | p[k - 1]; + } + + [[maybe_unused]] [[nodiscard]] inline auto hash(void const* key, size_t len) -> uint64_t { + static constexpr auto secret = std::array{UINT64_C(0xa0761d6478bd642f), + UINT64_C(0xe7037ed1a0b428db), + UINT64_C(0x8ebc6af09c88c6e3), + UINT64_C(0x589965cc75374cc3)}; + + auto const* p = static_cast(key); + uint64_t seed = secret[0]; + uint64_t a{}; + uint64_t b{}; + if (ANKERL_UNORDERED_DENSE_LIKELY(len <= 16)) { + if (ANKERL_UNORDERED_DENSE_LIKELY(len >= 4)) { + a = (r4(p) << 32U) | r4(p + ((len >> 3U) << 2U)); + b = (r4(p + len - 4) << 32U) | r4(p + len - 4 - ((len >> 3U) << 2U)); + } else if (ANKERL_UNORDERED_DENSE_LIKELY(len > 0)) { + a = r3(p, len); + b = 0; + } else { + a = 0; + b = 0; + } + } else { + size_t i = len; + if (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 48)) { + uint64_t see1 = seed; + uint64_t see2 = seed; + do { + seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); + see1 = mix(r8(p + 16) ^ secret[2], r8(p + 24) ^ see1); + see2 = mix(r8(p + 32) ^ secret[3], r8(p + 40) ^ see2); + p += 48; + i -= 48; + } while (ANKERL_UNORDERED_DENSE_LIKELY(i > 48)); + seed ^= see1 ^ see2; + } + while (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 16)) { + seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); + i -= 16; + p += 16; + } + a = r8(p + i - 16); + b = r8(p + i - 8); + } + + return mix(secret[1] ^ len, mix(a ^ secret[1], b ^ seed)); + } + + [[nodiscard]] inline auto hash(uint64_t x) -> uint64_t { + return detail::wyhash::mix(x, UINT64_C(0x9E3779B97F4A7C15)); + } + + } // namespace detail::wyhash + + ANKERL_UNORDERED_DENSE_EXPORT template + struct hash { + auto operator()(T const& obj) const noexcept(noexcept(std::declval>().operator()(std::declval()))) + -> uint64_t { + return std::hash{}(obj); + } + }; + + template + struct hash> { + using is_avalanching = void; + auto operator()(std::basic_string const& str) const noexcept -> uint64_t { + return detail::wyhash::hash(str.data(), sizeof(CharT) * str.size()); + } + }; + + template + struct hash> { + using is_avalanching = void; + auto operator()(std::basic_string_view const& sv) const noexcept -> uint64_t { + return detail::wyhash::hash(sv.data(), sizeof(CharT) * sv.size()); + } + }; + + template + struct hash { + using is_avalanching = void; + auto operator()(T* ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr)); + } + }; + + template + struct hash> { + using is_avalanching = void; + auto operator()(std::unique_ptr const& ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr.get())); + } + }; + + template + struct hash> { + using is_avalanching = void; + auto operator()(std::shared_ptr const& ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr.get())); + } + }; + + template + struct hash::value>::type> { + using is_avalanching = void; + auto operator()(Enum e) const noexcept -> uint64_t { + using underlying = typename std::underlying_type_t; + return detail::wyhash::hash(static_cast(e)); + } + }; + + template + struct tuple_hash_helper { + // Converts the value into 64bit. If it is an integral type, just cast it. Mixing is doing the rest. + // If it isn't an integral we need to hash it. + template + [[nodiscard]] constexpr static auto to64(Arg const& arg) -> uint64_t { + if constexpr (std::is_integral_v || std::is_enum_v) { + return static_cast(arg); + } else { + return hash{}(arg); + } + } + + [[nodiscard]] static auto mix64(uint64_t state, uint64_t v) -> uint64_t { + return detail::wyhash::mix(state + v, uint64_t{0x9ddfea08eb382d69}); + } + + // Creates a buffer that holds all the data from each element of the tuple. If possible we memcpy the data directly. If + // not, we hash the object and use this for the array. Size of the array is known at compile time, and memcpy is optimized + // away, so filling the buffer is highly efficient. Finally, call wyhash with this buffer. + template + [[nodiscard]] static auto calc_hash(T const& t, std::index_sequence) noexcept -> uint64_t { + auto h = uint64_t{}; + ((h = mix64(h, to64(std::get(t)))), ...); + return h; + } + }; + + template + struct hash> : tuple_hash_helper { + using is_avalanching = void; + auto operator()(std::tuple const& t) const noexcept -> uint64_t { + return tuple_hash_helper::calc_hash(t, std::index_sequence_for{}); + } + }; + + template + struct hash> : tuple_hash_helper { + using is_avalanching = void; + auto operator()(std::pair const& t) const noexcept -> uint64_t { + return tuple_hash_helper::calc_hash(t, std::index_sequence_for{}); + } + }; + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_HASH_STATICCAST(T) \ + template <> \ + struct hash { \ + using is_avalanching = void; \ + auto operator()(T const& obj) const noexcept -> uint64_t { \ + return detail::wyhash::hash(static_cast(obj)); \ + } \ + } + +# if defined(__GNUC__) && !defined(__clang__) + # pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wuseless-cast" +# endif +// see https://en.cppreference.com/w/cpp/utility/hash + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(bool); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(signed char); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned char); +# if ANKERL_UNORDERED_DENSE_CPP_VERSION >= 202002L && defined(__cpp_char8_t) + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char8_t); +# endif + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char16_t); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char32_t); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(wchar_t); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(short); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned short); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(int); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned int); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long long); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long long); + +# if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +# endif + +// bucket_type ////////////////////////////////////////////////////////// + + namespace bucket_type { + + struct standard { + static constexpr uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint + static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint + + uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash + uint32_t m_value_idx; // index into the m_values vector. + }; + + ANKERL_UNORDERED_DENSE_PACK(struct big { + static constexpr uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint + static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint + + uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash + size_t m_value_idx; // index into the m_values vector. + }); + + } // namespace bucket_type + + namespace detail { + + struct nonesuch {}; + + template class Op, class... Args> + struct detector { + using value_t = std::false_type; + using type = Default; + }; + + template class Op, class... Args> + struct detector>, Op, Args...> { + using value_t = std::true_type; + using type = Op; + }; + + template