this is a (hopefully just temporary) workaround to deal with static initialisation ordering problems. The original solution was cleaner from a code readability viewpoint, however, when lib::Depend was used from static initialisation code, it could be observed that the factory constructor was invoked after first use. And while this did not interfer with the instance lifecycle management itself, because the zero-initialisation of the instance (atomic) pointer did happen beforehand, it would discard any special factory functions installed from such a context (and this counts as bug for my taste).
367 lines
13 KiB
C++
367 lines
13 KiB
C++
/*
|
|
DEPEND.hpp - access point to singletons and dependencies
|
|
|
|
Copyright (C) Lumiera.org
|
|
2013, Hermann Vosseler <Ichthyostega@web.de>
|
|
2018, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
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 <b>Singleton Pattern</b> 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<TY>` 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<T>` for type `T`
|
|
** - a service with distinct lifecycle can be exposed through the `Depend<T>` front-end
|
|
** - it is possible to create a mock instance, which temporarily shadows what
|
|
** Depend<T> 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<TY>::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<TY>`, 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 <type_traits>
|
|
#include <functional>
|
|
#include <atomic>
|
|
#include <memory>
|
|
|
|
|
|
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 OBJ>
|
|
class DependencyFactory
|
|
: util::NonCopyable
|
|
{
|
|
using Creator = std::function<OBJ*()>;
|
|
using Deleter = std::function<void()>;
|
|
|
|
Creator creator_;
|
|
Deleter deleter_;
|
|
|
|
public:
|
|
ZombieCheck zombieCheck;
|
|
|
|
DependencyFactory() = default;
|
|
~DependencyFactory()
|
|
{
|
|
if (deleter_)
|
|
deleter_();
|
|
}
|
|
|
|
OBJ*
|
|
buildTarget()
|
|
{
|
|
return creator_? creator_()
|
|
: buildAndManage();
|
|
}
|
|
|
|
template<typename FUN>
|
|
void
|
|
defineCreator (FUN&& ctor)
|
|
{
|
|
creator_ = std::forward<FUN> (ctor);
|
|
}
|
|
|
|
template<typename FUN>
|
|
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"
|
|
,error::LERR_(LIFECYCLE));
|
|
};
|
|
}
|
|
|
|
template<typename FUN>
|
|
void
|
|
atDestruction (FUN&& additionalAction)
|
|
{
|
|
if (deleter_)
|
|
{
|
|
Deleter oldDeleter{std::move (deleter_)};
|
|
deleter_ = [oldDeleter, additionalAction]
|
|
{
|
|
oldDeleter();
|
|
additionalAction();
|
|
};
|
|
}
|
|
else
|
|
deleter_ = std::forward<FUN> (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<OBJ>();
|
|
atDestruction ([obj]{ delete obj; });
|
|
return obj;
|
|
}
|
|
|
|
// try to instantiate the default ctor
|
|
template<class X, typename = decltype(X())>
|
|
static std::true_type __try_instantiate(int);
|
|
template<class>
|
|
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<typename X>
|
|
struct canDefaultConstruct
|
|
: decltype(__try_instantiate<X>(0))
|
|
{ };
|
|
|
|
|
|
template<class TAR>
|
|
static meta::enable_if<canDefaultConstruct<TAR>,
|
|
TAR* >
|
|
buildInstance()
|
|
{
|
|
return new TAR;
|
|
}
|
|
|
|
template<class ABS>
|
|
static meta::enable_if<std::is_abstract<ABS>,
|
|
ABS* >
|
|
buildInstance()
|
|
{
|
|
throw error::Fatal("Attempt to create a singleton instance of an abstract class. "
|
|
"Application architecture or lifecycle is seriously broken.");
|
|
}
|
|
template<class ABS>
|
|
static meta::disable_if<std::__or_<std::is_abstract<ABS>,canDefaultConstruct<ABS>>,
|
|
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 SRV>
|
|
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<SRV>
|
|
* - singletons will be destroyed when the embedded static InstanceHolder is destroyed.
|
|
*/
|
|
template<class SRV>
|
|
class Depend
|
|
{
|
|
using Instance = std::atomic<SRV*>;
|
|
using Factory = DependencyFactory<SRV>;
|
|
using Lock = ClassLock<SRV, NonrecursiveLock_NoWait>;
|
|
|
|
/** 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<SRV>;
|
|
|
|
|
|
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<TY>`
|
|
*/
|
|
Depend()
|
|
{
|
|
factory().zombieCheck();
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
/* === allocate Storage for static per type instance management === */
|
|
template<class SRV>
|
|
std::atomic<SRV*> Depend<SRV>::instance;
|
|
|
|
|
|
|
|
} // namespace lib
|
|
#endif /*LIB_DEPEND_H*/
|