Skip to content
Snippets Groups Projects
Commit 4fce6dc4 authored by Nat Goodspeed's avatar Nat Goodspeed
Browse files

DRTVWR-493: Permit LLParamSingleton::initSingleton() circularity.

This was forbidden, but AndreyK points out cases in which LLParamSingleton::
initSingleton() should in fact be allowed to circle back to its own instance()
method. Use a recursive_mutex instead of plain mutex to permit that; remove
LL_ERRS preventing it.

Add LLParamSingleton::instance() method that calls
LLParamSingleton::getInstance(). Inheriting LLSingleton::instance() called
LLSingleton::getInstance() -- not at all what we want.

Add LLParamSingleton unit tests.
parent 5a72d34b
No related branches found
No related tags found
No related merge requests found
...@@ -491,9 +491,6 @@ typename LLSingleton<T>::SingletonData LLSingleton<T>::sData; ...@@ -491,9 +491,6 @@ typename LLSingleton<T>::SingletonData LLSingleton<T>::sData;
* * However, distinct initParamSingleton() calls can be used to engage * * However, distinct initParamSingleton() calls can be used to engage
* different constructors, as long as only one such call is executed at * different constructors, as long as only one such call is executed at
* runtime. * runtime.
* * Circularity is not permitted. No LLSingleton referenced by an
* LLParamSingleton's constructor or initSingleton() method may call this
* LLParamSingleton's instance() or getInstance() methods.
* * Unlike LLSingleton, an LLParamSingleton cannot be "revived" by an * * Unlike LLSingleton, an LLParamSingleton cannot be "revived" by an
* instance() or getInstance() call after deleteSingleton(). * instance() or getInstance() call after deleteSingleton().
* *
...@@ -508,7 +505,6 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE> ...@@ -508,7 +505,6 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
public: public:
using super::deleteSingleton; using super::deleteSingleton;
using super::instance;
using super::instanceExists; using super::instanceExists;
using super::wasDeleted; using super::wasDeleted;
...@@ -519,7 +515,7 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE> ...@@ -519,7 +515,7 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
// In case racing threads both call initParamSingleton() at the same // In case racing threads both call initParamSingleton() at the same
// time, serialize them. One should initialize; the other should see // time, serialize them. One should initialize; the other should see
// mInitState already set. // mInitState already set.
std::unique_lock<std::mutex> lk(mMutex); std::unique_lock<decltype(mMutex)> lk(mMutex);
// For organizational purposes this function shouldn't be called twice // For organizational purposes this function shouldn't be called twice
if (super::sData.mInitState != super::UNINITIALIZED) if (super::sData.mInitState != super::UNINITIALIZED)
{ {
...@@ -538,7 +534,7 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE> ...@@ -538,7 +534,7 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
{ {
// In case racing threads call getInstance() at the same moment as // In case racing threads call getInstance() at the same moment as
// initParamSingleton(), serialize the calls. // initParamSingleton(), serialize the calls.
std::unique_lock<std::mutex> lk(mMutex); std::unique_lock<decltype(mMutex)> lk(mMutex);
switch (super::sData.mInitState) switch (super::sData.mInitState)
{ {
...@@ -550,15 +546,10 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE> ...@@ -550,15 +546,10 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
case super::CONSTRUCTING: case super::CONSTRUCTING:
super::logerrs("Tried to access param singleton ", super::logerrs("Tried to access param singleton ",
super::demangle(typeid(DERIVED_TYPE).name()).c_str(), super::demangle(typeid(DERIVED_TYPE).name()).c_str(),
" from singleton constructor!"); " from singleton constructor!");
break; break;
case super::INITIALIZING: case super::INITIALIZING:
super::logerrs("Tried to access param singleton ",
super::demangle(typeid(DERIVED_TYPE).name()).c_str(),
" from initSingleton() method!");
break;
case super::INITIALIZED: case super::INITIALIZED:
return super::sData.mInstance; return super::sData.mInstance;
...@@ -573,12 +564,23 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE> ...@@ -573,12 +564,23 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
return nullptr; 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: private:
static std::mutex mMutex; // Use a recursive_mutex in case of constructor circularity. With a
// non-recursive mutex, that would result in deadlock rather than the
// logerrs() call coded above.
static std::recursive_mutex mMutex;
}; };
template<typename T> template<typename T>
typename std::mutex LLParamSingleton<T>::mMutex; typename std::recursive_mutex LLParamSingleton<T>::mMutex;
/** /**
* Initialization locked singleton, only derived class can decide when to initialize. * Initialization locked singleton, only derived class can decide when to initialize.
......
...@@ -29,7 +29,8 @@ ...@@ -29,7 +29,8 @@
#include "llsingleton.h" #include "llsingleton.h"
#include "../test/lltut.h" #include "../test/lltut.h"
#include "wrapllerrs.h"
#include "llsd.h"
// Capture execution sequence by appending to log string. // Capture execution sequence by appending to log string.
std::string sLog; std::string sLog;
...@@ -198,4 +199,130 @@ namespace tut ...@@ -198,4 +199,130 @@ namespace tut
TESTS(A, B, 4, 5, 6, 7) TESTS(A, B, 4, 5, 6, 7)
TESTS(B, A, 8, 9, 10, 11) TESTS(B, A, 8, 9, 10, 11)
#define PARAMSINGLETON(cls) \
class cls: public LLParamSingleton<cls> \
{ \
LLSINGLETON(cls, const LLSD::String& str): mDesc(str) {} \
cls(LLSD::Integer i): mDesc(i) {} \
\
public: \
std::string desc() const { return mDesc.asString(); } \
\
private: \
LLSD mDesc; \
}
// Declare two otherwise-identical LLParamSingleton classes so we can
// validly initialize each using two different constructors. If we tried
// to test that with a single LLParamSingleton class within the same test
// program, we'd get 'trying to use deleted LLParamSingleton' errors.
PARAMSINGLETON(PSing1);
PARAMSINGLETON(PSing2);
template<> template<>
void singleton_object_t::test<12>()
{
set_test_name("LLParamSingleton");
WrapLLErrs catcherr;
// query methods
ensure("false positive on instanceExists()", ! PSing1::instanceExists());
ensure("false positive on wasDeleted()", ! PSing1::wasDeleted());
// try to reference before initializing
std::string threw = catcherr.catch_llerrs([](){
(void)PSing1::instance();
});
ensure_contains("too-early instance() didn't throw", threw, "Uninitialized");
// getInstance() behaves the same as instance()
threw = catcherr.catch_llerrs([](){
(void)PSing1::getInstance();
});
ensure_contains("too-early getInstance() didn't throw", threw, "Uninitialized");
// initialize using LLSD::String constructor
PSing1::initParamSingleton("string");
ensure_equals(PSing1::instance().desc(), "string");
ensure("false negative on instanceExists()", PSing1::instanceExists());
// try to initialize again
threw = catcherr.catch_llerrs([](){
PSing1::initParamSingleton("again");
});
ensure_contains("second ctor(string) didn't throw", threw, "twice");
// try to initialize using the other constructor -- should be
// well-formed, but illegal at runtime
threw = catcherr.catch_llerrs([](){
PSing1::initParamSingleton(17);
});
ensure_contains("other ctor(int) didn't throw", threw, "twice");
PSing1::deleteSingleton();
ensure("false negative on wasDeleted()", PSing1::wasDeleted());
threw = catcherr.catch_llerrs([](){
(void)PSing1::instance();
});
ensure_contains("accessed deleted LLParamSingleton", threw, "deleted");
}
template<> template<>
void singleton_object_t::test<13>()
{
set_test_name("LLParamSingleton alternate ctor");
WrapLLErrs catcherr;
// We don't have to restate all the tests for PSing1. Only test validly
// using the other constructor.
PSing2::initParamSingleton(17);
ensure_equals(PSing2::instance().desc(), "17");
// can't do it twice
std::string threw = catcherr.catch_llerrs([](){
PSing2::initParamSingleton(34);
});
ensure_contains("second ctor(int) didn't throw", threw, "twice");
// can't use the other constructor either
threw = catcherr.catch_llerrs([](){
PSing2::initParamSingleton("string");
});
ensure_contains("other ctor(string) didn't throw", threw, "twice");
}
class CircularPCtor: public LLParamSingleton<CircularPCtor>
{
LLSINGLETON(CircularPCtor)
{
// never mind indirection, just go straight for the circularity
(void)instance();
}
};
template<> template<>
void singleton_object_t::test<14>()
{
set_test_name("Circular LLParamSingleton constructor");
WrapLLErrs catcherr;
std::string threw = catcherr.catch_llerrs([](){
CircularPCtor::initParamSingleton();
});
ensure_contains("constructor circularity didn't throw", threw, "constructor");
}
class CircularPInit: public LLParamSingleton<CircularPInit>
{
LLSINGLETON_EMPTY_CTOR(CircularPInit);
public:
virtual void initSingleton()
{
// never mind indirection, just go straight for the circularity
(void)instance();
}
};
template<> template<>
void singleton_object_t::test<15>()
{
set_test_name("Circular LLParamSingleton initSingleton()");
WrapLLErrs catcherr;
std::string threw = catcherr.catch_llerrs([](){
CircularPInit::initParamSingleton();
});
ensure("initSingleton() circularity threw", threw.empty());
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment