From 739a473f7e86544b42f22122be9bdd877b9e4893 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 19 Oct 2013 03:32:49 +0200 Subject: [PATCH] implemented the standard code path of DependencyFactory still mising: a mechanism to inject mock objects temporarily --- src/lib/depend.hpp | 27 +- src/lib/dependency-factory.cpp | 72 +++++ src/lib/dependency-factory.hpp | 310 ++++++++++++---------- tests/library/dependency-factory-test.cpp | 9 +- 4 files changed, 274 insertions(+), 144 deletions(-) diff --git a/src/lib/depend.hpp b/src/lib/depend.hpp index 26f6b015e..f9c90fa61 100644 --- a/src/lib/depend.hpp +++ b/src/lib/depend.hpp @@ -97,18 +97,30 @@ namespace lib { typedef DependencyFactory::InstanceConstructor Constructor; + + /** default configuration of the dependency factory + * is to build a singleton instance on demand */ + Depend() + { + factory.ensureInitialisation (buildSingleton()); + } + /** - * optionally, the instance creation process can be configured + * optionally, the instance creation process can be explicitly configured * \em once per type. By default, a singleton instance will be created. * Installing another factory function enables other kinds of dependency injection; * this configuration must be done prior to any use the dependency factory. + * @param ctor a constructor function, which will be invoked on first usage. + * @note basically a custom constructor function is responsible to manage any + * created service instances. Optionally it may install a deleter function + * via \c DependencyFactory::scheduleDestruction(void*,KillFun) * @remark typically the \c Depend factory will be placed into a static variable, - * embedded into some service interface type. In this case, actual storage - * for this static variable needs to be allocated within some translation unit. + * embedded into another type or interface. In this case, actual storage for + * this static variable needs to be allocated within some translation unit. * And this is the point where this ctor will be invoked, in the static * initialisation phase of the respective translation unit (*.cpp) */ - Depend (Constructor ctor = buildSingleton()) + Depend (Constructor ctor) { factory.installConstructorFunction (ctor); } @@ -151,18 +163,17 @@ namespace lib { injectReplacement (SI* mock) { REQUIRE (mock); - factory.takeOwnership (mock); // EX_SANE + factory.takeOwnership (mock); // EX_SANE SyncLock guard; - factory.shaddow (instance); // EX_FREE - instance = mock; + factory.shaddow (instance, mock); // EX_FREE } static void dropReplacement() { SyncLock guard; - factory.restore (instance); // EX_FREE + factory.restore (instance); // EX_FREE } }; diff --git a/src/lib/dependency-factory.cpp b/src/lib/dependency-factory.cpp index 9c36fb3a6..06581137b 100644 --- a/src/lib/dependency-factory.cpp +++ b/src/lib/dependency-factory.cpp @@ -28,12 +28,71 @@ #include "lib/dependency-factory.hpp" +#include "lib/del-stash.hpp" namespace lib { + namespace error = lumiera::error; namespace { // private implementation details... + + class AutoDestructor + { + DelStash destructionExecutor_; + static bool shutdownLock; + + + static AutoDestructor& + instance() + { + static AutoDestructor _instance_; + return _instance_; + } + + ~AutoDestructor() + { + shutdownLock = true; + } + + static void + __lifecycleCheck() + { + if (shutdownLock) + throw error::Fatal("Attempt to re-access a service, " + "while Application is already in shutdown" + ,error::LUMIERA_ERROR_LIFECYCLE); + } + + public: + static void + schedule (void* object, DependencyFactory::KillFun customDeleter) + { + __lifecycleCheck(); + instance().destructionExecutor_.manage (object, customDeleter); + } + + static void + kill (void* object) + { + __lifecycleCheck(); + instance().destructionExecutor_.kill (object); + } + }; + + bool AutoDestructor::shutdownLock = false; + + + +// struct TemporarySwitch +// { +// void* originalInstance; +// InstanceConstructor originalCtor; +// }; +// +// static TemporarySwitch temporarySwitch; + + } @@ -41,5 +100,18 @@ namespace lib { /** */ + /** */ + void + DependencyFactory::deconfigure (void* existingInstance) + { + AutoDestructor::kill (existingInstance); + } + + void + DependencyFactory::scheduleDestruction (void* object, KillFun customDeleter) + { + AutoDestructor::schedule (object, customDeleter); + } + }// lib diff --git a/src/lib/dependency-factory.hpp b/src/lib/dependency-factory.hpp index 74df44739..e59d7be13 100644 --- a/src/lib/dependency-factory.hpp +++ b/src/lib/dependency-factory.hpp @@ -32,122 +32,62 @@ #include "lib/error.hpp" //#include "lib/sync-classlock.hpp" -#include "lib/del-stash.hpp" ////////TODO namespace lib { namespace error = lumiera::error; - class AutoDestructor - { - typedef void KillFun(void*); - - DelStash destructionExecutor_; - static bool shutdownLock; - - static AutoDestructor& - instance() - { - static AutoDestructor _instance_; - return _instance_; - } - - ~AutoDestructor() - { - shutdownLock = true; - } - - static void - __lifecycleCheck() - { - if (shutdownLock) - throw error::Fatal("Attempt to re-access a service, " - "while Application is already in shutdown" - ,error::LUMIERA_ERROR_LIFECYCLE); - } - - public: - static void - schedule (void* object, KillFun* customDeleter) - { - __lifecycleCheck(); - instance().destructionExecutor_.manage (object, customDeleter); - } - - static void - kill (void* object) - { - __lifecycleCheck(); - instance().destructionExecutor_.kill (object); - } - }; - bool AutoDestructor::shutdownLock = false; - - template - struct InstanceHolder - : boost::noncopyable - { - - TAR* - buildInstance () - { -#if NOBUG_MODE_ALPHA - static uint callCount = 0; - ASSERT ( 0 == callCount++ ); -#endif - - // place new instance into embedded buffer - TAR* newInstance = new(buff_) TAR; - - try - { - AutoDestructor::schedule (newInstance, &destroy_in_place); - return newInstance; - } - - catch (std::exception& problem) - { - _kill_immediately (newInstance); - throw error::State (problem, "Failed to install a deleter function " - "for clean-up at application shutdown. "); - } - catch (...) - { - _kill_immediately (newInstance); - throw error::State ("Unknown error while installing a deleter function."); - } - } - - private: - /** storage for the service instance */ - char buff_[sizeof(TAR)]; - - - static void - destroy_in_place (void* pInstance) - { - if (!pInstance) return; - static_cast (pInstance) -> ~TAR(); - } - - - static void - _kill_immediately (void* allocatedObject) - { - destroy_in_place (allocatedObject); - const char* errID = lumiera_error(); - WARN (memory, "Failure in DependencyFactory. Error flag was: %s", errID); - } - }; - - - /** + /** * Factory to generate and manage service objects classified by type. */ class DependencyFactory { public: + typedef void* (*InstanceConstructor)(void); + typedef void (*KillFun) (void*); + + /** ensure initialisation by installing a default constructor function, + * but don't change an explicitly installed different constructor function. + * @remark deliberately this DependencyFactory has no constructor to + * initialise the object field \c ctorFunction_ to zero. + * The reason is, in the intended usage scenario, the + * DependencyFactory lives within a static variable, + * which might be constructed in no defined order + * in relation to the Depend instance. + */ + void + ensureInitialisation (InstanceConstructor defaultCtor) + { + if (!ctorFunction_) + this->ctorFunction_ = defaultCtor; + ENSURE (ctorFunction_); + } + + + /** explicitly set up constructor function, unless already configured + * In the default configuration, the template \c Depend installs a + * builder function to create a singleton instance in static memory. + * But specific instances might install e.g. a factory to create a + * implementation defined subclass; this might also be the place + * to hook in some kind of centralised service manager in future. + * @param ctor a function to be invoked to create a new service instance + * @throw error::Fatal when attempting to change an existing configuration. + */ + void + installConstructorFunction (InstanceConstructor ctor) + { + if (ctorFunction_ && ctor != ctorFunction_) + throw error::Fatal ("DependencyFactory: attempt to change the instance builder function " + "after-the-fact. Before this call, a different function was installed " + "and possibly also used already. Hint: visit all code locations, which " + "actually create an instance of the Depend template." + ,error::LUMIERA_ERROR_LIFECYCLE); + this->ctorFunction_ = ctor; + } + + + /** invoke the installed ctor function */ void* buildInstance() @@ -156,41 +96,96 @@ namespace lib { return ctorFunction_(); } - void - deconfigure (void* existingInstance) - { - AutoDestructor::kill (existingInstance); - } + void deconfigure (void* existingInstance); + template - void - takeOwnership (TAR* newInstance) - { - UNIMPLEMENTED("enrol existing instance and prepare deleter function"); - } + void takeOwnership (TAR*); + template - void - restore (TAR* volatile & activeInstance) - { - UNIMPLEMENTED("disable and destroy temporary shadowing instance and restore the dormant original instance"); - } + void shaddow (TAR* volatile & activeInstance, TAR* replacement); template - void - shaddow (TAR* volatile & activeInstance) + void restore (TAR* volatile & activeInstance); + + + + + /** hook to install a deleter function to clean up a service object. + * The standard constructor function uses this hook to schedule the + * destructor invocation on application shutdown; custom constructors + * are free to use this mechanism (or care for clean-up otherwise) + * @see lib::DelStash + */ + static void scheduleDestruction (void*, KillFun); + + + + private: + /** pointer to the concrete function + * used for building new service instances */ + InstanceConstructor ctorFunction_; + + + + template + class InstanceHolder + : boost::noncopyable { - UNIMPLEMENTED("set up a temporary replacement, allowing to restore the original later"); - } + /** storage for the service instance */ + char buff_[sizeof(TAR)]; + + + /** deleter function to invoke the destructor + * of the embedded service object instance. + * A pointer to this deleter function will be + * enrolled for execution at application shutdown + */ + static void + destroy_in_place (void* pInstance) + { + if (!pInstance) return; + static_cast (pInstance) -> ~TAR(); + } + + static void + _kill_immediately (void* allocatedObject) + { + destroy_in_place (allocatedObject); + const char* errID = lumiera_error(); + WARN (memory, "Failure in DependencyFactory. Error flag was: %s", errID); + } + + + public: + TAR* + buildInstance () + { + // place new instance into embedded buffer + TAR* newInstance = new(buff_) TAR; + + try + { + scheduleDestruction (newInstance, &destroy_in_place); + return newInstance; + } + + catch (std::exception& problem) + { + _kill_immediately (newInstance); + throw error::State (problem, "Failed to install a deleter function " + "for clean-up at application shutdown."); + } + catch (...) + { + _kill_immediately (newInstance); + throw error::State ("Unknown error while installing a deleter function."); + } + } + }; - typedef void* (*InstanceConstructor)(void); - - void - installConstructorFunction (InstanceConstructor ctor) - { - UNIMPLEMENTED("set up constructor function, unless already configured"); - } template static void* @@ -208,10 +203,59 @@ namespace lib { return & createSingletonInstance; } - private: - InstanceConstructor ctorFunction_; }; + namespace { + /** helper: destroy heap allocated object. + * This deleter function is used to clean-up + * a heap allocated mock object, which was installed + * as a temporary replacement for some service, + * typically during an unit test + */ + template + inline void + releaseOnHeap (void* o) + { + if (!o) return; + X* instance = static_cast (o); + delete instance; + } + } + + + template + void + DependencyFactory::takeOwnership (TAR* newInstance) + { + scheduleDestruction (newInstance, &releaseOnHeap); + } + + /** + * set up a temporary replacement, allowing to restore the original later + * @param activeInstance + * @param replacement + */ + template + void + DependencyFactory::shaddow (TAR* volatile & activeInstance, TAR* replacement) + { + ctorFunction_ = 0; /////TODO how to implement a functor without explicitly allocated storage?? + activeInstance = replacement; + } + + /** + * disable and destroy temporary shadowing instance and restore the dormant original instance + * @param activeInstance + */ + template + void + DependencyFactory::restore (TAR* volatile & activeInstance) + { + deconfigure (activeInstance); + activeInstance = NULL; + } + + } // namespace lib #endif diff --git a/tests/library/dependency-factory-test.cpp b/tests/library/dependency-factory-test.cpp index 151e29e5f..d2e8ad2f7 100644 --- a/tests/library/dependency-factory-test.cpp +++ b/tests/library/dependency-factory-test.cpp @@ -183,9 +183,12 @@ namespace test{ static void* customFactoryFunction (void) { - SubSubSub* newObject = static_cast (DependencyFactory::createSingletonInstance()); - newObject->instanceID_ = MAX_ID + 10; - return newObject; + static SubSubSub specialInstance; + // NOTE: the factory function is responsible + // for managing the instance's lifecycle + + specialInstance.instanceID_ = MAX_ID + 10; + return &specialInstance; }