diff options
Diffstat (limited to 'indra/llcommon')
27 files changed, 2306 insertions, 278 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 410a5819b3..d9ab7c1b38 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -40,8 +40,10 @@ set(llcommon_SOURCE_FILES llbase64.cpp llbitpack.cpp llcallbacklist.cpp + llcleanup.cpp llcommon.cpp llcommonutils.cpp + llcoro_get_id.cpp llcoros.cpp llcrc.cpp llcriticaldamp.cpp @@ -66,7 +68,9 @@ set(llcommon_SOURCE_FILES llformat.cpp llframetimer.cpp llheartbeat.cpp + llheteromap.cpp llinitparam.cpp + llinitdestroyclass.cpp llinstancetracker.cpp llleap.cpp llleaplistener.cpp @@ -135,8 +139,10 @@ set(llcommon_HEADER_FILES llbitpack.h llboost.h llcallbacklist.h + llcleanup.h llcommon.h llcommonutils.h + llcoro_get_id.h llcoros.h llcrc.h llcriticaldamp.h @@ -168,7 +174,9 @@ set(llcommon_HEADER_FILES llhandle.h llhash.h llheartbeat.h + llheteromap.h llindexedvector.h + llinitdestroyclass.h llinitparam.h llinstancetracker.h llkeythrottle.h @@ -185,6 +193,7 @@ set(llcommon_HEADER_FILES llmortician.h llnametable.h llpointer.h + llpounceable.h llpredicate.h llpreprocessor.h llpriqueuemap.h @@ -329,6 +338,8 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}") ## llexception_test.cpp isn't a regression test, and doesn't need to be run ## every build. It's to help a developer make implementation choices about diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index eb0699ad41..2c76f29020 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -48,6 +48,7 @@ #include "lleventtimer.h" #include "google_breakpad/exception_handler.h" #include "stringize.h" +#include "llcleanup.h" // // Signal handling @@ -177,7 +178,7 @@ LLApp::~LLApp() if(mExceptionHandler != 0) delete mExceptionHandler; - LLCommon::cleanupClass(); + SUBSYSTEM_CLEANUP(LLCommon); } // static diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index 5ae2df3994..4304db36be 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -63,8 +63,7 @@ struct AssetEntry : public LLDictionaryEntry class LLAssetDictionary : public LLSingleton<LLAssetDictionary>, public LLDictionary<LLAssetType::EType, AssetEntry> { -public: - LLAssetDictionary(); + LLSINGLETON(LLAssetDictionary); }; LLAssetDictionary::LLAssetDictionary() diff --git a/indra/llcommon/llcleanup.cpp b/indra/llcommon/llcleanup.cpp new file mode 100644 index 0000000000..c5283507bf --- /dev/null +++ b/indra/llcommon/llcleanup.cpp @@ -0,0 +1,29 @@ +/** + * @file llcleanup.cpp + * @author Nat Goodspeed + * @date 2016-08-30 + * @brief Implementation for llcleanup. + * + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llcleanup.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llerror.h" +#include "llerrorcontrol.h" + +void log_subsystem_cleanup(const char* file, int line, const char* function, + const char* classname) +{ + LL_INFOS("Cleanup") << LLError::abbreviateFile(file) << "(" << line << "): " + << "calling " << classname << "::cleanupClass() in " + << function << LL_ENDL; +} diff --git a/indra/llcommon/llcleanup.h b/indra/llcommon/llcleanup.h new file mode 100644 index 0000000000..a319171b5f --- /dev/null +++ b/indra/llcommon/llcleanup.h @@ -0,0 +1,33 @@ +/** + * @file llcleanup.h + * @author Nat Goodspeed + * @date 2015-05-20 + * @brief Mechanism for cleaning up subsystem resources + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Copyright (c) 2015, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLCLEANUP_H) +#define LL_LLCLEANUP_H + +#include <boost/current_function.hpp> + +// Instead of directly calling SomeClass::cleanupClass(), use +// SUBSYSTEM_CLEANUP(SomeClass); +// This logs the call as well as performing it. That gives us a baseline +// subsystem shutdown order against which to compare subsequent dynamic +// shutdown schemes. +#define SUBSYSTEM_CLEANUP(CLASSNAME) \ + do { \ + log_subsystem_cleanup(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, #CLASSNAME); \ + CLASSNAME::cleanupClass(); \ + } while (0) +// Use ancient do { ... } while (0) macro trick to permit a block of +// statements with the same syntax as a single statement. + +void log_subsystem_cleanup(const char* file, int line, const char* function, + const char* classname); + +#endif /* ! defined(LL_LLCLEANUP_H) */ diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp index 19642b0982..439ff4e628 100644 --- a/indra/llcommon/llcommon.cpp +++ b/indra/llcommon/llcommon.cpp @@ -31,6 +31,7 @@ #include "llthread.h" #include "lltrace.h" #include "lltracethreadrecorder.h" +#include "llcleanup.h" //static BOOL LLCommon::sAprInitialized = FALSE; @@ -63,11 +64,11 @@ void LLCommon::cleanupClass() sMasterThreadRecorder = NULL; LLTrace::set_master_thread_recorder(NULL); LLThreadSafeRefCount::cleanupThreadSafeRefCount(); - LLTimer::cleanupClass(); + SUBSYSTEM_CLEANUP(LLTimer); if (sAprInitialized) { ll_cleanup_apr(); sAprInitialized = FALSE; } - LLMemory::cleanupClass(); + SUBSYSTEM_CLEANUP(LLMemory); } diff --git a/indra/llcommon/llcoro_get_id.cpp b/indra/llcommon/llcoro_get_id.cpp new file mode 100644 index 0000000000..24ed1fe0c9 --- /dev/null +++ b/indra/llcommon/llcoro_get_id.cpp @@ -0,0 +1,32 @@ +/** + * @file llcoro_get_id.cpp + * @author Nat Goodspeed + * @date 2016-09-03 + * @brief Implementation for llcoro_get_id. + * + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llcoro_get_id.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llcoros.h" + +namespace llcoro +{ + +id get_id() +{ + // An instance of Current can convert to LLCoros::CoroData*, which can + // implicitly convert to void*, which is an llcoro::id. + return LLCoros::Current(); +} + +} // llcoro diff --git a/indra/llcommon/llcoro_get_id.h b/indra/llcommon/llcoro_get_id.h new file mode 100644 index 0000000000..4c1dca6f19 --- /dev/null +++ b/indra/llcommon/llcoro_get_id.h @@ -0,0 +1,30 @@ +/** + * @file llcoro_get_id.h + * @author Nat Goodspeed + * @date 2016-09-03 + * @brief Supplement the functionality in llcoro.h. + * + * This is broken out as a separate header file to resolve + * circularity: LLCoros isa LLSingleton, yet LLSingleton machinery + * requires llcoro::get_id(). + * + * Be very suspicious of anyone else #including this header. + * + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLCORO_GET_ID_H) +#define LL_LLCORO_GET_ID_H + +namespace llcoro +{ + +/// Get an opaque, distinct token for the running coroutine (or main). +typedef void* id; +id get_id(); + +} // llcoro + +#endif /* ! defined(LL_LLCORO_GET_ID_H) */ diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index 8e516d8beb..3ffce4810a 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -40,32 +40,79 @@ #include "stringize.h" #include "llexception.h" -// do nothing, when we need nothing done +namespace { +void no_op() {} +} // anonymous namespace + +// Do nothing, when we need nothing done. This is a static member of LLCoros +// because CoroData is a private nested class. void LLCoros::no_cleanup(CoroData*) {} // CoroData for the currently-running coroutine. Use a thread_specific_ptr // because each thread potentially has its own distinct pool of coroutines. -// This thread_specific_ptr does NOT own the CoroData object! That's owned by -// LLCoros::mCoros. It merely identifies it. For this reason we instantiate -// it with a no-op cleanup function. -boost::thread_specific_ptr<LLCoros::CoroData> -LLCoros::sCurrentCoro(LLCoros::no_cleanup); +LLCoros::Current::Current() +{ + // Use a function-static instance so this thread_specific_ptr is + // instantiated on demand. Since we happen to know it's consumed by + // LLSingleton, this is likely to happen before the runtime has finished + // initializing module-static data. For the same reason, we can't package + // this pointer in an LLSingleton. + + // This thread_specific_ptr does NOT own the CoroData object! That's owned + // by LLCoros::mCoros. It merely identifies it. For this reason we + // instantiate it with a no-op cleanup function. + static boost::thread_specific_ptr<LLCoros::CoroData> sCurrent(LLCoros::no_cleanup); + + // If this is the first time we're accessing sCurrent for the running + // thread, its get() will be NULL. This could be a problem, in that + // llcoro::get_id() would return the same (NULL) token value for the "main + // coroutine" in every thread, whereas what we really want is a distinct + // value for every distinct stack in the process. So if get() is NULL, + // give it a heap CoroData: this ensures that llcoro::get_id() will return + // distinct values. + // This tactic is "leaky": sCurrent explicitly does not destroy any + // CoroData to which it points, and we do NOT enter these "main coroutine" + // CoroData instances in the LLCoros::mCoros map. They are dummy entries, + // and they will leak at process shutdown: one CoroData per thread. + if (! sCurrent.get()) + { + // It's tempting to provide a distinct name for each thread's "main + // coroutine." But as getName() has always returned the empty string + // to mean "not in a coroutine," empty string should suffice here -- + // and truthfully the additional (thread-safe!) machinery to ensure + // uniqueness just doesn't feel worth the trouble. + // We use a no-op callable and a minimal stack size because, although + // CoroData's constructor in fact initializes its mCoro with a + // coroutine with that stack size, no one ever actually enters it by + // calling mCoro(). + sCurrent.reset(new CoroData(0, // no prev + "", // not a named coroutine + no_op, // no-op callable + 1024)); // stacksize moot + } + + mCurrent = &sCurrent; +} //static LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) { - CoroData* current = sCurrentCoro.get(); - if (! current) - { - LL_ERRS("LLCoros") << "Calling " << caller << " from non-coroutine context!" << LL_ENDL; - } + CoroData* current = Current(); + // With the dummy CoroData set in LLCoros::Current::Current(), this + // pointer should never be NULL. + llassert_always(current); return *current; } //static LLCoros::coro::self& LLCoros::get_self() { - return *get_CoroData("get_self()").mSelf; + CoroData& current = get_CoroData("get_self()"); + if (! current.mSelf) + { + LL_ERRS("LLCoros") << "Calling get_self() from non-coroutine context!" << LL_ENDL; + } + return *current.mSelf; } //static @@ -80,20 +127,23 @@ bool LLCoros::get_consuming() return get_CoroData("get_consuming()").mConsuming; } -llcoro::Suspending::Suspending(): - mSuspended(LLCoros::sCurrentCoro.get()) +llcoro::Suspending::Suspending() { - // Revert mCurrentCoro to the value it had at the moment we last switched + LLCoros::Current current; + // Remember currently-running coroutine: we're about to suspend it. + mSuspended = current; + // Revert Current to the value it had at the moment we last switched // into this coroutine. - LLCoros::sCurrentCoro.reset(mSuspended->mPrev); + current.reset(mSuspended->mPrev); } llcoro::Suspending::~Suspending() { + LLCoros::Current current; // Okay, we're back, update our mPrev - mSuspended->mPrev = LLCoros::sCurrentCoro.get(); - // and reinstate our sCurrentCoro. - LLCoros::sCurrentCoro.reset(mSuspended); + mSuspended->mPrev = current; + // and reinstate our Current. + current.reset(mSuspended); } LLCoros::LLCoros(): @@ -213,13 +263,7 @@ bool LLCoros::kill(const std::string& name) std::string LLCoros::getName() const { - CoroData* current = sCurrentCoro.get(); - if (! current) - { - // not in a coroutine - return ""; - } - return current->mName; + return Current()->mName; } void LLCoros::setStackSize(S32 stacksize) @@ -229,8 +273,8 @@ void LLCoros::setStackSize(S32 stacksize) } // Top-level wrapper around caller's coroutine callable. This function accepts -// the coroutine library's implicit coro::self& parameter and sets sCurrentSelf -// but does not pass it down to the caller's callable. +// the coroutine library's implicit coro::self& parameter and saves it, but +// does not pass it down to the caller's callable. void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& callable) { // capture the 'self' param in CoroData @@ -254,8 +298,8 @@ void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& calla CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName)); } // This cleanup isn't perfectly symmetrical with the way we initially set - // data->mPrev, but this is our last chance to reset mCurrentCoro. - sCurrentCoro.reset(data->mPrev); + // data->mPrev, but this is our last chance to reset Current. + Current().reset(data->mPrev); } /***************************************************************************** @@ -278,7 +322,7 @@ LLCoros::CoroData::CoroData(CoroData* prev, const std::string& name, mPrev(prev), mName(name), // Wrap the caller's callable in our toplevel() function so we can manage - // sCurrentCoro appropriately at startup and shutdown of each coroutine. + // Current appropriately at startup and shutdown of each coroutine. mCoro(boost::bind(toplevel, _1, this, callable), stacksize), // don't consume events unless specifically directed mConsuming(false), @@ -289,13 +333,13 @@ LLCoros::CoroData::CoroData(CoroData* prev, const std::string& name, std::string LLCoros::launch(const std::string& prefix, const callable_t& callable) { std::string name(generateDistinctName(prefix)); - // pass the current value of sCurrentCoro as previous context - CoroData* newCoro = new CoroData(sCurrentCoro.get(), name, - callable, mStackSize); + Current current; + // pass the current value of Current as previous context + CoroData* newCoro = new CoroData(current, name, callable, mStackSize); // Store it in our pointer map mCoros.insert(name, newCoro); // also set it as current - sCurrentCoro.reset(newCoro); + current.reset(newCoro); /* Run the coroutine until its first wait, then return here */ (newCoro->mCoro)(std::nothrow); return name; diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index 39316ed0e6..bbe2d22af4 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -35,8 +35,10 @@ #include <boost/ptr_container/ptr_map.hpp> #include <boost/function.hpp> #include <boost/thread/tss.hpp> +#include <boost/noncopyable.hpp> #include <string> #include <stdexcept> +#include "llcoro_get_id.h" // for friend declaration // forward-declare helper class namespace llcoro @@ -83,6 +85,7 @@ class Suspending; */ class LL_COMMON_API LLCoros: public LLSingleton<LLCoros> { + LLSINGLETON(LLCoros); public: /// Canonical boost::dcoroutines::coroutine signature we use typedef boost::dcoroutines::coroutine<void()> coro; @@ -173,9 +176,8 @@ public: class Future; private: - LLCoros(); - friend class LLSingleton<LLCoros>; friend class llcoro::Suspending; + friend llcoro::id llcoro::get_id(); std::string generateDistinctName(const std::string& prefix) const; bool cleanup(const LLSD&); struct CoroData; @@ -222,8 +224,22 @@ private: typedef boost::ptr_map<std::string, CoroData> CoroMap; CoroMap mCoros; - // identify the current coroutine's CoroData - static boost::thread_specific_ptr<LLCoros::CoroData> sCurrentCoro; + // Identify the current coroutine's CoroData. Use a little helper class so + // a caller can either use a temporary instance, or instantiate a named + // variable and access it multiple times. + class Current + { + public: + Current(); + + operator LLCoros::CoroData*() { return get(); } + LLCoros::CoroData* operator->() { return get(); } + LLCoros::CoroData* get() { return mCurrent->get(); } + void reset(LLCoros::CoroData* ptr) { mCurrent->reset(ptr); } + + private: + boost::thread_specific_ptr<LLCoros::CoroData>* mCurrent; + }; }; namespace llcoro @@ -231,7 +247,7 @@ namespace llcoro /// Instantiate one of these in a block surrounding any leaf point when /// control literally switches away from this coroutine. -class Suspending +class Suspending: boost::noncopyable { public: Suspending(); diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 6a62860c3f..229442cec1 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -239,6 +239,14 @@ namespace { std::string className(const std::type_info& type) { + return LLError::Log::demangle(type.name()); + } +} // anonymous + +namespace LLError +{ + std::string Log::demangle(const char* mangled) + { #ifdef __GNUC__ // GCC: type_info::name() returns a mangled class name,st demangle @@ -252,31 +260,34 @@ namespace // but gcc 3.3 libstc++'s implementation of demangling is broken // and fails without. - char* name = abi::__cxa_demangle(type.name(), + char* name = abi::__cxa_demangle(mangled, abi_name_buf, &abi_name_len, &status); // this call can realloc the abi_name_buf pointer (!) - return name ? name : type.name(); + return name ? name : mangled; #elif LL_WINDOWS // DevStudio: type_info::name() includes the text "class " at the start static const std::string class_prefix = "class "; - - std::string name = type.name(); - std::string::size_type p = name.find(class_prefix); - if (p == std::string::npos) + std::string name = mangled; + if (0 != name.compare(0, class_prefix.length(), class_prefix)) { + LL_DEBUGS() << "Did not see '" << class_prefix << "' prefix on '" + << name << "'" << LL_ENDL; return name; } - return name.substr(p + class_prefix.size()); + return name.substr(class_prefix.length()); -#else - return type.name(); +#else + return mangled; #endif } +} // LLError +namespace +{ std::string functionName(const std::string& preprocessor_name) { #if LL_WINDOWS @@ -363,9 +374,8 @@ namespace class Globals : public LLSingleton<Globals> { + LLSINGLETON(Globals); public: - Globals(); - std::ostringstream messageStream; bool messageStreamInUse; @@ -438,11 +448,10 @@ namespace LLError class Settings : public LLSingleton<Settings> { + LLSINGLETON(Settings); public: - Settings(); - SettingsConfigPtr getSettingsConfig(); - + void reset(); SettingsStoragePtr saveAndReset(); void restore(SettingsStoragePtr pSettingsStorage); @@ -450,7 +459,7 @@ namespace LLError private: SettingsConfigPtr mSettingsConfig; }; - + SettingsConfig::SettingsConfig() : LLRefCount(), mPrintLocation(false), @@ -475,8 +484,7 @@ namespace LLError mRecorders.clear(); } - Settings::Settings() - : LLSingleton<Settings>(), + Settings::Settings(): mSettingsConfig(new SettingsConfig()) { } @@ -485,26 +493,31 @@ namespace LLError { return mSettingsConfig; } - + void Settings::reset() { Globals::getInstance()->invalidateCallSites(); mSettingsConfig = new SettingsConfig(); } - + SettingsStoragePtr Settings::saveAndReset() { SettingsStoragePtr oldSettingsConfig(mSettingsConfig.get()); reset(); return oldSettingsConfig; } - + void Settings::restore(SettingsStoragePtr pSettingsStorage) { Globals::getInstance()->invalidateCallSites(); SettingsConfigPtr newSettingsConfig(dynamic_cast<SettingsConfig *>(pSettingsStorage.get())); mSettingsConfig = newSettingsConfig; } + + bool is_available() + { + return Settings::instanceExists() && Globals::instanceExists(); + } } namespace LLError diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 7cbe4334b3..ce4d4552df 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -190,6 +190,7 @@ namespace LLError static std::ostringstream* out(); static void flush(std::ostringstream* out, char* message); static void flush(std::ostringstream*, const CallSite&); + static std::string demangle(const char* mangled); }; struct LL_COMMON_API CallSite diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index 56ac52e5de..56e84f7172 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -189,6 +189,11 @@ namespace LLError LL_COMMON_API std::string abbreviateFile(const std::string& filePath); LL_COMMON_API int shouldLogCallCount(); + + // Check whether Globals exists. This should only be used by LLSingleton + // infrastructure to avoid trying to log when our internal LLSingleton is + // unavailable -- circularity ensues. + LL_COMMON_API bool is_available(); }; #endif // LL_LLERRORCONTROL_H diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index a3b9ec02e0..7cff7dfd45 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -229,7 +229,7 @@ class LLEventPump; */ class LL_COMMON_API LLEventPumps: public LLSingleton<LLEventPumps> { - friend class LLSingleton<LLEventPumps>; + LLSINGLETON(LLEventPumps); public: /** * Find or create an LLEventPump instance with a specific name. We return @@ -272,7 +272,6 @@ private: void unregister(const LLEventPump&); private: - LLEventPumps(); ~LLEventPumps(); testable: diff --git a/indra/llcommon/llheteromap.cpp b/indra/llcommon/llheteromap.cpp new file mode 100644 index 0000000000..7c19196e0c --- /dev/null +++ b/indra/llcommon/llheteromap.cpp @@ -0,0 +1,32 @@ +/** + * @file llheteromap.cpp + * @author Nat Goodspeed + * @date 2016-10-12 + * @brief Implementation for llheteromap. + * + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llheteromap.h" +// STL headers +// std headers +// external library headers +// other Linden headers + +LLHeteroMap::~LLHeteroMap() +{ + // For each entry in our map, we must call its deleter, which is the only + // record we have of its original type. + for (TypeMap::iterator mi(mMap.begin()), me(mMap.end()); mi != me; ++mi) + { + // mi->second is the std::pair; mi->second.first is the void*; + // mi->second.second points to the deleter function + (mi->second.second)(mi->second.first); + mi->second.first = NULL; + } +} diff --git a/indra/llcommon/llheteromap.h b/indra/llcommon/llheteromap.h new file mode 100644 index 0000000000..9d6f303d08 --- /dev/null +++ b/indra/llcommon/llheteromap.h @@ -0,0 +1,95 @@ +/** + * @file llheteromap.h + * @author Nat Goodspeed + * @date 2016-10-12 + * @brief Map capable of storing objects of diverse types, looked up by type. + * + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLHETEROMAP_H) +#define LL_LLHETEROMAP_H + +#include <typeinfo> +#include <utility> // std::pair +#include <map> + +/** + * LLHeteroMap addresses an odd requirement. Usually when you want to put + * objects of different classes into a runtime collection of any kind, you + * derive them all from a common base class and store pointers to that common + * base class. + * + * LLInitParam::BaseBlock uses disturbing raw-pointer arithmetic to find data + * members in its subclasses. It seems that no BaseBlock subclass can be + * stored in a polymorphic class of any kind: the presence of a vtbl pointer + * in the layout silently throws off the reinterpret_cast arithmetic. Bad + * Things result. (Many thanks to Nicky D for this analysis!) + * + * LLHeteroMap collects objects WITHOUT a common base class, retrieves them by + * object type and destroys them when the LLHeteroMap is destroyed. + */ +class LLHeteroMap +{ +public: + ~LLHeteroMap(); + + /// find or create + template <class T> + T& obtain() + { + // Look up map entry by typeid(T). We don't simply use mMap[typeid(T)] + // because that requires default-constructing T on every lookup. For + // some kinds of T, that could be expensive. + TypeMap::iterator found = mMap.find(&typeid(T)); + if (found == mMap.end()) + { + // Didn't find typeid(T). Create an entry. Because we're storing + // only a void* in the map, discarding type information, make sure + // we capture that type information in our deleter. + void* ptr = new T(); + void (*dlfn)(void*) = &deleter<T>; + std::pair<TypeMap::iterator, bool> inserted = + mMap.insert(TypeMap::value_type(&typeid(T), + TypeMap::mapped_type(ptr, dlfn))); + // Okay, now that we have an entry, claim we found it. + found = inserted.first; + } + // found->second is the std::pair; second.first is the void* + // pointer to the object in question. Cast it to correct type and + // dereference it. + return *(static_cast<T*>(found->second.first)); + } + +private: + template <class T> + static + void deleter(void* p) + { + delete static_cast<T*>(p); + } + + // Comparing two std::type_info* values is tricky, because the standard + // does not guarantee that there will be only one type_info instance for a + // given type. In other words, &typeid(A) in one part of the program may + // not always equal &typeid(A) in some other part. Use special comparator. + struct type_info_ptr_comp + { + bool operator()(const std::type_info* lhs, const std::type_info* rhs) + { + return lhs->before(*rhs); + } + }; + + // What we actually store is a map from std::type_info (permitting lookup + // by object type) to a void* pointer to the object PLUS its deleter. + typedef std::map< + const std::type_info*, std::pair<void*, void (*)(void*)>, + type_info_ptr_comp> + TypeMap; + TypeMap mMap; +}; + +#endif /* ! defined(LL_LLHETEROMAP_H) */ diff --git a/indra/llcommon/llinitdestroyclass.cpp b/indra/llcommon/llinitdestroyclass.cpp new file mode 100644 index 0000000000..e6382a7924 --- /dev/null +++ b/indra/llcommon/llinitdestroyclass.cpp @@ -0,0 +1,30 @@ +/** + * @file llinitdestroyclass.cpp + * @author Nat Goodspeed + * @date 2016-08-30 + * @brief Implementation for llinitdestroyclass. + * + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llinitdestroyclass.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llerror.h" + +void LLCallbackRegistry::fireCallbacks() const +{ + for (FuncList::const_iterator fi = mCallbacks.begin(), fe = mCallbacks.end(); + fi != fe; ++fi) + { + LL_INFOS("LLInitDestroyClass") << "calling " << fi->first << "()" << LL_ENDL; + fi->second(); + } +} diff --git a/indra/llcommon/llinitdestroyclass.h b/indra/llcommon/llinitdestroyclass.h new file mode 100644 index 0000000000..5f979614fe --- /dev/null +++ b/indra/llcommon/llinitdestroyclass.h @@ -0,0 +1,175 @@ +/** + * @file llinitdestroyclass.h + * @author Nat Goodspeed + * @date 2015-05-27 + * @brief LLInitClass / LLDestroyClass mechanism + * + * The LLInitClass template, extracted from llui.h, ensures that control will + * reach a static initClass() method. LLDestroyClass does the same for a + * static destroyClass() method. + * + * The distinguishing characteristics of these templates are: + * + * - All LLInitClass<T>::initClass() methods are triggered by an explicit call + * to LLInitClassList::instance().fireCallbacks(). Presumably this call + * happens sometime after all static objects in the program have been + * initialized. In other words, each LLInitClass<T>::initClass() method + * should be able to make some assumptions about global program state. + * + * - Similarly, LLDestroyClass<T>::destroyClass() methods are triggered by + * LLDestroyClassList::instance().fireCallbacks(). Again, presumably this + * happens at a well-defined moment in the program's shutdown sequence. + * + * - The initClass() calls happen in an unspecified sequence. You may not rely + * on the relative ordering of LLInitClass<T>::initClass() versus another + * LLInitClass<U>::initClass() method. If you need such a guarantee, use + * LLSingleton instead and make the dependency explicit. + * + * - Similarly, LLDestroyClass<T>::destroyClass() may happen either before or + * after LLDestroyClass<U>::destroyClass(). You cannot rely on that order. + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Copyright (c) 2015, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLINITDESTROYCLASS_H) +#define LL_LLINITDESTROYCLASS_H + +#include "llsingleton.h" +#include <boost/function.hpp> +#include <typeinfo> +#include <vector> +#include <utility> // std::pair + +/** + * LLCallbackRegistry is an implementation detail base class for + * LLInitClassList and LLDestroyClassList. It accumulates the initClass() or + * destroyClass() callbacks for registered classes. + */ +class LLCallbackRegistry +{ +public: + typedef boost::function<void()> func_t; + + void registerCallback(const std::string& name, const func_t& func) + { + mCallbacks.push_back(FuncList::value_type(name, func)); + } + + void fireCallbacks() const; + +private: + // Arguably this should be a boost::signals2::signal, which is, after all, + // a sequence of callables. We manage it by hand so we can log a name for + // each registered function we call. + typedef std::vector< std::pair<std::string, func_t> > FuncList; + FuncList mCallbacks; +}; + +/** + * LLInitClassList is the LLCallbackRegistry for LLInitClass. It stores the + * registered initClass() methods. It must be an LLSingleton because + * LLInitClass registers its initClass() method at static construction time + * (before main()), requiring LLInitClassList to be fully constructed on + * demand regardless of module initialization order. + */ +class LLInitClassList : + public LLCallbackRegistry, + public LLSingleton<LLInitClassList> +{ + LLSINGLETON_EMPTY_CTOR(LLInitClassList); +}; + +/** + * LLDestroyClassList is the LLCallbackRegistry for LLDestroyClass. It stores + * the registered destroyClass() methods. It must be an LLSingleton because + * LLDestroyClass registers its destroyClass() method at static construction + * time (before main()), requiring LLDestroyClassList to be fully constructed + * on demand regardless of module initialization order. + */ +class LLDestroyClassList : + public LLCallbackRegistry, + public LLSingleton<LLDestroyClassList> +{ + LLSINGLETON_EMPTY_CTOR(LLDestroyClassList); +}; + +/** + * LLRegisterWith is an implementation detail for LLInitClass and + * LLDestroyClass. It is intended to be used as a static class member whose + * constructor registers the specified callback with the LLMumbleClassList + * singleton registry specified as the template argument. + */ +template<typename T> +class LLRegisterWith +{ +public: + LLRegisterWith(const std::string& name, const LLCallbackRegistry::func_t& func) + { + T::instance().registerCallback(name, func); + } + + // this avoids a MSVC bug where non-referenced static members are "optimized" away + // even if their constructors have side effects + S32 reference() + { + S32 dummy; + dummy = 0; + return dummy; + } +}; + +/** + * Derive MyClass from LLInitClass<MyClass> (the Curiously Recurring Template + * Pattern) to ensure that the static method MyClass::initClass() will be + * called (along with all other LLInitClass<T> subclass initClass() methods) + * when someone calls LLInitClassList::instance().fireCallbacks(). This gives + * the application specific control over the timing of all such + * initializations, without having to insert calls for every such class into + * generic application code. + */ +template<typename T> +class LLInitClass +{ +public: + LLInitClass() { sRegister.reference(); } + + // When this static member is initialized, the subclass initClass() method + // is registered on LLInitClassList. See sRegister definition below. + static LLRegisterWith<LLInitClassList> sRegister; +}; + +/** + * Derive MyClass from LLDestroyClass<MyClass> (the Curiously Recurring + * Template Pattern) to ensure that the static method MyClass::destroyClass() + * will be called (along with other LLDestroyClass<T> subclass destroyClass() + * methods) when someone calls LLDestroyClassList::instance().fireCallbacks(). + * This gives the application specific control over the timing of all such + * cleanup calls, without having to insert calls for every such class into + * generic application code. + */ +template<typename T> +class LLDestroyClass +{ +public: + LLDestroyClass() { sRegister.reference(); } + + // When this static member is initialized, the subclass destroyClass() + // method is registered on LLInitClassList. See sRegister definition + // below. + static LLRegisterWith<LLDestroyClassList> sRegister; +}; + +// Here's where LLInitClass<T> specifies the subclass initClass() method. +template <typename T> +LLRegisterWith<LLInitClassList> +LLInitClass<T>::sRegister(std::string(typeid(T).name()) + "::initClass", + &T::initClass); +// Here's where LLDestroyClass<T> specifies the subclass destroyClass() method. +template <typename T> +LLRegisterWith<LLDestroyClassList> +LLDestroyClass<T>::sRegister(std::string(typeid(T).name()) + "::destroyClass", + &T::destroyClass); + +#endif /* ! defined(LL_LLINITDESTROYCLASS_H) */ diff --git a/indra/llcommon/llmetricperformancetester.cpp b/indra/llcommon/llmetricperformancetester.cpp index 1fc821d9a9..16fc365da1 100644 --- a/indra/llcommon/llmetricperformancetester.cpp +++ b/indra/llcommon/llmetricperformancetester.cpp @@ -40,7 +40,7 @@ LLMetricPerformanceTesterBasic::name_tester_map_t LLMetricPerformanceTesterBasic::sTesterMap ; /*static*/ -void LLMetricPerformanceTesterBasic::cleanClass() +void LLMetricPerformanceTesterBasic::cleanupClass() { for (name_tester_map_t::iterator iter = sTesterMap.begin() ; iter != sTesterMap.end() ; ++iter) { diff --git a/indra/llcommon/llmetricperformancetester.h b/indra/llcommon/llmetricperformancetester.h index 1a18cdf36f..e6b46be1cf 100644 --- a/indra/llcommon/llmetricperformancetester.h +++ b/indra/llcommon/llmetricperformancetester.h @@ -156,7 +156,7 @@ public: /** * @brief Delete all testers and reset the tester map */ - static void cleanClass() ; + static void cleanupClass() ; private: // Add a tester to the map. Returns false if adding fails. diff --git a/indra/llcommon/llpounceable.h b/indra/llcommon/llpounceable.h new file mode 100644 index 0000000000..0421ce966a --- /dev/null +++ b/indra/llcommon/llpounceable.h @@ -0,0 +1,216 @@ +/** + * @file llpounceable.h + * @author Nat Goodspeed + * @date 2015-05-22 + * @brief LLPounceable is tangentially related to a future: it's a holder for + * a value that may or may not exist yet. Unlike a future, though, + * LLPounceable freely allows reading the held value. (If the held + * type T does not have a distinguished "empty" value, consider using + * LLPounceable<boost::optional<T>>.) + * + * LLPounceable::callWhenReady() is this template's claim to fame. It + * allows its caller to "pounce" on the held value as soon as it + * becomes non-empty. Call callWhenReady() with any C++ callable + * accepting T. If the held value is already non-empty, callWhenReady() + * will immediately call the callable with the held value. If the held + * value is empty, though, callWhenReady() will enqueue the callable + * for later. As soon as LLPounceable is assigned a non-empty held + * value, it will flush the queue of deferred callables. + * + * Consider a global LLMessageSystem* gMessageSystem. Message system + * initialization happens at a very specific point during viewer + * initialization. Other subsystems want to register callbacks on the + * LLMessageSystem instance as soon as it's initialized, but their own + * initialization may precede that. If we define gMessageSystem to be + * an LLPounceable<LLMessageSystem*>, a subsystem can use + * callWhenReady() to either register immediately (if gMessageSystem + * is already up and runnning) or register as soon as gMessageSystem + * is set with a new, initialized instance. + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Copyright (c) 2015, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLPOUNCEABLE_H) +#define LL_LLPOUNCEABLE_H + +#include "llsingleton.h" +#include <boost/noncopyable.hpp> +#include <boost/call_traits.hpp> +#include <boost/type_traits/remove_pointer.hpp> +#include <boost/utility/value_init.hpp> +#include <boost/unordered_map.hpp> +#include <boost/signals2/signal.hpp> + +// Forward declare the user template, since we want to be able to point to it +// in some of its implementation classes. +template <typename T, class TAG> +class LLPounceable; + +template <typename T, typename TAG> +struct LLPounceableTraits +{ + // Our "queue" is a signal object with correct signature. + typedef boost::signals2::signal<void (typename boost::call_traits<T>::param_type)> signal_t; + // Call callWhenReady() with any callable accepting T. + typedef typename signal_t::slot_type func_t; + // owner pointer type + typedef LLPounceable<T, TAG>* owner_ptr; +}; + +// Tag types distinguish the two different implementations of LLPounceable's +// queue. +struct LLPounceableQueue {}; +struct LLPounceableStatic {}; + +// generic LLPounceableQueueImpl deliberately omitted: only the above tags are +// legal +template <typename T, class TAG> +class LLPounceableQueueImpl; + +// The implementation selected by LLPounceableStatic uses an LLSingleton +// because we can't count on a data member queue being initialized at the time +// we start getting callWhenReady() calls. This is that LLSingleton. +template <typename T> +class LLPounceableQueueSingleton: + public LLSingleton<LLPounceableQueueSingleton<T> > +{ + LLSINGLETON_EMPTY_CTOR(LLPounceableQueueSingleton); + + typedef LLPounceableTraits<T, LLPounceableStatic> traits; + typedef typename traits::owner_ptr owner_ptr; + typedef typename traits::signal_t signal_t; + + // For a given held type T, every LLPounceable<T, LLPounceableStatic> + // instance will call on the SAME LLPounceableQueueSingleton instance -- + // given how class statics work. We must keep a separate queue for each + // LLPounceable instance. Use a hash map for that. + typedef boost::unordered_map<owner_ptr, signal_t> map_t; + +public: + // Disambiguate queues belonging to different LLPounceables. + signal_t& get(owner_ptr owner) + { + // operator[] has find-or-create semantics -- just what we want! + return mMap[owner]; + } + +private: + map_t mMap; +}; + +// LLPounceableQueueImpl that uses the above LLSingleton +template <typename T> +class LLPounceableQueueImpl<T, LLPounceableStatic> +{ +public: + typedef LLPounceableTraits<T, LLPounceableStatic> traits; + typedef typename traits::owner_ptr owner_ptr; + typedef typename traits::signal_t signal_t; + + signal_t& get(owner_ptr owner) const + { + // this Impl contains nothing; it delegates to the Singleton + return LLPounceableQueueSingleton<T>::instance().get(owner); + } +}; + +// The implementation selected by LLPounceableQueue directly contains the +// queue of interest, suitable for an LLPounceable we can trust to be fully +// initialized when it starts getting callWhenReady() calls. +template <typename T> +class LLPounceableQueueImpl<T, LLPounceableQueue> +{ +public: + typedef LLPounceableTraits<T, LLPounceableQueue> traits; + typedef typename traits::owner_ptr owner_ptr; + typedef typename traits::signal_t signal_t; + + signal_t& get(owner_ptr) + { + return mQueue; + } + +private: + signal_t mQueue; +}; + +// LLPounceable<T> is for an LLPounceable instance on the heap or the stack. +// LLPounceable<T, LLPounceableStatic> is for a static LLPounceable instance. +template <typename T, class TAG=LLPounceableQueue> +class LLPounceable: public boost::noncopyable +{ +private: + typedef LLPounceableTraits<T, TAG> traits; + typedef typename traits::owner_ptr owner_ptr; + typedef typename traits::signal_t signal_t; + +public: + typedef typename traits::func_t func_t; + + // By default, both the initial value and the distinguished empty value + // are a default-constructed T instance. However you can explicitly + // specify each. + LLPounceable(typename boost::call_traits<T>::value_type init =boost::value_initialized<T>(), + typename boost::call_traits<T>::param_type empty=boost::value_initialized<T>()): + mHeld(init), + mEmpty(empty) + {} + + // make read access to mHeld as cheap and transparent as possible + operator T () const { return mHeld; } + typename boost::remove_pointer<T>::type operator*() const { return *mHeld; } + typename boost::call_traits<T>::value_type operator->() const { return mHeld; } + // uncomment 'explicit' as soon as we allow C++11 compilation + /*explicit*/ operator bool() const { return bool(mHeld); } + bool operator!() const { return ! mHeld; } + + // support both assignment (dumb ptr idiom) and reset() (smart ptr) + void operator=(typename boost::call_traits<T>::param_type value) + { + reset(value); + } + + void reset(typename boost::call_traits<T>::param_type value) + { + mHeld = value; + // If this new value is non-empty, flush anything pending in the queue. + if (mHeld != mEmpty) + { + signal_t& signal(get_signal()); + signal(mHeld); + signal.disconnect_all_slots(); + } + } + + // our claim to fame + void callWhenReady(const func_t& func) + { + if (mHeld != mEmpty) + { + // If the held value is already non-empty, immediately call func() + func(mHeld); + } + else + { + // Held value still empty, queue func() for later. By default, + // connect() enqueues slots in FIFO order. + get_signal().connect(func); + } + } + +private: + signal_t& get_signal() { return mQueue.get(this); } + + // Store both the current and the empty value. + // MAYBE: Might be useful to delegate to LLPounceableTraits the meaning of + // testing for "empty." For some types we want operator!(); for others we + // want to compare to a distinguished value. + typename boost::call_traits<T>::value_type mHeld, mEmpty; + // This might either contain the queue (LLPounceableQueue) or delegate to + // an LLSingleton (LLPounceableStatic). + LLPounceableQueueImpl<T, TAG> mQueue; +}; + +#endif /* ! defined(LL_LLPOUNCEABLE_H) */ diff --git a/indra/llcommon/llregistry.h b/indra/llcommon/llregistry.h index 29950c108d..750fe9fdc8 100644 --- a/indra/llcommon/llregistry.h +++ b/indra/llcommon/llregistry.h @@ -247,7 +247,10 @@ class LLRegistrySingleton : public LLRegistry<KEY, VALUE, COMPARATOR>, public LLSingleton<DERIVED_TYPE> { - friend class LLSingleton<DERIVED_TYPE>; + // This LLRegistrySingleton doesn't use LLSINGLETON(LLRegistrySingleton) + // because the concrete class is actually DERIVED_TYPE, not + // LLRegistrySingleton. So each concrete subclass needs + // LLSINGLETON(whatever) -- not this intermediate base class. public: typedef LLRegistry<KEY, VALUE, COMPARATOR> registry_t; typedef const KEY& ref_const_key_t; @@ -269,7 +272,7 @@ public: ~ScopedRegistrar() { - if (!singleton_t::destroyed()) + if (singleton_t::instanceExists()) { popScope(); } diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index 9b49e52377..9025e53bb2 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -25,7 +25,445 @@ */ #include "linden_common.h" - #include "llsingleton.h" +#include "llerror.h" +#include "llerrorcontrol.h" // LLError::is_available() +#include "lldependencies.h" +#include "llcoro_get_id.h" +#include <boost/foreach.hpp> +#include <boost/unordered_map.hpp> +#include <algorithm> +#include <iostream> // std::cerr in dire emergency +#include <sstream> +#include <stdexcept> + +namespace { +void log(LLError::ELevel level, + const char* p1, const char* p2, const char* p3, const char* p4); + +void logdebugs(const char* p1="", const char* p2="", const char* p3="", const char* p4=""); + +bool oktolog(); +} // anonymous namespace + +// Our master list of all LLSingletons is itself an LLSingleton. We used to +// store it in a function-local static, but that could get destroyed before +// the last of the LLSingletons -- and ~LLSingletonBase() definitely wants to +// remove itself from the master list. Since the whole point of this master +// list is to help track inter-LLSingleton dependencies, and since we have +// this implicit dependency from every LLSingleton to the master list, make it +// an LLSingleton. +class LLSingletonBase::MasterList: + public LLSingleton<LLSingletonBase::MasterList> +{ + LLSINGLETON_EMPTY_CTOR(MasterList); + +public: + // No need to make this private with accessors; nobody outside this source + // file can see it. + + // This is the master list of all instantiated LLSingletons (save the + // MasterList itself) in arbitrary order. You MUST call dep_sort() before + // traversing this list. + LLSingletonBase::list_t mMaster; + + // We need to maintain a stack of LLSingletons currently being + // initialized, either in the constructor or in initSingleton(). However, + // managing that as a stack depends on having a DISTINCT 'initializing' + // stack for every C++ stack in the process! And we have a distinct C++ + // stack for every running coroutine. It would be interesting and cool to + // implement a generic coroutine-local-storage mechanism and use that + // here. The trouble is that LLCoros is itself an LLSingleton, so + // depending on LLCoros functionality could dig us into infinite + // recursion. (Moreover, when we reimplement LLCoros on top of + // Boost.Fiber, that library already provides fiber_specific_ptr -- so + // it's not worth a great deal of time and energy implementing a generic + // equivalent on top of boost::dcoroutine, which is on its way out.) + // Instead, use a map of llcoro::id to select the appropriate + // coro-specific 'initializing' stack. llcoro::get_id() is carefully + // implemented to avoid requiring LLCoros. + typedef boost::unordered_map<llcoro::id, LLSingletonBase::list_t> InitializingMap; + InitializingMap mInitializing; + + // non-static method, cf. LLSingletonBase::get_initializing() + list_t& get_initializing_() + { + // map::operator[] has find-or-create semantics, exactly what we need + // here. It returns a reference to the selected mapped_type instance. + return mInitializing[llcoro::get_id()]; + } + + void cleanup_initializing_() + { + InitializingMap::iterator found = mInitializing.find(llcoro::get_id()); + if (found != mInitializing.end()) + { + mInitializing.erase(found); + } + } +}; + +//static +LLSingletonBase::list_t& LLSingletonBase::get_master() +{ + return LLSingletonBase::MasterList::instance().mMaster; +} + +void LLSingletonBase::add_master() +{ + // As each new LLSingleton is constructed, add to the master list. + get_master().push_back(this); +} + +void LLSingletonBase::remove_master() +{ + // When an LLSingleton is destroyed, remove from master list. + // add_master() used to capture the iterator to the newly-added list item + // so we could directly erase() it from the master list. Unfortunately + // that runs afoul of destruction-dependency order problems. So search the + // master list, and remove this item IF FOUND. We have few enough + // LLSingletons, and they are so rarely destroyed (once per run), that the + // cost of a linear search should not be an issue. + get_master().remove(this); +} + +//static +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) +{ + // log BEFORE pushing so logging singletons don't cry circularity + log_initializing("Pushing", name); + get_initializing().push_back(this); +} + +void LLSingletonBase::pop_initializing() +{ + list_t& list(get_initializing()); + + if (list.empty()) + { + logerrs("Underflow in stack of currently-initializing LLSingletons at ", + demangle(typeid(*this).name()).c_str(), "::getInstance()"); + } + + // Now we know list.back() exists: capture it + LLSingletonBase* back(list.back()); + // and pop it + list.pop_back(); + + // The viewer launches an open-ended number of coroutines. While we don't + // expect most of them to initialize LLSingleton instances, our present + // get_initializing() logic could lead to an open-ended number of map + // entries. So every time we pop the stack back to empty, delete the entry + // entirely. + if (list.empty()) + { + MasterList::instance().cleanup_initializing_(); + } + + // Now validate the newly-popped LLSingleton. + 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()); + } + + // log AFTER popping so logging singletons don't cry circularity + log_initializing("Popping", typeid(*back).name()); +} + +//static +void LLSingletonBase::log_initializing(const char* verb, const char* name) +{ + if (oktolog()) + { + LL_DEBUGS("LLSingleton") << verb << ' ' << demangle(name) << ';'; + list_t& list(get_initializing()); + for (list_t::const_reverse_iterator ri(list.rbegin()), rend(list.rend()); + ri != rend; ++ri) + { + LLSingletonBase* sb(*ri); + LL_CONT << ' ' << demangle(typeid(*sb).name()); + } + LL_ENDL; + } +} + +void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initState) +{ + // Did this getInstance() call come from another LLSingleton, or from + // vanilla application code? Note that although this is a nontrivial + // method, the vast majority of its calls arrive here with initializing + // empty(). + if (! initializing.empty()) + { + // getInstance() is being called by some other LLSingleton. But -- is + // this a circularity? That is, does 'this' already appear in the + // initializing stack? + // For what it's worth, normally 'initializing' should contain very + // few elements. + list_t::const_iterator found = + std::find(initializing.begin(), initializing.end(), this); + if (found != initializing.end()) + { + // Report the circularity. Requiring the coder to dig through the + // logic to diagnose exactly how we got here is less than helpful. + std::ostringstream out; + for ( ; found != initializing.end(); ++found) + { + // 'found' is an iterator; *found is an LLSingletonBase*; **found + // is the actual LLSingletonBase instance. + LLSingletonBase* foundp(*found); + out << demangle(typeid(*foundp).name()) << " -> "; + } + // We promise to capture dependencies from both the constructor + // and the initSingleton() method, so an LLSingleton's instance + // pointer is on the initializing list during both. Now that we've + // detected circularity, though, we must distinguish the two. If + // the recursive call is from the constructor, we CAN'T honor it: + // otherwise we'd be returning a pointer to a partially- + // constructed object! But from initSingleton() is okay: that + // method exists specifically to support circularity. + // Decide which log helper to call based on initState. They have + // identical signatures. + ((initState == CONSTRUCTING)? logerrs : logwarns) + ("LLSingleton circularity: ", out.str().c_str(), + demangle(typeid(*this).name()).c_str(), ""); + } + else + { + // Here 'this' is NOT already in the 'initializing' stack. Great! + // Record the dependency. + // initializing.back() is the LLSingletonBase* currently being + // initialized. Store 'this' in its mDepends set. + LLSingletonBase* current(initializing.back()); + 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()); + } + } + } +} + +//static +LLSingletonBase::vec_t LLSingletonBase::dep_sort() +{ + // While it would theoretically be possible to maintain a static + // SingletonDeps through the life of the program, dynamically adding and + // removing LLSingletons as they are created and destroyed, in practice + // it's less messy to construct it on demand. The overhead of doing so + // should happen basically twice: once for cleanupAll(), once for + // deleteAll(). + typedef LLDependencies<LLSingletonBase*> SingletonDeps; + SingletonDeps sdeps; + list_t& master(get_master()); + BOOST_FOREACH(LLSingletonBase* sp, master) + { + // Build the SingletonDeps structure by adding, for each + // LLSingletonBase* sp in the master list, sp itself. It has no + // associated value type in our SingletonDeps, hence the 0. We don't + // record the LLSingletons it must follow; rather, we record the ones + // it must precede. Copy its mDepends to a KeyList to express that. + sdeps.add(sp, 0, + SingletonDeps::KeyList(), + SingletonDeps::KeyList(sp->mDepends.begin(), sp->mDepends.end())); + } + vec_t ret; + ret.reserve(master.size()); + // We should be able to effect this with a transform_iterator that + // extracts just the first (key) element from each sorted_iterator, then + // uses vec_t's range constructor... but frankly this is more + // straightforward, as long as we remember the above reserve() call! + BOOST_FOREACH(SingletonDeps::sorted_iterator::value_type pair, sdeps.sort()) + { + ret.push_back(pair.first); + } + // The master list is not itself pushed onto the master list. Add it as + // the very last entry -- it is the LLSingleton on which ALL others + // depend! -- so our caller will process it. + ret.push_back(MasterList::getInstance()); + return ret; +} + +//static +void LLSingletonBase::cleanupAll() +{ + // It's essential to traverse these in dependency order. + BOOST_FOREACH(LLSingletonBase* sp, dep_sort()) + { + // Call cleanupSingleton() only if we haven't already done so for this + // instance. + if (! sp->mCleaned) + { + sp->mCleaned = true; + + logdebugs("calling ", + demangle(typeid(*sp).name()).c_str(), "::cleanupSingleton()"); + try + { + sp->cleanupSingleton(); + } + catch (const std::exception& e) + { + logwarns("Exception in ", demangle(typeid(*sp).name()).c_str(), + "::cleanupSingleton(): ", e.what()); + } + catch (...) + { + logwarns("Unknown exception in ", demangle(typeid(*sp).name()).c_str(), + "::cleanupSingleton()"); + } + } + } +} + +//static +void LLSingletonBase::deleteAll() +{ + // It's essential to traverse these in dependency order. + BOOST_FOREACH(LLSingletonBase* sp, dep_sort()) + { + // 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()); + try + { + // Call static method through instance function pointer. + if (! sp->mDeleteSingleton) + { + // This Should Not Happen... but carry on. + logwarns(name.c_str(), "::mDeleteSingleton not initialized!"); + } + else + { + // properly initialized: call it. + logdebugs("calling ", name.c_str(), "::deleteSingleton()"); + // From this point on, DO NOT DEREFERENCE sp! + sp->mDeleteSingleton(); + } + } + catch (const std::exception& e) + { + logwarns("Exception in ", name.c_str(), "::deleteSingleton(): ", e.what()); + } + catch (...) + { + logwarns("Unknown exception in ", name.c_str(), "::deleteSingleton()"); + } + } +} + +/*------------------------ Final cleanup management ------------------------*/ +class LLSingletonBase::MasterRefcount +{ +public: + // store a POD int so it will be statically initialized to 0 + int refcount; +}; +static LLSingletonBase::MasterRefcount sMasterRefcount; + +LLSingletonBase::ref_ptr_t LLSingletonBase::get_master_refcount() +{ + // Calling this method constructs a new ref_ptr_t, which implicitly calls + // intrusive_ptr_add_ref(MasterRefcount*). + return &sMasterRefcount; +} + +void intrusive_ptr_add_ref(LLSingletonBase::MasterRefcount* mrc) +{ + // Count outstanding SingletonLifetimeManager instances. + ++mrc->refcount; +} + +void intrusive_ptr_release(LLSingletonBase::MasterRefcount* mrc) +{ + // Notice when each SingletonLifetimeManager instance is destroyed. + if (! --mrc->refcount) + { + // The last instance was destroyed. Time to kill any remaining + // LLSingletons -- but in dependency order. + LLSingletonBase::deleteAll(); + } +} + +/*---------------------------- Logging helpers -----------------------------*/ +namespace { +bool oktolog() +{ + // See comments in log() below. + return sMasterRefcount.refcount && LLError::is_available(); +} + +void log(LLError::ELevel level, + const char* p1, const char* p2, const char* p3, const char* p4) +{ + // Check whether we're in the implicit final LLSingletonBase::deleteAll() + // call. We've carefully arranged for deleteAll() to be called when the + // last SingletonLifetimeManager instance is destroyed -- in other words, + // when the last translation unit containing an LLSingleton instance + // cleans up static data. That could happen after std::cerr is destroyed! + // The is_available() test below ensures that we'll stop logging once + // LLError has been cleaned up. If we had a similar portable test for + // std::cerr, this would be a good place to use it. As we do not, just + // don't log anything during implicit final deleteAll(). Detect that by + // the master refcount having gone to zero. + if (sMasterRefcount.refcount == 0) + return; + + // Check LLError::is_available() because some of LLError's infrastructure + // is itself an LLSingleton. If that LLSingleton has not yet been + // initialized, trying to log will engage LLSingleton machinery... and + // around and around we go. + if (LLError::is_available()) + { + LL_VLOGS(level, "LLSingleton") << p1 << p2 << p3 << p4 << LL_ENDL; + } + else + { + // Caller may be a test program, or something else whose stderr is + // visible to the user. + std::cerr << p1 << p2 << p3 << p4 << std::endl; + } +} + +void logdebugs(const char* p1, const char* p2, const char* p3, const char* p4) +{ + log(LLError::LEVEL_DEBUG, p1, p2, p3, p4); +} +} // anonymous namespace + +//static +void LLSingletonBase::logwarns(const char* p1, const char* p2, const char* p3, const char* p4) +{ + log(LLError::LEVEL_WARN, p1, p2, p3, p4); +} + +//static +void LLSingletonBase::logerrs(const char* p1, const char* p2, const char* p3, const char* p4) +{ + 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::string LLSingletonBase::demangle(const char* mangled) +{ + return LLError::Log::demangle(mangled); +} diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 6e6291a165..1b915dfd6e 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -25,188 +25,495 @@ #ifndef LLSINGLETON_H #define LLSINGLETON_H -#include "llerror.h" // *TODO: eliminate this - -#include <typeinfo> #include <boost/noncopyable.hpp> +#include <boost/unordered_set.hpp> +#include <boost/intrusive_ptr.hpp> +#include <list> +#include <vector> +#include <typeinfo> + +class LLSingletonBase: private boost::noncopyable +{ +public: + class MasterList; + class MasterRefcount; + typedef boost::intrusive_ptr<MasterRefcount> ref_ptr_t; + +private: + // All existing LLSingleton instances are tracked in this master list. + typedef std::list<LLSingletonBase*> list_t; + static list_t& get_master(); + // This, on the other hand, is a stack whose top indicates the LLSingleton + // currently being initialized. + static list_t& get_initializing(); + static list_t& get_initializing_from(MasterList*); + // Produce a vector<LLSingletonBase*> of master list, in dependency order. + typedef std::vector<LLSingletonBase*> vec_t; + static vec_t dep_sort(); + + bool mCleaned; // cleanupSingleton() has been called + // we directly depend on these other LLSingletons + typedef boost::unordered_set<LLSingletonBase*> set_t; + set_t mDepends; + +protected: + typedef enum e_init_state + { + UNINITIALIZED = 0, // must be default-initialized state + CONSTRUCTING, + INITIALIZING, + INITIALIZED, + DELETED + } EInitState; + + // Define tag<T> to pass to our template constructor. You can't explicitly + // invoke a template constructor with ordinary template syntax: + // http://stackoverflow.com/a/3960925/5533635 + template <typename T> + struct tag + { + typedef T type; + }; + + // Base-class constructor should only be invoked by the DERIVED_TYPE + // constructor, which passes tag<DERIVED_TYPE> for various purposes. + template <typename DERIVED_TYPE> + LLSingletonBase(tag<DERIVED_TYPE>); + virtual ~LLSingletonBase(); + + // Every new LLSingleton should be added to/removed from the master list + void add_master(); + void remove_master(); + // with a little help from our friends. + template <class T> friend struct LLSingleton_manage_master; + + // Maintain a stack of the LLSingleton subclass instance currently being + // initialized. We use this to notice direct dependencies: we want to know + // if A requires B. We deduce a dependency if while initializing A, + // control reaches B::getInstance(). + // We want &A to be at the top of that stack during both A::A() and + // A::initSingleton(), since a call to B::getInstance() might occur during + // either. + // Unfortunately the desired timespan does not correspond neatly with a + // single C++ scope, else we'd use RAII to track it. But we do know that + // LLSingletonBase's constructor definitely runs just before + // LLSingleton's, which runs just before the specific subclass's. + void push_initializing(const char*); + // LLSingleton is, and must remain, the only caller to initSingleton(). + // That being the case, we control exactly when it happens -- and we can + // pop the stack immediately thereafter. + void pop_initializing(); +private: + // logging + static void log_initializing(const char* verb, const char* name); +protected: + // If a given call to B::getInstance() happens during either A::A() or + // A::initSingleton(), record that A directly depends on B. + void capture_dependency(list_t& initializing, EInitState); + + // delegate LL_ERRS() logging to llsingleton.cpp + static void logerrs(const char* p1, const char* p2="", + const char* p3="", const char* p4=""); + // delegate LL_WARNS() logging to llsingleton.cpp + static void logwarns(const char* p1, const char* p2="", + const char* p3="", const char* p4=""); + static std::string demangle(const char* mangled); + + // obtain canonical ref_ptr_t + static ref_ptr_t get_master_refcount(); + + // Default methods in case subclass doesn't declare them. + virtual void initSingleton() {} + virtual void cleanupSingleton() {} + + // deleteSingleton() isn't -- and shouldn't be -- a virtual method. It's a + // class static. However, given only Foo*, deleteAll() does need to be + // able to reach Foo::deleteSingleton(). Make LLSingleton (which declares + // deleteSingleton()) store a pointer here. Since we know it's a static + // class method, a classic-C function pointer will do. + void (*mDeleteSingleton)(); + +public: + /** + * Call this to call the cleanupSingleton() method for every LLSingleton + * constructed since the start of the last cleanupAll() call. (Any + * LLSingleton constructed DURING a cleanupAll() call won't be cleaned up + * until the next cleanupAll() call.) cleanupSingleton() neither deletes + * nor destroys its LLSingleton; therefore it's safe to include logic that + * might take significant realtime or even throw an exception. + * + * The most important property of cleanupAll() is that cleanupSingleton() + * methods are called in dependency order, leaf classes last. Thus, given + * two LLSingleton subclasses A and B, if A's dependency on B is properly + * expressed as a B::getInstance() or B::instance() call during either + * A::A() or A::initSingleton(), B will be cleaned up after A. + * + * If a cleanupSingleton() method throws an exception, the exception is + * logged, but cleanupAll() attempts to continue calling the rest of the + * cleanupSingleton() methods. + */ + static void cleanupAll(); + /** + * Call this to call the deleteSingleton() method for every LLSingleton + * constructed since the start of the last deleteAll() call. (Any + * LLSingleton constructed DURING a deleteAll() call won't be cleaned up + * until the next deleteAll() call.) deleteSingleton() deletes and + * destroys its LLSingleton. Any cleanup logic that might take significant + * realtime -- or throw an exception -- must not be placed in your + * LLSingleton's destructor, but rather in its cleanupSingleton() method. + * + * The most important property of deleteAll() is that deleteSingleton() + * methods are called in dependency order, leaf classes last. Thus, given + * two LLSingleton subclasses A and B, if A's dependency on B is properly + * expressed as a B::getInstance() or B::instance() call during either + * A::A() or A::initSingleton(), B will be cleaned up after A. + * + * If a deleteSingleton() method throws an exception, the exception is + * logged, but deleteAll() attempts to continue calling the rest of the + * deleteSingleton() methods. + */ + static void deleteAll(); +}; + +// support ref_ptr_t +void intrusive_ptr_add_ref(LLSingletonBase::MasterRefcount*); +void intrusive_ptr_release(LLSingletonBase::MasterRefcount*); -// LLSingleton implements the getInstance() method part of the Singleton -// pattern. It can't make the derived class constructors protected, though, so -// you have to do that yourself. -// -// There are two ways to use LLSingleton. The first way is to inherit from it -// while using the typename that you'd like to be static as the template -// parameter, like so: -// -// class Foo: public LLSingleton<Foo>{}; -// -// Foo& instance = Foo::instance(); -// -// The second way is to use the singleton class directly, without inheritance: -// -// typedef LLSingleton<Foo> FooSingleton; -// -// Foo& instance = FooSingleton::instance(); -// -// In this case, the class being managed as a singleton needs to provide an -// initSingleton() method since the LLSingleton virtual method won't be -// available -// -// As currently written, it is not thread-safe. +// Most of the time, we want LLSingleton_manage_master() to forward its +// methods to real LLSingletonBase methods. +template <class T> +struct LLSingleton_manage_master +{ + void add(LLSingletonBase* sb) { sb->add_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(); } +}; +// But for the specific case of LLSingletonBase::MasterList, don't. +template <> +struct LLSingleton_manage_master<LLSingletonBase::MasterList> +{ + void add(LLSingletonBase*) {} + void remove(LLSingletonBase*) {} + void push_initializing(LLSingletonBase*) {} + void pop_initializing (LLSingletonBase*) {} + LLSingletonBase::list_t& get_initializing(LLSingletonBase::MasterList* instance) + { + return LLSingletonBase::get_initializing_from(instance); + } +}; + +// Now we can implement LLSingletonBase's template constructor. template <typename DERIVED_TYPE> -class LLSingleton : private boost::noncopyable +LLSingletonBase::LLSingletonBase(tag<DERIVED_TYPE>): + mCleaned(false), + mDeleteSingleton(NULL) +{ + // Make this the currently-initializing LLSingleton. + LLSingleton_manage_master<DERIVED_TYPE>().push_initializing(this); +} + +/** + * LLSingleton implements the getInstance() method part of the Singleton + * pattern. It can't make the derived class constructors protected, though, so + * you have to do that yourself. + * + * Derive your class from LLSingleton, passing your subclass name as + * LLSingleton's template parameter, like so: + * + * class Foo: public LLSingleton<Foo> + * { + * // use this macro at start of every LLSingleton subclass + * LLSINGLETON(Foo); + * public: + * // ... + * }; + * + * Foo& instance = Foo::instance(); + * + * LLSingleton recognizes a couple special methods in your derived class. + * + * If you override LLSingleton<T>::initSingleton(), your method will be called + * immediately after the instance is constructed. This is useful for breaking + * circular dependencies: if you find that your LLSingleton subclass + * constructor references other LLSingleton subclass instances in a chain + * leading back to yours, move the instance reference from your constructor to + * your initSingleton() method. + * + * If you override LLSingleton<T>::cleanupSingleton(), your method will be + * called if someone calls LLSingletonBase::cleanupAll(). The significant part + * of this promise is that cleanupAll() will call individual + * cleanupSingleton() methods in reverse dependency order. + * + * That is, consider LLSingleton subclasses C, B and A. A depends on B, which + * in turn depends on C. These dependencies are expressed as calls to + * B::instance() or B::getInstance(), and C::instance() or C::getInstance(). + * It shouldn't matter whether these calls appear in A::A() or + * A::initSingleton(), likewise B::B() or B::initSingleton(). + * + * We promise that if you later call LLSingletonBase::cleanupAll(): + * 1. A::cleanupSingleton() will be called before + * 2. B::cleanupSingleton(), which will be called before + * 3. C::cleanupSingleton(). + * Put differently, if your LLSingleton subclass constructor or + * initSingleton() method explicitly depends on some other LLSingleton + * subclass, you may continue to rely on that other subclass in your + * cleanupSingleton() method. + * + * We introduce a special cleanupSingleton() method because cleanupSingleton() + * operations can involve nontrivial realtime, or might throw an exception. A + * destructor should do neither! + * + * If your cleanupSingleton() method throws an exception, we log that + * exception but proceed with the remaining cleanupSingleton() calls. + * + * Similarly, if at some point you call LLSingletonBase::deleteAll(), all + * remaining LLSingleton instances will be destroyed in dependency order. (Or + * call MySubclass::deleteSingleton() to specifically destroy the canonical + * MySubclass instance.) + * + * As currently written, LLSingleton is not thread-safe. + */ +template <typename DERIVED_TYPE> +class LLSingleton : public LLSingletonBase { - private: - typedef enum e_init_state - { - UNINITIALIZED, - CONSTRUCTING, - INITIALIZING, - INITIALIZED, - DELETED - } EInitState; - static DERIVED_TYPE* constructSingleton() { return new DERIVED_TYPE(); } - - // stores pointer to singleton instance - struct SingletonLifetimeManager - { - SingletonLifetimeManager() - { - construct(); - } - - static void construct() - { - sData.mInitState = CONSTRUCTING; - sData.mInstance = constructSingleton(); - sData.mInitState = INITIALIZING; - } - - ~SingletonLifetimeManager() - { - if (sData.mInitState != DELETED) - { - deleteSingleton(); - } - } - }; - + + // We know of no way to instruct the compiler that every subclass + // constructor MUST be private. However, we can make the LLSINGLETON() + // macro both declare a private constructor and provide the required + // friend declaration. How can we ensure that every subclass uses + // LLSINGLETON()? 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 LLSINGLETON(yourclass) in the + // subclass body. + virtual void you_must_use_LLSINGLETON_macro() = 0; + + // stores pointer to singleton instance + struct SingletonLifetimeManager + { + SingletonLifetimeManager(): + mMasterRefcount(LLSingletonBase::get_master_refcount()) + { + construct(); + } + + static void construct() + { + 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. What we really want is to count the + // runtime's attempts to cleanup LLSingleton static data -- and on + // the very last one, call LLSingletonBase::deleteAll(). That + // method will properly honor cross-LLSingleton dependencies. This + // is why we store an intrusive_ptr to a MasterRefcount: our + // ref_ptr_t member counts SingletonLifetimeManager instances. + // Once the runtime destroys the last of these, THEN we can delete + // every remaining LLSingleton. + } + + LLSingletonBase::ref_ptr_t mMasterRefcount; + }; + +protected: + // Pass DERIVED_TYPE explicitly to LLSingletonBase's constructor because, + // until our subclass constructor completes, *this isn't yet a + // full-fledged DERIVED_TYPE. + LLSingleton(): LLSingletonBase(LLSingletonBase::tag<DERIVED_TYPE>()) + { + // 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<DERIVED_TYPE>().add(this); + } + public: - virtual ~LLSingleton() - { - sData.mInstance = NULL; - sData.mInitState = DELETED; - } - - /** - * @brief Immediately delete the singleton. - * - * A subsequent call to LLProxy::getInstance() will construct a new - * instance of the class. - * - * LLSingletons are normally 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() - { - static SingletonLifetimeManager sLifeTimeMgr; - - switch (sData.mInitState) - { - case UNINITIALIZED: - // should never be uninitialized at this point - llassert(false); - return NULL; - case CONSTRUCTING: - LL_ERRS() << "Tried to access singleton " << typeid(DERIVED_TYPE).name() << " from singleton constructor!" << LL_ENDL; - 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(); - return sData.mInstance; - case INITIALIZED: - return sData.mInstance; - case DELETED: - LL_WARNS() << "Trying to access deleted singleton " << typeid(DERIVED_TYPE).name() << " creating new instance" << LL_ENDL; - SingletonLifetimeManager::construct(); - // same as first time construction - sData.mInitState = INITIALIZED; - sData.mInstance->initSingleton(); - return sData.mInstance; - } - - return NULL; - } - - static DERIVED_TYPE* getIfExists() - { - 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 uet? - // Use this to avoid accessing singletons before the can safely be constructed - static bool instanceExists() - { - return sData.mInitState == INITIALIZED; - } - - // Has this singleton already been deleted? - // Use this to avoid accessing singletons from a static object's destructor - static bool destroyed() - { - return sData.mInitState == DELETED; - } + virtual ~LLSingleton() + { + // remove this instance from the master list + LLSingleton_manage_master<DERIVED_TYPE>().remove(this); + sData.mInstance = NULL; + sData.mInitState = DELETED; + } -private: + /** + * @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() + { + static SingletonLifetimeManager sLifeTimeMgr; - virtual void initSingleton() {} + switch (sData.mInitState) + { + case UNINITIALIZED: + // should never be uninitialized at this point + logerrs("Uninitialized singleton ", + demangle(typeid(DERIVED_TYPE).name()).c_str()); + return NULL; - 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; + case CONSTRUCTING: + 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<DERIVED_TYPE>().pop_initializing(sData.mInstance); + break; + + case INITIALIZED: + break; + + case DELETED: + 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<DERIVED_TYPE>().pop_initializing(sData.mInstance); + break; + } + + // By this point, if DERIVED_TYPE was pushed onto the initializing + // stack, it has been popped off. So the top of that stack, if any, is + // an LLSingleton that directly depends on DERIVED_TYPE. If this call + // came from another LLSingleton, rather than from vanilla application + // code, record the dependency. + sData.mInstance->capture_dependency( + LLSingleton_manage_master<DERIVED_TYPE>().get_initializing(sData.mInstance), + sData.mInitState); + 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; + } + +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<typename T> typename LLSingleton<T>::SingletonData LLSingleton<T>::sData; +/** + * Use LLSINGLETON(Foo); at the start of an LLSingleton<Foo> subclass body + * when you want to declare an out-of-line constructor: + * + * @code + * class Foo: public LLSingleton<Foo> + * { + * // use this macro at start of every LLSingleton subclass + * LLSINGLETON(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. If it's literally + * trivial, use LLSINGLETON_EMPTY_CTOR(); if not, use LLSINGLETON() 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 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() + +/** + * Use LLSINGLETON_EMPTY_CTOR(Foo); at the start of an LLSingleton<Foo> + * subclass body when the constructor is trivial: + * + * @code + * class Foo: public LLSingleton<Foo> + * { + * // use this macro at start of every LLSingleton subclass + * LLSINGLETON_EMPTY_CTOR(Foo); + * public: + * // ... + * }; + * @endcode + */ +#define LLSINGLETON_EMPTY_CTOR(DERIVED_CLASS) \ + /* LLSINGLETON() is carefully implemented to permit exactly this */ \ + LLSINGLETON(DERIVED_CLASS) {} + #endif diff --git a/indra/llcommon/tests/llheteromap_test.cpp b/indra/llcommon/tests/llheteromap_test.cpp new file mode 100644 index 0000000000..686bffb878 --- /dev/null +++ b/indra/llcommon/tests/llheteromap_test.cpp @@ -0,0 +1,163 @@ +/** + * @file llheteromap_test.cpp + * @author Nat Goodspeed + * @date 2016-10-12 + * @brief Test for llheteromap. + * + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llheteromap.h" +// STL headers +#include <set> +// std headers +// external library headers + +// (pacify clang) +std::ostream& operator<<(std::ostream& out, const std::set<std::string>& strset); +// other Linden headers +#include "../test/lltut.h" + +static std::string clog; +static std::set<std::string> dlog; + +// want to be able to use ensure_equals() on a set<string> +std::ostream& operator<<(std::ostream& out, const std::set<std::string>& strset) +{ + out << '{'; + const char* delim = ""; + for (std::set<std::string>::const_iterator si(strset.begin()), se(strset.end()); + si != se; ++si) + { + out << delim << '"' << *si << '"'; + delim = ", "; + } + out << '}'; + return out; +} + +// unrelated test classes +struct Chalk +{ + int dummy; + std::string name; + + Chalk(): + dummy(0) + { + clog.append("a"); + } + + ~Chalk() + { + dlog.insert("a"); + } + +private: + Chalk(const Chalk&); // no implementation +}; + +struct Cheese +{ + std::string name; + + Cheese() + { + clog.append("e"); + } + + ~Cheese() + { + dlog.insert("e"); + } + +private: + Cheese(const Cheese&); // no implementation +}; + +struct Chowdah +{ + char displace[17]; + std::string name; + + Chowdah() + { + displace[0] = '\0'; + clog.append("o"); + } + + ~Chowdah() + { + dlog.insert("o"); + } + +private: + Chowdah(const Chowdah&); // no implementation +}; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llheteromap_data + { + llheteromap_data() + { + clog.erase(); + dlog.clear(); + } + }; + typedef test_group<llheteromap_data> llheteromap_group; + typedef llheteromap_group::object object; + llheteromap_group llheteromapgrp("llheteromap"); + + template<> template<> + void object::test<1>() + { + set_test_name("create, get, delete"); + + { + LLHeteroMap map; + + { + // create each instance + Chalk& chalk = map.obtain<Chalk>(); + chalk.name = "Chalk"; + + Cheese& cheese = map.obtain<Cheese>(); + cheese.name = "Cheese"; + + Chowdah& chowdah = map.obtain<Chowdah>(); + chowdah.name = "Chowdah"; + } // refs go out of scope + + { + // verify each instance + Chalk& chalk = map.obtain<Chalk>(); + ensure_equals(chalk.name, "Chalk"); + + Cheese& cheese = map.obtain<Cheese>(); + ensure_equals(cheese.name, "Cheese"); + + Chowdah& chowdah = map.obtain<Chowdah>(); + ensure_equals(chowdah.name, "Chowdah"); + } + } // destroy map + + // Chalk, Cheese and Chowdah should have been created in specific order + ensure_equals(clog, "aeo"); + + // We don't care what order they're destroyed in, as long as each is + // appropriately destroyed. + std::set<std::string> dtorset; + for (const char* cp = "aeo"; *cp; ++cp) + dtorset.insert(std::string(1, *cp)); + ensure_equals(dlog, dtorset); + } +} // namespace tut diff --git a/indra/llcommon/tests/llpounceable_test.cpp b/indra/llcommon/tests/llpounceable_test.cpp new file mode 100644 index 0000000000..2f4915ce11 --- /dev/null +++ b/indra/llcommon/tests/llpounceable_test.cpp @@ -0,0 +1,230 @@ +/** + * @file llpounceable_test.cpp + * @author Nat Goodspeed + * @date 2015-05-22 + * @brief Test for llpounceable. + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Copyright (c) 2015, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llpounceable.h" +// STL headers +// std headers +// external library headers +#include <boost/bind.hpp> +// other Linden headers +#include "../test/lltut.h" + +/*----------------------------- string testing -----------------------------*/ +void append(std::string* dest, const std::string& src) +{ + dest->append(src); +} + +/*-------------------------- Data-struct testing ---------------------------*/ +struct Data +{ + Data(const std::string& data): + mData(data) + {} + const std::string mData; +}; + +void setter(Data** dest, Data* ptr) +{ + *dest = ptr; +} + +static Data* static_check = 0; + +// Set up an extern pointer to an LLPounceableStatic so the linker will fill +// in the forward reference from below, before runtime. +extern LLPounceable<Data*, LLPounceableStatic> gForward; + +struct EnqueueCall +{ + EnqueueCall() + { + // Intentionally use a forward reference to an LLPounceableStatic that + // we believe is NOT YET CONSTRUCTED. This models the scenario in + // which a constructor in another translation unit runs before + // constructors in this one. We very specifically want callWhenReady() + // to work even in that case: we need the LLPounceableQueueImpl to be + // initialized even if the LLPounceable itself is not. + gForward.callWhenReady(boost::bind(setter, &static_check, _1)); + } +} nqcall; +// When this declaration is processed, we should enqueue the +// setter(&static_check, _1) call for when gForward is set non-NULL. Needless +// to remark, we want this call not to crash. + +// Now declare gForward. Its constructor should not run until after nqcall's. +LLPounceable<Data*, LLPounceableStatic> gForward; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llpounceable_data + { + }; + typedef test_group<llpounceable_data> llpounceable_group; + typedef llpounceable_group::object object; + llpounceable_group llpounceablegrp("llpounceable"); + + template<> template<> + void object::test<1>() + { + set_test_name("LLPounceableStatic out-of-order test"); + // LLPounceable<T, LLPounceableStatic>::callWhenReady() must work even + // before LLPounceable's constructor runs. That's the whole point of + // implementing it with an LLSingleton queue. This models (say) + // LLPounceableStatic<LLMessageSystem*, LLPounceableStatic>. + ensure("static_check should still be null", ! static_check); + Data myData("test<1>"); + gForward = &myData; // should run setter + ensure_equals("static_check should be &myData", static_check, &myData); + } + + template<> template<> + void object::test<2>() + { + set_test_name("LLPounceableQueue different queues"); + // We expect that LLPounceable<T, LLPounceableQueue> should have + // different queues because that specialization stores the queue + // directly in the LLPounceable instance. + Data *aptr = 0, *bptr = 0; + LLPounceable<Data*> a, b; + a.callWhenReady(boost::bind(setter, &aptr, _1)); + b.callWhenReady(boost::bind(setter, &bptr, _1)); + ensure("aptr should be null", ! aptr); + ensure("bptr should be null", ! bptr); + Data adata("a"), bdata("b"); + a = &adata; + ensure_equals("aptr should be &adata", aptr, &adata); + // but we haven't yet set b + ensure("bptr should still be null", !bptr); + b = &bdata; + ensure_equals("bptr should be &bdata", bptr, &bdata); + } + + template<> template<> + void object::test<3>() + { + set_test_name("LLPounceableStatic different queues"); + // LLPounceable<T, LLPounceableStatic> should also have a distinct + // queue for each instance, but that engages an additional map lookup + // because there's only one LLSingleton for each T. + Data *aptr = 0, *bptr = 0; + LLPounceable<Data*, LLPounceableStatic> a, b; + a.callWhenReady(boost::bind(setter, &aptr, _1)); + b.callWhenReady(boost::bind(setter, &bptr, _1)); + ensure("aptr should be null", ! aptr); + ensure("bptr should be null", ! bptr); + Data adata("a"), bdata("b"); + a = &adata; + ensure_equals("aptr should be &adata", aptr, &adata); + // but we haven't yet set b + ensure("bptr should still be null", !bptr); + b = &bdata; + ensure_equals("bptr should be &bdata", bptr, &bdata); + } + + template<> template<> + void object::test<4>() + { + set_test_name("LLPounceable<T> looks like T"); + // We want LLPounceable<T, TAG> to be drop-in replaceable for a plain + // T for read constructs. In particular, it should behave like a dumb + // pointer -- and with zero abstraction cost for such usage. + Data* aptr = 0; + Data a("a"); + // should be able to initialize a pounceable (when its constructor + // runs) + LLPounceable<Data*> pounceable(&a); + // should be able to pass LLPounceable<T> to function accepting T + setter(&aptr, pounceable); + ensure_equals("aptr should be &a", aptr, &a); + // should be able to dereference with * + ensure_equals("deref with *", (*pounceable).mData, "a"); + // should be able to dereference with -> + ensure_equals("deref with ->", pounceable->mData, "a"); + // bool operations + ensure("test with operator bool()", pounceable); + ensure("test with operator !()", ! (! pounceable)); + } + + template<> template<> + void object::test<5>() + { + set_test_name("Multiple callWhenReady() queue items"); + Data *p1 = 0, *p2 = 0, *p3 = 0; + Data a("a"); + LLPounceable<Data*> pounceable; + // queue up a couple setter() calls for later + pounceable.callWhenReady(boost::bind(setter, &p1, _1)); + pounceable.callWhenReady(boost::bind(setter, &p2, _1)); + // should still be pending + ensure("p1 should be null", !p1); + ensure("p2 should be null", !p2); + ensure("p3 should be null", !p3); + pounceable = 0; + // assigning a new empty value shouldn't flush the queue + ensure("p1 should still be null", !p1); + ensure("p2 should still be null", !p2); + ensure("p3 should still be null", !p3); + // using whichever syntax + pounceable.reset(0); + // try to make ensure messages distinct... tough to pin down which + // ensure() failed if multiple ensure() calls in the same test<n> have + // the same message! + ensure("p1 should again be null", !p1); + ensure("p2 should again be null", !p2); + ensure("p3 should again be null", !p3); + pounceable.reset(&a); // should flush queue + ensure_equals("p1 should be &a", p1, &a); + ensure_equals("p2 should be &a", p2, &a); + ensure("p3 still not set", !p3); + // immediate call + pounceable.callWhenReady(boost::bind(setter, &p3, _1)); + ensure_equals("p3 should be &a", p3, &a); + } + + template<> template<> + void object::test<6>() + { + set_test_name("queue order"); + std::string data; + LLPounceable<std::string*> pounceable; + pounceable.callWhenReady(boost::bind(append, _1, "a")); + pounceable.callWhenReady(boost::bind(append, _1, "b")); + pounceable.callWhenReady(boost::bind(append, _1, "c")); + pounceable = &data; + ensure_equals("callWhenReady() must preserve chronological order", + data, "abc"); + + std::string data2; + pounceable = NULL; + pounceable.callWhenReady(boost::bind(append, _1, "d")); + pounceable.callWhenReady(boost::bind(append, _1, "e")); + pounceable.callWhenReady(boost::bind(append, _1, "f")); + pounceable = &data2; + ensure_equals("LLPounceable must reset queue when fired", + data2, "def"); + } + + template<> template<> + void object::test<7>() + { + set_test_name("compile-fail test, uncomment to check"); + // The following declaration should fail: only LLPounceableQueue and + // LLPounceableStatic should work as tags. +// LLPounceable<Data*, int> pounceable; + } +} // namespace tut diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index 385289aefe..56886bc73f 100644 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -30,47 +30,172 @@ #include "llsingleton.h" #include "../test/lltut.h" + +// Capture execution sequence by appending to log string. +std::string sLog; + +#define DECLARE_CLASS(CLS) \ +struct CLS: public LLSingleton<CLS> \ +{ \ + LLSINGLETON(CLS); \ + ~CLS(); \ +public: \ + static enum dep_flag { \ + DEP_NONE, /* no dependency */ \ + DEP_CTOR, /* dependency in ctor */ \ + DEP_INIT /* dependency in initSingleton */ \ + } sDepFlag; \ + \ + void initSingleton(); \ + void cleanupSingleton(); \ +}; \ + \ +CLS::dep_flag CLS::sDepFlag = DEP_NONE + +DECLARE_CLASS(A); +DECLARE_CLASS(B); + +#define DEFINE_MEMBERS(CLS, OTHER) \ +CLS::CLS() \ +{ \ + sLog.append(#CLS); \ + if (sDepFlag == DEP_CTOR) \ + { \ + (void)OTHER::instance(); \ + } \ +} \ + \ +void CLS::initSingleton() \ +{ \ + sLog.append("i" #CLS); \ + if (sDepFlag == DEP_INIT) \ + { \ + (void)OTHER::instance(); \ + } \ +} \ + \ +void CLS::cleanupSingleton() \ +{ \ + sLog.append("x" #CLS); \ +} \ + \ +CLS::~CLS() \ +{ \ + sLog.append("~" #CLS); \ +} + +DEFINE_MEMBERS(A, B) +DEFINE_MEMBERS(B, A) + namespace tut { - struct singleton - { - // We need a class created with the LLSingleton template to test with. - class LLSingletonTest: public LLSingleton<LLSingletonTest> - { - - }; - }; - - typedef test_group<singleton> singleton_t; - typedef singleton_t::object singleton_object_t; - tut::singleton_t tut_singleton("LLSingleton"); - - template<> template<> - void singleton_object_t::test<1>() - { - - } - template<> template<> - void singleton_object_t::test<2>() - { - LLSingletonTest* singleton_test = LLSingletonTest::getInstance(); - ensure(singleton_test); - } - template<> template<> - void singleton_object_t::test<3>() - { - //Construct the instance - LLSingletonTest::getInstance(); - ensure(LLSingletonTest::instanceExists()); - - //Delete the instance - LLSingletonTest::deleteSingleton(); - ensure(LLSingletonTest::destroyed()); - ensure(!LLSingletonTest::instanceExists()); - - //Construct it again. - LLSingletonTest* singleton_test = LLSingletonTest::getInstance(); - ensure(singleton_test); - ensure(LLSingletonTest::instanceExists()); - } + struct singleton + { + // We need a class created with the LLSingleton template to test with. + class LLSingletonTest: public LLSingleton<LLSingletonTest> + { + LLSINGLETON_EMPTY_CTOR(LLSingletonTest); + }; + }; + + typedef test_group<singleton> singleton_t; + typedef singleton_t::object singleton_object_t; + tut::singleton_t tut_singleton("LLSingleton"); + + template<> template<> + void singleton_object_t::test<1>() + { + + } + template<> template<> + void singleton_object_t::test<2>() + { + LLSingletonTest* singleton_test = LLSingletonTest::getInstance(); + ensure(singleton_test); + } + + template<> template<> + void singleton_object_t::test<3>() + { + //Construct the instance + LLSingletonTest::getInstance(); + ensure(LLSingletonTest::instanceExists()); + + //Delete the instance + LLSingletonTest::deleteSingleton(); + ensure(!LLSingletonTest::instanceExists()); + + //Construct it again. + LLSingletonTest* singleton_test = LLSingletonTest::getInstance(); + ensure(singleton_test); + ensure(LLSingletonTest::instanceExists()); + } + +#define TESTS(CLS, OTHER, N0, N1, N2, N3) \ + template<> template<> \ + void singleton_object_t::test<N0>() \ + { \ + set_test_name("just " #CLS); \ + CLS::sDepFlag = CLS::DEP_NONE; \ + OTHER::sDepFlag = OTHER::DEP_NONE; \ + sLog.clear(); \ + \ + (void)CLS::instance(); \ + ensure_equals(sLog, #CLS "i" #CLS); \ + LLSingletonBase::cleanupAll(); \ + ensure_equals(sLog, #CLS "i" #CLS "x" #CLS); \ + LLSingletonBase::deleteAll(); \ + ensure_equals(sLog, #CLS "i" #CLS "x" #CLS "~" #CLS); \ + } \ + \ + template<> template<> \ + void singleton_object_t::test<N1>() \ + { \ + set_test_name(#CLS " ctor depends " #OTHER); \ + CLS::sDepFlag = CLS::DEP_CTOR; \ + OTHER::sDepFlag = OTHER::DEP_NONE; \ + sLog.clear(); \ + \ + (void)CLS::instance(); \ + ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS); \ + LLSingletonBase::cleanupAll(); \ + ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER); \ + LLSingletonBase::deleteAll(); \ + ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + } \ + \ + template<> template<> \ + void singleton_object_t::test<N2>() \ + { \ + set_test_name(#CLS " init depends " #OTHER); \ + CLS::sDepFlag = CLS::DEP_INIT; \ + OTHER::sDepFlag = OTHER::DEP_NONE; \ + sLog.clear(); \ + \ + (void)CLS::instance(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \ + LLSingletonBase::cleanupAll(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \ + LLSingletonBase::deleteAll(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + } \ + \ + template<> template<> \ + void singleton_object_t::test<N3>() \ + { \ + set_test_name(#CLS " circular init"); \ + CLS::sDepFlag = CLS::DEP_INIT; \ + OTHER::sDepFlag = OTHER::DEP_CTOR; \ + sLog.clear(); \ + \ + (void)CLS::instance(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \ + LLSingletonBase::cleanupAll(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \ + LLSingletonBase::deleteAll(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + } + + TESTS(A, B, 4, 5, 6, 7) + TESTS(B, A, 8, 9, 10, 11) } |