diff options
author | Andrey Lihatskiy <alihatskiy@productengine.com> | 2020-02-03 15:18:34 +0200 |
---|---|---|
committer | Andrey Lihatskiy <alihatskiy@productengine.com> | 2020-02-03 15:18:34 +0200 |
commit | e0727702ea00c975361cf94b4d2903a9fec6f6f8 (patch) | |
tree | d64f1cf4ebf58a08fb7fc197206ce31fab2aa115 /indra/llcommon/llsingleton.h | |
parent | c5195905b8c9b30a2b3aab652aa9828e9825c42f (diff) | |
parent | e58b5263598368b6dec4a836c72c69b50d701911 (diff) |
Merge branch 'DRTVWR-501' into trunk
Diffstat (limited to 'indra/llcommon/llsingleton.h')
-rw-r--r-- | indra/llcommon/llsingleton.h | 409 |
1 files changed, 340 insertions, 69 deletions
diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 859e271e26..0da6d548ab 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -31,6 +31,18 @@ #include <vector> #include <typeinfo> +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable:4265) +#endif +// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual + +#include <mutex> + +#if LL_WINDOWS +#pragma warning (pop) +#endif + class LLSingletonBase: private boost::noncopyable { public: @@ -43,7 +55,6 @@ private: // This, on the other hand, is a stack whose top indicates the LLSingleton // currently being initialized. static list_t& get_initializing(); - static list_t& get_initializing_from(MasterList*); // Produce a vector<LLSingletonBase*> of master list, in dependency order. typedef std::vector<LLSingletonBase*> vec_t; static vec_t dep_sort(); @@ -57,10 +68,11 @@ protected: typedef enum e_init_state { UNINITIALIZED = 0, // must be default-initialized state - CONSTRUCTING, - INITIALIZING, - INITIALIZED, - DELETED + CONSTRUCTING, // within DERIVED_TYPE constructor + CONSTRUCTED, // finished DERIVED_TYPE constructor + INITIALIZING, // within DERIVED_TYPE::initSingleton() + INITIALIZED, // normal case + DELETED // deleteSingleton() or deleteAll() called } EInitState; // Define tag<T> to pass to our template constructor. You can't explicitly @@ -100,6 +112,9 @@ protected: // That being the case, we control exactly when it happens -- and we can // pop the stack immediately thereafter. void pop_initializing(); + // Remove 'this' from the init stack in case of exception in the + // LLSingleton subclass constructor. + static void reset_initializing(list_t::size_type size); private: // logging static void log_initializing(const char* verb, const char* name); @@ -115,6 +130,10 @@ protected: static void logwarns(const char* p1, const char* p2="", const char* p3="", const char* p4=""); static std::string demangle(const char* mangled); + template <typename T> + static std::string classname() { return demangle(typeid(T).name()); } + template <typename T> + static std::string classname(T* ptr) { return demangle(typeid(*ptr).name()); } // Default methods in case subclass doesn't declare them. virtual void initSingleton() {} @@ -178,7 +197,15 @@ struct LLSingleton_manage_master void remove(LLSingletonBase* sb) { sb->remove_master(); } void push_initializing(LLSingletonBase* sb) { sb->push_initializing(typeid(T).name()); } void pop_initializing (LLSingletonBase* sb) { sb->pop_initializing(); } - LLSingletonBase::list_t& get_initializing(T*) { return LLSingletonBase::get_initializing(); } + // used for init stack cleanup in case an LLSingleton subclass constructor + // throws an exception + void reset_initializing(LLSingletonBase::list_t::size_type size) + { + LLSingletonBase::reset_initializing(size); + } + // For any LLSingleton subclass except the MasterList, obtain the init + // stack from the MasterList singleton instance. + LLSingletonBase::list_t& get_initializing() { return LLSingletonBase::get_initializing(); } }; // But for the specific case of LLSingletonBase::MasterList, don't. @@ -189,9 +216,14 @@ struct LLSingleton_manage_master<LLSingletonBase::MasterList> void remove(LLSingletonBase*) {} void push_initializing(LLSingletonBase*) {} void pop_initializing (LLSingletonBase*) {} - LLSingletonBase::list_t& get_initializing(LLSingletonBase::MasterList* instance) + // since we never pushed, no need to clean up + void reset_initializing(LLSingletonBase::list_t::size_type size) {} + LLSingletonBase::list_t& get_initializing() { - return LLSingletonBase::get_initializing_from(instance); + // The MasterList shouldn't depend on any other LLSingletons. We'd + // get into trouble if we tried to recursively engage that machinery. + static LLSingletonBase::list_t sDummyList; + return sDummyList; } }; @@ -201,10 +233,19 @@ LLSingletonBase::LLSingletonBase(tag<DERIVED_TYPE>): mCleaned(false), mDeleteSingleton(NULL) { - // Make this the currently-initializing LLSingleton. + // This is the earliest possible point at which we can push this new + // instance onto the init stack. LLSingleton::constructSingleton() can't + // do it before calling the constructor, because it doesn't have an + // instance pointer until the constructor returns. Fortunately this + // constructor is guaranteed to be called before any subclass constructor. + // Make this new instance the currently-initializing LLSingleton. LLSingleton_manage_master<DERIVED_TYPE>().push_initializing(this); } +// forward declare for friend directive within LLSingleton +template <typename DERIVED_TYPE> +class LLParamSingleton; + /** * LLSingleton implements the getInstance() method part of the Singleton * pattern. It can't make the derived class constructors protected, though, so @@ -270,9 +311,94 @@ template <typename DERIVED_TYPE> class LLSingleton : public LLSingletonBase { private: - static DERIVED_TYPE* constructSingleton() + // Allow LLParamSingleton subclass -- but NOT DERIVED_TYPE itself -- to + // access our private members. + friend class LLParamSingleton<DERIVED_TYPE>; + + // LLSingleton only supports a nullary constructor. However, the specific + // purpose for its subclass LLParamSingleton is to support Singletons + // requiring constructor arguments. constructSingleton() supports both use + // cases. + template <typename... Args> + static void constructSingleton(Args&&... args) + { + auto prev_size = LLSingleton_manage_master<DERIVED_TYPE>().get_initializing().size(); + // getInstance() calls are from within constructor + sData.mInitState = CONSTRUCTING; + try + { + sData.mInstance = new DERIVED_TYPE(std::forward<Args>(args)...); + // we have called constructor, have not yet called initSingleton() + sData.mInitState = CONSTRUCTED; + } + catch (const std::exception& err) + { + // LLSingletonBase might -- or might not -- have pushed the new + // instance onto the init stack before the exception. Reset the + // init stack to its previous size BEFORE logging so log-machinery + // LLSingletons don't record a dependency on DERIVED_TYPE! + LLSingleton_manage_master<DERIVED_TYPE>().reset_initializing(prev_size); + logwarns("Error constructing ", classname<DERIVED_TYPE>().c_str(), + ": ", err.what()); + // There isn't a separate EInitState value meaning "we attempted + // to construct this LLSingleton subclass but could not," so use + // DELETED. That seems slightly more appropriate than UNINITIALIZED. + sData.mInitState = DELETED; + // propagate the exception + throw; + } + } + + static void finishInitializing() + { + // getInstance() calls are from within initSingleton() + sData.mInitState = INITIALIZING; + try + { + // initialize singleton after constructing it so that it can + // reference other singletons which in turn depend on it, thus + // breaking cyclic dependencies + sData.mInstance->initSingleton(); + sData.mInitState = INITIALIZED; + + // pop this off stack of initializing singletons + pop_initializing(); + } + catch (const std::exception& err) + { + // pop this off stack of initializing singletons here, too -- + // BEFORE logging, so log-machinery LLSingletons don't record a + // dependency on DERIVED_TYPE! + pop_initializing(); + logwarns("Error in ", classname<DERIVED_TYPE>().c_str(), + "::initSingleton(): ", err.what()); + // and get rid of the instance entirely + deleteSingleton(); + // propagate the exception + throw; + } + } + + static void pop_initializing() { - return new DERIVED_TYPE(); + // route through LLSingleton_manage_master so we Do The Right Thing + // (namely, nothing) for MasterList + LLSingleton_manage_master<DERIVED_TYPE>().pop_initializing(sData.mInstance); + } + + // Without this 'using' declaration, the static method we're declaring + // here would hide the base-class method we want it to call. + using LLSingletonBase::capture_dependency; + static void capture_dependency() + { + // By this point, if DERIVED_TYPE was pushed onto the initializing + // stack, it has been popped off. So the top of that stack, if any, is + // an LLSingleton that directly depends on DERIVED_TYPE. If + // getInstance() was called by another LLSingleton, rather than from + // vanilla application code, record the dependency. + sData.mInstance->capture_dependency( + LLSingleton_manage_master<DERIVED_TYPE>().get_initializing(), + sData.mInitState); } // We know of no way to instruct the compiler that every subclass @@ -285,34 +411,17 @@ private: // subclass body. virtual void you_must_use_LLSINGLETON_macro() = 0; - // stores pointer to singleton instance - struct SingletonLifetimeManager + // The purpose of this struct is to engage the C++11 guarantee that static + // variables declared in function scope are initialized exactly once, even + // if multiple threads concurrently reach the same declaration. + // https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables + // Since getInstance() declares a static instance of SingletonInitializer, + // only the first call to getInstance() calls constructSingleton(). + struct SingletonInitializer { - SingletonLifetimeManager() - { - construct(); - } - - static void construct() - { - sData.mInitState = CONSTRUCTING; - sData.mInstance = constructSingleton(); - sData.mInitState = INITIALIZING; - } - - ~SingletonLifetimeManager() + SingletonInitializer() { - // The dependencies between LLSingletons, and the arbitrary order - // of static-object destruction, mean that we DO NOT WANT this - // destructor to delete this LLSingleton. This destructor will run - // without regard to any other LLSingleton whose cleanup might - // depend on its existence. If you want to clean up LLSingletons, - // call LLSingletonBase::deleteAll() sometime before static-object - // destruction begins. That method will properly honor cross- - // LLSingleton dependencies. Otherwise we simply leak LLSingleton - // instances at shutdown. Since the whole process is terminating - // anyway, that's not necessarily a bad thing; it depends on what - // resources your LLSingleton instances are managing. + constructSingleton(); } }; @@ -363,64 +472,59 @@ public: static void deleteSingleton() { delete sData.mInstance; - sData.mInstance = NULL; - sData.mInitState = DELETED; + // SingletonData state handled by destructor, above } static DERIVED_TYPE* getInstance() { - static SingletonLifetimeManager sLifeTimeMgr; + // call constructSingleton() only the first time we get here + static SingletonInitializer sInitializer; switch (sData.mInitState) { case UNINITIALIZED: // should never be uninitialized at this point logerrs("Uninitialized singleton ", - demangle(typeid(DERIVED_TYPE).name()).c_str()); + classname<DERIVED_TYPE>().c_str()); return NULL; case CONSTRUCTING: + // here if DERIVED_TYPE's constructor (directly or indirectly) + // calls DERIVED_TYPE::getInstance() logerrs("Tried to access singleton ", - demangle(typeid(DERIVED_TYPE).name()).c_str(), + classname<DERIVED_TYPE>().c_str(), " from singleton constructor!"); return NULL; - case INITIALIZING: - // go ahead and flag ourselves as initialized so we can be - // reentrant during initialization - sData.mInitState = INITIALIZED; - // initialize singleton after constructing it so that it can - // reference other singletons which in turn depend on it, thus - // breaking cyclic dependencies - sData.mInstance->initSingleton(); - // pop this off stack of initializing singletons - LLSingleton_manage_master<DERIVED_TYPE>().pop_initializing(sData.mInstance); + case CONSTRUCTED: + // first time through: set to CONSTRUCTED by + // constructSingleton(), called by sInitializer's constructor; + // still have to call initSingleton() + finishInitializing(); break; + case INITIALIZING: + // here if DERIVED_TYPE::initSingleton() (directly or indirectly) + // calls DERIVED_TYPE::getInstance(): go ahead and allow it case INITIALIZED: + // normal subsequent calls break; case DELETED: + // called after deleteSingleton() logwarns("Trying to access deleted singleton ", - demangle(typeid(DERIVED_TYPE).name()).c_str(), + classname<DERIVED_TYPE>().c_str(), " -- creating new instance"); - SingletonLifetimeManager::construct(); - // same as first time construction - sData.mInitState = INITIALIZED; - sData.mInstance->initSingleton(); - // pop this off stack of initializing singletons - LLSingleton_manage_master<DERIVED_TYPE>().pop_initializing(sData.mInstance); + // This recovery sequence is NOT thread-safe! We would need a + // recursive_mutex a la LLParamSingleton. + constructSingleton(); + finishInitializing(); break; } - // By this point, if DERIVED_TYPE was pushed onto the initializing - // stack, it has been popped off. So the top of that stack, if any, is - // an LLSingleton that directly depends on DERIVED_TYPE. If this call - // came from another LLSingleton, rather than from vanilla application - // code, record the dependency. - sData.mInstance->capture_dependency( - LLSingleton_manage_master<DERIVED_TYPE>().get_initializing(sData.mInstance), - sData.mInitState); + // record the dependency, if any: check if we got here from another + // LLSingleton's constructor or initSingleton() method + capture_dependency(); return sData.mInstance; } @@ -460,6 +564,173 @@ private: template<typename T> typename LLSingleton<T>::SingletonData LLSingleton<T>::sData; + +/** + * LLParamSingleton<T> is like LLSingleton<T>, except in the following ways: + * + * * It is NOT instantiated on demand (instance() or getInstance()). You must + * first call initParamSingleton(constructor args...). + * * Before initParamSingleton(), calling instance() or getInstance() dies with + * LL_ERRS. + * * initParamSingleton() may be called only once. A second call dies with + * LL_ERRS. + * * However, distinct initParamSingleton() calls can be used to engage + * different constructors, as long as only one such call is executed at + * runtime. + * * Unlike LLSingleton, an LLParamSingleton cannot be "revived" by an + * instance() or getInstance() call after deleteSingleton(). + * + * Importantly, though, each LLParamSingleton subclass does participate in the + * dependency-ordered LLSingletonBase::deleteAll() processing. + */ +template <typename DERIVED_TYPE> +class LLParamSingleton : public LLSingleton<DERIVED_TYPE> +{ +private: + typedef LLSingleton<DERIVED_TYPE> super; + // Use a recursive_mutex in case of constructor circularity. With a + // non-recursive mutex, that would result in deadlock rather than the + // logerrs() call in getInstance(). + typedef std::recursive_mutex mutex_t; + +public: + using super::deleteSingleton; + using super::instanceExists; + using super::wasDeleted; + + // Passes arguments to DERIVED_TYPE's constructor and sets appropriate states + template <typename... Args> + static void initParamSingleton(Args&&... args) + { + // In case racing threads both call initParamSingleton() at the same + // time, serialize them. One should initialize; the other should see + // mInitState already set. + std::unique_lock<mutex_t> lk(getMutex()); + // For organizational purposes this function shouldn't be called twice + if (super::sData.mInitState != super::UNINITIALIZED) + { + super::logerrs("Tried to initialize singleton ", + super::template classname<DERIVED_TYPE>().c_str(), + " twice!"); + } + else + { + super::constructSingleton(std::forward<Args>(args)...); + super::finishInitializing(); + } + } + + static DERIVED_TYPE* getInstance() + { + // In case racing threads call getInstance() at the same moment as + // initParamSingleton(), serialize the calls. + std::unique_lock<mutex_t> lk(getMutex()); + + switch (super::sData.mInitState) + { + case super::UNINITIALIZED: + super::logerrs("Uninitialized param singleton ", + super::template classname<DERIVED_TYPE>().c_str()); + break; + + case super::CONSTRUCTING: + super::logerrs("Tried to access param singleton ", + super::template classname<DERIVED_TYPE>().c_str(), + " from singleton constructor!"); + break; + + case super::CONSTRUCTED: + // Should never happen!? The CONSTRUCTED state is specifically to + // navigate through LLSingleton::SingletonInitializer getting + // constructed (once) before LLSingleton::getInstance()'s switch + // on mInitState. But our initParamSingleton() method calls + // constructSingleton() and then calls finishInitializing(), which + // immediately sets INITIALIZING. Why are we here? + super::logerrs("Param singleton ", + super::template classname<DERIVED_TYPE>().c_str(), + "::initSingleton() not yet called"); + break; + + case super::INITIALIZING: + // As with LLSingleton, explicitly permit circular calls from + // within initSingleton() + case super::INITIALIZED: + // for any valid call, capture dependencies + super::capture_dependency(); + return super::sData.mInstance; + + case super::DELETED: + super::logerrs("Trying to access deleted param singleton ", + super::template classname<DERIVED_TYPE>().c_str()); + break; + } + + // should never actually get here; this is to pacify the compiler, + // which assumes control might return from logerrs() + return nullptr; + } + + // instance() is replicated here so it calls + // LLParamSingleton::getInstance() rather than LLSingleton::getInstance() + // -- avoid making getInstance() virtual + static DERIVED_TYPE& instance() + { + return *getInstance(); + } + +private: + // sMutex must be a function-local static rather than a static member. One + // of the essential features of LLSingleton and friends is that they must + // support getInstance() even when the containing module's static + // variables have not yet been runtime-initialized. A mutex requires + // construction. A static class member might not yet have been + // constructed. + // + // We could store a dumb mutex_t*, notice when it's NULL and allocate a + // heap mutex -- but that's vulnerable to race conditions. And we can't + // defend the dumb pointer with another mutex. + // + // We could store a std::atomic<mutex_t*> -- but a default-constructed + // std::atomic<T> does not contain a valid T, even a default-constructed + // T! Which means std::atomic, too, requires runtime initialization. + // + // But a function-local static is guaranteed to be initialized exactly + // once, the first time control reaches that declaration. + static mutex_t& getMutex() + { + static mutex_t sMutex; + return sMutex; + } +}; + +/** + * Initialization locked singleton, only derived class can decide when to initialize. + * Starts locked. + * For cases when singleton has a dependency onto something or. + * + * LLLockedSingleton is like an LLParamSingleton with a nullary constructor. + * It cannot be instantiated on demand (instance() or getInstance() call) -- + * it must be instantiated by calling construct(). However, it does + * participate in dependency-ordered LLSingletonBase::deleteAll() processing. + */ +template <typename DT> +class LLLockedSingleton : public LLParamSingleton<DT> +{ + typedef LLParamSingleton<DT> super; + +public: + using super::deleteSingleton; + using super::getInstance; + using super::instance; + using super::instanceExists; + using super::wasDeleted; + + static void construct() + { + super::initParamSingleton(); + } +}; + /** * Use LLSINGLETON(Foo); at the start of an LLSingleton<Foo> subclass body * when you want to declare an out-of-line constructor: @@ -484,13 +755,13 @@ typename LLSingleton<T>::SingletonData LLSingleton<T>::sData; * file, use 'inline' (unless it's a template class) to avoid duplicate-symbol * errors at link time. */ -#define LLSINGLETON(DERIVED_CLASS) \ +#define LLSINGLETON(DERIVED_CLASS, ...) \ private: \ /* implement LLSingleton pure virtual method whose sole purpose */ \ /* is to remind people to use this macro */ \ virtual void you_must_use_LLSINGLETON_macro() {} \ friend class LLSingleton<DERIVED_CLASS>; \ - DERIVED_CLASS() + DERIVED_CLASS(__VA_ARGS__) /** * Use LLSINGLETON_EMPTY_CTOR(Foo); at the start of an LLSingleton<Foo> |