/* DEPEND.hpp - access point to singletons and dependencies Copyright (C) Lumiera.org 2013, Hermann Vosseler 2018, Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @file depend.hpp ** Singleton services and Dependency Injection. ** The Singleton Pattern provides a single access point to a class or ** service and exploits this ubiquitous access point to limit the number of objects ** of this type to a single shared instance. Within Lumiera, we mostly employ a ** factory template for this purpose; the intention is to use on-demand initialisation ** and a standardised lifecycle. In the default configuration, this `Depend` factory ** maintains a singleton instance of type `TY`. The possibility to install other factory ** functions allows for subclass creation and various other kinds of service management. ** ** ** # Why Singletons? Inversion-of-Control and Dependency Injection ** ** Singletons are frequently over-used, and often they serve as disguised ** global variables to support a procedural programming style. As a remedy, typically ** the use of a »Dependency Injection Container« is promoted. And -- again typically -- ** these DI containers tend to evolve into heavyweight universal tools and substitute ** the original problem by metadata hell. ** ** Thus, for Lumiera, the choice to use Singletons was deliberate: we understand the ** Inversion-of-Control principle, yet we want to stay just below the level of building ** a central application manager core. At the usage site, we access a factory for some ** service *by name*, where the »name« is actually the type name of an interface or ** facade. Singleton is used as an _implementation_ of this factory, when the service ** is self-contained and can be brought up lazily. ** ** ## Conventions, Lifecycle and Unit Testing ** ** Usually we place an instance of the singleton factory (or some other kind of factory) ** as a static variable within the interface class describing the service or facade. ** As a rule, everything accessible as Singleton is sufficiently self-contained to come ** up any time -- even prior to `main()`. But at shutdown, any deregistration must be done ** explicitly using a lifecycle hook. In Lumiera, destructors aren't allowed to do ** _any significant work_ beyond releasing references, and we acknowledge that ** singletons can be released in _arbitrary order_. ** ** Lifecycle and management of dependencies is beyond the scope of this access mechanism ** exposed here. However, the actual product to be created or exposed lazily can be ** configured behind the scenes, as long as this configuration is performed _prior_ ** to the first access. This configuration is achieved with the help of the "sibling" ** template lib::DependInject, which is declared friend within `Depend` for type `T` ** - a service with distinct lifecycle can be exposed through the `Depend` front-end ** - it is possible to create a mock instance, which temporarily shadows what ** Depend delivers on access. ** ** ## Implementation and performance ** ** Due to this option for flexible configuration, the implementation can not be built ** as Meyer's Singleton. Rather, Double Checked Locking of a Mutex is combined with ** an std::atomic to work around the known (rather theoretical) concurrency problems. ** Microbenchmarks indicate that this implementation technique ranges close to the ** speed of a direct access to an already existing object; in the fully optimised ** variant it was found to be roughly at ≈ 1ns and thus about 3 to 4 times slower ** than the comparable unprotected direct access without lazy initialisation. ** This is orders of magnitude better than any flavour of conventional locking. ** ** ### Initialisation of the shared factory ** ** We package the creation and destruction functors for the object to be managed ** into a factory, which is shared per type. Such a shared factory could live within ** a static member field `Depend::factory` -- however, the _definition_ of such ** a templated static member happens on first use of the enclosing template _instance_, ** and it seems the _initialisation order_ of such fields is not guaranteed, especially ** when used prior to main, from static initialisation code. For that reason, we manage ** the _factory_ as Meyer's singleton, so it can be accessed independently from the ** actual target object's lifecycle and the compiler will ensure initialisation ** prior to first use. To ensure the lifespan of this embedded factory object ** extends beyond the last instance of `lib::Depend`, we also need to ** access that factory from a ctor ** ** @see depend-inject.hpp ** @see lib::DependInject ** @see Singleton_test ** @see DependencyConfiguration_test */ #ifndef LIB_DEPEND_H #define LIB_DEPEND_H #include "lib/error.hpp" #include "lib/nocopy.hpp" #include "lib/nobug-init.hpp" #include "lib/sync-classlock.hpp" #include "lib/zombie-check.hpp" #include "lib/meta/util.hpp" #include #include #include #include namespace lib { namespace error = lumiera::error; /** * Helper to abstract creation and lifecycle of a dependency. * Holds a configurable constructor function and optionally * an automatically invoked deleter function. * @note DependencyFactory can be declared friend to indicate * the expected way to invoke an otherwise private ctor. * This is a classical idiom for singletons. * @see lib::Depend * @see lib::DependInject */ template class DependencyFactory : util::NonCopyable { using Creator = std::function; using Deleter = std::function; Creator creator_; Deleter deleter_; public: ZombieCheck zombieCheck{util::typeStr()}; DependencyFactory() = default; ~DependencyFactory() { if (deleter_) deleter_(); } OBJ* buildTarget() { return creator_? creator_() : buildAndManage(); } template void defineCreator (FUN&& ctor) { creator_ = std::forward (ctor); } template void defineCreatorAndManage (FUN&& ctor) { creator_ = [this,ctor] { OBJ* obj = ctor(); atDestruction ([obj]{ delete obj; }); return obj; }; } void disable() { creator_ = []() -> OBJ* { throw error::Fatal("Service not available at this point of the Application Lifecycle" ,LERR_(LIFECYCLE)); }; } template void atDestruction (FUN&& additionalAction) { if (deleter_) { Deleter oldDeleter{std::move (deleter_)}; deleter_ = [oldDeleter, additionalAction] { oldDeleter(); additionalAction(); }; } else deleter_ = std::forward (additionalAction); } void transferDefinition (DependencyFactory&& source) { creator_ = std::move (source.creator_); deleter_ = std::move (source.deleter_); source.creator_ = Creator(); source.deleter_ = Deleter(); // clear possible leftover deleter } private: OBJ* buildAndManage() { OBJ* obj = buildInstance(); atDestruction ([obj]{ delete obj; }); return obj; } // try to instantiate the default ctor template static std::true_type __try_instantiate(int); template static std::false_type __try_instantiate(...); /** metafunction: can we instantiate the desired object here? * @remark need to perform the check right here in this scope, because * default ctor can be private with DependencyFactory declared friend */ template struct canDefaultConstruct : decltype(__try_instantiate(0)) { }; template static meta::enable_if, TAR* > buildInstance() { return new TAR; } template static meta::enable_if, ABS* > buildInstance() { throw error::Fatal("Attempt to create a singleton instance of an abstract class. " "Application architecture or lifecycle is seriously broken."); } template static meta::disable_if,canDefaultConstruct>, ABS* > buildInstance() { throw error::Fatal("Desired singleton class is not default constructible. " "Application architecture or lifecycle is seriously broken."); } }; /** * @internal access point to reconfigure dependency injection on a per type base * @see depend-inject.hpp */ template class DependInject; /** * Access point to singletons and other kinds of dependencies designated *by type*. * Actually this is a Factory object, which is typically placed into a static field * of the Singleton (target) class or some otherwise suitable interface. * @tparam SRV the class of the Service or Singleton instance * @note uses static fields internally, so all factory configuration is shared per type * @remarks * - threadsafe lazy instantiation implemented by Double Checked Locking with std::atomic. * - by default, without any explicit configuration, this template creates a singleton. * - a per-type factory function can be configured with the help of lib::DependInject * - singletons will be destroyed when the embedded static InstanceHolder is destroyed. */ template class Depend { using Instance = std::atomic; using Factory = DependencyFactory; using Lock = ClassLock; /** shared per type */ static Instance instance; static Factory& factory() ///< Meyer's Singleton to ensure initialisation on first use { static Factory sharedFactory; return sharedFactory; } friend class DependInject; public: /** * Interface to be used by clients for retrieving the service instance. * Manages the instance creation, lifecycle and access in multithreaded context. * @return instance of class `SRV`. When used in default configuration, * the returned service instance is a singleton. */ SRV& operator() () { SRV* object = instance.load (std::memory_order_acquire); if (!object) { factory().zombieCheck(); Lock guard; object = instance.load (std::memory_order_relaxed); if (!object) { object = factory().buildTarget(); factory().disable(); factory().atDestruction([]{ instance = nullptr; }); } instance.store (object, std::memory_order_release); } ENSURE (object); return *object; } /** * allow to "peek" if a dependency is already available and exposed. * @remark relevant when C code relies on a service with lifecycle. */ explicit operator bool() const { return instance.load (std::memory_order_acquire); } /** @remark this ctor ensures the factory is created prior to first use, * and stays alive during the whole lifespan of any `Depend` */ Depend() { factory().zombieCheck(); } }; /* === allocate Storage for static per type instance management === */ template std::atomic Depend::instance; } // namespace lib #endif /*LIB_DEPEND_H*/