summaryrefslogtreecommitdiff
path: root/indra/llcommon/lockstatic.h
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/lockstatic.h')
-rw-r--r--indra/llcommon/lockstatic.h79
1 files changed, 66 insertions, 13 deletions
diff --git a/indra/llcommon/lockstatic.h b/indra/llcommon/lockstatic.h
index 7cc9b7eec0..e83957b1fd 100644
--- a/indra/llcommon/lockstatic.h
+++ b/indra/llcommon/lockstatic.h
@@ -14,21 +14,36 @@
#define LL_LOCKSTATIC_H
#include "mutex.h" // std::unique_lock
+#include "llexception.h"
+#include <typeinfo>
namespace llthread
{
+class LockStaticBase
+{
+public:
+ // trying to lock Static after cleanup() has been called
+ struct Dead: public LLException
+ {
+ Dead(const std::string& what): LLException(what) {}
+ };
+
+protected:
+ static void throwDead(const char* mangled);
+};
+
// Instantiate this template to obtain a pointer to the canonical static
// instance of Static while holding a lock on that instance. Use of
// Static::mMutex presumes that Static declares some suitable mMutex.
template <typename Static>
-class LockStatic
+class LockStatic: public LockStaticBase
{
typedef std::unique_lock<decltype(Static::mMutex)> lock_t;
public:
LockStatic():
mData(getStatic()),
- mLock(mData->mMutex)
+ mLock(getLock(mData))
{}
Static* get() const { return mData; }
operator Static*() const { return get(); }
@@ -40,31 +55,69 @@ public:
mData = nullptr;
mLock.unlock();
}
+ // explicit destruction
+ // We used to store a static instance of Static in getStatic(). The
+ // trouble with that is that at some point during final termination
+ // cleanup, the compiler calls ~Static(), destroying the mutex. If some
+ // later static object's destructor tries to lock our Static, we blow up
+ // trying to lock a destroyed mutex object. This can happen, for instance,
+ // if some class's destructor tries to reference an LLSingleton.
+ // Since a plain dumb pointer has no destructor, the compiler leaves it
+ // alone, so the referenced heap Static instance can survive until we
+ // explicitly call this method.
+ void cleanup()
+ {
+ // certainly don't claim to lock after this point!
+ mData = nullptr;
+ Static*& ptrref{ getStatic() };
+ Static* ptr{ ptrref };
+ ptrref = nullptr;
+ delete ptr;
+ }
protected:
Static* mData;
lock_t mLock;
private:
- Static* getStatic()
+ static lock_t getLock(Static* data)
+ {
+ // data can be false if cleanup() has already been called. If so, no
+ // code in the caller is valid that depends on this instance. We dare
+ // to throw an exception because trying to lock Static after it's been
+ // deleted is not part of normal processing. There are callers who
+ // want to handle this exception, but it should indeed be treated as
+ // exceptional.
+ if (! data)
+ {
+ throwDead(typeid(LockStatic<Static>).name());
+ }
+ // Usual case: data isn't nullptr, carry on.
+ return lock_t(data->mMutex);
+ }
+
+ Static*& getStatic()
{
- // Static::mMutex must be function-local static rather than class-
- // static. Some of our consumers must function properly (therefore
- // lock properly) even when the containing module's static variables
- // have not yet been runtime-initialized. A mutex requires
+ // Our Static instance must be function-local static rather than
+ // class-static. Some of our consumers must function properly
+ // (therefore lock properly) 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 dumb mutex_t* class member, 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 Static sData;
- return &sData;
+ // once: the first time control reaches that declaration. Importantly,
+ // since a plain dumb pointer has no destructor, the compiler lets our
+ // heap Static instance survive until someone calls cleanup() (above).
+ static Static* sData{ new Static };
+ return sData;
}
};