diff --git a/.github/workflows/MacOs.yml b/.github/workflows/MacOs.yml index 1091706..588d501 100644 --- a/.github/workflows/MacOs.yml +++ b/.github/workflows/MacOs.yml @@ -19,8 +19,7 @@ jobs: { tool: apple-clang }, { tool: gcc, ver: 10 }, { tool: gcc, ver: 11 }, - { tool: gcc, ver: 12 }, - { tool: gcc, ver: 13 } ] + { tool: gcc, ver: 12 } ] build_type: [ Release ] os: [ macos-12 ] std: [ 17 ] diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e5854f..0a394f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.15.0) set(_7BIT_DI_LIBRARY 7bitDI) set(_7BIT_DI_VERSION_MAJOR 3) -set(_7BIT_DI_VERSION_MINOR 2) +set(_7BIT_DI_VERSION_MINOR 3) set(_7BIT_DI_VERSION_PATCH 0) set(_7BIT_DI_VERSION ${_7BIT_DI_VERSION_MAJOR}.${_7BIT_DI_VERSION_MINOR}.${_7BIT_DI_VERSION_PATCH}) diff --git a/Docs/advanced-guides.rst b/Docs/advanced-guides.rst index 0eeae13..b26ea9a 100644 --- a/Docs/advanced-guides.rst +++ b/Docs/advanced-guides.rst @@ -11,5 +11,4 @@ Advanced Guides advanced-guides/using-aliases advanced-guides/register-utility-class advanced-guides/injected-utility-class - advanced-guides/configuring-service-provider advanced-guides/building-library diff --git a/Docs/advanced-guides/injected-utility-class.rst b/Docs/advanced-guides/injected-utility-class.rst index 5ceaf89..36f0558 100644 --- a/Docs/advanced-guides/injected-utility-class.rst +++ b/Docs/advanced-guides/injected-utility-class.rst @@ -1,16 +1,16 @@ Injected Utility Class ======================================== -Library provides also Injected_ utility class. -This base class has inject() method that can be used to inject services in simple inline way, also -there are InjectedSingleton, InjectedScoped and InjectedTransient base classes that are inheriting -from Injected and Registered classes to combine these two features. Injected class has also method -getProvider(), raw provider can be used to get keyed services for example. +The library provides also Injected_ utility class. +This base class has inject() method that can be used to inject services in a simple inline way, also +there are InjectedSingleton, InjectedScoped and InjectedTransient base classes that inherit +from Injected and Registered classes to combine these two features. The injected class has also a method +getProvider(), the raw provider can be used to get keyed services for example. .. _Injected: ../reference/di/utils/injected.html .. note:: - Class should inherit Injected constructor with 'using Injected::Injected;' in public section + The class should inherit Injected constructor with 'using Injected::Injected;' in the public section .. literalinclude:: ../../Examples/Guides/InjectedUtilityClass.cpp :caption: Examples/Guides/InjectedUtilityClass diff --git a/Docs/advanced-guides/register-utility-class.rst b/Docs/advanced-guides/register-utility-class.rst index ba17282..079de67 100644 --- a/Docs/advanced-guides/register-utility-class.rst +++ b/Docs/advanced-guides/register-utility-class.rst @@ -1,13 +1,13 @@ Register Utility Class ======================================== -Library provides simple template RegisterService_ utility class. -This base class can be used to automatically register class in service collection with use of specialised TRegisterer. +The library provides a simple template RegisterService_ utility class. +This base class can be used to automatically register class in service collection with the use of a specialized TRegisterer. There are already created aliases RegisterSingleton_, RegisterScoped_, RegisterTransient_ that are registering services in GlobalServices_ singleton. .. note:: - Class should inherit Injected constructor with 'using Injected::Injected;' in public section + The class should inherit Injected constructor with 'using Injected::Injected;' in the public section .. _RegisterService: ../reference/di/utils/register.html .. _RegisterSingleton: ../reference/di/utils/register.html diff --git a/Docs/advanced-guides/using-aliases.rst b/Docs/advanced-guides/using-aliases.rst index 5d82c33..4a2d9bb 100644 --- a/Docs/advanced-guides/using-aliases.rst +++ b/Docs/advanced-guides/using-aliases.rst @@ -2,11 +2,11 @@ Using Aliases ======================================== With the use of aliases, one service can be injected through its multiple base classes, also aliases can be chained. -In case of injecting multiple aliases all real services will be fetched. +In case of injecting multiple aliases, all real services will be fetched. .. warning:: Using aliases is resource intensive, especially for injecting transient and multiple services, provider recursively - traverses through aliases chain to find proper service. Mixing scoped and singleton aliases for same base type will + traverses through the aliases chain to find the proper service. Mixing scoped and singleton aliases for the same base type will lead to undefined behavior .. literalinclude:: ../../Examples/Guides/ServiceAliases.cpp diff --git a/Docs/basic-guides.rst b/Docs/basic-guides.rst index 81c6ed4..62380f3 100644 --- a/Docs/basic-guides.rst +++ b/Docs/basic-guides.rst @@ -12,3 +12,4 @@ Basic Guides basic-guides/injecting-multiple-services basic-guides/injection-rules basic-guides/injecting-service-provider + basic-guides/configuring-service-provider diff --git a/Docs/advanced-guides/configuring-service-provider.rst b/Docs/basic-guides/configuring-service-provider.rst similarity index 91% rename from Docs/advanced-guides/configuring-service-provider.rst rename to Docs/basic-guides/configuring-service-provider.rst index c90fca8..11d1f67 100644 --- a/Docs/advanced-guides/configuring-service-provider.rst +++ b/Docs/basic-guides/configuring-service-provider.rst @@ -10,8 +10,8 @@ read comment documentation for details: :caption: Include/SevenBit/DI/ServiceProviderOptions.hpp :language: C++ -Pass the custom options to the ServiceCollection buildServiceProvider method to change produced -service provider behaviour +Pass the custom options to the ServiceCollection buildServiceProvider method to change the produced +service provider behavior .. literalinclude:: ../../Examples/Guides/ConfiguredServiceProvider.cpp :caption: Examples/Guides/ConfiguredServiceProvider diff --git a/Docs/conf.py b/Docs/conf.py index 5ec2320..e646c4f 100644 --- a/Docs/conf.py +++ b/Docs/conf.py @@ -12,7 +12,7 @@ def createIfNotExists(path): project = "7bitDI" copyright = "2023, 7BitCoder Sylwester Dawida" author = "Sylwester Dawida" -version = "3.2.0" +version = "3.3.0" extensions = [ "sphinx.ext.autodoc", diff --git a/Docs/getting-started.rst b/Docs/getting-started.rst index 79fb6e7..b942949 100644 --- a/Docs/getting-started.rst +++ b/Docs/getting-started.rst @@ -1,6 +1,16 @@ Getting Started ========================== +Main Features +-------------------- + +* Implementation separation +* Multiple implementations +* Keyed services +* Service aliases +* Thread safe +* Strong destruction order + Supported Platforms -------------------- @@ -27,8 +37,8 @@ Installation **There are a few ways of installation:** -#. Using Cmake fetch content api - Recommended - Update CMakeLists.txt file with following code +#. Using Cmake fetch content API - Recommended + Update CMakeLists.txt file with the following code .. code-block:: Cmake @@ -36,7 +46,7 @@ Installation FetchContent_Declare( 7bitDI GIT_REPOSITORY https://github.com/7bitcoder/7bitDI.git - GIT_TAG v3.2.0 + GIT_TAG v3.3.0 ) FetchContent_MakeAvailable(7bitDI) @@ -48,7 +58,7 @@ Installation .. code-block:: Txt [requires] - 7bitdi/3.2.0 + 7bitdi/3.3.0 change the version to newer if available, then run the command: diff --git a/Examples/Guides/ConfiguredServiceProvider.cpp b/Examples/Guides/ConfiguredServiceProvider.cpp index 2b7c8c7..17a67a4 100644 --- a/Examples/Guides/ConfiguredServiceProvider.cpp +++ b/Examples/Guides/ConfiguredServiceProvider.cpp @@ -39,6 +39,7 @@ int main() options.strongDestructionOrder = true; options.prebuildSingletons = true; options.checkServiceGlobalUniqueness = false; + options.threadSafe = true; ServiceProvider provider = ServiceCollection{} .addSingleton<IService, Service>() diff --git a/Include/SevenBit/DI/CmakeDef.hpp b/Include/SevenBit/DI/CmakeDef.hpp index 9dcd81a..56f8e83 100644 --- a/Include/SevenBit/DI/CmakeDef.hpp +++ b/Include/SevenBit/DI/CmakeDef.hpp @@ -13,5 +13,5 @@ #endif #define _7BIT_DI_VERSION_MAJOR 3 -#define _7BIT_DI_VERSION_MINOR 2 +#define _7BIT_DI_VERSION_MINOR 3 /* #undef _7BIT_DI_VERSION_PATCH */ diff --git a/Include/SevenBit/DI/Details/Containers/Impl/ServiceInstancesMap.hpp b/Include/SevenBit/DI/Details/Containers/Impl/ServiceInstancesMap.hpp index df48a37..1a8d494 100644 --- a/Include/SevenBit/DI/Details/Containers/Impl/ServiceInstancesMap.hpp +++ b/Include/SevenBit/DI/Details/Containers/Impl/ServiceInstancesMap.hpp @@ -42,9 +42,9 @@ namespace sb::di::details { for (auto it = _constructionOrder.rbegin(); it != _constructionOrder.rend(); ++it) { - if (const auto list = findInstances(*it)) + if (const auto listPtr = findInstances(*it)) { - list->clear(); + listPtr->clear(); } } } diff --git a/Include/SevenBit/DI/Details/Core/IServiceInstanceProviderRoot.hpp b/Include/SevenBit/DI/Details/Core/IServiceInstanceProviderRoot.hpp index 2005116..67aab32 100644 --- a/Include/SevenBit/DI/Details/Core/IServiceInstanceProviderRoot.hpp +++ b/Include/SevenBit/DI/Details/Core/IServiceInstanceProviderRoot.hpp @@ -1,5 +1,7 @@ #pragma once +#include <mutex> + #include "SevenBit/DI/LibraryConfig.hpp" #include "SevenBit/DI/Details/Containers/ServiceDescriptorsMap.hpp" @@ -16,6 +18,8 @@ namespace sb::di::details virtual ServiceInstancesCreator &getRootCreator() = 0; + virtual std::recursive_mutex *tryGetSyncMutex() = 0; + virtual ~IServiceInstanceProviderRoot() = default; }; } // namespace sb::di::details diff --git a/Include/SevenBit/DI/Details/Core/Impl/ServiceInstanceProvider.hpp b/Include/SevenBit/DI/Details/Core/Impl/ServiceInstanceProvider.hpp index 9d39b3c..365eb3f 100644 --- a/Include/SevenBit/DI/Details/Core/Impl/ServiceInstanceProvider.hpp +++ b/Include/SevenBit/DI/Details/Core/Impl/ServiceInstanceProvider.hpp @@ -27,7 +27,7 @@ namespace sb::di::details INLINE IServiceInstanceProvider::Ptr ServiceInstanceProvider::createScope() const { - return std::make_unique<ServiceInstanceProvider>(_root, _options); + return std::make_unique<ServiceInstanceProvider>(_root, getOptions()); } INLINE const ServiceInstance &ServiceInstanceProvider::getInstance(const ServiceId &id) diff --git a/Include/SevenBit/DI/Details/Core/Impl/ServiceInstanceProviderRoot.hpp b/Include/SevenBit/DI/Details/Core/Impl/ServiceInstanceProviderRoot.hpp index f684349..05de8bb 100644 --- a/Include/SevenBit/DI/Details/Core/Impl/ServiceInstanceProviderRoot.hpp +++ b/Include/SevenBit/DI/Details/Core/Impl/ServiceInstanceProviderRoot.hpp @@ -10,12 +10,16 @@ namespace sb::di::details : ServiceInstanceProvider(*this, options), _descriptorsMap(options.checkServiceGlobalUniqueness), _singletons(options.strongDestructionOrder) { - _descriptorsMap.seal(); } INLINE void ServiceInstanceProviderRoot::init(ServiceProvider &serviceProvider) { ServiceInstanceProvider::init(serviceProvider); + _descriptorsMap.seal(); + if (getOptions().threadSafe) + { + _mutex = std::make_unique<std::recursive_mutex>(); + } if (getOptions().prebuildSingletons) { prebuildSingletons(); diff --git a/Include/SevenBit/DI/Details/Core/ServiceInstanceProvider.hpp b/Include/SevenBit/DI/Details/Core/ServiceInstanceProvider.hpp index 04ace73..33ea0cb 100644 --- a/Include/SevenBit/DI/Details/Core/ServiceInstanceProvider.hpp +++ b/Include/SevenBit/DI/Details/Core/ServiceInstanceProvider.hpp @@ -18,9 +18,9 @@ namespace sb::di::details class EXPORT ServiceInstanceProvider : public IServiceInstanceProvider { ServiceProviderOptions _options; + IServiceInstanceProviderRoot &_root; ServiceInstancesCreator _instancesCreator; ServiceAliasesCreator _aliasesCreator; - IServiceInstanceProviderRoot &_root; ServiceInstancesMap _scoped; public: @@ -39,6 +39,8 @@ namespace sb::di::details [[nodiscard]] IServiceInstanceProvider::Ptr createScope() const override; + std::recursive_mutex *tryGetSyncMutex() override { return _root.tryGetSyncMutex(); } + const ServiceInstance &getInstance(const TypeId serviceTypeId) override { return getInstance(ServiceId{serviceTypeId}); diff --git a/Include/SevenBit/DI/Details/Core/ServiceInstanceProviderRoot.hpp b/Include/SevenBit/DI/Details/Core/ServiceInstanceProviderRoot.hpp index d51ac52..a051681 100644 --- a/Include/SevenBit/DI/Details/Core/ServiceInstanceProviderRoot.hpp +++ b/Include/SevenBit/DI/Details/Core/ServiceInstanceProviderRoot.hpp @@ -13,6 +13,7 @@ namespace sb::di::details { ServiceDescriptorsMap _descriptorsMap; ServiceInstancesMap _singletons; + std::unique_ptr<std::recursive_mutex> _mutex; public: using Ptr = std::unique_ptr<ServiceInstanceProviderRoot>; @@ -26,7 +27,6 @@ namespace sb::di::details _descriptorsMap(begin, end, options.checkServiceGlobalUniqueness), _singletons(options.strongDestructionOrder) { - _descriptorsMap.seal(); } void init(ServiceProvider &serviceProvider) override; @@ -37,6 +37,8 @@ namespace sb::di::details ServiceInstancesCreator &getRootCreator() override { return getCreator(); } + std::recursive_mutex *tryGetSyncMutex() override { return _mutex.get(); } + private: void prebuildSingletons(); }; diff --git a/Include/SevenBit/DI/IServiceInstanceProvider.hpp b/Include/SevenBit/DI/IServiceInstanceProvider.hpp index d9d5015..2ea20cd 100644 --- a/Include/SevenBit/DI/IServiceInstanceProvider.hpp +++ b/Include/SevenBit/DI/IServiceInstanceProvider.hpp @@ -1,6 +1,7 @@ #pragma once #include <memory> +#include <mutex> #include <optional> #include <string_view> @@ -29,6 +30,13 @@ namespace sb::di */ [[nodiscard]] virtual Ptr createScope() const = 0; + /** + * @brief Get sync mutex + * @details Mutex can be used to synchronize service accesses between threads, can be null if synchronization is + * not needed + */ + virtual std::recursive_mutex *tryGetSyncMutex() = 0; + /** * @brief Returns service instance reference, might throw exception * @details If service was not registered or was registered as transient, method throws exception diff --git a/Include/SevenBit/DI/ServiceCollection.hpp b/Include/SevenBit/DI/ServiceCollection.hpp index 502f5b8..cfe4bc4 100644 --- a/Include/SevenBit/DI/ServiceCollection.hpp +++ b/Include/SevenBit/DI/ServiceCollection.hpp @@ -476,8 +476,8 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.add<TestClass>(ServiceLifeTimes::Scoped, "key"); - * ServiceCollection{}.add<BaseClass, ImplementationClass>(ServiceLifeTimes::Transient, "key"); + * ServiceCollection{}.addKeyed<TestClass>(ServiceLifeTimes::Scoped, "key"); + * ServiceCollection{}.addKeyed<BaseClass, ImplementationClass>(ServiceLifeTimes::Transient, "key"); * @endcode */ template <class TService, class TImplementation = TService> @@ -515,8 +515,8 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.addSingleton<TestClass>("key"); - * ServiceCollection{}.addSingleton<BaseClass, ImplementationClass>("key"); + * ServiceCollection{}.addKeyedSingleton<TestClass>("key"); + * ServiceCollection{}.addKeyedSingleton<BaseClass, ImplementationClass>("key"); * @endcode */ template <class TService, class TImplementation = TService> @@ -554,8 +554,8 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.addScoped<TestClass>("key"); - * ServiceCollection{}.addScoped<BaseClass, ImplementationClass>("key"); + * ServiceCollection{}.addKeyedScoped<TestClass>("key"); + * ServiceCollection{}.addKeyedScoped<BaseClass, ImplementationClass>("key"); * @endcode */ template <class TService, class TImplementation = TService> @@ -593,8 +593,8 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.addTransient<TestClass>("key"); - * ServiceCollection{}.addTransient<BaseClass, ImplementationClass>("key"); + * ServiceCollection{}.addKeyedTransient<TestClass>("key"); + * ServiceCollection{}.addKeyedTransient<BaseClass, ImplementationClass>("key"); * @endcode */ template <class TService, class TImplementation = TService> @@ -629,7 +629,7 @@ namespace sb::di * Example: * @code{.cpp} * TestClass test; - * ServiceCollection{}.addSingleton("key", &test); + * ServiceCollection{}.addKeyedSingleton("key", &test); * @endcode */ template <class TService> ServiceCollection &addKeyedSingleton(std::string serviceKey, TService *service) @@ -667,7 +667,7 @@ namespace sb::di * Example: * @code{.cpp} * ImplementationClass implementation; - * ServiceCollection{}.addSingleton<BaseClass>("key", &implementation); + * ServiceCollection{}.addKeyedSingleton<BaseClass>("key", &implementation); * @endcode */ template <class TService, class TImplementation> @@ -710,7 +710,7 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.add<BaseClass>(ServiceLifeTimes::Scoped, "key", + * ServiceCollection{}.addKeyed<BaseClass>(ServiceLifeTimes::Scoped, "key", * []() { return std::make_unique<ImplementationClass>(); }); * @endcode */ @@ -750,7 +750,8 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.addSingleton<BaseClass>("key", []() { return std::make_unique<ImplementationClass>(); }); + * ServiceCollection{}.addKeyedSingleton<BaseClass>("key", []() { return + * std::make_unique<ImplementationClass>(); }); * @endcode */ template <class TService, class FactoryFcn> @@ -789,7 +790,8 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.addScoped<BaseClass>("key", []() { return std::make_unique<ImplementationClass>(); }); + * ServiceCollection{}.addKeyedScoped<BaseClass>("key", []() { return std::make_unique<ImplementationClass>(); + * }); * @endcode */ template <class TService, class FactoryFcn> @@ -828,7 +830,8 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.addTransient<BaseClass>("key", []() { return std::make_unique<ImplementationClass>(); }); + * ServiceCollection{}.addKeyedTransient<BaseClass>("key", []() { return + * std::make_unique<ImplementationClass>(); }); * @endcode */ template <class TService, class FactoryFcn> @@ -867,7 +870,8 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.add(ServiceLifeTimes::Transient, "key", []() { return std::make_unique<TestClass>(); }); + * ServiceCollection{}.addKeyed(ServiceLifeTimes::Transient, "key", []() { return std::make_unique<TestClass>(); + * }); * @endcode */ template <class FactoryFcn> @@ -904,7 +908,7 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.addSingleton("key", []() { return std::make_unique<TestClass>(); }); + * ServiceCollection{}.addKeyedSingleton("key", []() { return std::make_unique<TestClass>(); }); * @endcode */ template <class FactoryFcn> ServiceCollection &addKeyedSingleton(std::string serviceKey, FactoryFcn factory) @@ -940,7 +944,7 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.addScoped("key", []() { return std::make_unique<TestClass>(); }); + * ServiceCollection{}.addKeyedScoped("key", []() { return std::make_unique<TestClass>(); }); * @endcode */ template <class FactoryFcn> ServiceCollection &addKeyedScoped(std::string serviceKey, FactoryFcn factory) @@ -976,7 +980,7 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.addTransient("key", []() { return std::make_unique<TestClass>(); }); + * ServiceCollection{}.addKeyedTransient("key", []() { return std::make_unique<TestClass>(); }); * @endcode */ template <class FactoryFcn> ServiceCollection &addKeyedTransient(std::string serviceKey, FactoryFcn factory) @@ -1026,7 +1030,7 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.addAlias<AliasClass, ServiceClass>("aliasKey", "key"); + * ServiceCollection{}.addKeyedAlias<AliasClass, ServiceClass>("aliasKey", "key"); * @endcode */ template <class TAlias, class TService> @@ -1045,7 +1049,7 @@ namespace sb::di * * Example: * @code{.cpp} - * ServiceCollection{}.addAlias<AliasClass, ServiceClass>("aliasKey"); + * ServiceCollection{}.addKeyedAlias<AliasClass, ServiceClass>("aliasKey"); * @endcode */ template <class TAlias, class TService> ServiceCollection &addKeyedAlias(std::string serviceAliasKey) diff --git a/Include/SevenBit/DI/ServiceProvider.hpp b/Include/SevenBit/DI/ServiceProvider.hpp index 83c8345..8932be4 100644 --- a/Include/SevenBit/DI/ServiceProvider.hpp +++ b/Include/SevenBit/DI/ServiceProvider.hpp @@ -1,6 +1,7 @@ #pragma once #include <memory> +#include <mutex> #include <type_traits> #include <vector> @@ -15,37 +16,37 @@ namespace sb::di class ServiceProvider { IServiceInstanceProvider::Ptr _instanceProvider; + std::recursive_mutex *_syncMutex = nullptr; public: using Ptr = std::unique_ptr<ServiceProvider>; /** * @brief Constructs service provider with specified instance provider + * @details If service instance provider is nullptr, constructor throws exception + * @throws sb::di::NullPointerException */ explicit ServiceProvider(IServiceInstanceProvider::Ptr instanceProvider) : _instanceProvider(std::move(instanceProvider)) { details::Require::notNull(_instanceProvider); - getInstanceProvider().init(*this); + _instanceProvider->init(*this); + _syncMutex = _instanceProvider->tryGetSyncMutex(); } - ServiceProvider(const ServiceProvider &parent) = delete; + ServiceProvider(const ServiceProvider &) = delete; ServiceProvider(ServiceProvider &&) = delete; - ServiceProvider &operator=(const ServiceProvider &parent) = delete; - ServiceProvider &operator=(ServiceProvider &&parent) = delete; + ServiceProvider &operator=(const ServiceProvider &) = delete; + ServiceProvider &operator=(ServiceProvider &&) = delete; /** * @brief Returns inner service instance provider - * @details If service instance provider is nullptr, method throws exception - * @throws sb::di::NullPointerException */ [[nodiscard]] const IServiceInstanceProvider &getInstanceProvider() const { return *_instanceProvider; } /** * @brief Returns inner service instance provider - * @details If service instance provider is nullptr, method throws exception - * @throws sb::di::NullPointerException */ IServiceInstanceProvider &getInstanceProvider() { return *_instanceProvider; } @@ -96,12 +97,14 @@ namespace sb::di */ template <class TService> TService *tryGetService() { - if (const auto instancePtr = getInstanceProvider().tryGetInstance(typeid(TService)); - instancePtr && *instancePtr) - { - return instancePtr->getAs<TService>(); - } - return nullptr; + return safeAction([&]() -> TService * { + if (const auto instancePtr = getInstanceProvider().tryGetInstance(typeid(TService)); + instancePtr && *instancePtr) + { + return instancePtr->getAs<TService>(); + } + return nullptr; + }); } /** @@ -111,19 +114,21 @@ namespace sb::di * * Example: * @code{.cpp} - * auto provider = ServiceCollection{}.addScoped<TestClass>().buildServiceProvider(); + * auto provider = ServiceCollection{}.addKeyedScoped<TestClass>("key").buildServiceProvider(); * - * TestClass* service = provider.tryGetService<TestClass>(); + * TestClass* service = provider.tryGetKeyedService<TestClass>("key"); * @endcode */ template <class TService> TService *tryGetKeyedService(const std::string_view serviceKey) { - if (const auto instancePtr = getInstanceProvider().tryGetKeyedInstance(typeid(TService), serviceKey); - instancePtr && *instancePtr) - { - return instancePtr->getAs<TService>(); - } - return nullptr; + return safeAction([&]() -> TService * { + if (const auto instancePtr = getInstanceProvider().tryGetKeyedInstance(typeid(TService), serviceKey); + instancePtr && *instancePtr) + { + return instancePtr->getAs<TService>(); + } + return nullptr; + }); } /** @@ -140,9 +145,11 @@ namespace sb::di */ template <class TService> TService &getService() { - auto &instance = getInstanceProvider().getInstance(typeid(TService)); - details::RequireInstance::valid(instance); - return *instance.getAs<TService>(); + return *safeAction([&]() -> TService * { + auto &instance = getInstanceProvider().getInstance(typeid(TService)); + details::RequireInstance::valid(instance); + return instance.getAs<TService>(); + }); } /** @@ -153,16 +160,18 @@ namespace sb::di * * Example: * @code{.cpp} - * auto provider = ServiceCollection{}.addScoped<TestClass>().buildServiceProvider(); + * auto provider = ServiceCollection{}.addKeyedScoped<TestClass>("key").buildServiceProvider(); * - * TestClass& service = provider.getService<TestClass>(); + * TestClass& service = provider.getKeyedService<TestClass>("key"); * @endcode */ template <class TService> TService &getKeyedService(const std::string_view serviceKey) { - auto &instance = getInstanceProvider().getKeyedInstance(typeid(TService), serviceKey); - details::RequireInstance::valid(instance); - return *instance.getAs<TService>(); + return *safeAction([&]() -> TService * { + auto &instance = getInstanceProvider().getKeyedInstance(typeid(TService), serviceKey); + details::RequireInstance::valid(instance); + return instance.getAs<TService>(); + }); } /** @@ -181,14 +190,16 @@ namespace sb::di */ template <class TService> std::vector<TService *> getServices() { - if (auto instancesPtr = getInstanceProvider().tryGetInstances(typeid(TService))) - { - return instancesPtr->map([](const ServiceInstance &instance) { - details::RequireInstance::valid(instance); - return instance.getAs<TService>(); - }); - } - return {}; + return safeAction([&]() -> std::vector<TService *> { + if (auto instancesPtr = getInstanceProvider().tryGetInstances(typeid(TService))) + { + return instancesPtr->map([](const ServiceInstance &instance) { + details::RequireInstance::valid(instance); + return instance.getAs<TService>(); + }); + } + return {}; + }); } /** @@ -199,23 +210,25 @@ namespace sb::di * Example: * @code{.cpp} * auto provider = ServiceCollection{} - * .addScoped<ITestClass, TestClass1>() - * .addScoped<ITestClass, TestClass2>() + * .addKeyedScoped<ITestClass, TestClass1>("key") + * .addKeyedScoped<ITestClass, TestClass2>("key") * .buildServiceProvider(); * - * std::vector<ITestClass *> services = provider.getServices<ITestClass>(); + * std::vector<ITestClass *> services = provider.getKeyedServices<ITestClass>("key"); * @endcode */ template <class TService> std::vector<TService *> getKeyedServices(const std::string_view serviceKey) { - if (auto instancesPtr = getInstanceProvider().tryGetKeyedInstances(typeid(TService), serviceKey)) - { - return instancesPtr->map([](const ServiceInstance &instance) { - details::RequireInstance::valid(instance); - return instance.getAs<TService>(); - }); - } - return {}; + return safeAction([&]() -> std::vector<TService *> { + if (auto instancesPtr = getInstanceProvider().tryGetKeyedInstances(typeid(TService), serviceKey)) + { + return instancesPtr->map([](const ServiceInstance &instance) { + details::RequireInstance::valid(instance); + return instance.getAs<TService>(); + }); + } + return {}; + }); } /** @@ -231,11 +244,13 @@ namespace sb::di */ template <class TService> std::unique_ptr<TService> tryCreateService() { - if (auto instance = getInstanceProvider().tryCreateInstance(typeid(TService))) - { - return instance.moveOutAsUniquePtr<TService>(); - } - return nullptr; + return safeAction([&]() -> std::unique_ptr<TService> { + if (auto instance = getInstanceProvider().tryCreateInstance(typeid(TService))) + { + return instance.moveOutAsUniquePtr<TService>(); + } + return nullptr; + }); } /** @@ -245,18 +260,20 @@ namespace sb::di * * Example: * @code{.cpp} - * auto provider = ServiceCollection{}.addTransient<TestClass>().buildServiceProvider(); + * auto provider = ServiceCollection{}.addKeyedTransient<TestClass>("key").buildServiceProvider(); * - * std::unique_ptr<TestClass> service = provider.tryCreateService<TestClass>(); + * std::unique_ptr<TestClass> service = provider.tryCreateKeyedService<TestClass>("key"); * @endcode */ template <class TService> std::unique_ptr<TService> tryCreateKeyedService(const std::string_view serviceKey) { - if (auto instance = getInstanceProvider().tryCreateKeyedInstance(typeid(TService), serviceKey)) - { - return instance.moveOutAsUniquePtr<TService>(); - } - return nullptr; + return safeAction([&]() -> std::unique_ptr<TService> { + if (auto instance = getInstanceProvider().tryCreateKeyedInstance(typeid(TService), serviceKey)) + { + return instance.moveOutAsUniquePtr<TService>(); + } + return nullptr; + }); } /** @@ -273,9 +290,11 @@ namespace sb::di */ template <class TService> std::unique_ptr<TService> createService() { - auto instance = getInstanceProvider().createInstance(typeid(TService)); - details::RequireInstance::valid(instance); - return instance.moveOutAsUniquePtr<TService>(); + return safeAction([&]() -> std::unique_ptr<TService> { + auto instance = getInstanceProvider().createInstance(typeid(TService)); + details::RequireInstance::valid(instance); + return instance.moveOutAsUniquePtr<TService>(); + }); } /** @@ -286,16 +305,18 @@ namespace sb::di * * Example: * @code{.cpp} - * auto provider = ServiceCollection{}.addTransient<TestClass>().buildServiceProvider(); + * auto provider = ServiceCollection{}.addKeyedTransient<TestClass>("key").buildServiceProvider(); * - * std::unique_ptr<TestClass> service = provider.createService<TestClass>(); + * std::unique_ptr<TestClass> service = provider.createKeyedService<TestClass>("key"); * @endcode */ template <class TService> std::unique_ptr<TService> createKeyedService(const std::string_view serviceKey) { - auto instance = getInstanceProvider().createKeyedInstance(typeid(TService), serviceKey); - details::RequireInstance::valid(instance); - return instance.moveOutAsUniquePtr<TService>(); + return safeAction([&]() -> std::unique_ptr<TService> { + auto instance = getInstanceProvider().createKeyedInstance(typeid(TService), serviceKey); + details::RequireInstance::valid(instance); + return instance.moveOutAsUniquePtr<TService>(); + }); } /** @@ -312,16 +333,18 @@ namespace sb::di */ template <class TService> TService createServiceInPlace() { - auto instance = getInstanceProvider().createInstanceInPlace(typeid(TService)); - details::RequireInstance::valid(instance); - if constexpr (std::is_move_constructible_v<TService>) - { - return instance.moveOutAs<TService>(); - } - else - { - return instance.copyAs<TService>(); - } + return safeAction([&]() -> TService { + auto instance = getInstanceProvider().createInstanceInPlace(typeid(TService)); + details::RequireInstance::valid(instance); + if constexpr (std::is_move_constructible_v<TService>) + { + return instance.moveOutAs<TService>(); + } + else + { + return instance.copyAs<TService>(); + } + }); } /** @@ -332,23 +355,25 @@ namespace sb::di * * Example: * @code{.cpp} - * auto provider = ServiceCollection{}.addTransient<TestClass>().buildServiceProvider(); + * auto provider = ServiceCollection{}.addKeyedTransient<TestClass>("key").buildServiceProvider(); * - * TestClass service = provider.createServiceInPlace<TestClass>(); + * TestClass service = provider.createKeyedServiceInPlace<TestClass>("key"); * @endcode */ template <class TService> TService createKeyedServiceInPlace(const std::string_view serviceKey) { - auto instance = getInstanceProvider().createKeyedInstanceInPlace(typeid(TService), serviceKey); - details::RequireInstance::valid(instance); - if constexpr (std::is_move_constructible_v<TService>) - { - return instance.moveOutAs<TService>(); - } - else - { - return instance.copyAs<TService>(); - } + return safeAction([&]() -> TService { + auto instance = getInstanceProvider().createKeyedInstanceInPlace(typeid(TService), serviceKey); + details::RequireInstance::valid(instance); + if constexpr (std::is_move_constructible_v<TService>) + { + return instance.moveOutAs<TService>(); + } + else + { + return instance.copyAs<TService>(); + } + }); } /** @@ -367,10 +392,12 @@ namespace sb::di */ template <class TService> std::vector<std::unique_ptr<TService>> createServices() { - auto instances = getInstanceProvider().tryCreateInstances(typeid(TService)); - return instances.map([&](ServiceInstance &instance) { - details::RequireInstance::valid(instance); - return instance.moveOutAsUniquePtr<TService>(); + return safeAction([&]() -> std::vector<std::unique_ptr<TService>> { + auto instances = getInstanceProvider().tryCreateInstances(typeid(TService)); + return instances.map([&](ServiceInstance &instance) { + details::RequireInstance::valid(instance); + return instance.moveOutAsUniquePtr<TService>(); + }); }); } @@ -382,21 +409,34 @@ namespace sb::di * Example: * @code{.cpp} * auto provider = ServiceCollection{} - * .addTransient<ITestClass, TestClass1>() - * .addTransient<ITestClass, TestClass2>() + * .addKeyedTransient<ITestClass, TestClass1>("key") + * .addKeyedTransient<ITestClass, TestClass2>("key") * .buildServiceProvider(); * - * std::vector<std::unique_ptr<ITestClass>> services = provider.createServices<ITestClass>(); + * std::vector<std::unique_ptr<ITestClass>> services = provider.createKeyedServices<ITestClass>("key"); * @endcode */ template <class TService> std::vector<std::unique_ptr<TService>> createKeyedServices(const std::string_view serviceKey) { - auto instances = getInstanceProvider().tryCreateKeyedInstances(typeid(TService), serviceKey); - return instances.map([&](ServiceInstance &instance) { - details::RequireInstance::valid(instance); - return instance.moveOutAsUniquePtr<TService>(); + return safeAction([&]() -> std::vector<std::unique_ptr<TService>> { + auto instances = getInstanceProvider().tryCreateKeyedInstances(typeid(TService), serviceKey); + return instances.map([&](ServiceInstance &instance) { + details::RequireInstance::valid(instance); + return instance.moveOutAsUniquePtr<TService>(); + }); }); } + + private: + template <class TAction> auto safeAction(TAction action) + { + if (_syncMutex) + { + std::lock_guard lock{*_syncMutex}; + return action(); + } + return action(); + } }; } // namespace sb::di diff --git a/Include/SevenBit/DI/ServiceProviderOptions.hpp b/Include/SevenBit/DI/ServiceProviderOptions.hpp index c4265d2..8eea451 100644 --- a/Include/SevenBit/DI/ServiceProviderOptions.hpp +++ b/Include/SevenBit/DI/ServiceProviderOptions.hpp @@ -33,5 +33,11 @@ namespace sb::di * @details If set to true provider will search for service in singleton container first then in scoped */ bool searchInSigletonsFirst = true; + + /** + * @brief Enables thread safe mode + * @details Provider will synchronize service accesses between threads + */ + bool threadSafe = false; }; } // namespace sb::di diff --git a/README.md b/README.md index 54db97f..6aafa3b 100644 --- a/README.md +++ b/README.md @@ -17,19 +17,20 @@ </p> <h4> - <a href="https://7bitDI.readthedocs.io/en/latest/index.html">Documentation & Examples</a> + <a href="https://7bitDI.readthedocs.io">Documentation & Examples</a> </h4> </div> <br /> -## Built With +## Main Features -- [Google Test](https://github.com/google/googletest) -- [Google Benchmark](https://github.com/google/benchmark) -- [Sphinx](https://www.sphinx-doc.org/en/master/) -- [Breathe](https://breathe.readthedocs.io/en/latest/) -- [Quom](https://pypi.org/project/quom/) +- Implementation separation +- Multiple implementations +- Keyed services +- Service aliases +- Thread safe +- Strong destruction order ## Supported Platforms @@ -52,11 +53,15 @@ The library is officially supported on the following platforms: If you notice any problems/bugs, please file an issue on the repository GitHub Issue Tracker. Pull requests containing fixes are welcome! +## Documentation + +https://7bitDI.readthedocs.io + ## Installation ### There are a few ways of installation: -### 1. Using Cmake fetch content api - Recommended +### 1. Using Cmake fetch content API - Recommended Update CMakeLists.txt file with the following code @@ -65,7 +70,7 @@ include(FetchContent) FetchContent_Declare( 7bitDI GIT_REPOSITORY https://github.com/7bitcoder/7bitDI.git - GIT_TAG v3.2.0 + GIT_TAG v3.3.0 ) FetchContent_MakeAvailable(7bitDI) @@ -78,7 +83,7 @@ Download and install A [Conan](https://conan.io/), and create conanfile.txt in t ``` [requires] -7bitdi/3.2.0 +7bitdi/3.3.0 ``` change the version to newer if available, then run the command: @@ -230,6 +235,14 @@ actionA, actionB executed. ``` More examples and tutorials are available on the -[Documentation & Examples](https://7bitDI.readthedocs.io/en/latest/index.html) page +[Documentation & Examples](https://7bitDI.readthedocs.io) page + +## Built With + +- [Google Test](https://github.com/google/googletest) +- [Google Benchmark](https://github.com/google/benchmark) +- [Sphinx](https://www.sphinx-doc.org/en/master/) +- [Breathe](https://breathe.readthedocs.io/en/latest/) +- [Quom](https://pypi.org/project/quom/) @7bitcoder Sylwester Dawida 2023 diff --git a/Tests/Helpers/Mocks/ServiceInstanceProviderMock.hpp b/Tests/Helpers/Mocks/ServiceInstanceProviderMock.hpp index dc315cf..4fa46f9 100644 --- a/Tests/Helpers/Mocks/ServiceInstanceProviderMock.hpp +++ b/Tests/Helpers/Mocks/ServiceInstanceProviderMock.hpp @@ -7,6 +7,7 @@ struct ServiceInstanceProviderMock : public sb::di::IServiceInstanceProvider { MOCK_METHOD((std::unique_ptr<sb::di::IServiceInstanceProvider>), createScope, (), (const override)); + MOCK_METHOD((std::recursive_mutex *), tryGetSyncMutex, (), (override)); MOCK_METHOD((void), init, (sb::di::ServiceProvider &), (override)); MOCK_METHOD((const sb::di::ServiceInstance *), tryGetInstance, (sb::di::TypeId serviceTypeId), (override)); MOCK_METHOD((const sb::di::ServiceInstance &), getInstance, (sb::di::TypeId serviceTypeId), (override)); diff --git a/Tests/Integration/ThreadSafeTest.cpp b/Tests/Integration/ThreadSafeTest.cpp new file mode 100644 index 0000000..0211305 --- /dev/null +++ b/Tests/Integration/ThreadSafeTest.cpp @@ -0,0 +1,78 @@ +#include <gtest/gtest.h> +#include <thread> + +#include "../Helpers/Classes/Complex.hpp" +#include <SevenBit/DI/ServiceCollection.hpp> + +class ThreadSafeTest : public testing::Test +{ + protected: + static void SetUpTestSuite() {} + + ThreadSafeTest() {} + + void SetUp() override {} + + void TearDown() override {} + + ~ThreadSafeTest() override = default; + + static void TearDownTestSuite() {} +}; + +void getSafeServices(sb::di::ServiceProvider &provider) +{ + auto &service1 = provider.getService<ITestComplexClass1>(); + auto &service2 = provider.getService<ITestComplexClass2>(); + auto service3 = provider.createService<ITestComplexClass3>(); + auto &service4 = provider.getService<ITestComplexClass4>(); + auto &service5 = provider.getService<ITestComplexClass5>(); + auto &service6 = provider.getService<ITestComplexClass6>(); + + EXPECT_EQ(service1.number(), 1); + EXPECT_EQ(service2.number(), 2); + EXPECT_EQ(service3->number(), 3); + EXPECT_EQ(service4.number(), 4); + EXPECT_EQ(service5.number(), 5); + EXPECT_EQ(service6.number(), 6); + EXPECT_EQ(service2.getOne(), &service1); + EXPECT_EQ(service3->getOne(), &service1); + EXPECT_EQ(service3->getTwo(), &service2); + EXPECT_EQ(service4.getOne(), &service1); + EXPECT_EQ(service4.getTwo(), &service2); + EXPECT_NE(service4.getThree().get(), service3.get()); + EXPECT_EQ(service5.getOne(), &service1); + EXPECT_EQ(service5.getTwo(), &service2); + EXPECT_NE(service5.makeThree(), service3); + EXPECT_EQ(&service6.getOne(), &service1); + EXPECT_EQ(&service6.getTwo(), &service2); + EXPECT_FALSE(service6.getNonExisting()); + EXPECT_NE(service6.makeThree(), service3); +} + +TEST_F(ThreadSafeTest, ShouldGetSafeServices) +{ + sb::di::ServiceProviderOptions options; + options.threadSafe = true; + + auto provider = sb::di::ServiceCollection{} + .addSingleton<ITestComplexClass1, TestComplexClass1>() + .addSingleton<ITestComplexClass2, TestComplexClass2>() + .addTransient<ITestComplexClass3, TestComplexClass3>() + .addScoped<ITestComplexClass4, TestComplexClass4>() + .addScoped<ITestComplexClass5, TestComplexClass5>() + .addScoped<ITestComplexClass6, TestComplexClass6>() + .buildServiceProvider(options); + + constexpr size_t maxThreads = 50; + std::vector<std::thread> threads; + threads.reserve(maxThreads); + for (size_t i = 0; i < maxThreads; ++i) + { + threads.emplace_back(getSafeServices, std::ref(provider)); + } + for (auto &th : threads) + { + th.join(); + } +}