From 17fae30f721c716bab1dd78cc5f8ac6b0aad49e0 Mon Sep 17 00:00:00 2001 From: andreykproductengine Date: Thu, 25 Jul 2019 15:17:11 +0300 Subject: DRTVWR-493 LLImage to LLParamSingleton --- indra/llcommon/llsingleton.h | 205 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 859e271e26..b127f4f529 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -446,6 +446,177 @@ public: return sData.mInitState == DELETED; } +protected: + static EInitState getInitState() + { + return sData.mInitState; + } + +private: + struct SingletonData + { + // explicitly has a default constructor so that member variables are zero initialized in BSS + // and only changed by singleton logic, not constructor running during startup + EInitState mInitState; + DERIVED_TYPE* mInstance; + }; + static SingletonData sData; +}; + + +template +class LLParamSingleton : public LLSingletonBase +{ +private: + + template + static DERIVED_TYPE* constructSingleton(Args&&... args) + { + return new DERIVED_TYPE(std::forward(args)...); + } + + // We know of no way to instruct the compiler that every subclass + // constructor MUST be private. + // However, we can make the LLPARAMSINGLETON() macro both declare + // a private constructor and provide the required friend declaration. + // How can we ensure that every subclass uses LLPARAMSINGLETON()? + // By making that macro provide a definition for this pure virtual + // method. If you get "can't instantiate class due to missing pure + // virtual method" for this method, then add LLPARAMSINGLETON(yourclass) + // in the subclass body. + virtual void you_must_use_LLSINGLETON_macro() = 0; + +protected: + // Pass DERIVED_TYPE explicitly to LLSingletonBase's constructor because, + // until our subclass constructor completes, *this isn't yet a + // full-fledged DERIVED_TYPE. + LLParamSingleton() : LLSingletonBase(LLSingletonBase::tag()) + { + // populate base-class function pointer with the static + // deleteSingleton() function for this particular specialization + mDeleteSingleton = &deleteSingleton; + + // add this new instance to the master list + LLSingleton_manage_master().add(this); + } + +public: + + virtual ~LLParamSingleton() + { + // remove this instance from the master list + LLSingleton_manage_master().remove(this); + sData.mInstance = NULL; + sData.mInitState = DELETED; + } + + // Passes arguments to DERIVED_TYPE's constructor and sets apropriate states + template + static void initParamSingleton(Args&&... args) + { + sData.mInitState = CONSTRUCTING; + sData.mInstance = constructSingleton(std::forward(args)...); + 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().pop_initializing(sData.mInstance); + } + + /** + * @brief Immediately delete the singleton. + * + * A subsequent call to LLProxy::getInstance() will construct a new + * instance of the class. + * + * Without an explicit call to LLSingletonBase::deleteAll(), LLSingletons + * are implicitly destroyed after main() has exited and the C++ runtime is + * cleaning up statically-constructed objects. Some classes derived from + * LLSingleton have objects that are part of a runtime system that is + * terminated before main() exits. Calling the destructor of those objects + * after the termination of their respective systems can cause crashes and + * other problems during termination of the project. Using this method to + * destroy the singleton early can prevent these crashes. + * + * An example where this is needed is for a LLSingleton that has an APR + * object as a member that makes APR calls on destruction. The APR system is + * shut down explicitly before main() exits. This causes a crash on exit. + * Using this method before the call to apr_terminate() and NOT calling + * getInstance() again will prevent the crash. + */ + static void deleteSingleton() + { + delete sData.mInstance; + sData.mInstance = NULL; + sData.mInitState = DELETED; + } + + static DERIVED_TYPE* getInstance() + { + switch (sData.mInitState) + { + case UNINITIALIZED: + logerrs("Uninitialized param singleton ", + demangle(typeid(DERIVED_TYPE).name()).c_str()); + return NULL; + + case CONSTRUCTING: + logerrs("Tried to access singleton ", + demangle(typeid(DERIVED_TYPE).name()).c_str(), + " from singleton constructor!"); + return NULL; + + case INITIALIZING: + logerrs("State not supported by ", + demangle(typeid(DERIVED_TYPE).name()).c_str(), + " since it is a parametric singleton!"); + break; + + case INITIALIZED: + break; + + case DELETED: + logerrs("Trying to access deleted param singleton ", + demangle(typeid(DERIVED_TYPE).name()).c_str()); + + 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().get_initializing(sData.mInstance), + sData.mInitState); + return sData.mInstance; + } + + // Reference version of getInstance() + // Preferred over getInstance() as it disallows checking for NULL + static DERIVED_TYPE& instance() + { + return *getInstance(); + } + + // Has this singleton been created yet? + // Use this to avoid accessing singletons before they can safely be constructed. + static bool instanceExists() + { + return sData.mInitState == INITIALIZED; + } + + // Has this singleton been deleted? This can be useful during shutdown + // processing to avoid "resurrecting" a singleton we thought we'd already + // cleaned up. + static bool wasDeleted() + { + return sData.mInitState == DELETED; + } + private: struct SingletonData { @@ -460,6 +631,9 @@ private: template typename LLSingleton::SingletonData LLSingleton::sData; +template +typename LLParamSingleton::SingletonData LLParamSingleton::sData; + /** * Use LLSINGLETON(Foo); at the start of an LLSingleton subclass body * when you want to declare an out-of-line constructor: @@ -510,4 +684,35 @@ private: \ /* LLSINGLETON() is carefully implemented to permit exactly this */ \ LLSINGLETON(DERIVED_CLASS) {} +/** +* Use LLPARAMSINGLETON(Foo); at the start of an LLParamSingleton subclass body +* when you want to declare an out-of-line constructor: +* +* @code +* class Foo: public LLParamSingleton +* { +* // use this macro at start of every LLSingleton subclass +* LLPARAMSINGLETON(Foo); +* public: +* // ... +* }; +* // ... +* [inline] +* Foo::Foo() { ... } +* @endcode +* +* Unfortunately, this mechanism does not permit you to define even a simple +* (but nontrivial) constructor within the class body. Use LLPARAMSINGLETON() +* and define the constructor outside the class body. If you must define it +* in a header file, use 'inline' (unless it's a template class) to avoid +* duplicate-symbol errors at link time. +*/ +#define LLPARAMSINGLETON(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 LLParamSingleton; \ + DERIVED_CLASS(__VA_ARGS__) + #endif -- cgit v1.2.3 From adb3f447b33e42bdb6e4a5a1ac79eebd862dafb4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 10 Aug 2019 20:33:59 -0400 Subject: DRTVWR-493: Introduce test catch_what(), catch_llerrs() functions. Use them in place of awkward try/catch test boilerplate. --- indra/llcommon/tests/lleventcoro_test.cpp | 33 ++++------ indra/llcommon/tests/lleventdispatcher_test.cpp | 83 +++++++------------------ indra/llcommon/tests/lleventfilter_test.cpp | 12 +--- indra/llcommon/tests/llinstancetracker_test.cpp | 33 +++------- indra/llcommon/tests/lllazy_test.cpp | 13 ++-- indra/llcommon/tests/llleap_test.cpp | 20 +++--- indra/llcommon/tests/llprocess_test.cpp | 16 ++--- indra/llcommon/tests/wrapllerrs.h | 8 +++ 8 files changed, 70 insertions(+), 148 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index a459d17fb8..ea198849cd 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -506,16 +506,10 @@ namespace tut replyName = waiter.getName0(); errorName = waiter.getName1(); WrapLLErrs capture; - try - { - result = waiter.suspendWithLog(); - debug("no exception"); - } - catch (const WrapLLErrs::FatalException& e) - { - debug(STRINGIZE("exception " << e.what())); - threw = e.what(); - } + threw = catch_llerrs([&waiter, &debug](){ + result = waiter.suspendWithLog(); + debug("no exception"); + }); } END } @@ -762,18 +756,13 @@ namespace tut { LLCoroEventPumps waiter; WrapLLErrs capture; - try - { - result = waiter.postAndSuspendWithLog( - LLSDMap("value", 31)("fail", LLSD()), - immediateAPI.getPump(), "reply", "error"); - debug("no exception"); - } - catch (const WrapLLErrs::FatalException& e) - { - debug(STRINGIZE("exception " << e.what())); - threw = e.what(); - } + threw = catch_llerrs( + [&waiter, &debug](){ + result = waiter.postAndSuspendWithLog( + LLSDMap("value", 31)("fail", LLSD()), + immediateAPI.getPump(), "reply", "error"); + debug("no exception"); + }); } END } diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 5a4df81bf1..a181d5c941 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -22,6 +22,7 @@ #include "llsdutil.h" #include "stringize.h" #include "tests/wrapllerrs.h" +#include "../test/catch_and_store_what_in.h" #include #include @@ -630,16 +631,9 @@ namespace tut void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) { - std::string threw; - try - { - work(func, args); - } - catch (const std::runtime_error& e) - { - cout << "*** " << e.what() << '\n'; - threw = e.what(); - } + std::string threw = catch_what([this, &func, &args](){ + work(func, args); + }); ensure_has(threw, exc_frag); } @@ -717,15 +711,9 @@ namespace tut LLSD attempts(LLSDArray(17)(LLSDMap("pi", 3.14)("two", 2))); foreach(LLSD ae, inArray(attempts)) { - std::string threw; - try - { - work.add("freena_err", "freena", freena, ae); - } - catch (const std::exception& e) - { - threw = e.what(); - } + std::string threw = catch_what([this, &ae](){ + work.add("freena_err", "freena", freena, ae); + }); ensure_has(threw, "must be an array"); } } @@ -734,15 +722,9 @@ namespace tut void object::test<2>() { set_test_name("map-style registration with badly-formed defaults"); - std::string threw; - try - { - work.add("freena_err", "freena", freena, LLSDArray("a")("b"), 17); - } - catch (const std::exception& e) - { - threw = e.what(); - } + std::string threw = catch_what([this](){ + work.add("freena_err", "freena", freena, LLSDArray("a")("b"), 17); + }); ensure_has(threw, "must be a map or an array"); } @@ -750,17 +732,11 @@ namespace tut void object::test<3>() { set_test_name("map-style registration with too many array defaults"); - std::string threw; - try - { - work.add("freena_err", "freena", freena, - LLSDArray("a")("b"), - LLSDArray(17)(0.9)("gack")); - } - catch (const std::exception& e) - { - threw = e.what(); - } + std::string threw = catch_what([this](){ + work.add("freena_err", "freena", freena, + LLSDArray("a")("b"), + LLSDArray(17)(0.9)("gack")); + }); ensure_has(threw, "shorter than"); } @@ -768,17 +744,11 @@ namespace tut void object::test<4>() { set_test_name("map-style registration with too many map defaults"); - std::string threw; - try - { - work.add("freena_err", "freena", freena, - LLSDArray("a")("b"), - LLSDMap("b", 17)("foo", 3.14)("bar", "sinister")); - } - catch (const std::exception& e) - { - threw = e.what(); - } + std::string threw = catch_what([this](){ + work.add("freena_err", "freena", freena, + LLSDArray("a")("b"), + LLSDMap("b", 17)("foo", 3.14)("bar", "sinister")); + }); ensure_has(threw, "nonexistent params"); ensure_has(threw, "foo"); ensure_has(threw, "bar"); @@ -1039,16 +1009,9 @@ namespace tut // We don't have a comparable helper function for the one-arg // operator() method, and it's not worth building one just for this // case. Write it out. - std::string threw; - try - { - work(LLSDMap("op", "freek")); - } - catch (const std::runtime_error& e) - { - cout << "*** " << e.what() << "\n"; - threw = e.what(); - } + std::string threw = catch_what([this](){ + work(LLSDMap("op", "freek")); + }); ensure_has(threw, "bad"); ensure_has(threw, "op"); ensure_has(threw, "freek"); diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp index eb98b12ef5..a23abf453e 100644 --- a/indra/llcommon/tests/lleventfilter_test.cpp +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -350,15 +350,9 @@ namespace tut // Now let the timer expire. filter.forceTimeout(); // Notice the timeout. - std::string threw; - try - { - mainloop.post(17); - } - catch (const WrapLLErrs::FatalException& e) - { - threw = e.what(); - } + std::string threw = catch_llerrs([this](){ + mainloop.post(17); + }); ensure_contains("errorAfter() timeout exception", threw, "timeout"); // Timing out cancels the timer. Verify that. listener0.reset(0); diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index c7d4b8a06b..0fe65f0831 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -198,14 +198,9 @@ namespace tut { WrapLLErrs wrapper; Keyed::instance_iter i(Keyed::beginInstances()); - try - { - delete keyed; - } - catch (const WrapLLErrs::FatalException& e) - { - what = e.what(); - } + what = catch_llerrs([&keyed](){ + delete keyed; + }); } ensure(! what.empty()); } @@ -219,14 +214,9 @@ namespace tut { WrapLLErrs wrapper; Keyed::key_iter i(Keyed::beginKeys()); - try - { - delete keyed; - } - catch (const WrapLLErrs::FatalException& e) - { - what = e.what(); - } + what = catch_llerrs([&keyed](){ + delete keyed; + }); } ensure(! what.empty()); } @@ -240,14 +230,9 @@ namespace tut { WrapLLErrs wrapper; Unkeyed::instance_iter i(Unkeyed::beginInstances()); - try - { - delete unkeyed; - } - catch (const WrapLLErrs::FatalException& e) - { - what = e.what(); - } + what = catch_llerrs([&unkeyed](){ + delete unkeyed; + }); } ensure(! what.empty()); } diff --git a/indra/llcommon/tests/lllazy_test.cpp b/indra/llcommon/tests/lllazy_test.cpp index 32a717f4fc..542306ee22 100644 --- a/indra/llcommon/tests/lllazy_test.cpp +++ b/indra/llcommon/tests/lllazy_test.cpp @@ -38,6 +38,7 @@ #include // other Linden headers #include "../test/lltut.h" +#include "../test/catch_and_store_what_in.h" namespace bll = boost::lambda; @@ -200,15 +201,9 @@ namespace tut void lllazy_object::test<2>() { TestNeedsTesting tnt; - std::string threw; - try - { - tnt.toolate(); - } - catch (const LLLazyCommon::InstanceChange& e) - { - threw = e.what(); - } + std::string threw = catch_what([&tnt](){ + tnt.toolate(); + }); ensure_contains("InstanceChange exception", threw, "replace LLLazy instance"); } diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 45648536c4..bf0a74d10d 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -23,7 +23,7 @@ #include "../test/lltut.h" #include "../test/namedtempfile.h" #include "../test/catch_and_store_what_in.h" -#include "wrapllerrs.h" +#include "wrapllerrs.h" // CaptureLog #include "llevents.h" #include "llprocess.h" #include "llstring.h" @@ -290,12 +290,9 @@ namespace tut void object::test<6>() { set_test_name("empty plugin vector"); - std::string threw; - try - { - LLLeap::create("empty", StringVec()); - } - CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error) + std::string threw = catch_what([](){ + LLLeap::create("empty", StringVec()); + }); ensure_contains("LLLeap::Error", threw, "no plugin"); // try the suppress-exception variant ensure("bad launch returned non-NULL", ! LLLeap::create("empty", StringVec(), false)); @@ -308,12 +305,9 @@ namespace tut // Synthesize bogus executable name std::string BADPYTHON(PYTHON.substr(0, PYTHON.length()-1) + "x"); CaptureLog log; - std::string threw; - try - { - LLLeap::create("bad exe", BADPYTHON); - } - CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error) + std::string threw = catch_what([&BADPYTHON](){ + LLLeap::create("bad exe", BADPYTHON); + }); ensure_contains("LLLeap::create() didn't throw", threw, "failed"); log.messageWith("failed"); log.messageWith(BADPYTHON); diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 5c87cdabd9..222d832084 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -25,8 +25,6 @@ #include #include #include -//#include -//#include // other Linden headers #include "../test/lltut.h" #include "../test/namedtempfile.h" @@ -35,7 +33,7 @@ #include "llsdutil.h" #include "llevents.h" #include "llstring.h" -#include "wrapllerrs.h" +#include "wrapllerrs.h" // CaptureLog #if defined(LL_WINDOWS) #define sleep(secs) _sleep((secs) * 1000) @@ -45,8 +43,7 @@ #include #endif -//namespace lambda = boost::lambda; - std::string apr_strerror_helper(apr_status_t rv) +std::string apr_strerror_helper(apr_status_t rv) { char errbuf[256]; apr_strerror(rv, errbuf, sizeof(errbuf)); @@ -960,12 +957,9 @@ namespace tut #define CATCH_IN(THREW, EXCEPTION, CODE) \ do \ { \ - (THREW).clear(); \ - try \ - { \ - CODE; \ - } \ - CATCH_AND_STORE_WHAT_IN(THREW, EXCEPTION) \ + (THREW) = catch_what([&](){ \ + CODE; \ + }); \ ensure("failed to throw " #EXCEPTION ": " #CODE, ! (THREW).empty()); \ } while (0) diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 08fbf19b1c..3fb79f6b5d 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -37,6 +37,7 @@ #include "llerrorcontrol.h" #include "llexception.h" #include "stringize.h" +#include "../test/catch_and_store_what_in.h" #include #include #include @@ -86,6 +87,13 @@ struct WrapLLErrs LLError::FatalFunction mPriorFatal; }; +/// Convenience wrapper for catch_what() +template +std::string catch_llerrs(FUNC func) +{ + return catch_what(func); +} + /** * Capture log messages. This is adapted (simplified) from the one in * llerror_test.cpp. -- cgit v1.2.3 From f0fa4f94a5400fe5d21cf5bf7570129916bf9787 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Aug 2019 08:26:51 -0400 Subject: DRTVWR-493: Make catch_llerrs() a member of WrapLLErrs. --- indra/llcommon/tests/lleventcoro_test.cpp | 4 ++-- indra/llcommon/tests/lleventfilter_test.cpp | 2 +- indra/llcommon/tests/llinstancetracker_test.cpp | 6 ++--- indra/llcommon/tests/wrapllerrs.h | 32 +++++++++++++++++++------ 4 files changed, 31 insertions(+), 13 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index ea198849cd..fa02d2bb1a 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -506,7 +506,7 @@ namespace tut replyName = waiter.getName0(); errorName = waiter.getName1(); WrapLLErrs capture; - threw = catch_llerrs([&waiter, &debug](){ + threw = capture.catch_llerrs([&waiter, &debug](){ result = waiter.suspendWithLog(); debug("no exception"); }); @@ -756,7 +756,7 @@ namespace tut { LLCoroEventPumps waiter; WrapLLErrs capture; - threw = catch_llerrs( + threw = capture.catch_llerrs( [&waiter, &debug](){ result = waiter.postAndSuspendWithLog( LLSDMap("value", 31)("fail", LLSD()), diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp index a23abf453e..1875013794 100644 --- a/indra/llcommon/tests/lleventfilter_test.cpp +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -350,7 +350,7 @@ namespace tut // Now let the timer expire. filter.forceTimeout(); // Notice the timeout. - std::string threw = catch_llerrs([this](){ + std::string threw = capture.catch_llerrs([this](){ mainloop.post(17); }); ensure_contains("errorAfter() timeout exception", threw, "timeout"); diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index 0fe65f0831..d94fc0c56d 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -198,7 +198,7 @@ namespace tut { WrapLLErrs wrapper; Keyed::instance_iter i(Keyed::beginInstances()); - what = catch_llerrs([&keyed](){ + what = wrapper.catch_llerrs([&keyed](){ delete keyed; }); } @@ -214,7 +214,7 @@ namespace tut { WrapLLErrs wrapper; Keyed::key_iter i(Keyed::beginKeys()); - what = catch_llerrs([&keyed](){ + what = wrapper.catch_llerrs([&keyed](){ delete keyed; }); } @@ -230,7 +230,7 @@ namespace tut { WrapLLErrs wrapper; Unkeyed::instance_iter i(Unkeyed::beginInstances()); - what = catch_llerrs([&unkeyed](){ + what = wrapper.catch_llerrs([&unkeyed](){ delete unkeyed; }); } diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 3fb79f6b5d..b07d5afbd8 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -82,18 +82,36 @@ struct WrapLLErrs LLTHROW(FatalException(message)); } + /// Convenience wrapper for catch_what() + // + // The implementation makes it clear that this function need not be a + // member; it could easily be a free function. It is a member because it + // makes no sense to attempt to catch FatalException unless there is a + // WrapLLErrs instance in scope. Without a live WrapLLErrs instance, any + // LL_ERRS() reached by code within 'func' would terminate the test + // program instead of throwing FatalException. + // + // We were tempted to introduce a free function, likewise accepting + // arbitrary 'func', that would instantiate WrapLLErrs and then call + // catch_llerrs() on that instance. We decided against it, for this + // reason: on extending a test function containing a single call to that + // free function, a maintainer would most likely make additional calls to + // that free function, instead of switching to an explicit WrapLLErrs + // declaration with several calls to its catch_llerrs() member function. + // Even a construct such as WrapLLErrs().catch_llerrs(...) would make the + // object declaration more visible; it's not unreasonable to expect a + // maintainer to extend that by naming and reusing the WrapLLErrs instance. + template + std::string catch_llerrs(FUNC func) + { + return catch_what(func); + } + std::string error; LLError::SettingsStoragePtr mPriorErrorSettings; LLError::FatalFunction mPriorFatal; }; -/// Convenience wrapper for catch_what() -template -std::string catch_llerrs(FUNC func) -{ - return catch_what(func); -} - /** * Capture log messages. This is adapted (simplified) from the one in * llerror_test.cpp. -- cgit v1.2.3 From 98be6e141c1232bad28cc115bc7092f175b18809 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Aug 2019 09:44:56 -0400 Subject: DRTVWR-493: Streamline LLParamSingleton, LLLockedSingleton. Simplify LLSingleton::SingletonLifetimeManager to SingletonInitializer: that struct has not been responsible for deletion ever since LLSingletonBase acquired dependency-ordered deleteAll(). Move SingletonData::mInitState changes from SingletonLifetimeManager to constructSingleton() method. Similarly, constructSingleton() now sets SingletonData::mInstance instead of making its caller store the pointer. Add variadic arguments to LLSingleton::constructSingleton() so we can reuse it for LLParamSingleton. Add finishInitializing() method to encapsulate logic reused for getInstance()'s INITIALIZING and DELETED cases. Make LLParamSingleton a subclass of LLSingleton, just as LLLockedSingleton is a subclass of LLParamSingleton. Make LLParamSingleton a friend of LLSingleton, so it can access private members of LLSingleton without also granting access to any DERIVED_CLASS subclass. This eliminates the need for protected getInitState(). LLParamSingleton::initParamSingleton() reuses LLSingleton::constructSingleton() and finishInitializing(). Its getInstance() method completely replaces LLSingleton::getInstance(): in most EInitStates, LLParamSingleton::getInstance() is an error. Use a std::mutex to serialize calls to LLParamSingleton::initParamSingleton() and getInstance(). While LLSingleton::getInstance() relies on the "initialized exactly once" guarantee for block-scope static declarations, LLParamSingleton cannot rely on the same mechanism. LLLockedSingleton is now a very succinct subclass of LLParamSingleton -- they have very similar functionality. Giving the LLSINGLETON() macro variadic arguments eliminates the need for a separate LLPARAMSINGLETON() macro, while continuing to support existing usage. --- indra/llcommon/llsingleton.h | 401 +++++++++++++++++++------------------------ 1 file changed, 172 insertions(+), 229 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index b127f4f529..38d5af5309 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -31,6 +31,18 @@ #include #include +#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 + +#if LL_WINDOWS +#pragma warning (pop) +#endif + class LLSingletonBase: private boost::noncopyable { public: @@ -205,6 +217,10 @@ LLSingletonBase::LLSingletonBase(tag): LLSingleton_manage_master().push_initializing(this); } +// forward declare for friend directive within LLSingleton +template +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 +286,41 @@ template 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; + + // 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 + static void constructSingleton(Args&&... args) { - return new DERIVED_TYPE(); + sData.mInitState = CONSTRUCTING; + sData.mInstance = new DERIVED_TYPE(std::forward(args)...); + sData.mInitState = INITIALIZING; + } + + static void finishInitializing() + { + // 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().pop_initializing(sData.mInstance); + + // The remaining 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().get_initializing(sData.mInstance), + sData.mInitState); } // We know of no way to instruct the compiler that every subclass @@ -285,34 +333,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() + SingletonInitializer() { - sData.mInitState = CONSTRUCTING; - sData.mInstance = constructSingleton(); - sData.mInitState = INITIALIZING; - } - - ~SingletonLifetimeManager() - { - // 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(); } }; @@ -369,7 +400,8 @@ public: static DERIVED_TYPE* getInstance() { - static SingletonLifetimeManager sLifeTimeMgr; + // call constructSingleton() only the first time we get here + static SingletonInitializer sInitializer; switch (sData.mInitState) { @@ -380,47 +412,33 @@ public: 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(), " 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().pop_initializing(sData.mInstance); + // first time through: set to INITIALIZING by + // constructSingleton(), called by sInitializer's constructor + finishInitializing(); break; case INITIALIZED: + // normal subsequent calls break; case DELETED: + // called after deleteSingleton() logwarns("Trying to access deleted singleton ", demangle(typeid(DERIVED_TYPE).name()).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().pop_initializing(sData.mInstance); + 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().get_initializing(sData.mInstance), - sData.mInitState); return sData.mInstance; } @@ -446,12 +464,6 @@ public: return sData.mInitState == DELETED; } -protected: - static EInitState getInitState() - { - return sData.mInitState; - } - private: struct SingletonData { @@ -463,176 +475,138 @@ private: static SingletonData sData; }; +template +typename LLSingleton::SingletonData LLSingleton::sData; + +/** + * LLParamSingleton is like LLSingleton, 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. + * * 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 + * instance() or getInstance() call after deleteSingleton(). + * + * Importantly, though, each LLParamSingleton subclass does participate in the + * dependency-ordered LLSingletonBase::deleteAll() processing. + */ template -class LLParamSingleton : public LLSingletonBase +class LLParamSingleton : public LLSingleton { private: - - template - static DERIVED_TYPE* constructSingleton(Args&&... args) - { - return new DERIVED_TYPE(std::forward(args)...); - } - - // We know of no way to instruct the compiler that every subclass - // constructor MUST be private. - // However, we can make the LLPARAMSINGLETON() macro both declare - // a private constructor and provide the required friend declaration. - // How can we ensure that every subclass uses LLPARAMSINGLETON()? - // By making that macro provide a definition for this pure virtual - // method. If you get "can't instantiate class due to missing pure - // virtual method" for this method, then add LLPARAMSINGLETON(yourclass) - // in the subclass body. - virtual void you_must_use_LLSINGLETON_macro() = 0; - -protected: - // Pass DERIVED_TYPE explicitly to LLSingletonBase's constructor because, - // until our subclass constructor completes, *this isn't yet a - // full-fledged DERIVED_TYPE. - LLParamSingleton() : LLSingletonBase(LLSingletonBase::tag()) - { - // populate base-class function pointer with the static - // deleteSingleton() function for this particular specialization - mDeleteSingleton = &deleteSingleton; - - // add this new instance to the master list - LLSingleton_manage_master().add(this); - } + typedef LLSingleton super; public: + using super::deleteSingleton; + using super::instance; + using super::instanceExists; + using super::wasDeleted; - virtual ~LLParamSingleton() - { - // remove this instance from the master list - LLSingleton_manage_master().remove(this); - sData.mInstance = NULL; - sData.mInitState = DELETED; - } - - // Passes arguments to DERIVED_TYPE's constructor and sets apropriate states + // Passes arguments to DERIVED_TYPE's constructor and sets appropriate states template static void initParamSingleton(Args&&... args) { - sData.mInitState = CONSTRUCTING; - sData.mInstance = constructSingleton(std::forward(args)...); - 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().pop_initializing(sData.mInstance); - } - - /** - * @brief Immediately delete the singleton. - * - * A subsequent call to LLProxy::getInstance() will construct a new - * instance of the class. - * - * Without an explicit call to LLSingletonBase::deleteAll(), LLSingletons - * are implicitly destroyed after main() has exited and the C++ runtime is - * cleaning up statically-constructed objects. Some classes derived from - * LLSingleton have objects that are part of a runtime system that is - * terminated before main() exits. Calling the destructor of those objects - * after the termination of their respective systems can cause crashes and - * other problems during termination of the project. Using this method to - * destroy the singleton early can prevent these crashes. - * - * An example where this is needed is for a LLSingleton that has an APR - * object as a member that makes APR calls on destruction. The APR system is - * shut down explicitly before main() exits. This causes a crash on exit. - * Using this method before the call to apr_terminate() and NOT calling - * getInstance() again will prevent the crash. - */ - static void deleteSingleton() - { - delete sData.mInstance; - sData.mInstance = NULL; - sData.mInitState = DELETED; + // 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 lk(mMutex); + // For organizational purposes this function shouldn't be called twice + if (super::sData.mInitState != super::UNINITIALIZED) + { + super::logerrs("Tried to initialize singleton ", + super::demangle(typeid(DERIVED_TYPE).name()).c_str(), + " twice!"); + } + else + { + super::constructSingleton(std::forward(args)...); + super::finishInitializing(); + } } static DERIVED_TYPE* getInstance() { - switch (sData.mInitState) + // In case racing threads call getInstance() at the same moment as + // initParamSingleton(), serialize the calls. + std::unique_lock lk(mMutex); + + switch (super::sData.mInitState) { - case UNINITIALIZED: - logerrs("Uninitialized param singleton ", - demangle(typeid(DERIVED_TYPE).name()).c_str()); - return NULL; + case super::UNINITIALIZED: + super::logerrs("Uninitialized param singleton ", + super::demangle(typeid(DERIVED_TYPE).name()).c_str()); + break; - case CONSTRUCTING: - logerrs("Tried to access singleton ", - demangle(typeid(DERIVED_TYPE).name()).c_str(), + case super::CONSTRUCTING: + super::logerrs("Tried to access param singleton ", + super::demangle(typeid(DERIVED_TYPE).name()).c_str(), " from singleton constructor!"); - return NULL; - - case INITIALIZING: - logerrs("State not supported by ", - demangle(typeid(DERIVED_TYPE).name()).c_str(), - " since it is a parametric singleton!"); break; - case INITIALIZED: + case super::INITIALIZING: + super::logerrs("Tried to access param singleton ", + super::demangle(typeid(DERIVED_TYPE).name()).c_str(), + " from initSingleton() method!"); break; - case DELETED: - logerrs("Trying to access deleted param singleton ", - demangle(typeid(DERIVED_TYPE).name()).c_str()); + case super::INITIALIZED: + return super::sData.mInstance; + case super::DELETED: + super::logerrs("Trying to access deleted param singleton ", + super::demangle(typeid(DERIVED_TYPE).name()).c_str()); 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().get_initializing(sData.mInstance), - sData.mInitState); - return sData.mInstance; - } - - // Reference version of getInstance() - // Preferred over getInstance() as it disallows checking for NULL - static DERIVED_TYPE& instance() - { - return *getInstance(); - } - - // Has this singleton been created yet? - // Use this to avoid accessing singletons before they can safely be constructed. - static bool instanceExists() - { - return sData.mInitState == INITIALIZED; - } - - // Has this singleton been deleted? This can be useful during shutdown - // processing to avoid "resurrecting" a singleton we thought we'd already - // cleaned up. - static bool wasDeleted() - { - return sData.mInitState == DELETED; + // should never actually get here; this is to pacify the compiler, + // which assumes control might return from logerrs() + return nullptr; } private: - struct SingletonData - { - // explicitly has a default constructor so that member variables are zero initialized in BSS - // and only changed by singleton logic, not constructor running during startup - EInitState mInitState; - DERIVED_TYPE* mInstance; - }; - static SingletonData sData; + static std::mutex mMutex; }; template -typename LLSingleton::SingletonData LLSingleton::sData; +typename std::mutex LLParamSingleton::mMutex; -template -typename LLParamSingleton::SingletonData LLParamSingleton::sData; +/** + * 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 +class LLLockedSingleton : public LLParamSingleton
+{ + typedef LLParamSingleton
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 subclass body @@ -658,13 +632,13 @@ typename LLParamSingleton::SingletonData LLParamSingleton::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(__VA_ARGS__) /** * Use LLSINGLETON_EMPTY_CTOR(Foo); at the start of an LLSingleton @@ -684,35 +658,4 @@ private: \ /* LLSINGLETON() is carefully implemented to permit exactly this */ \ LLSINGLETON(DERIVED_CLASS) {} -/** -* Use LLPARAMSINGLETON(Foo); at the start of an LLParamSingleton subclass body -* when you want to declare an out-of-line constructor: -* -* @code -* class Foo: public LLParamSingleton -* { -* // use this macro at start of every LLSingleton subclass -* LLPARAMSINGLETON(Foo); -* public: -* // ... -* }; -* // ... -* [inline] -* Foo::Foo() { ... } -* @endcode -* -* Unfortunately, this mechanism does not permit you to define even a simple -* (but nontrivial) constructor within the class body. Use LLPARAMSINGLETON() -* and define the constructor outside the class body. If you must define it -* in a header file, use 'inline' (unless it's a template class) to avoid -* duplicate-symbol errors at link time. -*/ -#define LLPARAMSINGLETON(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 LLParamSingleton; \ - DERIVED_CLASS(__VA_ARGS__) - #endif -- cgit v1.2.3 From 3c552696bf8704e30c1525a4f9d4b3dd09034820 Mon Sep 17 00:00:00 2001 From: andreykproductengine Date: Mon, 12 Aug 2019 22:56:15 +0300 Subject: DRTVWR-493 LLWearableType to LLParamSingleton --- indra/llcommon/llsingleton.h | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 38d5af5309..9be02f7e7d 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -530,6 +530,7 @@ public: else { super::constructSingleton(std::forward(args)...); + lk.unlock(); super::finishInitializing(); } } -- cgit v1.2.3 From 4fce6dc4353dbf9ccd3c9c3aced89df72a4f3abd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Aug 2019 17:35:45 -0400 Subject: 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. --- indra/llcommon/llsingleton.h | 30 +++---- indra/llcommon/tests/llsingleton_test.cpp | 129 +++++++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 15 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 38d5af5309..03b7d5a349 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -491,9 +491,6 @@ typename LLSingleton::SingletonData LLSingleton::sData; * * However, distinct initParamSingleton() calls can be used to engage * different constructors, as long as only one such call is executed at * 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 * instance() or getInstance() call after deleteSingleton(). * @@ -508,7 +505,6 @@ private: public: using super::deleteSingleton; - using super::instance; using super::instanceExists; using super::wasDeleted; @@ -519,7 +515,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 lk(mMutex); + std::unique_lock lk(mMutex); // For organizational purposes this function shouldn't be called twice if (super::sData.mInitState != super::UNINITIALIZED) { @@ -538,7 +534,7 @@ public: { // In case racing threads call getInstance() at the same moment as // initParamSingleton(), serialize the calls. - std::unique_lock lk(mMutex); + std::unique_lock lk(mMutex); switch (super::sData.mInitState) { @@ -550,15 +546,10 @@ public: case super::CONSTRUCTING: super::logerrs("Tried to access param singleton ", super::demangle(typeid(DERIVED_TYPE).name()).c_str(), - " from singleton constructor!"); + " from singleton constructor!"); break; 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: return super::sData.mInstance; @@ -573,12 +564,23 @@ public: 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: - 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 std::mutex LLParamSingleton::mMutex; +typename std::recursive_mutex LLParamSingleton::mMutex; /** * Initialization locked singleton, only derived class can decide when to initialize. diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index 56886bc73f..da7bc6355c 100644 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -29,7 +29,8 @@ #include "llsingleton.h" #include "../test/lltut.h" - +#include "wrapllerrs.h" +#include "llsd.h" // Capture execution sequence by appending to log string. std::string sLog; @@ -198,4 +199,130 @@ namespace tut TESTS(A, B, 4, 5, 6, 7) TESTS(B, A, 8, 9, 10, 11) + +#define PARAMSINGLETON(cls) \ + class cls: public LLParamSingleton \ + { \ + 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 + { + 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 + { + 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()); + } } -- cgit v1.2.3 From 5196af8663653fb345394733bf4bf88185a7565a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Aug 2019 17:47:48 -0400 Subject: DRTVWR-493: Rely on recursive_mutex to handle circularity from LLParamSingleton::initSingleton(). --- indra/llcommon/llsingleton.h | 1 - 1 file changed, 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 596a2388a1..03b7d5a349 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -526,7 +526,6 @@ public: else { super::constructSingleton(std::forward(args)...); - lk.unlock(); super::finishInitializing(); } } -- cgit v1.2.3 From bc5d79e06e1664abd33fbe41e5ec3655f33b7e32 Mon Sep 17 00:00:00 2001 From: andreykproductengine Date: Tue, 13 Aug 2019 21:46:12 +0300 Subject: DRTVWR-493 Test fix for W64 --- indra/llcommon/tests/llsingleton_test.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index da7bc6355c..75ddff9d7d 100644 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -311,7 +311,11 @@ namespace tut virtual void initSingleton() { // never mind indirection, just go straight for the circularity - (void)instance(); + CircularPInit *pt = getInstance(); + if (!pt) + { + throw; + } } }; -- cgit v1.2.3 From 0e6a6c7775816a338801c1a74a5741b2eabe801b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 14 Aug 2019 13:57:13 -0400 Subject: DRTVWR-493: Work around static initialization order problem. LLParamSingleton contained a static member mutex. Unfortunately that wasn't guaranteed to be initialized by the time its getInstance() was entered. Use a function-local static instead. --- indra/llcommon/llsingleton.h | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 03b7d5a349..e0d75ed72a 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -502,6 +502,10 @@ class LLParamSingleton : public LLSingleton { private: typedef LLSingleton 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; @@ -515,7 +519,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 lk(mMutex); + std::unique_lock lk(getMutex()); // For organizational purposes this function shouldn't be called twice if (super::sData.mInitState != super::UNINITIALIZED) { @@ -534,7 +538,7 @@ public: { // In case racing threads call getInstance() at the same moment as // initParamSingleton(), serialize the calls. - std::unique_lock lk(mMutex); + std::unique_lock lk(getMutex()); switch (super::sData.mInitState) { @@ -573,15 +577,30 @@ public: } private: - // 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; + // 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 -- but a default-constructed + // std::atomic 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; + } }; -template -typename std::recursive_mutex LLParamSingleton::mMutex; - /** * Initialization locked singleton, only derived class can decide when to initialize. * Starts locked. @@ -634,7 +653,7 @@ public: * 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 */ \ -- cgit v1.2.3 From 310db14beefc29ee72e0d13f0cb63cb2958ebf68 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 19 Aug 2019 11:44:56 -0400 Subject: DRTVWR-493: Improve exception safety of LLSingleton initialization. Add try/catch clauses to constructSingleton() (to catch exceptions in the subclass constructor) and finishInitializing() (to catch exceptions in the subclass initSingleton() method). Each of these catch clauses rethrows the exception -- they're for cleanup, not for ultimate handling. Introduce LLSingletonBase::reset_initializing(list_t::size_t). The idea is that since we can't know whether the exception happened before or after the push_initializing() call in LLSingletonBase's constructor, we can't just pop the stack. Instead, constructSingleton() captures the stack size before attempting to construct the new LLSingleton subclass. On exception, it calls reset_initializing() to restore the stack to that size. Naturally that requires a corresponding LLSingleton_manage_master method, whose MasterList specialization is a no-op. finishInitializing()'s exception handling is a bit simpler because it has a constructed LLSingleton subclass instance in hand, therefore push_initializing() has definitely been called, therefore it can call pop_initializing(). Break out new static capture_dependency() method from finishInitializing() because, in the previous LLSingleton::getInstance() implementation, the logic now wrapped in capture_dependency() was reached even in the INITIALIZED case. TODO: Add a new EInitState to differentiate "have been constructed, now calling initSingleton()" from "fully initialized, normal case" -- in the latter control path we should not be calling capture_dependency(). The LLSingleton_manage_master specialization's get_initializing() function (which called get_initializing_from()) was potentially dangerous. get_initializing() is called by push_initializing(), which (in the general case) is called by LLSingletonBase's constructor. If somehow the MasterList's LLSingletonBase constructor ended up calling get_initializing(), it would have called get_initializing_from(), passing an LLSingletonBase which had not yet been constructed into the MasterList. In particular, its mInitializing map would not yet have been initialized at all. Since the MasterList must not, by design, depend on any other LLSingletons, LLSingleton_manage_master::get_initializing() need not return a list from the official mInitializing map anyway. It can, and should, and now does, return a static dummy list. That obviates get_initializing_from(), which is removed. That in turn means we no longer need to pass get_initializing() an LLSingletonBase*. Remove that parameter. --- indra/llcommon/llsingleton.cpp | 31 +++++++++++--- indra/llcommon/llsingleton.h | 97 +++++++++++++++++++++++++++++++++++------- 2 files changed, 106 insertions(+), 22 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index 9fbd78a000..adf72bf700 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -134,12 +134,6 @@ LLSingletonBase::list_t& LLSingletonBase::get_initializing() return LLSingletonBase::MasterList::instance().get_initializing_(); } -//static -LLSingletonBase::list_t& LLSingletonBase::get_initializing_from(MasterList* master) -{ - return master->get_initializing_(); -} - LLSingletonBase::~LLSingletonBase() {} void LLSingletonBase::push_initializing(const char* name) @@ -186,6 +180,31 @@ void LLSingletonBase::pop_initializing() log_initializing("Popping", typeid(*back).name()); } +void LLSingletonBase::reset_initializing(list_t::size_type size) +{ + // called for cleanup in case the LLSingleton subclass constructor throws + // an exception + + // The tricky thing about this, the reason we have a separate method + // instead of just calling pop_initializing(), is (hopefully remote) + // possibility that the exception happened *before* the + // 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()); + + while (list.size() > size) + { + list.pop_back(); + } + + // as in pop_initializing() + if (list.empty()) + { + MasterList::instance().cleanup_initializing_(); + } +} + //static void LLSingletonBase::log_initializing(const char* verb, const char* name) { diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index e0d75ed72a..de6990efd4 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -55,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 of master list, in dependency order. typedef std::vector vec_t; static vec_t dep_sort(); @@ -112,6 +111,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); @@ -190,7 +192,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. @@ -201,9 +211,14 @@ struct LLSingleton_manage_master 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; } }; @@ -213,7 +228,12 @@ LLSingletonBase::LLSingletonBase(tag): 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().push_initializing(this); } @@ -298,8 +318,27 @@ private: static void constructSingleton(Args&&... args) { sData.mInitState = CONSTRUCTING; - sData.mInstance = new DERIVED_TYPE(std::forward(args)...); - sData.mInitState = INITIALIZING; + auto prev_size = LLSingleton_manage_master().get_initializing().size(); + try + { + sData.mInstance = new DERIVED_TYPE(std::forward(args)...); + sData.mInitState = INITIALIZING; + } + catch (const std::exception& err) + { + logwarns("Error constructing ", demangle(typeid(DERIVED_TYPE).name()).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; + // 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. + LLSingleton_manage_master().reset_initializing(prev_size); + // propagate the exception + throw; + } } static void finishInitializing() @@ -310,16 +349,41 @@ private: // 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().pop_initializing(sData.mInstance); - - // The remaining 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. + try + { + sData.mInstance->initSingleton(); + + // pop this off stack of initializing singletons + LLSingleton_manage_master().pop_initializing(sData.mInstance); + + // record the dependency, if any + capture_dependency(); + } + catch (const std::exception& err) + { + logwarns("Error in ", demangle(typeid(DERIVED_TYPE).name()).c_str(), + "::initSingleton(): ", err.what()); + // pop this off stack of initializing singletons here, too + LLSingleton_manage_master().pop_initializing(sData.mInstance); + // and get rid of the instance entirely + deleteSingleton(); + // propagate the exception + throw; + } + } + + // 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().get_initializing(sData.mInstance), + LLSingleton_manage_master().get_initializing(), sData.mInitState); } @@ -427,6 +491,7 @@ public: case INITIALIZED: // normal subsequent calls + capture_dependency(); break; case DELETED: -- cgit v1.2.3 From 54b98cb8c1302a1da5779d8968e54e5be641e644 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Aug 2019 12:36:06 -0400 Subject: DRTVWR-493: Defend LL[Param]Singleton against ctor/init exceptions. An exception in the LLSingleton subclass constructor, or in its initSingleton() method, could leave the LLSingleton machinery in a bad state: the failing instance would remain in the MasterList, also on the stack of initializing LLSingletons. Catch exceptions in either and perform relevant cleanup. This problem is highlighted by test programs, in which LL_ERRS throws an exception rather than crashing the whole process. In the relevant catch clauses, clean up the initializing stack BEFORE logging. Otherwise we get tangled up recording bogus dependencies. Move capture_dependency() out of finishInitializing(): it must be called by every valid getInstance() call, both from LLSingleton and LLParamSingleton. Introduce new CONSTRUCTED EInitState value to distinguish "have called the constructor but not yet the initSingleton() method" from "currently within initSingleton() method." This is transient, but we execute the 'switch' on state within that moment. One could argue that the previous enum used INITIALIZING for current CONSTRUCTED, and INITIALIZED meant INITIALIZING too, but this is clearer. Introduce template LLSingletonBase::classname() helper methods to clarify verbose demangle(typeid(stuff).name()) calls. Similarly, introduce LLSingleton::pop_initializing() shorthand method. --- indra/llcommon/llsingleton.cpp | 40 +++++++++------ indra/llcommon/llsingleton.h | 111 +++++++++++++++++++++++++++-------------- 2 files changed, 99 insertions(+), 52 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index adf72bf700..88527a3d31 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -150,7 +150,7 @@ void LLSingletonBase::pop_initializing() if (list.empty()) { logerrs("Underflow in stack of currently-initializing LLSingletons at ", - demangle(typeid(*this).name()).c_str(), "::getInstance()"); + classname(this).c_str(), "::getInstance()"); } // Now we know list.back() exists: capture it @@ -172,8 +172,8 @@ void LLSingletonBase::pop_initializing() if (back != this) { logerrs("Push/pop mismatch in stack of currently-initializing LLSingletons: ", - demangle(typeid(*this).name()).c_str(), "::getInstance() trying to pop ", - demangle(typeid(*back).name()).c_str()); + classname(this).c_str(), "::getInstance() trying to pop ", + classname(back).c_str()); } // log AFTER popping so logging singletons don't cry circularity @@ -216,7 +216,7 @@ void LLSingletonBase::log_initializing(const char* verb, const char* name) ri != rend; ++ri) { LLSingletonBase* sb(*ri); - LL_CONT << ' ' << demangle(typeid(*sb).name()); + LL_CONT << ' ' << classname(sb); } LL_ENDL; } @@ -250,7 +250,7 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt // 'found' is an iterator; *found is an LLSingletonBase*; **found // is the actual LLSingletonBase instance. LLSingletonBase* foundp(*found); - out << demangle(typeid(*foundp).name()) << " -> "; + out << classname(foundp) << " -> "; } // We promise to capture dependencies from both the constructor // and the initSingleton() method, so an LLSingleton's instance @@ -264,7 +264,7 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt if (initState == CONSTRUCTING) { logerrs("LLSingleton circularity in Constructor: ", out.str().c_str(), - demangle(typeid(*this).name()).c_str(), ""); + classname(this).c_str(), ""); } else if (it_next == initializing.end()) { @@ -275,14 +275,14 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt // Example: LLNotifications singleton initializes default channels. // Channels register themselves with singleton once done. logdebugs("LLSingleton circularity: ", out.str().c_str(), - demangle(typeid(*this).name()).c_str(), ""); + classname(this).c_str(), ""); } else { // Actual circularity with other singleton (or single singleton is used extensively). // Dependency can be unclear. logwarns("LLSingleton circularity: ", out.str().c_str(), - demangle(typeid(*this).name()).c_str(), ""); + classname(this).c_str(), ""); } } else @@ -295,8 +295,8 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt if (current->mDepends.insert(this).second) { // only log the FIRST time we hit this dependency! - logdebugs(demangle(typeid(*current).name()).c_str(), - " depends on ", demangle(typeid(*this).name()).c_str()); + logdebugs(classname(current).c_str(), + " depends on ", classname(this).c_str()); } } } @@ -355,19 +355,19 @@ void LLSingletonBase::cleanupAll() sp->mCleaned = true; logdebugs("calling ", - demangle(typeid(*sp).name()).c_str(), "::cleanupSingleton()"); + classname(sp).c_str(), "::cleanupSingleton()"); try { sp->cleanupSingleton(); } catch (const std::exception& e) { - logwarns("Exception in ", demangle(typeid(*sp).name()).c_str(), + logwarns("Exception in ", classname(sp).c_str(), "::cleanupSingleton(): ", e.what()); } catch (...) { - logwarns("Unknown exception in ", demangle(typeid(*sp).name()).c_str(), + logwarns("Unknown exception in ", classname(sp).c_str(), "::cleanupSingleton()"); } } @@ -382,7 +382,7 @@ void LLSingletonBase::deleteAll() { // Capture the class name first: in case of exception, don't count on // being able to extract it later. - const std::string name = demangle(typeid(*sp).name()); + const std::string name = classname(sp); try { // Call static method through instance function pointer. @@ -459,7 +459,17 @@ void LLSingletonBase::logerrs(const char* p1, const char* p2, const char* p3, co log(LLError::LEVEL_ERROR, p1, p2, p3, p4); // The other important side effect of LL_ERRS() is // https://www.youtube.com/watch?v=OMG7paGJqhQ (emphasis on OMG) - LLError::crashAndLoop(std::string()); + std::ostringstream out; + out << p1 << p2 << p3 << p4; + auto crash{ LLError::getFatalFunction() }; + if (crash) + { + crash(out.str()); + } + else + { + LLError::crashAndLoop(out.str()); + } } std::string LLSingletonBase::demangle(const char* mangled) diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index de6990efd4..0da6d548ab 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -68,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 to pass to our template constructor. You can't explicitly @@ -129,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 + static std::string classname() { return demangle(typeid(T).name()); } + template + static std::string classname(T* ptr) { return demangle(typeid(*ptr).name()); } // Default methods in case subclass doesn't declare them. virtual void initSingleton() {} @@ -317,25 +322,28 @@ private: template static void constructSingleton(Args&&... args) { - sData.mInitState = CONSTRUCTING; auto prev_size = LLSingleton_manage_master().get_initializing().size(); + // getInstance() calls are from within constructor + sData.mInitState = CONSTRUCTING; try { sData.mInstance = new DERIVED_TYPE(std::forward(args)...); - sData.mInitState = INITIALIZING; + // we have called constructor, have not yet called initSingleton() + sData.mInitState = CONSTRUCTED; } catch (const std::exception& err) { - logwarns("Error constructing ", demangle(typeid(DERIVED_TYPE).name()).c_str(), + // 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().reset_initializing(prev_size); + logwarns("Error constructing ", classname().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; - // 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. - LLSingleton_manage_master().reset_initializing(prev_size); // propagate the exception throw; } @@ -343,28 +351,27 @@ private: static void finishInitializing() { - // 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 + // 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 - LLSingleton_manage_master().pop_initializing(sData.mInstance); - - // record the dependency, if any - capture_dependency(); + pop_initializing(); } catch (const std::exception& err) { - logwarns("Error in ", demangle(typeid(DERIVED_TYPE).name()).c_str(), + // 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().c_str(), "::initSingleton(): ", err.what()); - // pop this off stack of initializing singletons here, too - LLSingleton_manage_master().pop_initializing(sData.mInstance); // and get rid of the instance entirely deleteSingleton(); // propagate the exception @@ -372,6 +379,13 @@ private: } } + static void pop_initializing() + { + // route through LLSingleton_manage_master so we Do The Right Thing + // (namely, nothing) for MasterList + LLSingleton_manage_master().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; @@ -458,8 +472,7 @@ public: static void deleteSingleton() { delete sData.mInstance; - sData.mInstance = NULL; - sData.mInitState = DELETED; + // SingletonData state handled by destructor, above } static DERIVED_TYPE* getInstance() @@ -472,38 +485,46 @@ public: case UNINITIALIZED: // should never be uninitialized at this point logerrs("Uninitialized singleton ", - demangle(typeid(DERIVED_TYPE).name()).c_str()); + classname().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().c_str(), " from singleton constructor!"); return NULL; - case INITIALIZING: - // first time through: set to INITIALIZING by - // constructSingleton(), called by sInitializer's constructor + 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 - capture_dependency(); break; case DELETED: // called after deleteSingleton() logwarns("Trying to access deleted singleton ", - demangle(typeid(DERIVED_TYPE).name()).c_str(), + classname().c_str(), " -- creating new instance"); + // This recovery sequence is NOT thread-safe! We would need a + // recursive_mutex a la LLParamSingleton. constructSingleton(); finishInitializing(); break; } + // record the dependency, if any: check if we got here from another + // LLSingleton's constructor or initSingleton() method + capture_dependency(); return sData.mInstance; } @@ -589,7 +610,7 @@ public: if (super::sData.mInitState != super::UNINITIALIZED) { super::logerrs("Tried to initialize singleton ", - super::demangle(typeid(DERIVED_TYPE).name()).c_str(), + super::template classname().c_str(), " twice!"); } else @@ -609,22 +630,38 @@ public: { case super::UNINITIALIZED: super::logerrs("Uninitialized param singleton ", - super::demangle(typeid(DERIVED_TYPE).name()).c_str()); + super::template classname().c_str()); break; case super::CONSTRUCTING: super::logerrs("Tried to access param singleton ", - super::demangle(typeid(DERIVED_TYPE).name()).c_str(), + super::template classname().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().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::demangle(typeid(DERIVED_TYPE).name()).c_str()); + super::template classname().c_str()); break; } -- cgit v1.2.3 From 5b5eb55f0c999a55cd0df1dd983629967d447b6c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Aug 2019 12:48:21 -0400 Subject: DRTVWR-493: Clarify capturing LLError::getFatalFunction() in a var. VS 2013 thought we were storing an initialization-list. --- indra/llcommon/llsingleton.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index 88527a3d31..c45c144570 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -461,7 +461,7 @@ void LLSingletonBase::logerrs(const char* p1, const char* p2, const char* p3, co // https://www.youtube.com/watch?v=OMG7paGJqhQ (emphasis on OMG) std::ostringstream out; out << p1 << p2 << p3 << p4; - auto crash{ LLError::getFatalFunction() }; + auto crash = LLError::getFatalFunction(); if (crash) { crash(out.str()); -- cgit v1.2.3