From d07b0d120677b897f39954b00922d239bcdd04ec Mon Sep 17 00:00:00 2001 From: sebaszm <45654185+sebaszm@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:42:23 +0200 Subject: [PATCH 1/4] [ProcessContainers] Runtime configurable containers (#1723) * [ProcessContainers] Runtime selectable containers * [ProcessContainers] Adapt LXC to selectable containers * [ProcessContainers] Adapt runC to selectable containers * Add cmake files for AWC, Dobby and libcrun implementations * ContainerAdministartor is external * Export correct headers * Namespace compat --- Source/Thunder/Config.h | 60 ++----- Source/Thunder/PluginServer.cpp | 9 +- Source/Thunder/PluginServer.h | 4 - Source/com/Communicator.cpp | 7 +- Source/com/Communicator.h | 53 +++--- Source/core/Proxy.h | 23 ++- .../processcontainers/CMakeLists.txt | 132 +++++---------- .../ContainerAdministrator.cpp | 146 +++++++++++++++++ .../ContainerAdministrator.h | 113 +++++++++++++ .../processcontainers/ContainerProducer.h | 51 ++++++ .../processcontainers/IProcessContainers.h | 127 +++++++++++++++ .../processcontainers/ProcessContainer.h | 135 ---------------- .../common/BaseAdministrator.h | 109 ------------- .../common/BaseContainerIterator.h | 4 +- .../common/CGroupContainerInfo.h | 1 - .../AWCImplementation/CMakeLists.txt | 62 +++++++ .../CRunImplementation/CMakeLists.txt | 46 ++++++ .../DobbyImplementation/CMakeLists.txt | 47 ++++++ .../LXCImplementation/CMakeLists.txt | 46 ++++++ .../LXCImplementation/LXCImplementation.cpp | 153 +++++++++--------- .../LXCImplementation/LXCImplementation.h | 59 +++---- .../RunCImplementation/CMakeLists.txt | 47 ++++++ .../RunCImplementation/RunCImplementation.cpp | 126 ++++++--------- .../RunCImplementation/RunCImplementation.h | 35 ++-- .../processcontainers/processcontainers.h | 29 ++++ cmake/modules/FindLXC.cmake | 1 - 26 files changed, 990 insertions(+), 635 deletions(-) create mode 100644 Source/extensions/processcontainers/ContainerAdministrator.cpp create mode 100644 Source/extensions/processcontainers/ContainerAdministrator.h create mode 100644 Source/extensions/processcontainers/ContainerProducer.h create mode 100644 Source/extensions/processcontainers/IProcessContainers.h delete mode 100644 Source/extensions/processcontainers/ProcessContainer.h delete mode 100644 Source/extensions/processcontainers/common/BaseAdministrator.h create mode 100644 Source/extensions/processcontainers/implementations/AWCImplementation/CMakeLists.txt create mode 100644 Source/extensions/processcontainers/implementations/CRunImplementation/CMakeLists.txt create mode 100644 Source/extensions/processcontainers/implementations/DobbyImplementation/CMakeLists.txt create mode 100644 Source/extensions/processcontainers/implementations/LXCImplementation/CMakeLists.txt create mode 100644 Source/extensions/processcontainers/implementations/RunCImplementation/CMakeLists.txt create mode 100644 Source/extensions/processcontainers/processcontainers.h diff --git a/Source/Thunder/Config.h b/Source/Thunder/Config.h index 7d1b01521..d3fc8f160 100644 --- a/Source/Thunder/Config.h +++ b/Source/Thunder/Config.h @@ -362,46 +362,6 @@ namespace PluginHost { Core::JSON::String PluginConfigPath; }; -#ifdef PROCESSCONTAINERS_ENABLED - - class ProcessContainerConfig : public Core::JSON::Container { - public: - ProcessContainerConfig() - : Logging(_T("NONE")) - { - - Add(_T("logging"), &Logging); - } - ProcessContainerConfig(const ProcessContainerConfig& copy) - : Logging(copy.Logging) - { - Add(_T("logging"), &Logging); - } - ProcessContainerConfig(ProcessContainerConfig&& move) noexcept - : Logging(std::move(move.Logging)) - { - Add(_T("logging"), &Logging); - } - ~ProcessContainerConfig() override = default; - - ProcessContainerConfig& operator=(const ProcessContainerConfig& RHS) - { - Logging = RHS.Logging; - return (*this); - } - ProcessContainerConfig& operator=(ProcessContainerConfig&& move) noexcept - { - if (this != &move) { - Logging = std::move(move.Logging); - } - return (*this); - } - - Core::JSON::String Logging; - }; - -#endif - #ifdef HIBERNATE_SUPPORT_ENABLED class HibernateConfig : public Core::JSON::Container { public: @@ -572,7 +532,7 @@ namespace PluginHost { Core::JSON::DecSInt32 Longitude; Core::JSON::Boolean DelegatedReleases; #ifdef PROCESSCONTAINERS_ENABLED - ProcessContainerConfig ProcessContainers; + Core::JSON::String ProcessContainers; #endif Core::JSON::ArrayType LinkerPluginPaths; Observables Observe; @@ -737,13 +697,13 @@ namespace PluginHost { , _substituter(*this) , _configLock() , _delegatedReleases(true) - #ifdef PROCESSCONTAINERS_ENABLED - , _ProcessContainersLogging() - #endif +#ifdef PROCESSCONTAINERS_ENABLED + , _processContainersConfig() +#endif , _linkerPluginPaths() - #ifdef HIBERNATE_SUPPORT_ENABLED +#ifdef HIBERNATE_SUPPORT_ENABLED , _hibernateLocator() - #endif +#endif { JSONConfig config; @@ -754,7 +714,7 @@ namespace PluginHost { _webPrefix = '/' + _prefix; _JSONRPCPrefix = '/' + config.JSONRPC.Value(); #ifdef PROCESSCONTAINERS_ENABLED - _ProcessContainersLogging = config.ProcessContainers.Logging.Value(); + _processContainersConfig = config.ProcessContainers.Value(); #endif #ifdef HIBERNATE_SUPPORT_ENABLED _hibernateLocator = config.Hibernate.Locator.Value(); @@ -882,8 +842,8 @@ POP_WARNING() return (_JSONRPCPrefix); } #ifdef PROCESSCONTAINERS_ENABLED - inline const string& ProcessContainersLogging() const { - return (_ProcessContainersLogging); + inline const string& ProcessContainersConfig() const { + return (_processContainersConfig); } #endif @@ -1175,7 +1135,7 @@ POP_WARNING() bool _delegatedReleases; #ifdef PROCESSCONTAINERS_ENABLED - string _ProcessContainersLogging; + string _processContainersConfig; #endif std::vector _linkerPluginPaths; #ifdef HIBERNATE_SUPPORT_ENABLED diff --git a/Source/Thunder/PluginServer.cpp b/Source/Thunder/PluginServer.cpp index 4814d2d65..6a5a9f406 100644 --- a/Source/Thunder/PluginServer.cpp +++ b/Source/Thunder/PluginServer.cpp @@ -25,7 +25,7 @@ #endif #ifdef PROCESSCONTAINERS_ENABLED -#include "../processcontainers/ProcessContainer.h" +#include "../processcontainers/processcontainers.h" #endif #ifdef HIBERNATE_SUPPORT_ENABLED @@ -1175,11 +1175,10 @@ namespace PluginHost { // Add the controller as a service to the services. _controller = _services.Insert(metaDataConfig, Service::mode::CONFIGURED); - #ifdef PROCESSCONTAINERS_ENABLED +#ifdef PROCESSCONTAINERS_ENABLED // turn on ProcessContainer logging - ProcessContainers::IContainerAdministrator& admin = ProcessContainers::IContainerAdministrator::Instance(); - admin.Logging(_config.VolatilePath(), configuration.ProcessContainersLogging()); - #endif + ProcessContainers::ContainerAdministrator::Instance().Initialize(_config.ProcessContainersConfig()); +#endif } POP_WARNING() diff --git a/Source/Thunder/PluginServer.h b/Source/Thunder/PluginServer.h index 411bd2d93..eb9bab386 100644 --- a/Source/Thunder/PluginServer.h +++ b/Source/Thunder/PluginServer.h @@ -27,10 +27,6 @@ #include "WarningReportingCategories.h" #include "PostMortem.h" -#ifdef PROCESSCONTAINERS_ENABLED -#include "../processcontainers/ProcessContainer.h" -#endif - #ifndef HOSTING_COMPROCESS #error "Please define the name of the COM process!!!" #endif diff --git a/Source/com/Communicator.cpp b/Source/com/Communicator.cpp index 40addf41f..266c64772 100644 --- a/Source/com/Communicator.cpp +++ b/Source/com/Communicator.cpp @@ -342,14 +342,15 @@ namespace RPC { void Communicator::ContainerProcess::Terminate() /* override */ { - ASSERT(_container != nullptr); - g_destructor.Destruct(Id(), *this); + if (_container.IsValid() == true) { + g_destructor.Destruct(Id(), *this); + } } void Communicator::ContainerProcess::PostMortem() /* override */ { Core::process_t pid; - if ( (_container != nullptr) && ((pid = static_cast(_container->Pid())) != 0) ) { + if ( (_container.IsValid() == true) && ((pid = static_cast(_container->Pid())) != 0) ) { Core::ProcessInfo process(pid); process.Dump(); } diff --git a/Source/com/Communicator.h b/Source/com/Communicator.h index 195852f8c..35aea1ced 100644 --- a/Source/com/Communicator.h +++ b/Source/com/Communicator.h @@ -32,7 +32,7 @@ #ifdef PROCESSCONTAINERS_ENABLED -#include +#include #endif @@ -823,16 +823,21 @@ namespace RPC { ContainerConfig() : Core::JSON::Container() + , ContainerType(ProcessContainers::IContainer::LXC) #ifdef __DEBUG__ , ContainerPath() #endif { + Add(_T("containertype"), &ContainerType); #ifdef __DEBUG__ Add(_T("containerpath"), &ContainerPath); #endif } ~ContainerConfig() = default; + public: + Core::JSON::EnumType ContainerType; + #ifdef __DEBUG__ Core::JSON::String ContainerPath; #endif @@ -850,33 +855,41 @@ namespace RPC { : MonitorableProcess(instance.Callsign(), parent) , _process(RemoteConnection::Id(), baseConfig, instance) { - ProcessContainers::IContainerAdministrator& admin = ProcessContainers::IContainerAdministrator::Instance(); - - std::vector searchpaths(3); - searchpaths[0] = baseConfig.VolatilePath(); - searchpaths[1] = baseConfig.PersistentPath(); - searchpaths[2] = baseConfig.DataPath(); - -#ifdef __DEBUG__ ContainerConfig config; - config.FromString(instance.Configuration()); - if (config.ContainerPath.IsSet() == true) { - searchpaths.emplace(searchpaths.cbegin(), config.ContainerPath.Value()); + // Fetch container type + Core::OptionalType error; + config.FromString(instance.Configuration(), error); + + if (error.IsSet() == true) { + TRACE_L1("Invalid process container configuration"); } + else { + std::vector searchPaths(3); -#endif + #ifdef __DEBUG__ + if (config.ContainerPath.IsSet() == true) { + searchPaths.push_back(config.ContainerPath.Value()); + } + #endif + + searchPaths.push_back(baseConfig.VolatilePath()); + searchPaths.push_back(baseConfig.PersistentPath()); + searchPaths.push_back(baseConfig.DataPath()); - Core::IteratorType, const string> searchpathsit(searchpaths); + Core::IteratorType, const string> searchPathsIt(searchPaths); - string volatilecallsignpath(baseConfig.VolatilePath() + instance.Callsign() + _T('/')); - _container = admin.Container(instance.Callsign(), searchpathsit, volatilecallsignpath, instance.Configuration()); + const string volatilePath(Core::Directory::Normalize(baseConfig.VolatilePath() + instance.Callsign())); + + _container = ProcessContainers::ContainerAdministrator::Instance().Container(config.ContainerType, instance.Callsign(), + searchPathsIt, volatilePath, instance.Configuration()); + } } ~ContainerProcess() override { - if (_container != nullptr) { - _container->Release(); + if (_container.IsValid() == true) { + _container.Release(); } } @@ -885,7 +898,7 @@ namespace RPC { { uint32_t result = Core::ERROR_GENERAL; - if (_container != nullptr) { + if (_container.IsValid() == true) { // Note: replace below code with something more efficient when Iterators redesigned Core::Process::Options::Iterator it(_process.Options()); @@ -954,7 +967,7 @@ namespace RPC { END_INTERFACE_MAP private: - ProcessContainers::IContainer* _container; + Core::ProxyType _container; Process _process; }; diff --git a/Source/core/Proxy.h b/Source/core/Proxy.h index 4647c532c..d51cce599 100644 --- a/Source/core/Proxy.h +++ b/Source/core/Proxy.h @@ -1794,7 +1794,28 @@ POP_WARNING() return (result); } - + template + void Visit(ACTION&& action) + { + _lock.Lock(); + for (auto& entry : _list) { + if (action(entry.first) == true) { + break; + } + } + _lock.Unlock(); + } + template + void Visit(ACTION&& action) const + { + _lock.Lock(); + for (auto const& entry : _list) { + if (action(entry.first) == true) { + break; + } + } + _lock.Unlock(); + } void Clear() { _lock.Lock(); diff --git a/Source/extensions/processcontainers/CMakeLists.txt b/Source/extensions/processcontainers/CMakeLists.txt index b25939a34..1fadeecfa 100644 --- a/Source/extensions/processcontainers/CMakeLists.txt +++ b/Source/extensions/processcontainers/CMakeLists.txt @@ -16,55 +16,58 @@ # limitations under the License. set(TARGET ${NAMESPACE}ProcessContainers) +message("Setup ${TARGET}") # Backend options -option(PROCESSCONTAINERS_LXC - "Use LXC backend." OFF) -option(PROCESSCONTAINERS_RUNC - "Use RunC backend." OFF) -option(PROCESSCONTAINERS_CRUN - "Use CRun backend." OFF) -option(PROCESSCONTAINERS_DOBBY - "Use Dobby backend." OFF) -option(PROCESSCONTAINERS_AWC - "Use AWC/slauncher - One Middleware backend." OFF) +option(PROCESSCONTAINERS_LXC "Use LXC backend" OFF) +option(PROCESSCONTAINERS_RUNC "Use RunC backend" OFF) +option(PROCESSCONTAINERS_CRUN "Use CRun backend" OFF) +option(PROCESSCONTAINERS_DOBBY "Use Dobby backend" OFF) +option(PROCESSCONTAINERS_AWC "Use AWC/slauncher - One Middleware backend" OFF) # Construct a library object add_library(${TARGET} SHARED Module.cpp + ContainerAdministrator.cpp ) # Selects backend -if (PROCESSCONTAINERS_LXC) - target_sources(${TARGET} PRIVATE implementations/LXCImplementation/LXCImplementation.cpp) -elseif(PROCESSCONTAINERS_RUNC) - target_sources(${TARGET} PRIVATE implementations/RunCImplementation/RunCImplementation.cpp - ) -elseif(PROCESSCONTAINERS_CRUN) - target_sources(${TARGET} PRIVATE implementations/CRunImplementation/CRunImplementation.cpp - ) -elseif(PROCESSCONTAINERS_DOBBY) - target_sources(${TARGET} PRIVATE implementations/DobbyImplementation/DobbyImplementation.cpp) -elseif(PROCESSCONTAINERS_AWC) - target_sources( - ${TARGET} PRIVATE - implementations/AWCImplementation/AWC.cpp - implementations/AWCImplementation/AWCContainerAdministrator.cpp - implementations/AWCImplementation/AWCContainerBase.cpp - implementations/AWCImplementation/AWCImplementation.cpp - implementations/AWCImplementation/AWCProxyContainer.cpp - implementations/AWCImplementation/dbus/Client.cpp - implementations/AWCImplementation/dbus/api.c) +if(PROCESSCONTAINERS_LXC) + add_subdirectory(implementations/LXCImplementation) +endif() + +if(PROCESSCONTAINERS_RUNC) + add_subdirectory(implementations/RunCImplementation) +endif() + +if(PROCESSCONTAINERS_CRUN) + add_subdirectory(implementations/CRunImplementation) +endif() + +if(PROCESSCONTAINERS_DOBBY) + add_subdirectory(implementations/DobbyImplementation) +endif() + +if(PROCESSCONTAINERS_AWC) + add_subdirectory(implementations/AWCImplementation) +endif() + +if("${CONTAINERS}" STREQUAL "") + message(FATAL_ERROR "No container runtime backend selected!") endif() set(PUBLIC_HEADERS - ProcessContainer.h + processcontainers.h + IProcessContainers.h + ContainerAdministrator.h Module.h ) target_link_libraries(${TARGET} - PUBLIC + PRIVATE + CompileSettingsDebug ${NAMESPACE}Core::${NAMESPACE}Core + -Wl,--whole-archive ${CONTAINERS} -Wl,--no-whole-archive ) set_target_properties(${TARGET} PROPERTIES @@ -76,8 +79,8 @@ set_target_properties(${TARGET} PROPERTIES ) if(HUMAN_VERSIONED_BINARIES) -set_target_properties(${TARGET} PROPERTIES - VERSION ${VERSION} + set_target_properties(${TARGET} PROPERTIES + VERSION ${VERSION} ) endif() @@ -88,67 +91,6 @@ target_include_directories( ${TARGET} $ ) -if (PROCESSCONTAINERS_LXC) - find_package(LXC REQUIRED) - target_link_libraries(${TARGET} - PRIVATE - LXC::LXC - CompileSettingsDebug::CompileSettingsDebug - ) - - target_include_directories( ${TARGET} - PRIVATE - LXC::LXC - ) -elseif (PROCESSCONTAINERS_CRUN) - find_package(LibCRUN REQUIRED) - - target_link_libraries(${TARGET} - PRIVATE - libcrun::libcrun - ) - - target_include_directories( ${TARGET} - PRIVATE - libcrun::libcrun - ) -elseif (PROCESSCONTAINERS_DOBBY) - find_package(Dobby REQUIRED CONFIG) - - find_package(Systemd REQUIRED) - - target_link_libraries(${TARGET} - PRIVATE - DobbyClientLib - Systemd::Systemd - ) -elseif (PROCESSCONTAINERS_AWC) - find_package(LXC REQUIRED) - find_package(Slauncher REQUIRED) - find_package(LibGio REQUIRED) - find_package(LibGioUnix REQUIRED) - find_package(LibGlib REQUIRED) - find_package(LibGobject REQUIRED) - target_link_libraries(${TARGET} - PRIVATE - Slauncher::Slauncher - CompileSettingsDebug::CompileSettingsDebug - ${LIBGIO_INCLUDE_LIBRARIES} - ${LIBGIOUNIX_LIBRARIES} - ${LIBGLIB_LIBRARIES} - ${LIBGOBJECT_LIBRARIES} - ) - - target_include_directories( ${TARGET} - PRIVATE - Slauncher::Slauncher - ${LIBGIO_INCLUDE_DIRS} - ${LIBGIOUNIX_INCLUDE_DIRS} - ${LIBGLIB_INCLUDE_DIRS} - ${LIBGOBJECT_INCLUDE_DIRS} - ) -endif() - install( TARGETS ${TARGET} EXPORT ${TARGET}Targets ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development diff --git a/Source/extensions/processcontainers/ContainerAdministrator.cpp b/Source/extensions/processcontainers/ContainerAdministrator.cpp new file mode 100644 index 000000000..92c1475c8 --- /dev/null +++ b/Source/extensions/processcontainers/ContainerAdministrator.cpp @@ -0,0 +1,146 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ContainerAdministrator.h" +#include "common/BaseContainerIterator.h" + +namespace Thunder { + +ENUM_CONVERSION_BEGIN(ProcessContainers::IContainer::containertype) + + { ProcessContainers::IContainer::containertype::LXC, _TXT("lxc") }, + { ProcessContainers::IContainer::containertype::RUNC, _TXT("runc") }, + { ProcessContainers::IContainer::containertype::CRUN, _TXT("crun") }, + { ProcessContainers::IContainer::containertype::DOBBY, _TXT("dobby") }, + { ProcessContainers::IContainer::containertype::AWC, _TXT("awc") }, + +ENUM_CONVERSION_END(ProcessContainers::IContainer::containertype) + + +namespace ProcessContainers { + + uint32_t ContainerAdministrator::Initialize(const string& configuration) + { + uint32_t result = Core::ERROR_NONE; + + _adminLock.Lock(); + + for (auto& runtime : _producers) { + + const Core::EnumerateType value(runtime.first); + ASSERT(value.IsSet() == true); + + // Extract runtime system specific config, if any + Core::JSON::String specific; + Core::JSON::Container container; + container.Add(value.Data(), &specific); + container.FromString(configuration); + + TRACE(Trace::Information, (_T("Initializing container runtime system '%s'..."), value.Data())); + + // Pass the configuration to the runtime + const uint32_t initResult = runtime.second->Initialize(specific); + + if (initResult != Core::ERROR_NONE) { + TRACE(Trace::Error, (_T("Initialization failure"))); + result = Core::ERROR_GENERAL; + } + } + + _adminLock.Unlock(); + + return (result); + } + + void ContainerAdministrator::Deinitialize() + { + _adminLock.Lock(); + + for (auto& runtime : _producers) { + runtime.second->Deinitialize(); + } + + _adminLock.Unlock(); + } + + Core::ProxyType ContainerAdministrator::Container(const IContainer::containertype type, const string& id, + IStringIterator& searchPaths, const string& logPath, const string& configuration) + { + Core::ProxyType container; + + _adminLock.Lock(); + + auto it = _producers.find(type); + + const Core::EnumerateType value(type); + DEBUG_VARIABLE(value); + + ASSERT(value.IsSet() == true); + + if (it != _producers.end()) { + + auto& runtime = (*it).second; + ASSERT(runtime != nullptr); + + container = runtime->Container(id, searchPaths, logPath, configuration); + + if (container.IsValid() == true) { + TRACE(Trace::Information, (_T("Container '%s' created successfully (runtime system '%s')"), + container->Id().c_str(), value.Data())); + } + } + else { + TRACE(Trace::Error, (_T("Container runtime system '%s' is not enabled!"), value.Data())); + } + + _adminLock.Unlock(); + + return (container); + } + + Core::ProxyType ContainerAdministrator::Get(const string& id) + { + Core::ProxyType container; + + _containers.Visit([&container, &id](Core::ProxyType& entry) -> bool { + if (entry->Id() == id) { + container = entry; + return (true); + } else { + return (false); + } + }); + + return (container); + } + + IContainerIterator* ContainerAdministrator::Containers() const + { + std::vector containers; + + _containers.Visit([&containers](const Core::ProxyType& entry) -> bool { + containers.push_back(entry->Id()); + return (false); + }); + + return (new BaseContainerIterator(std::move(containers))); + } + +} // namespace ProcessContainers +} \ No newline at end of file diff --git a/Source/extensions/processcontainers/ContainerAdministrator.h b/Source/extensions/processcontainers/ContainerAdministrator.h new file mode 100644 index 000000000..31e33afdf --- /dev/null +++ b/Source/extensions/processcontainers/ContainerAdministrator.h @@ -0,0 +1,113 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" + +#include "IProcessContainers.h" + +namespace Thunder { + +namespace ProcessContainers { + + class EXTERNAL ContainerAdministrator { + private: + template + friend class Core::SingletonType; + + ContainerAdministrator() + : _adminLock() + { + } + + public: + ContainerAdministrator(const ContainerAdministrator&) = delete; + ContainerAdministrator(ContainerAdministrator&&) = delete; + ContainerAdministrator& operator=(const ContainerAdministrator&) = delete; + ContainerAdministrator& operator=(ContainerAdministrator&&) = delete; + + ~ContainerAdministrator() + { + ASSERT(_containers.Count() == 0); + ASSERT(_producers.empty() == true); + } + + public: + static ContainerAdministrator& Instance() { + static ContainerAdministrator& instance = Core::SingletonType::Instance(); + return (instance); + } + + public: + uint32_t Initialize(const string& configuration); + void Deinitialize(); + + public: + template + Core::ProxyType Create(Args&&... args) { + return (_containers.Instance(std::forward(args)...)); + } + + public: + Core::ProxyType Container(const IContainer::containertype type, const string& id, + IStringIterator& searchPaths, const string& logPath, const string& configuration); + + Core::ProxyType Get(const string& id); + IContainerIterator* Containers() const; + + private: + template + friend class ContainerProducerRegistrationType; + + private: + template + void Announce(const IContainer::containertype& type) + { + _adminLock.Lock(); + + ASSERT(_producers.find(type) == _producers.end()); + + // Announce another container runtime... + _producers.emplace(type, new PRODUCER()); + + _adminLock.Unlock(); + } + + void Revoke(const IContainer::containertype& type) + { + _adminLock.Lock(); + + ASSERT(_producers.find(type) != _producers.end()); + + // No longer available + _producers.erase(type); + + _adminLock.Unlock(); + } + + private: + mutable Core::CriticalSection _adminLock; + std::map> _producers; + Core::ProxyListType _containers; + }; + +} // namespace ProcessContainers + +} diff --git a/Source/extensions/processcontainers/ContainerProducer.h b/Source/extensions/processcontainers/ContainerProducer.h new file mode 100644 index 000000000..28d942f84 --- /dev/null +++ b/Source/extensions/processcontainers/ContainerProducer.h @@ -0,0 +1,51 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#pragma once + +#include "Module.h" +#include "IProcessContainers.h" +#include "ContainerAdministrator.h" + +namespace Thunder { +namespace ProcessContainers { + + template + class ContainerProducerRegistrationType { + public: + ContainerProducerRegistrationType(const ContainerProducerRegistrationType&) = delete; + ContainerProducerRegistrationType(ContainerProducerRegistrationType&&) = delete; + ContainerProducerRegistrationType& operator=(const ContainerProducerRegistrationType&) = delete; + ContainerProducerRegistrationType& operator=(ContainerProducerRegistrationType&&) = delete; + + ContainerProducerRegistrationType() + { + ContainerAdministrator::Instance().Announce(CONTAINERTYPE); + } + + ~ContainerProducerRegistrationType() + { + ContainerAdministrator::Instance().Revoke(CONTAINERTYPE); + } + }; + +} + +} \ No newline at end of file diff --git a/Source/extensions/processcontainers/IProcessContainers.h b/Source/extensions/processcontainers/IProcessContainers.h new file mode 100644 index 000000000..264f279b7 --- /dev/null +++ b/Source/extensions/processcontainers/IProcessContainers.h @@ -0,0 +1,127 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" + +namespace Thunder { +namespace ProcessContainers { + + using IStringIterator = Core::IteratorType, const string>; + + struct INetworkInterfaceIterator : public Core::IIterator, public Core::IReferenceCounted { + + ~INetworkInterfaceIterator() override = default; + + // Return interface name (eg. eth0) + virtual string Name() const = 0; + + // Return numver of IP addresses assigned to the interface + virtual uint16_t NumAddresses() const = 0; + + // Get n-th IP assigned to the container. Empty string is returned if n is larger than NumAddresses() + virtual string Address(const uint16_t n = 0) const = 0; + }; + + struct IMemoryInfo : public Core::IReferenceCounted { + + ~IMemoryInfo() override = default; + + // Memory size in bytes, returns UINT64_MAX on error + virtual uint64_t Allocated() const = 0; + virtual uint64_t Resident() const = 0; + virtual uint64_t Shared() const = 0; + }; + + struct IProcessorInfo : public Core::IReferenceCounted { + + ~IProcessorInfo() override = default; + + // Total usage of CPU, in nanoseconds. Returns UINT64_MAX on error + virtual uint64_t TotalUsage() const = 0; + + // CPU usage per core in nanoseconds. Returns UINT64_MAX on error + virtual uint64_t CoreUsage(uint32_t coreNum) const = 0; + + virtual uint16_t NumberOfCores() const = 0; + }; + + struct IContainerIterator : public Core::IIterator, public Core::IReferenceCounted { + + ~IContainerIterator() override = default; + + // Return Id of container + virtual const string& Id() const = 0; + }; + + struct IContainer { + + enum containertype : uint8_t { + LXC, + RUNC, + CRUN, + DOBBY, + AWC + }; + + virtual ~IContainer() = default; + + virtual containertype Type() const = 0; + + virtual const string& Id() const = 0; + + // Get PID of the container in Host namespace + virtual uint32_t Pid() const = 0; + + // Return memory usage statistics for the whole container + virtual IMemoryInfo* Memory() const = 0; + + // Return time of CPU spent in whole container + virtual IProcessorInfo* ProcessorInfo() const = 0; + + // Return information on network status of the container + virtual INetworkInterfaceIterator* NetworkInterfaces() const = 0; + + // Tells if the container is running or not + virtual bool IsRunning() const = 0; + + // Start the container with provided process + virtual bool Start(const string& command, IStringIterator& parameters) = 0; + + // Stops the running containerized process + virtual bool Stop(const uint32_t timeout /*ms*/) = 0; + }; + + struct IContainerProducer { + + virtual ~IContainerProducer() = default; + + // Initializes the producer + virtual uint32_t Initialize(const string& /* configuration */) = 0; + + // Shuts down the producer + virtual void Deinitialize()= 0; + + // Creates a new container + virtual Core::ProxyType Container(const string& id, IStringIterator& searchPaths, const string& logPath, const string& configuration) = 0; + }; + +} // ProcessContainers +} diff --git a/Source/extensions/processcontainers/ProcessContainer.h b/Source/extensions/processcontainers/ProcessContainer.h deleted file mode 100644 index ed5283846..000000000 --- a/Source/extensions/processcontainers/ProcessContainer.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2020 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "Module.h" - -namespace Thunder { -namespace ProcessContainers { - using IStringIterator = Core::IteratorType, const string>; - - class INetworkInterfaceIterator : public Core::IIterator, public Core::IReferenceCounted { - public: - ~INetworkInterfaceIterator() override = default; - - // Return interface name (eg. eth0) - virtual string Name() const = 0; - - // Return numver of ip addresses assigned to the interface - virtual uint16_t NumAddresses() const = 0; - - // Get n-th ip assigned to the container. Empty string - // is returned if n is larger than NumAddresses() - virtual string Address(const uint16_t n = 0) const = 0; - }; - - class IMemoryInfo : public Core::IReferenceCounted { - public: - ~IMemoryInfo() override = default; - - virtual uint64_t Allocated() const = 0; // in bytes, returns UINT64_MAX on error - virtual uint64_t Resident() const = 0; // in bytes, returns UINT64_MAX on error - virtual uint64_t Shared() const = 0; // in bytes, returns UINT64_MAX on error - }; - - class IProcessorInfo : public Core::IReferenceCounted { - public: - ~IProcessorInfo() override = default; - - // total usage of cpu, in nanoseconds. Returns UINT64_MAX on error - virtual uint64_t TotalUsage() const = 0; - - // cpu usage per core in nanoseconds. Returns UINT64_MAX on error - virtual uint64_t CoreUsage(uint32_t coreNum) const = 0; - - // return number of cores - virtual uint16_t NumberOfCores() const = 0; - }; - - class IContainerIterator : public Core::IIterator, public Core::IReferenceCounted { - public: - ~IContainerIterator() override = default; - - // Return Id of container - virtual const string& Id() const = 0; - }; - - class IContainer : public Core::IReferenceCounted { - public: - ~IContainer() override = default; - - // Return the Name of the Container - virtual const string& Id() const = 0; - - // Get PID of the container in Host namespace - virtual uint32_t Pid() const = 0; - - // Return memory usage statistics for the whole container - virtual IMemoryInfo* Memory() const = 0; - - // Return time of CPU spent in whole container - virtual IProcessorInfo* ProcessorInfo() const = 0; - - // Return information on network status of the container - virtual INetworkInterfaceIterator* NetworkInterfaces() const = 0; - - // Tells if the container is running or not - virtual bool IsRunning() const = 0; - - // Start the container with provided process. Returns true if process is started successfully - virtual bool Start(const string& command, IStringIterator& parameters) = 0; - - // Stops the running containerized process. Returns true when stopped. - // Note: if timeout == 0, call is asynchronous - virtual bool Stop(const uint32_t timeout /*ms*/) = 0; - }; - - struct EXTERNAL IContainerAdministrator { - static IContainerAdministrator& Instance(); - virtual ~IContainerAdministrator() = default; - - /** - Creates new Container instance - - \param id Name of the created created container. - \param searchpaths List of locations, which will be checked (in order from first to last) - for presence of the container. Eg. when id="ContainerName", then it will - search for /Container. - \param containerLogPath Path to the folder, where logfile for created container will be created - \param configuration Implementation-specific configuration provided to the container. - */ - virtual IContainer* Container(const string& id, - IStringIterator& searchpaths, - const string& containerLogPath, - const string& configuration) - = 0; //searchpaths will be searched in order in which they are iterated - - // Enable logging for the container framework - virtual void Logging(const string& globalLogPath, const string& loggingoptions) = 0; - - // Returns ids of all created containers - virtual IContainerIterator* Containers() const = 0; - - // Return a container by its ID. Returns nullptr if container is not found - // It needs to be released. - virtual IContainer* Get(const string& id) = 0; - }; -} // ProcessContainers -} // Thunder diff --git a/Source/extensions/processcontainers/common/BaseAdministrator.h b/Source/extensions/processcontainers/common/BaseAdministrator.h deleted file mode 100644 index 67a8bcda9..000000000 --- a/Source/extensions/processcontainers/common/BaseAdministrator.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2020 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "processcontainers/ProcessContainer.h" -#include "processcontainers/common/BaseContainerIterator.h" - -namespace Thunder { -namespace ProcessContainers { - - template - class BaseContainerAdministrator : public IContainerAdministrator { - public: - BaseContainerAdministrator() - : _containers() - , _adminLock() - { - } - - ~BaseContainerAdministrator() override - { - if (_containers.size() > 0) { - TRACE_L1("There are still active containers when shutting down administrator!"); - - while (_containers.size() > 0) { - _containers.back()->Release(); - _containers.pop_back(); - } - } - } - - IContainerIterator* Containers() const override - { - std::vector containers; - containers.reserve(_containers.size()); - - _adminLock.Lock(); - for (auto& container : _containers) { - containers.push_back(container->Id()); - } - _adminLock.Unlock(); - - return new BaseContainerIterator(std::move(containers)); - } - - IContainer* Get(const string& id) override - { - IContainer* result = nullptr; - - _adminLock.Lock(); - auto found = std::find_if(_containers.begin(), _containers.end(), [&id](const IContainer* c) { return c->Id() == id; }); - if (found != _containers.end()) { - result = *found; - result->AddRef(); - } - _adminLock.Unlock(); - - return result; - } - - // Called only from CONTAINER when destructing instance! - void RemoveContainer(CONTAINER* container) - { - _adminLock.Lock(); - _containers.remove(container); - _adminLock.Unlock(); - } - - void InternalLock() const - { - _adminLock.Lock(); - } - - void InternalUnlock() const - { - _adminLock.Unlock(); - } - - protected: - // Must be called in internal Lock! - void InsertContainer(CONTAINER* container) - { - _containers.push_back(container); - } - - private: - std::list _containers; - mutable Core::CriticalSection _adminLock; - }; - -} // ProcessContainers -} // Thunder diff --git a/Source/extensions/processcontainers/common/BaseContainerIterator.h b/Source/extensions/processcontainers/common/BaseContainerIterator.h index e650c2a25..a593ac04a 100644 --- a/Source/extensions/processcontainers/common/BaseContainerIterator.h +++ b/Source/extensions/processcontainers/common/BaseContainerIterator.h @@ -19,8 +19,8 @@ #pragma once -#include "processcontainers/ProcessContainer.h" -#include "processcontainers/common/BaseRefCount.h" +#include "processcontainers/IProcessContainers.h" +#include "BaseRefCount.h" namespace Thunder { namespace ProcessContainers { diff --git a/Source/extensions/processcontainers/common/CGroupContainerInfo.h b/Source/extensions/processcontainers/common/CGroupContainerInfo.h index d4a6c7653..8ae6057b8 100644 --- a/Source/extensions/processcontainers/common/CGroupContainerInfo.h +++ b/Source/extensions/processcontainers/common/CGroupContainerInfo.h @@ -19,7 +19,6 @@ #pragma once -#include "processcontainers/ProcessContainer.h" #include "processcontainers/common/BaseRefCount.h" namespace Thunder { diff --git a/Source/extensions/processcontainers/implementations/AWCImplementation/CMakeLists.txt b/Source/extensions/processcontainers/implementations/AWCImplementation/CMakeLists.txt new file mode 100644 index 000000000..1703ff78e --- /dev/null +++ b/Source/extensions/processcontainers/implementations/AWCImplementation/CMakeLists.txt @@ -0,0 +1,62 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2024 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(CONTAINER_NAME AWC) +set(TARGET ${NAMESPACE}ProcessContainers${CONTAINER_NAME}) +message("Setup ${TARGET}") + +find_package(LXC REQUIRED) +find_package(Slauncher REQUIRED) +find_package(LibGio REQUIRED) +find_package(LibGioUnix REQUIRED) +find_package(LibGlib REQUIRED) +find_package(LibGobject REQUIRED) + +list(APPEND CONTAINERS ${TARGET}) +set(CONTAINERS ${CONTAINERS} PARENT_SCOPE) + +add_library(${TARGET} STATIC + AWCImplementation.cpp + AWCContainerAdministrator.cpp + AWCContainerBase.cpp + AWCProxyContainer.cpp + ) + +target_include_directories(${TARGET} + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/../../../ + Slauncher::Slauncher + ${LIBGIO_INCLUDE_DIRS} + ${LIBGIOUNIX_INCLUDE_DIRS} + ${LIBGLIB_INCLUDE_DIRS} + ${LIBGOBJECT_INCLUDE_DIRS} + ) + +target_link_libraries(${TARGET} + PRIVATE + Slauncher::Slauncher + ${LIBGIO_INCLUDE_LIBRARIES} + ${LIBGIOUNIX_LIBRARIES} + ${LIBGLIB_LIBRARIES} + ${LIBGOBJECT_LIBRARIES} + CompileSettings::CompileSettings + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Core::${NAMESPACE}Core + ) + +install(TARGETS ${TARGET} + DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development) diff --git a/Source/extensions/processcontainers/implementations/CRunImplementation/CMakeLists.txt b/Source/extensions/processcontainers/implementations/CRunImplementation/CMakeLists.txt new file mode 100644 index 000000000..b7495b163 --- /dev/null +++ b/Source/extensions/processcontainers/implementations/CRunImplementation/CMakeLists.txt @@ -0,0 +1,46 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2024 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(CONTAINER_NAME Crun) +set(TARGET ${NAMESPACE}ProcessContainers${CONTAINER_NAME}) +message("Setup ${TARGET}") + +find_package(LibCRUN REQUIRED) + +list(APPEND CONTAINERS ${TARGET}) +set(CONTAINERS ${CONTAINERS} PARENT_SCOPE) + +add_library(${TARGET} STATIC + LXCImplementation.cpp + ) + +target_include_directories(${TARGET} + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/../../../ + libcrun::libcrun + ) + +target_link_libraries(${TARGET} + PRIVATE + libcrun::libcrun + CompileSettings::CompileSettings + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Core::${NAMESPACE}Core + ) + +install(TARGETS ${TARGET} + DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development) diff --git a/Source/extensions/processcontainers/implementations/DobbyImplementation/CMakeLists.txt b/Source/extensions/processcontainers/implementations/DobbyImplementation/CMakeLists.txt new file mode 100644 index 000000000..dc4ee3a23 --- /dev/null +++ b/Source/extensions/processcontainers/implementations/DobbyImplementation/CMakeLists.txt @@ -0,0 +1,47 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2024 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(CONTAINER_NAME Dobby) +set(TARGET ${NAMESPACE}ProcessContainers${CONTAINER_NAME}) +message("Setup ${TARGET}") + +find_package(Dobby REQUIRED CONFIG) +find_package(Systemd REQUIRED) + +list(APPEND CONTAINERS ${TARGET}) +set(CONTAINERS ${CONTAINERS} PARENT_SCOPE) + +add_library(${TARGET} STATIC + DobbyImplementation.cpp + ) + +target_include_directories(${TARGET} + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/../../../ + ) + +target_link_libraries(${TARGET} + PRIVATE + DobbyClientLib + Systemd::Systemd + CompileSettings::CompileSettings + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Core::${NAMESPACE}Core + ) + +install(TARGETS ${TARGET} + DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development) diff --git a/Source/extensions/processcontainers/implementations/LXCImplementation/CMakeLists.txt b/Source/extensions/processcontainers/implementations/LXCImplementation/CMakeLists.txt new file mode 100644 index 000000000..a8f57c0b5 --- /dev/null +++ b/Source/extensions/processcontainers/implementations/LXCImplementation/CMakeLists.txt @@ -0,0 +1,46 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2024 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(CONTAINER_NAME LXC) +set(TARGET ${NAMESPACE}ProcessContainers${CONTAINER_NAME}) +message("Setup ${TARGET}") + +find_package(LXC REQUIRED) + +list(APPEND CONTAINERS ${TARGET}) +set(CONTAINERS ${CONTAINERS} PARENT_SCOPE) + +add_library(${TARGET} STATIC + LXCImplementation.cpp + ) + +target_include_directories(${TARGET} + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/../../../ + LXC::LXC + ) + +target_link_libraries(${TARGET} + PRIVATE + LXC::LXC + CompileSettings::CompileSettings + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Core::${NAMESPACE}Core + ) + +install(TARGETS ${TARGET} + DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development) diff --git a/Source/extensions/processcontainers/implementations/LXCImplementation/LXCImplementation.cpp b/Source/extensions/processcontainers/implementations/LXCImplementation/LXCImplementation.cpp index d20354475..2e43d8311 100644 --- a/Source/extensions/processcontainers/implementations/LXCImplementation/LXCImplementation.cpp +++ b/Source/extensions/processcontainers/implementations/LXCImplementation/LXCImplementation.cpp @@ -18,11 +18,18 @@ */ #include "LXCImplementation.h" +#include "processcontainers/Messaging.h" +#include "processcontainers/ContainerProducer.h" +#include "processcontainers/ContainerAdministrator.h" #include "processcontainers/common/CGroupContainerInfo.h" namespace Thunder { namespace ProcessContainers { + + static constexpr TCHAR const* logFileName = _T("lxclogging.log"); + static constexpr TCHAR const* configFileName = _T("config"); + LXCNetworkInterfaceIterator::LXCNetworkInterfaceIterator(LxcContainerType* lxcContainer) : _current(UINT32_MAX) , _interfaces() @@ -176,6 +183,8 @@ namespace ProcessContainers { _attach = config.Attach.Value(); #endif + ::lxc_container_get(lxcContainer); + InheritRequestedEnvironment(); if ((config.ConsoleLogging.IsSet() == true) && (config.ConsoleLogging.Value() != _T("0"))) { @@ -205,11 +214,13 @@ namespace ProcessContainers { LXCContainer::~LXCContainer() { - ASSERT(_lxcContainer == nullptr); // should be released by now! + ASSERT(_lxcContainer != nullptr); - TRACE(Debug, (_T("LXC container [%s] released"), _name.c_str())); + int result = ::lxc_container_put(_lxcContainer); + ASSERT(result == 1); + DEBUG_VARIABLE(result); - static_cast(LXCContainerAdministrator::Instance()).RemoveContainer(this); + TRACE(Debug, (_T("LXC container [%s] released"), _name.c_str())); } const string& LXCContainer::Id() const @@ -364,7 +375,7 @@ namespace ProcessContainers { params.push_back(nullptr); - TRACE(Trace::Warning, (_T("Starting container [%s]..."), _name.c_str())); + TRACE(Debug, (_T("Starting container [%s]..."), _name.c_str())); _adminLock.Lock(); @@ -400,7 +411,7 @@ namespace ProcessContainers { if (result == true) { ASSERT(_pid != 0); - TRACE(Trace::Information, (_T("Container [%s] started successfully, PID=%u"), _name.c_str(), _pid)); + TRACE(Debug, (_T("Container [%s] started successfully, PID=%u"), _name.c_str(), _pid)); } else { TRACE(Trace::Error, (_T("Failed to start LXC container [%s]"), _name.c_str())); @@ -450,43 +461,6 @@ namespace ProcessContainers { return (result); } - uint32_t LXCContainer::AddRef() const - { - _adminLock.Lock(); - - ASSERT(_lxcContainer != nullptr); - - ::lxc_container_get(_lxcContainer); - - _adminLock.Unlock(); - - uint32_t result = BaseRefCount::AddRef(); - - return (result); - } - - uint32_t LXCContainer::Release() const - { - _adminLock.Lock(); - - ASSERT(_lxcContainer != nullptr); - - const uint32_t lxcresult = ::lxc_container_put(_lxcContainer); - - if (lxcresult == 1) { - // Last put, container is gone - _lxcContainer = nullptr; - } - - _adminLock.Unlock(); - - const uint32_t result = BaseRefCount::Release(); - - ASSERT((result != Thunder::Core::ERROR_DESTRUCTION_SUCCEEDED) || (lxcresult == 1)); // if 1 is returned, lxc also released the container - - return (result); - } - void LXCContainer::InheritRequestedEnvironment() { // According to https://linuxcontainers.org/lxc/manpages/man5/lxc.container.conf.5.html#lbBM we @@ -528,24 +502,14 @@ namespace ProcessContainers { } } - LXCContainerAdministrator::LXCContainerAdministrator() - : BaseContainerAdministrator() - { - TRACE(Trace::Information, (_T("LXC library initialization, version: %s"), ::lxc_get_version())); - } - - LXCContainerAdministrator::~LXCContainerAdministrator() - { - ::lxc_log_close(); - } - - IContainer* LXCContainerAdministrator::Container(const string& name, IStringIterator& searchPaths, const string& containerLogDir, const string& configuration) + Core::ProxyType LXCContainerAdministrator::Container(const string& name, IStringIterator& searchPaths, + const string& containerLogDir, const string& configuration) /* override */ { - LXCContainer* container = nullptr; + Core::ProxyType container; searchPaths.Reset(0); - while ((container == nullptr) && (searchPaths.Next() == true)) { + while ((container.IsValid() == false) && (searchPaths.Next() == true)) { LxcContainerType** clist = nullptr; @@ -559,25 +523,18 @@ namespace ProcessContainers { int32_t index = 0; - while ((container == nullptr) && (index < count)) { + while ((container.IsValid() == false) && (index < count)) { LxcContainerType* c = clist[index]; ASSERT(c != nullptr); if (::strcmp(c->name, "Container") == 0) { - container = new LXCContainer(name, c, containerLogDir, configuration, containerPath); - ASSERT(container != nullptr); - - InternalLock(); - InsertContainer(container); - InternalUnlock(); - - TRACE(Trace::Information, (_T("LXC container [%s] created from %sContainer/%s"), name.c_str(), containerPath.c_str(), configFileName)); - } - else { - lxc_container_put(c); + container = ContainerAdministrator::Instance().Create(name, c, containerLogDir, configuration, containerPath); + TRACE(Debug, (_T("LXC container [%s] created from %sContainer/%s"), name.c_str(), containerPath.c_str(), configFileName)); } + ::lxc_container_put(c); + ++index; } @@ -585,7 +542,7 @@ namespace ProcessContainers { } } - if (container == nullptr) { + if (container.IsValid() == false) { TRACE(Trace::Error, (_T("LXC container configuration for container [%s] not found!"), name.c_str())); } @@ -607,17 +564,18 @@ namespace ProcessContainers { ASSERT(globalLogDir.empty() == false); // Create logging directory - Core::Directory logDir(globalLogDir.c_str()); + const string dirname = Core::Directory::Normalize(globalLogDir); + Core::Directory logDir(dirname.c_str()); logDir.CreatePath(); bool valid{}; - const string filename(Core::File::Normalize(globalLogDir + logFileName, valid)); + const string filename(Core::File::Normalize(dirname + logFileName, valid)); ASSERT(valid == true); const std::string file = Core::ToString(filename); const std::string level = Core::ToString(loggingOptions); - const std::string lxcpath = Core::ToString(globalLogDir); + const std::string lxcpath = Core::ToString(dirname); lxc_log log; log.name = "ThunderLXC"; @@ -636,15 +594,52 @@ namespace ProcessContainers { } } - IContainerAdministrator& IContainerAdministrator::Instance() + uint32_t LXCContainerAdministrator::Initialize(const string& configuration) /* override */ + { + uint32_t result = Core::ERROR_NONE; + + TRACE(Trace::Information, (_T("LXC library initialization, version: %s"), ::lxc_get_version())); + + class Config : public Core::JSON::Container { + public: + Config() + : Logging() + , LoggingDir() + { + Add(_T("logging"), &Logging); + Add(_T("loggingdir"), &LoggingDir); + } + + ~Config() override = default; + + Config(const Config&) = delete; + Config(Config&&) = delete; + Config& operator=(const Config&) = delete; + Config& operator=(Config&&) = delete; + + public: + Core::JSON::String Logging; + Core::JSON::String LoggingDir; + }; + + Config config; + config.FromString(configuration); + + if ((config.Logging.IsSet() == true) && (config.LoggingDir.IsSet() == true)) { + Logging(config.LoggingDir, config.Logging); + } + + return (result); + } + + void LXCContainerAdministrator::Deinitialize() /* override */ { - static LXCContainerAdministrator& administrator = Core::SingletonType::Instance(); - return (administrator); + ::lxc_log_close(); } - constexpr char const* LXCContainerAdministrator::logFileName; - constexpr char const* LXCContainerAdministrator::configFileName; - constexpr uint32_t LXCContainerAdministrator::maxReadSize; + // FACTORY REGISTRATION + static ContainerProducerRegistrationType registration; + +} // namespace ProcessContainers -} } diff --git a/Source/extensions/processcontainers/implementations/LXCImplementation/LXCImplementation.h b/Source/extensions/processcontainers/implementations/LXCImplementation/LXCImplementation.h index 38ba0751c..4718a4856 100644 --- a/Source/extensions/processcontainers/implementations/LXCImplementation/LXCImplementation.h +++ b/Source/extensions/processcontainers/implementations/LXCImplementation/LXCImplementation.h @@ -19,16 +19,11 @@ #pragma once -#include "processcontainers/ProcessContainer.h" -#include "processcontainers/common/BaseAdministrator.h" -#include "processcontainers/common/BaseRefCount.h" -#include "Module.h" -#include "Messaging.h" -#include #include -#include -#include -#include + +#include "processcontainers/Messaging.h" +#include "processcontainers/ContainerAdministrator.h" +#include "processcontainers/common/CGroupContainerInfo.h" namespace Thunder { namespace ProcessContainers { @@ -61,7 +56,7 @@ namespace ProcessContainers { std::vector _interfaces; }; - class LXCContainer : public BaseRefCount { + class LXCContainer : public IContainer { private: class Config : public Core::JSON::Container { public: @@ -128,17 +123,20 @@ namespace ProcessContainers { private: static constexpr uint32_t defaultTimeOutInMSec = 2000; - friend class LXCContainerAdministrator; - LXCContainer(const string& name, LxcContainerType* lxcContainer, const string& containerLogDir, const string& configuration, const string& lxcPath); - public: + LXCContainer(const string& name, LxcContainerType* lxcContainer, const string& containerLogDir, + const string& configuration, const string& lxcPath); + + LXCContainer() = delete; + ~LXCContainer() override; + LXCContainer(const LXCContainer&) = delete; LXCContainer(LXCContainer&&) = delete; - ~LXCContainer() override; LXCContainer& operator=(const LXCContainer&) = delete; LXCContainer& operator=(LXCContainer&&) = delete; public: + containertype Type() const override { return IContainer::LXC; } const string& Id() const override; uint32_t Pid() const override; IMemoryInfo* Memory() const override; @@ -148,10 +146,7 @@ namespace ProcessContainers { bool Start(const string& command, ProcessContainers::IStringIterator& parameters) override; bool Stop(const uint32_t timeout /*ms*/) override; - uint32_t AddRef() const override; - uint32_t Release() const override; - - protected: + private: void InheritRequestedEnvironment(); private: @@ -167,26 +162,24 @@ namespace ProcessContainers { #endif }; - class LXCContainerAdministrator : public BaseContainerAdministrator { - friend class LXCContainer; - friend class Core::SingletonType; - - private: - static constexpr TCHAR const* logFileName = "lxclogging.log"; - static constexpr TCHAR const* configFileName = "config"; - static constexpr uint32_t maxReadSize = 32 * (1 << 10); // 32KiB - - private: - LXCContainerAdministrator(); - + class LXCContainerAdministrator : public IContainerProducer { public: + LXCContainerAdministrator() = default; + ~LXCContainerAdministrator() override = default; + LXCContainerAdministrator(const LXCContainerAdministrator&) = delete; + LXCContainerAdministrator(LXCContainerAdministrator&&) = delete; LXCContainerAdministrator& operator=(const LXCContainerAdministrator&) = delete; - ~LXCContainerAdministrator() override; + LXCContainerAdministrator& operator=(LXCContainerAdministrator&&) = delete; - ProcessContainers::IContainer* Container(const string& name, IStringIterator& searchpaths, const string& containerLogDir, const string& configuration) override; + public: + uint32_t Initialize(const string& config) override; + void Deinitialize() override; + Core::ProxyType Container(const string& name, IStringIterator& searchpaths, + const string& containerLogDir, const string& configuration) override; - void Logging(const string& globalLogDir, const string& loggingOptions) override; + private: + void Logging(const string& globalLogDir, const string& loggingOptions); }; } diff --git a/Source/extensions/processcontainers/implementations/RunCImplementation/CMakeLists.txt b/Source/extensions/processcontainers/implementations/RunCImplementation/CMakeLists.txt new file mode 100644 index 000000000..fd5c50439 --- /dev/null +++ b/Source/extensions/processcontainers/implementations/RunCImplementation/CMakeLists.txt @@ -0,0 +1,47 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2024 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(CONTAINER_NAME RunC) +set(TARGET ${NAMESPACE}ProcessContainers${CONTAINER_NAME}) +message("Setup ${TARGET}") + +list(APPEND CONTAINERS ${TARGET}) +set(CONTAINERS ${CONTAINERS} PARENT_SCOPE) + +add_library(${TARGET} STATIC + RunCImplementation.cpp + ) + +set_target_properties(${TARGET} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + ) + +target_include_directories(${TARGET} + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/../../../ + ) + +target_link_libraries(${TARGET} + PRIVATE + CompileSettings::CompileSettings + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Core::${NAMESPACE}Core + ) + +install(TARGETS ${TARGET} + DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development) diff --git a/Source/extensions/processcontainers/implementations/RunCImplementation/RunCImplementation.cpp b/Source/extensions/processcontainers/implementations/RunCImplementation/RunCImplementation.cpp index 30f0334b6..0a8b98d9a 100644 --- a/Source/extensions/processcontainers/implementations/RunCImplementation/RunCImplementation.cpp +++ b/Source/extensions/processcontainers/implementations/RunCImplementation/RunCImplementation.cpp @@ -18,13 +18,16 @@ */ #include "RunCImplementation.h" -#include +#include "processcontainers/Messaging.h" +#include "processcontainers/ContainerProducer.h" +#include "processcontainers/ContainerAdministrator.h" +#include "processcontainers/common/CGroupContainerInfo.h" namespace Thunder { namespace ProcessContainers { - static constexpr uint32_t DEFAULT_TIMEOUT = 5000 /* ms */; + static constexpr uint32_t DEFAULT_TIMEOUT = 2000 /* ms */; namespace runc { @@ -137,16 +140,7 @@ namespace ProcessContainers { // runc --log create --no-new-keyring --bundle - const uint32_t result = Execute(options); - - if (result == Core::ERROR_NONE) { - TRACE_L2("Successfuly created container '%s'", id.c_str()); - } - else { - TRACE_L1("Failed to create container '%s'", id.c_str()); - } - - return (result); + return (Execute(options)); } uint32_t Exec(const string& id, const string& bundlePath, const string& command, IStringIterator* parameters = nullptr) const { @@ -177,16 +171,7 @@ namespace ProcessContainers { // runc --log exec --detach [--env ...] [command-parameters...] - const uint32_t result = Execute(options); - - if (result == Core::ERROR_NONE) { - TRACE_L2("Successfuly executed command '%s' on container '%s'", command.c_str(), id.c_str()); - } - else { - TRACE_L1("Failed to execute command '%s' on container '%s'", command.c_str(), id.c_str()); - } - - return (result); + return (Execute(options)); } uint32_t Delete(const string& id, const uint32_t timeout = 0) const { @@ -204,16 +189,7 @@ namespace ProcessContainers { // runc --log delete --force - const uint32_t result = Execute(options, nullptr, timeout); - - if (result == Core::ERROR_NONE) { - TRACE_L2("Successfuly deleted container '%s'", id.c_str()); - } - else { - TRACE_L1("Failed to delete container '%s'", id.c_str()); - } - - return (result); + return (Execute(options, nullptr, timeout)); } uint32_t State(const string& id, Info& info) const { @@ -231,11 +207,6 @@ namespace ProcessContainers { if (result == Core::ERROR_NONE) { info.FromString(output); - - TRACE_L2("Successfully retrieved state from container '%s'", id.c_str()); - } - else { - TRACE_L1("Failed to retrieve state from container '%s'", id.c_str()); } return (result); @@ -248,16 +219,16 @@ namespace ProcessContainers { // runc list --quiet - const uint32_t result = Execute(options, &list); + return (Execute(options, &list)); + } + uint32_t Version(string& version) const + { + Options options; + options.Add(_T("--version")); - if (result == Core::ERROR_NONE) { - TRACE_L2("Successfully retrieved container list (%s)", list.c_str()); - } - else { - TRACE_L1("Failed to retrieve container list"); - } + // runc --version - return (result); + return (Execute(options, &version)); } private: @@ -345,7 +316,7 @@ namespace ProcessContainers { string value; if ((Core::SystemInfo::GetEnvironment(hostVar, value) == true) && (value.size() != 0)) { - TRACE_L1("Using host environment variable: %s", hostVar.c_str()); + TRACE_GLOBAL(Debug, (_T("Using host environment variable: %s"), hostVar.c_str())); options.Add(_T("--env")).Add(var + _T("=") + value); } } @@ -356,7 +327,7 @@ namespace ProcessContainers { file.Close(); } else { - TRACE_L1("Failed to open config.json file"); + TRACE_GLOBAL(Trace::Error, (_T("Failed to open config.json file"))); } } @@ -370,18 +341,27 @@ namespace ProcessContainers { // ContainerAdministrator - IContainerAdministrator& ProcessContainers::IContainerAdministrator::Instance() + uint32_t RunCContainerAdministrator::Initialize(const string& configuration VARIABLE_IS_NOT_USED) { - static RunCContainerAdministrator& runCContainerAdministrator = Core::SingletonType::Instance(); - return (runCContainerAdministrator); + string version; + + runc::Runner runner; + runner.Version(version); + + DEBUG_VARIABLE(runner); + DEBUG_VARIABLE(version); + + TRACE(Trace::Information, (_T("runc runtime: %s"), version.substr(0, version.find('\n')).c_str())); + + return (Core::ERROR_NONE); } - IContainer* RunCContainerAdministrator::Container(const string& id, IStringIterator& searchPaths, const string& logPath, + Core::ProxyType RunCContainerAdministrator::Container(const string& id, IStringIterator& searchPaths, const string& logPath, const string& configuration VARIABLE_IS_NOT_USED) /* override */ { ASSERT(id.empty() == false); - RunCContainer* container = nullptr; + Core::ProxyType container; searchPaths.Reset(0); @@ -391,31 +371,22 @@ namespace ProcessContainers { Core::File configFile(containerPath + runc::CONFIG_FILE); if (configFile.Exists() == true) { - container = new RunCContainer(id, containerPath, logPath); - ASSERT(container != nullptr); - - InternalLock(); - InsertContainer(container); - InternalUnlock(); + container = ContainerAdministrator::Instance().Create(id, containerPath, logPath); + ASSERT(container.IsValid() == true); - TRACE(Trace::Information, (_T("Container '%s' created from '%s'"), id.c_str(), configFile.Name().c_str())); + TRACE(Debug, (_T("Container '%s' created from '%s'"), id.c_str(), configFile.Name().c_str())); break; } } - if (container == nullptr) { - TRACE(Trace::Error, (_T("Runc container configuration not found!"))); + if (container.IsValid() == false) { + TRACE(Trace::Error, (_T("Runc container configuration for '%s' not found!"), id.c_str())); } return (container); } - void RunCContainerAdministrator::Logging(const string& logDir VARIABLE_IS_NOT_USED, const string& loggingOptions VARIABLE_IS_NOT_USED) /* override */ - { - TRACE_L1("runc logging only on container level"); - } - // Container @@ -437,10 +408,10 @@ namespace ProcessContainers { { _adminLock.Lock(); - static_cast(ProcessContainers::IContainerAdministrator::Instance()).RemoveContainer(this); - InternalPurge(DEFAULT_TIMEOUT); + TRACE(Debug, (_T("runc container '%s' released"), _id.c_str())); + _adminLock.Unlock(); } @@ -460,8 +431,6 @@ namespace ProcessContainers { if (_runner->State(_id, info) == Core::ERROR_NONE) { _pid = info.Pid; - - TRACE_L1("Container '%s' PID is %u", _id.c_str(), _pid); } } @@ -485,12 +454,7 @@ namespace ProcessContainers { if (_runner->State(_id, info) == Core::ERROR_NONE) { result = info.Status.IsSet(); - - TRACE_L1("Container '%s' is %s", _id.c_str(), info.Status.Value().c_str()); - } - } - else { - TRACE_L1("Container '%s' is not available", _id.c_str()); + } } } @@ -501,7 +465,7 @@ namespace ProcessContainers { { ASSERT(command.empty() == false); - TRACE(Trace::Information, (_T("Starting container '%s'..."), _id.c_str())); + TRACE(Debug, (_T("Starting container '%s'..."), _id.c_str())); _adminLock.Lock(); @@ -526,7 +490,7 @@ namespace ProcessContainers { { bool result = false; - TRACE_L1("Stopping container '%s'...", _id.c_str()); + TRACE(Debug, ("Stopping container '%s'...", _id.c_str())); _adminLock.Lock(); @@ -537,7 +501,7 @@ namespace ProcessContainers { if ((purgeResult == Core::ERROR_NONE) || (purgeResult == Core::ERROR_ALREADY_RELEASED)) { if (IsRunning() == false) { - TRACE(Trace::Information, (_T("Container '%s' has been stopped"), _id.c_str())); + TRACE(Debug, (_T("Container '%s' has been stopped"), _id.c_str())); result = true; } else { @@ -576,6 +540,10 @@ namespace ProcessContainers { return (IsRunning()? _runner->Delete(_id, timeout) : static_cast(Core::ERROR_ALREADY_RELEASED)); } + + // FACTORY REGISTRATION + static ContainerProducerRegistrationType registration; + } // namespace ProcessContainers } \ No newline at end of file diff --git a/Source/extensions/processcontainers/implementations/RunCImplementation/RunCImplementation.h b/Source/extensions/processcontainers/implementations/RunCImplementation/RunCImplementation.h index 5b37a4326..9a3a3589f 100644 --- a/Source/extensions/processcontainers/implementations/RunCImplementation/RunCImplementation.h +++ b/Source/extensions/processcontainers/implementations/RunCImplementation/RunCImplementation.h @@ -19,9 +19,8 @@ #pragma once -#include "processcontainers/Messaging.h" -#include "processcontainers/ProcessContainer.h" -#include "processcontainers/common/BaseAdministrator.h" +#include "processcontainers/IProcessContainers.h" +#include "processcontainers/ContainerAdministrator.h" #include "processcontainers/common/BaseRefCount.h" #include "processcontainers/common/CGroupContainerInfo.h" @@ -32,20 +31,21 @@ namespace ProcessContainers { class Runner; } - class RunCContainer : public BaseRefCount { - private: - friend class RunCContainerAdministrator; + class RunCContainer : public IContainer { + public: RunCContainer(const string& id, const string& path, const string& logPath); - public: + RunCContainer() = delete; + ~RunCContainer() override; + RunCContainer(const RunCContainer&) = delete; - RunCContainer& operator=(const RunCContainer&) = delete; RunCContainer(RunCContainer&&) = delete; + RunCContainer& operator=(const RunCContainer&) = delete; RunCContainer& operator=(RunCContainer&&) = delete; - ~RunCContainer() override; public: // IContainer methods + containertype Type() const override { return IContainer::RUNC; } const string& Id() const override; uint32_t Pid() const override; bool IsRunning() const override; @@ -66,23 +66,22 @@ namespace ProcessContainers { std::unique_ptr _runner; }; - class RunCContainerAdministrator : public BaseContainerAdministrator { - private: - friend class RunCContainer; - friend class Core::SingletonType; + class RunCContainerAdministrator : public IContainerProducer { + public: RunCContainerAdministrator() = default; + ~RunCContainerAdministrator() override = default; - public: RunCContainerAdministrator(const RunCContainerAdministrator&) = delete; RunCContainerAdministrator(RunCContainerAdministrator&&) = delete; RunCContainerAdministrator& operator=(const RunCContainerAdministrator&) = delete; RunCContainerAdministrator& operator=(RunCContainerAdministrator&&) = delete; - ~RunCContainerAdministrator() override = default; public: - // IContainerAdministrator methods - IContainer* Container(const string& id, IStringIterator& searchpaths, const string& logpath, const string& configuration) override; - void Logging(const string& logDir, const string& loggingOptions) override; + Core::ProxyType Container(const string& id, IStringIterator& searchpaths, + const string& logpath, const string& configuration) override; + + uint32_t Initialize(const string& configuration) override; + void Deinitialize() { } }; } // namespace ProcessContainers diff --git a/Source/extensions/processcontainers/processcontainers.h b/Source/extensions/processcontainers/processcontainers.h new file mode 100644 index 000000000..f862bee42 --- /dev/null +++ b/Source/extensions/processcontainers/processcontainers.h @@ -0,0 +1,29 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef MODULE_NAME +#error "Please define a MODULE_NAME that describes the binary/library you are building." +#endif + +#include "IProcessContainers.h" +#include "ContainerAdministrator.h" + +WPEFRAMEWORK_NESTEDNAMESPACE_COMPATIBILIY(ProcessContainers) diff --git a/cmake/modules/FindLXC.cmake b/cmake/modules/FindLXC.cmake index 624520971..d5ffcbe32 100644 --- a/cmake/modules/FindLXC.cmake +++ b/cmake/modules/FindLXC.cmake @@ -44,7 +44,6 @@ if(${PC_LXC_FOUND}) PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" IMPORTED_LOCATION "${LXC_LIBRARY}" - INTERFACE_COMPILE_DEFINITIONS "LXC" INTERFACE_COMPILE_OPTIONS "${PC_LXC_CFLAGS_OTHER}" INTERFACE_INCLUDE_DIRECTORIES "${PC_LXC_INCLUDE_DIRS}" INTERFACE_LINK_LIBRARIES "${PC_LXC_LIBRARIES}" From d5443b271c29c9b302322cef00d36859c35ec379 Mon Sep 17 00:00:00 2001 From: nxtum <94901881+nxtum@users.noreply.github.com> Date: Thu, 15 Aug 2024 11:21:40 +0200 Subject: [PATCH 2/4] Added buildinfo method (build flags) for QA report (#1712) * Added buildinfo method for QA report * Added public compilesettings * Updated Controller.cpp from seb comments * Updated CMakeLists.txt in core * added priv compilesettings * Added threadcount to buildinfo * Added unused variable macro * systemtype and universal build flags * enum extensions * spelling * empty commit to test action * shortened namespaces and whitespace * extra line --------- Co-authored-by: Pierre Wielders --- Source/Thunder/CMakeLists.txt | 1 + Source/Thunder/Controller.cpp | 83 ++++++++++++++++++++++++++++++ Source/Thunder/Controller.h | 1 + Source/Thunder/PluginServer.h | 4 +- Source/Thunder/Probe.cpp | 2 +- Source/plugins/IController.h | 39 ++++++++++++++ cmake/common/CompileSettings.cmake | 2 + 7 files changed, 129 insertions(+), 3 deletions(-) diff --git a/Source/Thunder/CMakeLists.txt b/Source/Thunder/CMakeLists.txt index fb3c0cdf8..4b52c9026 100644 --- a/Source/Thunder/CMakeLists.txt +++ b/Source/Thunder/CMakeLists.txt @@ -60,6 +60,7 @@ endif() target_link_libraries(${TARGET} PRIVATE + CompileSettings::CompileSettings CompileSettingsDebug::CompileSettingsDebug ${NAMESPACE}Core::${NAMESPACE}Core ${NAMESPACE}Cryptalgo::${NAMESPACE}Cryptalgo diff --git a/Source/Thunder/Controller.cpp b/Source/Thunder/Controller.cpp index 3103fd239..d66ea2baa 100644 --- a/Source/Thunder/Controller.cpp +++ b/Source/Thunder/Controller.cpp @@ -1363,5 +1363,88 @@ namespace Plugin { // also notify the JSON RPC listeners (if any) Exchange::Controller::JLifeTime::Event::StateChange(*this, callsign, state, reason); } + + Core::hresult Controller::BuildInfo(IMetadata::Data::BuildInfo& buildInfo) const + { + #if defined(__WINDOWS__) + buildInfo.SystemType = IMetadata::Data::BuildInfo::SYSTEM_WINDOWS; + #elif defined(__LINUX__) + buildInfo.SystemType = IMetadata::Data::BuildInfo::SYSTEM_LINUX; + #elif defined(__APPLE__) + buildInfo.SystemType = IMetadata::Data::BuildInfo::SYSTEM_MACOS; + #else + #error No system type detected + #endif + + #if defined(__DEBUG__) + #if defined(_THUNDER_DEBUG_OPTIMIZED_) + buildInfo.BuildType = IMetadata::Data::BuildInfo::DEBUG_OPTIMIZED; + #else + buildInfo.BuildType = IMetadata::Data::BuildInfo::DEBUG; + #endif + #else // !__DEBUG__ + #if defined(_THUNDER_NDEBUG_DEB_INFO) + buildInfo.BuildType = IMetadata::Data::BuildInfo::RELEASE_WITH_DEBUG_INFO; + #elif defined(_THUNDER_PRODUCTION) + buildInfo.BuildType = IMetadata::Data::BuildInfo::PRODUCTION; + #else + buildInfo.BuildType = IMetadata::Data::BuildInfo::RELEASE; + #endif + #endif + + #ifdef _TRACE_LEVEL + buildInfo.TraceLevel = _TRACE_LEVEL; + #endif + + uint8_t extensions = 0; + #ifdef __CORE_WARNING_REPORTING__ + extensions |= IMetadata::Data::BuildInfo::WARNING_REPORTING; + #endif + #ifdef __CORE_BLUETOOTH_SUPPORT__ + extensions |= IMetadata::Data::BuildInfo::BLUETOOTH; + #endif + #ifdef HIBERNATE_SUPPORT_ENABLED + extensions |= IMetadata::Data::BuildInfo::HIBERNATE; + #endif + #ifdef PROCESSCONTAINERS_ENABLED + extensions |= IMetadata::Data::BuildInfo::PROCESS_CONTAINERS; + #endif + + if (extensions != 0) { + buildInfo.Extensions = static_cast(extensions); + } + + #ifdef __CORE_MESSAGING__ + buildInfo.Messaging = true; + #else + buildInfo.Messaging= false; + #endif + + #ifdef __CORE_EXCEPTION_CATCHING__ + buildInfo.ExceptionCatching = true; + #else + buildInfo.ExceptionCatching = false; + #endif + + buildInfo.InstanceIDBits = (sizeof(Core::instance_id) * 8); + + #ifdef __CORE_CRITICAL_SECTION_LOG__ + buildInfo.DeadlockDetection = true; + #else + buildInfo.DeadlockDetection = false; + #endif + + #ifdef __CORE_NO_WCHAR_SUPPORT__ + buildInfo.WCharSupport = false; + #else + buildInfo.WCharSupport = true; + #endif + + #ifdef THREADPOOL_COUNT + buildInfo.ThreadPoolCount = THREADPOOL_COUNT; + #endif + + return (Core::ERROR_NONE); + } } } diff --git a/Source/Thunder/Controller.h b/Source/Thunder/Controller.h index bf026583a..bd7ed365a 100644 --- a/Source/Thunder/Controller.h +++ b/Source/Thunder/Controller.h @@ -330,6 +330,7 @@ namespace Plugin { Core::hresult Threads(IMetadata::Data::IThreadsIterator*& threads) const override; Core::hresult PendingRequests(IMetadata::Data::IPendingRequestsIterator*& requests) const override; Core::hresult Version(IMetadata::Data::Version& version) const override; + Core::hresult BuildInfo(IMetadata::Data::BuildInfo& buildInfo) const override; // IShells overrides Core::hresult Register(Exchange::Controller::IShells::INotification* sink) override; diff --git a/Source/Thunder/PluginServer.h b/Source/Thunder/PluginServer.h index eb9bab386..905639a9c 100644 --- a/Source/Thunder/PluginServer.h +++ b/Source/Thunder/PluginServer.h @@ -4184,7 +4184,7 @@ namespace PluginHost { } } } - void Send(const Core::ProxyType& response) override + void Send(const Core::ProxyType& response VARIABLE_IS_NOT_USED) override { if (_requestClose == true) { PluginHost::Channel::Close(0); @@ -4209,7 +4209,7 @@ namespace PluginHost { return (result); } - void Send(const Core::ProxyType& element) override + void Send(const Core::ProxyType& element VARIABLE_IS_NOT_USED) override { TRACE(SocketFlow, (element)); } diff --git a/Source/Thunder/Probe.cpp b/Source/Thunder/Probe.cpp index 18bfa149d..5c4d61037 100644 --- a/Source/Thunder/Probe.cpp +++ b/Source/Thunder/Probe.cpp @@ -174,7 +174,7 @@ namespace Plugin { } // Notification of a Response send. - /* virtual */ void Probe::Listener::Send(const Core::ProxyType& response) + /* virtual */ void Probe::Listener::Send(const Core::ProxyType& response VARIABLE_IS_NOT_USED) { TRACE(Discovery, (response, _destinations.front()._destination)); diff --git a/Source/plugins/IController.h b/Source/plugins/IController.h index 6ce91cbdc..47c1d705e 100644 --- a/Source/plugins/IController.h +++ b/Source/plugins/IController.h @@ -227,6 +227,41 @@ namespace Controller { uint8_t Patch /* @brief Patch number */; }; + struct BuildInfo { + + enum systemtype : uint8_t { + SYSTEM_WINDOWS /* @text:Windows */, + SYSTEM_LINUX /* @text:Linux */, + SYSTEM_MACOS /* @text:MacOS */ + }; + + enum buildtype : uint8_t { + DEBUG, + DEBUG_OPTIMIZED, + RELEASE_WITH_DEBUG_INFO, + RELEASE, + PRODUCTION + }; + + enum extensiontype : uint8_t { + WARNING_REPORTING = 1, + BLUETOOTH = 2, + HIBERBATE = 4, + PROCESS_CONTAINERS = 8 + }; + + systemtype SystemType /* @brief System type */; + buildtype BuildType /* @brief Build type */; + Core::OptionalType Extensions /* @bitmask */; + bool Messaging /* @brief Denotes whether Messaging is enabled*/; + bool ExceptionCatching /* @brief Denotes whether there is an exception */; + bool DeadlockDetection /* @brief Denotes whether deadlock detection is enabled */; + bool WCharSupport /* Denotes whether there is wchar support */; + uint8_t InstanceIDBits /* @brief Core instance bits */; + Core::OptionalType TraceLevel /* @brief Trace level */; + uint8_t ThreadPoolCount /* Number of configured threads on the threadpool */; + }; + struct CallStack { Core::instance_id Address /* @brief Memory address */; string Module /* @brief Module name */; @@ -341,6 +376,10 @@ namespace Controller { // @property // @brief Thread callstack virtual Core::hresult CallStack(const uint8_t thread /* @index */, Data::ICallStackIterator*& callstack /* @out */) const = 0; + + // @property + // @brief Build information + virtual Core::hresult BuildInfo(Data::BuildInfo& buildInfo /* @out */) const = 0; }; } // namespace Controller diff --git a/cmake/common/CompileSettings.cmake b/cmake/common/CompileSettings.cmake index c3943bacc..c7861393e 100644 --- a/cmake/common/CompileSettings.cmake +++ b/cmake/common/CompileSettings.cmake @@ -79,12 +79,14 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") elseif("${CMAKE_BUILD_TYPE}" STREQUAL "DebugOptimized") target_compile_definitions(CompileSettings INTERFACE _THUNDER_DEBUG) + target_compile_definitions(CompileSettings INTERFACE _THUNDER_DEBUG_OPTIMIZED) target_compile_definitions(CompileSettings INTERFACE _THUNDER_CALLSTACK_INFO) target_compile_options(CompileSettings INTERFACE -funwind-tables) set(CONFIG_DIR "DebugOptimized" CACHE STRING "Build config directory" FORCE) elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") target_compile_definitions(CompileSettings INTERFACE _THUNDER_NDEBUG) + target_compile_definitions(CompileSettings INTERFACE _THUNDER_NDEBUG_DEB_INFO) target_compile_definitions(CompileSettings INTERFACE _THUNDER_CALLSTACK_INFO) target_compile_options(CompileSettings INTERFACE -funwind-tables) set(CONFIG_DIR "RelWithDebInfo" CACHE STRING "Build config directory" FORCE) From 7e4fdbda0e5a8b8482fef755dbd1513834129730 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:14:47 +0200 Subject: [PATCH 3/4] Development/cyclicbuffer (#1726) * [Core/cyclicbuffer] : Initialize mutex and condition variable only at (the true) creation of the underlying buffer. Initialization of an already intialized mutex or condition variable may result in undefined behavior. In the context of multiple users one 'creates' and others 'open'. This allows differentiation similar to the contructor taking DataElementFile as argument. * [Core/cyclicbuffer] : Let (all) users 'wait' until the mutex and condition variables are properly initialized before first use. * [Core/cyclicbuffer] : replace getpid() with gettid(). The typical use case is inter-process, but it allows for multiple threads within a process as gettid() is unique over all processes. * [Core/cyclicbuffer] : POSIX requires a locked mutex before 'pthread_cond_wait' and 'pthread_cond_timedwait'. * [Core/cyclicbuffer] : make successive time samples monotonic increasing. * [Core/cyclicbuffer] : Avoid Thread workers to trigger false positives. Different workers may be executed from the same underlying pthread. * [Core/cyclicbuffer] : Refactor locks and conditions (in an attempt) to avoid deadlocks. * [Core/cyclicbuffer] : Align Windows an Linux identifiers. * [Tests] : Add initial cyclic-buffer test. * [Tests/cyclic-buffer] : Add actual write and read operations on variable (underlying) buffer sizes. * [Tests/cyclic-buffer] : Add some randomness to read and write sample size. * [Core/cyclicbuffer] : Avoid deadlock in Read with lenght equal 0. * [Tests/cyclic-buffer] : Add some Thread reschedule randomness. * [Tests/cyclic-buffer] : Enable M:N writers and readers. * [Tests/cyclic-buffer] : Some housekeeping. * [Tests/cyclic-buffer] : Refactor 'Read(...)' and 'Write(...). * [Core/cyclicbuffer] : Amend 'f41de1249bc139271c199167307777486499c407'. Check 'length == 0' only once. * [core/cyclicbufferi] : Checrry-picked '5b057fe59fd6656d93f29d5059acd90c1e9c3cfa' from 'development/cyclicbuffer-deadlock-on-length'. * [core/cyclicbuffer] : Correct ASSERT condition. * [core/cyclicbuffer] : Cherry-picked 'a5c5ef9c665c8b4ffa1389bf04e9c28914546c50' from 'development/cyclicbuffer-deadlock-on-length'. * [core/cyclicbuffer] : relax the 'mode' condition. Although explicit specification may be preferred, implicit is apparently the accepted use case. * [Tests/cyclic-buffer] : Accommodate '2fa27014692851c5cf60f16daf449f3ddf7d2259'. * [Tests/unit/IPTestAdministrator] : Disable abort. While (looped) waiting on condition variable in 'Sync()' the time out triggers an 'abort()'. * [Tests/cyclic-buffer] : Refactor into creator and users. * [Tests/cyclic-buffer] : Initial multi-process setup. * [Tests/cyclic-buffer] : Some housekeeping. * [Tests/cyclic-buffer] : Avoid fallthrough on error. * [Tests/cyclic-buffer] : Proper use of handshake timeouts. * [Tests/cyclic-buffer] : Improve handshaking. * [Tests/cyclic-buffer] : Some housekeeping and prepare for multiple children. * [Tests/cyclic-buffer] : use 'lockTimeout in locks and allow 'Core::infinite'.' * [Tests/cyclic-buffer] : Allow 'ungraceful' ending of writer(s) and reader(s). * Tests/cyclic-buffer : Continue with 'e538ab849b664a73add18fc6f9d5951f042a5c63'. * [core/cyclicbuffer] : Add robustness checks. In addition: 'initiator' is only relevant for valid buffers. * [Tests/cyclic-buffer] : Refactor for use with unit testing. * [Tests/cyclic-buffer] : Add configuration options. * [Tests/cyclic-buffer] : Add 'storage' conditions. * [core/cyclicbuffer] : Remove superfluous ASSERT. Multiple 'writers' can try to make a reservation in a guarded state. There is no option to check beforehand and they should check the return value. In addition, the return value is ambiguous as error codes are masked by 'actuallength'. * [Tests/cyclic-buffer] : Add reservation by writers. * [core/cyclicbuffer] : Do not ASSERT on write with reservation. 'writers' have no ability to check the presense of any ongoing reservation. * [core/cyclicbuffer] : buffer reads should not let the tail exceed the head. Custom lengths should consider (maximum) distance between tail and head. * [Tests/cyclic-buffer] : amend 'cc16fecec2c616798cde80c69069a96648432dbd'. * [Tests/unit/core] : Introduce Cyclicbuffer data exchange test. - Unresponsive child processes are 'killed' after a set timeout. - Disable ASSERT triggers in 'test_cyclicbuffer' * [core/cyclicbuffer] : Include the last element in writing. It is unintuitive that the anmount of data is always 1 less than the (maximum) size. * [core/cyclicbuffer] : Remove ASSERT in 'IsValid()'. The condition prevents checking an invalid state after construction. Moreover, the invalid state condition is part of the return value, and, identical ASSERTs are used throughout the code where the invalid pointer may be used. * [Tests/unit/core] : Fix ASSERTS in 'test_cyclicbuffer.cpp'. * [Tests/unit/core] : Introduce expected timeout result in 'test_cyclicbuffer_dataexchange'. * [Tests/cyclic-buffer] : Improve use of block reservation. * [Tests/cyclic-buffer] : Add unresponsiveness detection after 'c628be11af07068533d5035e434137863df792cc' * [core/cyclicbuffer] : Assert on overflow in 'GetCompleteTail'. * [Tests/cyclic-buffer] : Do not validate if 'overwrite' has been set. * [Tests/cyclic-buffer] : Amend '2485bc2120017f587e29df393b6b1e4f206a8723'. - Start using the unresponsive detection. - Copy and use the boolean result of the guarded code. * [Tests/cyclic-buffer] : Few improvements. - Added or modified comments. - Only validate reader data if overwrite is disabled. * [core/cyclicbuffer / Tests/unit/core/] : Add 'ReservedRemaining' for testing the effect of 'Reserved' and 'Write' * [Tests/unit/core/] : Minor improvements CyclicBuffer tests. * [Tests/unit/core/] : Amend '40e79d46267710d826c5622700dd4b7fdc8fb5a0' Make more explicit the effect of 'write', 'reserved' and 'overwrite' with modified data set * [Tests / Test/unit/core] : Fix build 'cyclic-buffer' and 'test_cyclicbuffer_dataexchange.cpp'. * WIP * [Core/CyclicBuffer / Tests/unit/core] : fix build 'test_cyclicbuffer' and revert 'CyclicBuffer' changes * [Tests/cyclic-buffer] : remove erroneously added file --------- Co-authored-by: MFransen69 <39826971+MFransen69@users.noreply.github.com> --- Tests/CMakeLists.txt | 4 + Tests/cyclic-buffer/CMakeLists.txt | 31 + Tests/cyclic-buffer/main.cpp | 72 ++ Tests/cyclic-buffer/process.h | 1026 +++++++++++++++++ Tests/unit/core/CMakeLists.txt | 3 +- Tests/unit/core/test_cyclicbuffer.cpp | 668 +++++++---- .../core/test_cyclicbuffer_dataexchange.cpp | 105 ++ 7 files changed, 1656 insertions(+), 253 deletions(-) create mode 100644 Tests/cyclic-buffer/CMakeLists.txt create mode 100644 Tests/cyclic-buffer/main.cpp create mode 100644 Tests/cyclic-buffer/process.h create mode 100644 Tests/unit/core/test_cyclicbuffer_dataexchange.cpp diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 3b5921e48..e4aacf55a 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -32,3 +32,7 @@ endif() if(MESSAGEBUFFER_TEST) add_subdirectory(message-buffer) endif() + +if(CYCLICBUFFER_TEST) + add_subdirectory(cyclic-buffer) +endif() diff --git a/Tests/cyclic-buffer/CMakeLists.txt b/Tests/cyclic-buffer/CMakeLists.txt new file mode 100644 index 000000000..c5ac3c112 --- /dev/null +++ b/Tests/cyclic-buffer/CMakeLists.txt @@ -0,0 +1,31 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_executable(CyclicBuffer main.cpp) + +set_target_properties(CyclicBuffer PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES +) + +target_link_libraries(CyclicBuffer + PRIVATE + ${NAMESPACE}Core +) + +install(TARGETS CyclicBuffer DESTINATION bin) + diff --git a/Tests/cyclic-buffer/main.cpp b/Tests/cyclic-buffer/main.cpp new file mode 100644 index 000000000..564cccf36 --- /dev/null +++ b/Tests/cyclic-buffer/main.cpp @@ -0,0 +1,72 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "process.h" + +#include + +#define ASYNC_TIMEOUT_BEGIN \ + std::promise promise; \ + std::future future = promise.get_future(); \ + std::thread([&](std::promise completed) \ + { /* Before code that should complete before timeout expires */ + +#define ASYNC_TIMEOUT_END(MILLISECONDS /* timeout in milliseconds */, RESULT /* variable that has boolean result of executed code */) \ + /* After code that should complete timely */ \ + /* completed.set_value(true); */ \ + completed.set_value_at_thread_exit(RESULT); \ + } \ + , std::move(promise)).detach() \ + ; \ + if (future.wait_for(std::chrono::milliseconds(MILLISECONDS)) == std::future_status::timeout) { /* Task completed before timeout */ \ + TRACE_L1(_T("Error : Stopping unresposive process.")); \ + killpg(getpgrp(), SIGUSR1); /* Possible 'unresponsive' system, 'unlock' all related 'child' processes, default action is terminate */ \ + } \ + RESULT = future.get(); + +int main(int argc, char* argv[]) +{ + using namespace WPEFramework::Core; + + constexpr uint8_t maxChildren = 3; + + constexpr uint32_t memoryMappedFileRequestedSize = 446; + constexpr uint32_t internalBufferSize = 446; + + constexpr char fileName[] = "/tmp/SharedCyclicBuffer"; + + constexpr uint32_t totalRuntime = infinite /*20000*/; // Milliseconds + constexpr uint32_t totalTimeout = /*totalRuntime +*/ 20000; // Milliseconds + + WPEFramework::Tests::Process process(fileName); + + bool result = false; + + ASYNC_TIMEOUT_BEGIN // result will never be updated in its original scope + + result = process.SetTotalRuntime(totalRuntime) + && process.SetParentUsers(0, 0) /* 0 extra writer(s), 0 reader(s) */ + && process.SetChildUsers(1, 1) /* 1 writer(s), 1 reader(s) */ + && process.Execute() + ; + + ASYNC_TIMEOUT_END(totalTimeout, result) + + return result ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/Tests/cyclic-buffer/process.h b/Tests/cyclic-buffer/process.h new file mode 100644 index 000000000..68dd04bd1 --- /dev/null +++ b/Tests/cyclic-buffer/process.h @@ -0,0 +1,1026 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define MODULE_NAME CyclicBufferStress + +#include +#include +#include +#include +#include +#include +#include /* Definition of FUTEX_* constants */ +#include /* Definition of SYS_* constants */ +#include +#include + +// https://en.wikipedia.org/wiki/Lorem_ipsum +#define LOREM_IPSUM_TEXT "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + +// Define in unit that includes this file +//#define CREATOR_WRITE +//#define CREATOR_EXTRA_USERS + +namespace WPEFramework { +namespace Tests { + +class Thread : public Core::Thread +{ +private : + + static constexpr uint32_t n = sizeof(LOREM_IPSUM_TEXT); + +public : + + Thread() + : _status{ true } + { + memcpy(_data.data(), LOREM_IPSUM_TEXT, n); + } + + virtual ~Thread() = default; + +protected : + + bool Validate(const uint8_t data[], uint32_t count) + { + _status = false; + + std::queue reference; + + std::for_each(_data.begin(), _data.end(), [&reference] (uint8_t value){reference.push(value);} ); + + // Just a little further than the buffer size since the matching pattern might be shifted + size_t stop = 2 * reference.size(); + + uint32_t offset = 0; + + while ( stop > 0 + && offset < count + ) { + auto element = reference.front(); + + if (element == data[offset]) { + ++offset; + } else { + offset = 0; + } + // Shift by one and match pattern again + + reference.push(element); + reference.pop(); + + --stop; + } + + _status = offset == count; + + return _status; + } + + bool Status() const + { + return _status; + } + + static constexpr uint8_t threadWorkerInterval = 10; // Milliseconds + static constexpr uint32_t lockTimeout = Core::infinite; // Milliseconds + static constexpr uint8_t sampleSizeInterval = 5; // Number of uint8_t elements + static constexpr uint8_t forceUnlockRetryCount = 5; // Attemps until Alert() at destruction + + static constexpr uint32_t N = n; + + mutable std::array _data; + + std::atomic _status; +}; + +template +class Reader : public Thread +{ +public : + + Reader() = delete; + Reader(const Reader&) = delete; + Reader& operator=(const Reader&) = delete; + + Reader(const std::string& fileName) + : Reader(fileName, 0) + {} + + Reader(const std::string& fileName, size_t numReservedBlocks) + : Thread{} + , _enabled{ false } + , _fileName{ fileName } + , _index{ 0 } + , _numReservedBlocks{ numReservedBlocks } + , _buffer{ + fileName + , Core::File::USER_READ // Not relevant for readers + | Core::File::USER_WRITE // Open the existing file with write permission + // Readers normally require readonly but the cyclic buffer administration is updated after read +// | Core::File::CREATE // Readers access exisitng underlying memory mapped files + | Core::File::SHAREABLE // Updates are visible to other processes, but a reader should not update any content except when read data is (automatically) removed + , 0 // Readers do not specify sizes + , false // Overwrite unread data + } + { + _output.fill('\0'); + } + + ~Reader() + { +// ASSERT(_buffer.LockPid() != gettid()); // Thunder schedules Worker on a different Thread + + Stop(); + + // No way to recover if the lock is taken indefinitly, eg, Core::infinite + uint8_t count { forceUnlockRetryCount }; + do { + if (count == 0) { + _buffer.Alert(); + } else { + --count; + } + } while (!Wait(Thread::STOPPED | Thread::BLOCKED, threadWorkerInterval)); + + _buffer.Close(); + } + + bool Enable() + { + _enabled = _enabled + || ( _buffer.IsValid() + && _buffer.Open() // It already should + && _buffer.Storage().Name() == _fileName + && Core::File(_fileName).Exists() + && _buffer.Storage().Exists() + ) + ; + + ASSERT(_enabled); + + return _enabled; + } + + bool IsEnabled() const + { + return _enabled; + } + + bool HasError() const + { + return !Status(); + } + + uint32_t Worker() override + { + uint32_t waitTimeForNextRun = Core::infinite; + + uint32_t status = _buffer.Lock(false /* data present, false == signalling path, true == PID path */, lockTimeout /* waiting time to give up lock */); + + if (status == Core::ERROR_NONE) { + const uint32_t count = std::rand() % sampleSizeInterval; + + uint32_t read = Read(count); + if (read == count) { +// TRACE_L1(_T("Data read")); + } else if (count > 0 ){ +// TRACE_L1(_T("Less data read than requested")); // Possibly too few writes + } + + status = _buffer.Unlock(); + + if (status != Core::ERROR_NONE) { + TRACE_L1(_T("Error: reader unlock failed")); + Stop(); + } else { + waitTimeForNextRun = std::rand() % threadWorkerInterval; + } + } else { + if (status == Core::ERROR_TIMEDOUT) { + TRACE_L1(_T("Warning: reader lock timed out")); + } else { + TRACE_L1(_T("Error: reader lock failed")); + Stop(); + } + } + + return waitTimeForNextRun; // Schedule milliseconds from now to be called (again) eg interval time + } + +private : + + uint32_t Read(uint32_t count) + { + uint32_t read = 0; + + if (count > 0) { + std::vector data(count, '\0'); + + if ((_index + count) > N) { + // Two passes when passing the boundary + read = _buffer.Read(&(data.data()[0]), N - _index, false); + memcpy(&_output[_index], &(data.data()[0]), read); + + if ( read == (N - _index) + && count > read + ) { + const uint32_t position = read; + + read = _buffer.Read(&(data.data()[position]), count - read, false); + memcpy(&_output[0], &(data.data()[position]), read); + + read = read + position; + } + } else { + read = _buffer.Read(&(data.data()[0]), count, false); + memcpy(&_output[_index], &(data.data()[0]), read); + } + + if ( read > 0 + && _buffer.IsOverwrite() // The data may have been overwritten + && !Validate(data.data(), read) + ) { + TRACE_L1("Error: detected read data corruption."); + } + + _index = (_index + read) % N; + } + + return read; + } + + // Only relevant with reserve being actively used, and, in single writer context + bool Validate(const uint8_t data[], uint32_t count) + { + bool result = true; + + if ( _numReservedBlocks > 0 + && count > 0 + ) { + result = Thread::Validate(data, count); + } + + return result; + } + + bool _enabled; + + const std::string _fileName; + + uint64_t _index; + + const size_t _numReservedBlocks; + + std::array _output; + + Core::CyclicBuffer _buffer; +}; + + +template +class Writer : public Thread +{ +public : + + Writer() = delete; + Writer(const Writer&) = delete; + Writer& operator=(const Writer&) = delete; + + Writer(const std::string& fileName, uint32_t requiredSharedBufferSize) + : Writer(fileName, requiredSharedBufferSize, 0) + {} + + Writer(const std::string& fileName, uint32_t requiredSharedBufferSize, size_t numReservedBlocks) + : Thread{} + , _enabled{ false } + , _fileName { fileName } + , _index{ 0 } + , _numReservedBlocks{ numReservedBlocks } + , _reserved{ 0 } + , _buffer{ + fileName + , Core::File::USER_READ // Enable read permissions on the underlying file for other users + | Core::File::USER_WRITE // Enable write permission on the underlying file + | (requiredSharedBufferSize ? Core::File::CREATE : 0) // Create a new underlying memory mapped file + | Core::File::SHAREABLE // Allow other processes to access the content of the file + , requiredSharedBufferSize // Requested size + , false // Overwrite unread data + } + { + static_assert(N > 0 || Thread::N > N, "Specify a data set with at least one character (N > 0) with N <= Thread::N."); + + memcpy(_input.data(), Thread::_data.data(), N); + } + + ~Writer() + { +// ASSERT(_buffer.LockPid() != gettid()); // Thunder schedules Worker on a different Thread + + Stop(); + + // No way to recover if the lock is taken indefinitly, eg, Core::infinite + uint8_t count { forceUnlockRetryCount }; + do { + if (count == 0) { + _buffer.Alert(); + } else { + --count; + } + } while (!Wait(Thread::STOPPED | Thread::BLOCKED, threadWorkerInterval)); + + _buffer.Close(); + } + + bool Enable() + { + _enabled = _enabled + || ( _buffer.IsValid() + && _buffer.Open() // It already should + && _buffer.Storage().Name() == _fileName + && Core::File(_fileName).Exists() + && _buffer.Storage().Exists() + ) + ; + + ASSERT(_enabled); + + return _enabled; + } + + bool IsEnabled() const + { + return _enabled; + } + + bool HasError() const + { + return !Status(); + } + + uint32_t Worker() override + { + uint32_t waitTimeForNextRun = Core::infinite; + + // Write(), Free() and Reserve() may all experience race conditions due to thread scheduling + uint32_t status = _buffer.Lock(false /* data present, false == signalling path, true == PID path */, lockTimeout /* waiting time to give up lock */); + if (status == Core::ERROR_NONE) { + uint32_t count = std::rand() % sampleSizeInterval; + + if ( _numReservedBlocks > 0 + && _reserved == 0 + ) { + _reserved = _buffer.Size() / _numReservedBlocks; + +// TRACE_L1(_T("reserved : %ld"), reserved); + ASSERT(_buffer.Size() % _numReservedBlocks == 0); + + // Both Free() and Reserve() may experience a race condition with other writers + if ( _reserved > _buffer.Free() + || _buffer.Reserve(_reserved) != Core::ERROR_NONE) { + // Another has already made a reservation or the block is unavailable + _reserved = 0; + } + } + + if ( _numReservedBlocks > 0 + && _reserved <= count + ) { + count = _reserved; + } + + uint32_t written = 0; + + if ( _numReservedBlocks > 0 + && _reserved == 0 + ) { + // Reservation mode but failed to 'allocate' a block + } else { + written = Write(count); + _reserved -= (_numReservedBlocks > 0 ? written : 0); + } + + if ( written > 0 + && written == count + ) { +// TRACE_L1(_T("Data written")); + } else if (count > 0) { +// TRACE_L1(_T("Less data written than requested")); // Possibly too few reads + } + + status = _buffer.Unlock(); + + if (status != Core::ERROR_NONE) { + TRACE_L1(_T("Error: writer unlock failed")); + Block(); + } else { + waitTimeForNextRun = std::rand() % threadWorkerInterval; + } + } else { + if (status == Core::ERROR_TIMEDOUT) { + TRACE_L1(_T("Warning: writer lock timed out")); + } else { + TRACE_L1(_T("Error: writer lock failed")); + Block(); + } + } + + return waitTimeForNextRun; // Schedule milliseconds from now to be called (again) eg interval time + } + +private : + + uint32_t Write(uint32_t count) + { + uint32_t written = 0; + + if (count > 0) { + std::vector data(count, '\0'); + + // _index 0..N-1 for N + if ((_index + count) > N) { + // Two passes when passing the boundary + // Note: full data writes with insufficient (total free) space typically are refused but here might be bypassed + + data.assign(&_input[_index], &_input[_index + N -_index]); + written = _buffer.Write(&(data.data()[0]), N - _index); + + if ( written == N - _index + && count > written + ) { + data.insert(data.begin() + written, &_input[0], &_input[count - written]); + written += _buffer.Write(&(data.data()[written]), count - written); + } + } else { + // One pass + data.assign(&_input[_index], &_input[_index + count]); + written = _buffer.Write(&(data.data()[0]), count); + } + + if ( written > 0 + && !Validate(data.data(), written) + ) { + TRACE_L1("Error: detected written data corruption."); + } + + _index = (_index + written) % N; + } + + return written; + } + + // Only 'relevant' with reserve being actively used + bool Validate(const uint8_t data[], uint32_t count) + { + bool result = true; + + if ( _numReservedBlocks > 0 + && count > 0 + ) { + result = Thread::Validate(data, count); + } + + return result; + } + + bool _enabled; + + const std::string _fileName; + + uint64_t _index; + + const size_t _numReservedBlocks; + + uint32_t _reserved; + + mutable std::array _input; + + Core::CyclicBuffer _buffer; +}; + +template +class BufferCreator +{ +public : + + BufferCreator() = delete; + BufferCreator(const BufferCreator&) = delete; + BufferCreator& operator=(const BufferCreator&) = delete; + + BufferCreator(const std::string& fileName) + : BufferCreator(fileName, 0) + {} + + BufferCreator(const std::string& fileName, size_t numReservedBlocks) + : _writer(fileName, memoryMappedFileRequestedSize, numReservedBlocks) + { + static_assert(memoryMappedFileRequestedSize, "Specify memoryMappedFileRequestedSize > 0"); + } + + bool Enable() + { + return _writer.Enable(); + } + + bool Start() + { + constexpr bool result = true; + + _writer.Run(); + + return result; + } + + bool Stop() + { + _writer.Stop(); + + return !_writer.HasError(); + } + + bool IsEnabled() const + { + return _writer.IsEnabled(); + } + +private : + + Writer _writer; +}; + +template +class BufferUsers +{ +public : + + BufferUsers() = delete; + BufferUsers(const BufferUsers&) = delete; + BufferUsers& operator=(const BufferUsers&) = delete; + + BufferUsers(const std::string& fileName) + : BufferUsers(fileName, W, R, 0) + { + } + + BufferUsers(const std::string& fileName, size_t maxWriters, size_t maxReaders) + : BufferUsers(fileName, maxWriters, maxReaders, 0) + {} + + BufferUsers(const std::string& fileName, size_t maxWriters, size_t maxReaders, size_t numReservedBlocks) + : _writers(maxWriters > W ? W : maxWriters) + , _readers(maxReaders > R ? R : maxReaders) + { + for_each(_writers.begin(), _writers.end(), [&fileName, &numReservedBlocks] (std::unique_ptr>& writer){ writer = std::move(std::unique_ptr>(new Writer(fileName, 0, numReservedBlocks))); }); + for_each(_readers.begin(), _readers.end(), [&fileName, &numReservedBlocks] (std::unique_ptr>& reader){ reader = std::move(std::unique_ptr>(new Reader(fileName, numReservedBlocks))); }); + } + + ~BufferUsers() + { + /* bool */ Stop(); + } + + bool Enable() + { + return std::all_of(_writers.begin(), _writers.end(), [] (std::unique_ptr>& writer){ return writer->Enable(); }) + && std::all_of(_readers.begin(), _readers.end(), [] (std::unique_ptr>& reader){ return reader->Enable(); }) + ; + } + + bool Start() const + { + constexpr bool result = true; + + for_each(_writers.begin(), _writers.end(), [] (const std::unique_ptr>& writer){ writer->Run(); }); + for_each(_readers.begin(), _readers.end(), [] (const std::unique_ptr>& reader){ reader->Run(); }); + + return result; + } + + bool Stop() const + { + bool result = true; + + for_each(_writers.begin(), _writers.end(), [&result] (const std::unique_ptr>& writer){ writer->Stop(); result = result && !writer->HasError(); }); + for_each(_readers.begin(), _readers.end(), [&result] (const std::unique_ptr>& reader){ reader->Stop(); result = result && !reader->HasError(); }); + + + return result; + } + +private : + + std::vector>> _writers; + std::vector>> _readers; +}; + +template +class Process +{ +public : + + Process() = delete; + Process(const Process&) = delete; + Process& operator=(const Process&) = delete; + + Process(const std::string& fileName) + : _fileName{ fileName } + , _sync{ nullptr } + , _childUsersSet{ maxReadersPerProcess, maxReadersPerProcess } + , _parentUsersSet{ maxWritersPerProcess, maxReadersPerProcess } + , _setupTime{ Core::infinite } + , _runTime{ Core::infinite } + , _numReservedBlocks{ 0 } + { + std::for_each(_children.begin(), _children.end(), [] (pid_t& child){ child = 0; } ); + + // Add some randomness + std::srand(std::time(nullptr)); + + bool result = PrepareIPSync(); + + ASSERT(result); DEBUG_VARIABLE(result); + } + + ~Process() + { + bool result = CleanupIPSync(); + + ASSERT(result); DEBUG_VARIABLE(result); + } + + bool Execute() + { + return ForkAndExecute(); + } + + bool SetChildUsers(uint8_t numWriters, uint8_t numReaders) + { + if (numWriters <= maxWritersPerProcess) { + _childUsersSet[0] = numWriters; + } + + if (numReaders <= maxReadersPerProcess) { + _childUsersSet[1] = numReaders; + } + + return _childUsersSet[0] == numWriters + && _childUsersSet[1] == numReaders + ; + } + + bool SetParentUsers(uint8_t numWriters, uint8_t numReaders) + { + if (numWriters <= maxWritersPerProcess) { + _parentUsersSet[0] = numWriters; + } + + if (numReaders <= maxReadersPerProcess) { + _parentUsersSet[1] = numReaders; + } + + return _parentUsersSet[0] == numWriters + && _parentUsersSet[1] == numReaders + ; + } + + bool SetTotalRuntime(uint32_t runTime /* milliseconds */) + { + if (runTime <= maxTotalRuntime) { + _runTime = runTime; + } + + return _runTime == runTime; + } + + bool SetAllowedSetupTime(uint32_t setupTime /* seconds */) + { + if (setupTime <= maxSetupTime) { + _setupTime = setupTime; + } + + return _setupTime == setupTime; + } + + bool SetNumReservedBlocks(uint8_t numReservedBlocks) + { + if (numReservedBlocks <= maxNumReservedBlocks) { + _numReservedBlocks = numReservedBlocks; + } + + return _numReservedBlocks == numReservedBlocks; + } + +private : + + // Hard limits + + static constexpr uint32_t maxSetupTime = 10; // Seconds + static constexpr uint32_t maxTotalRuntime = Core::infinite; // Milliseconds + + static constexpr uint8_t maxWritersPerProcess = 2; + static constexpr uint8_t maxReadersPerProcess = 2; + + static constexpr uint8_t maxNumReservedBlocks = 2; + + // The order is important, sync variable + enum status : uint8_t { + uninitialized = 1 + , initialized = 2 + , ready = 4 + }; + + bool PrepareIPSync() + { + if (_sync == nullptr) { + _sync = reinterpret_cast(mmap(nullptr, sizeof(struct sync_wrapper), PROT_READ | PROT_WRITE, MAP_SHARED/*_VALIDATE*/ | MAP_ANONYMOUS, -1, 0)); + } + + bool result = _sync != nullptr; + + if (_sync != nullptr) { + _sync->level = status::uninitialized; + } + + return result; + } + + bool CleanupIPSync() + { + bool result = _sync != nullptr + && munmap(_sync, sizeof(struct sync_wrapper)) == 0 + ; + + return result; + } + + bool LockIPSync(const std::array& flags, const struct timespec& timeout) const + { + long retval = 0; + int state = 0; + + do { + if (_sync->level == flags[0]) { + retval = syscall(SYS_futex, reinterpret_cast(_sync), FUTEX_WAIT, static_cast(flags[0]), &timeout, nullptr, 0); + state = errno; + } + } while (( retval == -1 + && ( (state & ETIMEDOUT) == ETIMEDOUT + || (state & EAGAIN) == EAGAIN + ) + ) + && _sync->level != flags[1] + ); + + // ETIMEDOUT : The parent / child is not yet ready + // EAGAIN : The handshake between parent and (another) child has been completed as the value has already been altered + + return retval >= 0 || _sync->level != flags[0]; // 0 == woken up or parent / child already ready + } + + bool UnlockIPSync(const std::array& flags, long& retval) + { + if ( _sync->level == flags[0] + && _sync->level != flags[1] + ) { + _sync->level = flags[1]; + /*long*/ retval = syscall(SYS_futex, reinterpret_cast(_sync), FUTEX_WAKE, static_cast(std::numeric_limits::max()) /* INT_MAX, number of waiters to wake up */, nullptr, 0, 0); + } + + return retval >= 0; // The parent / child might already be ready + } + + bool ForkAndExecute() + { + bool result = true; + + for (auto it = _children.begin(), end = _children.end(); it != end; it++) { + pid_t pid = fork(); + + // Format specifier %ld, pid_t, gettid() and getpid() + static_assert(sizeof(pid_t) <= sizeof(long), "Specify a more suitable formmat specifier for pid_t."); + + switch (pid) { + case -1 : // Error + { + result = pid < 0; + + TRACE_L1(_T("Error: failed to create the remote process.")); + break; + } + case 0 : // Child + { + const struct timespec timeout = {.tv_sec = _setupTime, .tv_nsec = 0}; + + std::array flags = {status::uninitialized, status::initialized}; + + result = LockIPSync(flags, timeout); + + ASSERT(result); + + // ETIMEDOUT : The parent is not yet ready + // EAGAIN : The handshake between parent and (another) child has been completed as the value has already been altered + + TRACE_L1(_T("Child %ld knows its parent is ready [true/false]: %s."), getpid(), _sync->level != status::uninitialized ? _T("true") : _T("false")); + + //BufferUsers users(_fileName, _childUsersSet[0], _childUsersSet[1]); // # writer(s), # reader(s) + BufferUsers users(_fileName, _childUsersSet[0], _childUsersSet[1], _numReservedBlocks); // # writer(s), # reader(s) + + flags = {status::initialized, status::ready}; + + long retval = 0; + + result = UnlockIPSync(flags, retval); + + ASSERT(result); + + TRACE_L1(_T("Child %ld has woken up %ld parent(s)."), getpid(), retval); + + result = CleanupIPSync() + && Core::File(_fileName).Exists() + && users.Enable() + && users.Start() + ; + + if (result) { + SleepMs(_runTime); + + result = users.Stop(); + } else { + TRACE_L1(_T("Error: Unable to access the CyclicBuffer.")); + } + + return result; + } + default : // Parent + { + // Keep track of conceived children + *it = pid; + + result = pid > 0 + && result + ; + } + } + } + + return result + && ExecuteParent() + ; + } + + bool ExecuteParent() + { + // The underlying memory mapped file will be created and opened via DataElementFile construction + std::unique_ptr> creator { std::move(std::unique_ptr>(new BufferCreator(_fileName, _numReservedBlocks))) }; + + bool result = creator.get() != nullptr + // Immunize, signal to use for unresponsive child processes, default action is terminate + && signal(SIGUSR1, SIG_IGN) != SIG_ERR + && creator->Enable() + && creator->IsEnabled() +#ifdef CREATOR_WRITE + && creator->Start() +#endif + ; + + if (result) { + TRACE_L1(_T("'creator' and shared cyclic buffer ready.")); + +#ifdef CREATOR_EXTRA_USERS + BufferUsers users(_fileName, _parentUsersSet[0], _parentUsersSet[1]); // # writer(s), # reader(s) + + result = users.Enable() + && users.Start() + ; + + if (result) { +#endif + _sync->level = status::uninitialized; + + std::array flags = {status::uninitialized, status::initialized}; + + long retval = 0; + + result = UnlockIPSync(flags, retval); + + ASSERT(result); + + TRACE_L1(_T("Parent %ld has woken up %ld child(ren)."), gettid(), retval); + + const struct timespec timeout = {.tv_sec = _setupTime, .tv_nsec = 0}; + + flags = {status::initialized, status::ready}; + + bool result = LockIPSync(flags, timeout); + + ASSERT(result); + + TRACE_L1(_T("Parent %ld has been woken up by child [true/false]: %s."), gettid(), retval == 0 ? _T("true") : _T("false")); + + result = CleanupIPSync() + && WaitForCompletion(/*timeout for waitpid*/); + +#ifdef CREATOR_EXTRA_USERS + result = users.Stop() + && result + ; + } else { + TRACE_L1(_T("Error: Unable to start 'extra users'.")); + } +#endif + +#ifdef CREATOR_WRITE + result = creator->Stop() + && result + ; +#endif + + } else { + TRACE_L1(_T("Error: 'creator' and shared cyclic buffer unavailable.")); + } + + return result; + } + + bool WaitForCompletion(/*timeout*/) + { + bool result = true; + + // Reap + for (auto it = _children.begin(), end = _children.end(); it != end; it++) { + int status = 0; + +// pid_t pid = waitpid(-1 /* wait for child */, &status, 0); // Wait out of order + pid_t pid = waitpid(*it /* wait for child */, &status, 0); // Wait in order + + switch (pid) { + case -1 : // No more children / child has died + if (errno == ECHILD) { + TRACE_L1(_T("Child %ld is not our offspring."), pid); + result = false; + } + break; + case 0 : // Child has not changed state, see WNOHANG + result = false; + break; + default : // Child's pid + if (WIFEXITED(status)) { + TRACE_L1(_T("Child %ld died NORMALLY with status %ld."), pid, WEXITSTATUS(status)); + result = result + && true; + } else { + result = false; + + if (WIFSIGNALED(status)) { + TRACE_L1(_T("Child %ld died ABNORMALLY due to signal %ld."), pid, WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + TRACE_L1(_T("Child %ld died ABNORMALLY due to stop signal %ld."), pid, WSTOPSIG(status)); + } else if (errno != EAGAIN) {// pid non-blocking + TRACE_L1(_T("Child %ld died ABNORMALLY."), pid); + } else { + TRACE_L1(_T("Error: unprocessed child %ld status."), pid); + } + } + } + } + + return result; + } + + const std::string _fileName; + + struct sync_wrapper { + status level; + }* _sync; + + std::array _children; + + std::array _childUsersSet; + std::array _parentUsersSet; + + uint32_t _runTime; // Milliseconds + uint32_t _setupTime; // Seconds + + uint8_t _numReservedBlocks; +}; + +} // Tests +} // WPEFramework + diff --git a/Tests/unit/core/CMakeLists.txt b/Tests/unit/core/CMakeLists.txt index 2e665133f..de3786839 100644 --- a/Tests/unit/core/CMakeLists.txt +++ b/Tests/unit/core/CMakeLists.txt @@ -21,7 +21,8 @@ if(LINUX) # IPTestAdministrator only supported on LINUX platform add_executable(${TEST_RUNNER_NAME} ../IPTestAdministrator.cpp - #test_cyclicbuffer.cpp + test_cyclicbuffer.cpp +# test_cyclicbuffer_dataexchange.cpp test_databuffer.cpp test_dataelement.cpp test_dataelementfile.cpp diff --git a/Tests/unit/core/test_cyclicbuffer.cpp b/Tests/unit/core/test_cyclicbuffer.cpp index 667982cde..8c7277b09 100644 --- a/Tests/unit/core/test_cyclicbuffer.cpp +++ b/Tests/unit/core/test_cyclicbuffer.cpp @@ -907,46 +907,56 @@ namespace Core { } void SetSharePermissionsFromForkedProcessAndVerify(bool shareable, bool usingDataElementFile = false, uint32_t offset = 0) { - std::string bufferName {"cyclicbuffer01"}; - const uint32_t CyclicBufferSize = 10; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 1; - uint32_t shareableFlag = (shareable == true) ? ::Thunder::Core::File::Mode::SHAREABLE : 0; - const uint32_t mode = - (::Thunder::Core::File::Mode::USER_READ | ::Thunder::Core::File::Mode::USER_WRITE | ::Thunder::Core::File::Mode::USER_EXECUTE | - ::Thunder::Core::File::Mode::GROUP_READ | ::Thunder::Core::File::Mode::GROUP_WRITE | - shareableFlag); + const std::string bufferName {"cyclicbuffer01"}; - struct Data data; - data.mode = mode; - data.bufferName = bufferName.c_str(); - data.shareable = shareable; - data.usingDataElementFile = usingDataElementFile; - data.offset = offset; + constexpr uint32_t CyclicBufferSize = 10; + constexpr uint32_t cyclicBufferWithControlDataSize = CyclicBufferSize + sizeof(::Thunder::Core::CyclicBuffer::control); + + const uint32_t mode = ( ::Thunder::Core::File::Mode::USER_READ + | ::Thunder::Core::File::Mode::USER_WRITE + | ::Thunder::Core::File::Mode::USER_EXECUTE + | ::Thunder::Core::File::Mode::GROUP_READ + | ::Thunder::Core::File::Mode::GROUP_WRITE + | (shareable ? ::Thunder::Core::File::Mode::SHAREABLE : 0) + ); + + const struct Data data{shareable, usingDataElementFile, mode, offset, bufferName.c_str()}; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + // a small delay so the parent can be set up + SleepMs(maxInitTime); - auto lambdaFunc = [bufferName](IPTestAdministrator & testAdmin) { - struct Data* data = (reinterpret_cast(testAdmin.Data())); uint32_t result; - uint32_t cyclicBufferSize = CyclicBufferSize; - uint8_t loadBuffer[cyclicBufferSize + 1]; + uint8_t loadBuffer[CyclicBufferSize + 1]; - CyclicBufferTest* buffer = nullptr; + CyclicBufferTest* buffer = nullptr; ::Thunder::Core::DataElementFile* dataElementFile = nullptr; - if (data->usingDataElementFile == true) { - uint8_t cyclicBufferWithControlDataSize = cyclicBufferSize + sizeof(::Thunder::Core::CyclicBuffer::control); - dataElementFile = new ::Thunder::Core::DataElementFile(bufferName, data->mode | ::Thunder::Core::File::CREATE, cyclicBufferWithControlDataSize + data->offset); - buffer = new CyclicBufferTest(*dataElementFile, true, data->offset, cyclicBufferWithControlDataSize, false); + + if (data.usingDataElementFile == true) { + dataElementFile = new ::Thunder::Core::DataElementFile(bufferName, data.mode | ::Thunder::Core::File::CREATE, cyclicBufferWithControlDataSize + data.offset); + + ASSERT_TRUE(dataElementFile != nullptr); + + buffer = new CyclicBufferTest(*dataElementFile, true, data.offset, cyclicBufferWithControlDataSize, false); } else { - buffer = new CyclicBufferTest(bufferName, data->mode, cyclicBufferSize, false); + buffer = new CyclicBufferTest(bufferName, data.mode, CyclicBufferSize, false); } - EXPECT_EQ(buffer->Size(), cyclicBufferSize); - testAdmin.Sync("setup server"); - testAdmin.Sync("setup client"); + ASSERT_TRUE(buffer != nullptr); - if (data->shareable == true) { - testAdmin.Sync("client read empty data"); - string dataStr = "abcd"; + EXPECT_EQ(buffer->Size(), CyclicBufferSize); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + if (data.shareable == true) { + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + string dataStr = "abcd"; result = buffer->Write(reinterpret_cast(dataStr.c_str()), dataStr.size()); EXPECT_EQ(result, dataStr.size()); @@ -954,94 +964,118 @@ namespace Core { result = buffer->Write(reinterpret_cast(dataStr.c_str()), dataStr.size()); EXPECT_EQ(result, dataStr.size()); - testAdmin.Sync("server wrote"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); - testAdmin.Sync("client read"); + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); - testAdmin.Sync("client wrote"); + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); result = buffer->Peek(loadBuffer, buffer->Used()); loadBuffer[result] = '\0'; EXPECT_EQ(result, 9u); - EXPECT_STREQ((char*)loadBuffer, "bcdefghkl"); + + EXPECT_STREQ(reinterpret_cast(&loadBuffer[0]), "bcdefghkl"); EXPECT_EQ(buffer->Used(), 9u); - testAdmin.Sync("server peek"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); - testAdmin.Sync("server start read"); result = buffer->Read(loadBuffer, buffer->Used()); loadBuffer[result] = '\0'; + EXPECT_EQ(result, 9u); - EXPECT_STREQ((char*)loadBuffer, "bcdefghkl"); + EXPECT_STREQ(reinterpret_cast(&loadBuffer[0]), "bcdefghkl"); EXPECT_EQ(buffer->Used(), 0u); - testAdmin.Sync("server read"); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + delete buffer; + if (dataElementFile) { delete dataElementFile; } } }; - static std::function lambdaVar = lambdaFunc; - - IPTestAdministrator::OtherSideMain otherSide = [](IPTestAdministrator& testAdmin ) { lambdaVar(testAdmin); }; - - // This side (tested) acts as client - IPTestAdministrator testAdmin(otherSide, reinterpret_cast(&data), 10); - { - testAdmin.Sync("setup server"); + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); uint32_t result; - uint32_t cyclicBufferSize = CyclicBufferSize; - uint8_t loadBuffer[cyclicBufferSize]; + uint8_t loadBuffer[CyclicBufferSize]; - CyclicBufferTest* buffer; + CyclicBufferTest* buffer = nullptr; ::Thunder::Core::DataElementFile* dataElementFile = nullptr; + if (usingDataElementFile == true) { dataElementFile = new ::Thunder::Core::DataElementFile(bufferName, mode, 0); + + ASSERT_TRUE(dataElementFile != nullptr); + buffer = new CyclicBufferTest(*dataElementFile, false, offset, 0, false); } else { buffer = new CyclicBufferTest(bufferName, mode, 0, false); } - EXPECT_EQ(buffer->Size(), static_cast(((shareable == true) ?CyclicBufferSize : 0))); - testAdmin.Sync("setup client"); + ASSERT_TRUE(buffer != nullptr); + + EXPECT_EQ(buffer->Size(), static_cast((shareable ? CyclicBufferSize : 0))); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); if (shareable == true) { - memset(loadBuffer, 0, cyclicBufferSize); + memset(loadBuffer, 0, CyclicBufferSize); + result = buffer->Read(loadBuffer, 1, false); - EXPECT_EQ(result, static_cast(0)); + EXPECT_EQ(result, 0); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); - testAdmin.Sync("client read empty data"); - testAdmin.Sync("server wrote"); result = buffer->Read(loadBuffer, 1, false); - EXPECT_EQ(result, static_cast(1)); + EXPECT_EQ(result, 1); + loadBuffer[result] = '\0'; - EXPECT_STREQ((char*)loadBuffer, "a"); - testAdmin.Sync("client read"); + EXPECT_STREQ(reinterpret_cast(&loadBuffer[0]), "a"); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); string data = "kl"; result = buffer->Reserve(data.size()); EXPECT_EQ(result, 2u); + result = buffer->Write(reinterpret_cast(data.c_str()), data.size()); EXPECT_EQ(result, 2u); - testAdmin.Sync("client wrote"); - testAdmin.Sync("server peek"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + EXPECT_EQ(buffer->Used(), 9u); - testAdmin.Sync("server start read"); - testAdmin.Sync("server read"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + EXPECT_EQ(buffer->Used(), 0u); } + buffer->Close(); + delete buffer; + if (dataElementFile) { delete dataElementFile; } - } + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + ::Thunder::Core::Singleton::Dispose(); } TEST(Core_CyclicBuffer, CheckSharePermissionsFromForkedProcessWithoutOverwrite) @@ -1061,35 +1095,52 @@ namespace Core { } TEST(Core_CyclicBuffer, WithoutOverwriteUsingForksReversed) { - std::string bufferName {"cyclicbuffer02"}; - auto lambdaFunc = [bufferName](IPTestAdministrator & testAdmin) { - testAdmin.Sync("setup client"); + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 1; - uint32_t cyclicBufferSize = 0; - const uint32_t mode = - ::Thunder::Core::File::Mode::USER_READ | ::Thunder::Core::File::Mode::USER_WRITE | ::Thunder::Core::File::Mode::USER_EXECUTE | - ::Thunder::Core::File::Mode::GROUP_READ | ::Thunder::Core::File::Mode::GROUP_WRITE | - ::Thunder::Core::File::Mode::SHAREABLE; + const std::string bufferName {"cyclicbuffer02"}; - ::Thunder::Core::CyclicBuffer buffer(bufferName.c_str(), mode, cyclicBufferSize, false); + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + constexpr uint32_t cyclicBufferSize = 0; + + constexpr uint32_t mode = ::Thunder::Core::File::Mode::USER_READ + | ::Thunder::Core::File::Mode::USER_WRITE + | ::Thunder::Core::File::Mode::USER_EXECUTE + | ::Thunder::Core::File::Mode::GROUP_READ + | ::Thunder::Core::File::Mode::GROUP_WRITE + | ::Thunder::Core::File::Mode::SHAREABLE + ; + + ::Thunder::Core::CyclicBuffer buffer(bufferName.c_str(), mode, cyclicBufferSize, false); + + ASSERT_TRUE(buffer.IsValid()); - testAdmin.Sync("setup server"); - testAdmin.Sync("client wrote"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); uint8_t loadBuffer[cyclicBufferSize + 1]; + uint32_t result = buffer.Read(loadBuffer, 4); loadBuffer[result] = '\0'; - EXPECT_STREQ((char*)loadBuffer, "abcd"); - testAdmin.Sync("server read"); - string data = "klmnopq"; + EXPECT_STREQ(reinterpret_cast(&loadBuffer[0]), "abcd"); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + const string data = "klmnopq"; + result = buffer.Reserve(data.size()); EXPECT_EQ(result, ::Thunder::Core::ERROR_INVALID_INPUT_LENGTH); + result = buffer.Write(reinterpret_cast(data.c_str()), data.size()); EXPECT_EQ(result, 0u); - testAdmin.Sync("server wrote"); - testAdmin.Sync("client peek"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); EXPECT_FALSE(buffer.Overwritten()); EXPECT_FALSE(buffer.IsLocked()); @@ -1097,244 +1148,338 @@ namespace Core { EXPECT_EQ(buffer.LockPid(), 0u); EXPECT_EQ(buffer.Free(), 5u); - testAdmin.Sync("client start read"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + EXPECT_STREQ(buffer.Name().c_str(), bufferName.c_str()); EXPECT_STREQ(buffer.Storage().Name().c_str(), bufferName.c_str()); - testAdmin.Sync("client read"); + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + EXPECT_EQ(buffer.Used(), 0u); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); }; - static std::function lambdaVar = lambdaFunc; + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + // a small delay so the child can be set up + SleepMs(maxInitTime); - IPTestAdministrator::OtherSideMain otherSide = [](IPTestAdministrator& testAdmin ) { lambdaVar(testAdmin); }; + constexpr uint32_t cyclicBufferSize = 10; - // This side (tested) acts as client - IPTestAdministrator testAdmin(otherSide); - { - uint32_t cyclicBufferSize = 10; + constexpr uint32_t mode = ::Thunder::Core::File::Mode::USER_READ + | ::Thunder::Core::File::Mode::USER_WRITE + | ::Thunder::Core::File::Mode::USER_EXECUTE + | ::Thunder::Core::File::Mode::GROUP_READ + | ::Thunder::Core::File::Mode::GROUP_WRITE + | ::Thunder::Core::File::Mode::SHAREABLE + ; - const uint32_t mode = - ::Thunder::Core::File::Mode::USER_READ | ::Thunder::Core::File::Mode::USER_WRITE | ::Thunder::Core::File::Mode::USER_EXECUTE | - ::Thunder::Core::File::Mode::GROUP_READ | ::Thunder::Core::File::Mode::GROUP_WRITE | - ::Thunder::Core::File::Mode::SHAREABLE; + ::Thunder::Core::CyclicBuffer buffer(bufferName.c_str(), mode, cyclicBufferSize, false); - ::Thunder::Core::CyclicBuffer buffer(bufferName.c_str(), mode, cyclicBufferSize, false); + ASSERT_TRUE(buffer.IsValid()); - testAdmin.Sync("setup client"); - testAdmin.Sync("setup server"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); uint8_t loadBuffer[cyclicBufferSize + 1]; - string data = "abcdefghi"; + const string data = "abcdefghi"; + uint32_t result = buffer.Write(reinterpret_cast(data.c_str()), data.size()); EXPECT_EQ(result, data.size()); - testAdmin.Sync("client wrote"); - testAdmin.Sync("server read"); - testAdmin.Sync("server wrote"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); result = buffer.Peek(loadBuffer, buffer.Used()); loadBuffer[result] = '\0'; + EXPECT_EQ(result, 5u); - EXPECT_STREQ((char*)loadBuffer, "efghi"); + EXPECT_STREQ(reinterpret_cast(&loadBuffer[0]), "efghi"); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); - testAdmin.Sync("client peek"); - testAdmin.Sync("client start read"); result = buffer.Read(loadBuffer, buffer.Used()); loadBuffer[result] = '\0'; + EXPECT_EQ(result, 5u); - EXPECT_STREQ((char*)loadBuffer, "efghi"); + EXPECT_STREQ(reinterpret_cast(&loadBuffer[0]), "efghi"); EXPECT_EQ(buffer.Used(), 0u); - testAdmin.Sync("client read"); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); buffer.Close(); - } + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + ::Thunder::Core::Singleton::Dispose(); } TEST(Core_CyclicBuffer, WithOverWriteUsingFork) { - std::string bufferName {"cyclicbuffer03"}; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 1; - auto lambdaFunc = [bufferName](IPTestAdministrator & testAdmin) { - uint32_t cyclicBufferSize = 10; + const std::string bufferName {"cyclicbuffer03"}; - const uint32_t mode = - ::Thunder::Core::File::Mode::USER_READ | ::Thunder::Core::File::Mode::USER_WRITE | ::Thunder::Core::File::Mode::USER_EXECUTE | - ::Thunder::Core::File::Mode::GROUP_READ | ::Thunder::Core::File::Mode::GROUP_WRITE | - ::Thunder::Core::File::Mode::SHAREABLE; + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + // a small delay so the parent can be set up + SleepMs(maxInitTime); + + constexpr uint32_t cyclicBufferSize = 10; - CyclicBufferTest buffer(bufferName.c_str(), mode, cyclicBufferSize, true); + constexpr uint32_t mode = ::Thunder::Core::File::Mode::USER_READ + | ::Thunder::Core::File::Mode::USER_WRITE + | ::Thunder::Core::File::Mode::USER_EXECUTE + | ::Thunder::Core::File::Mode::GROUP_READ + | ::Thunder::Core::File::Mode::GROUP_WRITE + | ::Thunder::Core::File::Mode::SHAREABLE + ; + + CyclicBufferTest buffer(bufferName.c_str(), mode, cyclicBufferSize, true); - testAdmin.Sync("setup server"); - testAdmin.Sync("setup client"); + ASSERT_TRUE(buffer.IsValid()); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + const string data = "abcdef"; + const uint16_t size = data.size() + 2; - string data = "abcdef"; - uint16_t size = data.size() + 2; uint32_t result = buffer.Write(reinterpret_cast(&size), 2u); EXPECT_EQ(result, 2u); + result = buffer.Write(reinterpret_cast(data.c_str()), data.size()); EXPECT_EQ(result, data.size()); - testAdmin.Sync("server wrote"); - testAdmin.Sync("client wrote"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); uint8_t loadBuffer[cyclicBufferSize + 1]; + result = buffer.Peek(loadBuffer, buffer.Used()); loadBuffer[result] = '\0'; - testAdmin.Sync("server peek"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); result = buffer.Read(loadBuffer, buffer.Used()); loadBuffer[result] = '\0'; + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + buffer.Alert(); buffer.Flush(); - testAdmin.Sync("server read"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); }; - static std::function lambdaVar = lambdaFunc; + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); - IPTestAdministrator::OtherSideMain otherSide = [](IPTestAdministrator& testAdmin ) { lambdaVar(testAdmin); }; + constexpr uint32_t cyclicBufferSize = 0; - // This side (tested) acts as client - IPTestAdministrator testAdmin(otherSide); - { - testAdmin.Sync("setup server"); + constexpr uint32_t mode = ::Thunder::Core::File::Mode::USER_READ + | ::Thunder::Core::File::Mode::USER_WRITE + | ::Thunder::Core::File::Mode::USER_EXECUTE + | ::Thunder::Core::File::Mode::GROUP_READ + | ::Thunder::Core::File::Mode::GROUP_WRITE + | ::Thunder::Core::File::Mode::SHAREABLE + ; - uint32_t cyclicBufferSize = 0; - const uint32_t mode = - ::Thunder::Core::File::Mode::USER_READ | ::Thunder::Core::File::Mode::USER_WRITE | ::Thunder::Core::File::Mode::USER_EXECUTE | - ::Thunder::Core::File::Mode::GROUP_READ | ::Thunder::Core::File::Mode::GROUP_WRITE | - ::Thunder::Core::File::Mode::SHAREABLE; + CyclicBufferTest buffer(bufferName.c_str(), mode, cyclicBufferSize, true); + + ASSERT_TRUE(buffer.IsValid()); - CyclicBufferTest buffer(bufferName.c_str(), mode, cyclicBufferSize, true); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); - testAdmin.Sync("setup client"); - testAdmin.Sync("server wrote"); + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); string data = "j"; - uint16_t size = 9; + const uint16_t size = 9; + uint32_t result = buffer.Reserve(9); EXPECT_EQ(result, 9u); + result = buffer.Write(reinterpret_cast(&size), 2u); EXPECT_EQ(result, 2u); + result = buffer.Write(reinterpret_cast(data.c_str()), 1u); EXPECT_EQ(result, data.size()); data = "klmnop"; + result = buffer.Write(reinterpret_cast(data.c_str()), 6u); EXPECT_EQ(result, data.size()); - testAdmin.Sync("client wrote"); - testAdmin.Sync("server peek"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); - testAdmin.Sync("server read"); buffer.Close(); - } + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + ::Thunder::Core::Singleton::Dispose(); } TEST(Core_CyclicBuffer, WithOverwriteUsingForksReversed) { - std::string bufferName {"cyclicbuffer03"}; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 1; - auto lambdaFunc = [bufferName](IPTestAdministrator & testAdmin) { - uint32_t cyclicBufferSize = 0; - testAdmin.Sync("setup server"); + const std::string bufferName {"cyclicbuffer03"}; - const uint32_t mode = - ::Thunder::Core::File::Mode::USER_READ | ::Thunder::Core::File::Mode::USER_WRITE | ::Thunder::Core::File::Mode::USER_EXECUTE | - ::Thunder::Core::File::Mode::GROUP_READ | ::Thunder::Core::File::Mode::GROUP_WRITE | - ::Thunder::Core::File::Mode::SHAREABLE; + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + constexpr uint32_t cyclicBufferSize = 0; + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); - CyclicBufferTest buffer(bufferName.c_str(), mode, cyclicBufferSize, true); + constexpr uint32_t mode = ::Thunder::Core::File::Mode::USER_READ + | ::Thunder::Core::File::Mode::USER_WRITE + | ::Thunder::Core::File::Mode::USER_EXECUTE + | ::Thunder::Core::File::Mode::GROUP_READ + | ::Thunder::Core::File::Mode::GROUP_WRITE + | ::Thunder::Core::File::Mode::SHAREABLE + ; + + CyclicBufferTest buffer(bufferName.c_str(), mode, cyclicBufferSize, true); EXPECT_TRUE(buffer.IsOverwrite()); - testAdmin.Sync("setup client"); - testAdmin.Sync("server wrote"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); uint16_t size; - buffer.Read(reinterpret_cast(&size), 2); + EXPECT_EQ(buffer.Read(reinterpret_cast(&size), 2), 2); + uint8_t loadBuffer[cyclicBufferSize + 1]; uint32_t result = buffer.Read(loadBuffer, size - 2); loadBuffer[result] = '\0'; + EXPECT_EQ(result, size - 2); - testAdmin.Sync("client read"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); string data = "j"; size = 9; + result = buffer.Reserve(size); + EXPECT_EQ(result, size); + result = buffer.Write(reinterpret_cast(&size), 2); + EXPECT_EQ(result, size); + result = buffer.Write(reinterpret_cast(data.c_str()), 1); + EXPECT_EQ(result, size); + data = "lmnopq"; result = buffer.Write(reinterpret_cast(data.c_str()), 6); + EXPECT_EQ(result, 6); + EXPECT_EQ(buffer.Used(), 9u); EXPECT_EQ(buffer.Overwritten(), false); size = 7; result = buffer.Reserve(size); + EXPECT_EQ(result, size); + result = buffer.Write(reinterpret_cast(&size), 2); + EXPECT_EQ(result, size); + result = buffer.Write(reinterpret_cast(data.c_str()), size - 2); + EXPECT_EQ(result, size); + EXPECT_EQ(buffer.Free(), 3u); EXPECT_EQ(buffer.Used(), 7u); - testAdmin.Sync("client wrote"); - testAdmin.Sync("server peek"); - testAdmin.Sync("server read"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); buffer.Flush(); EXPECT_EQ(buffer.Used(), 0u); - testAdmin.Sync("client flush"); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); }; - static std::function lambdaVar = lambdaFunc; + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + // a small delay so the child can be set up + SleepMs(maxInitTime); - IPTestAdministrator::OtherSideMain otherSide = [](IPTestAdministrator& testAdmin ) { lambdaVar(testAdmin); }; - // This side (tested) acts as server - IPTestAdministrator testAdmin(otherSide); - { - uint32_t cyclicBufferSize = 10; - const uint32_t mode = - ::Thunder::Core::File::Mode::USER_READ | ::Thunder::Core::File::Mode::USER_WRITE | ::Thunder::Core::File::Mode::USER_EXECUTE | - ::Thunder::Core::File::Mode::GROUP_READ | ::Thunder::Core::File::Mode::GROUP_WRITE | - ::Thunder::Core::File::Mode::SHAREABLE; + constexpr uint32_t cyclicBufferSize = 10; + + constexpr uint32_t mode = ::Thunder::Core::File::Mode::USER_READ + | ::Thunder::Core::File::Mode::USER_WRITE + | ::Thunder::Core::File::Mode::USER_EXECUTE + | ::Thunder::Core::File::Mode::GROUP_READ + | ::Thunder::Core::File::Mode::GROUP_WRITE + | ::Thunder::Core::File::Mode::SHAREABLE + ; + + CyclicBufferTest buffer(bufferName.c_str(), mode, cyclicBufferSize, true); - CyclicBufferTest buffer(bufferName.c_str(), mode, cyclicBufferSize, true); EXPECT_TRUE(buffer.IsOverwrite()); EXPECT_TRUE(buffer.IsValid()); - testAdmin.Sync("setup server"); - testAdmin.Sync("setup client"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); uint8_t loadBuffer[cyclicBufferSize + 1]; uint16_t size = 9; string data = "abcdefi"; uint32_t result = buffer.Write(reinterpret_cast(&size), 2); + result = buffer.Write(reinterpret_cast(data.c_str()), size - 2); EXPECT_EQ(result, data.size()); - testAdmin.Sync("server wrote"); - testAdmin.Sync("client read"); - testAdmin.Sync("client wrote"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); buffer.Peek(reinterpret_cast(&size), 2); EXPECT_EQ(size, buffer.Used()); + result = buffer.Peek(loadBuffer, size); loadBuffer[result] = '\0'; EXPECT_EQ(result, 7u); - EXPECT_STREQ((char*)loadBuffer +2, "lmnop"); + EXPECT_STREQ(reinterpret_cast(&loadBuffer[0] + 2), "lmnop"); + + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); - testAdmin.Sync("server peek"); EXPECT_EQ(buffer.Free(), 3u); + buffer.Read(reinterpret_cast(&size), 2); EXPECT_EQ(buffer.Used(), static_cast(size - 2)); + result = buffer.Read(loadBuffer, size - 2); loadBuffer[result] = '\0'; EXPECT_EQ(result, 5u); - EXPECT_STREQ((char*)loadBuffer, "lmnop"); + EXPECT_STREQ(reinterpret_cast(&loadBuffer[0]), "lmnop"); + EXPECT_EQ(buffer.Free(), 10u); EXPECT_EQ(buffer.Used(), 0u); @@ -1342,12 +1487,19 @@ namespace Core { EXPECT_FALSE(buffer.IsLocked()); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("server read"); - testAdmin.Sync("client flush"); + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + EXPECT_EQ(buffer.Free(), cyclicBufferSize); buffer.Close(); - } + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + ::Thunder::Core::Singleton::Dispose(); } TEST(Core_CyclicBuffer, LockUnlock_WithoutDataPresent) @@ -1387,9 +1539,12 @@ namespace Core { TEST(Core_CyclicBuffer, DISABLED_LockUnLock_FromParentAndForks) { + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 1; + std::string bufferName {"cyclicbuffer04"}; - auto lambdaFunc = [bufferName](IPTestAdministrator & testAdmin) { + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { uint32_t cyclicBufferSize = 20; const uint32_t mode = @@ -1399,23 +1554,23 @@ namespace Core { ::Thunder::Core::CyclicBuffer buffer(bufferName.c_str(), mode, cyclicBufferSize, false); - testAdmin.Sync("setup server"); - testAdmin.Sync("setup client"); +// testAdmin.Sync("setup server"); +// testAdmin.Sync("setup client"); EXPECT_EQ(buffer.LockPid(), 0u); buffer.Lock(false); EXPECT_EQ(buffer.LockPid(), static_cast(getpid())); - testAdmin.Sync("server locked"); +// testAdmin.Sync("server locked"); - testAdmin.Sync("client wrote"); +// testAdmin.Sync("client wrote"); buffer.Unlock(); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("server unlocked"); +// testAdmin.Sync("server unlocked"); - testAdmin.Sync("client locked"); +// testAdmin.Sync("client locked"); EXPECT_NE(buffer.LockPid(), 0u); EXPECT_NE(buffer.LockPid(), static_cast(getpid())); - testAdmin.Sync("server verified"); +// testAdmin.Sync("server verified"); // TODO: What is the purpose of lock ?? since we are able to write from server process // even it is locked from client process @@ -1426,22 +1581,19 @@ namespace Core { uint8_t loadBuffer[cyclicBufferSize + 1]; result = buffer.Peek(loadBuffer, buffer.Used()); loadBuffer[result] = '\0'; - testAdmin.Sync("server wrote and peeked"); +// testAdmin.Sync("server wrote and peeked"); - testAdmin.Sync("client unlocked"); +// testAdmin.Sync("client unlocked"); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("client read"); +// testAdmin.Sync("client read"); }; - static std::function lambdaVar = lambdaFunc; + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + // a small delay so the child can be set up + SleepMs(maxInitTime); - IPTestAdministrator::OtherSideMain otherSide = [](IPTestAdministrator& testAdmin ) { lambdaVar(testAdmin); }; - - // This side (tested) acts as client - IPTestAdministrator testAdmin(otherSide, 10); - { - testAdmin.Sync("setup server"); +// testAdmin.Sync("setup server"); uint32_t cyclicBufferSize = 0; const uint32_t mode = @@ -1452,8 +1604,8 @@ namespace Core { ::Thunder::Core::CyclicBuffer buffer(bufferName.c_str(), mode, cyclicBufferSize, true); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("setup client"); - testAdmin.Sync("server locked"); +// testAdmin.Sync("setup client"); +// testAdmin.Sync("server locked"); EXPECT_NE(buffer.LockPid(), 0u); EXPECT_NE(buffer.LockPid(), static_cast(getpid())); @@ -1465,37 +1617,45 @@ namespace Core { EXPECT_NE(buffer.LockPid(), 0u); EXPECT_NE(buffer.LockPid(), static_cast(getpid())); - testAdmin.Sync("client wrote"); - testAdmin.Sync("server unlocked"); +// testAdmin.Sync("client wrote"); +// testAdmin.Sync("server unlocked"); EXPECT_EQ(buffer.LockPid(), 0u); buffer.Lock(false); EXPECT_EQ(buffer.LockPid(), static_cast(getpid())); - testAdmin.Sync("client locked"); - testAdmin.Sync("server verified"); +// testAdmin.Sync("client locked"); +// testAdmin.Sync("server verified"); - testAdmin.Sync("server wrote and peeked"); +// testAdmin.Sync("server wrote and peeked"); buffer.Unlock(); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("client unlocked"); +// testAdmin.Sync("client unlocked"); uint8_t loadBuffer[cyclicBufferSize + 1]; result = buffer.Read(loadBuffer, 4); loadBuffer[result] = '\0'; EXPECT_STREQ((char*)loadBuffer, "jklm"); - testAdmin.Sync("client read"); +// testAdmin.Sync("client read"); buffer.Close(); - } + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + ::Thunder::Core::Singleton::Dispose(); } //TODO: revisit these test cases after fixing the issues with cyclicbuffer lock/unlock sequence TEST(Core_CyclicBuffer, DISABLED_LockUnlock_FromParentAndForks_WithDataPresent) { + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 1; + std::string bufferName {"cyclicbuffer05"}; - auto lambdaFunc = [bufferName](IPTestAdministrator & testAdmin) { + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { uint32_t cyclicBufferSize = 20; const uint32_t mode = @@ -1505,13 +1665,13 @@ namespace Core { ::Thunder::Core::CyclicBuffer buffer(bufferName.c_str(), mode, cyclicBufferSize, false); - testAdmin.Sync("setup server"); - testAdmin.Sync("setup client"); +// testAdmin.Sync("setup server"); +// testAdmin.Sync("setup client"); EXPECT_EQ(buffer.LockPid(), 0u); buffer.Lock(true, 100); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("server timedLock"); +// testAdmin.Sync("server timedLock"); EXPECT_EQ(buffer.IsLocked(), false); { @@ -1539,7 +1699,7 @@ namespace Core { EXPECT_EQ(buffer.IsLocked(), true); buffer.Unlock(); } - testAdmin.Sync("server locked & unlocked"); +// testAdmin.Sync("server locked & unlocked"); buffer.Flush(); EXPECT_EQ(buffer.Used(), 0u); @@ -1558,7 +1718,7 @@ namespace Core { } sleep(1); - testAdmin.Sync("server locked"); +// testAdmin.Sync("server locked"); EXPECT_EQ(buffer.LockPid(), 0u); EXPECT_EQ(buffer.IsLocked(), false); @@ -1574,24 +1734,18 @@ namespace Core { event.ResetEvent(); threadLock.Stop(); - testAdmin.Sync("server locked & wrote"); +// testAdmin.Sync("server locked & wrote"); buffer.Unlock(); - testAdmin.Sync("server unlocked"); - testAdmin.Sync("client wrote & locked"); +// testAdmin.Sync("server unlocked"); +// testAdmin.Sync("client wrote & locked"); - testAdmin.Sync("client exit"); +// testAdmin.Sync("client exit"); EXPECT_EQ(buffer.LockPid(), 0u); }; - static std::function lambdaVar = lambdaFunc; - - IPTestAdministrator::OtherSideMain otherSide = [](IPTestAdministrator& testAdmin ) { lambdaVar(testAdmin); }; - - // This side (tested) acts as client - IPTestAdministrator testAdmin(otherSide, 15); - { - testAdmin.Sync("setup server"); + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { +// testAdmin.Sync("setup server"); uint32_t cyclicBufferSize = 0; const uint32_t mode = @@ -1600,17 +1754,17 @@ namespace Core { ::Thunder::Core::File::Mode::SHAREABLE; ::Thunder::Core::CyclicBuffer buffer(bufferName.c_str(), mode, cyclicBufferSize, true); - testAdmin.Sync("setup client"); +// testAdmin.Sync("setup client"); - testAdmin.Sync("server timedLock"); +// testAdmin.Sync("server timedLock"); - testAdmin.Sync("server locked & unlocked"); +// testAdmin.Sync("server locked & unlocked"); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("server locked"); +// testAdmin.Sync("server locked"); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("server locked & wrote"); +// testAdmin.Sync("server locked & wrote"); EXPECT_NE(buffer.LockPid(), 0u); - testAdmin.Sync("server unlocked"); +// testAdmin.Sync("server unlocked"); EXPECT_EQ(buffer.LockPid(), 0u); // Check Lock Timed Out after wait Time @@ -1643,12 +1797,17 @@ namespace Core { threadLock.Stop(); EXPECT_EQ(buffer.LockPid(), static_cast(getpid())); - testAdmin.Sync("client wrote & locked"); +// testAdmin.Sync("client wrote & locked"); buffer.Unlock(); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("client exit"); +// testAdmin.Sync("client exit"); buffer.Close(); - } + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + ::Thunder::Core::Singleton::Dispose(); } TEST(Core_CyclicBuffer, DISABLED_LockUnlock_UsingAlert) @@ -1685,9 +1844,12 @@ namespace Core { } TEST(Core_CyclicBuffer, DISABLED_LockUnlock_FromParentAndForks_UsingAlert) { + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 1; + std::string bufferName {"cyclicbuffer05"}; - auto lambdaFunc = [bufferName](IPTestAdministrator & testAdmin) { + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { uint32_t cyclicBufferSize = 20; const uint32_t mode = @@ -1697,15 +1859,15 @@ namespace Core { ::Thunder::Core::CyclicBuffer buffer(bufferName.c_str(), mode, cyclicBufferSize, false); - testAdmin.Sync("setup server"); - testAdmin.Sync("setup client"); +// testAdmin.Sync("setup server"); +// testAdmin.Sync("setup client"); EXPECT_EQ(buffer.LockPid(), 0u); ::Thunder::Core::Event event(false, false); ThreadLock threadLock(buffer, ::Thunder::Core::infinite, event); threadLock.Run(); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("server locked"); +// testAdmin.Sync("server locked"); // Lock before requesting cyclic buffer lock if (event.Lock(MaxSignalWaitTime * 2) == ::Thunder::Core::ERROR_NONE) { @@ -1721,21 +1883,18 @@ namespace Core { event.ResetEvent(); threadLock.Stop(); - testAdmin.Sync("server alerted"); - testAdmin.Sync("client locked"); +// testAdmin.Sync("server alerted"); +// testAdmin.Sync("client locked"); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("client alerted"); +// testAdmin.Sync("client alerted"); EXPECT_EQ(buffer.LockPid(), 0u); }; - static std::function lambdaVar = lambdaFunc; - - IPTestAdministrator::OtherSideMain otherSide = [](IPTestAdministrator& testAdmin ) { lambdaVar(testAdmin); }; + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + // a small delay so the child can be set up + SleepMs(maxInitTime); - // This side (tested) acts as client - IPTestAdministrator testAdmin(otherSide, 10); - { - testAdmin.Sync("setup server"); +// testAdmin.Sync("setup server"); uint32_t cyclicBufferSize = 0; const uint32_t mode = @@ -1745,19 +1904,19 @@ namespace Core { ::Thunder::Core::CyclicBuffer buffer(bufferName.c_str(), mode, cyclicBufferSize, true); - testAdmin.Sync("setup client"); - testAdmin.Sync("server locked"); +// testAdmin.Sync("setup client"); +// testAdmin.Sync("server locked"); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("server alerted"); +// testAdmin.Sync("server alerted"); EXPECT_EQ(buffer.LockPid(), 0u); ::Thunder::Core::Event event(false, false); ThreadLock threadLock(buffer, ::Thunder::Core::infinite, event); threadLock.Run(); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("client locked"); +// testAdmin.Sync("client locked"); // Lock before requesting cyclic buffer lock if (event.Lock(MaxSignalWaitTime * 2) == ::Thunder::Core::ERROR_NONE) { @@ -1774,10 +1933,15 @@ namespace Core { threadLock.Stop(); EXPECT_EQ(buffer.LockPid(), 0u); - testAdmin.Sync("client alerted"); +// testAdmin.Sync("client alerted"); buffer.Close(); - } + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + ::Thunder::Core::Singleton::Dispose(); } diff --git a/Tests/unit/core/test_cyclicbuffer_dataexchange.cpp b/Tests/unit/core/test_cyclicbuffer_dataexchange.cpp new file mode 100644 index 000000000..a066539b4 --- /dev/null +++ b/Tests/unit/core/test_cyclicbuffer_dataexchange.cpp @@ -0,0 +1,105 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include "../../cyclic-buffer/process.h" + + +namespace WPEFramework { +namespace Core { +namespace Tests { + +#define ASYNC_TIMEOUT_BEGIN \ + std::promise promise; \ + std::future future = promise.get_future(); \ + std::thread([&](std::promise completed) \ + { /* Before code that should complete before timeout expires */ + +#define ASYNC_TIMEOUT_END(MILLISECONDS /* timeout in milliseconds */, EXPECTATION /* boolean */) \ + /* After code that should complete timely */ \ + /* completed.set_value(true); */ \ + completed.set_value_at_thread_exit(true); \ + } \ + , std::move(promise)).detach() \ + ; \ + bool result = future.wait_for(std::chrono::milliseconds(MILLISECONDS)) != std::future_status::timeout; /* Task completed before timeout */ \ + EXPECT_TRUE(result == EXPECTATION); /* Task completed before timeout */ \ + if (!result) { \ + TRACE_L1(_T("Error : Stopping unresposive process.")); \ + killpg(getpgrp(), SIGUSR1); /* Possible 'unresponsive' system, 'unlock' all related 'child' processes, default action is terminate */ \ + } + +TEST(Core_CyclicBuffer, DataExchangeTimeout) +{ + constexpr uint8_t maxChildren = 1; + + constexpr uint32_t memoryMappedFileRequestedSize = 30;//446; + constexpr uint32_t internalBufferSize = 40;//446; + + constexpr char fileName[] = "/tmp/SharedCyclicBuffer"; + + constexpr uint32_t totalRuntime = Core::infinite; // Milliseconds + constexpr uint32_t timeout = 10000; // Milliseconds + + WPEFramework::Tests::Process process(fileName); + + ASYNC_TIMEOUT_BEGIN; // Avoid leaking resources, eg, children + + EXPECT_TRUE(process.SetTotalRuntime(totalRuntime) + && process.SetNumReservedBlocks(2) + && process.SetParentUsers(0, 0) /* 0 extra writer(s), 0 reader(s) */ + && process.SetChildUsers(0, 1) /* 1 writer(s), 1 reader(s) */ + && process.Execute() + ); + + ASYNC_TIMEOUT_END(timeout, false /* expect no timeout */); +} + +TEST(Core_CyclicBuffer, DataExchange) +{ + constexpr uint8_t maxChildren = 1; + + constexpr uint32_t memoryMappedFileRequestedSize = 30;//446; + constexpr uint32_t internalBufferSize = 40;//446; + + constexpr char fileName[] = "/tmp/SharedCyclicBuffer"; + + constexpr uint32_t totalRuntime = 10000; // Milliseconds + constexpr uint32_t timeout = totalRuntime + 10000; // Milliseconds + + WPEFramework::Tests::Process process(fileName); + + ASYNC_TIMEOUT_BEGIN; // Avoid leaking resources, eg, children + + EXPECT_TRUE(process.SetTotalRuntime(totalRuntime) + && process.SetNumReservedBlocks(2) + && process.SetParentUsers(0, 0) /* 0 extra writer(s), 0 reader(s) */ + && process.SetChildUsers(0, 1) /* 1 writer(s), 1 reader(s) */ + && process.Execute() + ); + + ASYNC_TIMEOUT_END(timeout, true /* expect no timeout */); +} + +} // Tests +} // Core +} // WPEFramework From 2a4a9428f857f569008faf6dfbd17dac5df3906a Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:11:25 +0200 Subject: [PATCH 4/4] Development/valuerecorder (#1722) * [core / Tests/unit/core] : Fix build 'test_valuerecorder'. * [core / Tests/unit/core] : prepare 'test_value_recorder' for unpredictable failure. * [Tests/unit/core] : fix build 'test_valuerecorder' * [Tests/unit/core] : redesign 'test_value_recorder' * Tests/unit/core] : Always reset to a valid position * [Tests/unit/core] : Explicitly save the content of the writer before employing the reader * [Tests/unit/core] : Adjust the expectations for multiple files --------- Co-authored-by: Pierre Wielders --- Tests/unit/core/CMakeLists.txt | 2 +- Tests/unit/core/test_valuerecorder.cpp | 178 ++++++++++++------------- 2 files changed, 85 insertions(+), 95 deletions(-) diff --git a/Tests/unit/core/CMakeLists.txt b/Tests/unit/core/CMakeLists.txt index de3786839..eaedf30aa 100644 --- a/Tests/unit/core/CMakeLists.txt +++ b/Tests/unit/core/CMakeLists.txt @@ -73,7 +73,7 @@ add_executable(${TEST_RUNNER_NAME} test_time.cpp test_timer.cpp test_tristate.cpp - #test_valuerecorder.cpp + test_valuerecorder.cpp test_weblinkjson.cpp test_weblinktext.cpp test_websocketjson.cpp diff --git a/Tests/unit/core/test_valuerecorder.cpp b/Tests/unit/core/test_valuerecorder.cpp index db51c7226..4babdbf96 100644 --- a/Tests/unit/core/test_valuerecorder.cpp +++ b/Tests/unit/core/test_valuerecorder.cpp @@ -29,112 +29,102 @@ namespace Thunder { namespace Tests { namespace Core { - const unsigned int BLOCKSIZE = 20; + constexpr unsigned int BLOCKSIZE = 20; - class WriterClass : public RecorderType::Writer + class WriterClass { - public: - WriterClass() = delete; - - WriterClass(string filename) - : Writer(filename) - , _file(filename) - { - } - - ~WriterClass() - { - } - - public: - void WriterJob() - { - uint8_t arr[] = {1,2,3}; - SetBuffer(arr); - auto object = Create(_file); - Record(10); - uint64_t TimeValue = Time(); - std::string storageName = Source(); - uint32_t value = Value(); - object.Release(); - } - - private: - string _file; + public: + WriterClass() = delete; + + WriterClass(const string& filename) + : _file{filename} + { + _writer = ::Thunder::Core::RecorderType::Writer::Create(filename); + } + + ~WriterClass() + { + _writer.Release(); + } + + public: + void WriterJob(const uint32_t& value) + { + ASSERT_TRUE(_writer.IsValid()); + + _writer->Record(value); + + EXPECT_STREQ(_writer->Source().c_str(), _file.c_str()); + EXPECT_EQ(_writer->Value(), value); + } + + void Save() + { + _writer->Save(); + } + + private: + const string _file; + ::Thunder::Core::ProxyType<::Thunder::Core::RecorderType::Writer> _writer; }; - class ReaderClass : public RecorderType::Reader + class ReaderClass : public ::Thunder::Core::RecorderType::Reader { - public: - ReaderClass() = delete; - - ReaderClass(string filename) - : Reader(filename) - , _file(filename) - { - } - - ReaderClass(const ProxyType& recorder, const uint32_t id = static_cast(~0)) - : Reader(recorder->Source()) - , _file(recorder->Source()) - { - } - - ~ReaderClass() - { - } - - public: - void ReaderJob() - { - Next(); - EXPECT_TRUE(IsValid()); - - uint32_t time = 20; - ::Thunder::Core::Time curTime = ::Thunder::Core::Time::Now(); - curTime.Add(time); - uint32_t index = Store(curTime.Ticks(), 1); - - StepForward(); - StepBack(); - ClearData(); - - Reader obj1(_file, 1u); - EXPECT_FALSE(obj1.Previous()); - EXPECT_TRUE(obj1.Next()); - - EXPECT_EQ(StartId(),1u); - - if (EndId() == StartId()) - EXPECT_EQ(EndId(),1u); - else - EXPECT_EQ(EndId(),2u); - - uint32_t id = EndId(); - std::string storageName = Source(); - } - - private: - string _file; - }; + public: + ReaderClass() = delete; - TEST(test_valuerecorder, test_writer) - { - string filename = "baseRecorder.txt"; + ReaderClass(const string& filename) + : _file{filename} + , Reader(filename) + { + } + + ~ReaderClass() = default; + + public: + void ReaderJob(const uint32_t value) + { + static_assert(std::is_same<::Thunder::Core::Time::microsecondsfromepoch, uint64_t>::value); + + const ::Thunder::Core::Time::microsecondsfromepoch readTime = ::Thunder::Core::Time::Now().Ticks(); + + // Get a valid position + Reset(StartId()); + + ASSERT_TRUE(IsValid()); + + EXPECT_EQ(Id(), StartId()); - auto obj1 = RecorderType::Writer::Create(filename); + EXPECT_STREQ(Source().c_str(), _file.c_str()); + EXPECT_EQ(value, Value()); - obj1->Copy(*(obj1),1); - obj1->Copy(*(obj1),100); + ASSERT_EQ(Id(), EndId()); - static_cast(*obj1).WriterJob(); + // Load next file if it exist if no additional data exist + EXPECT_FALSE(Next()); + + // The previous failed so no new files has been loaded and the current file is still used + EXPECT_TRUE(Previous()); + + EXPECT_LE(Time(), readTime); + } + + private: + const string _file; + }; + + TEST(test_valuerecorder, test_writer) + { + const string filename = "baseRecorder.txt"; - ReaderClass obj2(filename); - obj2.ReaderJob(); + constexpr uint32_t value = 10; - ReaderClass obj4(ProxyType(obj3)); + WriterClass writer(filename); + writer.WriterJob(value); + writer.Save(); - obj1.Release(); + ReaderClass reader(filename); + reader.ReaderJob(value); } } // Core