summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/CMakeLists.txt22
-rw-r--r--indra/llcommon/llapp.cpp75
-rw-r--r--indra/llcommon/llapp.h16
-rw-r--r--indra/llcommon/llassettype.cpp3
-rw-r--r--indra/llcommon/llassettype.h2
-rw-r--r--indra/llcommon/llcleanup.cpp29
-rw-r--r--indra/llcommon/llcleanup.h33
-rw-r--r--indra/llcommon/llcommon.cpp5
-rw-r--r--indra/llcommon/llcoro_get_id.cpp32
-rw-r--r--indra/llcommon/llcoro_get_id.h30
-rw-r--r--indra/llcommon/llcoros.cpp159
-rw-r--r--indra/llcommon/llcoros.h29
-rw-r--r--indra/llcommon/llerror.cpp55
-rw-r--r--indra/llcommon/llerror.h1
-rw-r--r--indra/llcommon/llerrorcontrol.h5
-rw-r--r--indra/llcommon/lleventfilter.cpp245
-rw-r--r--indra/llcommon/lleventfilter.h161
-rw-r--r--indra/llcommon/llevents.cpp7
-rw-r--r--indra/llcommon/llevents.h3
-rw-r--r--indra/llcommon/llfile.cpp4
-rw-r--r--indra/llcommon/llfile.h2
-rw-r--r--indra/llcommon/llheteromap.cpp32
-rw-r--r--indra/llcommon/llheteromap.h95
-rw-r--r--indra/llcommon/llinitdestroyclass.cpp30
-rw-r--r--indra/llcommon/llinitdestroyclass.h175
-rw-r--r--indra/llcommon/llinitparam.h1
-rw-r--r--indra/llcommon/llmetricperformancetester.cpp16
-rw-r--r--indra/llcommon/llmetricperformancetester.h12
-rw-r--r--indra/llcommon/llpounceable.h216
-rw-r--r--indra/llcommon/llregistry.h7
-rw-r--r--indra/llcommon/llsdparam.h1
-rw-r--r--indra/llcommon/llsdserialize.cpp43
-rw-r--r--indra/llcommon/llsingleton.cpp462
-rw-r--r--indra/llcommon/llsingleton.h637
-rw-r--r--indra/llcommon/llstring.h27
-rw-r--r--indra/llcommon/llsys.h5
-rw-r--r--indra/llcommon/lluri.cpp7
-rw-r--r--indra/llcommon/lluri.h8
-rw-r--r--indra/llcommon/tests/listener.h11
-rw-r--r--indra/llcommon/tests/lleventfilter_test.cpp124
-rw-r--r--indra/llcommon/tests/llheteromap_test.cpp163
-rw-r--r--indra/llcommon/tests/llpounceable_test.cpp230
-rw-r--r--indra/llcommon/tests/llsingleton_test.cpp207
43 files changed, 3113 insertions, 314 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 5bce2b8809..aa76a57f1d 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -41,8 +41,10 @@ set(llcommon_SOURCE_FILES
llbitpack.cpp
llcallbacklist.cpp
llcallstack.cpp
+ llcleanup.cpp
llcommon.cpp
llcommonutils.cpp
+ llcoro_get_id.cpp
llcoros.cpp
llcrc.cpp
llcriticaldamp.cpp
@@ -67,7 +69,9 @@ set(llcommon_SOURCE_FILES
llformat.cpp
llframetimer.cpp
llheartbeat.cpp
+ llheteromap.cpp
llinitparam.cpp
+ llinitdestroyclass.cpp
llinstancetracker.cpp
llleap.cpp
llleaplistener.cpp
@@ -138,8 +142,10 @@ set(llcommon_HEADER_FILES
llboost.h
llcallbacklist.h
llcallstack.h
+ llcleanup.h
llcommon.h
llcommonutils.h
+ llcoro_get_id.h
llcoros.h
llcrc.h
llcriticaldamp.h
@@ -171,7 +177,9 @@ set(llcommon_HEADER_FILES
llhandle.h
llhash.h
llheartbeat.h
+ llheteromap.h
llindexedvector.h
+ llinitdestroyclass.h
llinitparam.h
llinstancetracker.h
llkeythrottle.h
@@ -188,6 +196,7 @@ set(llcommon_HEADER_FILES
llmortician.h
llnametable.h
llpointer.h
+ llpounceable.h
llpredicate.h
llpreprocessor.h
llpriqueuemap.h
@@ -315,24 +324,27 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llerror "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lleventfilter "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llframetimer "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltrace "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llunits "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${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..6cc9e804d4 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
@@ -255,6 +256,70 @@ bool LLApp::parseCommandOptions(int argc, char** argv)
return true;
}
+bool LLApp::parseCommandOptions(int argc, wchar_t** wargv)
+{
+ LLSD commands;
+ std::string name;
+ std::string value;
+ for(int ii = 1; ii < argc; ++ii)
+ {
+ if(wargv[ii][0] != '-')
+ {
+ LL_INFOS() << "Did not find option identifier while parsing token: "
+ << wargv[ii] << LL_ENDL;
+ return false;
+ }
+ int offset = 1;
+ if(wargv[ii][1] == '-') ++offset;
+
+#if LL_WINDOWS
+ name.assign(utf16str_to_utf8str(&wargv[ii][offset]));
+#else
+ name.assign(wstring_to_utf8str(&wargv[ii][offset]));
+#endif
+ if(((ii+1) >= argc) || (wargv[ii+1][0] == '-'))
+ {
+ // we found another option after this one or we have
+ // reached the end. simply record that this option was
+ // found and continue.
+ int flag = name.compare("logfile");
+ if (0 == flag)
+ {
+ commands[name] = "log";
+ }
+ else
+ {
+ commands[name] = true;
+ }
+
+ continue;
+ }
+ ++ii;
+
+#if LL_WINDOWS
+ value.assign(utf16str_to_utf8str((wargv[ii])));
+#else
+ value.assign(wstring_to_utf8str((wargv[ii])));
+#endif
+
+#if LL_WINDOWS
+ //Windows changed command line parsing. Deal with it.
+ S32 slen = value.length() - 1;
+ S32 start = 0;
+ S32 end = slen;
+ if (wargv[ii][start]=='"')start++;
+ if (wargv[ii][end]=='"')end--;
+ if (start!=0 || end!=slen)
+ {
+ value = value.substr (start,end);
+ }
+#endif
+
+ commands[name] = value;
+ }
+ setOptionData(PRIORITY_COMMAND_LINE, commands);
+ return true;
+}
void LLApp::manageLiveFile(LLLiveFile* livefile)
{
@@ -353,7 +418,7 @@ void LLApp::setupErrorHandling(bool second_instance)
std::wstring wpipe_name;
wpipe_name = mCrashReportPipeStr + wstringize(getPid());
- const std::wstring wdump_path(wstringize(mDumpPath));
+ const std::wstring wdump_path(utf8str_to_utf16str(mDumpPath));
int retries = 30;
for (; retries > 0; --retries)
@@ -514,9 +579,9 @@ void LLApp::setMiniDumpDir(const std::string &path)
if(mExceptionHandler == 0) return;
#ifdef LL_WINDOWS
- wchar_t buffer[MAX_MINDUMP_PATH_LENGTH];
- mbstowcs(buffer, mDumpPath.c_str(), MAX_MINDUMP_PATH_LENGTH);
- mExceptionHandler->set_dump_path(std::wstring(buffer));
+ std::wstring buffer(utf8str_to_utf16str(mDumpPath));
+ if (buffer.size() > MAX_MINDUMP_PATH_LENGTH) buffer.resize(MAX_MINDUMP_PATH_LENGTH);
+ mExceptionHandler->set_dump_path(buffer);
#elif LL_LINUX
//google_breakpad::MinidumpDescriptor desc("/tmp"); //path works in debug fails in production inside breakpad lib so linux gets a little less stack reporting until it is patched.
google_breakpad::MinidumpDescriptor desc(mDumpPath); //path works in debug fails in production inside breakpad lib so linux gets a little less stack reporting until it is patched.
diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h
index ff9a92b45f..acd829d864 100644
--- a/indra/llcommon/llapp.h
+++ b/indra/llcommon/llapp.h
@@ -106,7 +106,7 @@ public:
LLSD getOption(const std::string& name) const;
/**
- * @brief Parse command line options and insert them into
+ * @brief Parse ASCII command line options and insert them into
* application command line options.
*
* The name inserted into the option will have leading option
@@ -119,6 +119,20 @@ public:
*/
bool parseCommandOptions(int argc, char** argv);
+ /**
+ * @brief Parse Unicode command line options and insert them into
+ * application command line options.
+ *
+ * The name inserted into the option will have leading option
+ * identifiers (a minus or double minus) stripped. All options
+ * with values will be stored as a string, while all options
+ * without values will be stored as true.
+ * @param argc The argc passed into main().
+ * @param wargv The wargv passed into main().
+ * @return Returns true if the parse succeeded.
+ */
+ bool parseCommandOptions(int argc, wchar_t** wargv);
+
/**
* @brief Keep track of live files automatically.
*
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/llassettype.h b/indra/llcommon/llassettype.h
index 3a4b5dad18..b849be9f16 100644
--- a/indra/llcommon/llassettype.h
+++ b/indra/llcommon/llassettype.h
@@ -152,7 +152,7 @@ public:
static bool lookupIsAssetFetchByIDAllowed(EType asset_type); // the asset allows direct download
static bool lookupIsAssetIDKnowable(EType asset_type); // asset data can be known by the viewer
-
+
static const std::string& badLookup(); // error string when a lookup fails
protected:
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..934f04287d 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -40,32 +40,83 @@
#include "stringize.h"
#include "llexception.h"
-// do nothing, when we need nothing done
+#if LL_WINDOWS
+#include <excpt.h>
+#endif
+
+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 +131,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 +267,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)
@@ -228,9 +276,46 @@ void LLCoros::setStackSize(S32 stacksize)
mStackSize = stacksize;
}
+#if LL_WINDOWS
+
+static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific
+
+U32 exception_filter(U32 code, struct _EXCEPTION_POINTERS *exception_infop)
+{
+ if (code == STATUS_MSC_EXCEPTION)
+ {
+ // C++ exception, go on
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+ else
+ {
+ // handle it
+ return EXCEPTION_EXECUTE_HANDLER;
+ }
+}
+
+void LLCoros::winlevel(const callable_t& callable)
+{
+ __try
+ {
+ callable();
+ }
+ __except (exception_filter(GetExceptionCode(), GetExceptionInformation()))
+ {
+ // convert to C++ styled exception
+ // Note: it might be better to use _se_set_translator
+ // if you want exception to inherit full callstack
+ char integer_string[32];
+ sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode());
+ throw std::exception(integer_string);
+ }
+}
+
+#endif
+
// 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
@@ -238,7 +323,11 @@ void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& calla
// run the code the caller actually wants in the coroutine
try
{
+#if LL_WINDOWS
+ winlevel(callable);
+#else
callable();
+#endif
}
catch (const LLContinueError&)
{
@@ -254,8 +343,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 +367,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 +378,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..884d6b159c 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,13 +176,15 @@ 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;
static void no_cleanup(CoroData*);
+#if LL_WINDOWS
+ static void winlevel(const callable_t& callable);
+#endif
static void toplevel(coro::self& self, CoroData* data, const callable_t& callable);
static CoroData& get_CoroData(const std::string& caller);
@@ -222,8 +227,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 +250,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 7887a942e9..9c49f7eff4 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
@@ -559,7 +572,7 @@ namespace LLError
mFunctionString += std::string(mFunction) + ":";
for (size_t i = 0; i < mTagCount; i++)
{
- mTagString += std::string("#") + mTags[i] + ((i == mTagCount - 1) ? "" : ",");
+ mTagString += std::string("#") + mTags[i] + ((i == mTagCount - 1) ? " " : ",");
}
}
diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h
index 3573b3f44e..ceb1fd2c5f 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/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp
index 64ab58adcd..9fb18dc67d 100644
--- a/indra/llcommon/lleventfilter.cpp
+++ b/indra/llcommon/lleventfilter.cpp
@@ -38,12 +38,18 @@
#include "llerror.h" // LL_ERRS
#include "llsdutil.h" // llsd_matches()
+/*****************************************************************************
+* LLEventFilter
+*****************************************************************************/
LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak):
LLEventStream(name, tweak),
mSource(source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1)))
{
}
+/*****************************************************************************
+* LLEventMatching
+*****************************************************************************/
LLEventMatching::LLEventMatching(const LLSD& pattern):
LLEventFilter("matching"),
mPattern(pattern)
@@ -64,6 +70,9 @@ bool LLEventMatching::post(const LLSD& event)
return LLEventStream::post(event);
}
+/*****************************************************************************
+* LLEventTimeoutBase
+*****************************************************************************/
LLEventTimeoutBase::LLEventTimeoutBase():
LLEventFilter("timeout")
{
@@ -148,6 +157,14 @@ bool LLEventTimeoutBase::tick(const LLSD&)
return false; // show event to other listeners
}
+bool LLEventTimeoutBase::running() const
+{
+ return mMainloop.connected();
+}
+
+/*****************************************************************************
+* LLEventTimeout
+*****************************************************************************/
LLEventTimeout::LLEventTimeout() {}
LLEventTimeout::LLEventTimeout(LLEventPump& source):
@@ -164,3 +181,231 @@ bool LLEventTimeout::countdownElapsed() const
{
return mTimer.hasExpired();
}
+
+/*****************************************************************************
+* LLEventBatch
+*****************************************************************************/
+LLEventBatch::LLEventBatch(std::size_t size):
+ LLEventFilter("batch"),
+ mBatchSize(size)
+{}
+
+LLEventBatch::LLEventBatch(LLEventPump& source, std::size_t size):
+ LLEventFilter(source, "batch"),
+ mBatchSize(size)
+{}
+
+void LLEventBatch::flush()
+{
+ // copy and clear mBatch BEFORE posting to avoid weird circularity effects
+ LLSD batch(mBatch);
+ mBatch.clear();
+ LLEventStream::post(batch);
+}
+
+bool LLEventBatch::post(const LLSD& event)
+{
+ mBatch.append(event);
+ // calling setSize(same) performs the very check we want
+ setSize(mBatchSize);
+ return false;
+}
+
+void LLEventBatch::setSize(std::size_t size)
+{
+ mBatchSize = size;
+ // changing the size might mean that we have to flush NOW
+ if (mBatch.size() >= mBatchSize)
+ {
+ flush();
+ }
+}
+
+/*****************************************************************************
+* LLEventThrottleBase
+*****************************************************************************/
+LLEventThrottleBase::LLEventThrottleBase(F32 interval):
+ LLEventFilter("throttle"),
+ mInterval(interval),
+ mPosts(0)
+{}
+
+LLEventThrottleBase::LLEventThrottleBase(LLEventPump& source, F32 interval):
+ LLEventFilter(source, "throttle"),
+ mInterval(interval),
+ mPosts(0)
+{}
+
+void LLEventThrottleBase::flush()
+{
+ // flush() is a no-op unless there's something pending.
+ // Don't test mPending because there's no requirement that the consumer
+ // post() anything but an isUndefined(). This is what mPosts is for.
+ if (mPosts)
+ {
+ mPosts = 0;
+ alarmCancel();
+ // This is not to set our alarm; we are not yet requesting
+ // any notification. This is just to track whether subsequent post()
+ // calls fall within this mInterval or not.
+ timerSet(mInterval);
+ // copy and clear mPending BEFORE posting to avoid weird circularity
+ // effects
+ LLSD pending = mPending;
+ mPending.clear();
+ LLEventStream::post(pending);
+ }
+}
+
+LLSD LLEventThrottleBase::pending() const
+{
+ return mPending;
+}
+
+bool LLEventThrottleBase::post(const LLSD& event)
+{
+ // Always capture most recent post() event data. If caller wants to
+ // aggregate multiple events, let them retrieve pending() and modify
+ // before calling post().
+ mPending = event;
+ // Always increment mPosts. Unless we count this call, flush() does
+ // nothing.
+ ++mPosts;
+ // We reset mTimer on every flush() call to let us know if we're still
+ // within the same mInterval. So -- are we?
+ F32 timeRemaining = timerGetRemaining();
+ if (! timeRemaining)
+ {
+ // more than enough time has elapsed, immediately flush()
+ flush();
+ }
+ else
+ {
+ // still within mInterval of the last flush() call: have to defer
+ if (! alarmRunning())
+ {
+ // timeRemaining tells us how much longer it will be until
+ // mInterval seconds since the last flush() call. At that time,
+ // flush() deferred events.
+ alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this));
+ }
+ }
+ return false;
+}
+
+void LLEventThrottleBase::setInterval(F32 interval)
+{
+ F32 oldInterval = mInterval;
+ mInterval = interval;
+ // If we are not now within oldInterval of the last flush(), we're done:
+ // this will only affect behavior starting with the next flush().
+ F32 timeRemaining = timerGetRemaining();
+ if (timeRemaining)
+ {
+ // We are currently within oldInterval of the last flush(). Figure out
+ // how much time remains until (the new) mInterval of the last
+ // flush(). Bt we don't actually store a timestamp for the last
+ // flush(); it's implicit. There are timeRemaining seconds until what
+ // used to be the end of the interval. Move that endpoint by the
+ // difference between the new interval and the old.
+ timeRemaining += (mInterval - oldInterval);
+ // If we're called with a larger interval, the difference is positive
+ // and timeRemaining increases.
+ // If we're called with a smaller interval, the difference is negative
+ // and timeRemaining decreases. The interesting case is when it goes
+ // nonpositive: when the new interval means we can flush immediately.
+ if (timeRemaining <= 0.0f)
+ {
+ flush();
+ }
+ else
+ {
+ // immediately reset mTimer
+ timerSet(timeRemaining);
+ // and if mAlarm is running, reset that too
+ if (alarmRunning())
+ {
+ alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this));
+ }
+ }
+ }
+}
+
+F32 LLEventThrottleBase::getDelay() const
+{
+ return timerGetRemaining();
+}
+
+/*****************************************************************************
+* LLEventThrottle implementation
+*****************************************************************************/
+LLEventThrottle::LLEventThrottle(F32 interval):
+ LLEventThrottleBase(interval)
+{}
+
+LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval):
+ LLEventThrottleBase(source, interval)
+{}
+
+void LLEventThrottle::alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action)
+{
+ mAlarm.actionAfter(interval, action);
+}
+
+bool LLEventThrottle::alarmRunning() const
+{
+ return mAlarm.running();
+}
+
+void LLEventThrottle::alarmCancel()
+{
+ return mAlarm.cancel();
+}
+
+void LLEventThrottle::timerSet(F32 interval)
+{
+ mTimer.setTimerExpirySec(interval);
+}
+
+F32 LLEventThrottle::timerGetRemaining() const
+{
+ return mTimer.getRemainingTimeF32();
+}
+
+/*****************************************************************************
+* LLEventBatchThrottle
+*****************************************************************************/
+LLEventBatchThrottle::LLEventBatchThrottle(F32 interval, std::size_t size):
+ LLEventThrottle(interval),
+ mBatchSize(size)
+{}
+
+LLEventBatchThrottle::LLEventBatchThrottle(LLEventPump& source, F32 interval, std::size_t size):
+ LLEventThrottle(source, interval),
+ mBatchSize(size)
+{}
+
+bool LLEventBatchThrottle::post(const LLSD& event)
+{
+ // simply retrieve pending value and append the new event to it
+ LLSD partial = pending();
+ partial.append(event);
+ bool ret = LLEventThrottle::post(partial);
+ // The post() call above MIGHT have called flush() already. If it did,
+ // then pending() was reset to empty. If it did not, though, but the batch
+ // size has grown to the limit, flush() anyway. If there's a limit at all,
+ // of course. Calling setSize(same) performs the very check we want.
+ setSize(mBatchSize);
+ return ret;
+}
+
+void LLEventBatchThrottle::setSize(std::size_t size)
+{
+ mBatchSize = size;
+ // Changing the size might mean that we have to flush NOW. Don't forget
+ // that 0 means unlimited.
+ if (mBatchSize && pending().size() >= mBatchSize)
+ {
+ flush();
+ }
+}
diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h
index 66f3c14869..ff8fc9bc7f 100644
--- a/indra/llcommon/lleventfilter.h
+++ b/indra/llcommon/lleventfilter.h
@@ -177,6 +177,9 @@ public:
/// Cancel timer without event
void cancel();
+ /// Is this timer currently running?
+ bool running() const;
+
protected:
virtual void setCountdown(F32 seconds) = 0;
virtual bool countdownElapsed() const = 0;
@@ -215,4 +218,162 @@ private:
LLTimer mTimer;
};
+/**
+ * LLEventBatch: accumulate post() events (LLSD blobs) into an LLSD Array
+ * until the array reaches a certain size, then call listeners with the Array
+ * and clear it back to empty.
+ */
+class LL_COMMON_API LLEventBatch: public LLEventFilter
+{
+public:
+ // pass batch size
+ LLEventBatch(std::size_t size);
+ // construct and connect
+ LLEventBatch(LLEventPump& source, std::size_t size);
+
+ // force out the pending batch
+ void flush();
+
+ // accumulate an event and flush() when big enough
+ virtual bool post(const LLSD& event);
+
+ // query or reset batch size
+ std::size_t getSize() const { return mBatchSize; }
+ void setSize(std::size_t size);
+
+private:
+ LLSD mBatch;
+ std::size_t mBatchSize;
+};
+
+/**
+ * LLEventThrottleBase: construct with a time interval. Regardless of how
+ * frequently you call post(), LLEventThrottle will pass on an event to
+ * its listeners no more often than once per specified interval.
+ *
+ * A new event after more than the specified interval will immediately be
+ * passed along to listeners. But subsequent events will be delayed until at
+ * least one time interval since listeners were last called. Consider the
+ * sequence below. Suppose we have an LLEventThrottle constructed with an
+ * interval of 3 seconds. The numbers on the left are timestamps in seconds
+ * relative to an arbitrary reference point.
+ *
+ * 1: post(): event immediately passed to listeners, next no sooner than 4
+ * 2: post(): deferred: waiting for 3 seconds to elapse
+ * 3: post(): deferred
+ * 4: no post() call, but event delivered to listeners; next no sooner than 7
+ * 6: post(): deferred
+ * 7: no post() call, but event delivered; next no sooner than 10
+ * 12: post(): immediately passed to listeners, next no sooner than 15
+ * 17: post(): immediately passed to listeners, next no sooner than 20
+ *
+ * For a deferred event, the LLSD blob delivered to listeners is from the most
+ * recent deferred post() call. However, a sender may obtain the previous
+ * event blob by calling pending(), modifying it as desired and post()ing the
+ * new value. (See LLEventBatchThrottle.) Each time an event is delivered to
+ * listeners, the pending() value is reset to isUndefined().
+ *
+ * You may also call flush() to immediately pass along any deferred events to
+ * all listeners.
+ *
+ * @NOTE This is an abstract base class so that, for testing, we can use an
+ * alternate "timer" that doesn't actually consume real time. See
+ * LLEventThrottle.
+ */
+class LL_COMMON_API LLEventThrottleBase: public LLEventFilter
+{
+public:
+ // pass time interval
+ LLEventThrottleBase(F32 interval);
+ // construct and connect
+ LLEventThrottleBase(LLEventPump& source, F32 interval);
+
+ // force out any deferred events
+ void flush();
+
+ // retrieve (aggregate) deferred event since last event sent to listeners
+ LLSD pending() const;
+
+ // register an event, may be either passed through or deferred
+ virtual bool post(const LLSD& event);
+
+ // query or reset interval
+ F32 getInterval() const { return mInterval; }
+ void setInterval(F32 interval);
+
+ // deferred posts
+ std::size_t getPostCount() const { return mPosts; }
+
+ // time until next event would be passed through, 0.0 if now
+ F32 getDelay() const;
+
+protected:
+ // Implement these time-related methods for a valid LLEventThrottleBase
+ // subclass (see LLEventThrottle). For testing, we use a subclass that
+ // doesn't involve actual elapsed time.
+ virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) = 0;
+ virtual bool alarmRunning() const = 0;
+ virtual void alarmCancel() = 0;
+ virtual void timerSet(F32 interval) = 0;
+ virtual F32 timerGetRemaining() const = 0;
+
+private:
+ // remember throttle interval
+ F32 mInterval;
+ // count post() calls since last flush()
+ std::size_t mPosts;
+ // pending event data from most recent deferred event
+ LLSD mPending;
+};
+
+/**
+ * Production implementation of LLEventThrottle.
+ */
+class LLEventThrottle: public LLEventThrottleBase
+{
+public:
+ LLEventThrottle(F32 interval);
+ LLEventThrottle(LLEventPump& source, F32 interval);
+
+private:
+ virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) /*override*/;
+ virtual bool alarmRunning() const /*override*/;
+ virtual void alarmCancel() /*override*/;
+ virtual void timerSet(F32 interval) /*override*/;
+ virtual F32 timerGetRemaining() const /*override*/;
+
+ // use this to arrange a deferred flush() call
+ LLEventTimeout mAlarm;
+ // use this to track whether we're within mInterval of last flush()
+ LLTimer mTimer;
+};
+
+/**
+ * LLEventBatchThrottle: like LLEventThrottle, it's reluctant to pass events
+ * to listeners more often than once per specified time interval -- but only
+ * reluctant, since exceeding the specified batch size limit can cause it to
+ * deliver accumulated events sooner. Like LLEventBatch, it accumulates
+ * pending events into an LLSD Array, optionally flushing when the batch grows
+ * to a certain size.
+ */
+class LLEventBatchThrottle: public LLEventThrottle
+{
+public:
+ // pass time interval and (optionally) max batch size; 0 means batch can
+ // grow arbitrarily large
+ LLEventBatchThrottle(F32 interval, std::size_t size = 0);
+ // construct and connect
+ LLEventBatchThrottle(LLEventPump& source, F32 interval, std::size_t size = 0);
+
+ // append a new event to current batch
+ virtual bool post(const LLSD& event);
+
+ // query or reset batch size
+ std::size_t getSize() const { return mBatchSize; }
+ void setSize(std::size_t size);
+
+private:
+ std::size_t mBatchSize;
+};
+
#endif /* ! defined(LL_LLEVENTFILTER_H) */
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index 97270e4931..93db6c0d17 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -316,6 +316,13 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL
const NameList& after,
const NameList& before)
{
+ if (!mSignal)
+ {
+ LL_WARNS() << "Can't connect listener" << LL_ENDL;
+ // connect will fail, return dummy
+ return LLBoundListener();
+ }
+
float nodePosition = 1.0;
// if the supplied name is empty we are not interested in the ordering mechanism
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/llfile.cpp b/indra/llcommon/llfile.cpp
index 873a7bce25..7b559861bb 100644
--- a/indra/llcommon/llfile.cpp
+++ b/indra/llcommon/llfile.cpp
@@ -239,7 +239,7 @@ int LLFile::close(LLFILE * file)
}
-int LLFile::remove(const std::string& filename)
+int LLFile::remove(const std::string& filename, int supress_error)
{
#if LL_WINDOWS
std::string utf8filename = filename;
@@ -248,7 +248,7 @@ int LLFile::remove(const std::string& filename)
#else
int rc = ::remove(filename.c_str());
#endif
- return warnif("remove", filename, rc);
+ return warnif("remove", filename, rc, supress_error);
}
int LLFile::rename(const std::string& filename, const std::string& newname)
diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h
index 3e25228aeb..d8f84daf2b 100644
--- a/indra/llcommon/llfile.h
+++ b/indra/llcommon/llfile.h
@@ -72,7 +72,7 @@ public:
static int mkdir(const std::string& filename, int perms = 0700);
static int rmdir(const std::string& filename);
- static int remove(const std::string& filename);
+ static int remove(const std::string& filename, int supress_error = 0);
static int rename(const std::string& filename,const std::string& newname);
static bool copy(const std::string from, const std::string to);
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/llinitparam.h b/indra/llcommon/llinitparam.h
index c65b05f610..f1f4226c40 100644
--- a/indra/llcommon/llinitparam.h
+++ b/indra/llcommon/llinitparam.h
@@ -551,6 +551,7 @@ namespace LLInitParam
}
virtual std::string getCurrentElementName() = 0;
+ virtual std::string getCurrentFileName() = 0;
virtual void parserWarning(const std::string& message);
virtual void parserError(const std::string& message);
void setParseSilently(bool silent) { mParseSilently = silent; }
diff --git a/indra/llcommon/llmetricperformancetester.cpp b/indra/llcommon/llmetricperformancetester.cpp
index 1fc821d9a9..f8a93baf45 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)
{
@@ -132,8 +132,8 @@ void LLMetricPerformanceTesterBasic::doAnalysisMetrics(std::string baseline, std
}
// Open baseline and current target, exit if one is inexistent
- std::ifstream base_is(baseline.c_str());
- std::ifstream target_is(target.c_str());
+ llifstream base_is(baseline.c_str());
+ llifstream target_is(target.c_str());
if (!base_is.is_open() || !target_is.is_open())
{
LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL;
@@ -151,7 +151,7 @@ void LLMetricPerformanceTesterBasic::doAnalysisMetrics(std::string baseline, std
target_is.close();
//output comparision
- std::ofstream os(output.c_str());
+ llofstream os(output.c_str());
os << "Label, Metric, Base(B), Target(T), Diff(T-B), Percentage(100*T/B)\n";
for(LLMetricPerformanceTesterBasic::name_tester_map_t::iterator iter = LLMetricPerformanceTesterBasic::sTesterMap.begin() ;
@@ -212,7 +212,7 @@ void LLMetricPerformanceTesterBasic::addMetric(std::string str)
}
/*virtual*/
-void LLMetricPerformanceTesterBasic::analyzePerformance(std::ofstream* os, LLSD* base, LLSD* current)
+void LLMetricPerformanceTesterBasic::analyzePerformance(llofstream* os, LLSD* base, LLSD* current)
{
resetCurrentCount() ;
@@ -254,14 +254,14 @@ void LLMetricPerformanceTesterBasic::analyzePerformance(std::ofstream* os, LLSD*
}
/*virtual*/
-void LLMetricPerformanceTesterBasic::compareTestResults(std::ofstream* os, std::string metric_string, S32 v_base, S32 v_current)
+void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, S32 v_base, S32 v_current)
{
*os << llformat(" ,%s, %d, %d, %d, %.4f\n", metric_string.c_str(), v_base, v_current,
v_current - v_base, (v_base != 0) ? 100.f * v_current / v_base : 0) ;
}
/*virtual*/
-void LLMetricPerformanceTesterBasic::compareTestResults(std::ofstream* os, std::string metric_string, F32 v_base, F32 v_current)
+void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, F32 v_base, F32 v_current)
{
*os << llformat(" ,%s, %.4f, %.4f, %.4f, %.4f\n", metric_string.c_str(), v_base, v_current,
v_current - v_base, (fabs(v_base) > 0.0001f) ? 100.f * v_current / v_base : 0.f ) ;
@@ -293,7 +293,7 @@ LLMetricPerformanceTesterWithSession::~LLMetricPerformanceTesterWithSession()
}
/*virtual*/
-void LLMetricPerformanceTesterWithSession::analyzePerformance(std::ofstream* os, LLSD* base, LLSD* current)
+void LLMetricPerformanceTesterWithSession::analyzePerformance(llofstream* os, LLSD* base, LLSD* current)
{
// Load the base session
resetCurrentCount() ;
diff --git a/indra/llcommon/llmetricperformancetester.h b/indra/llcommon/llmetricperformancetester.h
index 1a18cdf36f..2e99ed979d 100644
--- a/indra/llcommon/llmetricperformancetester.h
+++ b/indra/llcommon/llmetricperformancetester.h
@@ -60,7 +60,7 @@ public:
* By default, compares the test results against the baseline one by one, item by item,
* in the increasing order of the LLSD record counter, starting from the first one.
*/
- virtual void analyzePerformance(std::ofstream* os, LLSD* base, LLSD* current) ;
+ virtual void analyzePerformance(llofstream* os, LLSD* base, LLSD* current) ;
static void doAnalysisMetrics(std::string baseline, std::string target, std::string output) ;
@@ -93,8 +93,8 @@ protected:
* @param[in] v_base - Base value of the metric.
* @param[in] v_current - Current value of the metric.
*/
- virtual void compareTestResults(std::ofstream* os, std::string metric_string, S32 v_base, S32 v_current) ;
- virtual void compareTestResults(std::ofstream* os, std::string metric_string, F32 v_base, F32 v_current) ;
+ virtual void compareTestResults(llofstream* os, std::string metric_string, S32 v_base, S32 v_current) ;
+ virtual void compareTestResults(llofstream* os, std::string metric_string, F32 v_base, F32 v_current) ;
/**
* @brief Reset internal record count. Count starts with 1.
@@ -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.
@@ -181,7 +181,7 @@ public:
* This will be loading the base and current sessions and compare them using the virtual
* abstract methods loadTestSession() and compareTestSessions()
*/
- virtual void analyzePerformance(std::ofstream* os, LLSD* base, LLSD* current) ;
+ virtual void analyzePerformance(llofstream* os, LLSD* base, LLSD* current) ;
protected:
/**
@@ -205,7 +205,7 @@ protected:
* @brief Compare the base session and the target session. Assumes base and current sessions have been loaded.
* @param[out] os - The comparison result as a standard stream
*/
- virtual void compareTestSessions(std::ofstream* os) = 0;
+ virtual void compareTestSessions(llofstream* os) = 0;
LLTestSession* mBaseSessionp;
LLTestSession* mCurrentSessionp;
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/llsdparam.h b/indra/llcommon/llsdparam.h
index 09f1bdf1e3..93910b70ae 100644
--- a/indra/llcommon/llsdparam.h
+++ b/indra/llcommon/llsdparam.h
@@ -66,6 +66,7 @@ public:
}
/*virtual*/ std::string getCurrentElementName();
+ /*virtual*/ std::string getCurrentFileName(){ return LLStringUtil::null; }
private:
void writeSDImpl(LLSD& sd,
diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp
index d49ff0feb5..3a219eb998 100644
--- a/indra/llcommon/llsdserialize.cpp
+++ b/indra/llcommon/llsdserialize.cpp
@@ -1200,6 +1200,7 @@ bool LLSDBinaryParser::parseString(
read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/
S32 size = (S32)ntohl(value_nbo);
if(mCheckLimits && (size > mMaxBytesLeft)) return false;
+ if(size < 0) return false;
std::vector<char> buf;
if(size)
{
@@ -2090,7 +2091,18 @@ std::string zip_llsd(LLSD& data)
}
have = CHUNK-strm.avail_out;
- output = (U8*) realloc(output, cur_size+have);
+ U8* new_output = (U8*) realloc(output, cur_size+have);
+ if (new_output == NULL)
+ {
+ LL_WARNS() << "Failed to compress LLSD block: can't reallocate memory, current size: " << cur_size << " bytes; requested " << cur_size + have << " bytes." << LL_ENDL;
+ deflateEnd(&strm);
+ if (output)
+ {
+ free(output);
+ }
+ return std::string();
+ }
+ output = new_output;
memcpy(output+cur_size, out, have);
cur_size += have;
}
@@ -2173,7 +2185,19 @@ bool unzip_llsd(LLSD& data, std::istream& is, S32 size)
U32 have = CHUNK-strm.avail_out;
- result = (U8*) realloc(result, cur_size + have);
+ U8* new_result = (U8*)realloc(result, cur_size + have);
+ if (new_result == NULL)
+ {
+ LL_WARNS() << "Failed to unzip LLSD block: can't reallocate memory, current size: " << cur_size << " bytes; requested " << cur_size + have << " bytes." << LL_ENDL;
+ inflateEnd(&strm);
+ if (result)
+ {
+ free(result);
+ }
+ delete[] in;
+ return false;
+ }
+ result = new_result;
memcpy(result+cur_size, out, have);
cur_size += have;
@@ -2265,7 +2289,20 @@ U8* unzip_llsdNavMesh( bool& valid, unsigned int& outsize, std::istream& is, S32
U32 have = CHUNK-strm.avail_out;
- result = (U8*) realloc(result, cur_size + have);
+ U8* new_result = (U8*) realloc(result, cur_size + have);
+ if (new_result == NULL)
+ {
+ LL_WARNS() << "Failed to unzip LLSD NavMesh block: can't reallocate memory, current size: " << cur_size << " bytes; requested " << cur_size + have << " bytes." << LL_ENDL;
+ inflateEnd(&strm);
+ if (result)
+ {
+ free(result);
+ }
+ delete[] in;
+ valid = false;
+ return NULL;
+ }
+ result = new_result;
memcpy(result+cur_size, out, have);
cur_size += have;
diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp
index 9b49e52377..a3a87edd88 100644
--- a/indra/llcommon/llsingleton.cpp
+++ b/indra/llcommon/llsingleton.cpp
@@ -25,7 +25,467 @@
*/
#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())
+ {
+ list_t::const_iterator it_next = found;
+ it_next++;
+
+ // 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.
+ if (initState == CONSTRUCTING)
+ {
+ logerrs("LLSingleton circularity in Constructor: ", out.str().c_str(),
+ demangle(typeid(*this).name()).c_str(), "");
+ }
+ else if (it_next == initializing.end())
+ {
+ // Points to self after construction, but during initialization.
+ // Singletons can initialize other classes that depend onto them,
+ // so this is expected.
+ //
+ // Example: LLNotifications singleton initializes default channels.
+ // Channels register themselves with singleton once done.
+ logdebugs("LLSingleton circularity: ", out.str().c_str(),
+ demangle(typeid(*this).name()).c_str(), "");
+ }
+ else
+ {
+ // Actual circularity with other singleton (or single singleton is used extensively).
+ // Dependency can be unclear.
+ logwarns("LLSingleton circularity: ", out.str().c_str(),
+ demangle(typeid(*this).name()).c_str(), "");
+ }
+ }
+ 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/llstring.h b/indra/llcommon/llstring.h
index a40db0f8cc..abe5fda603 100644
--- a/indra/llcommon/llstring.h
+++ b/indra/llcommon/llstring.h
@@ -336,6 +336,7 @@ public:
static void addCRLF(string_type& string);
static void removeCRLF(string_type& string);
+ static void removeWindowsCR(string_type& string);
static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab );
static void replaceNonstandardASCII( string_type& string, T replacement );
@@ -1322,6 +1323,32 @@ void LLStringUtilBase<T>::removeCRLF(string_type& string)
//static
template<class T>
+void LLStringUtilBase<T>::removeWindowsCR(string_type& string)
+{
+ if (string.empty())
+ {
+ return;
+ }
+ const T LF = 10;
+ const T CR = 13;
+
+ size_type cr_count = 0;
+ size_type len = string.size();
+ size_type i;
+ for( i = 0; i < len - cr_count - 1; i++ )
+ {
+ if( string[i+cr_count] == CR && string[i+cr_count+1] == LF)
+ {
+ cr_count++;
+ }
+
+ string[i] = string[i+cr_count];
+ }
+ string.erase(i, cr_count);
+}
+
+//static
+template<class T>
void LLStringUtilBase<T>::replaceChar( string_type& string, T target, T replacement )
{
size_type found_pos = 0;
diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h
index 962367f69f..294d0066ca 100644
--- a/indra/llcommon/llsys.h
+++ b/indra/llcommon/llsys.h
@@ -37,13 +37,14 @@
//
#include "llsd.h"
+#include "llsingleton.h"
#include <iosfwd>
#include <string>
-class LL_COMMON_API LLOSInfo
+class LL_COMMON_API LLOSInfo : public LLSingleton<LLOSInfo>
{
+ LLSINGLETON(LLOSInfo);
public:
- LLOSInfo();
void stream(std::ostream& s) const;
const std::string& getOSString() const;
diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp
index 9f12d49244..758b98e143 100644
--- a/indra/llcommon/lluri.cpp
+++ b/indra/llcommon/lluri.cpp
@@ -40,7 +40,8 @@
#include <boost/algorithm/string/find_iterator.hpp>
#include <boost/algorithm/string/finder.hpp>
-void encode_character(std::ostream& ostr, std::string::value_type val)
+// static
+void LLURI::encodeCharacter(std::ostream& ostr, std::string::value_type val)
{
ostr << "%"
@@ -95,7 +96,7 @@ std::string LLURI::escape(
}
else
{
- encode_character(ostr, c);
+ encodeCharacter(ostr, c);
}
}
}
@@ -106,7 +107,7 @@ std::string LLURI::escape(
c = *it;
if(allowed.find(c) == std::string::npos)
{
- encode_character(ostr, c);
+ encodeCharacter(ostr, c);
}
else
{
diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h
index c82a666e48..9e44cc7da2 100644
--- a/indra/llcommon/lluri.h
+++ b/indra/llcommon/lluri.h
@@ -121,6 +121,14 @@ public:
/** @name Escaping Utilities */
//@{
/**
+ * @brief 'Escape' symbol into stream
+ *
+ * @param ostr Output stream.
+ * @param val Symbol to encode.
+ */
+ static void encodeCharacter(std::ostream& ostr, std::string::value_type val);
+
+ /**
* @brief Escape the string passed except for unreserved
*
* ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
diff --git a/indra/llcommon/tests/listener.h b/indra/llcommon/tests/listener.h
index 9c5c18a150..6072060bb6 100644
--- a/indra/llcommon/tests/listener.h
+++ b/indra/llcommon/tests/listener.h
@@ -138,4 +138,15 @@ struct Collect
StringVec result;
};
+struct Concat
+{
+ bool operator()(const LLSD& event)
+ {
+ result += event.asString();
+ return false;
+ }
+ void clear() { result.clear(); }
+ std::string result;
+};
+
#endif /* ! defined(LL_LISTENER_H) */
diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp
index 2cdfb52f2f..eb98b12ef5 100644
--- a/indra/llcommon/tests/lleventfilter_test.cpp
+++ b/indra/llcommon/tests/lleventfilter_test.cpp
@@ -70,6 +70,85 @@ private:
bool mElapsed;
};
+// Similar remarks about LLEventThrottle: we're actually testing the logic in
+// LLEventThrottleBase, dummying out the LLTimer and LLEventTimeout used by
+// the production LLEventThrottle class.
+class TestEventThrottle: public LLEventThrottleBase
+{
+public:
+ TestEventThrottle(F32 interval):
+ LLEventThrottleBase(interval),
+ mAlarmRemaining(-1),
+ mTimerRemaining(-1)
+ {}
+ TestEventThrottle(LLEventPump& source, F32 interval):
+ LLEventThrottleBase(source, interval),
+ mAlarmRemaining(-1),
+ mTimerRemaining(-1)
+ {}
+
+ /*----- implementation of LLEventThrottleBase timing functionality -----*/
+ virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) /*override*/
+ {
+ mAlarmRemaining = interval;
+ mAlarmAction = action;
+ }
+
+ virtual bool alarmRunning() const /*override*/
+ {
+ // decrementing to exactly 0 should mean the alarm fires
+ return mAlarmRemaining > 0;
+ }
+
+ virtual void alarmCancel() /*override*/
+ {
+ mAlarmRemaining = -1;
+ }
+
+ virtual void timerSet(F32 interval) /*override*/
+ {
+ mTimerRemaining = interval;
+ }
+
+ virtual F32 timerGetRemaining() const /*override*/
+ {
+ // LLTimer.getRemainingTimeF32() never returns negative; 0.0 means expired
+ return (mTimerRemaining > 0.0)? mTimerRemaining : 0.0;
+ }
+
+ /*------------------- methods for manipulating time --------------------*/
+ void alarmAdvance(F32 delta)
+ {
+ bool wasRunning = alarmRunning();
+ mAlarmRemaining -= delta;
+ if (wasRunning && ! alarmRunning())
+ {
+ mAlarmAction();
+ }
+ }
+
+ void timerAdvance(F32 delta)
+ {
+ // This simple implementation, like alarmAdvance(), completely ignores
+ // HOW negative mTimerRemaining might go. All that matters is whether
+ // it's negative. We trust that no test method in this source will
+ // drive it beyond the capacity of an F32. Seems like a safe assumption.
+ mTimerRemaining -= delta;
+ }
+
+ void advance(F32 delta)
+ {
+ // Advance the timer first because it has no side effects.
+ // alarmAdvance() might call flush(), which will need to see the
+ // change in the timer.
+ timerAdvance(delta);
+ alarmAdvance(delta);
+ }
+
+ F32 mAlarmRemaining, mTimerRemaining;
+ LLEventTimeoutBase::Action mAlarmAction;
+};
+
/*****************************************************************************
* TUT
*****************************************************************************/
@@ -116,7 +195,9 @@ namespace tut
listener0.listenTo(driver));
// Construct a pattern LLSD: desired Event must have a key "foo"
// containing string "bar"
- LLEventMatching filter(driver, LLSD().insert("foo", "bar"));
+ LLSD pattern;
+ pattern.insert("foo", "bar");
+ LLEventMatching filter(driver, pattern);
listener1.reset(0);
LLTempBoundListener temp2(
listener1.listenTo(filter));
@@ -285,6 +366,47 @@ namespace tut
mainloop.post(17);
check_listener("no timeout 3", listener0, LLSD(0));
}
+
+ template<> template<>
+ void filter_object::test<5>()
+ {
+ set_test_name("LLEventThrottle");
+ TestEventThrottle throttle(3);
+ Concat cat;
+ throttle.listen("concat", boost::ref(cat));
+
+ // (sequence taken from LLEventThrottleBase Doxygen comments)
+ // 1: post(): event immediately passed to listeners, next no sooner than 4
+ throttle.advance(1);
+ throttle.post("1");
+ ensure_equals("1", cat.result, "1"); // delivered immediately
+ // 2: post(): deferred: waiting for 3 seconds to elapse
+ throttle.advance(1);
+ throttle.post("2");
+ ensure_equals("2", cat.result, "1"); // "2" not yet delivered
+ // 3: post(): deferred
+ throttle.advance(1);
+ throttle.post("3");
+ ensure_equals("3", cat.result, "1"); // "3" not yet delivered
+ // 4: no post() call, but event delivered to listeners; next no sooner than 7
+ throttle.advance(1);
+ ensure_equals("4", cat.result, "13"); // "3" delivered
+ // 6: post(): deferred
+ throttle.advance(2);
+ throttle.post("6");
+ ensure_equals("6", cat.result, "13"); // "6" not yet delivered
+ // 7: no post() call, but event delivered; next no sooner than 10
+ throttle.advance(1);
+ ensure_equals("7", cat.result, "136"); // "6" delivered
+ // 12: post(): immediately passed to listeners, next no sooner than 15
+ throttle.advance(5);
+ throttle.post(";12");
+ ensure_equals("12", cat.result, "136;12"); // "12" delivered
+ // 17: post(): immediately passed to listeners, next no sooner than 20
+ throttle.advance(5);
+ throttle.post(";17");
+ ensure_equals("17", cat.result, "136;12;17"); // "17" delivered
+ }
} // namespace tut
/*****************************************************************************
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)
}