summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llcommon/llsingleton.cpp151
-rw-r--r--indra/llcommon/llsingleton.h164
2 files changed, 197 insertions, 118 deletions
diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp
index c45c144570..812fd31719 100644
--- a/indra/llcommon/llsingleton.cpp
+++ b/indra/llcommon/llsingleton.cpp
@@ -31,6 +31,7 @@
#include "llerrorcontrol.h" // LLError::is_available()
#include "lldependencies.h"
#include "llcoro_get_id.h"
+#include "llexception.h"
#include <boost/foreach.hpp>
#include <boost/unordered_map.hpp>
#include <algorithm>
@@ -57,17 +58,59 @@ bool oktolog();
class LLSingletonBase::MasterList:
public LLSingleton<LLSingletonBase::MasterList>
{
+private:
LLSINGLETON_EMPTY_CTOR(MasterList);
-public:
- // No need to make this private with accessors; nobody outside this source
- // file can see it.
+ // Independently of the LLSingleton locks governing construction,
+ // destruction and other state changes of the MasterList instance itself,
+ // we must also defend each of the data structures owned by the
+ // MasterList.
+ // This must be a recursive_mutex because, while the lock is held for
+ // manipulating some data in the master list, we must also check whether
+ // it's safe to log -- which involves querying a different LLSingleton --
+ // which requires accessing the master list.
+ typedef std::recursive_mutex mutex_t;
+ typedef std::unique_lock<mutex_t> lock_t;
+
+ mutex_t mMutex;
+public:
+ // Instantiate this to both obtain a reference to MasterList::instance()
+ // and lock its mutex for the lifespan of this Lock instance.
+ class Lock
+ {
+ public:
+ Lock():
+ mMasterList(MasterList::instance()),
+ mLock(mMasterList.mMutex)
+ {}
+ Lock(const Lock&) = delete;
+ Lock& operator=(const Lock&) = delete;
+ MasterList& get() const { return mMasterList; }
+ operator MasterList&() const { return get(); }
+
+ protected:
+ MasterList& mMasterList;
+ MasterList::lock_t mLock;
+ };
+
+private:
// This is the master list of all instantiated LLSingletons (save the
// MasterList itself) in arbitrary order. You MUST call dep_sort() before
// traversing this list.
- LLSingletonBase::list_t mMaster;
+ list_t mMaster;
+
+public:
+ // Instantiate this to obtain a reference to MasterList::mMaster and to
+ // hold the MasterList lock for the lifespan of this LockedMaster
+ // instance.
+ struct LockedMaster: public Lock
+ {
+ list_t& get() const { return mMasterList.mMaster; }
+ operator list_t&() const { return get(); }
+ };
+private:
// We need to maintain a stack of LLSingletons currently being
// initialized, either in the constructor or in initSingleton(). However,
// managing that as a stack depends on having a DISTINCT 'initializing'
@@ -83,10 +126,44 @@ public:
// Instead, use a map of llcoro::id to select the appropriate
// coro-specific 'initializing' stack. llcoro::get_id() is carefully
// implemented to avoid requiring LLCoros.
- typedef boost::unordered_map<llcoro::id, LLSingletonBase::list_t> InitializingMap;
+ typedef boost::unordered_map<llcoro::id, list_t> InitializingMap;
InitializingMap mInitializing;
- // non-static method, cf. LLSingletonBase::get_initializing()
+public:
+ // Instantiate this to obtain a reference to the coroutine-specific
+ // initializing list and to hold the MasterList lock for the lifespan of
+ // this LockedInitializing instance.
+ struct LockedInitializing: public Lock
+ {
+ public:
+ LockedInitializing():
+ // only do the lookup once, cache the result
+ // note that the lock is already locked during this lookup
+ mList(&mMasterList.get_initializing_())
+ {}
+ list_t& get() const
+ {
+ if (! mList)
+ {
+ LLTHROW(std::runtime_error("Trying to use LockedInitializing "
+ "after cleanup_initializing()"));
+ }
+ return *mList;
+ }
+ operator list_t&() const { return get(); }
+ void log(const char* verb, const char* name);
+ void cleanup_initializing()
+ {
+ mMasterList.cleanup_initializing_();
+ mList = nullptr;
+ }
+
+ private:
+ // Store pointer since cleanup_initializing() must clear it.
+ list_t* mList;
+ };
+
+private:
list_t& get_initializing_()
{
// map::operator[] has find-or-create semantics, exactly what we need
@@ -104,16 +181,12 @@ public:
}
};
-//static
-LLSingletonBase::list_t& LLSingletonBase::get_master()
-{
- return LLSingletonBase::MasterList::instance().mMaster;
-}
-
void LLSingletonBase::add_master()
{
// As each new LLSingleton is constructed, add to the master list.
- get_master().push_back(this);
+ // This temporary LockedMaster should suffice to hold the MasterList lock
+ // during the push_back() call.
+ MasterList::LockedMaster().get().push_back(this);
}
void LLSingletonBase::remove_master()
@@ -125,27 +198,32 @@ void LLSingletonBase::remove_master()
// master list, and remove this item IF FOUND. We have few enough
// LLSingletons, and they are so rarely destroyed (once per run), that the
// cost of a linear search should not be an issue.
- get_master().remove(this);
+ // This temporary LockedMaster should suffice to hold the MasterList lock
+ // during the remove() call.
+ MasterList::LockedMaster().get().remove(this);
}
//static
-LLSingletonBase::list_t& LLSingletonBase::get_initializing()
+LLSingletonBase::list_t::size_type LLSingletonBase::get_initializing_size()
{
- return LLSingletonBase::MasterList::instance().get_initializing_();
+ return MasterList::LockedInitializing().get().size();
}
LLSingletonBase::~LLSingletonBase() {}
void LLSingletonBase::push_initializing(const char* name)
{
+ MasterList::LockedInitializing locked_list;
// log BEFORE pushing so logging singletons don't cry circularity
- log_initializing("Pushing", name);
- get_initializing().push_back(this);
+ locked_list.log("Pushing", name);
+ locked_list.get().push_back(this);
}
void LLSingletonBase::pop_initializing()
{
- list_t& list(get_initializing());
+ // Lock the MasterList for the duration of this call
+ MasterList::LockedInitializing locked_list;
+ list_t& list(locked_list.get());
if (list.empty())
{
@@ -165,7 +243,7 @@ void LLSingletonBase::pop_initializing()
// entirely.
if (list.empty())
{
- MasterList::instance().cleanup_initializing_();
+ locked_list.cleanup_initializing();
}
// Now validate the newly-popped LLSingleton.
@@ -177,7 +255,7 @@ void LLSingletonBase::pop_initializing()
}
// log AFTER popping so logging singletons don't cry circularity
- log_initializing("Popping", typeid(*back).name());
+ locked_list.log("Popping", typeid(*back).name());
}
void LLSingletonBase::reset_initializing(list_t::size_type size)
@@ -191,7 +269,8 @@ void LLSingletonBase::reset_initializing(list_t::size_type size)
// push_initializing() call in LLSingletonBase's constructor. So only
// remove the stack top if in fact we've pushed something more than the
// previous size.
- list_t& list(get_initializing());
+ MasterList::LockedInitializing locked_list;
+ list_t& list(locked_list.get());
while (list.size() > size)
{
@@ -201,29 +280,32 @@ void LLSingletonBase::reset_initializing(list_t::size_type size)
// as in pop_initializing()
if (list.empty())
{
- MasterList::instance().cleanup_initializing_();
+ locked_list.cleanup_initializing();
}
}
-//static
-void LLSingletonBase::log_initializing(const char* verb, const char* name)
+void LLSingletonBase::MasterList::LockedInitializing::log(const char* verb, const char* name)
{
if (oktolog())
{
LL_DEBUGS("LLSingleton") << verb << ' ' << demangle(name) << ';';
- list_t& list(get_initializing());
- for (list_t::const_reverse_iterator ri(list.rbegin()), rend(list.rend());
- ri != rend; ++ri)
+ if (mList)
{
- LLSingletonBase* sb(*ri);
- LL_CONT << ' ' << classname(sb);
+ for (list_t::const_reverse_iterator ri(mList->rbegin()), rend(mList->rend());
+ ri != rend; ++ri)
+ {
+ LLSingletonBase* sb(*ri);
+ LL_CONT << ' ' << classname(sb);
+ }
}
LL_ENDL;
}
}
-void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initState)
+void LLSingletonBase::capture_dependency(EInitState initState)
{
+ MasterList::LockedInitializing locked_list;
+ list_t& initializing(locked_list.get());
// Did this getInstance() call come from another LLSingleton, or from
// vanilla application code? Note that although this is a nontrivial
// method, the vast majority of its calls arrive here with initializing
@@ -313,8 +395,9 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort()
// deleteAll().
typedef LLDependencies<LLSingletonBase*> SingletonDeps;
SingletonDeps sdeps;
- list_t& master(get_master());
- BOOST_FOREACH(LLSingletonBase* sp, master)
+ // Lock while traversing the master list
+ MasterList::LockedMaster master;
+ BOOST_FOREACH(LLSingletonBase* sp, master.get())
{
// Build the SingletonDeps structure by adding, for each
// LLSingletonBase* sp in the master list, sp itself. It has no
@@ -326,7 +409,7 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort()
SingletonDeps::KeyList(sp->mDepends.begin(), sp->mDepends.end()));
}
vec_t ret;
- ret.reserve(master.size());
+ ret.reserve(master.get().size());
// We should be able to effect this with a transform_iterator that
// extracts just the first (key) element from each sorted_iterator, then
// uses vec_t's range constructor... but frankly this is more
diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h
index 0da6d548ab..8dec8bfb3b 100644
--- a/indra/llcommon/llsingleton.h
+++ b/indra/llcommon/llsingleton.h
@@ -51,10 +51,9 @@ public:
private:
// All existing LLSingleton instances are tracked in this master list.
typedef std::list<LLSingletonBase*> list_t;
- static list_t& get_master();
- // This, on the other hand, is a stack whose top indicates the LLSingleton
- // currently being initialized.
- static list_t& get_initializing();
+ // Size of stack whose top indicates the LLSingleton currently being
+ // initialized.
+ static list_t::size_type get_initializing_size();
// Produce a vector<LLSingletonBase*> of master list, in dependency order.
typedef std::vector<LLSingletonBase*> vec_t;
static vec_t dep_sort();
@@ -115,13 +114,10 @@ protected:
// 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);
protected:
// If a given call to B::getInstance() happens during either A::A() or
// A::initSingleton(), record that A directly depends on B.
- void capture_dependency(list_t& initializing, EInitState);
+ void capture_dependency(EInitState);
// delegate LL_ERRS() logging to llsingleton.cpp
static void logerrs(const char* p1, const char* p2="",
@@ -203,9 +199,16 @@ struct LLSingleton_manage_master
{
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(); }
+ // For any LLSingleton subclass except the MasterList, obtain the size of
+ // the init stack from the MasterList singleton instance.
+ LLSingletonBase::list_t::size_type get_initializing_size()
+ {
+ return LLSingletonBase::get_initializing_size();
+ }
+ void capture_dependency(LLSingletonBase* sb, LLSingletonBase::EInitState state)
+ {
+ sb->capture_dependency(state);
+ }
};
// But for the specific case of LLSingletonBase::MasterList, don't.
@@ -218,13 +221,8 @@ struct LLSingleton_manage_master<LLSingletonBase::MasterList>
void pop_initializing (LLSingletonBase*) {}
// since we never pushed, no need to clean up
void reset_initializing(LLSingletonBase::list_t::size_type size) {}
- LLSingletonBase::list_t& get_initializing()
- {
- // 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;
- }
+ LLSingletonBase::list_t::size_type get_initializing_size() { return 0; }
+ void capture_dependency(LLSingletonBase*, LLSingletonBase::EInitState) {}
};
// Now we can implement LLSingletonBase's template constructor.
@@ -304,8 +302,6 @@ class LLParamSingleton;
* remaining LLSingleton instances will be destroyed in dependency order. (Or
* call MySubclass::deleteSingleton() to specifically destroy the canonical
* MySubclass instance.)
- *
- * As currently written, LLSingleton is not thread-safe.
*/
template <typename DERIVED_TYPE>
class LLSingleton : public LLSingletonBase
@@ -315,6 +311,47 @@ private:
// access our private members.
friend class LLParamSingleton<DERIVED_TYPE>;
+ // Scoped lock on the mutex associated with this LLSingleton<T>
+ class Locker
+ {
+ public:
+ Locker(): mLock(getMutex()) {}
+
+ private:
+ // Use a recursive_mutex in case of constructor circularity. With a
+ // non-recursive mutex, that would result in deadlock.
+ typedef std::recursive_mutex mutex_t;
+
+ // LLSingleton<T> must have a distinct instance of sMutex for every
+ // distinct T. It's tempting to consider hoisting Locker up into
+ // LLSingletonBase. Don't do it.
+ //
+ // 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;
+ }
+
+ std::unique_lock<mutex_t> mLock;
+ };
+
// 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
@@ -322,7 +359,7 @@ private:
template <typename... Args>
static void constructSingleton(Args&&... args)
{
- auto prev_size = LLSingleton_manage_master<DERIVED_TYPE>().get_initializing().size();
+ auto prev_size = LLSingleton_manage_master<DERIVED_TYPE>().get_initializing_size();
// getInstance() calls are from within constructor
sData.mInitState = CONSTRUCTING;
try
@@ -386,9 +423,6 @@ private:
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
@@ -396,9 +430,8 @@ private:
// 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);
+ LLSingleton_manage_master<DERIVED_TYPE>().capture_dependency(
+ sData.mInstance, sData.mInitState);
}
// We know of no way to instruct the compiler that every subclass
@@ -411,20 +444,6 @@ private:
// subclass body.
virtual void you_must_use_LLSINGLETON_macro() = 0;
- // 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
- {
- SingletonInitializer()
- {
- constructSingleton();
- }
- };
-
protected:
// Pass DERIVED_TYPE explicitly to LLSingletonBase's constructor because,
// until our subclass constructor completes, *this isn't yet a
@@ -439,15 +458,20 @@ protected:
LLSingleton_manage_master<DERIVED_TYPE>().add(this);
}
-public:
+protected:
virtual ~LLSingleton()
{
+ // In case racing threads call getInstance() at the same moment as
+ // this destructor, serialize the calls.
+ Locker lk;
+
// remove this instance from the master list
LLSingleton_manage_master<DERIVED_TYPE>().remove(this);
sData.mInstance = NULL;
sData.mInitState = DELETED;
}
+public:
/**
* @brief Immediately delete the singleton.
*
@@ -477,17 +501,12 @@ public:
static DERIVED_TYPE* getInstance()
{
- // call constructSingleton() only the first time we get here
- static SingletonInitializer sInitializer;
+ // In case racing threads call getInstance() at the same moment,
+ // serialize the calls.
+ Locker lk;
switch (sData.mInitState)
{
- case UNINITIALIZED:
- // should never be uninitialized at this point
- logerrs("Uninitialized singleton ",
- classname<DERIVED_TYPE>().c_str());
- return NULL;
-
case CONSTRUCTING:
// here if DERIVED_TYPE's constructor (directly or indirectly)
// calls DERIVED_TYPE::getInstance()
@@ -496,9 +515,11 @@ public:
" from singleton constructor!");
return NULL;
+ case UNINITIALIZED:
+ constructSingleton();
+ // fall through...
+
case CONSTRUCTED:
- // first time through: set to CONSTRUCTED by
- // constructSingleton(), called by sInitializer's constructor;
// still have to call initSingleton()
finishInitializing();
break;
@@ -515,8 +536,6 @@ public:
logwarns("Trying to access deleted singleton ",
classname<DERIVED_TYPE>().c_str(),
" -- creating new instance");
- // This recovery sequence is NOT thread-safe! We would need a
- // recursive_mutex a la LLParamSingleton.
constructSingleton();
finishInitializing();
break;
@@ -539,6 +558,8 @@ public:
// Use this to avoid accessing singletons before they can safely be constructed.
static bool instanceExists()
{
+ // defend any access to sData from racing threads
+ Locker lk;
return sData.mInitState == INITIALIZED;
}
@@ -547,6 +568,8 @@ public:
// cleaned up.
static bool wasDeleted()
{
+ // defend any access to sData from racing threads
+ Locker lk;
return sData.mInitState == DELETED;
}
@@ -588,10 +611,7 @@ 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;
+ using typename super::Locker;
public:
using super::deleteSingleton;
@@ -605,7 +625,7 @@ public:
// 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());
+ Locker lk;
// For organizational purposes this function shouldn't be called twice
if (super::sData.mInitState != super::UNINITIALIZED)
{
@@ -624,7 +644,7 @@ public:
{
// In case racing threads call getInstance() at the same moment as
// initParamSingleton(), serialize the calls.
- std::unique_lock<mutex_t> lk(getMutex());
+ Locker lk;
switch (super::sData.mInitState)
{
@@ -677,30 +697,6 @@ public:
{
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;
- }
};
/**