summaryrefslogtreecommitdiff
path: root/indra/llcommon/llsingleton.h
diff options
context:
space:
mode:
authorAndrey Lihatskiy <alihatskiy@productengine.com>2020-02-03 15:18:34 +0200
committerAndrey Lihatskiy <alihatskiy@productengine.com>2020-02-03 15:18:34 +0200
commite0727702ea00c975361cf94b4d2903a9fec6f6f8 (patch)
treed64f1cf4ebf58a08fb7fc197206ce31fab2aa115 /indra/llcommon/llsingleton.h
parentc5195905b8c9b30a2b3aab652aa9828e9825c42f (diff)
parente58b5263598368b6dec4a836c72c69b50d701911 (diff)
Merge branch 'DRTVWR-501' into trunk
Diffstat (limited to 'indra/llcommon/llsingleton.h')
-rw-r--r--indra/llcommon/llsingleton.h409
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>