summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
authorDave Houlton <euclid@lindenlab.com>2020-07-21 11:04:56 -0600
committerDave Houlton <euclid@lindenlab.com>2020-07-21 11:04:56 -0600
commit19d063952c4706d4344f207775aa5dfdff8802fd (patch)
tree596d38d591177ccc2ffef695b6f79437c5efce97 /indra/llcommon
parent6362aa300766484909cdfc8884edf3e6744fc727 (diff)
parent72423372d6cd7f763a5567ad75752fa4e7131d60 (diff)
Merge master 6.4.6 into DRTVWR-510
Includes the conversion to VS2017 build tools.
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/CMakeLists.txt15
-rw-r--r--indra/llcommon/StackWalker.cpp9
-rw-r--r--indra/llcommon/StackWalker.h2
-rw-r--r--indra/llcommon/llapp.cpp36
-rw-r--r--indra/llcommon/llapr.h12
-rw-r--r--indra/llcommon/llcond.h405
-rw-r--r--indra/llcommon/llcoro_get_id.cpp32
-rw-r--r--indra/llcommon/llcoro_get_id.h30
-rw-r--r--indra/llcommon/llcoros.cpp384
-rw-r--r--indra/llcommon/llcoros.h275
-rw-r--r--indra/llcommon/llerror.cpp558
-rw-r--r--indra/llcommon/llerror.h78
-rw-r--r--indra/llcommon/llerrorcontrol.h1
-rw-r--r--indra/llcommon/lleventcoro.cpp419
-rw-r--r--indra/llcommon/lleventcoro.h204
-rw-r--r--indra/llcommon/lleventfilter.cpp82
-rw-r--r--indra/llcommon/lleventfilter.h162
-rw-r--r--indra/llcommon/llevents.cpp163
-rw-r--r--indra/llcommon/llevents.h507
-rw-r--r--indra/llcommon/lleventtimer.cpp16
-rw-r--r--indra/llcommon/lleventtimer.h69
-rw-r--r--indra/llcommon/llexception.cpp32
-rw-r--r--indra/llcommon/llexception.h26
-rw-r--r--indra/llcommon/llfasttimer.cpp31
-rw-r--r--indra/llcommon/llfasttimer.h6
-rw-r--r--indra/llcommon/llfile.h63
-rw-r--r--indra/llcommon/llinstancetracker.cpp20
-rw-r--r--indra/llcommon/llinstancetracker.h694
-rw-r--r--indra/llcommon/llleaplistener.cpp58
-rw-r--r--indra/llcommon/llleaplistener.h5
-rw-r--r--indra/llcommon/lllistenerwrapper.h198
-rw-r--r--indra/llcommon/llmainthreadtask.cpp22
-rw-r--r--indra/llcommon/llmainthreadtask.h99
-rw-r--r--indra/llcommon/llmake.h52
-rw-r--r--indra/llcommon/llmutex.cpp13
-rw-r--r--indra/llcommon/llmutex.h25
-rw-r--r--indra/llcommon/llpreprocessor.h7
-rw-r--r--indra/llcommon/llprocess.cpp4
-rw-r--r--indra/llcommon/llrefcount.h3
-rw-r--r--indra/llcommon/llsdserialize.cpp78
-rw-r--r--indra/llcommon/llsdserialize.h100
-rw-r--r--indra/llcommon/llsdserialize_xml.cpp10
-rw-r--r--indra/llcommon/llsdutil.cpp68
-rw-r--r--indra/llcommon/llsdutil.h105
-rw-r--r--indra/llcommon/llsingleton.cpp267
-rw-r--r--indra/llcommon/llsingleton.h586
-rw-r--r--indra/llcommon/llstacktrace.cpp5
-rw-r--r--indra/llcommon/llstring.cpp16
-rw-r--r--indra/llcommon/llstring.h26
-rw-r--r--indra/llcommon/lltempredirect.cpp138
-rw-r--r--indra/llcommon/lltempredirect.h91
-rw-r--r--indra/llcommon/llthread.cpp54
-rw-r--r--indra/llcommon/llthread.h27
-rw-r--r--indra/llcommon/llthreadlocalstorage.cpp12
-rw-r--r--indra/llcommon/llthreadsafequeue.h156
-rw-r--r--indra/llcommon/lltrace.h2
-rw-r--r--indra/llcommon/lltraceaccumulators.cpp4
-rw-r--r--indra/llcommon/lltraceaccumulators.h8
-rw-r--r--indra/llcommon/lltracethreadrecorder.cpp12
-rw-r--r--indra/llcommon/lluuid.cpp3
-rw-r--r--indra/llcommon/llworkerthread.h1
-rw-r--r--indra/llcommon/lockstatic.h73
-rw-r--r--indra/llcommon/mutex.h22
-rw-r--r--indra/llcommon/tests/llcond_test.cpp67
-rw-r--r--indra/llcommon/tests/lleventcoro_test.cpp728
-rw-r--r--indra/llcommon/tests/lleventdispatcher_test.cpp123
-rw-r--r--indra/llcommon/tests/lleventfilter_test.cpp75
-rw-r--r--indra/llcommon/tests/llexception_test.cpp15
-rw-r--r--indra/llcommon/tests/llinstancetracker_test.cpp107
-rw-r--r--indra/llcommon/tests/llleap_test.cpp28
-rw-r--r--indra/llcommon/tests/llmainthreadtask_test.cpp137
-rw-r--r--indra/llcommon/tests/llprocess_test.cpp8
-rw-r--r--indra/llcommon/tests/llsdserialize_test.cpp29
-rw-r--r--indra/llcommon/tests/llsingleton_test.cpp14
74 files changed, 4379 insertions, 3633 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index af41b9e460..eeb315ead6 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -1,4 +1,3 @@
-
# -*- cmake -*-
project(llcommon)
@@ -44,7 +43,6 @@ set(llcommon_SOURCE_FILES
llcleanup.cpp
llcommon.cpp
llcommonutils.cpp
- llcoro_get_id.cpp
llcoros.cpp
llcrc.cpp
llcriticaldamp.cpp
@@ -106,6 +104,7 @@ set(llcommon_SOURCE_FILES
llstring.cpp
llstringtable.cpp
llsys.cpp
+ lltempredirect.cpp
llthread.cpp
llthreadlocalstorage.cpp
llthreadsafequeue.cpp
@@ -146,7 +145,7 @@ set(llcommon_HEADER_FILES
llcleanup.h
llcommon.h
llcommonutils.h
- llcoro_get_id.h
+ llcond.h
llcoros.h
llcrc.h
llcriticaldamp.h
@@ -186,9 +185,9 @@ set(llcommon_HEADER_FILES
llkeythrottle.h
llleap.h
llleaplistener.h
- lllistenerwrapper.h
llliveappconfig.h
lllivefile.h
+ llmainthreadtask.h
llmd5.h
llmemory.h
llmemorystream.h
@@ -230,6 +229,7 @@ set(llcommon_HEADER_FILES
llstaticstringtable.h
llstatsaccumulator.h
llsys.h
+ lltempredirect.h
llthread.h
llthreadlocalstorage.h
llthreadsafequeue.h
@@ -247,6 +247,7 @@ set(llcommon_HEADER_FILES
llwin32headers.h
llwin32headerslean.h
llworkerthread.h
+ lockstatic.h
stdtypes.h
stringize.h
timer.h
@@ -291,7 +292,7 @@ target_link_libraries(
${JSONCPP_LIBRARIES}
${ZLIB_LIBRARIES}
${WINDOWS_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_PROGRAM_OPTIONS_LIBRARY}
${BOOST_REGEX_LIBRARY}
@@ -320,13 +321,14 @@ if (LL_TESTS)
${LLCOMMON_LIBRARIES}
${WINDOWS_LIBRARIES}
${GOOGLEMOCK_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_THREAD_LIBRARY}
${BOOST_SYSTEM_LIBRARY})
LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}")
@@ -338,6 +340,7 @@ if (LL_TESTS)
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(llmainthreadtask "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}")
diff --git a/indra/llcommon/StackWalker.cpp b/indra/llcommon/StackWalker.cpp
index c0d3104099..56defc6465 100644
--- a/indra/llcommon/StackWalker.cpp
+++ b/indra/llcommon/StackWalker.cpp
@@ -98,7 +98,10 @@
// If VC7 and later, then use the shipped 'dbghelp.h'-file
#pragma pack(push,8)
#if _MSC_VER >= 1300
+#pragma warning (push)
+#pragma warning (disable:4091) // a microsoft header has warnings. Very nice.
#include <dbghelp.h>
+#pragma warning (pop)
#else
// inline the important dbghelp.h-declarations...
typedef enum {
@@ -422,7 +425,7 @@ public:
LPSTR m_szSymPath;
#pragma pack(push,8)
-typedef struct IMAGEHLP_MODULE64_V3 {
+struct IMAGEHLP_MODULE64_V3 {
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
DWORD64 BaseOfImage; // base load address of module
DWORD ImageSize; // virtual size of the loaded module
@@ -450,7 +453,7 @@ typedef struct IMAGEHLP_MODULE64_V3 {
BOOL Publics; // contains public symbols
};
-typedef struct IMAGEHLP_MODULE64_V2 {
+struct IMAGEHLP_MODULE64_V2 {
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
DWORD64 BaseOfImage; // base load address of module
DWORD ImageSize; // virtual size of the loaded module
@@ -657,7 +660,7 @@ private:
pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" );
if ( (pEPM == NULL) || (pGMFNE == NULL) || (pGMBN == NULL) || (pGMI == NULL) )
{
- // we couldnīt find all functions
+ // we couldn't find all functions
FreeLibrary(hPsapi);
return FALSE;
}
diff --git a/indra/llcommon/StackWalker.h b/indra/llcommon/StackWalker.h
index 834f89c471..4634765d0b 100644
--- a/indra/llcommon/StackWalker.h
+++ b/indra/llcommon/StackWalker.h
@@ -148,7 +148,7 @@ protected:
CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
} CallstackEntry;
- typedef enum CallstackEntryType {firstEntry, nextEntry, lastEntry};
+ enum CallstackEntryType {firstEntry, nextEntry, lastEntry};
virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion);
diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp
index 421af3006e..3dab632aef 100644
--- a/indra/llcommon/llapp.cpp
+++ b/indra/llcommon/llapp.cpp
@@ -49,6 +49,8 @@
#include "google_breakpad/exception_handler.h"
#include "stringize.h"
#include "llcleanup.h"
+#include "llevents.h"
+#include "llsdutil.h"
//
// Signal handling
@@ -561,10 +563,42 @@ void LLApp::runErrorHandler()
LLApp::setStopped();
}
+namespace
+{
+
+static std::map<LLApp::EAppStatus, const char*> statusDesc
+{
+ { LLApp::APP_STATUS_RUNNING, "running" },
+ { LLApp::APP_STATUS_QUITTING, "quitting" },
+ { LLApp::APP_STATUS_STOPPED, "stopped" },
+ { LLApp::APP_STATUS_ERROR, "error" }
+};
+
+} // anonymous namespace
+
// static
void LLApp::setStatus(EAppStatus status)
{
- sStatus = status;
+ sStatus = status;
+
+ // This can also happen very late in the application lifecycle -- don't
+ // resurrect a deleted LLSingleton
+ if (! LLEventPumps::wasDeleted())
+ {
+ // notify interested parties of status change
+ LLSD statsd;
+ auto found = statusDesc.find(status);
+ if (found != statusDesc.end())
+ {
+ statsd = found->second;
+ }
+ else
+ {
+ // unknown status? at least report value
+ statsd = LLSD::Integer(status);
+ }
+ LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd));
+ }
}
diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h
index da50dda103..3c07976f42 100644
--- a/indra/llcommon/llapr.h
+++ b/indra/llcommon/llapr.h
@@ -41,17 +41,7 @@
#include "llstring.h"
-#if LL_WINDOWS
-#pragma warning (push)
-#pragma warning (disable:4265)
-#endif
-// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual
-
-#include <mutex>
-
-#if LL_WINDOWS
-#pragma warning (pop)
-#endif
+#include "mutex.h"
struct apr_dso_handle_t;
/**
diff --git a/indra/llcommon/llcond.h b/indra/llcommon/llcond.h
new file mode 100644
index 0000000000..e31b67d893
--- /dev/null
+++ b/indra/llcommon/llcond.h
@@ -0,0 +1,405 @@
+/**
+ * @file llcond.h
+ * @author Nat Goodspeed
+ * @date 2019-07-10
+ * @brief LLCond is a wrapper around condition_variable to encapsulate the
+ * obligatory condition_variable usage pattern. We also provide
+ * simplified versions LLScalarCond, LLBoolCond and LLOneShotCond.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLCOND_H)
+#define LL_LLCOND_H
+
+#include "llunits.h"
+#include "llcoros.h"
+#include LLCOROS_MUTEX_HEADER
+#include "mutex.h"
+#include <chrono>
+
+/**
+ * LLCond encapsulates the pattern required to use a condition_variable. It
+ * bundles subject data, a mutex and a condition_variable: the three required
+ * data objects. It provides wait() methods analogous to condition_variable,
+ * but using the contained condition_variable and the contained mutex. It
+ * provides modify() methods accepting an invocable to safely modify the
+ * contained data and notify waiters. These methods implicitly perform the
+ * required locking.
+ *
+ * The generic LLCond template assumes that DATA might be a struct or class.
+ * For a scalar DATA type, consider LLScalarCond instead. For specifically
+ * bool, consider LLBoolCond.
+ *
+ * Use of LLCoros::ConditionVariable makes LLCond work between
+ * coroutines as well as between threads.
+ */
+template <typename DATA>
+class LLCond
+{
+public:
+ typedef DATA value_type;
+
+private:
+ // This is the DATA controlled by the condition_variable.
+ value_type mData;
+ // condition_variable must be used in conjunction with a mutex. Use
+ // LLCoros::Mutex instead of std::mutex because the latter blocks
+ // the entire calling thread, whereas the former blocks only the current
+ // coroutine within the calling thread. Yet LLCoros::Mutex is safe to
+ // use across threads as well: it subsumes std::mutex functionality.
+ LLCoros::Mutex mMutex;
+ // Use LLCoros::ConditionVariable for the same reason.
+ LLCoros::ConditionVariable mCond;
+
+public:
+ /// LLCond can be explicitly initialized with a specific value for mData if
+ /// desired.
+ LLCond(const value_type& init=value_type()):
+ mData(init)
+ {}
+
+ /// LLCond is move-only
+ LLCond(const LLCond&) = delete;
+ LLCond& operator=(const LLCond&) = delete;
+
+ /// get() returns a const reference to the stored DATA. The only way to
+ /// get a non-const reference -- to modify the stored DATA -- is via
+ /// update_one() or update_all().
+ const value_type& get() const { return mData; }
+
+ /**
+ * Pass update_one() an invocable accepting non-const (DATA&). The
+ * invocable will presumably modify the referenced DATA. update_one()
+ * will lock the mutex, call the invocable and then call notify_one() on
+ * the condition_variable.
+ *
+ * For scalar DATA, it's simpler to use LLScalarCond::set_one(). Use
+ * update_one() when DATA is a struct or class.
+ */
+ template <typename MODIFY>
+ void update_one(MODIFY modify)
+ {
+ { // scope of lock can/should end before notify_one()
+ LLCoros::LockType lk(mMutex);
+ modify(mData);
+ }
+ mCond.notify_one();
+ }
+
+ /**
+ * Pass update_all() an invocable accepting non-const (DATA&). The
+ * invocable will presumably modify the referenced DATA. update_all()
+ * will lock the mutex, call the invocable and then call notify_all() on
+ * the condition_variable.
+ *
+ * For scalar DATA, it's simpler to use LLScalarCond::set_all(). Use
+ * update_all() when DATA is a struct or class.
+ */
+ template <typename MODIFY>
+ void update_all(MODIFY modify)
+ {
+ { // scope of lock can/should end before notify_all()
+ LLCoros::LockType lk(mMutex);
+ modify(mData);
+ }
+ mCond.notify_all();
+ }
+
+ /**
+ * Pass wait() a predicate accepting (const DATA&), returning bool. The
+ * predicate returns true when the condition for which it is waiting has
+ * been satisfied, presumably determined by examining the referenced DATA.
+ * wait() locks the mutex and, until the predicate returns true, calls
+ * wait() on the condition_variable.
+ */
+ template <typename Pred>
+ void wait(Pred pred)
+ {
+ LLCoros::LockType lk(mMutex);
+ // We must iterate explicitly since the predicate accepted by
+ // condition_variable::wait() requires a different signature:
+ // condition_variable::wait() calls its predicate with no arguments.
+ // Fortunately, the loop is straightforward.
+ // We advise the caller to pass a predicate accepting (const DATA&).
+ // But what if they instead pass a predicate accepting non-const
+ // (DATA&)? Such a predicate could modify mData, which would be Bad.
+ // Forbid that.
+ while (! pred(const_cast<const value_type&>(mData)))
+ {
+ mCond.wait(lk);
+ }
+ }
+
+ /**
+ * Pass wait_for() a chrono::duration, indicating how long we're willing
+ * to wait, and a predicate accepting (const DATA&), returning bool. The
+ * predicate returns true when the condition for which it is waiting has
+ * been satisfied, presumably determined by examining the referenced DATA.
+ * wait_for() locks the mutex and, until the predicate returns true, calls
+ * wait_for() on the condition_variable. wait_for() returns false if
+ * condition_variable::wait_for() timed out without the predicate
+ * returning true.
+ */
+ template <typename Rep, typename Period, typename Pred>
+ bool wait_for(const std::chrono::duration<Rep, Period>& timeout_duration, Pred pred)
+ {
+ // Instead of replicating wait_until() logic, convert duration to
+ // time_point and just call wait_until().
+ // An implementation in which we repeatedly called
+ // condition_variable::wait_for() with our passed duration would be
+ // wrong! We'd keep pushing the timeout time farther and farther into
+ // the future. This way, we establish a definite timeout time and
+ // stick to it.
+ return wait_until(std::chrono::steady_clock::now() + timeout_duration, pred);
+ }
+
+ /**
+ * This wait_for() overload accepts F32Milliseconds as the duration. Any
+ * duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for() method.
+ */
+ template <typename Pred>
+ bool wait_for(F32Milliseconds timeout_duration, Pred pred)
+ {
+ return wait_for(convert(timeout_duration), pred);
+ }
+
+protected:
+ // convert F32Milliseconds to a chrono::duration
+ auto convert(F32Milliseconds duration)
+ {
+ // std::chrono::milliseconds doesn't like to be constructed from a
+ // float (F32), rubbing our nose in the thought that
+ // std::chrono::duration::rep is probably integral. Therefore
+ // converting F32Milliseconds to std::chrono::milliseconds would lose
+ // precision. Use std::chrono::microseconds instead. Extract the F32
+ // milliseconds from F32Milliseconds, scale to microseconds, construct
+ // std::chrono::microseconds from that value.
+ return std::chrono::microseconds{ std::chrono::microseconds::rep(duration.value() * 1000) };
+ }
+
+private:
+ /**
+ * Pass wait_until() a chrono::time_point, indicating the time at which we
+ * should stop waiting, and a predicate accepting (const DATA&), returning
+ * bool. The predicate returns true when the condition for which it is
+ * waiting has been satisfied, presumably determined by examining the
+ * referenced DATA. wait_until() locks the mutex and, until the predicate
+ * returns true, calls wait_until() on the condition_variable.
+ * wait_until() returns false if condition_variable::wait_until() timed
+ * out without the predicate returning true.
+ *
+ * Originally this class and its subclasses published wait_until() methods
+ * corresponding to each wait_for() method. But that raised all sorts of
+ * fascinating questions about the time zone of the passed time_point:
+ * local time? server time? UTC? The bottom line is that for LLCond
+ * timeout purposes, we really shouldn't have to care -- timeout duration
+ * is all we need. This private method remains because it's the simplest
+ * way to support iteratively waiting across spurious wakeups while
+ * honoring a fixed timeout.
+ */
+ template <typename Clock, typename Duration, typename Pred>
+ bool wait_until(const std::chrono::time_point<Clock, Duration>& timeout_time, Pred pred)
+ {
+ LLCoros::LockType lk(mMutex);
+ // We advise the caller to pass a predicate accepting (const DATA&).
+ // But what if they instead pass a predicate accepting non-const
+ // (DATA&)? Such a predicate could modify mData, which would be Bad.
+ // Forbid that.
+ while (! pred(const_cast<const value_type&>(mData)))
+ {
+ if (LLCoros::cv_status::timeout == mCond.wait_until(lk, timeout_time))
+ {
+ // It's possible that wait_until() timed out AND the predicate
+ // became true more or less simultaneously. Even though
+ // wait_until() timed out, check the predicate one more time.
+ return pred(const_cast<const value_type&>(mData));
+ }
+ }
+ return true;
+ }
+};
+
+template <typename DATA>
+class LLScalarCond: public LLCond<DATA>
+{
+ using super = LLCond<DATA>;
+
+public:
+ using typename super::value_type;
+ using super::get;
+ using super::wait;
+ using super::wait_for;
+
+ /// LLScalarCond can be explicitly initialized with a specific value for
+ /// mData if desired.
+ LLScalarCond(const value_type& init=value_type()):
+ super(init)
+ {}
+
+ /// Pass set_one() a new value to which to update mData. set_one() will
+ /// lock the mutex, update mData and then call notify_one() on the
+ /// condition_variable.
+ void set_one(const value_type& value)
+ {
+ super::update_one([&value](value_type& data){ data = value; });
+ }
+
+ /// Pass set_all() a new value to which to update mData. set_all() will
+ /// lock the mutex, update mData and then call notify_all() on the
+ /// condition_variable.
+ void set_all(const value_type& value)
+ {
+ super::update_all([&value](value_type& data){ data = value; });
+ }
+
+ /**
+ * Pass wait_equal() a value for which to wait. wait_equal() locks the
+ * mutex and, until the stored DATA equals that value, calls wait() on the
+ * condition_variable.
+ */
+ void wait_equal(const value_type& value)
+ {
+ super::wait([&value](const value_type& data){ return (data == value); });
+ }
+
+ /**
+ * Pass wait_for_equal() a chrono::duration, indicating how long we're
+ * willing to wait, and a value for which to wait. wait_for_equal() locks
+ * the mutex and, until the stored DATA equals that value, calls
+ * wait_for() on the condition_variable. wait_for_equal() returns false if
+ * condition_variable::wait_for() timed out without the stored DATA being
+ * equal to the passed value.
+ */
+ template <typename Rep, typename Period>
+ bool wait_for_equal(const std::chrono::duration<Rep, Period>& timeout_duration,
+ const value_type& value)
+ {
+ return super::wait_for(timeout_duration,
+ [&value](const value_type& data){ return (data == value); });
+ }
+
+ /**
+ * This wait_for_equal() overload accepts F32Milliseconds as the duration.
+ * Any duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for_equal() method.
+ */
+ bool wait_for_equal(F32Milliseconds timeout_duration, const value_type& value)
+ {
+ return wait_for_equal(super::convert(timeout_duration), value);
+ }
+
+ /**
+ * Pass wait_unequal() a value from which to move away. wait_unequal()
+ * locks the mutex and, until the stored DATA no longer equals that value,
+ * calls wait() on the condition_variable.
+ */
+ void wait_unequal(const value_type& value)
+ {
+ super::wait([&value](const value_type& data){ return (data != value); });
+ }
+
+ /**
+ * Pass wait_for_unequal() a chrono::duration, indicating how long we're
+ * willing to wait, and a value from which to move away.
+ * wait_for_unequal() locks the mutex and, until the stored DATA no longer
+ * equals that value, calls wait_for() on the condition_variable.
+ * wait_for_unequal() returns false if condition_variable::wait_for()
+ * timed out with the stored DATA still being equal to the passed value.
+ */
+ template <typename Rep, typename Period>
+ bool wait_for_unequal(const std::chrono::duration<Rep, Period>& timeout_duration,
+ const value_type& value)
+ {
+ return super::wait_for(timeout_duration,
+ [&value](const value_type& data){ return (data != value); });
+ }
+
+ /**
+ * This wait_for_unequal() overload accepts F32Milliseconds as the duration.
+ * Any duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for_unequal() method.
+ */
+ bool wait_for_unequal(F32Milliseconds timeout_duration, const value_type& value)
+ {
+ return wait_for_unequal(super::convert(timeout_duration), value);
+ }
+
+protected:
+ using super::convert;
+};
+
+/// Using bool as LLScalarCond's DATA seems like a particularly useful case
+using LLBoolCond = LLScalarCond<bool>;
+
+/// LLOneShotCond -- init false, set (and wait for) true
+class LLOneShotCond: public LLBoolCond
+{
+ using super = LLBoolCond;
+
+public:
+ using typename super::value_type;
+ using super::get;
+ using super::wait;
+ using super::wait_for;
+ using super::wait_equal;
+ using super::wait_for_equal;
+ using super::wait_unequal;
+ using super::wait_for_unequal;
+
+ /// The bool stored in LLOneShotCond is initially false
+ LLOneShotCond(): super(false) {}
+
+ /// LLOneShotCond assumes that nullary set_one() means to set its bool true
+ void set_one(bool value=true)
+ {
+ super::set_one(value);
+ }
+
+ /// LLOneShotCond assumes that nullary set_all() means to set its bool true
+ void set_all(bool value=true)
+ {
+ super::set_all(value);
+ }
+
+ /**
+ * wait() locks the mutex and, until the stored bool is true, calls wait()
+ * on the condition_variable.
+ */
+ void wait()
+ {
+ super::wait_unequal(false);
+ }
+
+ /**
+ * Pass wait_for() a chrono::duration, indicating how long we're willing
+ * to wait. wait_for() locks the mutex and, until the stored bool is true,
+ * calls wait_for() on the condition_variable. wait_for() returns false if
+ * condition_variable::wait_for() timed out without the stored bool being
+ * true.
+ */
+ template <typename Rep, typename Period>
+ bool wait_for(const std::chrono::duration<Rep, Period>& timeout_duration)
+ {
+ return super::wait_for_unequal(timeout_duration, false);
+ }
+
+ /**
+ * This wait_for() overload accepts F32Milliseconds as the duration.
+ * Any duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for() method.
+ */
+ bool wait_for(F32Milliseconds timeout_duration)
+ {
+ return wait_for(super::convert(timeout_duration));
+ }
+};
+
+#endif /* ! defined(LL_LLCOND_H) */
diff --git a/indra/llcommon/llcoro_get_id.cpp b/indra/llcommon/llcoro_get_id.cpp
deleted file mode 100644
index 24ed1fe0c9..0000000000
--- a/indra/llcommon/llcoro_get_id.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * @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
deleted file mode 100644
index 4c1dca6f19..0000000000
--- a/indra/llcommon/llcoro_get_id.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @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 cc775775bf..262929006d 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -26,15 +26,30 @@
* $/LicenseInfo$
*/
+#include "llwin32headers.h"
+
// Precompiled header
#include "linden_common.h"
// associated header
#include "llcoros.h"
// STL headers
// std headers
+#include <atomic>
// external library headers
#include <boost/bind.hpp>
+#include <boost/fiber/fiber.hpp>
+#ifndef BOOST_DISABLE_ASSERTS
+#define UNDO_BOOST_DISABLE_ASSERTS
+// with Boost 1.65.1, needed for Mac with this specific header
+#define BOOST_DISABLE_ASSERTS
+#endif
+#include <boost/fiber/protected_fixedsize_stack.hpp>
+#ifdef UNDO_BOOST_DISABLE_ASSERTS
+#undef UNDO_BOOST_DISABLE_ASSERTS
+#undef BOOST_DISABLE_ASSERTS
+#endif
// other Linden headers
+#include "llapp.h"
#include "lltimer.h"
#include "llevents.h"
#include "llerror.h"
@@ -45,85 +60,43 @@
#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.
-LLCoros::Current::Current()
+// static
+LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
{
- // 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())
+ CoroData* current{ nullptr };
+ // be careful about attempted accesses in the final throes of app shutdown
+ if (! wasDeleted())
{
- // 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
+ current = instance().mCurrent.get();
+ }
+ // For the main() coroutine, the one NOT explicitly launched by launch(),
+ // we never explicitly set mCurrent. Use a static CoroData instance with
+ // canonical values.
+ if (! current)
+ {
+ static std::atomic<int> which_thread(0);
+ // Use alternate CoroData constructor.
+ static thread_local CoroData sMain(which_thread++);
+ // We need not reset() the local_ptr to this instance; we'll simply
+ // find it again every time we discover that current is null.
+ current = &sMain;
}
-
- mCurrent = &sCurrent;
-}
-
-//static
-LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
-{
- 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()
+LLCoros::coro::id LLCoros::get_self()
{
- CoroData& current = get_CoroData("get_self()");
- if (! current.mSelf)
- {
- LL_ERRS("LLCoros") << "Calling get_self() from non-coroutine context!" << LL_ENDL;
- }
- return *current.mSelf;
+ return boost::this_fiber::get_id();
}
//static
void LLCoros::set_consuming(bool consuming)
{
- get_CoroData("set_consuming()").mConsuming = consuming;
+ CoroData& data(get_CoroData("set_consuming()"));
+ // DO NOT call this on the main() coroutine.
+ llassert_always(! data.mName.empty());
+ data.mConsuming = consuming;
}
//static
@@ -132,89 +105,59 @@ bool LLCoros::get_consuming()
return get_CoroData("get_consuming()").mConsuming;
}
-llcoro::Suspending::Suspending()
+// static
+void LLCoros::setStatus(const std::string& status)
{
- 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.
- current.reset(mSuspended->mPrev);
+ get_CoroData("setStatus()").mStatus = status;
}
-llcoro::Suspending::~Suspending()
+// static
+std::string LLCoros::getStatus()
{
- LLCoros::Current current;
- // Okay, we're back, update our mPrev
- mSuspended->mPrev = current;
- // and reinstate our Current.
- current.reset(mSuspended);
+ return get_CoroData("getStatus()").mStatus;
}
LLCoros::LLCoros():
// MAINT-2724: default coroutine stack size too small on Windows.
// Previously we used
// boost::context::guarded_stack_allocator::default_stacksize();
- // empirically this is 64KB on Windows and Linux. Try quadrupling.
+ // empirically this is insufficient.
#if ADDRESS_SIZE == 64
- mStackSize(512*1024)
+ mStackSize(512*1024),
#else
- mStackSize(256*1024)
+ mStackSize(256*1024),
#endif
+ // mCurrent does NOT own the current CoroData instance -- it simply
+ // points to it. So initialize it with a no-op deleter.
+ mCurrent{ [](CoroData*){} }
{
- // Register our cleanup() method for "mainloop" ticks
- LLEventPumps::instance().obtain("mainloop").listen(
- "LLCoros", boost::bind(&LLCoros::cleanup, this, _1));
}
-bool LLCoros::cleanup(const LLSD&)
+LLCoros::~LLCoros()
{
- static std::string previousName;
- static int previousCount = 0;
- // Walk the mCoros map, checking and removing completed coroutines.
- for (CoroMap::iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; )
+ printActiveCoroutines("at entry to ~LLCoros()");
+ // Other LLApp status-change listeners do things like close
+ // work queues and inject the Stop exception into pending
+ // promises, to force coroutines waiting on those things to
+ // notice and terminate. The only problem is that by the time
+ // LLApp sets "quitting" status, the main loop has stopped
+ // pumping the fiber scheduler with yield() calls. A waiting
+ // coroutine still might not wake up until after resources on
+ // which it depends have been freed. Pump it a few times
+ // ourselves. Of course, stop pumping as soon as the last of
+ // the coroutines has terminated.
+ for (size_t count = 0; count < 10 && CoroData::instanceCount() > 0; ++count)
{
- // Has this coroutine exited (normal return, exception, exit() call)
- // since last tick?
- if (mi->second->mCoro.exited())
- {
- if (previousName != mi->first)
- {
- previousName = mi->first;
- previousCount = 1;
- }
- else
- {
- ++previousCount;
- }
-
- if ((previousCount < 5) || !(previousCount % 50))
- {
- if (previousCount < 5)
- LL_DEBUGS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL;
- else
- LL_DEBUGS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << "("<< previousCount << ")" << LL_ENDL;
-
- }
- // The erase() call will invalidate its passed iterator value --
- // so increment mi FIRST -- but pass its original value to
- // erase(). This is what postincrement is all about.
- mCoros.erase(mi++);
- }
- else
- {
- // Still live, just skip this entry as if incrementing at the top
- // of the loop as usual.
- ++mi;
- }
+ // don't use llcoro::suspend() because that module depends
+ // on this one
+ boost::this_fiber::yield();
}
- return false;
+ printActiveCoroutines("after pumping");
}
std::string LLCoros::generateDistinctName(const std::string& prefix) const
{
- static std::string previousName;
- static int previousCount = 0;
+ static int unique = 0;
// Allowing empty name would make getName()'s not-found return ambiguous.
if (prefix.empty())
@@ -225,37 +168,15 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const
// If the specified name isn't already in the map, just use that.
std::string name(prefix);
- // Find the lowest numeric suffix that doesn't collide with an existing
- // entry. Start with 2 just to make it more intuitive for any interested
- // parties: e.g. "joe", "joe2", "joe3"...
- for (int i = 2; ; name = STRINGIZE(prefix << i++))
+ // Until we find an unused name, append a numeric suffix for uniqueness.
+ while (CoroData::getInstance(name))
{
- if (mCoros.find(name) == mCoros.end())
- {
- if (previousName != name)
- {
- previousName = name;
- previousCount = 1;
- }
- else
- {
- ++previousCount;
- }
-
- if ((previousCount < 5) || !(previousCount % 50))
- {
- if (previousCount < 5)
- LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL;
- else
- LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << "(" << previousCount << ")" << LL_ENDL;
-
- }
-
- return name;
- }
+ name = STRINGIZE(prefix << unique++);
}
+ return name;
}
+/*==========================================================================*|
bool LLCoros::kill(const std::string& name)
{
CoroMap::iterator found = mCoros.find(name);
@@ -269,10 +190,19 @@ bool LLCoros::kill(const std::string& name)
mCoros.erase(found);
return true;
}
+|*==========================================================================*/
-std::string LLCoros::getName() const
+//static
+std::string LLCoros::getName()
{
- return Current()->mName;
+ return get_CoroData("getName()").mName;
+}
+
+//static
+std::string LLCoros::logname()
+{
+ LLCoros::CoroData& data(get_CoroData("logname()"));
+ return data.mName.empty()? data.getKey() : data.mName;
}
void LLCoros::setStackSize(S32 stacksize)
@@ -281,25 +211,46 @@ void LLCoros::setStackSize(S32 stacksize)
mStackSize = stacksize;
}
-void LLCoros::printActiveCoroutines()
+void LLCoros::printActiveCoroutines(const std::string& when)
{
- LL_INFOS("LLCoros") << "Number of active coroutines: " << (S32)mCoros.size() << LL_ENDL;
- if (mCoros.size() > 0)
+ LL_INFOS("LLCoros") << "Number of active coroutines " << when
+ << ": " << CoroData::instanceCount() << LL_ENDL;
+ if (CoroData::instanceCount() > 0)
{
LL_INFOS("LLCoros") << "-------------- List of active coroutines ------------";
- CoroMap::iterator iter;
- CoroMap::iterator end = mCoros.end();
F64 time = LLTimer::getTotalSeconds();
- for (iter = mCoros.begin(); iter != end; iter++)
+ for (auto& cd : CoroData::instance_snapshot())
{
- F64 life_time = time - iter->second->mCreationTime;
- LL_CONT << LL_NEWLINE << "Name: " << iter->first << " life: " << life_time;
+ F64 life_time = time - cd.mCreationTime;
+ LL_CONT << LL_NEWLINE
+ << cd.getKey() << ' ' << cd.mStatus << " life: " << life_time;
}
LL_CONT << LL_ENDL;
LL_INFOS("LLCoros") << "-----------------------------------------------------" << LL_ENDL;
}
}
+std::string LLCoros::launch(const std::string& prefix, const callable_t& callable)
+{
+ std::string name(generateDistinctName(prefix));
+ // 'dispatch' means: enter the new fiber immediately, returning here only
+ // when the fiber yields for whatever reason.
+ // std::allocator_arg is a flag to indicate that the following argument is
+ // a StackAllocator.
+ // protected_fixedsize_stack sets a guard page past the end of the new
+ // stack so that stack underflow will result in an access violation
+ // instead of weird, subtle, possibly undiagnosed memory stomps.
+ boost::fibers::fiber newCoro(boost::fibers::launch::dispatch,
+ std::allocator_arg,
+ boost::fibers::protected_fixedsize_stack(mStackSize),
+ [this, &name, &callable](){ toplevel(name, callable); });
+ // You have two choices with a fiber instance: you can join() it or you
+ // can detach() it. If you try to destroy the instance before doing
+ // either, the program silently terminates. We don't need this handle.
+ newCoro.detach();
+ return name;
+}
+
#if LL_WINDOWS
static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific
@@ -337,13 +288,16 @@ void LLCoros::winlevel(const callable_t& callable)
#endif
-// Top-level wrapper around caller's coroutine callable. This function accepts
-// 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)
+// Top-level wrapper around caller's coroutine callable.
+// Normally we like to pass strings and such by const reference -- but in this
+// case, we WANT to copy both the name and the callable to our local stack!
+void LLCoros::toplevel(std::string name, callable_t callable)
{
- // capture the 'self' param in CoroData
- data->mSelf = &self;
+ // keep the CoroData on this top-level function's stack frame
+ CoroData corodata(name);
+ // set it as current
+ mCurrent.reset(&corodata);
+
// run the code the caller actually wants in the coroutine
try
{
@@ -353,75 +307,69 @@ void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& calla
callable();
#endif
}
+ catch (const Stop& exc)
+ {
+ LL_INFOS("LLCoros") << "coroutine " << name << " terminating because "
+ << exc.what() << LL_ENDL;
+ }
catch (const LLContinueError&)
{
// Any uncaught exception derived from LLContinueError will be caught
// here and logged. This coroutine will terminate but the rest of the
// viewer will carry on.
- LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName));
+ LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name));
}
catch (...)
{
// Any OTHER kind of uncaught exception will cause the viewer to
// crash, hopefully informatively.
- CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName));
+ CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name));
}
- // This cleanup isn't perfectly symmetrical with the way we initially set
- // data->mPrev, but this is our last chance to reset Current.
- Current().reset(data->mPrev);
}
-/*****************************************************************************
-* MUST BE LAST
-*****************************************************************************/
-// Turn off MSVC optimizations for just LLCoros::launch() -- see
-// DEV-32777. But MSVC doesn't support push/pop for optimization flags as it
-// does for warning suppression, and we really don't want to force
-// optimization ON for other code even in Debug or RelWithDebInfo builds.
-
-#if LL_MSVC
-// work around broken optimizations
-#pragma warning(disable: 4748)
-#pragma warning(disable: 4355) // 'this' used in initializer list: yes, intentionally
-#pragma optimize("", off)
-#endif // LL_MSVC
+//static
+void LLCoros::checkStop()
+{
+ if (wasDeleted())
+ {
+ LLTHROW(Shutdown("LLCoros was deleted"));
+ }
+ // do this AFTER the check above, because getName() depends on
+ // get_CoroData(), which depends on the local_ptr in our instance().
+ if (getName().empty())
+ {
+ // Our Stop exception and its subclasses are intended to stop loitering
+ // coroutines. Don't throw it from the main coroutine.
+ return;
+ }
+ if (LLApp::isStopped())
+ {
+ LLTHROW(Stopped("viewer is stopped"));
+ }
+ if (! LLApp::isRunning())
+ {
+ LLTHROW(Stopping("viewer is stopping"));
+ }
+}
-LLCoros::CoroData::CoroData(CoroData* prev, const std::string& name,
- const callable_t& callable, S32 stacksize):
- mPrev(prev),
+LLCoros::CoroData::CoroData(const std::string& name):
+ LLInstanceTracker<CoroData, std::string>(name),
mName(name),
- // Wrap the caller's callable in our toplevel() function so we can manage
- // 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),
- mSelf(0),
mCreationTime(LLTimer::getTotalSeconds())
{
}
-std::string LLCoros::launch(const std::string& prefix, const callable_t& callable)
+LLCoros::CoroData::CoroData(int n):
+ // This constructor is used for the thread_local instance belonging to the
+ // default coroutine on each thread. We must give each one a different
+ // LLInstanceTracker key because LLInstanceTracker's map spans all
+ // threads, but we want the default coroutine on each thread to have the
+ // empty string as its visible name because some consumers test for that.
+ LLInstanceTracker<CoroData, std::string>("main" + stringize(n)),
+ mName(),
+ mConsuming(false),
+ mCreationTime(LLTimer::getTotalSeconds())
{
- std::string name(generateDistinctName(prefix));
- Current current;
- // pass the current value of Current as previous context
- CoroData* newCoro = new(std::nothrow) CoroData(current, name, callable, mStackSize);
- if (newCoro == NULL)
- {
- // Out of memory?
- printActiveCoroutines();
- LL_ERRS("LLCoros") << "Failed to start coroutine: " << name << " Stacksize: " << mStackSize << " Total coroutines: " << mCoros.size() << LL_ENDL;
- }
- // Store it in our pointer map
- mCoros.insert(name, newCoro);
- // also set it as current
- current.reset(newCoro);
- /* Run the coroutine until its first wait, then return here */
- (newCoro->mCoro)(std::nothrow);
- return name;
}
-
-#if LL_MSVC
-// reenable optimizations
-#pragma optimize("", on)
-#endif // LL_MSVC
diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h
index c551413811..38c2356c99 100644
--- a/indra/llcommon/llcoros.h
+++ b/indra/llcommon/llcoros.h
@@ -29,21 +29,26 @@
#if ! defined(LL_LLCOROS_H)
#define LL_LLCOROS_H
-#include <boost/dcoroutine/coroutine.hpp>
-#include <boost/dcoroutine/future.hpp>
+#include "llexception.h"
+#include <boost/fiber/fss.hpp>
+#include <boost/fiber/future/promise.hpp>
+#include <boost/fiber/future/future.hpp>
+#include "mutex.h"
#include "llsingleton.h"
-#include <boost/ptr_container/ptr_map.hpp>
+#include "llinstancetracker.h"
#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
-{
-class Suspending;
+// e.g. #include LLCOROS_MUTEX_HEADER
+#define LLCOROS_MUTEX_HEADER <boost/fiber/mutex.hpp>
+#define LLCOROS_CONDVAR_HEADER <boost/fiber/condition_variable.hpp>
+
+namespace boost {
+ namespace fibers {
+ class mutex;
+ enum class cv_status;
+ class condition_variable;
+ }
}
/**
@@ -76,19 +81,21 @@ class Suspending;
* name prefix; from your prefix it generates a distinct name, registers the
* new coroutine and returns the actual name.
*
- * The name can be used to kill off the coroutine prematurely, if needed. It
- * can also provide diagnostic info: we can look up the name of the
+ * The name
+ * can provide diagnostic info: we can look up the name of the
* currently-running coroutine.
- *
- * Finally, the next frame ("mainloop" event) after the coroutine terminates,
- * LLCoros will notice its demise and destroy it.
*/
class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
{
LLSINGLETON(LLCoros);
+ ~LLCoros();
public:
- /// Canonical boost::dcoroutines::coroutine signature we use
- typedef boost::dcoroutines::coroutine<void()> coro;
+ /// The viewer's use of the term "coroutine" became deeply embedded before
+ /// the industry term "fiber" emerged to distinguish userland threads from
+ /// simpler, more transient kinds of coroutines. Semantically they've
+ /// always been fibers. But at this point in history, we're pretty much
+ /// stuck with the term "coroutine."
+ typedef boost::fibers::fiber coro;
/// Canonical callable type
typedef boost::function<void()> callable_t;
@@ -119,10 +126,10 @@ public:
* DEV-32777 comments for an explanation.
*
* Pass a nullary callable. It works to directly pass a nullary free
- * function (or static method); for all other cases use boost::bind(). Of
- * course, for a non-static class method, the first parameter must be the
- * class instance. Any other parameters should be passed via the bind()
- * expression.
+ * function (or static method); for other cases use a lambda expression,
+ * std::bind() or boost::bind(). Of course, for a non-static class method,
+ * the first parameter must be the class instance. Any other parameters
+ * should be passed via the enclosing expression.
*
* launch() tweaks the suggested name so it won't collide with any
* existing coroutine instance, creates the coroutine instance, registers
@@ -138,7 +145,7 @@ public:
* one prematurely. Returns @c true if the specified name was found and
* still running at the time.
*/
- bool kill(const std::string& name);
+// bool kill(const std::string& name);
/**
* From within a coroutine, look up the (tweaked) name string by which
@@ -146,16 +153,27 @@ public:
* (e.g. if the coroutine was launched by hand rather than using
* LLCoros::launch()).
*/
- std::string getName() const;
+ static std::string getName();
- /// for delayed initialization
+ /**
+ * This variation returns a name suitable for log messages: the explicit
+ * name for an explicitly-launched coroutine, or "mainN" for the default
+ * coroutine on a thread.
+ */
+ static std::string logname();
+
+ /**
+ * For delayed initialization. To be clear, this will only affect
+ * coroutines launched @em after this point. The underlying facility
+ * provides no way to alter the stack size of any running coroutine.
+ */
void setStackSize(S32 stacksize);
- /// for delayed initialization
- void printActiveCoroutines();
+ /// diagnostic
+ void printActiveCoroutines(const std::string& when=std::string());
- /// get the current coro::self& for those who really really care
- static coro::self& get_self();
+ /// get the current coro::id for those who really really care
+ static coro::id get_self();
/**
* Most coroutines, most of the time, don't "consume" the events for which
@@ -180,6 +198,7 @@ public:
{
set_consuming(consuming);
}
+ OverrideConsuming(const OverrideConsuming&) = delete;
~OverrideConsuming()
{
set_consuming(mPrevConsuming);
@@ -189,142 +208,124 @@ public:
bool mPrevConsuming;
};
+ /// set string coroutine status for diagnostic purposes
+ static void setStatus(const std::string& status);
+ static std::string getStatus();
+
+ /// RAII control of status
+ class TempStatus
+ {
+ public:
+ TempStatus(const std::string& status):
+ mOldStatus(getStatus())
+ {
+ setStatus(status);
+ }
+ TempStatus(const TempStatus&) = delete;
+ ~TempStatus()
+ {
+ setStatus(mOldStatus);
+ }
+
+ private:
+ std::string mOldStatus;
+ };
+
+ /// thrown by checkStop()
+ // It may sound ironic that Stop is derived from LLContinueError, but the
+ // point is that LLContinueError is the category of exception that should
+ // not immediately crash the viewer. Stop and its subclasses are to notify
+ // coroutines that the viewer intends to shut down. The expected response
+ // is to terminate the coroutine, rather than abort the viewer.
+ struct Stop: public LLContinueError
+ {
+ Stop(const std::string& what): LLContinueError(what) {}
+ };
+
+ /// early stages
+ struct Stopping: public Stop
+ {
+ Stopping(const std::string& what): Stop(what) {}
+ };
+
+ /// cleaning up
+ struct Stopped: public Stop
+ {
+ Stopped(const std::string& what): Stop(what) {}
+ };
+
+ /// cleaned up -- not much survives!
+ struct Shutdown: public Stop
+ {
+ Shutdown(const std::string& what): Stop(what) {}
+ };
+
+ /// Call this intermittently if there's a chance your coroutine might
+ /// continue running into application shutdown. Throws Stop if LLCoros has
+ /// been cleaned up.
+ static void checkStop();
+
/**
- * Please do NOT directly use boost::dcoroutines::future! It is essential
- * to maintain the "current" coroutine at every context switch. This
- * Future wraps the essential boost::dcoroutines::future functionality
- * with that maintenance.
+ * Aliases for promise and future. An older underlying future implementation
+ * required us to wrap future; that's no longer needed. However -- if it's
+ * important to restore kill() functionality, we might need to provide a
+ * proxy, so continue using the aliases.
*/
template <typename T>
- class Future;
+ using Promise = boost::fibers::promise<T>;
+ template <typename T>
+ using Future = boost::fibers::future<T>;
+ template <typename T>
+ static Future<T> getFuture(Promise<T>& promise) { return promise.get_future(); }
+
+ // use mutex, lock, condition_variable suitable for coroutines
+ using Mutex = boost::fibers::mutex;
+ using LockType = std::unique_lock<Mutex>;
+ using cv_status = boost::fibers::cv_status;
+ using ConditionVariable = boost::fibers::condition_variable;
+
+ /// for data local to each running coroutine
+ template <typename T>
+ using local_ptr = boost::fibers::fiber_specific_ptr<T>;
private:
- friend class llcoro::Suspending;
- friend llcoro::id llcoro::get_id();
std::string generateDistinctName(const std::string& prefix) const;
- bool cleanup(const LLSD&);
+ void toplevel(std::string name, callable_t callable);
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);
S32 mStackSize;
// coroutine-local storage, as it were: one per coro we track
- struct CoroData
+ struct CoroData: public LLInstanceTracker<CoroData, std::string>
{
- CoroData(CoroData* prev, const std::string& name,
- const callable_t& callable, S32 stacksize);
-
- // The boost::dcoroutines library supports asymmetric coroutines. Every
- // time we context switch out of a coroutine, we pass control to the
- // previously-active one (or to the non-coroutine stack owned by the
- // thread). So our management of the "current" coroutine must be able to
- // restore the previous value when we're about to switch away.
- CoroData* mPrev;
+ CoroData(const std::string& name);
+ CoroData(int n);
+
// tweaked name of the current coroutine
const std::string mName;
- // the actual coroutine instance
- LLCoros::coro mCoro;
// set_consuming() state
bool mConsuming;
- // When the dcoroutine library calls a top-level callable, it implicitly
- // passes coro::self& as the first parameter. All our consumer code used
- // to explicitly pass coro::self& down through all levels of call stack,
- // because at the leaf level we need it for context-switching. But since
- // coroutines are based on cooperative switching, we can cause the
- // top-level entry point to stash a pointer to the currently-running
- // coroutine, and manage it appropriately as we switch out and back in.
- // That eliminates the need to pass it as an explicit parameter down
- // through every level, which is unfortunately viral in nature. Finding it
- // implicitly rather than explicitly allows minor maintenance in which a
- // leaf-level function adds a new async I/O call that suspends the calling
- // coroutine, WITHOUT having to propagate coro::self& through every
- // function signature down to that point -- and of course through every
- // other caller of every such function.
- LLCoros::coro::self* mSelf;
+ // setStatus() state
+ std::string mStatus;
F64 mCreationTime; // since epoch
};
- typedef boost::ptr_map<std::string, CoroData> CoroMap;
- CoroMap mCoros;
- // 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;
- };
+ // Identify the current coroutine's CoroData. This local_ptr isn't static
+ // because it's a member of an LLSingleton, and we rely on it being
+ // cleaned up in proper dependency order.
+ local_ptr<CoroData> mCurrent;
};
namespace llcoro
{
-/// Instantiate one of these in a block surrounding any leaf point when
-/// control literally switches away from this coroutine.
-class Suspending: boost::noncopyable
-{
-public:
- Suspending();
- ~Suspending();
-
-private:
- LLCoros::CoroData* mSuspended;
-};
-
-} // namespace llcoro
-
-template <typename T>
-class LLCoros::Future
-{
- typedef boost::dcoroutines::future<T> dfuture;
-
-public:
- Future():
- mFuture(get_self())
- {}
-
- typedef typename boost::dcoroutines::make_callback_result<dfuture>::type callback_t;
-
- callback_t make_callback()
- {
- return boost::dcoroutines::make_callback(mFuture);
- }
-
-#ifndef LL_LINUX
- explicit
-#endif
- operator bool() const
- {
- return bool(mFuture);
- }
-
- bool operator!() const
- {
- return ! mFuture;
- }
+inline
+std::string logname() { return LLCoros::logname(); }
- T get()
- {
- // instantiate Suspending to manage the "current" coroutine
- llcoro::Suspending suspended;
- return *mFuture;
- }
-
-private:
- dfuture mFuture;
-};
+} // llcoro
#endif /* ! defined(LL_LLCOROS_H) */
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index b46f49ba34..411412c883 100644
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -39,6 +39,9 @@
#if !LL_WINDOWS
# include <syslog.h>
# include <unistd.h>
+# include <sys/stat.h>
+#else
+# include <io.h>
#endif // !LL_WINDOWS
#include <vector>
#include "string.h"
@@ -53,6 +56,13 @@
#include "llstl.h"
#include "lltimer.h"
+// On Mac, got:
+// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define
+// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if
+// _Unwind_Backtrace is available without `_GNU_SOURCE`."
+#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED
+#include <boost/stacktrace.hpp>
+
namespace {
#if LL_WINDOWS
void debugger_print(const std::string& s)
@@ -118,27 +128,28 @@ namespace {
class RecordToFile : public LLError::Recorder
{
public:
- RecordToFile(const std::string& filename)
+ RecordToFile(const std::string& filename):
+ mName(filename)
{
mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app);
if (!mFile)
{
LL_INFOS() << "Error setting log file to " << filename << LL_ENDL;
}
- else
- {
- if (!LLError::getAlwaysFlush())
- {
- mFile.sync_with_stdio(false);
- }
- }
+ else
+ {
+ if (!LLError::getAlwaysFlush())
+ {
+ mFile.sync_with_stdio(false);
+ }
+ }
}
-
+
~RecordToFile()
{
mFile.close();
}
-
+
virtual bool enabled() override
{
#ifdef LL_RELEASE_FOR_DOWNLOAD
@@ -148,11 +159,13 @@ namespace {
#endif
}
- bool okay() { return mFile.good(); }
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
+ bool okay() const { return mFile.good(); }
+
+ std::string getFilename() const { return mName; }
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
if (LLError::getAlwaysFlush())
{
mFile << message << std::endl;
@@ -161,9 +174,10 @@ namespace {
{
mFile << message << "\n";
}
- }
-
+ }
+
private:
+ const std::string mName;
llofstream mFile;
};
@@ -171,7 +185,7 @@ namespace {
class RecordToStderr : public LLError::Recorder
{
public:
- RecordToStderr(bool timestamp) : mUseANSI(ANSI_PROBE)
+ RecordToStderr(bool timestamp) : mUseANSI(checkANSI())
{
this->showMultiline(true);
}
@@ -193,14 +207,12 @@ namespace {
virtual void recordMessage(LLError::ELevel level,
const std::string& message) override
- {
+ {
static std::string s_ansi_error = createANSI("31"); // red
static std::string s_ansi_warn = createANSI("34"); // blue
static std::string s_ansi_debug = createANSI("35"); // magenta
- mUseANSI = (ANSI_PROBE == mUseANSI) ? (checkANSI() ? ANSI_YES : ANSI_NO) : mUseANSI;
-
- if (ANSI_YES == mUseANSI)
+ if (mUseANSI)
{
writeANSI((level == LLError::LEVEL_ERROR) ? s_ansi_error :
(level == LLError::LEVEL_WARN) ? s_ansi_warn :
@@ -213,12 +225,7 @@ namespace {
}
private:
- enum ANSIState
- {
- ANSI_PROBE,
- ANSI_YES,
- ANSI_NO
- } mUseANSI;
+ bool mUseANSI;
LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message)
{
@@ -229,16 +236,13 @@ namespace {
fprintf(stderr, "%s%s%s\n%s", s_ansi_bold.c_str(), ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() );
}
- bool checkANSI(void)
+ static bool checkANSI(void)
{
-#if LL_LINUX || LL_DARWIN
// Check whether it's okay to use ANSI; if stderr is
// a tty then we assume yes. Can be turned off with
// the LL_NO_ANSI_COLOR env var.
return (0 != isatty(2)) &&
(NULL == getenv("LL_NO_ANSI_COLOR"));
-#endif // LL_LINUX
- return FALSE; // works in a cygwin shell... ;)
}
};
@@ -308,28 +312,35 @@ namespace LLError
{
#ifdef __GNUC__
// GCC: type_info::name() returns a mangled class name,st demangle
- // passing nullptr, 0 forces allocation of a unique buffer we can free
- // fixing MAINT-8724 on OSX 10.14
+ // passing nullptr, 0 forces allocation of a unique buffer we can free
+ // fixing MAINT-8724 on OSX 10.14
int status = -1;
char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status);
- std::string result(name ? name : mangled);
- free(name);
- return result;
-#elif LL_WINDOWS
- // DevStudio: type_info::name() includes the text "class " at the start
+ std::string result(name ? name : mangled);
+ free(name);
+ return result;
- static const std::string class_prefix = "class ";
+#elif LL_WINDOWS
+ // Visual Studio: type_info::name() includes the text "class " at the start
std::string name = mangled;
- if (0 != name.compare(0, class_prefix.length(), class_prefix))
+ for (const auto& prefix : std::vector<std::string>{ "class ", "struct " })
{
- LL_DEBUGS() << "Did not see '" << class_prefix << "' prefix on '"
- << name << "'" << LL_ENDL;
- return name;
+ if (0 == name.compare(0, prefix.length(), prefix))
+ {
+ return name.substr(prefix.length());
+ }
}
+ // huh, that's odd, we should see one or the other prefix -- but don't
+ // try to log unless logging is already initialized
+ if (is_available())
+ {
+ // in Python, " or ".join(vector) -- but in C++, a PITB
+ LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '"
+ << name << "'" << LL_ENDL;
+ }
+ return name;
- return name.substr(class_prefix.length());
-
-#else
+#else // neither GCC nor Visual Studio
return mangled;
#endif
}
@@ -408,7 +419,7 @@ namespace
return false;
}
- if (configuration.isUndefined() || !configuration.isMap() || configuration.emptyMap())
+ if (! configuration || !configuration.isMap())
{
LL_WARNS() << filename() << " missing, ill-formed, or simply undefined"
" content; not changing configuration"
@@ -490,14 +501,11 @@ namespace LLError
LLError::FatalFunction mCrashFunction;
LLError::TimeFunction mTimeFunction;
-
+
Recorders mRecorders;
- RecorderPtr mFileRecorder;
- RecorderPtr mFixedBufferRecorder;
- std::string mFileRecorderFileName;
-
- int mShouldLogCallCounter;
-
+
+ int mShouldLogCallCounter;
+
private:
SettingsConfig();
};
@@ -531,9 +539,6 @@ namespace LLError
mCrashFunction(NULL),
mTimeFunction(NULL),
mRecorders(),
- mFileRecorder(),
- mFixedBufferRecorder(),
- mFileRecorderFileName(),
mShouldLogCallCounter(0)
{
}
@@ -656,22 +661,38 @@ namespace LLError
namespace
{
- bool shouldLogToStderr()
- {
+ bool shouldLogToStderr()
+ {
#if LL_DARWIN
- // On Mac OS X, stderr from apps launched from the Finder goes to the
- // console log. It's generally considered bad form to spam too much
- // there.
-
- // If stdin is a tty, assume the user launched from the command line and
- // therefore wants to see stderr. Otherwise, assume we've been launched
- // from the finder and shouldn't spam stderr.
- return isatty(0);
+ // On Mac OS X, stderr from apps launched from the Finder goes to the
+ // console log. It's generally considered bad form to spam too much
+ // there. That scenario can be detected by noticing that stderr is a
+ // character device (S_IFCHR).
+
+ // If stderr is a tty or a pipe, assume the user launched from the
+ // command line or debugger and therefore wants to see stderr.
+ if (isatty(STDERR_FILENO))
+ return true;
+ // not a tty, but might still be a pipe -- check
+ struct stat st;
+ if (fstat(STDERR_FILENO, &st) < 0)
+ {
+ // capture errno right away, before engaging any other operations
+ auto errno_save = errno;
+ // this gets called during log-system setup -- can't log yet!
+ std::cerr << "shouldLogToStderr: fstat(" << STDERR_FILENO << ") failed, errno "
+ << errno_save << std::endl;
+ // if we can't tell, err on the safe side and don't write stderr
+ return false;
+ }
+
+ // fstat() worked: return true only if stderr is a pipe
+ return ((st.st_mode & S_IFMT) == S_IFIFO);
#else
- return true;
+ return true;
#endif
- }
-
+ }
+
bool stderrLogWantsTime()
{
#if LL_WINDOWS
@@ -685,20 +706,19 @@ namespace
void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true)
{
LLError::Settings::getInstance()->reset();
-
+
LLError::setDefaultLevel(LLError::LEVEL_INFO);
- LLError::setAlwaysFlush(true);
- LLError::setEnabledLogTypesMask(0xFFFFFFFF);
+ LLError::setAlwaysFlush(true);
+ LLError::setEnabledLogTypesMask(0xFFFFFFFF);
LLError::setFatalFunction(LLError::crashAndLoop);
LLError::setTimeFunction(LLError::utcTime);
// log_to_stderr is only false in the unit and integration tests to keep builds quieter
if (log_to_stderr && shouldLogToStderr())
{
- LLError::RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime()));
- LLError::addRecorder(recordToStdErr);
+ LLError::logToStderr();
}
-
+
#if LL_WINDOWS
LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug());
LLError::addRecorder(recordToWinDebug);
@@ -996,49 +1016,110 @@ namespace LLError
s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder),
s->mRecorders.end());
}
+
+ // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
+ // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
+ // points to the Recorder base class), but a shared_ptr<RECORDER> which
+ // specifically points to the concrete RECORDER subclass instance, along
+ // with a Recorders::iterator indicating the position of that entry in
+ // mRecorders. The shared_ptr might be empty (operator!() returns true) if
+ // there was no such RECORDER subclass instance in mRecorders.
+ template <typename RECORDER>
+ std::pair<boost::shared_ptr<RECORDER>, Recorders::iterator>
+ findRecorderPos()
+ {
+ SettingsConfigPtr s = Settings::instance().getSettingsConfig();
+ // Since we promise to return an iterator, use a classic iterator
+ // loop.
+ auto end{s->mRecorders.end()};
+ for (Recorders::iterator it{s->mRecorders.begin()}; it != end; ++it)
+ {
+ // *it is a RecorderPtr, a shared_ptr<Recorder>. Use a
+ // dynamic_pointer_cast to try to downcast to test if it's also a
+ // shared_ptr<RECORDER>.
+ auto ptr = boost::dynamic_pointer_cast<RECORDER>(*it);
+ if (ptr)
+ {
+ // found the entry we want
+ return { ptr, it };
+ }
+ }
+ // dropped out of the loop without finding any such entry -- instead
+ // of default-constructing Recorders::iterator (which might or might
+ // not be valid), return a value that is valid but not dereferenceable.
+ return { {}, end };
+ }
+
+ // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
+ // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
+ // points to the Recorder base class), but a shared_ptr<RECORDER> which
+ // specifically points to the concrete RECORDER subclass instance. The
+ // shared_ptr might be empty (operator!() returns true) if there was no
+ // such RECORDER subclass instance in mRecorders.
+ template <typename RECORDER>
+ boost::shared_ptr<RECORDER> findRecorder()
+ {
+ return findRecorderPos<RECORDER>().first;
+ }
+
+ // Remove an entry from SettingsConfig::mRecorders whose RecorderPtr
+ // points to a Recorder subclass of type RECORDER. Return true if there
+ // was one and we removed it, false if there wasn't one to start with.
+ template <typename RECORDER>
+ bool removeRecorder()
+ {
+ auto found = findRecorderPos<RECORDER>();
+ if (found.first)
+ {
+ SettingsConfigPtr s = Settings::instance().getSettingsConfig();
+ s->mRecorders.erase(found.second);
+ }
+ return bool(found.first);
+ }
}
namespace LLError
{
void logToFile(const std::string& file_name)
{
- SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
+ // remove any previous Recorder filling this role
+ removeRecorder<RecordToFile>();
- removeRecorder(s->mFileRecorder);
- s->mFileRecorder.reset();
- s->mFileRecorderFileName.clear();
-
if (!file_name.empty())
{
- RecorderPtr recordToFile(new RecordToFile(file_name));
- if (boost::dynamic_pointer_cast<RecordToFile>(recordToFile)->okay())
- {
- s->mFileRecorderFileName = file_name;
- s->mFileRecorder = recordToFile;
- addRecorder(recordToFile);
- }
+ boost::shared_ptr<RecordToFile> recordToFile(new RecordToFile(file_name));
+ if (recordToFile->okay())
+ {
+ addRecorder(recordToFile);
+ }
}
}
-
- void logToFixedBuffer(LLLineBuffer* fixedBuffer)
+
+ std::string logFileName()
{
- SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
+ auto found = findRecorder<RecordToFile>();
+ return found? found->getFilename() : std::string();
+ }
- removeRecorder(s->mFixedBufferRecorder);
- s->mFixedBufferRecorder.reset();
-
- if (fixedBuffer)
- {
- RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
- s->mFixedBufferRecorder = recordToFixedBuffer;
- addRecorder(recordToFixedBuffer);
+ void logToStderr()
+ {
+ if (! findRecorder<RecordToStderr>())
+ {
+ RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime()));
+ addRecorder(recordToStdErr);
}
- }
+ }
- std::string logFileName()
+ void logToFixedBuffer(LLLineBuffer* fixedBuffer)
{
- SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
- return s->mFileRecorderFileName;
+ // remove any previous Recorder filling this role
+ removeRecorder<RecordToFixedBuffer>();
+
+ if (fixedBuffer)
+ {
+ RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
+ addRecorder(recordToFixedBuffer);
+ }
}
}
@@ -1154,8 +1235,25 @@ namespace
}
namespace {
- LLMutex gLogMutex;
- LLMutex gCallStacksLogMutex;
+ // We need a couple different mutexes, but we want to use the same mechanism
+ // for both. Make getMutex() a template function with different instances
+ // for different MutexDiscriminator values.
+ enum MutexDiscriminator
+ {
+ LOG_MUTEX,
+ STACKS_MUTEX
+ };
+ // Some logging calls happen very early in processing -- so early that our
+ // module-static variables aren't yet initialized. getMutex() wraps a
+ // function-static LLMutex so that early calls can still have a valid
+ // LLMutex instance.
+ template <MutexDiscriminator MTX>
+ LLMutex* getMutex()
+ {
+ // guaranteed to be initialized the first time control reaches here
+ static LLMutex sMutex;
+ return &sMutex;
+ }
bool checkLevelMap(const LevelMap& map, const std::string& key,
LLError::ELevel& level)
@@ -1203,7 +1301,7 @@ namespace LLError
bool Log::shouldLog(CallSite& site)
{
- LLMutexTrylock lock(&gLogMutex, 5);
+ LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
if (!lock.isLocked())
{
return false;
@@ -1254,7 +1352,7 @@ namespace LLError
std::ostringstream* Log::out()
{
- LLMutexTrylock lock(&gLogMutex,5);
+ LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5);
// If we hit a logging request very late during shutdown processing,
// when either of the relevant LLSingletons has already been deleted,
// DO NOT resurrect them.
@@ -1274,7 +1372,7 @@ namespace LLError
void Log::flush(std::ostringstream* out, char* message)
{
- LLMutexTrylock lock(&gLogMutex,5);
+ LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5);
if (!lock.isLocked())
{
return;
@@ -1314,7 +1412,7 @@ namespace LLError
void Log::flush(std::ostringstream* out, const CallSite& site)
{
- LLMutexTrylock lock(&gLogMutex,5);
+ LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5);
if (!lock.isLocked())
{
return;
@@ -1486,129 +1584,133 @@ namespace LLError
S32 LLCallStacks::sIndex = 0 ;
//static
- void LLCallStacks::allocateStackBuffer()
- {
- if(sBuffer == NULL)
- {
- sBuffer = new char*[512] ;
- sBuffer[0] = new char[512 * 128] ;
- for(S32 i = 1 ; i < 512 ; i++)
- {
- sBuffer[i] = sBuffer[i-1] + 128 ;
- }
- sIndex = 0 ;
- }
- }
-
- void LLCallStacks::freeStackBuffer()
- {
- if(sBuffer != NULL)
- {
- delete [] sBuffer[0] ;
- delete [] sBuffer ;
- sBuffer = NULL ;
- }
- }
-
- //static
- void LLCallStacks::push(const char* function, const int line)
- {
- LLMutexTrylock lock(&gCallStacksLogMutex, 5);
- if (!lock.isLocked())
- {
- return;
- }
-
- if(sBuffer == NULL)
- {
- allocateStackBuffer();
- }
-
- if(sIndex > 511)
- {
- clear() ;
- }
-
- strcpy(sBuffer[sIndex], function) ;
- sprintf(sBuffer[sIndex] + strlen(function), " line: %d ", line) ;
- sIndex++ ;
-
- return ;
- }
+ void LLCallStacks::allocateStackBuffer()
+ {
+ if(sBuffer == NULL)
+ {
+ sBuffer = new char*[512] ;
+ sBuffer[0] = new char[512 * 128] ;
+ for(S32 i = 1 ; i < 512 ; i++)
+ {
+ sBuffer[i] = sBuffer[i-1] + 128 ;
+ }
+ sIndex = 0 ;
+ }
+ }
- //static
- std::ostringstream* LLCallStacks::insert(const char* function, const int line)
- {
- std::ostringstream* _out = LLError::Log::out();
- *_out << function << " line " << line << " " ;
-
- return _out ;
- }
-
- //static
- void LLCallStacks::end(std::ostringstream* _out)
- {
- LLMutexTrylock lock(&gCallStacksLogMutex, 5);
- if (!lock.isLocked())
- {
- return;
- }
-
- if(sBuffer == NULL)
- {
- allocateStackBuffer();
- }
-
- if(sIndex > 511)
- {
- clear() ;
- }
-
- LLError::Log::flush(_out, sBuffer[sIndex++]) ;
- }
-
- //static
- void LLCallStacks::print()
- {
- LLMutexTrylock lock(&gCallStacksLogMutex, 5);
- if (!lock.isLocked())
- {
- return;
- }
-
- if(sIndex > 0)
- {
- LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL;
- while(sIndex > 0)
- {
- sIndex-- ;
- LL_INFOS() << sBuffer[sIndex] << LL_ENDL;
- }
- LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL;
- }
-
- if(sBuffer != NULL)
- {
- freeStackBuffer();
- }
- }
-
- //static
- void LLCallStacks::clear()
- {
- sIndex = 0 ;
- }
-
- //static
- void LLCallStacks::cleanup()
- {
- freeStackBuffer();
- }
+ void LLCallStacks::freeStackBuffer()
+ {
+ if(sBuffer != NULL)
+ {
+ delete [] sBuffer[0] ;
+ delete [] sBuffer ;
+ sBuffer = NULL ;
+ }
+ }
+
+ //static
+ void LLCallStacks::push(const char* function, const int line)
+ {
+ LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
+
+ if(sBuffer == NULL)
+ {
+ allocateStackBuffer();
+ }
+
+ if(sIndex > 511)
+ {
+ clear() ;
+ }
+
+ strcpy(sBuffer[sIndex], function) ;
+ sprintf(sBuffer[sIndex] + strlen(function), " line: %d ", line) ;
+ sIndex++ ;
+
+ return ;
+ }
+
+ //static
+ std::ostringstream* LLCallStacks::insert(const char* function, const int line)
+ {
+ std::ostringstream* _out = LLError::Log::out();
+ *_out << function << " line " << line << " " ;
+ return _out ;
+ }
+
+ //static
+ void LLCallStacks::end(std::ostringstream* _out)
+ {
+ LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
+
+ if(sBuffer == NULL)
+ {
+ allocateStackBuffer();
+ }
+
+ if(sIndex > 511)
+ {
+ clear() ;
+ }
+
+ LLError::Log::flush(_out, sBuffer[sIndex++]) ;
+ }
+
+ //static
+ void LLCallStacks::print()
+ {
+ LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
+
+ if(sIndex > 0)
+ {
+ LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL;
+ while(sIndex > 0)
+ {
+ sIndex-- ;
+ LL_INFOS() << sBuffer[sIndex] << LL_ENDL;
+ }
+ LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL;
+ }
+
+ if(sBuffer != NULL)
+ {
+ freeStackBuffer();
+ }
+ }
+
+ //static
+ void LLCallStacks::clear()
+ {
+ sIndex = 0 ;
+ }
+
+ //static
+ void LLCallStacks::cleanup()
+ {
+ freeStackBuffer();
+ }
+
+ std::ostream& operator<<(std::ostream& out, const LLStacktrace&)
+ {
+ return out << boost::stacktrace::stacktrace();
+ }
}
bool debugLoggingEnabled(const std::string& tag)
{
- LLMutexTrylock lock(&gLogMutex, 5);
+ LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
if (!lock.isLocked())
{
return false;
diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h
index 0a78229555..ffaa464d77 100644
--- a/indra/llcommon/llerror.h
+++ b/indra/llcommon/llerror.h
@@ -191,9 +191,9 @@ namespace LLError
The classes CallSite and Log are used by the logging macros below.
They are not intended for general use.
*/
-
+
struct CallSite;
-
+
class LL_COMMON_API Log
{
public:
@@ -202,8 +202,17 @@ namespace LLError
static void flush(std::ostringstream* out, char* message);
static void flush(std::ostringstream*, const CallSite&);
static std::string demangle(const char* mangled);
+ /// classname<TYPE>()
+ template <typename T>
+ static std::string classname() { return demangle(typeid(T).name()); }
+ /// classname(some_pointer)
+ template <typename T>
+ static std::string classname(T* const ptr) { return ptr? demangle(typeid(*ptr).name()) : "nullptr"; }
+ /// classname(some_reference)
+ template <typename T>
+ static std::string classname(const T& obj) { return demangle(typeid(obj).name()); }
};
-
+
struct LL_COMMON_API CallSite
{
// Represents a specific place in the code where a message is logged
@@ -262,30 +271,36 @@ namespace LLError
class LL_COMMON_API NoClassInfo { };
// used to indicate no class info known for logging
- //LLCallStacks keeps track of call stacks and output the call stacks to log file
- //when LLAppViewer::handleViewerCrash() is triggered.
- //
- //Note: to be simple, efficient and necessary to keep track of correct call stacks,
- //LLCallStacks is designed not to be thread-safe.
- //so try not to use it in multiple parallel threads at same time.
- //Used in a single thread at a time is fine.
- class LL_COMMON_API LLCallStacks
- {
- private:
- static char** sBuffer ;
- static S32 sIndex ;
-
- static void allocateStackBuffer();
- static void freeStackBuffer();
-
- public:
- static void push(const char* function, const int line) ;
- static std::ostringstream* insert(const char* function, const int line) ;
- static void print() ;
- static void clear() ;
- static void end(std::ostringstream* _out) ;
- static void cleanup();
- };
+ //LLCallStacks keeps track of call stacks and output the call stacks to log file
+ //when LLAppViewer::handleViewerCrash() is triggered.
+ //
+ //Note: to be simple, efficient and necessary to keep track of correct call stacks,
+ //LLCallStacks is designed not to be thread-safe.
+ //so try not to use it in multiple parallel threads at same time.
+ //Used in a single thread at a time is fine.
+ class LL_COMMON_API LLCallStacks
+ {
+ private:
+ static char** sBuffer ;
+ static S32 sIndex ;
+
+ static void allocateStackBuffer();
+ static void freeStackBuffer();
+
+ public:
+ static void push(const char* function, const int line) ;
+ static std::ostringstream* insert(const char* function, const int line) ;
+ static void print() ;
+ static void clear() ;
+ static void end(std::ostringstream* _out) ;
+ static void cleanup();
+ };
+
+ // class which, when streamed, inserts the current stack trace
+ struct LLStacktrace
+ {
+ friend std::ostream& operator<<(std::ostream& out, const LLStacktrace&);
+ };
}
//this is cheaper than llcallstacks if no need to output other variables to call stacks.
@@ -381,8 +396,13 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
#define LL_WARNS(...) lllog(LLError::LEVEL_WARN, false, ##__VA_ARGS__)
#define LL_ERRS(...) lllog(LLError::LEVEL_ERROR, false, ##__VA_ARGS__)
// alternative to llassert_always that prints explanatory message
-#define LL_WARNS_IF(exp, ...) if (exp) LL_WARNS(##__VA_ARGS__) << "(" #exp ")"
-#define LL_ERRS_IF(exp, ...) if (exp) LL_ERRS(##__VA_ARGS__) << "(" #exp ")"
+// note ## token paste operator hack used above will only work in gcc following
+// a comma and is completely unnecessary in VS since the comma is automatically
+// suppressed
+// https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
+// https://docs.microsoft.com/en-us/cpp/preprocessor/variadic-macros?view=vs-2015
+#define LL_WARNS_IF(exp, ...) if (exp) LL_WARNS(__VA_ARGS__) << "(" #exp ")"
+#define LL_ERRS_IF(exp, ...) if (exp) LL_ERRS(__VA_ARGS__) << "(" #exp ")"
// Only print the log message once (good for warnings or infos that would otherwise
// spam the log file over and over, such as tighter loops).
diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h
index 276d22fc36..bfa2269025 100644
--- a/indra/llcommon/llerrorcontrol.h
+++ b/indra/llcommon/llerrorcontrol.h
@@ -183,6 +183,7 @@ namespace LLError
// each error message is passed to each recorder via recordMessage()
LL_COMMON_API void logToFile(const std::string& filename);
+ LL_COMMON_API void logToStderr();
LL_COMMON_API void logToFixedBuffer(LLLineBuffer*);
// Utilities to add recorders for logging to a file or a fixed buffer
// A second call to the same function will remove the logger added
diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp
index 56367b8f54..995356dc52 100644
--- a/indra/llcommon/lleventcoro.cpp
+++ b/indra/llcommon/lleventcoro.cpp
@@ -31,17 +31,17 @@
// associated header
#include "lleventcoro.h"
// STL headers
-#include <map>
+#include <chrono>
+#include <exception>
// std headers
// external library headers
+#include <boost/fiber/operations.hpp>
// other Linden headers
#include "llsdserialize.h"
+#include "llsdutil.h"
#include "llerror.h"
#include "llcoros.h"
-#include "llmake.h"
-#include "llexception.h"
-
-#include "lleventfilter.h"
+#include "stringize.h"
namespace
{
@@ -62,7 +62,7 @@ namespace
std::string listenerNameForCoro()
{
// If this coroutine was launched by LLCoros::launch(), find that name.
- std::string name(LLCoros::instance().getName());
+ std::string name(LLCoros::getName());
if (! name.empty())
{
return name;
@@ -92,137 +92,173 @@ std::string listenerNameForCoro()
* In the degenerate case in which @a path is an empty array, @a dest will
* @em become @a value rather than @em containing it.
*/
-void storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value)
+void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value)
{
- if (rawPath.isUndefined())
+ if (path.isUndefined())
{
// no-op case
return;
}
- // Arrange to treat rawPath uniformly as an array. If it's not already an
- // array, store it as the only entry in one.
- LLSD path;
- if (rawPath.isArray())
- {
- path = rawPath;
- }
- else
- {
- path.append(rawPath);
- }
-
- // Need to indicate a current destination -- but that current destination
- // needs to change as we step through the path array. Where normally we'd
- // use an LLSD& to capture a subscripted LLSD lvalue, this time we must
- // instead use a pointer -- since it must be reassigned.
- LLSD* pdest = &dest;
-
- // Now loop through that array
- for (LLSD::Integer i = 0; i < path.size(); ++i)
- {
- if (path[i].isString())
- {
- // *pdest is an LLSD map
- pdest = &((*pdest)[path[i].asString()]);
- }
- else if (path[i].isInteger())
- {
- // *pdest is an LLSD array
- pdest = &((*pdest)[path[i].asInteger()]);
- }
- else
- {
- // What do we do with Real or Array or Map or ...?
- // As it's a coder error -- not a user error -- rub the coder's
- // face in it so it gets fixed.
- LL_ERRS("lleventcoro") << "storeToLLSDPath(" << dest << ", " << rawPath << ", " << value
- << "): path[" << i << "] bad type " << path[i].type() << LL_ENDL;
- }
- }
-
- // Here *pdest is where we should store value.
- *pdest = value;
+ // Drill down to where we should store 'value'.
+ llsd::drill(dest, path) = value;
}
-/// For LLCoros::Future<LLSD>::make_callback(), the callback has a signature
-/// like void callback(LLSD), which isn't a valid LLEventPump listener: such
-/// listeners must return bool.
-template <typename LISTENER>
-class FutureListener
-{
-public:
- // FutureListener is instantiated on the coroutine stack: the stack, in
- // other words, that wants to suspend.
- FutureListener(const LISTENER& listener):
- mListener(listener),
- // Capture the suspending coroutine's flag as a consuming or
- // non-consuming listener.
- mConsume(LLCoros::get_consuming())
- {}
-
- // operator()() is called on the main stack: the stack on which the
- // expected event is fired.
- bool operator()(const LLSD& event)
- {
- mListener(event);
- // tell upstream LLEventPump whether listener consumed
- return mConsume;
- }
-
-protected:
- LISTENER mListener;
- bool mConsume;
-};
-
} // anonymous
void llcoro::suspend()
{
- // By viewer convention, we post an event on the "mainloop" LLEventPump
- // each iteration of the main event-handling loop. So waiting for a single
- // event on "mainloop" gives us a one-frame suspend.
- suspendUntilEventOn("mainloop");
+ LLCoros::checkStop();
+ LLCoros::TempStatus st("waiting one tick");
+ boost::this_fiber::yield();
}
void llcoro::suspendUntilTimeout(float seconds)
{
- LLEventTimeout timeout;
-
- timeout.eventAfter(seconds, LLSD());
- llcoro::suspendUntilEventOn(timeout);
+ LLCoros::checkStop();
+ // We used to call boost::this_fiber::sleep_for(). But some coroutines
+ // (e.g. LLExperienceCache::idleCoro()) sit in a suspendUntilTimeout()
+ // loop, in which case a sleep_for() call risks sleeping through shutdown.
+ // So instead, listen for "LLApp" state-changing events -- which
+ // fortunately is handled for us by suspendUntilEventOnWithTimeout().
+ // Wait for an event on a bogus LLEventPump on which nobody ever posts
+ // events. Don't make it static because that would force instantiation of
+ // the LLEventPumps LLSingleton registry at static initialization time.
+ // DO allow tweaking the name for uniqueness, this definitely gets
+ // re-entered on multiple coroutines!
+ // We could use an LLUUID if it were important to actively prohibit anyone
+ // from ever posting on this LLEventPump.
+ LLEventStream bogus("xyzzy", true);
+ // Timeout is the NORMAL case for this call!
+ static LLSD timedout;
+ // Deliver, but ignore, timedout when (as usual) we did not receive any
+ // "LLApp" event. The point is that suspendUntilEventOnWithTimeout() will
+ // itself throw Stopping when "LLApp" starts broadcasting shutdown events.
+ suspendUntilEventOnWithTimeout(bogus, seconds, timedout);
}
-LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump,
- const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath)
+namespace
{
- // declare the future
- LLCoros::Future<LLSD> future;
- // make a callback that will assign a value to the future, and listen on
- // the specified LLEventPump with that callback
- std::string listenerName(listenerNameForCoro());
- LLTempBoundListener connection(
- replyPump.getPump().listen(listenerName,
- llmake<FutureListener>(future.make_callback())));
+
+// returns a listener on replyPumpP, also on "mainloop" -- both should be
+// stored in LLTempBoundListeners on the caller's stack frame
+std::pair<LLBoundListener, LLBoundListener>
+postAndSuspendSetup(const std::string& callerName,
+ const std::string& listenerName,
+ LLCoros::Promise<LLSD>& promise,
+ const LLSD& event,
+ const LLEventPumpOrPumpName& requestPumpP,
+ const LLEventPumpOrPumpName& replyPumpP,
+ const LLSD& replyPumpNamePath)
+{
+ // Before we get any farther -- should we be stopping instead of
+ // suspending?
+ LLCoros::checkStop();
+ // Get the consuming attribute for THIS coroutine, the one that's about to
+ // suspend. Don't call get_consuming() in the lambda body: that would
+ // return the consuming attribute for some other coroutine, most likely
+ // the main routine.
+ bool consuming(LLCoros::get_consuming());
+ // listen on the specified LLEventPump with a lambda that will assign a
+ // value to the promise, thus fulfilling its future
+ llassert_always_msg(replyPumpP, ("replyPump required for " + callerName));
+ LLEventPump& replyPump(replyPumpP.getPump());
+ // The relative order of the two listen() calls below would only matter if
+ // "LLApp" were an LLEventMailDrop. But if we ever go there, we'd want to
+ // notice the pending LLApp status first.
+ LLBoundListener stopper(
+ LLEventPumps::instance().obtain("LLApp").listen(
+ listenerName,
+ [&promise, listenerName](const LLSD& status)
+ {
+ // anything except "running" should wake up the waiting
+ // coroutine
+ auto& statsd = status["status"];
+ if (statsd.asString() != "running")
+ {
+ LL_DEBUGS("lleventcoro") << listenerName
+ << " spotted status " << statsd
+ << ", throwing Stopping" << LL_ENDL;
+ try
+ {
+ promise.set_exception(
+ std::make_exception_ptr(
+ LLCoros::Stopping("status " + statsd.asString())));
+ }
+ catch (const boost::fibers::promise_already_satisfied&)
+ {
+ LL_WARNS("lleventcoro") << listenerName
+ << " couldn't throw Stopping "
+ "because promise already set" << LL_ENDL;
+ }
+ }
+ // do not consume -- every listener must see status
+ return false;
+ }));
+ LLBoundListener connection(
+ replyPump.listen(
+ listenerName,
+ [&promise, consuming, listenerName](const LLSD& result)
+ {
+ try
+ {
+ promise.set_value(result);
+ // We did manage to propagate the result value to the
+ // (real) listener. If we're supposed to indicate that
+ // we've consumed it, do so.
+ return consuming;
+ }
+ catch(boost::fibers::promise_already_satisfied & ex)
+ {
+ LL_DEBUGS("lleventcoro") << "promise already satisfied in '"
+ << listenerName << "': " << ex.what() << LL_ENDL;
+ // We could not propagate the result value to the
+ // listener.
+ return false;
+ }
+ }));
+
// skip the "post" part if requestPump is default-constructed
- if (requestPump)
+ if (requestPumpP)
{
+ LLEventPump& requestPump(requestPumpP.getPump());
// If replyPumpNamePath is non-empty, store the replyPump name in the
// request event.
LLSD modevent(event);
- storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName());
- LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
- << " posting to " << requestPump.getPump().getName()
+ storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getName());
+ LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName
+ << " posting to " << requestPump.getName()
<< LL_ENDL;
// *NOTE:Mani - Removed because modevent could contain user's hashed passwd.
// << ": " << modevent << LL_ENDL;
- requestPump.getPump().post(modevent);
+ requestPump.post(modevent);
}
- LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
- << " about to wait on LLEventPump " << replyPump.getPump().getName()
+ LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName
+ << " about to wait on LLEventPump " << replyPump.getName()
<< LL_ENDL;
+ return { connection, stopper };
+}
+
+} // anonymous
+
+LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump,
+ const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath)
+{
+ LLCoros::Promise<LLSD> promise;
+ std::string listenerName(listenerNameForCoro());
+
+ // Store both connections into LLTempBoundListeners so we implicitly
+ // disconnect on return from this function.
+ auto connections =
+ postAndSuspendSetup("postAndSuspend()", listenerName, promise,
+ event, requestPump, replyPump, replyPumpNamePath);
+ LLTempBoundListener connection(connections.first), stopper(connections.second);
+
+ // declare the future
+ LLCoros::Future<LLSD> future = LLCoros::getFuture(promise);
// calling get() on the future makes us wait for it
+ LLCoros::TempStatus st(STRINGIZE("waiting for " << replyPump.getPump().getName()));
LLSD value(future.get());
LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
<< " resuming with " << value << LL_ENDL;
@@ -230,147 +266,52 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ
return value;
}
-LLSD llcoro::suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName,
- F32 timeoutin, const LLSD &timeoutResult)
-{
- /**
- * The timeout pump is attached upstream of of the waiting pump and will
- * pass the timeout event through it. We CAN NOT attach downstream since
- * doing so will cause the suspendPump to fire any waiting events immediately
- * and they will be lost. This becomes especially problematic with the
- * LLEventTimeout(pump) constructor which will also attempt to fire those
- * events using the virtual listen_impl method in the not yet fully constructed
- * timeoutPump.
- */
- LLEventTimeout timeoutPump;
- LLEventPump &suspendPump = suspendPumpOrName.getPump();
-
- LLTempBoundListener timeoutListener(timeoutPump.listen(suspendPump.getName(),
- boost::bind(&LLEventPump::post, &suspendPump, _1)));
-
- timeoutPump.eventAfter(timeoutin, timeoutResult);
- return llcoro::suspendUntilEventOn(suspendPump);
-}
-
-namespace
-{
-
-/**
- * This helper is specifically for postAndSuspend2(). We use a single future
- * object, but we want to listen on two pumps with it. Since we must still
- * adapt from the callable constructed by boost::dcoroutines::make_callback()
- * (void return) to provide an event listener (bool return), we've adapted
- * FutureListener for the purpose. The basic idea is that we construct a
- * distinct instance of FutureListener2 -- binding different instance data --
- * for each of the pumps. Then, when a pump delivers an LLSD value to either
- * FutureListener2, it can combine that LLSD with its discriminator to feed
- * the future object.
- *
- * DISCRIM is a template argument so we can use llmake() rather than
- * having to write our own argument-deducing helper function.
- */
-template <typename LISTENER, typename DISCRIM>
-class FutureListener2: public FutureListener<LISTENER>
+LLSD llcoro::postAndSuspendWithTimeout(const LLSD& event,
+ const LLEventPumpOrPumpName& requestPump,
+ const LLEventPumpOrPumpName& replyPump,
+ const LLSD& replyPumpNamePath,
+ F32 timeout, const LLSD& timeoutResult)
{
- typedef FutureListener<LISTENER> super;
-
-public:
- // instantiated on coroutine stack: the stack about to suspend
- FutureListener2(const LISTENER& listener, DISCRIM discriminator):
- super(listener),
- mDiscrim(discriminator)
- {}
-
- // called on main stack: the stack on which event is fired
- bool operator()(const LLSD& event)
- {
- // our future object is defined to accept LLEventWithID
- super::mListener(LLEventWithID(event, mDiscrim));
- // tell LLEventPump whether or not event was consumed
- return super::mConsume;
- }
-
-private:
- const DISCRIM mDiscrim;
-};
+ LLCoros::Promise<LLSD> promise;
+ std::string listenerName(listenerNameForCoro());
-} // anonymous
+ // Store both connections into LLTempBoundListeners so we implicitly
+ // disconnect on return from this function.
+ auto connections =
+ postAndSuspendSetup("postAndSuspendWithTimeout()", listenerName, promise,
+ event, requestPump, replyPump, replyPumpNamePath);
+ LLTempBoundListener connection(connections.first), stopper(connections.second);
-namespace llcoro
-{
-
-LLEventWithID postAndSuspend2(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLEventPumpOrPumpName& replyPump0,
- const LLEventPumpOrPumpName& replyPump1,
- const LLSD& replyPump0NamePath,
- const LLSD& replyPump1NamePath)
-{
// declare the future
- LLCoros::Future<LLEventWithID> future;
- // either callback will assign a value to this future; listen on
- // each specified LLEventPump with a callback
- std::string name(listenerNameForCoro());
- LLTempBoundListener connection0(
- replyPump0.getPump().listen(
- name + "a",
- llmake<FutureListener2>(future.make_callback(), 0)));
- LLTempBoundListener connection1(
- replyPump1.getPump().listen(
- name + "b",
- llmake<FutureListener2>(future.make_callback(), 1)));
- // skip the "post" part if requestPump is default-constructed
- if (requestPump)
+ LLCoros::Future<LLSD> future = LLCoros::getFuture(promise);
+ // wait for specified timeout
+ boost::fibers::future_status status;
{
- // If either replyPumpNamePath is non-empty, store the corresponding
- // replyPump name in the request event.
- LLSD modevent(event);
- storeToLLSDPath(modevent, replyPump0NamePath,
- replyPump0.getPump().getName());
- storeToLLSDPath(modevent, replyPump1NamePath,
- replyPump1.getPump().getName());
- LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name
- << " posting to " << requestPump.getPump().getName()
- << ": " << modevent << LL_ENDL;
- requestPump.getPump().post(modevent);
+ LLCoros::TempStatus st(STRINGIZE("waiting for " << replyPump.getPump().getName()
+ << " for " << timeout << "s"));
+ // The fact that we accept non-integer seconds means we should probably
+ // use granularity finer than one second. However, given the overhead of
+ // the rest of our processing, it seems silly to use granularity finer
+ // than a millisecond.
+ status = future.wait_for(std::chrono::milliseconds(long(timeout * 1000)));
}
- LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name
- << " about to wait on LLEventPumps " << replyPump0.getPump().getName()
- << ", " << replyPump1.getPump().getName() << LL_ENDL;
- // calling get() on the future makes us wait for it
- LLEventWithID value(future.get());
- LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << name
- << " resuming with (" << value.first << ", " << value.second << ")"
- << LL_ENDL;
- // returning should disconnect both connections
- return value;
-}
-
-LLSD errorException(const LLEventWithID& result, const std::string& desc)
-{
- // If the result arrived on the error pump (pump 1), instead of
- // returning it, deliver it via exception.
- if (result.second)
+ // if the future is NOT yet ready, return timeoutResult instead
+ if (status == boost::fibers::future_status::timeout)
{
- LLTHROW(LLErrorEvent(desc, result.first));
+ LL_DEBUGS("lleventcoro") << "postAndSuspendWithTimeout(): coroutine " << listenerName
+ << " timed out after " << timeout << " seconds,"
+ << " resuming with " << timeoutResult << LL_ENDL;
+ return timeoutResult;
}
- // That way, our caller knows a simple return must be from the reply
- // pump (pump 0).
- return result.first;
-}
-
-LLSD errorLog(const LLEventWithID& result, const std::string& desc)
-{
- // If the result arrived on the error pump (pump 1), log it as a fatal
- // error.
- if (result.second)
+ else
{
- LL_ERRS("errorLog") << desc << ":" << std::endl;
- LLSDSerialize::toPrettyXML(result.first, LL_CONT);
- LL_CONT << LL_ENDL;
+ llassert_always(status == boost::fibers::future_status::ready);
+
+ // future is now ready, no more waiting
+ LLSD value(future.get());
+ LL_DEBUGS("lleventcoro") << "postAndSuspendWithTimeout(): coroutine " << listenerName
+ << " resuming with " << value << LL_ENDL;
+ // returning should disconnect the connection
+ return value;
}
- // A simple return must therefore be from the reply pump (pump 0).
- return result.first;
}
-
-} // namespace llcoro
diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h
index 84827aab4a..c0fe8b094f 100644
--- a/indra/llcommon/lleventcoro.h
+++ b/indra/llcommon/lleventcoro.h
@@ -29,12 +29,8 @@
#if ! defined(LL_LLEVENTCORO_H)
#define LL_LLEVENTCORO_H
-#include <boost/optional.hpp>
#include <string>
-#include <utility> // std::pair
#include "llevents.h"
-#include "llerror.h"
-#include "llexception.h"
/**
* Like LLListenerOrPumpName, this is a class intended for parameter lists:
@@ -147,117 +143,29 @@ LLSD suspendUntilEventOn(const LLEventPumpOrPumpName& pump)
return postAndSuspend(LLSD(), LLEventPumpOrPumpName(), pump);
}
+/// Like postAndSuspend(), but if we wait longer than @a timeout seconds,
+/// stop waiting and return @a timeoutResult instead.
+LLSD postAndSuspendWithTimeout(const LLSD& event,
+ const LLEventPumpOrPumpName& requestPump,
+ const LLEventPumpOrPumpName& replyPump,
+ const LLSD& replyPumpNamePath,
+ F32 timeout, const LLSD& timeoutResult);
+
/// Suspend the coroutine until an event is fired on the identified pump
/// or the timeout duration has elapsed. If the timeout duration
/// elapses the specified LLSD is returned.
-LLSD suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName, F32 timeoutin, const LLSD &timeoutResult);
-
-} // namespace llcoro
-
-/// return type for two-pump variant of suspendUntilEventOn()
-typedef std::pair<LLSD, int> LLEventWithID;
-
-namespace llcoro
-{
-
-/**
- * This function waits for a reply on either of two specified LLEventPumps.
- * Otherwise, it closely resembles postAndSuspend(); please see the documentation
- * for that function for detailed parameter info.
- *
- * While we could have implemented the single-pump variant in terms of this
- * one, there's enough added complexity here to make it worthwhile to give the
- * single-pump variant its own straightforward implementation. Conversely,
- * though we could use preprocessor logic to generate n-pump overloads up to
- * BOOST_COROUTINE_WAIT_MAX, we don't foresee a use case. This two-pump
- * overload exists because certain event APIs are defined in terms of a reply
- * LLEventPump and an error LLEventPump.
- *
- * The LLEventWithID return value provides not only the received event, but
- * the index of the pump on which it arrived (0 or 1).
- *
- * @note
- * I'd have preferred to overload the name postAndSuspend() for both signatures.
- * But consider the following ambiguous call:
- * @code
- * postAndSuspend(LLSD(), requestPump, replyPump, "someString");
- * @endcode
- * "someString" could be converted to either LLSD (@a replyPumpNamePath for
- * the single-pump function) or LLEventOrPumpName (@a replyPump1 for two-pump
- * function).
- *
- * It seems less burdensome to write postAndSuspend2() than to write either
- * LLSD("someString") or LLEventOrPumpName("someString").
- */
-LLEventWithID postAndSuspend2(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLEventPumpOrPumpName& replyPump0,
- const LLEventPumpOrPumpName& replyPump1,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD());
-
-/**
- * Wait for the next event on either of two specified LLEventPumps.
- */
inline
-LLEventWithID
-suspendUntilEventOn(const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1)
+LLSD suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName,
+ F32 timeoutin, const LLSD &timeoutResult)
{
- // This is now a convenience wrapper for postAndSuspend2().
- return postAndSuspend2(LLSD(), LLEventPumpOrPumpName(), pump0, pump1);
+ return postAndSuspendWithTimeout(LLSD(), // event
+ LLEventPumpOrPumpName(), // requestPump
+ suspendPumpOrName, // replyPump
+ LLSD(), // replyPumpNamePath
+ timeoutin,
+ timeoutResult);
}
-/**
- * Helper for the two-pump variant of suspendUntilEventOn(), e.g.:
- *
- * @code
- * LLSD reply = errorException(suspendUntilEventOn(replyPump, errorPump),
- * "error response from login.cgi");
- * @endcode
- *
- * Examines an LLEventWithID, assuming that the second pump (pump 1) is
- * listening for an error indication. If the incoming data arrived on pump 1,
- * throw an LLErrorEvent exception. If the incoming data arrived on pump 0,
- * just return it. Since a normal return can only be from pump 0, we no longer
- * need the LLEventWithID's discriminator int; we can just return the LLSD.
- *
- * @note I'm not worried about introducing the (fairly generic) name
- * errorException() into global namespace, because how many other overloads of
- * the same name are going to accept an LLEventWithID parameter?
- */
-LLSD errorException(const LLEventWithID& result, const std::string& desc);
-
-} // namespace llcoro
-
-/**
- * Exception thrown by errorException(). We don't call this LLEventError
- * because it's not an error in event processing: rather, this exception
- * announces an event that bears error information (for some other API).
- */
-class LL_COMMON_API LLErrorEvent: public LLException
-{
-public:
- LLErrorEvent(const std::string& what, const LLSD& data):
- LLException(what),
- mData(data)
- {}
- virtual ~LLErrorEvent() throw() {}
-
- LLSD getData() const { return mData; }
-
-private:
- LLSD mData;
-};
-
-namespace llcoro
-{
-
-/**
- * Like errorException(), save that this trips a fatal error using LL_ERRS
- * rather than throwing an exception.
- */
-LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc);
-
} // namespace llcoro
/**
@@ -304,84 +212,4 @@ private:
LLEventStream mPump;
};
-/**
- * Other event APIs require the names of two different LLEventPumps: one for
- * success response, the other for error response. Extend LLCoroEventPump
- * for the two-pump use case.
- */
-class LL_COMMON_API LLCoroEventPumps
-{
-public:
- LLCoroEventPumps(const std::string& name="coro",
- const std::string& suff0="Reply",
- const std::string& suff1="Error"):
- mPump0(name + suff0, true), // allow tweaking the pump instance name
- mPump1(name + suff1, true)
- {}
- /// request pump 0's name
- std::string getName0() const { return mPump0.getName(); }
- /// request pump 1's name
- std::string getName1() const { return mPump1.getName(); }
- /// request both names
- std::pair<std::string, std::string> getNames() const
- {
- return std::pair<std::string, std::string>(mPump0.getName(), mPump1.getName());
- }
-
- /// request pump 0
- LLEventPump& getPump0() { return mPump0; }
- /// request pump 1
- LLEventPump& getPump1() { return mPump1; }
-
- /// suspendUntilEventOn(either of our two LLEventPumps)
- LLEventWithID suspend()
- {
- return llcoro::suspendUntilEventOn(mPump0, mPump1);
- }
-
- /// errorException(suspend())
- LLSD suspendWithException()
- {
- return llcoro::errorException(suspend(), std::string("Error event on ") + getName1());
- }
-
- /// errorLog(suspend())
- LLSD suspendWithLog()
- {
- return llcoro::errorLog(suspend(), std::string("Error event on ") + getName1());
- }
-
- LLEventWithID postAndSuspend(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD())
- {
- return llcoro::postAndSuspend2(event, requestPump, mPump0, mPump1,
- replyPump0NamePath, replyPump1NamePath);
- }
-
- LLSD postAndSuspendWithException(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD())
- {
- return llcoro::errorException(postAndSuspend(event, requestPump,
- replyPump0NamePath, replyPump1NamePath),
- std::string("Error event on ") + getName1());
- }
-
- LLSD postAndSuspendWithLog(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD())
- {
- return llcoro::errorLog(postAndSuspend(event, requestPump,
- replyPump0NamePath, replyPump1NamePath),
- std::string("Error event on ") + getName1());
- }
-
-private:
- LLEventStream mPump0, mPump1;
-};
-
#endif /* ! defined(LL_LLEVENTCORO_H) */
diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp
index 9fb18dc67d..4cded7f88e 100644
--- a/indra/llcommon/lleventfilter.cpp
+++ b/indra/llcommon/lleventfilter.cpp
@@ -37,6 +37,9 @@
// other Linden headers
#include "llerror.h" // LL_ERRS
#include "llsdutil.h" // llsd_matches()
+#include "stringize.h"
+#include "lleventtimer.h"
+#include "lldate.h"
/*****************************************************************************
* LLEventFilter
@@ -182,6 +185,27 @@ bool LLEventTimeout::countdownElapsed() const
return mTimer.hasExpired();
}
+LLEventTimer* LLEventTimeout::post_every(F32 period, const std::string& pump, const LLSD& data)
+{
+ return LLEventTimer::run_every(
+ period,
+ [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
+}
+
+LLEventTimer* LLEventTimeout::post_at(const LLDate& time, const std::string& pump, const LLSD& data)
+{
+ return LLEventTimer::run_at(
+ time,
+ [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
+}
+
+LLEventTimer* LLEventTimeout::post_after(F32 interval, const std::string& pump, const LLSD& data)
+{
+ return LLEventTimer::run_after(
+ interval,
+ [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
+}
+
/*****************************************************************************
* LLEventBatch
*****************************************************************************/
@@ -409,3 +433,61 @@ void LLEventBatchThrottle::setSize(std::size_t size)
flush();
}
}
+
+/*****************************************************************************
+* LLEventLogProxy
+*****************************************************************************/
+LLEventLogProxy::LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak):
+ // note: we are NOT using the constructor that implicitly connects!
+ LLEventFilter(name, tweak),
+ // instead we simply capture a reference to the subject LLEventPump
+ mPump(source)
+{
+}
+
+bool LLEventLogProxy::post(const LLSD& event) /* override */
+{
+ auto counter = mCounter++;
+ auto eventplus = event;
+ if (eventplus.type() == LLSD::TypeMap)
+ {
+ eventplus["_cnt"] = counter;
+ }
+ std::string hdr{STRINGIZE(getName() << ": post " << counter)};
+ LL_INFOS("LogProxy") << hdr << ": " << event << LL_ENDL;
+ bool result = mPump.post(eventplus);
+ LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL;
+ return result;
+}
+
+LLBoundListener LLEventLogProxy::listen_impl(const std::string& name,
+ const LLEventListener& target,
+ const NameList& after,
+ const NameList& before)
+{
+ LL_DEBUGS("LogProxy") << "LLEventLogProxy('" << getName() << "').listen('"
+ << name << "')" << LL_ENDL;
+ return mPump.listen(name,
+ [this, name, target](const LLSD& event)->bool
+ { return listener(name, target, event); },
+ after,
+ before);
+}
+
+bool LLEventLogProxy::listener(const std::string& name,
+ const LLEventListener& target,
+ const LLSD& event) const
+{
+ auto eventminus = event;
+ std::string counter{"**"};
+ if (eventminus.has("_cnt"))
+ {
+ counter = stringize(eventminus["_cnt"].asInteger());
+ eventminus.erase("_cnt");
+ }
+ std::string hdr{STRINGIZE(getName() << " to " << name << " " << counter)};
+ LL_INFOS("LogProxy") << hdr << ": " << eventminus << LL_ENDL;
+ bool result = target(eventminus);
+ LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL;
+ return result;
+}
diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h
index ff8fc9bc7f..48c2570732 100644
--- a/indra/llcommon/lleventfilter.h
+++ b/indra/llcommon/lleventfilter.h
@@ -32,8 +32,12 @@
#include "llevents.h"
#include "stdtypes.h"
#include "lltimer.h"
+#include "llsdutil.h"
#include <boost/function.hpp>
+class LLEventTimer;
+class LLDate;
+
/**
* Generic base class
*/
@@ -210,6 +214,19 @@ public:
LLEventTimeout();
LLEventTimeout(LLEventPump& source);
+ /// using LLEventTimeout as namespace for free functions
+ /// Post event to specified LLEventPump every period seconds. Delete
+ /// returned LLEventTimer* to cancel.
+ static LLEventTimer* post_every(F32 period, const std::string& pump, const LLSD& data);
+ /// Post event to specified LLEventPump at specified future time. Call
+ /// LLEventTimer::getInstance(returned pointer) to check whether it's still
+ /// pending; if so, delete the pointer to cancel.
+ static LLEventTimer* post_at(const LLDate& time, const std::string& pump, const LLSD& data);
+ /// Post event to specified LLEventPump after specified interval. Call
+ /// LLEventTimer::getInstance(returned pointer) to check whether it's still
+ /// pending; if so, delete the pointer to cancel.
+ static LLEventTimer* post_after(F32 interval, const std::string& pump, const LLSD& data);
+
protected:
virtual void setCountdown(F32 seconds);
virtual bool countdownElapsed() const;
@@ -376,4 +393,149 @@ private:
std::size_t mBatchSize;
};
+/**
+ * LLStoreListener self-registers on the LLEventPump of interest, and
+ * unregisters on destruction. As long as it exists, a particular element is
+ * extracted from every event that comes through the upstream LLEventPump and
+ * stored into the target variable.
+ *
+ * This is implemented as a subclass of LLEventFilter, though strictly
+ * speaking it isn't really a "filter" at all: it never passes incoming events
+ * to its own listeners, if any.
+ *
+ * TBD: A variant based on output iterators that stores and then increments
+ * the iterator. Useful with boost::coroutine2!
+ */
+template <typename T>
+class LLStoreListener: public LLEventFilter
+{
+public:
+ // pass target and optional path to element
+ LLStoreListener(T& target, const LLSD& path=LLSD(), bool consume=false):
+ LLEventFilter("store"),
+ mTarget(target),
+ mPath(path),
+ mConsume(consume)
+ {}
+ // construct and connect
+ LLStoreListener(LLEventPump& source, T& target, const LLSD& path=LLSD(), bool consume=false):
+ LLEventFilter(source, "store"),
+ mTarget(target),
+ mPath(path),
+ mConsume(consume)
+ {}
+
+ // Calling post() with an LLSD event extracts the element indicated by
+ // path, then stores it to mTarget.
+ virtual bool post(const LLSD& event)
+ {
+ // Extract the element specified by 'mPath' from 'event'. To perform a
+ // generic type-appropriate store through mTarget, construct an
+ // LLSDParam<T> and store that, thus engaging LLSDParam's custom
+ // conversions.
+ mTarget = LLSDParam<T>(llsd::drill(event, mPath));
+ return mConsume;
+ }
+
+private:
+ T& mTarget;
+ const LLSD mPath;
+ const bool mConsume;
+};
+
+/*****************************************************************************
+* LLEventLogProxy
+*****************************************************************************/
+/**
+ * LLEventLogProxy is a little different than the other LLEventFilter
+ * subclasses declared in this header file, in that it completely wraps the
+ * passed LLEventPump (both input and output) instead of simply processing its
+ * output. Of course, if someone directly posts to the wrapped LLEventPump by
+ * looking up its string name in LLEventPumps, LLEventLogProxy can't intercept
+ * that post() call. But as long as consuming code is willing to access the
+ * LLEventLogProxy instance instead of the wrapped LLEventPump, all event data
+ * both post()ed and received is logged.
+ *
+ * The proxy role means that LLEventLogProxy intercepts more of LLEventPump's
+ * API than a typical LLEventFilter subclass.
+ */
+class LLEventLogProxy: public LLEventFilter
+{
+ typedef LLEventFilter super;
+public:
+ /**
+ * Construct LLEventLogProxy, wrapping the specified LLEventPump.
+ * Unlike a typical LLEventFilter subclass, the name parameter is @emph
+ * not optional because typically you want LLEventLogProxy to completely
+ * replace the wrapped LLEventPump. So you give the subject LLEventPump
+ * some other name and give the LLEventLogProxy the name that would have
+ * been used for the subject LLEventPump.
+ */
+ LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak=false);
+
+ /// register a new listener
+ LLBoundListener listen_impl(const std::string& name, const LLEventListener& target,
+ const NameList& after, const NameList& before);
+
+ /// Post an event to all listeners
+ virtual bool post(const LLSD& event) /* override */;
+
+private:
+ /// This method intercepts each call to any target listener. We pass it
+ /// the listener name and the caller's intended target listener plus the
+ /// posted LLSD event.
+ bool listener(const std::string& name,
+ const LLEventListener& target,
+ const LLSD& event) const;
+
+ LLEventPump& mPump;
+ LLSD::Integer mCounter{0};
+};
+
+/**
+ * LLEventPumpHolder<T> is a helper for LLEventLogProxyFor<T>. It simply
+ * stores an instance of T, presumably a subclass of LLEventPump. We derive
+ * LLEventLogProxyFor<T> from LLEventPumpHolder<T>, ensuring that
+ * LLEventPumpHolder's contained mWrappedPump is fully constructed before
+ * passing it to LLEventLogProxyFor's LLEventLogProxy base class constructor.
+ * But since LLEventPumpHolder<T> presents none of the LLEventPump API,
+ * LLEventLogProxyFor<T> inherits its methods unambiguously from
+ * LLEventLogProxy.
+ */
+template <class T>
+class LLEventPumpHolder
+{
+protected:
+ LLEventPumpHolder(const std::string& name, bool tweak=false):
+ mWrappedPump(name, tweak)
+ {}
+ T mWrappedPump;
+};
+
+/**
+ * LLEventLogProxyFor<T> is a wrapper around any of the LLEventPump subclasses.
+ * Instantiating an LLEventLogProxy<T> instantiates an internal T. Otherwise
+ * it behaves like LLEventLogProxy.
+ */
+template <class T>
+class LLEventLogProxyFor: private LLEventPumpHolder<T>, public LLEventLogProxy
+{
+ // We derive privately from LLEventPumpHolder because it's an
+ // implementation detail of LLEventLogProxyFor. The only reason it's a
+ // base class at all is to guarantee that it's constructed first so we can
+ // pass it to our LLEventLogProxy base class constructor.
+ typedef LLEventPumpHolder<T> holder;
+ typedef LLEventLogProxy super;
+
+public:
+ LLEventLogProxyFor(const std::string& name, bool tweak=false):
+ // our wrapped LLEventPump subclass instance gets a name suffix
+ // because that's not the LLEventPump we want consumers to obtain when
+ // they ask LLEventPumps for this name
+ holder(name + "-", tweak),
+ // it's our LLEventLogProxy that gets the passed name
+ super(holder::mWrappedPump, name, tweak)
+ {}
+};
+
#endif /* ! defined(LL_LLEVENTFILTER_H) */
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index eedd8c92b5..64fb985951 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -45,6 +45,7 @@
#include <cctype>
// external library headers
#include <boost/range/iterator_range.hpp>
+#include <boost/make_shared.hpp>
#if LL_WINDOWS
#pragma warning (push)
#pragma warning (disable : 4701) // compiler thinks might use uninitialized var, but no
@@ -63,51 +64,23 @@
#endif
/*****************************************************************************
-* queue_names: specify LLEventPump names that should be instantiated as
-* LLEventQueue
-*****************************************************************************/
-/**
- * At present, we recognize particular requested LLEventPump names as needing
- * LLEventQueues. Later on we'll migrate this information to an external
- * configuration file.
- */
-const char* queue_names[] =
-{
- "placeholder - replace with first real name string"
-};
-
-/*****************************************************************************
-* If there's a "mainloop" pump, listen on that to flush all LLEventQueues
+* LLEventPumps
*****************************************************************************/
-struct RegisterFlush : public LLEventTrackable
-{
- RegisterFlush():
- pumps(LLEventPumps::instance())
+LLEventPumps::LLEventPumps():
+ mFactories
{
- pumps.obtain("mainloop").listen("flushLLEventQueues", boost::bind(&RegisterFlush::flush, this, _1));
- }
- bool flush(const LLSD&)
+ { "LLEventStream", [](const std::string& name, bool tweak)
+ { return new LLEventStream(name, tweak); } },
+ { "LLEventMailDrop", [](const std::string& name, bool tweak)
+ { return new LLEventMailDrop(name, tweak); } }
+ },
+ mTypes
{
- pumps.flush();
- return false;
+ // LLEventStream is the default for obtain(), so even if somebody DOES
+ // call obtain("placeholder"), this sample entry won't break anything.
+ { "placeholder", "LLEventStream" }
}
- ~RegisterFlush()
- {
- // LLEventTrackable handles stopListening for us.
- }
- LLEventPumps& pumps;
-};
-static RegisterFlush registerFlush;
-
-/*****************************************************************************
-* LLEventPumps
-*****************************************************************************/
-LLEventPumps::LLEventPumps():
- // Until we migrate this information to an external config file,
- // initialize mQueueNames from the static queue_names array.
- mQueueNames(boost::begin(queue_names), boost::end(queue_names))
-{
-}
+{}
LLEventPump& LLEventPumps::obtain(const std::string& name)
{
@@ -118,14 +91,31 @@ LLEventPump& LLEventPumps::obtain(const std::string& name)
// name.
return *found->second;
}
- // Here we must instantiate an LLEventPump subclass.
- LLEventPump* newInstance;
- // Should this name be an LLEventQueue?
- PumpNames::const_iterator nfound = mQueueNames.find(name);
- if (nfound != mQueueNames.end())
- newInstance = new LLEventQueue(name);
- else
- newInstance = new LLEventStream(name);
+
+ // Here we must instantiate an LLEventPump subclass. Is there a
+ // preregistered class name override for this specific instance name?
+ auto nfound = mTypes.find(name);
+ std::string type;
+ if (nfound != mTypes.end())
+ {
+ type = nfound->second;
+ }
+ // pass tweak=false: we already know there's no existing instance with
+ // this name
+ return make(name, false, type);
+}
+
+LLEventPump& LLEventPumps::make(const std::string& name, bool tweak,
+ const std::string& type)
+{
+ // find the relevant factory for this (or default) type
+ auto found = mFactories.find(type.empty()? "LLEventStream" : type);
+ if (found == mFactories.end())
+ {
+ // Passing an unrecognized type name is a no-no
+ LLTHROW(BadType(type));
+ }
+ auto newInstance = (found->second)(name, tweak);
// LLEventPump's constructor implicitly registers each new instance in
// mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll
// delete it later.
@@ -143,14 +133,23 @@ bool LLEventPumps::post(const std::string&name, const LLSD&message)
return (*found).second->post(message);
}
-
void LLEventPumps::flush()
{
// Flush every known LLEventPump instance. Leave it up to each instance to
// decide what to do with the flush() call.
- for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi)
+ for (PumpMap::value_type& pair : mPumpMap)
+ {
+ pair.second->flush();
+ }
+}
+
+void LLEventPumps::clear()
+{
+ // Clear every known LLEventPump instance. Leave it up to each instance to
+ // decide what to do with the clear() call.
+ for (PumpMap::value_type& pair : mPumpMap)
{
- pmi->second->flush();
+ pair.second->clear();
}
}
@@ -158,9 +157,9 @@ void LLEventPumps::reset()
{
// Reset every known LLEventPump instance. Leave it up to each instance to
// decide what to do with the reset() call.
- for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi)
+ for (PumpMap::value_type& pair : mPumpMap)
{
- pmi->second->reset();
+ pair.second->reset();
}
}
@@ -267,6 +266,9 @@ LLEventPumps::~LLEventPumps()
{
delete *mOurPumps.begin();
}
+ // Reset every remaining registered LLEventPump subclass instance: those
+ // we DIDN'T instantiate using either make() or obtain().
+ reset();
}
/*****************************************************************************
@@ -283,7 +285,7 @@ LLEventPump::LLEventPump(const std::string& name, bool tweak):
// Register every new instance with LLEventPumps
mRegistry(LLEventPumps::instance().getHandle()),
mName(mRegistry.get()->registerNew(*this, name, tweak)),
- mSignal(new LLStandardSignal()),
+ mSignal(boost::make_shared<LLStandardSignal>()),
mEnabled(true)
{}
@@ -311,6 +313,14 @@ std::string LLEventPump::inventName(const std::string& pfx)
return STRINGIZE(pfx << suffix++);
}
+void LLEventPump::clear()
+{
+ // Destroy the original LLStandardSignal instance, replacing it with a
+ // whole new one.
+ mSignal = boost::make_shared<LLStandardSignal>();
+ mConnections.clear();
+}
+
void LLEventPump::reset()
{
mSignal.reset();
@@ -553,7 +563,7 @@ bool LLEventMailDrop::post(const LLSD& event)
// be posted to any future listeners when they attach.
mEventHistory.push_back(event);
}
-
+
return posted;
}
@@ -583,46 +593,9 @@ LLBoundListener LLEventMailDrop::listen_impl(const std::string& name,
return LLEventStream::listen_impl(name, listener, after, before);
}
-
-/*****************************************************************************
-* LLEventQueue
-*****************************************************************************/
-bool LLEventQueue::post(const LLSD& event)
-{
- if (mEnabled)
- {
- // Defer sending this event by queueing it until flush()
- mEventQueue.push_back(event);
- }
- // Unconditionally return false. We won't know until flush() whether a
- // listener claims to have handled the event -- meanwhile, don't block
- // other listeners.
- return false;
-}
-
-void LLEventQueue::flush()
+void LLEventMailDrop::discard()
{
- if(!mSignal) return;
-
- // Consider the case when a given listener on this LLEventQueue posts yet
- // another event on the same queue. If we loop over mEventQueue directly,
- // we'll end up processing all those events during the same flush() call
- // -- rather like an EventStream. Instead, copy mEventQueue and clear it,
- // so that any new events posted to this LLEventQueue during flush() will
- // be processed in the *next* flush() call.
- EventQueue queue(mEventQueue);
- mEventQueue.clear();
- // NOTE NOTE NOTE: Any new access to member data beyond this point should
- // cause us to move our LLStandardSignal object to a pimpl class along
- // with said member data. Then the local shared_ptr will preserve both.
-
- // DEV-43463: capture a local copy of mSignal. See LLEventStream::post()
- // for detailed comments.
- boost::shared_ptr<LLStandardSignal> signal(mSignal);
- for ( ; ! queue.empty(); queue.pop_front())
- {
- (*signal)(queue.front());
- }
+ mEventHistory.clear();
}
/*****************************************************************************
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h
index 62d97007ac..e380c108f4 100644
--- a/indra/llcommon/llevents.h
+++ b/indra/llcommon/llevents.h
@@ -37,6 +37,7 @@
#include <set>
#include <vector>
#include <deque>
+#include <functional>
#if LL_WINDOWS
#pragma warning (push)
#pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch
@@ -55,7 +56,6 @@
#include <boost/visit_each.hpp>
#include <boost/ref.hpp> // reference_wrapper
#include <boost/type_traits/is_pointer.hpp>
-#include <boost/function.hpp>
#include <boost/static_assert.hpp>
#include "llsd.h"
#include "llsingleton.h"
@@ -211,8 +211,7 @@ public:
/// exception if you try to call when empty
struct Empty: public LLException
{
- Empty(const std::string& what):
- LLException(std::string("LLListenerOrPumpName::Empty: ") + what) {}
+ Empty(const std::string& what): LLException("LLListenerOrPumpName::Empty: " + what) {}
};
private:
@@ -247,6 +246,30 @@ public:
*/
LLEventPump& obtain(const std::string& name);
+ /// exception potentially thrown by make()
+ struct BadType: public LLException
+ {
+ BadType(const std::string& what): LLException("BadType: " + what) {}
+ };
+
+ /**
+ * Create an LLEventPump with suggested name (optionally of specified
+ * LLEventPump subclass type). As with obtain(), LLEventPumps owns the new
+ * instance.
+ *
+ * As with LLEventPump's constructor, make() could throw
+ * LLEventPump::DupPumpName unless you pass tweak=true.
+ *
+ * As with a hand-constructed LLEventPump subclass, if you pass
+ * tweak=true, the tweaked name can be obtained by LLEventPump::getName().
+ *
+ * Pass empty type to get the default LLEventStream.
+ *
+ * If you pass an unrecognized type string, make() throws BadType.
+ */
+ LLEventPump& make(const std::string& name, bool tweak=false,
+ const std::string& type=std::string());
+
/**
* Find the named LLEventPump instance. If it exists post the message to it.
* If the pump does not exist, do nothing.
@@ -264,6 +287,11 @@ public:
void flush();
/**
+ * Disconnect listeners from all known LLEventPump instances
+ */
+ void clear();
+
+ /**
* Reset all known LLEventPump instances
* workaround for DEV-35406 crash on shutdown
*/
@@ -298,44 +326,22 @@ testable:
// destroyed.
typedef std::set<LLEventPump*> PumpSet;
PumpSet mOurPumps;
- // LLEventPump names that should be instantiated as LLEventQueue rather
- // than as LLEventStream
- typedef std::set<std::string> PumpNames;
- PumpNames mQueueNames;
+ // for make(), map string type name to LLEventPump subclass factory function
+ typedef std::map<std::string, std::function<LLEventPump*(const std::string&, bool)>> PumpFactories;
+ // Data used by make().
+ // One might think mFactories and mTypes could reasonably be static. So
+ // they could -- if not for the fact that make() or obtain() might be
+ // called before this module's static variables have been initialized.
+ // This is why we use singletons in the first place.
+ PumpFactories mFactories;
+
+ // for obtain(), map desired string instance name to string type when
+ // obtain() must create the instance
+ typedef std::map<std::string, std::string> InstanceTypes;
+ InstanceTypes mTypes;
};
/*****************************************************************************
-* details
-*****************************************************************************/
-namespace LLEventDetail
-{
- /// Any callable capable of connecting an LLEventListener to an
- /// LLStandardSignal to produce an LLBoundListener can be mapped to this
- /// signature.
- typedef boost::function<LLBoundListener(const LLEventListener&)> ConnectFunc;
-
- /// overload of visit_and_connect() when we have a string identifier available
- template <typename LISTENER>
- LLBoundListener visit_and_connect(const std::string& name,
- const LISTENER& listener,
- const ConnectFunc& connect_func);
- /**
- * Utility template function to use Visitor appropriately
- *
- * @param listener Callable to connect, typically a boost::bind()
- * expression. This will be visited by Visitor using boost::visit_each().
- * @param connect_func Callable that will connect() @a listener to an
- * LLStandardSignal, returning LLBoundListener.
- */
- template <typename LISTENER>
- LLBoundListener visit_and_connect(const LISTENER& listener,
- const ConnectFunc& connect_func)
- {
- return visit_and_connect("", listener, connect_func);
- }
-} // namespace LLEventDetail
-
-/*****************************************************************************
* LLEventTrackable
*****************************************************************************/
/**
@@ -369,11 +375,6 @@ namespace LLEventDetail
* instance, it attempts to dereference the <tt>Foo*</tt> pointer that was
* <tt>delete</tt>d but not zeroed.)
* - Undefined behavior results.
- * If you suspect you may encounter any such scenario, you're better off
- * managing the lifespan of your object with <tt>boost::shared_ptr</tt>.
- * Passing <tt>LLEventPump::listen()</tt> a <tt>boost::bind()</tt> expression
- * involving a <tt>boost::weak_ptr<Foo></tt> is recognized specially, engaging
- * thread-safe Boost.Signals2 machinery.
*/
typedef boost::signals2::trackable LLEventTrackable;
@@ -382,7 +383,7 @@ typedef boost::signals2::trackable LLEventTrackable;
*****************************************************************************/
/**
* LLEventPump is the base class interface through which we access the
- * concrete subclasses LLEventStream and LLEventQueue.
+ * concrete subclasses such as LLEventStream.
*
* @NOTE
* LLEventPump derives from LLEventTrackable so that when you "chain"
@@ -403,8 +404,7 @@ public:
*/
struct DupPumpName: public LLException
{
- DupPumpName(const std::string& what):
- LLException(std::string("DupPumpName: ") + what) {}
+ DupPumpName(const std::string& what): LLException("DupPumpName: " + what) {}
};
/**
@@ -440,9 +440,7 @@ public:
*/
struct DupListenerName: public ListenError
{
- DupListenerName(const std::string& what):
- ListenError(std::string("DupListenerName: ") + what)
- {}
+ DupListenerName(const std::string& what): ListenError("DupListenerName: " + what) {}
};
/**
* exception thrown by listen(). The order dependencies specified for your
@@ -454,7 +452,7 @@ public:
*/
struct Cycle: public ListenError
{
- Cycle(const std::string& what): ListenError(std::string("Cycle: ") + what) {}
+ Cycle(const std::string& what): ListenError("Cycle: " + what) {}
};
/**
* exception thrown by listen(). This one means that your new listener
@@ -475,7 +473,7 @@ public:
*/
struct OrderChange: public ListenError
{
- OrderChange(const std::string& what): ListenError(std::string("OrderChange: ") + what) {}
+ OrderChange(const std::string& what): ListenError("OrderChange: " + what) {}
};
/// used by listen()
@@ -512,44 +510,13 @@ public:
* the result be assigned to a LLTempBoundListener or the listener is
* manually disconnected when no longer needed since there will be no
* way to later find and disconnect this listener manually.
- *
- * If (as is typical) you pass a <tt>boost::bind()</tt> expression as @a
- * listener, listen() will inspect the components of that expression. If a
- * bound object matches any of several cases, the connection will
- * automatically be disconnected when that object is destroyed.
- *
- * * You bind a <tt>boost::weak_ptr</tt>.
- * * Binding a <tt>boost::shared_ptr</tt> that way would ensure that the
- * referenced object would @em never be destroyed, since the @c
- * shared_ptr stored in the LLEventPump would remain an outstanding
- * reference. Use the weaken() function to convert your @c shared_ptr to
- * @c weak_ptr. Because this is easy to forget, binding a @c shared_ptr
- * will produce a compile error (@c BOOST_STATIC_ASSERT failure).
- * * You bind a simple pointer or reference to an object derived from
- * <tt>boost::enable_shared_from_this</tt>. (UNDER CONSTRUCTION)
- * * You bind a simple pointer or reference to an object derived from
- * LLEventTrackable. Unlike the cases described above, though, this is
- * vulnerable to a couple of cross-thread race conditions, as described
- * in the LLEventTrackable documentation.
*/
- template <typename LISTENER>
- LLBoundListener listen(const std::string& name, const LISTENER& listener,
+ LLBoundListener listen(const std::string& name,
+ const LLEventListener& listener,
const NameList& after=NameList(),
const NameList& before=NameList())
{
- // Examine listener, using our listen_impl() method to make the
- // actual connection.
- // This is why listen() is a template. Conversion from boost::bind()
- // to LLEventListener performs type erasure, so it's important to look
- // at the boost::bind object itself before that happens.
- return LLEventDetail::visit_and_connect(name,
- listener,
- boost::bind(&LLEventPump::listen_invoke,
- this,
- name,
- _1,
- after,
- before));
+ return listen_impl(name, listener, after, before);
}
/// Get the LLBoundListener associated with the passed name (dummy
@@ -587,19 +554,12 @@ public:
private:
friend class LLEventPumps;
-
+ virtual void clear();
virtual void reset();
private:
- LLBoundListener listen_invoke(const std::string& name, const LLEventListener& listener,
- const NameList& after,
- const NameList& before)
- {
- return this->listen_impl(name, listener, after, before);
- }
-
// must precede mName; see LLEventPump::LLEventPump()
LLHandle<LLEventPumps> mRegistry;
@@ -663,11 +623,10 @@ public:
* event *must* eventually reach a listener that will consume it, else the
* queue will grow to arbitrary length.
*
- * @NOTE: When using an LLEventMailDrop (or LLEventQueue) with a LLEventTimeout or
+ * @NOTE: When using an LLEventMailDrop with an LLEventTimeout or
* LLEventFilter attaching the filter downstream, using Timeout's constructor will
* cause the MailDrop to discharge any of its stored events. The timeout should
* instead be connected upstream using its listen() method.
- * See llcoro::suspendUntilEventOnWithTimeout() for an example.
*/
class LL_COMMON_API LLEventMailDrop : public LLEventStream
{
@@ -679,7 +638,8 @@ public:
virtual bool post(const LLSD& event) override;
/// Remove any history stored in the mail drop.
- virtual void flush() override { mEventHistory.clear(); LLEventStream::flush(); };
+ void discard();
+
protected:
virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&,
const NameList& after,
@@ -691,30 +651,6 @@ private:
};
/*****************************************************************************
-* LLEventQueue
-*****************************************************************************/
-/**
- * LLEventQueue is a LLEventPump whose post() method defers calling registered
- * listeners until flush() is called.
- */
-class LL_COMMON_API LLEventQueue: public LLEventPump
-{
-public:
- LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {}
- virtual ~LLEventQueue() {}
-
- /// Post an event to all listeners
- virtual bool post(const LLSD& event);
-
- /// flush queued events
- virtual void flush();
-
-private:
- typedef std::deque<LLSD> EventQueue;
- EventQueue mEventQueue;
-};
-
-/*****************************************************************************
* LLReqID
*****************************************************************************/
/**
@@ -809,329 +745,6 @@ private:
LL_COMMON_API bool sendReply(const LLSD& reply, const LLSD& request,
const std::string& replyKey="reply");
-/**
- * Base class for LLListenerWrapper. See visit_and_connect() and llwrap(). We
- * provide virtual @c accept_xxx() methods, customization points allowing a
- * subclass access to certain data visible at LLEventPump::listen() time.
- * Example subclass usage:
- *
- * @code
- * myEventPump.listen("somename",
- * llwrap<MyListenerWrapper>(boost::bind(&MyClass::method, instance, _1)));
- * @endcode
- *
- * Because of the anticipated usage (note the anonymous temporary
- * MyListenerWrapper instance in the example above), the @c accept_xxx()
- * methods must be @c const.
- */
-class LL_COMMON_API LLListenerWrapperBase
-{
-public:
- /// New instance. The accept_xxx() machinery makes it important to use
- /// shared_ptrs for our data. Many copies of this object are made before
- /// the instance that actually ends up in the signal, yet accept_xxx()
- /// will later be called on the @em original instance. All copies of the
- /// same original instance must share the same data.
- LLListenerWrapperBase():
- mName(new std::string),
- mConnection(new LLBoundListener)
- {
- }
-
- /// Copy constructor. Copy shared_ptrs to original instance data.
- LLListenerWrapperBase(const LLListenerWrapperBase& that):
- mName(that.mName),
- mConnection(that.mConnection)
- {
- }
- virtual ~LLListenerWrapperBase() {}
-
- /// Ask LLEventPump::listen() for the listener name
- virtual void accept_name(const std::string& name) const
- {
- *mName = name;
- }
-
- /// Ask LLEventPump::listen() for the new connection
- virtual void accept_connection(const LLBoundListener& connection) const
- {
- *mConnection = connection;
- }
-
-protected:
- /// Listener name.
- boost::shared_ptr<std::string> mName;
- /// Connection.
- boost::shared_ptr<LLBoundListener> mConnection;
-};
-
-/*****************************************************************************
-* Underpinnings
-*****************************************************************************/
-/**
- * We originally provided a suite of overloaded
- * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call
- * LLEventPump::listen(...) and then pass the returned LLBoundListener to
- * LLEventTrackable::track(). This was workable but error-prone: the coder
- * must remember to call listenTo() rather than the more straightforward
- * listen() method.
- *
- * Now we publish only the single canonical listen() method, so there's a
- * uniform mechanism. Having a single way to do this is good, in that there's
- * no question in the coder's mind which of several alternatives to choose.
- *
- * To support automatic connection management, we use boost::visit_each
- * (http://www.boost.org/doc/libs/1_37_0/doc/html/boost/visit_each.html) to
- * inspect each argument of a boost::bind expression. (Although the visit_each
- * mechanism was first introduced with the original Boost.Signals library, it
- * was only later documented.)
- *
- * Cases:
- * * At least one of the function's arguments is a boost::weak_ptr<T>. Pass
- * the corresponding shared_ptr to slot_type::track(). Ideally that would be
- * the object whose method we want to call, but in fact we do the same for
- * any weak_ptr we might find among the bound arguments. If we're passing
- * our bound method a weak_ptr to some object, wouldn't the destruction of
- * that object invalidate the call? So we disconnect automatically when any
- * such object is destroyed. This is the mechanism preferred by boost::
- * signals2.
- * * One of the functions's arguments is a boost::shared_ptr<T>. This produces
- * a compile error: the bound copy of the shared_ptr stored in the
- * boost_bind object stored in the signal object would make the referenced
- * T object immortal. We provide a weaken() function. Pass
- * weaken(your_shared_ptr) instead. (We can inspect, but not modify, the
- * boost::bind object. Otherwise we'd replace the shared_ptr with weak_ptr
- * implicitly and just proceed.)
- * * One of the function's arguments is a plain pointer/reference to an object
- * derived from boost::enable_shared_from_this. We assume that this object
- * is managed using boost::shared_ptr, so we implicitly extract a shared_ptr
- * and track that. (UNDER CONSTRUCTION)
- * * One of the function's arguments is derived from LLEventTrackable. Pass
- * the LLBoundListener to its LLEventTrackable::track(). This is vulnerable
- * to a couple different race conditions, as described in LLEventTrackable
- * documentation. (NOTE: Now that LLEventTrackable is a typedef for
- * boost::signals2::trackable, the Signals2 library handles this itself, so
- * our visitor needs no special logic for this case.)
- * * Any other argument type is irrelevant to automatic connection management.
- */
-
-namespace LLEventDetail
-{
- template <typename F>
- const F& unwrap(const F& f) { return f; }
-
- template <typename F>
- const F& unwrap(const boost::reference_wrapper<F>& f) { return f.get(); }
-
- // Most of the following is lifted from the Boost.Signals use of
- // visit_each.
- template<bool Cond> struct truth {};
-
- /**
- * boost::visit_each() Visitor, used on a template argument <tt>const F&
- * f</tt> as follows (see visit_and_connect()):
- * @code
- * LLEventListener listener(f);
- * Visitor visitor(listener); // bind listener so it can track() shared_ptrs
- * using boost::visit_each; // allow unqualified visit_each() call for ADL
- * visit_each(visitor, unwrap(f));
- * @endcode
- */
- class Visitor
- {
- public:
- /**
- * Visitor binds a reference to LLEventListener so we can track() any
- * shared_ptrs we find in the argument list.
- */
- Visitor(LLEventListener& listener):
- mListener(listener)
- {
- }
-
- /**
- * boost::visit_each() calls this method for each component of a
- * boost::bind() expression.
- */
- template <typename T>
- void operator()(const T& t) const
- {
- decode(t, 0);
- }
-
- private:
- // decode() decides between a reference wrapper and anything else
- // boost::ref() variant
- template<typename T>
- void decode(const boost::reference_wrapper<T>& t, int) const
- {
-// add_if_trackable(t.get_pointer());
- }
-
- // decode() anything else
- template<typename T>
- void decode(const T& t, long) const
- {
- typedef truth<(boost::is_pointer<T>::value)> is_a_pointer;
- maybe_get_pointer(t, is_a_pointer());
- }
-
- // maybe_get_pointer() decides between a pointer and a non-pointer
- // plain pointer variant
- template<typename T>
- void maybe_get_pointer(const T& t, truth<true>) const
- {
-// add_if_trackable(t);
- }
-
- // shared_ptr variant
- template<typename T>
- void maybe_get_pointer(const boost::shared_ptr<T>& t, truth<false>) const
- {
- // If we have a shared_ptr to this object, it doesn't matter
- // whether the object is derived from LLEventTrackable, so no
- // further analysis of T is needed.
-// mListener.track(t);
-
- // Make this case illegal. Passing a bound shared_ptr to
- // slot_type::track() is useless, since the bound shared_ptr will
- // keep the object alive anyway! Force the coder to cast to weak_ptr.
-
- // Trivial as it is, make the BOOST_STATIC_ASSERT() condition
- // dependent on template param so the macro is only evaluated if
- // this method is in fact instantiated, as described here:
- // http://www.boost.org/doc/libs/1_34_1/doc/html/boost_staticassert.html
-
- // ATTENTION: Don't bind a shared_ptr<anything> using
- // LLEventPump::listen(boost::bind()). Doing so captures a copy of
- // the shared_ptr, making the referenced object effectively
- // immortal. Use the weaken() function, e.g.:
- // somepump.listen(boost::bind(...weaken(my_shared_ptr)...));
- // This lets us automatically disconnect when the referenced
- // object is destroyed.
- BOOST_STATIC_ASSERT(sizeof(T) == 0);
- }
-
- // weak_ptr variant
- template<typename T>
- void maybe_get_pointer(const boost::weak_ptr<T>& t, truth<false>) const
- {
- // If we have a weak_ptr to this object, it doesn't matter
- // whether the object is derived from LLEventTrackable, so no
- // further analysis of T is needed.
- mListener.track(t);
-// std::cout << "Found weak_ptr<" << typeid(T).name() << ">!\n";
- }
-
-#if 0
- // reference to anything derived from boost::enable_shared_from_this
- template <typename T>
- inline void maybe_get_pointer(const boost::enable_shared_from_this<T>& ct,
- truth<false>) const
- {
- // Use the slot_type::track(shared_ptr) mechanism. Cast away
- // const-ness because (in our code base anyway) it's unusual
- // to find shared_ptr<const T>.
- boost::enable_shared_from_this<T>&
- t(const_cast<boost::enable_shared_from_this<T>&>(ct));
- std::cout << "Capturing shared_from_this()" << std::endl;
- boost::shared_ptr<T> sp(t.shared_from_this());
-/*==========================================================================*|
- std::cout << "Capturing weak_ptr" << std::endl;
- boost::weak_ptr<T> wp(sp);
-|*==========================================================================*/
- std::cout << "Tracking shared__ptr" << std::endl;
- mListener.track(sp);
- }
-#endif
-
- // non-pointer variant
- template<typename T>
- void maybe_get_pointer(const T& t, truth<false>) const
- {
- // Take the address of this object, because the object itself may be
- // trackable
-// add_if_trackable(boost::addressof(t));
- }
-
-/*==========================================================================*|
- // add_if_trackable() adds LLEventTrackable objects to mTrackables
- inline void add_if_trackable(const LLEventTrackable* t) const
- {
- if (t)
- {
- }
- }
-
- // pointer to anything not an LLEventTrackable subclass
- inline void add_if_trackable(const void*) const
- {
- }
-
- // pointer to free function
- // The following construct uses the preprocessor to generate
- // add_if_trackable() overloads accepting pointer-to-function taking
- // 0, 1, ..., LLEVENTS_LISTENER_ARITY parameters of arbitrary type.
-#define BOOST_PP_LOCAL_MACRO(n) \
- template <typename R \
- BOOST_PP_COMMA_IF(n) \
- BOOST_PP_ENUM_PARAMS(n, typename T)> \
- inline void \
- add_if_trackable(R (*)(BOOST_PP_ENUM_PARAMS(n, T))) const \
- { \
- }
-#define BOOST_PP_LOCAL_LIMITS (0, LLEVENTS_LISTENER_ARITY)
-#include BOOST_PP_LOCAL_ITERATE()
-#undef BOOST_PP_LOCAL_MACRO
-#undef BOOST_PP_LOCAL_LIMITS
-|*==========================================================================*/
-
- /// Bind a reference to the LLEventListener to call its track() method.
- LLEventListener& mListener;
- };
-
- /**
- * Utility template function to use Visitor appropriately
- *
- * @param raw_listener Callable to connect, typically a boost::bind()
- * expression. This will be visited by Visitor using boost::visit_each().
- * @param connect_funct Callable that will connect() @a raw_listener to an
- * LLStandardSignal, returning LLBoundListener.
- */
- template <typename LISTENER>
- LLBoundListener visit_and_connect(const std::string& name,
- const LISTENER& raw_listener,
- const ConnectFunc& connect_func)
- {
- // Capture the listener
- LLEventListener listener(raw_listener);
- // Define our Visitor, binding the listener so we can call
- // listener.track() if we discover any shared_ptr<Foo>.
- LLEventDetail::Visitor visitor(listener);
- // Allow unqualified visit_each() call for ADL
- using boost::visit_each;
- // Visit each component of a boost::bind() expression. Pass
- // 'raw_listener', our template argument, rather than 'listener' from
- // which type details have been erased. unwrap() comes from
- // Boost.Signals, in case we were passed a boost::ref().
- visit_each(visitor, LLEventDetail::unwrap(raw_listener));
- // Make the connection using passed function.
- LLBoundListener connection(connect_func(listener));
- // If the LISTENER is an LLListenerWrapperBase subclass, pass it the
- // desired information. It's important that we pass the raw_listener
- // so the compiler can make decisions based on its original type.
- const LLListenerWrapperBase* lwb =
- ll_template_cast<const LLListenerWrapperBase*>(&raw_listener);
- if (lwb)
- {
- lwb->accept_name(name);
- lwb->accept_connection(connection);
- }
- // In any case, show new connection to caller.
- return connection;
- }
-} // namespace LLEventDetail
-
// Somewhat to my surprise, passing boost::bind(...boost::weak_ptr<T>...) to
// listen() fails in Boost code trying to instantiate LLEventListener (i.e.
// LLStandardSignal::slot_type) because the boost::get_pointer() utility function isn't
@@ -1142,12 +755,4 @@ namespace boost
T* get_pointer(const weak_ptr<T>& ptr) { return shared_ptr<T>(ptr).get(); }
}
-/// Since we forbid use of listen(boost::bind(...shared_ptr<T>...)), provide an
-/// easy way to cast to the corresponding weak_ptr.
-template <typename T>
-boost::weak_ptr<T> weaken(const boost::shared_ptr<T>& ptr)
-{
- return boost::weak_ptr<T>(ptr);
-}
-
#endif /* ! defined(LL_LLEVENTS_H) */
diff --git a/indra/llcommon/lleventtimer.cpp b/indra/llcommon/lleventtimer.cpp
index 0d96e03da4..f575a7b6bf 100644
--- a/indra/llcommon/lleventtimer.cpp
+++ b/indra/llcommon/lleventtimer.cpp
@@ -57,29 +57,17 @@ LLEventTimer::~LLEventTimer()
//static
void LLEventTimer::updateClass()
{
- std::list<LLEventTimer*> completed_timers;
- for (instance_iter iter = beginInstances(); iter != endInstances(); )
+ for (auto& timer : instance_snapshot())
{
- LLEventTimer& timer = *iter++;
F32 et = timer.mEventTimer.getElapsedTimeF32();
if (timer.mEventTimer.getStarted() && et > timer.mPeriod) {
timer.mEventTimer.reset();
if ( timer.tick() )
{
- completed_timers.push_back( &timer );
+ delete &timer;
}
}
}
-
- if ( completed_timers.size() > 0 )
- {
- for (std::list<LLEventTimer*>::iterator completed_iter = completed_timers.begin();
- completed_iter != completed_timers.end();
- completed_iter++ )
- {
- delete *completed_iter;
- }
- }
}
diff --git a/indra/llcommon/lleventtimer.h b/indra/llcommon/lleventtimer.h
index dc918121e1..dbbfe0c6e6 100644
--- a/indra/llcommon/lleventtimer.h
+++ b/indra/llcommon/lleventtimer.h
@@ -40,16 +40,83 @@ public:
LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds
LLEventTimer(const LLDate& time);
virtual ~LLEventTimer();
-
+
//function to be called at the supplied frequency
// Normally return FALSE; TRUE will delete the timer after the function returns.
virtual BOOL tick() = 0;
static void updateClass();
+ /// Schedule recurring calls to generic callable every period seconds.
+ /// Returns a pointer; if you delete it, cancels the recurring calls.
+ template <typename CALLABLE>
+ static LLEventTimer* run_every(F32 period, const CALLABLE& callable);
+
+ /// Schedule a future call to generic callable. Returns a pointer.
+ /// CAUTION: The object referenced by that pointer WILL BE DELETED once
+ /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT
+ /// pointer->getInstance(pointer)!) can be used to test whether the
+ /// pointer is still valid. If it is, deleting it will cancel the
+ /// callback.
+ template <typename CALLABLE>
+ static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable);
+
+ /// Like run_at(), but after a time delta rather than at a timestamp.
+ /// Same CAUTION.
+ template <typename CALLABLE>
+ static LLEventTimer* run_after(F32 interval, const CALLABLE& callable);
+
protected:
LLTimer mEventTimer;
F32 mPeriod;
+
+private:
+ template <typename CALLABLE>
+ class Generic;
+};
+
+template <typename CALLABLE>
+class LLEventTimer::Generic: public LLEventTimer
+{
+public:
+ // making TIME generic allows engaging either LLEventTimer constructor
+ template <typename TIME>
+ Generic(const TIME& time, bool once, const CALLABLE& callable):
+ LLEventTimer(time),
+ mOnce(once),
+ mCallable(callable)
+ {}
+ BOOL tick() override
+ {
+ mCallable();
+ // true tells updateClass() to delete this instance
+ return mOnce;
+ }
+
+private:
+ bool mOnce;
+ CALLABLE mCallable;
};
+template <typename CALLABLE>
+LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable)
+{
+ // return false to schedule recurring calls
+ return new Generic<CALLABLE>(period, false, callable);
+}
+
+template <typename CALLABLE>
+LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable)
+{
+ // return true for one-shot callback
+ return new Generic<CALLABLE>(time, true, callable);
+}
+
+template <typename CALLABLE>
+LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable)
+{
+ // one-shot callback after specified interval
+ return new Generic<CALLABLE>(interval, true, callable);
+}
+
#endif //LL_EVENTTIMER_H
diff --git a/indra/llcommon/llexception.cpp b/indra/llcommon/llexception.cpp
index b32ec2c9c9..5ce8958687 100644
--- a/indra/llcommon/llexception.cpp
+++ b/indra/llcommon/llexception.cpp
@@ -18,10 +18,28 @@
#include <typeinfo>
// external library headers
#include <boost/exception/diagnostic_information.hpp>
+#include <boost/exception/error_info.hpp>
+// On Mac, got:
+// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define
+// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if
+// _Unwind_Backtrace is available without `_GNU_SOURCE`."
+#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED
+#if LL_WINDOWS
+// On Windows, header-only implementation causes macro collisions -- use
+// prebuilt library
+#define BOOST_STACKTRACE_LINK
+#endif // LL_WINDOWS
+#include <boost/stacktrace.hpp>
// other Linden headers
#include "llerror.h"
#include "llerrorcontrol.h"
+// used to attach and extract stacktrace information to/from boost::exception,
+// see https://www.boost.org/doc/libs/release/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.exceptions_with_stacktrace
+// apparently the struct passed as the first template param needs no definition?
+typedef boost::error_info<struct errinfo_stacktrace_, boost::stacktrace::stacktrace>
+ errinfo_stacktrace;
+
namespace {
// used by crash_on_unhandled_exception_() and log_unhandled_exception_()
void log_unhandled_exception_(LLError::ELevel level,
@@ -53,3 +71,17 @@ void log_unhandled_exception_(const char* file, int line, const char* pretty_fun
// routinely, but we DO expect to return from this function.
log_unhandled_exception_(LLError::LEVEL_WARN, file, line, pretty_function, context);
}
+
+void annotate_exception_(boost::exception& exc)
+{
+ // https://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_transporting_data.html
+ // "Adding of Arbitrary Data to Active Exception Objects"
+ // Given a boost::exception&, we can add boost::error_info items to it
+ // without knowing its leaf type.
+ // The stacktrace constructor that lets us skip a level -- and why would
+ // we always include annotate_exception_()? -- also requires a max depth.
+ // For the nullary constructor, the stacktrace class declaration itself
+ // passes static_cast<std::size_t>(-1), but that's kind of dubious.
+ // Anyway, which of us is really going to examine more than 100 frames?
+ exc << errinfo_stacktrace(boost::stacktrace::stacktrace(1, 100));
+}
diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h
index dfcb7c192f..422dd8810a 100644
--- a/indra/llcommon/llexception.h
+++ b/indra/llcommon/llexception.h
@@ -67,9 +67,29 @@ struct LLContinueError: public LLException
* enriches the exception's diagnostic_information() with the source file,
* line and containing function of the LLTHROW() macro.
*/
-// Currently we implement that using BOOST_THROW_EXCEPTION(). Wrap it in
-// LLTHROW() in case we ever want to revisit that implementation decision.
-#define LLTHROW(x) BOOST_THROW_EXCEPTION(x)
+#define LLTHROW(x) \
+do { \
+ /* Capture the exception object 'x' by value. (Exceptions must */ \
+ /* be copyable.) It might seem simpler to use */ \
+ /* BOOST_THROW_EXCEPTION(annotate_exception_(x)) instead of */ \
+ /* three separate statements, but: */ \
+ /* - We want to throw 'x' with its original type, not just a */ \
+ /* reference to boost::exception. */ \
+ /* - To return x's original type, annotate_exception_() would */ \
+ /* have to be a template function. */ \
+ /* - We want annotate_exception_() to be opaque. */ \
+ /* We also might consider embedding BOOST_THROW_EXCEPTION() in */ \
+ /* our helper function, but we want the filename and line info */ \
+ /* embedded by BOOST_THROW_EXCEPTION() to be the throw point */ \
+ /* rather than always indicating the same line in */ \
+ /* llexception.cpp. */ \
+ auto exc{x}; \
+ annotate_exception_(exc); \
+ BOOST_THROW_EXCEPTION(exc); \
+ /* Use the classic 'do { ... } while (0)' macro trick to wrap */ \
+ /* our multiple statements. */ \
+} while (0)
+void annotate_exception_(boost::exception& exc);
/// Call this macro from a catch (...) clause
#define CRASH_ON_UNHANDLED_EXCEPTION(CONTEXT) \
diff --git a/indra/llcommon/llfasttimer.cpp b/indra/llcommon/llfasttimer.cpp
index 3d28cd15b0..08ea668964 100644
--- a/indra/llcommon/llfasttimer.cpp
+++ b/indra/llcommon/llfasttimer.cpp
@@ -193,27 +193,26 @@ TimeBlockTreeNode& BlockTimerStatHandle::getTreeNode() const
void BlockTimer::bootstrapTimerTree()
{
- for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(), end_it = BlockTimerStatHandle::instance_tracker_t::endInstances();
- it != end_it;
- ++it)
+ for (auto& base : BlockTimerStatHandle::instance_snapshot())
{
- BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(*it);
+ // because of indirect derivation from LLInstanceTracker, have to downcast
+ BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(base);
if (&timer == &BlockTimer::getRootTimeBlock()) continue;
// bootstrap tree construction by attaching to last timer to be on stack
// when this timer was called
if (timer.getParent() == &BlockTimer::getRootTimeBlock())
-{
+ {
TimeBlockAccumulator& accumulator = timer.getCurrentAccumulator();
if (accumulator.mLastCaller)
- {
+ {
timer.setParent(accumulator.mLastCaller);
accumulator.mParent = accumulator.mLastCaller;
- }
+ }
// no need to push up tree on first use, flag can be set spuriously
accumulator.mMoveUpTree = false;
- }
+ }
}
}
@@ -306,12 +305,10 @@ void BlockTimer::processTimes()
updateTimes();
// reset for next frame
- for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(),
- end_it = BlockTimerStatHandle::instance_tracker_t::endInstances();
- it != end_it;
- ++it)
+ for (auto& base : BlockTimerStatHandle::instance_snapshot())
{
- BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(*it);
+ // because of indirect derivation from LLInstanceTracker, have to downcast
+ BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(base);
TimeBlockAccumulator& accumulator = timer.getCurrentAccumulator();
accumulator.mLastCaller = NULL;
@@ -362,12 +359,10 @@ void BlockTimer::logStats()
LLSD sd;
{
- for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(),
- end_it = BlockTimerStatHandle::instance_tracker_t::endInstances();
- it != end_it;
- ++it)
+ for (auto& base : BlockTimerStatHandle::instance_snapshot())
{
- BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(*it);
+ // because of indirect derivation from LLInstanceTracker, have to downcast
+ BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(base);
LLTrace::PeriodicRecording& frame_recording = LLTrace::get_frame_recording();
sd[timer.getName()]["Time"] = (LLSD::Real) (frame_recording.getLastRecording().getSum(timer).value());
sd[timer.getName()]["Calls"] = (LLSD::Integer) (frame_recording.getLastRecording().getSum(timer.callCount()));
diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h
index d463fc9d65..5628a05b00 100644
--- a/indra/llcommon/llfasttimer.h
+++ b/indra/llcommon/llfasttimer.h
@@ -31,6 +31,10 @@
#include "lltrace.h"
#include "lltreeiterators.h"
+#if LL_WINDOWS
+#include <intrin.h>
+#endif
+
#define LL_FAST_TIMER_ON 1
#define LL_FASTTIMER_USE_RDTSC 1
@@ -85,6 +89,8 @@ public:
// return __rdtsc();
//}
+
+
// shift off lower 8 bits for lower resolution but longer term timing
// on 1Ghz machine, a 32-bit word will hold ~1000 seconds of timing
#if LL_FASTTIMER_USE_RDTSC
diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h
index 398938b729..9de095b45d 100644
--- a/indra/llcommon/llfile.h
+++ b/indra/llcommon/llfile.h
@@ -86,6 +86,69 @@ public:
static const char * tmpdir();
};
+/// RAII class
+class LLUniqueFile
+{
+public:
+ // empty
+ LLUniqueFile(): mFileHandle(nullptr) {}
+ // wrap (e.g.) result of LLFile::fopen()
+ LLUniqueFile(LLFILE* f): mFileHandle(f) {}
+ // no copy
+ LLUniqueFile(const LLUniqueFile&) = delete;
+ // move construction
+ LLUniqueFile(LLUniqueFile&& other)
+ {
+ mFileHandle = other.mFileHandle;
+ other.mFileHandle = nullptr;
+ }
+ // The point of LLUniqueFile is to close on destruction.
+ ~LLUniqueFile()
+ {
+ close();
+ }
+
+ // simple assignment
+ LLUniqueFile& operator=(LLFILE* f)
+ {
+ close();
+ mFileHandle = f;
+ return *this;
+ }
+ // copy assignment deleted
+ LLUniqueFile& operator=(const LLUniqueFile&) = delete;
+ // move assignment
+ LLUniqueFile& operator=(LLUniqueFile&& other)
+ {
+ close();
+ std::swap(mFileHandle, other.mFileHandle);
+ return *this;
+ }
+
+ // explicit close operation
+ void close()
+ {
+ if (mFileHandle)
+ {
+ // in case close() throws, set mFileHandle null FIRST
+ LLFILE* h{nullptr};
+ std::swap(h, mFileHandle);
+ LLFile::close(h);
+ }
+ }
+
+ // detect whether the wrapped LLFILE is open or not
+ explicit operator bool() const { return bool(mFileHandle); }
+ bool operator!() { return ! mFileHandle; }
+
+ // LLUniqueFile should be usable for any operation that accepts LLFILE*
+ // (or FILE* for that matter)
+ operator LLFILE*() const { return mFileHandle; }
+
+private:
+ LLFILE* mFileHandle;
+};
+
#if LL_WINDOWS
/**
* @brief Controlling input for files.
diff --git a/indra/llcommon/llinstancetracker.cpp b/indra/llcommon/llinstancetracker.cpp
index 3f990f4869..e7193b70b5 100644
--- a/indra/llcommon/llinstancetracker.cpp
+++ b/indra/llcommon/llinstancetracker.cpp
@@ -27,25 +27,15 @@
#include "linden_common.h"
// associated header
#include "llinstancetracker.h"
-#include "llapr.h"
-
+#include "llerror.h"
// STL headers
// std headers
// external library headers
// other Linden headers
-void LLInstanceTrackerBase::StaticBase::incrementDepth()
-{
- ++sIterationNestDepth;
-}
-
-void LLInstanceTrackerBase::StaticBase::decrementDepth()
-{
- llassert(sIterationNestDepth);
- --sIterationNestDepth;
-}
-
-U32 LLInstanceTrackerBase::StaticBase::getDepth()
+void LLInstanceTrackerPrivate::logerrs(const char* cls, const std::string& arg1,
+ const std::string& arg2, const std::string& arg3)
{
- return sIterationNestDepth;
+ LL_ERRS("LLInstanceTracker") << LLError::Log::demangle(cls)
+ << arg1 << arg2 << arg3 << LL_ENDL;
}
diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h
index 363d0bcbd5..402333cca7 100644
--- a/indra/llcommon/llinstancetracker.h
+++ b/indra/llcommon/llinstancetracker.h
@@ -28,354 +28,432 @@
#ifndef LL_LLINSTANCETRACKER_H
#define LL_LLINSTANCETRACKER_H
-#include <atomic>
#include <map>
+#include <set>
+#include <vector>
#include <typeinfo>
+#include <memory>
+#include <type_traits>
+
+#include "mutex.h"
-#include "llstringtable.h"
#include <boost/iterator/transform_iterator.hpp>
#include <boost/iterator/indirect_iterator.hpp>
+#include <boost/iterator/filter_iterator.hpp>
-// As of 2017-05-06, as far as nat knows, only clang supports __has_feature().
-// Unfortunately VS2013's preprocessor shortcut logic doesn't prevent it from
-// producing (fatal) warnings for defined(__clang__) && __has_feature(...).
-// Have to work around that.
-#if ! defined(__clang__)
-#define __has_feature(x) 0
-#endif // __clang__
-
-#if defined(LL_TEST_llinstancetracker) && __has_feature(cxx_noexcept)
-// ~LLInstanceTracker() performs llassert_always() validation. That's fine in
-// production code, since the llassert_always() is implemented as an LL_ERRS
-// message, which will crash-with-message. In our integration test executable,
-// though, this llassert_always() throws an exception instead so we can test
-// error conditions and continue running the test. However -- as of C++11,
-// destructors are implicitly noexcept(true). Unless we mark
-// ~LLInstanceTracker() noexcept(false), the test executable crashes even on
-// the ATTEMPT to throw.
-#define LLINSTANCETRACKER_DTOR_NOEXCEPT noexcept(false)
-#else
-// If we're building for production, or in fact building *any other* test, or
-// we're using a compiler that doesn't support __has_feature(), or we're not
-// compiling with a C++ version that supports noexcept -- don't specify it.
-#define LLINSTANCETRACKER_DTOR_NOEXCEPT
-#endif
+#include "lockstatic.h"
+#include "stringize.h"
-/**
- * Base class manages "class-static" data that must actually have singleton
- * semantics: one instance per process, rather than one instance per module as
- * sometimes happens with data simply declared static.
- */
-class LL_COMMON_API LLInstanceTrackerBase
+/*****************************************************************************
+* StaticBase
+*****************************************************************************/
+namespace LLInstanceTrackerPrivate
{
-protected:
- /// It's not essential to derive your STATICDATA (for use with
- /// getStatic()) from StaticBase; it's just that both known
- /// implementations do.
struct StaticBase
{
- StaticBase():
- sIterationNestDepth(0)
- {}
-
- void incrementDepth();
- void decrementDepth();
- U32 getDepth();
- private:
-#ifdef LL_WINDOWS
- std::atomic_uint32_t sIterationNestDepth;
-#else
- std::atomic_uint sIterationNestDepth;
-#endif
- };
-};
+ // We need to be able to lock static data while manipulating it.
+ std::mutex mMutex;
+ };
-LL_COMMON_API void assert_main_thread();
+ void logerrs(const char* cls, const std::string&, const std::string&, const std::string&);
+} // namespace LLInstanceTrackerPrivate
+/*****************************************************************************
+* LLInstanceTracker with key
+*****************************************************************************/
enum EInstanceTrackerAllowKeyCollisions
{
- LLInstanceTrackerErrorOnCollision,
- LLInstanceTrackerReplaceOnCollision
+ LLInstanceTrackerErrorOnCollision,
+ LLInstanceTrackerReplaceOnCollision
};
/// This mix-in class adds support for tracking all instances of the specified class parameter T
/// The (optional) key associates a value of type KEY with a given instance of T, for quick lookup
/// If KEY is not provided, then instances are stored in a simple set
/// @NOTE: see explicit specialization below for default KEY==void case
-/// @NOTE: this class is not thread-safe unless used as read-only
-template<typename T, typename KEY = void, EInstanceTrackerAllowKeyCollisions KEY_COLLISION_BEHAVIOR = LLInstanceTrackerErrorOnCollision>
-class LLInstanceTracker : public LLInstanceTrackerBase
+template<typename T, typename KEY = void,
+ EInstanceTrackerAllowKeyCollisions KEY_COLLISION_BEHAVIOR = LLInstanceTrackerErrorOnCollision>
+class LLInstanceTracker
{
- typedef LLInstanceTracker<T, KEY> self_t;
- typedef typename std::multimap<KEY, T*> InstanceMap;
- struct StaticData: public StaticBase
- {
- InstanceMap sMap;
- };
- static StaticData& getStatic() { static StaticData sData; return sData;}
- static InstanceMap& getMap_() { return getStatic().sMap; }
+ typedef std::map<KEY, std::shared_ptr<T>> InstanceMap;
+ struct StaticData: public LLInstanceTrackerPrivate::StaticBase
+ {
+ InstanceMap mMap;
+ };
+ typedef llthread::LockStatic<StaticData> LockStatic;
public:
- class instance_iter : public boost::iterator_facade<instance_iter, T, boost::forward_traversal_tag>
- {
- public:
- typedef boost::iterator_facade<instance_iter, T, boost::forward_traversal_tag> super_t;
-
- instance_iter(const typename InstanceMap::iterator& it)
- : mIterator(it)
- {
- getStatic().incrementDepth();
- }
-
- ~instance_iter()
- {
- getStatic().decrementDepth();
- }
-
-
- private:
- friend class boost::iterator_core_access;
-
- void increment() { mIterator++; }
- bool equal(instance_iter const& other) const
- {
- return mIterator == other.mIterator;
- }
-
- T& dereference() const
- {
- return *(mIterator->second);
- }
-
- typename InstanceMap::iterator mIterator;
- };
-
- class key_iter : public boost::iterator_facade<key_iter, KEY, boost::forward_traversal_tag>
- {
- public:
- typedef boost::iterator_facade<key_iter, KEY, boost::forward_traversal_tag> super_t;
-
- key_iter(typename InstanceMap::iterator it)
- : mIterator(it)
- {
- getStatic().incrementDepth();
- }
-
- key_iter(const key_iter& other)
- : mIterator(other.mIterator)
- {
- getStatic().incrementDepth();
- }
-
- ~key_iter()
- {
- getStatic().decrementDepth();
- }
-
-
- private:
- friend class boost::iterator_core_access;
-
- void increment() { mIterator++; }
- bool equal(key_iter const& other) const
- {
- return mIterator == other.mIterator;
- }
-
- KEY& dereference() const
- {
- return const_cast<KEY&>(mIterator->first);
- }
-
- typename InstanceMap::iterator mIterator;
- };
-
- static T* getInstance(const KEY& k)
- {
- const InstanceMap& map(getMap_());
- typename InstanceMap::const_iterator found = map.find(k);
- return (found == map.end()) ? NULL : found->second;
- }
-
- static instance_iter beginInstances()
- {
- return instance_iter(getMap_().begin());
- }
-
- static instance_iter endInstances()
- {
- return instance_iter(getMap_().end());
- }
-
- static S32 instanceCount()
- {
- return getMap_().size();
- }
-
- static key_iter beginKeys()
- {
- return key_iter(getMap_().begin());
- }
- static key_iter endKeys()
- {
- return key_iter(getMap_().end());
- }
+ // snapshot of std::pair<const KEY, std::shared_ptr<T>> pairs
+ class snapshot
+ {
+ // It's very important that what we store in this snapshot are
+ // weak_ptrs, NOT shared_ptrs. That's how we discover whether any
+ // instance has been deleted during the lifespan of a snapshot.
+ typedef std::vector<std::pair<const KEY, std::weak_ptr<T>>> VectorType;
+ // Dereferencing our iterator produces a std::shared_ptr for each
+ // instance that still exists. Since we store weak_ptrs, that involves
+ // two chained transformations:
+ // - a transform_iterator to lock the weak_ptr and return a shared_ptr
+ // - a filter_iterator to skip any shared_ptr that has become invalid.
+ // It is very important that we filter lazily, that is, during
+ // traversal. Any one of our stored weak_ptrs might expire during
+ // traversal.
+ typedef std::pair<const KEY, std::shared_ptr<T>> strong_pair;
+ // Note for future reference: nat has not yet had any luck (up to
+ // Boost 1.67) trying to use boost::transform_iterator with a hand-
+ // coded functor, only with actual functions. In my experience, an
+ // internal boost::result_of() operation fails, even with an explicit
+ // result_type typedef. But this works.
+ static strong_pair strengthen(typename VectorType::value_type& pair)
+ {
+ return { pair.first, pair.second.lock() };
+ }
+ static bool dead_skipper(const strong_pair& pair)
+ {
+ return bool(pair.second);
+ }
+
+ public:
+ snapshot():
+ // populate our vector with a snapshot of (locked!) InstanceMap
+ // note, this assigns pair<KEY, shared_ptr> to pair<KEY, weak_ptr>
+ mData(mLock->mMap.begin(), mLock->mMap.end())
+ {
+ // release the lock once we've populated mData
+ mLock.unlock();
+ }
+
+ // You can't make a transform_iterator (or anything else) that
+ // literally stores a C++ function (decltype(strengthen)) -- but you
+ // can make a transform_iterator based on a _function pointer._
+ typedef boost::transform_iterator<decltype(strengthen)*,
+ typename VectorType::iterator> strong_iterator;
+ typedef boost::filter_iterator<decltype(dead_skipper)*, strong_iterator> iterator;
+
+ iterator begin() { return make_iterator(mData.begin()); }
+ iterator end() { return make_iterator(mData.end()); }
+
+ private:
+ iterator make_iterator(typename VectorType::iterator iter)
+ {
+ // transform_iterator only needs the base iterator and the transform.
+ // filter_iterator wants the predicate and both ends of the range.
+ return iterator(dead_skipper,
+ strong_iterator(iter, strengthen),
+ strong_iterator(mData.end(), strengthen));
+ }
+
+ // lock static data during construction
+#if ! LL_WINDOWS
+ LockStatic mLock;
+#else // LL_WINDOWS
+ // We want to be able to use (e.g.) our instance_snapshot subclass as:
+ // for (auto& inst : T::instance_snapshot()) ...
+ // But when this snapshot base class directly contains LockStatic, as
+ // above, Visual Studio 2017 requires us to code instead:
+ // for (auto& inst : std::move(T::instance_snapshot())) ...
+ // nat thinks this should be unnecessary, as an anonymous class
+ // instance is already a temporary. It shouldn't need to be cast to
+ // rvalue reference (the role of std::move()). clang evidently agrees,
+ // as the short form works fine with Xcode on Mac.
+ // To support the succinct usage, instead of directly storing
+ // LockStatic, store std::shared_ptr<LockStatic>, which is copyable.
+ std::shared_ptr<LockStatic> mLockp{std::make_shared<LockStatic>()};
+ LockStatic& mLock{*mLockp};
+#endif // LL_WINDOWS
+ VectorType mData;
+ };
+
+ // iterate over this for references to each instance
+ class instance_snapshot: public snapshot
+ {
+ private:
+ static T& instance_getter(typename snapshot::iterator::reference pair)
+ {
+ return *pair.second;
+ }
+ public:
+ typedef boost::transform_iterator<decltype(instance_getter)*,
+ typename snapshot::iterator> iterator;
+ iterator begin() { return iterator(snapshot::begin(), instance_getter); }
+ iterator end() { return iterator(snapshot::end(), instance_getter); }
+
+ void deleteAll()
+ {
+ for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it)
+ {
+ delete it->second.get();
+ }
+ }
+ };
+
+ // iterate over this for each key
+ class key_snapshot: public snapshot
+ {
+ private:
+ static KEY key_getter(typename snapshot::iterator::reference pair)
+ {
+ return pair.first;
+ }
+ public:
+ typedef boost::transform_iterator<decltype(key_getter)*,
+ typename snapshot::iterator> iterator;
+ iterator begin() { return iterator(snapshot::begin(), key_getter); }
+ iterator end() { return iterator(snapshot::end(), key_getter); }
+ };
+
+ static T* getInstance(const KEY& k)
+ {
+ LockStatic lock;
+ const InstanceMap& map(lock->mMap);
+ typename InstanceMap::const_iterator found = map.find(k);
+ return (found == map.end()) ? NULL : found->second.get();
+ }
+
+ static S32 instanceCount()
+ {
+ return LockStatic()->mMap.size();
+ }
protected:
- LLInstanceTracker(const KEY& key)
- {
- // make sure static data outlives all instances
- getStatic();
- add_(key);
- }
- virtual ~LLInstanceTracker() LLINSTANCETRACKER_DTOR_NOEXCEPT
- {
- // it's unsafe to delete instances of this type while all instances are being iterated over.
- llassert_always(getStatic().getDepth() == 0);
- remove_();
- }
- virtual void setKey(KEY key) { remove_(); add_(key); }
- virtual const KEY& getKey() const { return mInstanceKey; }
+ LLInstanceTracker(const KEY& key)
+ {
+ // We do not intend to manage the lifespan of this object with
+ // shared_ptr, so give it a no-op deleter. We store shared_ptrs in our
+ // InstanceMap specifically so snapshot can store weak_ptrs so we can
+ // detect deletions during traversals.
+ std::shared_ptr<T> ptr(static_cast<T*>(this), [](T*){});
+ LockStatic lock;
+ add_(lock, key, ptr);
+ }
+public:
+ virtual ~LLInstanceTracker()
+ {
+ LockStatic lock;
+ remove_(lock);
+ }
+protected:
+ virtual void setKey(KEY key)
+ {
+ LockStatic lock;
+ // Even though the shared_ptr we store in our map has a no-op deleter
+ // for T itself, letting the use count decrement to 0 will still
+ // delete the use-count object. Capture the shared_ptr we just removed
+ // and re-add it to the map with the new key.
+ auto ptr = remove_(lock);
+ add_(lock, key, ptr);
+ }
+public:
+ virtual const KEY& getKey() const { return mInstanceKey; }
private:
- LLInstanceTracker( const LLInstanceTracker& );
- const LLInstanceTracker& operator=( const LLInstanceTracker& );
-
- void add_(const KEY& key)
- {
- mInstanceKey = key;
- InstanceMap& map = getMap_();
- typename InstanceMap::iterator insertion_point_it = map.lower_bound(key);
- if (insertion_point_it != map.end()
- && insertion_point_it->first == key)
- { // found existing entry with that key
- switch(KEY_COLLISION_BEHAVIOR)
- {
- case LLInstanceTrackerErrorOnCollision:
- {
- // use assert here instead of LL_ERRS(), otherwise the error will be ignored since this call is made during global object initialization
- llassert_always_msg(false, "Instance with this same key already exists!");
- break;
- }
- case LLInstanceTrackerReplaceOnCollision:
- {
- // replace pointer, but leave key (should have compared equal anyway)
- insertion_point_it->second = static_cast<T*>(this);
- break;
- }
- default:
- break;
- }
- }
- else
- { // new key
- map.insert(insertion_point_it, std::make_pair(key, static_cast<T*>(this)));
- }
- }
- void remove_()
- {
- InstanceMap& map = getMap_();
- typename InstanceMap::iterator iter = map.find(mInstanceKey);
- if (iter != map.end())
- {
- map.erase(iter);
- }
- }
+ LLInstanceTracker( const LLInstanceTracker& ) = delete;
+ LLInstanceTracker& operator=( const LLInstanceTracker& ) = delete;
+
+ // for logging
+ template <typename K>
+ static std::string report(K key) { return stringize(key); }
+ static std::string report(const std::string& key) { return "'" + key + "'"; }
+ static std::string report(const char* key) { return report(std::string(key)); }
+
+ // caller must instantiate LockStatic
+ void add_(LockStatic& lock, const KEY& key, const std::shared_ptr<T>& ptr)
+ {
+ mInstanceKey = key;
+ InstanceMap& map = lock->mMap;
+ switch(KEY_COLLISION_BEHAVIOR)
+ {
+ case LLInstanceTrackerErrorOnCollision:
+ {
+ // map stores shared_ptr to self
+ auto pair = map.emplace(key, ptr);
+ if (! pair.second)
+ {
+ LLInstanceTrackerPrivate::logerrs(typeid(*this).name(), " instance with key ",
+ report(key), " already exists!");
+ }
+ break;
+ }
+ case LLInstanceTrackerReplaceOnCollision:
+ map[key] = ptr;
+ break;
+ default:
+ break;
+ }
+ }
+ std::shared_ptr<T> remove_(LockStatic& lock)
+ {
+ InstanceMap& map = lock->mMap;
+ typename InstanceMap::iterator iter = map.find(mInstanceKey);
+ if (iter != map.end())
+ {
+ auto ret = iter->second;
+ map.erase(iter);
+ return ret;
+ }
+ return {};
+ }
private:
- KEY mInstanceKey;
+ KEY mInstanceKey;
};
+/*****************************************************************************
+* LLInstanceTracker without key
+*****************************************************************************/
+// TODO:
+// - For the case of omitted KEY template parameter, consider storing
+// std::map<T*, std::shared_ptr<T>> instead of std::set<std::shared_ptr<T>>.
+// That might let us share more of the implementation between KEY and
+// non-KEY LLInstanceTracker subclasses.
+// - Even if not that, consider trying to unify the snapshot implementations.
+// The trouble is that the 'iterator' published by each (and by their
+// subclasses) must reflect the specific type of the callables that
+// distinguish them. (Maybe make instance_snapshot() and key_snapshot()
+// factory functions that pass lambdas to a factory function for the generic
+// template class?)
+
/// explicit specialization for default case where KEY is void
/// use a simple std::set<T*>
template<typename T, EInstanceTrackerAllowKeyCollisions KEY_COLLISION_BEHAVIOR>
-class LLInstanceTracker<T, void, KEY_COLLISION_BEHAVIOR> : public LLInstanceTrackerBase
+class LLInstanceTracker<T, void, KEY_COLLISION_BEHAVIOR>
{
- typedef LLInstanceTracker<T, void> self_t;
- typedef typename std::set<T*> InstanceSet;
- struct StaticData: public StaticBase
- {
- InstanceSet sSet;
- };
- static StaticData& getStatic() { static StaticData sData; return sData; }
- static InstanceSet& getSet_() { return getStatic().sSet; }
+ typedef std::set<std::shared_ptr<T>> InstanceSet;
+ struct StaticData: public LLInstanceTrackerPrivate::StaticBase
+ {
+ InstanceSet mSet;
+ };
+ typedef llthread::LockStatic<StaticData> LockStatic;
public:
+ /**
+ * Storing a dumb T* somewhere external is a bad idea, since
+ * LLInstanceTracker subclasses are explicitly destroyed rather than
+ * managed by smart pointers. It's legal to declare stack instances of an
+ * LLInstanceTracker subclass. But it's reasonable to store a
+ * std::weak_ptr<T>, which will become invalid when the T instance is
+ * destroyed.
+ */
+ std::weak_ptr<T> getWeak()
+ {
+ return mSelf;
+ }
+
+ static S32 instanceCount() { return LockStatic()->mSet.size(); }
- /**
- * Does a particular instance still exist? Of course, if you already have
- * a T* in hand, you need not call getInstance() to @em locate the
- * instance -- unlike the case where getInstance() accepts some kind of
- * key. Nonetheless this method is still useful to @em validate a
- * particular T*, since each instance's destructor removes itself from the
- * underlying set.
- */
- static T* getInstance(T* k)
- {
- const InstanceSet& set(getSet_());
- typename InstanceSet::const_iterator found = set.find(k);
- return (found == set.end())? NULL : *found;
- }
- static S32 instanceCount() { return getSet_().size(); }
-
- class instance_iter : public boost::iterator_facade<instance_iter, T, boost::forward_traversal_tag>
- {
- public:
- instance_iter(const typename InstanceSet::iterator& it)
- : mIterator(it)
- {
- getStatic().incrementDepth();
- }
-
- instance_iter(const instance_iter& other)
- : mIterator(other.mIterator)
- {
- getStatic().incrementDepth();
- }
-
- ~instance_iter()
- {
- getStatic().decrementDepth();
- }
-
- private:
- friend class boost::iterator_core_access;
-
- void increment() { mIterator++; }
- bool equal(instance_iter const& other) const
- {
- return mIterator == other.mIterator;
- }
-
- T& dereference() const
- {
- return **mIterator;
- }
-
- typename InstanceSet::iterator mIterator;
- };
-
- static instance_iter beginInstances() { return instance_iter(getSet_().begin()); }
- static instance_iter endInstances() { return instance_iter(getSet_().end()); }
+ // snapshot of std::shared_ptr<T> pointers
+ class snapshot
+ {
+ // It's very important that what we store in this snapshot are
+ // weak_ptrs, NOT shared_ptrs. That's how we discover whether any
+ // instance has been deleted during the lifespan of a snapshot.
+ typedef std::vector<std::weak_ptr<T>> VectorType;
+ // Dereferencing our iterator produces a std::shared_ptr for each
+ // instance that still exists. Since we store weak_ptrs, that involves
+ // two chained transformations:
+ // - a transform_iterator to lock the weak_ptr and return a shared_ptr
+ // - a filter_iterator to skip any shared_ptr that has become invalid.
+ typedef std::shared_ptr<T> strong_ptr;
+ static strong_ptr strengthen(typename VectorType::value_type& ptr)
+ {
+ return ptr.lock();
+ }
+ static bool dead_skipper(const strong_ptr& ptr)
+ {
+ return bool(ptr);
+ }
+
+ public:
+ snapshot():
+ // populate our vector with a snapshot of (locked!) InstanceSet
+ // note, this assigns stored shared_ptrs to weak_ptrs for snapshot
+ mData(mLock->mSet.begin(), mLock->mSet.end())
+ {
+ // release the lock once we've populated mData
+ mLock.unlock();
+ }
+
+ typedef boost::transform_iterator<decltype(strengthen)*,
+ typename VectorType::iterator> strong_iterator;
+ typedef boost::filter_iterator<decltype(dead_skipper)*, strong_iterator> iterator;
+
+ iterator begin() { return make_iterator(mData.begin()); }
+ iterator end() { return make_iterator(mData.end()); }
+
+ private:
+ iterator make_iterator(typename VectorType::iterator iter)
+ {
+ // transform_iterator only needs the base iterator and the transform.
+ // filter_iterator wants the predicate and both ends of the range.
+ return iterator(dead_skipper,
+ strong_iterator(iter, strengthen),
+ strong_iterator(mData.end(), strengthen));
+ }
+
+ // lock static data during construction
+#if ! LL_WINDOWS
+ LockStatic mLock;
+#else // LL_WINDOWS
+ // We want to be able to use our instance_snapshot subclass as:
+ // for (auto& inst : T::instance_snapshot()) ...
+ // But when this snapshot base class directly contains LockStatic, as
+ // above, Visual Studio 2017 requires us to code instead:
+ // for (auto& inst : std::move(T::instance_snapshot())) ...
+ // nat thinks this should be unnecessary, as an anonymous class
+ // instance is already a temporary. It shouldn't need to be cast to
+ // rvalue reference (the role of std::move()). clang evidently agrees,
+ // as the short form works fine with Xcode on Mac.
+ // To support the succinct usage, instead of directly storing
+ // LockStatic, store std::shared_ptr<LockStatic>, which is copyable.
+ std::shared_ptr<LockStatic> mLockp{std::make_shared<LockStatic>()};
+ LockStatic& mLock{*mLockp};
+#endif // LL_WINDOWS
+ VectorType mData;
+ };
+
+ // iterate over this for references to each instance
+ struct instance_snapshot: public snapshot
+ {
+ typedef boost::indirect_iterator<typename snapshot::iterator> iterator;
+ iterator begin() { return iterator(snapshot::begin()); }
+ iterator end() { return iterator(snapshot::end()); }
+
+ void deleteAll()
+ {
+ for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it)
+ {
+ delete it->get();
+ }
+ }
+ };
protected:
- LLInstanceTracker()
- {
- // make sure static data outlives all instances
- getStatic();
- getSet_().insert(static_cast<T*>(this));
- }
- virtual ~LLInstanceTracker() LLINSTANCETRACKER_DTOR_NOEXCEPT
- {
- // it's unsafe to delete instances of this type while all instances are being iterated over.
- llassert_always(getStatic().getDepth() == 0);
- getSet_().erase(static_cast<T*>(this));
- }
-
- LLInstanceTracker(const LLInstanceTracker& other)
- {
- getSet_().insert(static_cast<T*>(this));
- }
+ LLInstanceTracker()
+ {
+ // Since we do not intend for this shared_ptr to manage lifespan, give
+ // it a no-op deleter.
+ std::shared_ptr<T> ptr(static_cast<T*>(this), [](T*){});
+ // save corresponding weak_ptr for future reference
+ mSelf = ptr;
+ // Also store it in our class-static set to track this instance.
+ LockStatic()->mSet.emplace(ptr);
+ }
+public:
+ virtual ~LLInstanceTracker()
+ {
+ // convert weak_ptr to shared_ptr because that's what we store in our
+ // InstanceSet
+ LockStatic()->mSet.erase(mSelf.lock());
+ }
+protected:
+ LLInstanceTracker(const LLInstanceTracker& other):
+ LLInstanceTracker()
+ {}
+
+private:
+ // Storing a weak_ptr to self is a bit like deriving from
+ // std::enable_shared_from_this(), except more explicit.
+ std::weak_ptr<T> mSelf;
};
#endif
diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp
index fa5730f112..3e6ce9092c 100644
--- a/indra/llcommon/llleaplistener.cpp
+++ b/indra/llcommon/llleaplistener.cpp
@@ -14,6 +14,8 @@
// associated header
#include "llleaplistener.h"
// STL headers
+#include <map>
+#include <functional>
// std headers
// external library headers
#include <boost/foreach.hpp>
@@ -60,16 +62,11 @@ LLLeapListener::LLLeapListener(const ConnectFunc& connect):
LLSD need_name(LLSDMap("name", LLSD()));
add("newpump",
"Instantiate a new LLEventPump named like [\"name\"] and listen to it.\n"
- "If [\"type\"] == \"LLEventQueue\", make LLEventQueue, else LLEventStream.\n"
+ "[\"type\"] == \"LLEventStream\", \"LLEventMailDrop\" et al.\n"
"Events sent through new LLEventPump will be decorated with [\"pump\"]=name.\n"
"Returns actual name in [\"name\"] (may be different if collision).",
&LLLeapListener::newpump,
need_name);
- add("killpump",
- "Delete LLEventPump [\"name\"] created by \"newpump\".\n"
- "Returns [\"status\"] boolean indicating whether such a pump existed.",
- &LLLeapListener::killpump,
- need_name);
LLSD need_source_listener(LLSDMap("source", LLSD())("listener", LLSD()));
add("listen",
"Listen to an existing LLEventPump named [\"source\"], with listener name\n"
@@ -124,40 +121,23 @@ void LLLeapListener::newpump(const LLSD& request)
Response reply(LLSD(), request);
std::string name = request["name"];
- LLSD const & type = request["type"];
+ std::string type = request["type"];
- LLEventPump * new_pump = NULL;
- if (type.asString() == "LLEventQueue")
+ try
{
- new_pump = new LLEventQueue(name, true); // tweak name for uniqueness
+ // tweak name for uniqueness
+ LLEventPump& new_pump(LLEventPumps::instance().make(name, true, type));
+ name = new_pump.getName();
+ reply["name"] = name;
+
+ // Now listen on this new pump with our plugin listener
+ std::string myname("llleap");
+ saveListener(name, myname, mConnect(new_pump, myname));
}
- else
+ catch (const LLEventPumps::BadType& error)
{
- if (! (type.isUndefined() || type.asString() == "LLEventStream"))
- {
- reply.warn(STRINGIZE("unknown 'type' " << type << ", using LLEventStream"));
- }
- new_pump = new LLEventStream(name, true); // tweak name for uniqueness
+ reply.error(error.what());
}
-
- name = new_pump->getName();
-
- mEventPumps.insert(name, new_pump);
-
- // Now listen on this new pump with our plugin listener
- std::string myname("llleap");
- saveListener(name, myname, mConnect(*new_pump, myname));
-
- reply["name"] = name;
-}
-
-void LLLeapListener::killpump(const LLSD& request)
-{
- Response reply(LLSD(), request);
-
- std::string name = request["name"];
- // success == (nonzero number of entries were erased)
- reply["status"] = bool(mEventPumps.erase(name));
}
void LLLeapListener::listen(const LLSD& request)
@@ -228,13 +208,11 @@ void LLLeapListener::getAPIs(const LLSD& request) const
{
Response reply(LLSD(), request);
- for (LLEventAPI::instance_iter eai(LLEventAPI::beginInstances()),
- eaend(LLEventAPI::endInstances());
- eai != eaend; ++eai)
+ for (auto& ea : LLEventAPI::instance_snapshot())
{
LLSD info;
- info["desc"] = eai->getDesc();
- reply[eai->getName()] = info;
+ info["desc"] = ea.getDesc();
+ reply[ea.getName()] = info;
}
}
diff --git a/indra/llcommon/llleaplistener.h b/indra/llcommon/llleaplistener.h
index 2193d81b9e..0ca5893657 100644
--- a/indra/llcommon/llleaplistener.h
+++ b/indra/llcommon/llleaplistener.h
@@ -40,7 +40,6 @@ public:
private:
void newpump(const LLSD&);
- void killpump(const LLSD&);
void listen(const LLSD&);
void stoplistening(const LLSD&);
void ping(const LLSD&) const;
@@ -64,10 +63,6 @@ private:
// and listener name.
typedef std::map<std::pair<std::string, std::string>, LLBoundListener> ListenersMap;
ListenersMap mListeners;
- // Similar lifespan reasoning applies to LLEventPumps instantiated by
- // newpump() operations.
- typedef boost::ptr_map<std::string, LLEventPump> EventPumpsMap;
- EventPumpsMap mEventPumps;
};
#endif /* ! defined(LL_LLLEAPLISTENER_H) */
diff --git a/indra/llcommon/lllistenerwrapper.h b/indra/llcommon/lllistenerwrapper.h
deleted file mode 100644
index 09d074abca..0000000000
--- a/indra/llcommon/lllistenerwrapper.h
+++ /dev/null
@@ -1,198 +0,0 @@
-/**
- * @file lllistenerwrapper.h
- * @author Nat Goodspeed
- * @date 2009-11-30
- * @brief Introduce LLListenerWrapper template
- *
- * $LicenseInfo:firstyear=2009&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#if ! defined(LL_LLLISTENERWRAPPER_H)
-#define LL_LLLISTENERWRAPPER_H
-
-#include "llevents.h" // LLListenerWrapperBase
-#include <boost/visit_each.hpp>
-
-/**
- * Template base class for coding wrappers for LLEventPump listeners.
- *
- * Derive your listener wrapper from LLListenerWrapper. You must use
- * LLLISTENER_WRAPPER_SUBCLASS() so your subclass will play nicely with
- * boost::visit_each (q.v.). That way boost::signals2 can still detect
- * derivation from LLEventTrackable, and so forth.
- */
-template <typename LISTENER>
-class LLListenerWrapper: public LLListenerWrapperBase
-{
-public:
- /// Wrap an arbitrary listener object
- LLListenerWrapper(const LISTENER& listener):
- mListener(listener)
- {}
-
- /// call
- virtual bool operator()(const LLSD& event)
- {
- return mListener(event);
- }
-
- /// Allow boost::visit_each() to peek at our mListener.
- template <class V>
- void accept_visitor(V& visitor) const
- {
- using boost::visit_each;
- visit_each(visitor, mListener, 0);
- }
-
-private:
- LISTENER mListener;
-};
-
-/**
- * Specialize boost::visit_each() (leveraging ADL) to peek inside an
- * LLListenerWrapper<T> to traverse its LISTENER. We borrow the
- * accept_visitor() pattern from boost::bind(), avoiding the need to make
- * mListener public.
- */
-template <class V, typename T>
-void visit_each(V& visitor, const LLListenerWrapper<T>& wrapper, int)
-{
- wrapper.accept_visitor(visitor);
-}
-
-/// use this (sigh!) for each subclass of LLListenerWrapper<T> you write
-#define LLLISTENER_WRAPPER_SUBCLASS(CLASS) \
-template <class V, typename T> \
-void visit_each(V& visitor, const CLASS<T>& wrapper, int) \
-{ \
- visit_each(visitor, static_cast<const LLListenerWrapper<T>&>(wrapper), 0); \
-} \
- \
-/* Have to state this explicitly, rather than using LL_TEMPLATE_CONVERTIBLE, */ \
-/* because the source type is itself a template. */ \
-template <typename T> \
-struct ll_template_cast_impl<const LLListenerWrapperBase*, const CLASS<T>*> \
-{ \
- const LLListenerWrapperBase* operator()(const CLASS<T>* wrapper) \
- { \
- return wrapper; \
- } \
-}
-
-/**
- * Make an instance of a listener wrapper. Every wrapper class must be a
- * template accepting a listener object of arbitrary type. In particular, the
- * type of a boost::bind() expression is deliberately undocumented. So we
- * can't just write Wrapper<CorrectType>(boost::bind(...)). Instead we must
- * write llwrap<Wrapper>(boost::bind(...)).
- */
-template <template<typename> class WRAPPER, typename T>
-WRAPPER<T> llwrap(const T& listener)
-{
- return WRAPPER<T>(listener);
-}
-
-/**
- * This LLListenerWrapper template subclass is used to report entry/exit to an
- * event listener, by changing this:
- * @code
- * someEventPump.listen("MyClass",
- * boost::bind(&MyClass::method, ptr, _1));
- * @endcode
- * to this:
- * @code
- * someEventPump.listen("MyClass",
- * llwrap<LLCoutListener>(
- * boost::bind(&MyClass::method, ptr, _1)));
- * @endcode
- */
-template <class LISTENER>
-class LLCoutListener: public LLListenerWrapper<LISTENER>
-{
- typedef LLListenerWrapper<LISTENER> super;
-
-public:
- /// Wrap an arbitrary listener object
- LLCoutListener(const LISTENER& listener):
- super(listener)
- {}
-
- /// call
- virtual bool operator()(const LLSD& event)
- {
- std::cout << "Entering listener " << *super::mName << " with " << event << std::endl;
- bool handled = super::operator()(event);
- std::cout << "Leaving listener " << *super::mName;
- if (handled)
- {
- std::cout << " (handled)";
- }
- std::cout << std::endl;
- return handled;
- }
-};
-
-LLLISTENER_WRAPPER_SUBCLASS(LLCoutListener);
-
-/**
- * This LLListenerWrapper template subclass is used to log entry/exit to an
- * event listener, by changing this:
- * @code
- * someEventPump.listen("MyClass",
- * boost::bind(&MyClass::method, ptr, _1));
- * @endcode
- * to this:
- * @code
- * someEventPump.listen("MyClass",
- * llwrap<LLLogListener>(
- * boost::bind(&MyClass::method, ptr, _1)));
- * @endcode
- */
-template <class LISTENER>
-class LLLogListener: public LLListenerWrapper<LISTENER>
-{
- typedef LLListenerWrapper<LISTENER> super;
-
-public:
- /// Wrap an arbitrary listener object
- LLLogListener(const LISTENER& listener):
- super(listener)
- {}
-
- /// call
- virtual bool operator()(const LLSD& event)
- {
- LL_DEBUGS("LLLogListener") << "Entering listener " << *super::mName << " with " << event << LL_ENDL;
- bool handled = super::operator()(event);
- LL_DEBUGS("LLLogListener") << "Leaving listener " << *super::mName;
- if (handled)
- {
- LL_CONT << " (handled)";
- }
- LL_CONT << LL_ENDL;
- return handled;
- }
-};
-
-LLLISTENER_WRAPPER_SUBCLASS(LLLogListener);
-
-#endif /* ! defined(LL_LLLISTENERWRAPPER_H) */
diff --git a/indra/llcommon/llmainthreadtask.cpp b/indra/llcommon/llmainthreadtask.cpp
new file mode 100644
index 0000000000..e0d70cacd8
--- /dev/null
+++ b/indra/llcommon/llmainthreadtask.cpp
@@ -0,0 +1,22 @@
+/**
+ * @file llmainthreadtask.cpp
+ * @author Nat Goodspeed
+ * @date 2019-12-05
+ * @brief Implementation for llmainthreadtask.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llmainthreadtask.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+
+// This file is required by our CMake integration-test machinery. It
+// contributes no code to the viewer executable.
diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h
new file mode 100644
index 0000000000..d509b687c0
--- /dev/null
+++ b/indra/llcommon/llmainthreadtask.h
@@ -0,0 +1,99 @@
+/**
+ * @file llmainthreadtask.h
+ * @author Nat Goodspeed
+ * @date 2019-12-04
+ * @brief LLMainThreadTask dispatches work to the main thread. When invoked on
+ * the main thread, it performs the work inline.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLMAINTHREADTASK_H)
+#define LL_LLMAINTHREADTASK_H
+
+#include "lleventtimer.h"
+#include "llthread.h"
+#include "llmake.h"
+#include <future>
+#include <type_traits> // std::result_of
+
+/**
+ * LLMainThreadTask provides a way to perform some task specifically on the
+ * main thread, waiting for it to complete. A task consists of a C++ nullary
+ * invocable (i.e. any callable that requires no arguments) with arbitrary
+ * return type.
+ *
+ * Instead of instantiating LLMainThreadTask, pass your invocable to its
+ * static dispatch() method. dispatch() returns the result of calling your
+ * task. (Or, if your task throws an exception, dispatch() throws that
+ * exception. See std::packaged_task.)
+ *
+ * When you call dispatch() on the main thread (as determined by
+ * on_main_thread() in llthread.h), it simply calls your task and returns the
+ * result.
+ *
+ * When you call dispatch() on a secondary thread, it instantiates an
+ * LLEventTimer subclass scheduled immediately. Next time the main loop calls
+ * LLEventTimer::updateClass(), your task will be run, and LLMainThreadTask
+ * will fulfill a future with its result. Meanwhile the requesting thread
+ * blocks on that future. As soon as it is set, the requesting thread wakes up
+ * with the task result.
+ */
+class LLMainThreadTask
+{
+private:
+ // Don't instantiate this class -- use dispatch() instead.
+ LLMainThreadTask() {}
+
+public:
+ /// dispatch() is the only way to invoke this functionality.
+ template <typename CALLABLE>
+ static auto dispatch(CALLABLE&& callable) -> decltype(callable())
+ {
+ if (on_main_thread())
+ {
+ // we're already running on the main thread, perfect
+ return callable();
+ }
+ else
+ {
+ // It's essential to construct LLEventTimer subclass instances on
+ // the heap because, on completion, LLEventTimer deletes them.
+ // Once we enable C++17, we can use Class Template Argument
+ // Deduction. Until then, use llmake_heap().
+ auto* task = llmake_heap<Task>(std::forward<CALLABLE>(callable));
+ auto future = task->mTask.get_future();
+ // Now simply block on the future.
+ return future.get();
+ }
+ }
+
+private:
+ template <typename CALLABLE>
+ struct Task: public LLEventTimer
+ {
+ Task(CALLABLE&& callable):
+ // no wait time: call tick() next chance we get
+ LLEventTimer(0),
+ mTask(std::forward<CALLABLE>(callable))
+ {}
+ BOOL tick() override
+ {
+ // run the task on the main thread, will populate the future
+ // obtained by get_future()
+ mTask();
+ // tell LLEventTimer we're done (one shot)
+ return TRUE;
+ }
+ // Given arbitrary CALLABLE, which might be a lambda, how are we
+ // supposed to obtain its signature for std::packaged_task? It seems
+ // redundant to have to add an argument list to engage result_of, then
+ // add the argument list again to complete the signature. At least we
+ // only support a nullary CALLABLE.
+ std::packaged_task<typename std::result_of<CALLABLE()>::type()> mTask;
+ };
+};
+
+#endif /* ! defined(LL_LLMAINTHREADTASK_H) */
diff --git a/indra/llcommon/llmake.h b/indra/llcommon/llmake.h
index 08744f90fb..02463d97ea 100644
--- a/indra/llcommon/llmake.h
+++ b/indra/llcommon/llmake.h
@@ -12,10 +12,8 @@
*
* also relevant:
*
- * Template argument deduction for class templates
- * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0091r3.html
- * was apparently adopted in June 2016? Unclear when compilers will
- * portably support this, but there is hope.
+ * Template argument deduction for class templates (C++17)
+ * https://en.cppreference.com/w/cpp/language/class_template_argument_deduction
*
* $LicenseInfo:firstyear=2015&license=viewerlgpl$
* Copyright (c) 2015, Linden Research, Inc.
@@ -25,37 +23,43 @@
#if ! defined(LL_LLMAKE_H)
#define LL_LLMAKE_H
-/*==========================================================================*|
-// When we allow ourselves to compile with C++11 features enabled, this form
-// should generically handle an arbitrary number of arguments.
-
+/**
+ * Usage: llmake<SomeTemplate>(args...)
+ *
+ * Deduces the types T... of 'args' and returns an instance of
+ * SomeTemplate<T...>(args...).
+ */
template <template<typename...> class CLASS_TEMPLATE, typename... ARGS>
CLASS_TEMPLATE<ARGS...> llmake(ARGS && ... args)
{
return CLASS_TEMPLATE<ARGS...>(std::forward<ARGS>(args)...);
}
-|*==========================================================================*/
-// As of 2015-12-18, this is what we'll use instead. Add explicit overloads
-// for different numbers of template parameters as use cases arise.
+/// dumb pointer template just in case that's what's wanted
+template <typename T>
+using dumb_pointer = T*;
/**
- * Usage: llmake<SomeTemplate>(arg)
+ * Same as llmake(), but returns a pointer to a new heap instance of
+ * SomeTemplate<T...>(args...) using the pointer of your choice.
*
- * Deduces the type T of 'arg' and returns an instance of SomeTemplate<T>
- * initialized with 'arg'. Assumes a constructor accepting T (by value,
- * reference or whatever).
+ * @code
+ * auto* dumb = llmake_heap<SomeTemplate>(args...);
+ * auto shared = llmake_heap<SomeTemplate, std::shared_ptr>(args...);
+ * auto unique = llmake_heap<SomeTemplate, std::unique_ptr>(args...);
+ * @endcode
*/
-template <template<typename> class CLASS_TEMPLATE, typename ARG1>
-CLASS_TEMPLATE<ARG1> llmake(const ARG1& arg1)
-{
- return CLASS_TEMPLATE<ARG1>(arg1);
-}
-
-template <template<typename, typename> class CLASS_TEMPLATE, typename ARG1, typename ARG2>
-CLASS_TEMPLATE<ARG1, ARG2> llmake(const ARG1& arg1, const ARG2& arg2)
+// POINTER_TEMPLATE is characterized as template<typename...> rather than as
+// template<typename T> because (e.g.) std::unique_ptr has multiple template
+// arguments. Even though we only engage one, std::unique_ptr doesn't match a
+// template template parameter that itself takes only one template parameter.
+template <template<typename...> class CLASS_TEMPLATE,
+ template<typename...> class POINTER_TEMPLATE=dumb_pointer,
+ typename... ARGS>
+POINTER_TEMPLATE<CLASS_TEMPLATE<ARGS...>> llmake_heap(ARGS&&... args)
{
- return CLASS_TEMPLATE<ARG1, ARG2>(arg1, arg2);
+ return POINTER_TEMPLATE<CLASS_TEMPLATE<ARGS...>>(
+ new CLASS_TEMPLATE<ARGS...>(std::forward<ARGS>(args)...));
}
#endif /* ! defined(LL_LLMAKE_H) */
diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp
index 75f43a4704..4d73c04d07 100644
--- a/indra/llcommon/llmutex.cpp
+++ b/indra/llcommon/llmutex.cpp
@@ -32,8 +32,7 @@
//============================================================================
LLMutex::LLMutex() :
- mCount(0),
- mLockingThread(NO_THREAD)
+ mCount(0)
{
}
@@ -55,7 +54,7 @@ void LLMutex::lock()
#if MUTEX_DEBUG
// Have to have the lock before we can access the debug info
- U32 id = LLThread::currentID();
+ auto id = LLThread::currentID();
if (mIsLocked[id] != FALSE)
LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL;
mIsLocked[id] = TRUE;
@@ -74,13 +73,13 @@ void LLMutex::unlock()
#if MUTEX_DEBUG
// Access the debug info while we have the lock
- U32 id = LLThread::currentID();
+ auto id = LLThread::currentID();
if (mIsLocked[id] != TRUE)
LL_ERRS() << "Not locked in Thread: " << id << LL_ENDL;
mIsLocked[id] = FALSE;
#endif
- mLockingThread = NO_THREAD;
+ mLockingThread = LLThread::id_t();
mMutex.unlock();
}
@@ -102,7 +101,7 @@ bool LLMutex::isSelfLocked()
return mLockingThread == LLThread::currentID();
}
-U32 LLMutex::lockingThread() const
+LLThread::id_t LLMutex::lockingThread() const
{
return mLockingThread;
}
@@ -122,7 +121,7 @@ bool LLMutex::trylock()
#if MUTEX_DEBUG
// Have to have the lock before we can access the debug info
- U32 id = LLThread::currentID();
+ auto id = LLThread::currentID();
if (mIsLocked[id] != FALSE)
LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL;
mIsLocked[id] = TRUE;
diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h
index f841d7f950..838d7d34c0 100644
--- a/indra/llcommon/llmutex.h
+++ b/indra/llcommon/llmutex.h
@@ -28,20 +28,12 @@
#define LL_LLMUTEX_H
#include "stdtypes.h"
+#include "llthread.h"
#include <boost/noncopyable.hpp>
-#if LL_WINDOWS
-#pragma warning (push)
-#pragma warning (disable:4265)
-#endif
-// 'std::_Pad' : class has virtual functions, but destructor is not virtual
-#include <mutex>
+#include "mutex.h"
#include <condition_variable>
-#if LL_WINDOWS
-#pragma warning (pop)
-#endif
-
//============================================================================
#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
@@ -53,11 +45,6 @@
class LL_COMMON_API LLMutex
{
public:
- typedef enum
- {
- NO_THREAD = 0xFFFFFFFF
- } e_locking_thread;
-
LLMutex();
virtual ~LLMutex();
@@ -66,15 +53,15 @@ public:
void unlock(); // undefined behavior when called on mutex not being held
bool isLocked(); // non-blocking, but does do a lock/unlock so not free
bool isSelfLocked(); //return true if locked in a same thread
- U32 lockingThread() const; //get ID of locking thread
-
+ LLThread::id_t lockingThread() const; //get ID of locking thread
+
protected:
std::mutex mMutex;
mutable U32 mCount;
- mutable U32 mLockingThread;
+ mutable LLThread::id_t mLockingThread;
#if MUTEX_DEBUG
- std::map<U32, BOOL> mIsLocked;
+ std::map<LLThread::id_t, BOOL> mIsLocked;
#endif
};
diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h
index e8f9981437..bae402110a 100644
--- a/indra/llcommon/llpreprocessor.h
+++ b/indra/llcommon/llpreprocessor.h
@@ -232,4 +232,11 @@
#define LL_COMPILE_TIME_MESSAGE(msg)
#endif
+// __FUNCTION__ works on all the platforms we care about, but...
+#if LL_WINDOWS
+#define LL_PRETTY_FUNCTION __FUNCSIG__
+#else
+#define LL_PRETTY_FUNCTION __PRETTY_FUNCTION__
+#endif
+
#endif // not LL_LINDEN_PREPROCESSOR_H
diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp
index 1fa53f322b..23936f0526 100644
--- a/indra/llcommon/llprocess.cpp
+++ b/indra/llcommon/llprocess.cpp
@@ -994,9 +994,9 @@ void LLProcess::handle_status(int reason, int status)
// wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT);
// It's just wrong to call apr_proc_wait() here. The only way APR knows to
// call us with APR_OC_REASON_DEATH is that it's already reaped this child
- // process, so calling suspend() will only produce "huh?" from the OS. We
+ // process, so calling wait() will only produce "huh?" from the OS. We
// must rely on the status param passed in, which unfortunately comes
- // straight from the OS suspend() call, which means we have to decode it by
+ // straight from the OS wait() call, which means we have to decode it by
// hand.
mStatus = interpret_status(status);
LL_INFOS("LLProcess") << getStatusString() << LL_ENDL;
diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h
index fb0411d27b..7e4af6ea66 100644
--- a/indra/llcommon/llrefcount.h
+++ b/indra/llcommon/llrefcount.h
@@ -28,9 +28,10 @@
#include <boost/noncopyable.hpp>
#include <boost/intrusive_ptr.hpp>
-#include "llmutex.h"
#include "llatomic.h"
+class LLMutex;
+
//----------------------------------------------------------------------------
// RefCount objects should generally only be accessed by way of LLPointer<>'s
// see llthread.h for LLThreadSafeRefCount
diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp
index 79934642ae..022a5d4659 100644
--- a/indra/llcommon/llsdserialize.cpp
+++ b/indra/llcommon/llsdserialize.cpp
@@ -66,7 +66,8 @@ const std::string LLSD_NOTATION_HEADER("llsd/notation");
*/
// static
-void LLSDSerialize::serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize type, U32 options)
+void LLSDSerialize::serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize type,
+ LLSDFormatter::EFormatterOptions options)
{
LLPointer<LLSDFormatter> f = NULL;
@@ -174,10 +175,10 @@ bool LLSDSerialize::deserialize(LLSD& sd, std::istream& str, S32 max_bytes)
{
p = new LLSDXMLParser;
}
- else if (header == LLSD_NOTATION_HEADER)
- {
- p = new LLSDNotationParser;
- }
+ else if (header == LLSD_NOTATION_HEADER)
+ {
+ p = new LLSDNotationParser;
+ }
else
{
LL_WARNS() << "deserialize request for unknown ELLSD_Serialize" << LL_ENDL;
@@ -1234,9 +1235,11 @@ bool LLSDBinaryParser::parseString(
/**
* LLSDFormatter
*/
-LLSDFormatter::LLSDFormatter() :
- mBoolAlpha(false)
+LLSDFormatter::LLSDFormatter(bool boolAlpha, const std::string& realFmt, EFormatterOptions options):
+ mOptions(options)
{
+ boolalpha(boolAlpha);
+ realFormat(realFmt);
}
// virtual
@@ -1253,6 +1256,17 @@ void LLSDFormatter::realFormat(const std::string& format)
mRealFormat = format;
}
+S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr) const
+{
+ // pass options captured by constructor
+ return format(data, ostr, mOptions);
+}
+
+S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const
+{
+ return format_impl(data, ostr, options, 0);
+}
+
void LLSDFormatter::formatReal(LLSD::Real real, std::ostream& ostr) const
{
std::string buffer = llformat(mRealFormat.c_str(), real);
@@ -1262,7 +1276,9 @@ void LLSDFormatter::formatReal(LLSD::Real real, std::ostream& ostr) const
/**
* LLSDNotationFormatter
*/
-LLSDNotationFormatter::LLSDNotationFormatter()
+LLSDNotationFormatter::LLSDNotationFormatter(bool boolAlpha, const std::string& realFormat,
+ EFormatterOptions options):
+ LLSDFormatter(boolAlpha, realFormat, options)
{
}
@@ -1278,14 +1294,8 @@ std::string LLSDNotationFormatter::escapeString(const std::string& in)
return ostr.str();
}
-// virtual
-S32 LLSDNotationFormatter::format(const LLSD& data, std::ostream& ostr, U32 options) const
-{
- S32 rv = format_impl(data, ostr, options, 0);
- return rv;
-}
-
-S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr, U32 options, U32 level) const
+S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr,
+ EFormatterOptions options, U32 level) const
{
S32 format_count = 1;
std::string pre;
@@ -1406,21 +1416,33 @@ S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr, U32
{
// *FIX: memory inefficient.
const std::vector<U8>& buffer = data.asBinary();
- ostr << "b(" << buffer.size() << ")\"";
- if(buffer.size())
+ if (options & LLSDFormatter::OPTIONS_PRETTY_BINARY)
{
- if (options & LLSDFormatter::OPTIONS_PRETTY_BINARY)
+ ostr << "b16\"";
+ if (! buffer.empty())
{
std::ios_base::fmtflags old_flags = ostr.flags();
ostr.setf( std::ios::hex, std::ios::basefield );
- ostr << "0x";
+ // It shouldn't strictly matter whether the emitted hex digits
+ // are uppercase; LLSDNotationParser handles either; but as of
+ // 2020-05-13, Python's llbase.llsd requires uppercase hex.
+ ostr << std::uppercase;
+ auto oldfill(ostr.fill('0'));
+ auto oldwidth(ostr.width());
for (int i = 0; i < buffer.size(); i++)
{
- ostr << (int) buffer[i];
+ // have to restate setw() before every conversion
+ ostr << std::setw(2) << (int) buffer[i];
}
+ ostr.width(oldwidth);
+ ostr.fill(oldfill);
ostr.flags(old_flags);
}
- else
+ }
+ else // ! OPTIONS_PRETTY_BINARY
+ {
+ ostr << "b(" << buffer.size() << ")\"";
+ if (! buffer.empty())
{
ostr.write((const char*)&buffer[0], buffer.size());
}
@@ -1437,11 +1459,12 @@ S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr, U32
return format_count;
}
-
/**
* LLSDBinaryFormatter
*/
-LLSDBinaryFormatter::LLSDBinaryFormatter()
+LLSDBinaryFormatter::LLSDBinaryFormatter(bool boolAlpha, const std::string& realFormat,
+ EFormatterOptions options):
+ LLSDFormatter(boolAlpha, realFormat, options)
{
}
@@ -1450,7 +1473,8 @@ LLSDBinaryFormatter::~LLSDBinaryFormatter()
{ }
// virtual
-S32 LLSDBinaryFormatter::format(const LLSD& data, std::ostream& ostr, U32 options) const
+S32 LLSDBinaryFormatter::format_impl(const LLSD& data, std::ostream& ostr,
+ EFormatterOptions options, U32 level) const
{
S32 format_count = 1;
switch(data.type())
@@ -1466,7 +1490,7 @@ S32 LLSDBinaryFormatter::format(const LLSD& data, std::ostream& ostr, U32 option
{
ostr.put('k');
formatString((*iter).first, ostr);
- format_count += format((*iter).second, ostr);
+ format_count += format_impl((*iter).second, ostr, options, level+1);
}
ostr.put('}');
break;
@@ -1481,7 +1505,7 @@ S32 LLSDBinaryFormatter::format(const LLSD& data, std::ostream& ostr, U32 option
LLSD::array_const_iterator end = data.endArray();
for(; iter != end; ++iter)
{
- format_count += format(*iter, ostr);
+ format_count += format_impl(*iter, ostr, options, level+1);
}
ostr.put(']');
break;
diff --git a/indra/llcommon/llsdserialize.h b/indra/llcommon/llsdserialize.h
index fe0f4443ef..d6079fd9fa 100644
--- a/indra/llcommon/llsdserialize.h
+++ b/indra/llcommon/llsdserialize.h
@@ -435,7 +435,8 @@ public:
/**
* @brief Constructor
*/
- LLSDFormatter();
+ LLSDFormatter(bool boolAlpha=false, const std::string& realFormat="",
+ EFormatterOptions options=OPTIONS_PRETTY_BINARY);
/**
* @brief Set the boolean serialization format.
@@ -459,16 +460,38 @@ public:
void realFormat(const std::string& format);
/**
- * @brief Call this method to format an LLSD to a stream.
+ * @brief Call this method to format an LLSD to a stream with options as
+ * set by the constructor.
+ *
+ * @param data The data to write.
+ * @param ostr The destination stream for the data.
+ * @return Returns The number of LLSD objects formatted out
+ */
+ S32 format(const LLSD& data, std::ostream& ostr) const;
+
+ /**
+ * @brief Call this method to format an LLSD to a stream, passing options
+ * explicitly.
*
* @param data The data to write.
* @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
+ * @param options OPTIONS_NONE to emit LLSD::Binary as raw bytes
+ * @return Returns The number of LLSD objects formatted out
*/
- virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const = 0;
+ virtual S32 format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const;
protected:
/**
+ * @brief Implementation to format the data. This is called recursively.
+ *
+ * @param data The data to write.
+ * @param ostr The destination stream for the data.
+ * @return Returns The number of LLSD objects formatted out
+ */
+ virtual S32 format_impl(const LLSD& data, std::ostream& ostr, EFormatterOptions options,
+ U32 level) const = 0;
+
+ /**
* @brief Helper method which appropriately obeys the real format.
*
* @param real The real value to format.
@@ -476,9 +499,9 @@ protected:
*/
void formatReal(LLSD::Real real, std::ostream& ostr) const;
-protected:
bool mBoolAlpha;
std::string mRealFormat;
+ EFormatterOptions mOptions;
};
@@ -498,7 +521,8 @@ public:
/**
* @brief Constructor
*/
- LLSDNotationFormatter();
+ LLSDNotationFormatter(bool boolAlpha=false, const std::string& realFormat="",
+ EFormatterOptions options=OPTIONS_PRETTY_BINARY);
/**
* @brief Helper static method to return a notation escaped string
@@ -512,25 +536,16 @@ public:
*/
static std::string escapeString(const std::string& in);
- /**
- * @brief Call this method to format an LLSD to a stream.
- *
- * @param data The data to write.
- * @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
- */
- virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const;
-
protected:
-
/**
* @brief Implementation to format the data. This is called recursively.
*
* @param data The data to write.
* @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
+ * @return Returns The number of LLSD objects formatted out
*/
- S32 format_impl(const LLSD& data, std::ostream& ostr, U32 options, U32 level) const;
+ S32 format_impl(const LLSD& data, std::ostream& ostr, EFormatterOptions options,
+ U32 level) const override;
};
@@ -550,7 +565,8 @@ public:
/**
* @brief Constructor
*/
- LLSDXMLFormatter();
+ LLSDXMLFormatter(bool boolAlpha=false, const std::string& realFormat="",
+ EFormatterOptions options=OPTIONS_PRETTY_BINARY);
/**
* @brief Helper static method to return an xml escaped string
@@ -565,20 +581,23 @@ public:
*
* @param data The data to write.
* @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
+ * @return Returns The number of LLSD objects formatted out
*/
- virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const;
+ S32 format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const override;
-protected:
+ // also pull down base-class format() method that isn't overridden
+ using LLSDFormatter::format;
+protected:
/**
* @brief Implementation to format the data. This is called recursively.
*
* @param data The data to write.
* @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
+ * @return Returns The number of LLSD objects formatted out
*/
- S32 format_impl(const LLSD& data, std::ostream& ostr, U32 options, U32 level) const;
+ S32 format_impl(const LLSD& data, std::ostream& ostr, EFormatterOptions options,
+ U32 level) const override;
};
@@ -618,18 +637,20 @@ public:
/**
* @brief Constructor
*/
- LLSDBinaryFormatter();
+ LLSDBinaryFormatter(bool boolAlpha=false, const std::string& realFormat="",
+ EFormatterOptions options=OPTIONS_PRETTY_BINARY);
+protected:
/**
- * @brief Call this method to format an LLSD to a stream.
+ * @brief Implementation to format the data. This is called recursively.
*
* @param data The data to write.
* @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
+ * @return Returns The number of LLSD objects formatted out
*/
- virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const;
+ S32 format_impl(const LLSD& data, std::ostream& ostr, EFormatterOptions options,
+ U32 level) const override;
-protected:
/**
* @brief Helper method to serialize strings
*
@@ -669,7 +690,8 @@ public:
/**
* @brief Constructor
*/
- LLSDOStreamer(const LLSD& data, U32 options = LLSDFormatter::OPTIONS_NONE) :
+ LLSDOStreamer(const LLSD& data,
+ LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY) :
mSD(data), mOptions(options) {}
/**
@@ -681,17 +703,17 @@ public:
* @return Returns the stream passed in after streaming mSD.
*/
friend std::ostream& operator<<(
- std::ostream& str,
- const LLSDOStreamer<Formatter>& formatter)
+ std::ostream& out,
+ const LLSDOStreamer<Formatter>& streamer)
{
LLPointer<Formatter> f = new Formatter;
- f->format(formatter.mSD, str, formatter.mOptions);
- return str;
+ f->format(streamer.mSD, out, streamer.mOptions);
+ return out;
}
protected:
LLSD mSD;
- U32 mOptions;
+ LLSDFormatter::EFormatterOptions mOptions;
};
typedef LLSDOStreamer<LLSDNotationFormatter> LLSDNotationStreamer;
@@ -724,7 +746,7 @@ public:
* Generic in/outs
*/
static void serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize,
- U32 options = LLSDFormatter::OPTIONS_NONE);
+ LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY);
/**
* @brief Examine a stream, and parse 1 sd object out based on contents.
@@ -752,9 +774,9 @@ public:
static S32 toPrettyBinaryNotation(const LLSD& sd, std::ostream& str)
{
LLPointer<LLSDNotationFormatter> f = new LLSDNotationFormatter;
- return f->format(sd, str,
- LLSDFormatter::OPTIONS_PRETTY |
- LLSDFormatter::OPTIONS_PRETTY_BINARY);
+ return f->format(sd, str,
+ LLSDFormatter::EFormatterOptions(LLSDFormatter::OPTIONS_PRETTY |
+ LLSDFormatter::OPTIONS_PRETTY_BINARY));
}
static S32 fromNotation(LLSD& sd, std::istream& str, S32 max_bytes)
{
diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp
index 6d0fe862b9..0da824d694 100644
--- a/indra/llcommon/llsdserialize_xml.cpp
+++ b/indra/llcommon/llsdserialize_xml.cpp
@@ -45,7 +45,9 @@ extern "C"
/**
* LLSDXMLFormatter
*/
-LLSDXMLFormatter::LLSDXMLFormatter()
+LLSDXMLFormatter::LLSDXMLFormatter(bool boolAlpha, const std::string& realFormat,
+ EFormatterOptions options):
+ LLSDFormatter(boolAlpha, realFormat, options)
{
}
@@ -55,7 +57,8 @@ LLSDXMLFormatter::~LLSDXMLFormatter()
}
// virtual
-S32 LLSDXMLFormatter::format(const LLSD& data, std::ostream& ostr, U32 options) const
+S32 LLSDXMLFormatter::format(const LLSD& data, std::ostream& ostr,
+ EFormatterOptions options) const
{
std::streamsize old_precision = ostr.precision(25);
@@ -72,7 +75,8 @@ S32 LLSDXMLFormatter::format(const LLSD& data, std::ostream& ostr, U32 options)
return rv;
}
-S32 LLSDXMLFormatter::format_impl(const LLSD& data, std::ostream& ostr, U32 options, U32 level) const
+S32 LLSDXMLFormatter::format_impl(const LLSD& data, std::ostream& ostr,
+ EFormatterOptions options, U32 level) const
{
S32 format_count = 1;
std::string pre;
diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp
index 1f8384f415..3f3edb661f 100644
--- a/indra/llcommon/llsdutil.cpp
+++ b/indra/llcommon/llsdutil.cpp
@@ -856,6 +856,74 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits)
}
}
+/*****************************************************************************
+* llsd::drill()
+*****************************************************************************/
+namespace llsd
+{
+
+LLSD& drill(LLSD& blob, const LLSD& rawPath)
+{
+ // Treat rawPath uniformly as an array. If it's not already an array,
+ // store it as the only entry in one. (But let's say Undefined means an
+ // empty array.)
+ LLSD path;
+ if (rawPath.isArray() || rawPath.isUndefined())
+ {
+ path = rawPath;
+ }
+ else
+ {
+ path.append(rawPath);
+ }
+
+ // Need to indicate a current destination -- but that current destination
+ // must change as we step through the path array. Where normally we'd use
+ // an LLSD& to capture a subscripted LLSD lvalue, this time we must
+ // instead use a pointer -- since it must be reassigned.
+ // Start by pointing to the input blob exactly as is.
+ LLSD* located{&blob};
+
+ // Extract the element of interest by walking path. Use an explicit index
+ // so that, in case of a bogus type in path, we can identify the specific
+ // path entry that's bad.
+ for (LLSD::Integer i = 0; i < path.size(); ++i)
+ {
+ const LLSD& key{path[i]};
+ if (key.isString())
+ {
+ // a string path element is a map key
+ located = &((*located)[key.asString()]);
+ }
+ else if (key.isInteger())
+ {
+ // an integer path element is an array index
+ located = &((*located)[key.asInteger()]);
+ }
+ else
+ {
+ // What do we do with Real or Array or Map or ...?
+ // As it's a coder error -- not a user error -- rub the coder's
+ // face in it so it gets fixed.
+ LL_ERRS("llsdutil") << "drill(" << blob << ", " << rawPath
+ << "): path[" << i << "] bad type "
+ << sTypes.lookup(key.type()) << LL_ENDL;
+ }
+ }
+
+ // dereference the pointer to return a reference to the element we found
+ return *located;
+}
+
+LLSD drill(const LLSD& blob, const LLSD& path)
+{
+ // non-const drill() does exactly what we want. Temporarily cast away
+ // const-ness and use that.
+ return drill(const_cast<LLSD&>(blob), path);
+}
+
+} // namespace llsd
+
// Construct a deep partial clone of of an LLSD object. primitive types share
// references, however maps, arrays and binary objects are duplicated. An optional
// filter may be include to exclude/include keys in a map.
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h
index 863be04c8a..84be95ba54 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -143,6 +143,16 @@ LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data,
/// equality rather than bitwise equality, pass @a bits as for
/// is_approx_equal_fraction().
LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits=-1);
+/// If you don't care about LLSD::Real equality
+inline bool operator==(const LLSD& lhs, const LLSD& rhs)
+{
+ return llsd_equals(lhs, rhs);
+}
+inline bool operator!=(const LLSD& lhs, const LLSD& rhs)
+{
+ // operator!=() should always be the negation of operator==()
+ return ! (lhs == rhs);
+}
// Simple function to copy data out of input & output iterators if
// there is no need for casting.
@@ -156,6 +166,31 @@ template<typename Input> LLSD llsd_copy_array(Input iter, Input end)
return dest;
}
+namespace llsd
+{
+
+/**
+ * Drill down to locate an element in 'blob' according to 'path', where 'path'
+ * is one of the following:
+ *
+ * - LLSD::String: 'blob' is an LLSD::Map. Find the entry with key 'path'.
+ * - LLSD::Integer: 'blob' is an LLSD::Array. Find the entry with index 'path'.
+ * - Any other 'path' type will be interpreted as LLSD::Array, and 'blob' is a
+ * nested structure. For each element of 'path':
+ * - If it's an LLSD::Integer, select the entry with that index from an
+ * LLSD::Array at that level.
+ * - If it's an LLSD::String, select the entry with that key from an
+ * LLSD::Map at that level.
+ * - Anything else is an error.
+ *
+ * By implication, if path.isUndefined() or otherwise equivalent to an empty
+ * LLSD::Array, drill() returns 'blob' as is.
+ */
+LLSD drill(const LLSD& blob, const LLSD& path);
+LLSD& drill( LLSD& blob, const LLSD& path);
+
+}
+
/*****************************************************************************
* LLSDArray
*****************************************************************************/
@@ -225,6 +260,36 @@ private:
LLSD _data;
};
+namespace llsd
+{
+
+/**
+ * Construct an LLSD::Array inline, using modern C++ variadic arguments.
+ */
+
+// recursion tail
+inline
+void array_(LLSD&) {}
+
+// recursive call
+template <typename T0, typename... Ts>
+void array_(LLSD& data, T0&& v0, Ts&&... vs)
+{
+ data.append(std::forward<T0>(v0));
+ array_(data, std::forward<Ts>(vs)...);
+}
+
+// public interface
+template <typename... Ts>
+LLSD array(Ts&&... vs)
+{
+ LLSD data;
+ array_(data, std::forward<Ts>(vs)...);
+ return data;
+}
+
+} // namespace llsd
+
/*****************************************************************************
* LLSDMap
*****************************************************************************/
@@ -269,6 +334,36 @@ private:
LLSD _data;
};
+namespace llsd
+{
+
+/**
+ * Construct an LLSD::Map inline, using modern C++ variadic arguments.
+ */
+
+// recursion tail
+inline
+void map_(LLSD&) {}
+
+// recursive call
+template <typename T0, typename... Ts>
+void map_(LLSD& data, const LLSD::String& k0, T0&& v0, Ts&&... vs)
+{
+ data[k0] = v0;
+ map_(data, std::forward<Ts>(vs)...);
+}
+
+// public interface
+template <typename... Ts>
+LLSD map(Ts&&... vs)
+{
+ LLSD data;
+ map_(data, std::forward<Ts>(vs)...);
+ return data;
+}
+
+} // namespace llsd
+
/*****************************************************************************
* LLSDParam
*****************************************************************************/
@@ -452,6 +547,16 @@ LLSD llsd_clone(LLSD value, LLSD filter = LLSD());
// the filter parameter.
LLSD llsd_shallow(LLSD value, LLSD filter = LLSD());
+namespace llsd
+{
+
+// llsd namespace aliases
+inline
+LLSD clone (LLSD value, LLSD filter=LLSD()) { return llsd_clone (value, filter); }
+inline
+LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter); }
+
+} // namespace llsd
// Specialization for generating a hash value from an LLSD block.
template <>
diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp
index c45c144570..d3d25201b2 100644
--- a/indra/llcommon/llsingleton.cpp
+++ b/indra/llcommon/llsingleton.cpp
@@ -30,9 +30,9 @@
#include "llerror.h"
#include "llerrorcontrol.h" // LLError::is_available()
#include "lldependencies.h"
-#include "llcoro_get_id.h"
+#include "llexception.h"
+#include "llcoros.h"
#include <boost/foreach.hpp>
-#include <boost/unordered_map.hpp>
#include <algorithm>
#include <iostream> // std::cerr in dire emergency
#include <sstream>
@@ -42,8 +42,6 @@ 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
@@ -57,63 +55,131 @@ bool oktolog();
class LLSingletonBase::MasterList:
public LLSingleton<LLSingletonBase::MasterList>
{
+private:
LLSINGLETON_EMPTY_CTOR(MasterList);
-public:
- // No need to make this private with accessors; nobody outside this source
- // file can see it.
+ // Independently of the LLSingleton locks governing construction,
+ // destruction and other state changes of the MasterList instance itself,
+ // we must also defend each of the data structures owned by the
+ // MasterList.
+ // This must be a recursive_mutex because, while the lock is held for
+ // manipulating some data in the master list, we must also check whether
+ // it's safe to log -- which involves querying a different LLSingleton --
+ // which requires accessing the master list.
+ typedef std::recursive_mutex mutex_t;
+ typedef std::unique_lock<mutex_t> lock_t;
+ mutex_t mMutex;
+
+public:
+ // Instantiate this to both obtain a reference to MasterList::instance()
+ // and lock its mutex for the lifespan of this Lock instance.
+ class Lock
+ {
+ public:
+ Lock():
+ mMasterList(MasterList::instance()),
+ mLock(mMasterList.mMutex)
+ {}
+ Lock(const Lock&) = delete;
+ Lock& operator=(const Lock&) = delete;
+ MasterList& get() const { return mMasterList; }
+ operator MasterList&() const { return get(); }
+
+ protected:
+ MasterList& mMasterList;
+ MasterList::lock_t mLock;
+ };
+
+private:
// 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;
+ list_t mMaster;
+
+public:
+ // Instantiate this to obtain a reference to MasterList::mMaster and to
+ // hold the MasterList lock for the lifespan of this LockedMaster
+ // instance.
+ struct LockedMaster: public Lock
+ {
+ list_t& get() const { return mMasterList.mMaster; }
+ operator list_t&() const { return get(); }
+ };
+private:
// 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()
+ // stack for every running coroutine. Therefore this stack must be based
+ // on a coroutine-local pointer.
+ // This local_ptr isn't static because it's a member of an LLSingleton.
+ LLCoros::local_ptr<list_t> mInitializing;
+
+public:
+ // Instantiate this to obtain a reference to the coroutine-specific
+ // initializing list and to hold the MasterList lock for the lifespan of
+ // this LockedInitializing instance.
+ struct LockedInitializing: public Lock
+ {
+ public:
+ LockedInitializing():
+ // only do the lookup once, cache the result
+ // note that the lock is already locked during this lookup
+ mList(&mMasterList.get_initializing_())
+ {}
+ list_t& get() const
+ {
+ if (! mList)
+ {
+ LLTHROW(LLException("Trying to use LockedInitializing "
+ "after cleanup_initializing()"));
+ }
+ return *mList;
+ }
+ operator list_t&() const { return get(); }
+ void log(const char* verb, const char* name);
+ void cleanup_initializing()
+ {
+ mMasterList.cleanup_initializing_();
+ mList = nullptr;
+ }
+
+ private:
+ // Store pointer since cleanup_initializing() must clear it.
+ list_t* mList;
+ };
+
+private:
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()];
+ LLSingletonBase::list_t* current = mInitializing.get();
+ if (! current)
+ {
+ // If the running coroutine doesn't already have an initializing
+ // stack, allocate a new one and save it for future reference.
+ current = new LLSingletonBase::list_t();
+ mInitializing.reset(current);
+ }
+ return *current;
}
+ // By the time mInitializing is destroyed, its value for every coroutine
+ // except the running one must have been reset() to nullptr. So every time
+ // we pop the list to empty, reset() the running coroutine's local_ptr.
void cleanup_initializing_()
{
- InitializingMap::iterator found = mInitializing.find(llcoro::get_id());
- if (found != mInitializing.end())
- {
- mInitializing.erase(found);
- }
+ mInitializing.reset(nullptr);
}
};
-//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);
+ // This temporary LockedMaster should suffice to hold the MasterList lock
+ // during the push_back() call.
+ MasterList::LockedMaster().get().push_back(this);
}
void LLSingletonBase::remove_master()
@@ -125,27 +191,32 @@ void LLSingletonBase::remove_master()
// 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);
+ // This temporary LockedMaster should suffice to hold the MasterList lock
+ // during the remove() call.
+ MasterList::LockedMaster().get().remove(this);
}
//static
-LLSingletonBase::list_t& LLSingletonBase::get_initializing()
+LLSingletonBase::list_t::size_type LLSingletonBase::get_initializing_size()
{
- return LLSingletonBase::MasterList::instance().get_initializing_();
+ return MasterList::LockedInitializing().get().size();
}
LLSingletonBase::~LLSingletonBase() {}
void LLSingletonBase::push_initializing(const char* name)
{
+ MasterList::LockedInitializing locked_list;
// log BEFORE pushing so logging singletons don't cry circularity
- log_initializing("Pushing", name);
- get_initializing().push_back(this);
+ locked_list.log("Pushing", name);
+ locked_list.get().push_back(this);
}
void LLSingletonBase::pop_initializing()
{
- list_t& list(get_initializing());
+ // Lock the MasterList for the duration of this call
+ MasterList::LockedInitializing locked_list;
+ list_t& list(locked_list.get());
if (list.empty())
{
@@ -165,7 +236,7 @@ void LLSingletonBase::pop_initializing()
// entirely.
if (list.empty())
{
- MasterList::instance().cleanup_initializing_();
+ locked_list.cleanup_initializing();
}
// Now validate the newly-popped LLSingleton.
@@ -177,7 +248,7 @@ void LLSingletonBase::pop_initializing()
}
// log AFTER popping so logging singletons don't cry circularity
- log_initializing("Popping", typeid(*back).name());
+ locked_list.log("Popping", typeid(*back).name());
}
void LLSingletonBase::reset_initializing(list_t::size_type size)
@@ -191,7 +262,8 @@ void LLSingletonBase::reset_initializing(list_t::size_type size)
// push_initializing() call in LLSingletonBase's constructor. So only
// remove the stack top if in fact we've pushed something more than the
// previous size.
- list_t& list(get_initializing());
+ MasterList::LockedInitializing locked_list;
+ list_t& list(locked_list.get());
while (list.size() > size)
{
@@ -201,29 +273,32 @@ void LLSingletonBase::reset_initializing(list_t::size_type size)
// as in pop_initializing()
if (list.empty())
{
- MasterList::instance().cleanup_initializing_();
+ locked_list.cleanup_initializing();
}
}
-//static
-void LLSingletonBase::log_initializing(const char* verb, const char* name)
+void LLSingletonBase::MasterList::LockedInitializing::log(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)
+ if (mList)
{
- LLSingletonBase* sb(*ri);
- LL_CONT << ' ' << classname(sb);
+ for (list_t::const_reverse_iterator ri(mList->rbegin()), rend(mList->rend());
+ ri != rend; ++ri)
+ {
+ LLSingletonBase* sb(*ri);
+ LL_CONT << ' ' << classname(sb);
+ }
}
LL_ENDL;
}
}
-void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initState)
+void LLSingletonBase::capture_dependency()
{
+ MasterList::LockedInitializing locked_list;
+ list_t& initializing(locked_list.get());
// 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
@@ -252,21 +327,8 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt
LLSingletonBase* foundp(*found);
out << classname(foundp) << " -> ";
}
- // 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(),
- classname(this).c_str(), "");
- }
- else if (it_next == initializing.end())
+ if (it_next == initializing.end())
{
// Points to self after construction, but during initialization.
// Singletons can initialize other classes that depend onto them,
@@ -309,12 +371,12 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort()
// 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().
+ // should happen basically once: for deleteAll().
typedef LLDependencies<LLSingletonBase*> SingletonDeps;
SingletonDeps sdeps;
- list_t& master(get_master());
- BOOST_FOREACH(LLSingletonBase* sp, master)
+ // Lock while traversing the master list
+ MasterList::LockedMaster master;
+ for (LLSingletonBase* sp : master.get())
{
// Build the SingletonDeps structure by adding, for each
// LLSingletonBase* sp in the master list, sp itself. It has no
@@ -326,51 +388,32 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort()
SingletonDeps::KeyList(sp->mDepends.begin(), sp->mDepends.end()));
}
vec_t ret;
- ret.reserve(master.size());
+ ret.reserve(master.get().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())
+ for (const 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());
+ ret.push_back(&master.Lock::get());
return ret;
}
-//static
-void LLSingletonBase::cleanupAll()
+void LLSingletonBase::cleanup_()
{
- // It's essential to traverse these in dependency order.
- BOOST_FOREACH(LLSingletonBase* sp, dep_sort())
+ logdebugs("calling ", classname(this).c_str(), "::cleanupSingleton()");
+ try
{
- // Call cleanupSingleton() only if we haven't already done so for this
- // instance.
- if (! sp->mCleaned)
- {
- sp->mCleaned = true;
-
- logdebugs("calling ",
- classname(sp).c_str(), "::cleanupSingleton()");
- try
- {
- sp->cleanupSingleton();
- }
- catch (const std::exception& e)
- {
- logwarns("Exception in ", classname(sp).c_str(),
- "::cleanupSingleton(): ", e.what());
- }
- catch (...)
- {
- logwarns("Unknown exception in ", classname(sp).c_str(),
- "::cleanupSingleton()");
- }
- }
+ cleanupSingleton();
+ }
+ catch (...)
+ {
+ LOG_UNHANDLED_EXCEPTION(classname(this) + "::cleanupSingleton()");
}
}
@@ -441,10 +484,6 @@ void log(LLError::ELevel level,
}
}
-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
@@ -454,6 +493,18 @@ void LLSingletonBase::logwarns(const char* p1, const char* p2, const char* p3, c
}
//static
+void LLSingletonBase::loginfos(const char* p1, const char* p2, const char* p3, const char* p4)
+{
+ log(LLError::LEVEL_INFO, p1, p2, p3, p4);
+}
+
+//static
+void LLSingletonBase::logdebugs(const char* p1, const char* p2, const char* p3, const char* p4)
+{
+ log(LLError::LEVEL_DEBUG, 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);
diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h
index 7def9b019c..30a5b21cf8 100644
--- a/indra/llcommon/llsingleton.h
+++ b/indra/llcommon/llsingleton.h
@@ -30,18 +30,10 @@
#include <list>
#include <vector>
#include <typeinfo>
-
-#if LL_WINDOWS
-#pragma warning (push)
-#pragma warning (disable:4265)
-#endif
-// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual
-
-#include <mutex>
-
-#if LL_WINDOWS
-#pragma warning (pop)
-#endif
+#include "mutex.h"
+#include "lockstatic.h"
+#include "llthread.h" // on_main_thread()
+#include "llmainthreadtask.h"
class LLSingletonBase: private boost::noncopyable
{
@@ -51,15 +43,13 @@ public:
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();
+ // Size of stack whose top indicates the LLSingleton currently being
+ // initialized.
+ static list_t::size_type get_initializing_size();
// 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;
@@ -68,8 +58,8 @@ protected:
typedef enum e_init_state
{
UNINITIALIZED = 0, // must be default-initialized state
+ QUEUED, // construction queued, not yet executing
CONSTRUCTING, // within DERIVED_TYPE constructor
- CONSTRUCTED, // finished DERIVED_TYPE constructor
INITIALIZING, // within DERIVED_TYPE::initSingleton()
INITIALIZED, // normal case
DELETED // deleteSingleton() or deleteAll() called
@@ -115,21 +105,23 @@ protected:
// Remove 'this' from the init stack in case of exception in the
// LLSingleton subclass constructor.
static void reset_initializing(list_t::size_type size);
-private:
- // logging
- static void log_initializing(const char* verb, const char* name);
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);
+ void capture_dependency();
- // delegate LL_ERRS() logging to llsingleton.cpp
+ // delegate logging calls 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 void loginfos(const char* p1, const char* p2="",
+ const char* p3="", const char* p4="");
+ static void logdebugs(const char* p1, const char* p2="",
+ const char* p3="", const char* p4="");
static std::string demangle(const char* mangled);
+ // these classname() declarations restate template functions declared in
+ // llerror.h because we avoid #including that here
template <typename T>
static std::string classname() { return demangle(typeid(T).name()); }
template <typename T>
@@ -139,6 +131,9 @@ protected:
virtual void initSingleton() {}
virtual void cleanupSingleton() {}
+ // internal wrapper around calls to cleanupSingleton()
+ void cleanup_();
+
// 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
@@ -148,32 +143,15 @@ protected:
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.
+ * deleteAll() calls the cleanupSingleton() and deleteSingleton() methods
+ * 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, which is called implicitly by
+ * deleteSingleton().
*
* The most important property of deleteAll() is that deleteSingleton()
* methods are called in dependency order, leaf classes last. Thus, given
@@ -181,9 +159,9 @@ public:
* 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.
+ * If a cleanupSingleton() or deleteSingleton() method throws an
+ * exception, the exception is logged, but deleteAll() attempts to
+ * continue calling the rest of the deleteSingleton() methods.
*/
static void deleteAll();
};
@@ -203,9 +181,16 @@ struct LLSingleton_manage_master
{
LLSingletonBase::reset_initializing(size);
}
- // For any LLSingleton subclass except the MasterList, obtain the init
- // stack from the MasterList singleton instance.
- LLSingletonBase::list_t& get_initializing() { return LLSingletonBase::get_initializing(); }
+ // For any LLSingleton subclass except the MasterList, obtain the size of
+ // the init stack from the MasterList singleton instance.
+ LLSingletonBase::list_t::size_type get_initializing_size()
+ {
+ return LLSingletonBase::get_initializing_size();
+ }
+ void capture_dependency(LLSingletonBase* sb)
+ {
+ sb->capture_dependency();
+ }
};
// But for the specific case of LLSingletonBase::MasterList, don't.
@@ -218,20 +203,14 @@ struct LLSingleton_manage_master<LLSingletonBase::MasterList>
void pop_initializing (LLSingletonBase*) {}
// since we never pushed, no need to clean up
void reset_initializing(LLSingletonBase::list_t::size_type size) {}
- LLSingletonBase::list_t& get_initializing()
- {
- // The MasterList shouldn't depend on any other LLSingletons. We'd
- // get into trouble if we tried to recursively engage that machinery.
- static LLSingletonBase::list_t sDummyList;
- return sDummyList;
- }
+ LLSingletonBase::list_t::size_type get_initializing_size() { return 0; }
+ void capture_dependency(LLSingletonBase*) {}
};
// Now we can implement LLSingletonBase's template constructor.
template <typename DERIVED_TYPE>
LLSingletonBase::LLSingletonBase(tag<DERIVED_TYPE>):
- mCleaned(false),
- mDeleteSingleton(NULL)
+ mDeleteSingleton(nullptr)
{
// This is the earliest possible point at which we can push this new
// instance onto the init stack. LLSingleton::constructSingleton() can't
@@ -273,10 +252,19 @@ class LLParamSingleton;
* 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.
+ * If you override LLSingleton<T>::cleanupSingleton(), your method will
+ * implicitly be called by LLSingleton<T>::deleteSingleton() just before the
+ * instance is destroyed. We introduce a special cleanupSingleton() method
+ * because cleanupSingleton() operations can involve nontrivial realtime, or
+ * throw an exception. A destructor should do neither!
+ *
+ * If your cleanupSingleton() method throws an exception, we log that
+ * exception but carry on.
+ *
+ * If at some point you call LLSingletonBase::deleteAll(), all remaining
+ * LLSingleton<T> instances will be destroyed in reverse dependency order. (Or
+ * call MySubclass::deleteSingleton() to specifically destroy the canonical
+ * MySubclass instance.)
*
* 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
@@ -284,33 +272,34 @@ class LLParamSingleton;
* 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().
+ * We promise that if you later call LLSingletonBase::deleteAll():
+ * 1. A::deleteSingleton() will be called before
+ * 2. B::deleteSingleton(), which will be called before
+ * 3. C::deleteSingleton().
* 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:
+ // LLSingleton<DERIVED_TYPE> must have a distinct instance of
+ // SingletonData for every distinct DERIVED_TYPE. It's tempting to
+ // consider hoisting SingletonData up into LLSingletonBase. Don't do it.
+ struct SingletonData
+ {
+ // Use a recursive_mutex in case of constructor circularity. With a
+ // non-recursive mutex, that would result in deadlock.
+ typedef std::recursive_mutex mutex_t;
+ mutex_t mMutex; // LockStatic looks for mMutex
+
+ EInitState mInitState{UNINITIALIZED};
+ DERIVED_TYPE* mInstance{nullptr};
+ };
+ typedef llthread::LockStatic<SingletonData> LockStatic;
+
// Allow LLParamSingleton subclass -- but NOT DERIVED_TYPE itself -- to
// access our private members.
friend class LLParamSingleton<DERIVED_TYPE>;
@@ -319,17 +308,17 @@ private:
// purpose for its subclass LLParamSingleton is to support Singletons
// requiring constructor arguments. constructSingleton() supports both use
// cases.
+ // Accepting LockStatic& requires that the caller has already locked our
+ // static data before calling.
template <typename... Args>
- static void constructSingleton(Args&&... args)
+ static void constructSingleton(LockStatic& lk, Args&&... args)
{
- auto prev_size = LLSingleton_manage_master<DERIVED_TYPE>().get_initializing().size();
- // getInstance() calls are from within constructor
- sData.mInitState = CONSTRUCTING;
+ auto prev_size = LLSingleton_manage_master<DERIVED_TYPE>().get_initializing_size();
+ // Any getInstance() calls after this point are from within constructor
+ lk->mInitState = CONSTRUCTING;
try
{
- sData.mInstance = new DERIVED_TYPE(std::forward<Args>(args)...);
- // we have called constructor, have not yet called initSingleton()
- sData.mInitState = CONSTRUCTED;
+ lk->mInstance = new DERIVED_TYPE(std::forward<Args>(args)...);
}
catch (const std::exception& err)
{
@@ -343,62 +332,56 @@ private:
// There isn't a separate EInitState value meaning "we attempted
// to construct this LLSingleton subclass but could not," so use
// DELETED. That seems slightly more appropriate than UNINITIALIZED.
- sData.mInitState = DELETED;
+ lk->mInitState = DELETED;
// propagate the exception
throw;
}
- }
- static void finishInitializing()
- {
- // getInstance() calls are from within initSingleton()
- sData.mInitState = INITIALIZING;
+ // Any getInstance() calls after this point are from within initSingleton()
+ lk->mInitState = INITIALIZING;
try
{
// initialize singleton after constructing it so that it can
// reference other singletons which in turn depend on it, thus
// breaking cyclic dependencies
- sData.mInstance->initSingleton();
- sData.mInitState = INITIALIZED;
+ lk->mInstance->initSingleton();
+ lk->mInitState = INITIALIZED;
// pop this off stack of initializing singletons
- pop_initializing();
+ pop_initializing(lk->mInstance);
}
catch (const std::exception& err)
{
// pop this off stack of initializing singletons here, too --
// BEFORE logging, so log-machinery LLSingletons don't record a
// dependency on DERIVED_TYPE!
- pop_initializing();
+ pop_initializing(lk->mInstance);
logwarns("Error in ", classname<DERIVED_TYPE>().c_str(),
"::initSingleton(): ", err.what());
- // and get rid of the instance entirely
+ // Get rid of the instance entirely. This call depends on our
+ // recursive_mutex. We could have a deleteSingleton(LockStatic&)
+ // overload and pass lk, but we don't strictly need it.
deleteSingleton();
// propagate the exception
throw;
}
}
- static void pop_initializing()
+ static void pop_initializing(LLSingletonBase* sb)
{
// route through LLSingleton_manage_master so we Do The Right Thing
// (namely, nothing) for MasterList
- LLSingleton_manage_master<DERIVED_TYPE>().pop_initializing(sData.mInstance);
+ LLSingleton_manage_master<DERIVED_TYPE>().pop_initializing(sb);
}
- // Without this 'using' declaration, the static method we're declaring
- // here would hide the base-class method we want it to call.
- using LLSingletonBase::capture_dependency;
- static void capture_dependency()
+ static void capture_dependency(LLSingletonBase* sb)
{
// By this point, if DERIVED_TYPE was pushed onto the initializing
// stack, it has been popped off. So the top of that stack, if any, is
// an LLSingleton that directly depends on DERIVED_TYPE. If
// getInstance() was called by another LLSingleton, rather than from
// vanilla application code, record the dependency.
- sData.mInstance->capture_dependency(
- LLSingleton_manage_master<DERIVED_TYPE>().get_initializing(),
- sData.mInitState);
+ LLSingleton_manage_master<DERIVED_TYPE>().capture_dependency(sb);
}
// We know of no way to instruct the compiler that every subclass
@@ -411,20 +394,6 @@ private:
// subclass body.
virtual void you_must_use_LLSINGLETON_macro() = 0;
- // The purpose of this struct is to engage the C++11 guarantee that static
- // variables declared in function scope are initialized exactly once, even
- // if multiple threads concurrently reach the same declaration.
- // https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
- // Since getInstance() declares a static instance of SingletonInitializer,
- // only the first call to getInstance() calls constructSingleton().
- struct SingletonInitializer
- {
- SingletonInitializer()
- {
- constructSingleton();
- }
- };
-
protected:
// Pass DERIVED_TYPE explicitly to LLSingletonBase's constructor because,
// until our subclass constructor completes, *this isn't yet a
@@ -439,97 +408,176 @@ protected:
LLSingleton_manage_master<DERIVED_TYPE>().add(this);
}
-public:
+protected:
virtual ~LLSingleton()
{
- // remove this instance from the master list
+ // This phase of cleanup is performed in the destructor rather than in
+ // deleteSingleton() to defend against manual deletion. When we moved
+ // cleanup to deleteSingleton(), we hit crashes due to dangling
+ // pointers in the MasterList.
+ LockStatic lk;
+ lk->mInstance = nullptr;
+ lk->mInitState = DELETED;
+
+ // Remove this instance from the master list.
LLSingleton_manage_master<DERIVED_TYPE>().remove(this);
- sData.mInstance = NULL;
- sData.mInitState = DELETED;
}
+public:
/**
- * @brief Immediately delete the singleton.
+ * @brief Cleanup and destroy the singleton instance.
*
- * A subsequent call to LLProxy::getInstance() will construct a new
- * instance of the class.
+ * deleteSingleton() calls this instance's cleanupSingleton() method and
+ * then destroys the instance.
*
- * 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.
+ * A subsequent call to LLSingleton<T>::getInstance() will construct a new
+ * instance of the class.
*
- * 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.
+ * Without an explicit call to LLSingletonBase::deleteAll(), or
+ * LLSingleton<T>::deleteSingleton(), LLSingleton instances are simply
+ * leaked. (Allowing implicit destruction at shutdown caused too many
+ * problems.)
*/
static void deleteSingleton()
{
- delete sData.mInstance;
- // SingletonData state handled by destructor, above
+ // Hold the lock while we call cleanupSingleton() and the destructor.
+ // Our destructor also instantiates LockStatic, requiring a recursive
+ // mutex.
+ LockStatic lk;
+ // of course, only cleanup and delete if there's something there
+ if (lk->mInstance)
+ {
+ lk->mInstance->cleanup_();
+ delete lk->mInstance;
+ // destructor clears mInstance (and mInitState)
+ }
}
static DERIVED_TYPE* getInstance()
{
- // call constructSingleton() only the first time we get here
- static SingletonInitializer sInitializer;
-
- switch (sData.mInitState)
- {
- case UNINITIALIZED:
- // should never be uninitialized at this point
- logerrs("Uninitialized singleton ",
- classname<DERIVED_TYPE>().c_str());
- return NULL;
-
- case CONSTRUCTING:
- // here if DERIVED_TYPE's constructor (directly or indirectly)
- // calls DERIVED_TYPE::getInstance()
- logerrs("Tried to access singleton ",
- classname<DERIVED_TYPE>().c_str(),
- " from singleton constructor!");
- return NULL;
-
- case CONSTRUCTED:
- // first time through: set to CONSTRUCTED by
- // constructSingleton(), called by sInitializer's constructor;
- // still have to call initSingleton()
- finishInitializing();
- break;
-
- case INITIALIZING:
- // here if DERIVED_TYPE::initSingleton() (directly or indirectly)
- // calls DERIVED_TYPE::getInstance(): go ahead and allow it
- case INITIALIZED:
- // normal subsequent calls
- break;
-
- case DELETED:
- // called after deleteSingleton()
- logwarns("Trying to access deleted singleton ",
- classname<DERIVED_TYPE>().c_str(),
- " -- creating new instance");
- // This recovery sequence is NOT thread-safe! We would need a
- // recursive_mutex a la LLParamSingleton.
- constructSingleton();
- finishInitializing();
- break;
- }
-
- // record the dependency, if any: check if we got here from another
- // LLSingleton's constructor or initSingleton() method
- capture_dependency();
- return sData.mInstance;
+ // We know the viewer has LLSingleton dependency circularities. If you
+ // feel strongly motivated to eliminate them, cheers and good luck.
+ // (At that point we could consider a much simpler locking mechanism.)
+
+ // If A and B depend on each other, and thread T1 requests A at the
+ // same moment thread T2 requests B, you could get a sequence like this:
+ // - T1 locks A
+ // - T2 locks B
+ // - T1, having constructed A, calls A::initSingleton(), which calls
+ // B::getInstance() and blocks on B's lock
+ // - T2, having constructed B, calls B::initSingleton(), which calls
+ // A::getInstance() and blocks on A's lock
+ // In other words, classic deadlock.
+
+ // Avoid that by constructing and initializing every LLSingleton on
+ // the main thread. In that scenario:
+ // - T1 locks A
+ // - T2 locks B
+ // - T1 discovers A is UNINITIALIZED, so it queues a task for the main
+ // thread, unlocks A and blocks on the std::future.
+ // - T2 discovers B is UNINITIALIZED, so it queues a task for the main
+ // thread, unlocks B and blocks on the std::future.
+ // - The main thread executes T1's request for A. It locks A and
+ // starts to construct it.
+ // - A::initSingleton() calls B::getInstance(). Fine: nobody's holding
+ // B's lock.
+ // - The main thread locks B, constructs B, calls B::initSingleton(),
+ // which calls A::getInstance(), which returns A.
+ // - B::getInstance() returns B to A::initSingleton(), unlocking B.
+ // - A::getInstance() returns A to the task wrapper, unlocking A.
+ // - The task wrapper passes A to T1 via the future. T1 resumes.
+ // - The main thread executes T2's request for B. Oh look, B already
+ // exists. The task wrapper passes B to T2 via the future. T2
+ // resumes.
+ // This still works even if one of T1 or T2 *is* the main thread.
+ // This still works even if thread T3 requests B at the same moment as
+ // T2. Finding B still UNINITIALIZED, T3 also queues a task for the
+ // main thread, unlocks B and blocks on a (distinct) std::future. By
+ // the time the main thread executes T3's request for B, B already
+ // exists, and is simply delivered via the future.
+
+ { // nested scope for 'lk'
+ // In case racing threads call getInstance() at the same moment,
+ // serialize the calls.
+ LockStatic lk;
+
+ switch (lk->mInitState)
+ {
+ case CONSTRUCTING:
+ // here if DERIVED_TYPE's constructor (directly or indirectly)
+ // calls DERIVED_TYPE::getInstance()
+ logerrs("Tried to access singleton ",
+ classname<DERIVED_TYPE>().c_str(),
+ " from singleton constructor!");
+ return nullptr;
+
+ case INITIALIZING:
+ // here if DERIVED_TYPE::initSingleton() (directly or indirectly)
+ // calls DERIVED_TYPE::getInstance(): go ahead and allow it
+ case INITIALIZED:
+ // normal subsequent calls
+ // record the dependency, if any: check if we got here from another
+ // LLSingleton's constructor or initSingleton() method
+ capture_dependency(lk->mInstance);
+ return lk->mInstance;
+
+ case DELETED:
+ // called after deleteSingleton()
+ logwarns("Trying to access deleted singleton ",
+ classname<DERIVED_TYPE>().c_str(),
+ " -- creating new instance");
+ // fall through
+ case UNINITIALIZED:
+ case QUEUED:
+ // QUEUED means some secondary thread has already requested an
+ // instance, but for present purposes that's semantically
+ // identical to UNINITIALIZED: either way, we must ourselves
+ // request an instance.
+ break;
+ }
+
+ // Here we need to construct a new instance.
+ if (on_main_thread())
+ {
+ // On the main thread, directly construct the instance while
+ // holding the lock.
+ constructSingleton(lk);
+ capture_dependency(lk->mInstance);
+ return lk->mInstance;
+ }
+
+ // Here we need to construct a new instance, but we're on a secondary
+ // thread.
+ lk->mInitState = QUEUED;
+ } // unlock 'lk'
+
+ // Per the comment block above, dispatch to the main thread.
+ loginfos(classname<DERIVED_TYPE>().c_str(),
+ "::getInstance() dispatching to main thread");
+ auto instance = LLMainThreadTask::dispatch(
+ [](){
+ // VERY IMPORTANT to call getInstance() on the main thread,
+ // rather than going straight to constructSingleton()!
+ // During the time window before mInitState is INITIALIZED,
+ // multiple requests might be queued. It's essential that, as
+ // the main thread processes them, only the FIRST such request
+ // actually constructs the instance -- every subsequent one
+ // simply returns the existing instance.
+ loginfos(classname<DERIVED_TYPE>().c_str(),
+ "::getInstance() on main thread");
+ return getInstance();
+ });
+ // record the dependency chain tracked on THIS thread, not the main
+ // thread (consider a getInstance() overload with a tag param that
+ // suppresses dep tracking when dispatched to the main thread)
+ capture_dependency(instance);
+ loginfos(classname<DERIVED_TYPE>().c_str(),
+ "::getInstance() returning on requesting thread");
+ return instance;
}
// Reference version of getInstance()
- // Preferred over getInstance() as it disallows checking for NULL
+ // Preferred over getInstance() as it disallows checking for nullptr
static DERIVED_TYPE& instance()
{
return *getInstance();
@@ -539,7 +587,9 @@ public:
// Use this to avoid accessing singletons before they can safely be constructed.
static bool instanceExists()
{
- return sData.mInitState == INITIALIZED;
+ // defend any access to sData from racing threads
+ LockStatic lk;
+ return lk->mInitState == INITIALIZED;
}
// Has this singleton been deleted? This can be useful during shutdown
@@ -547,23 +597,12 @@ public:
// cleaned up.
static bool wasDeleted()
{
- return sData.mInitState == DELETED;
+ // defend any access to sData from racing threads
+ LockStatic lk;
+ return lk->mInitState == DELETED;
}
-
-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;
-
/**
* LLParamSingleton<T> is like LLSingleton<T>, except in the following ways:
@@ -588,47 +627,86 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
{
private:
typedef LLSingleton<DERIVED_TYPE> super;
- // Use a recursive_mutex in case of constructor circularity. With a
- // non-recursive mutex, that would result in deadlock rather than the
- // logerrs() call in getInstance().
- typedef std::recursive_mutex mutex_t;
+ using typename super::LockStatic;
-public:
- using super::deleteSingleton;
- using super::instanceExists;
- using super::wasDeleted;
-
- // Passes arguments to DERIVED_TYPE's constructor and sets appropriate states
+ // Passes arguments to DERIVED_TYPE's constructor and sets appropriate
+ // states, returning a pointer to the new instance.
template <typename... Args>
- static void initParamSingleton(Args&&... args)
+ static DERIVED_TYPE* initParamSingleton_(Args&&... args)
{
// In case racing threads both call initParamSingleton() at the same
// time, serialize them. One should initialize; the other should see
// mInitState already set.
- std::unique_lock<mutex_t> lk(getMutex());
+ LockStatic lk;
// For organizational purposes this function shouldn't be called twice
- if (super::sData.mInitState != super::UNINITIALIZED)
+ if (lk->mInitState != super::UNINITIALIZED)
{
super::logerrs("Tried to initialize singleton ",
super::template classname<DERIVED_TYPE>().c_str(),
" twice!");
+ return nullptr;
+ }
+ else if (on_main_thread())
+ {
+ // on the main thread, simply construct instance while holding lock
+ super::logdebugs(super::template classname<DERIVED_TYPE>().c_str(),
+ "::initParamSingleton()");
+ super::constructSingleton(lk, std::forward<Args>(args)...);
+ return lk->mInstance;
}
else
{
- super::constructSingleton(std::forward<Args>(args)...);
- super::finishInitializing();
+ // on secondary thread, dispatch to main thread --
+ // set state so we catch any other calls before the main thread
+ // picks up the task
+ lk->mInitState = super::QUEUED;
+ // very important to unlock here so main thread can actually process
+ lk.unlock();
+ super::loginfos(super::template classname<DERIVED_TYPE>().c_str(),
+ "::initParamSingleton() dispatching to main thread");
+ // Normally it would be the height of folly to reference-bind
+ // 'args' into a lambda to be executed on some other thread! By
+ // the time that thread executed the lambda, the references would
+ // all be dangling, and Bad Things would result. But
+ // LLMainThreadTask::dispatch() promises to block until the passed
+ // task has completed. So in this case we know the references will
+ // remain valid until the lambda has run, so we dare to bind
+ // references.
+ auto instance = LLMainThreadTask::dispatch(
+ [&](){
+ super::loginfos(super::template classname<DERIVED_TYPE>().c_str(),
+ "::initParamSingleton() on main thread");
+ return initParamSingleton_(std::forward<Args>(args)...);
+ });
+ super::loginfos(super::template classname<DERIVED_TYPE>().c_str(),
+ "::initParamSingleton() returning on requesting thread");
+ return instance;
}
}
+public:
+ using super::deleteSingleton;
+ using super::instanceExists;
+ using super::wasDeleted;
+
+ /// initParamSingleton() constructs the instance, returning a reference.
+ /// Pass whatever arguments are required to construct DERIVED_TYPE.
+ template <typename... Args>
+ static DERIVED_TYPE& initParamSingleton(Args&&... args)
+ {
+ return *initParamSingleton_(std::forward<Args>(args)...);
+ }
+
static DERIVED_TYPE* getInstance()
{
// In case racing threads call getInstance() at the same moment as
// initParamSingleton(), serialize the calls.
- std::unique_lock<mutex_t> lk(getMutex());
+ LockStatic lk;
- switch (super::sData.mInitState)
+ switch (lk->mInitState)
{
case super::UNINITIALIZED:
+ case super::QUEUED:
super::logerrs("Uninitialized param singleton ",
super::template classname<DERIVED_TYPE>().c_str());
break;
@@ -639,25 +717,13 @@ public:
" from singleton constructor!");
break;
- case super::CONSTRUCTED:
- // Should never happen!? The CONSTRUCTED state is specifically to
- // navigate through LLSingleton::SingletonInitializer getting
- // constructed (once) before LLSingleton::getInstance()'s switch
- // on mInitState. But our initParamSingleton() method calls
- // constructSingleton() and then calls finishInitializing(), which
- // immediately sets INITIALIZING. Why are we here?
- super::logerrs("Param singleton ",
- super::template classname<DERIVED_TYPE>().c_str(),
- "::initSingleton() not yet called");
- break;
-
case super::INITIALIZING:
// As with LLSingleton, explicitly permit circular calls from
// within initSingleton()
case super::INITIALIZED:
// for any valid call, capture dependencies
- super::capture_dependency();
- return super::sData.mInstance;
+ super::capture_dependency(lk->mInstance);
+ return lk->mInstance;
case super::DELETED:
super::logerrs("Trying to access deleted param singleton ",
@@ -677,30 +743,6 @@ public:
{
return *getInstance();
}
-
-private:
- // sMutex must be a function-local static rather than a static member. One
- // of the essential features of LLSingleton and friends is that they must
- // support getInstance() even when the containing module's static
- // variables have not yet been runtime-initialized. A mutex requires
- // construction. A static class member might not yet have been
- // constructed.
- //
- // We could store a dumb mutex_t*, notice when it's NULL and allocate a
- // heap mutex -- but that's vulnerable to race conditions. And we can't
- // defend the dumb pointer with another mutex.
- //
- // We could store a std::atomic<mutex_t*> -- but a default-constructed
- // std::atomic<T> does not contain a valid T, even a default-constructed
- // T! Which means std::atomic, too, requires runtime initialization.
- //
- // But a function-local static is guaranteed to be initialized exactly
- // once, the first time control reaches that declaration.
- static mutex_t& getMutex()
- {
- static mutex_t sMutex;
- return sMutex;
- }
};
/**
@@ -725,9 +767,9 @@ public:
using super::instanceExists;
using super::wasDeleted;
- static void construct()
+ static DT* construct()
{
- super::initParamSingleton();
+ return super::initParamSingleton();
}
};
diff --git a/indra/llcommon/llstacktrace.cpp b/indra/llcommon/llstacktrace.cpp
index bbf0e1e141..80057bf0f2 100644
--- a/indra/llcommon/llstacktrace.cpp
+++ b/indra/llcommon/llstacktrace.cpp
@@ -33,7 +33,10 @@
#include <sstream>
#include "llwin32headerslean.h"
-#include "Dbghelp.h"
+#pragma warning (push)
+#pragma warning (disable:4091) // a microsoft header has warnings. Very nice.
+#include <dbghelp.h>
+#pragma warning (pop)
typedef USHORT NTAPI RtlCaptureStackBackTrace_Function(
IN ULONG frames_to_skip,
diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp
index 0174c411b4..0290eea143 100644
--- a/indra/llcommon/llstring.cpp
+++ b/indra/llcommon/llstring.cpp
@@ -657,22 +657,6 @@ std::string utf8str_removeCRLF(const std::string& utf8str)
}
#if LL_WINDOWS
-// documentation moved to header. Phoenix 2007-11-27
-namespace snprintf_hack
-{
- int snprintf(char *str, size_t size, const char *format, ...)
- {
- va_list args;
- va_start(args, format);
-
- int num_written = _vsnprintf(str, size, format, args); /* Flawfinder: ignore */
- va_end(args);
-
- str[size-1] = '\0'; // always null terminate
- return num_written;
- }
-}
-
std::string ll_convert_wide_to_string(const wchar_t* in)
{
return ll_convert_wide_to_string(in, CP_UTF8);
diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h
index b619a9e48c..6b1a1e0a03 100644
--- a/indra/llcommon/llstring.h
+++ b/indra/llcommon/llstring.h
@@ -707,32 +707,6 @@ LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str);
//@{
/**
- * @brief Implementation the expected snprintf interface.
- *
- * If the size of the passed in buffer is not large enough to hold the string,
- * two bad things happen:
- * 1. resulting formatted string is NOT null terminated
- * 2. Depending on the platform, the return value could be a) the required
- * size of the buffer to copy the entire formatted string or b) -1.
- * On Windows with VS.Net 2003, it returns -1 e.g.
- *
- * safe_snprintf always adds a NULL terminator so that the caller does not
- * need to check for return value or need to add the NULL terminator.
- * It does not, however change the return value - to let the caller know
- * that the passed in buffer size was not large enough to hold the
- * formatted string.
- *
- */
-
-// Deal with the differeneces on Windows
-namespace snprintf_hack
-{
- LL_COMMON_API int snprintf(char *str, size_t size, const char *format, ...);
-}
-
-using snprintf_hack::snprintf;
-
-/**
* @brief Convert a wide string to std::string
*
* This replaces the unsafe W2A macro from ATL.
diff --git a/indra/llcommon/lltempredirect.cpp b/indra/llcommon/lltempredirect.cpp
new file mode 100644
index 0000000000..ec194c1d29
--- /dev/null
+++ b/indra/llcommon/lltempredirect.cpp
@@ -0,0 +1,138 @@
+/**
+ * @file lltempredirect.cpp
+ * @author Nat Goodspeed
+ * @date 2019-10-31
+ * @brief Implementation for lltempredirect.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "lltempredirect.h"
+// STL headers
+// std headers
+#if !LL_WINDOWS
+# include <unistd.h>
+#else
+# include <io.h>
+#endif // !LL_WINDOWS
+// external library headers
+// other Linden headers
+
+/*****************************************************************************
+* llfd
+*****************************************************************************/
+// We could restate the implementation of each of llfd::close(), etc., but
+// this is way more succinct.
+#if LL_WINDOWS
+#define fhclose _close
+#define fhdup _dup
+#define fhdup2 _dup2
+#define fhfdopen _fdopen
+#define fhfileno _fileno
+#else
+#define fhclose ::close
+#define fhdup ::dup
+#define fhdup2 ::dup2
+#define fhfdopen ::fdopen
+#define fhfileno ::fileno
+#endif
+
+int llfd::close(int fd)
+{
+ return fhclose(fd);
+}
+
+int llfd::dup(int target)
+{
+ return fhdup(target);
+}
+
+int llfd::dup2(int target, int reference)
+{
+ return fhdup2(target, reference);
+}
+
+FILE* llfd::open(int fd, const char* mode)
+{
+ return fhfdopen(fd, mode);
+}
+
+int llfd::fileno(FILE* stream)
+{
+ return fhfileno(stream);
+}
+
+/*****************************************************************************
+* LLTempRedirect
+*****************************************************************************/
+LLTempRedirect::LLTempRedirect():
+ mOrigTarget(-1), // -1 is an invalid file descriptor
+ mReference(-1)
+{}
+
+LLTempRedirect::LLTempRedirect(FILE* target, FILE* reference):
+ LLTempRedirect((target? fhfileno(target) : -1),
+ (reference? fhfileno(reference) : -1))
+{}
+
+LLTempRedirect::LLTempRedirect(int target, int reference):
+ // capture a duplicate file descriptor for the file originally targeted by
+ // 'reference'
+ mOrigTarget((reference >= 0)? fhdup(reference) : -1),
+ mReference(reference)
+{
+ if (target >= 0 && reference >= 0)
+ {
+ // As promised, force 'reference' to refer to 'target'. This first
+ // implicitly closes 'reference', which is why we first capture a
+ // duplicate so the original target file stays open.
+ fhdup2(target, reference);
+ }
+}
+
+LLTempRedirect::LLTempRedirect(LLTempRedirect&& other)
+{
+ mOrigTarget = other.mOrigTarget;
+ mReference = other.mReference;
+ // other LLTempRedirect must be in moved-from state so its destructor
+ // won't repeat the same operations as ours!
+ other.mOrigTarget = -1;
+ other.mReference = -1;
+}
+
+LLTempRedirect::~LLTempRedirect()
+{
+ reset();
+}
+
+void LLTempRedirect::reset()
+{
+ // If this instance was default-constructed (or constructed with an
+ // invalid file descriptor), skip the following.
+ if (mOrigTarget >= 0)
+ {
+ // Restore mReference to point to mOrigTarget. This implicitly closes
+ // the duplicate created by our constructor of its 'target' file
+ // descriptor.
+ fhdup2(mOrigTarget, mReference);
+ // mOrigTarget has served its purpose
+ fhclose(mOrigTarget);
+ }
+ // assign these because reset() is also responsible for a "moved from"
+ // instance
+ mOrigTarget = -1;
+ mReference = -1;
+}
+
+LLTempRedirect& LLTempRedirect::operator=(LLTempRedirect&& other)
+{
+ reset();
+ std::swap(mOrigTarget, other.mOrigTarget);
+ std::swap(mReference, other.mReference);
+ return *this;
+}
diff --git a/indra/llcommon/lltempredirect.h b/indra/llcommon/lltempredirect.h
new file mode 100644
index 0000000000..33e05dc06b
--- /dev/null
+++ b/indra/llcommon/lltempredirect.h
@@ -0,0 +1,91 @@
+/**
+ * @file lltempredirect.h
+ * @author Nat Goodspeed
+ * @date 2019-10-31
+ * @brief RAII low-level file-descriptor redirection
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLTEMPREDIRECT_H)
+#define LL_LLTEMPREDIRECT_H
+
+// Functions in this namespace are intended to insulate the caller from the
+// aggravating distinction between ::close() and Microsoft _close().
+namespace llfd
+{
+
+int close(int fd);
+int dup(int target);
+int dup2(int target, int reference);
+FILE* open(int fd, const char* mode);
+int fileno(FILE* stream);
+
+} // namespace llfd
+
+/**
+ * LLTempRedirect is an RAII class that performs file redirection on low-level
+ * file descriptors, expressed as ints. (Use llfd::fileno() to obtain the file
+ * descriptor from a classic-C FILE*. There is no portable way to obtain the
+ * file descriptor from a std::fstream.)
+ *
+ * Instantiate LLTempRedirect with a target file descriptor (e.g. for some
+ * open file) and a reference file descriptor (e.g. for stderr). From that
+ * point until the LLTempRedirect instance is destroyed, all OS-level writes
+ * to the reference file descriptor will be redirected to the target file.
+ *
+ * Because dup2() is used for redirection, the original passed target file
+ * descriptor remains open. If you want LLTempRedirect's destructor to close
+ * the target file, close() the target file descriptor after passing it to
+ * LLTempRedirect's constructor.
+ *
+ * LLTempRedirect's constructor saves the original target of the reference
+ * file descriptor. Its destructor restores the reference file descriptor to
+ * point once again to its original target.
+ */
+class LLTempRedirect
+{
+public:
+ LLTempRedirect();
+ /**
+ * For the lifespan of this LLTempRedirect instance, all writes to
+ * 'reference' will be redirected to 'target'. When this LLTempRedirect is
+ * destroyed, the original target for 'reference' will be restored.
+ *
+ * Pass 'target' as NULL if you simply want to save and restore
+ * 'reference' against possible redirection in the meantime.
+ */
+ LLTempRedirect(FILE* target, FILE* reference);
+ /**
+ * For the lifespan of this LLTempRedirect instance, all writes to
+ * 'reference' will be redirected to 'target'. When this LLTempRedirect is
+ * destroyed, the original target for 'reference' will be restored.
+ *
+ * Pass 'target' as -1 if you simply want to save and restore
+ * 'reference' against possible redirection in the meantime.
+ */
+ LLTempRedirect(int target, int reference);
+ LLTempRedirect(const LLTempRedirect&) = delete;
+ LLTempRedirect(LLTempRedirect&& other);
+
+ ~LLTempRedirect();
+
+ LLTempRedirect& operator=(const LLTempRedirect&) = delete;
+ LLTempRedirect& operator=(LLTempRedirect&& other);
+
+ /// returns (duplicate file descriptor for) the original target of the
+ /// 'reference' file descriptor passed to our constructor
+ int getOriginalTarget() const { return mOrigTarget; }
+ /// returns the original 'reference' file descriptor passed to our
+ /// constructor
+ int getReference() const { return mReference; }
+
+private:
+ void reset();
+
+ int mOrigTarget, mReference;
+};
+
+#endif /* ! defined(LL_LLTEMPREDIRECT_H) */
diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp
index a4171729db..0b9dec969c 100644
--- a/indra/llcommon/llthread.cpp
+++ b/indra/llcommon/llthread.cpp
@@ -92,26 +92,39 @@ void set_thread_name( DWORD dwThreadID, const char* threadName)
// }
//
//----------------------------------------------------------------------------
+namespace
+{
-U32 LL_THREAD_LOCAL sThreadID = 0;
+ LLThread::id_t main_thread()
+ {
+ // Using a function-static variable to identify the main thread
+ // requires that control reach here from the main thread before it
+ // reaches here from any other thread. We simply trust that whichever
+ // thread gets here first is the main thread.
+ static LLThread::id_t s_thread_id = LLThread::currentID();
+ return s_thread_id;
+ }
-U32 LLThread::sIDIter = 0;
+} // anonymous namespace
+LL_COMMON_API bool on_main_thread()
+{
+ return (LLThread::currentID() == main_thread());
+}
LL_COMMON_API void assert_main_thread()
{
- static U32 s_thread_id = LLThread::currentID();
- if (LLThread::currentID() != s_thread_id)
+ auto curr = LLThread::currentID();
+ auto main = main_thread();
+ if (curr != main)
{
- LL_WARNS() << "Illegal execution from thread id " << (S32) LLThread::currentID()
- << " outside main thread " << (S32) s_thread_id << LL_ENDL;
+ LL_WARNS() << "Illegal execution from thread id " << curr
+ << " outside main thread " << main << LL_ENDL;
}
}
-void LLThread::registerThreadID()
-{
- sThreadID = ++sIDIter;
-}
+// this function has become moot
+void LLThread::registerThreadID() {}
//
// Handed to the APR thread creation function
@@ -122,11 +135,12 @@ void LLThread::threadRun()
set_thread_name(-1, mName.c_str());
#endif
+ // this is the first point at which we're actually running in the new thread
+ mID = currentID();
+
// for now, hard code all LLThreads to report to single master thread recorder, which is known to be running on main thread
mRecorder = new LLTrace::ThreadRecorder(*LLTrace::get_master_thread_recorder());
- sThreadID = mID;
-
// Run the user supplied function
do
{
@@ -168,8 +182,6 @@ LLThread::LLThread(const std::string& name, apr_pool_t *poolp) :
mStatus(STOPPED),
mRecorder(NULL)
{
-
- mID = ++sIDIter;
mRunCondition = new LLCondition();
mDataLock = new LLMutex();
mLocalAPRFilePoolp = NULL ;
@@ -347,9 +359,9 @@ void LLThread::setQuitting()
}
// static
-U32 LLThread::currentID()
+LLThread::id_t LLThread::currentID()
{
- return sThreadID;
+ return std::this_thread::get_id();
}
// static
@@ -376,6 +388,16 @@ void LLThread::wakeLocked()
}
}
+void LLThread::lockData()
+{
+ mDataLock->lock();
+}
+
+void LLThread::unlockData()
+{
+ mDataLock->unlock();
+}
+
//============================================================================
//----------------------------------------------------------------------------
diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h
index 863c9051f3..5cd0731f6c 100644
--- a/indra/llcommon/llthread.h
+++ b/indra/llcommon/llthread.h
@@ -30,12 +30,9 @@
#include "llapp.h"
#include "llapr.h"
#include "boost/intrusive_ptr.hpp"
-#include "llmutex.h"
#include "llrefcount.h"
#include <thread>
-LL_COMMON_API void assert_main_thread();
-
namespace LLTrace
{
class ThreadRecorder;
@@ -45,7 +42,6 @@ class LL_COMMON_API LLThread
{
private:
friend class LLMutex;
- static U32 sIDIter;
public:
typedef enum e_thread_status
@@ -55,6 +51,7 @@ public:
QUITTING= 2, // Someone wants this thread to quit
CRASHED = -1 // An uncaught exception was thrown by the thread
} EThreadStatus;
+ typedef std::thread::id id_t;
LLThread(const std::string& name, apr_pool_t *poolp = NULL);
virtual ~LLThread(); // Warning! You almost NEVER want to destroy a thread unless it's in the STOPPED state.
@@ -64,7 +61,7 @@ public:
bool isStopped() const { return (STOPPED == mStatus) || (CRASHED == mStatus); }
bool isCrashed() const { return (CRASHED == mStatus); }
- static U32 currentID(); // Return ID of current thread
+ static id_t currentID(); // Return ID of current thread
static void yield(); // Static because it can be called by the main thread, which doesn't have an LLThread data structure.
public:
@@ -88,7 +85,7 @@ public:
LLVolatileAPRPool* getLocalAPRFilePool() { return mLocalAPRFilePoolp ; }
- U32 getID() const { return mID; }
+ id_t getID() const { return mID; }
// Called by threads *not* created via LLThread to register some
// internal state used by LLMutex. You must call this once early
@@ -109,7 +106,7 @@ protected:
std::thread *mThreadp;
EThreadStatus mStatus;
- U32 mID;
+ id_t mID;
LLTrace::ThreadRecorder* mRecorder;
//a local apr_pool for APRFile operations in this thread. If it exists, LLAPRFile::sAPRFilePoolp should not be used.
@@ -126,8 +123,8 @@ protected:
virtual bool runCondition(void);
// Lock/Unlock Run Condition -- use around modification of any variable used in runCondition()
- inline void lockData();
- inline void unlockData();
+ void lockData();
+ void unlockData();
// This is the predicate that decides whether the thread should sleep.
// It should only be called with mDataLock locked, since the virtual runCondition() function may need to access
@@ -142,17 +139,6 @@ protected:
};
-void LLThread::lockData()
-{
- mDataLock->lock();
-}
-
-void LLThread::unlockData()
-{
- mDataLock->unlock();
-}
-
-
//============================================================================
// Simple responder for self destructing callbacks
@@ -168,5 +154,6 @@ public:
//============================================================================
extern LL_COMMON_API void assert_main_thread();
+extern LL_COMMON_API bool on_main_thread();
#endif // LL_LLTHREAD_H
diff --git a/indra/llcommon/llthreadlocalstorage.cpp b/indra/llcommon/llthreadlocalstorage.cpp
index 8cef05caac..d8a063e8d5 100644
--- a/indra/llcommon/llthreadlocalstorage.cpp
+++ b/indra/llcommon/llthreadlocalstorage.cpp
@@ -93,11 +93,9 @@ void LLThreadLocalPointerBase::initAllThreadLocalStorage()
{
if (!sInitialized)
{
- for (LLInstanceTracker<LLThreadLocalPointerBase>::instance_iter it = beginInstances(), end_it = endInstances();
- it != end_it;
- ++it)
+ for (auto& base : instance_snapshot())
{
- (*it).initStorage();
+ base.initStorage();
}
sInitialized = true;
}
@@ -108,11 +106,9 @@ void LLThreadLocalPointerBase::destroyAllThreadLocalStorage()
{
if (sInitialized)
{
- //for (LLInstanceTracker<LLThreadLocalPointerBase>::instance_iter it = beginInstances(), end_it = endInstances();
- // it != end_it;
- // ++it)
+ //for (auto& base : instance_snapshot())
//{
- // (*it).destroyStorage();
+ // base.destroyStorage();
//}
sInitialized = false;
}
diff --git a/indra/llcommon/llthreadsafequeue.h b/indra/llcommon/llthreadsafequeue.h
index b0bddac8e5..30dd507f73 100644
--- a/indra/llcommon/llthreadsafequeue.h
+++ b/indra/llcommon/llthreadsafequeue.h
@@ -30,18 +30,12 @@
#include "llexception.h"
#include <deque>
#include <string>
-
-#if LL_WINDOWS
-#pragma warning (push)
-#pragma warning (disable:4265)
-#endif
-// 'std::_Pad' : class has virtual functions, but destructor is not virtual
-#include <mutex>
-#include <condition_variable>
-
-#if LL_WINDOWS
-#pragma warning (pop)
-#endif
+#include <chrono>
+#include "mutex.h"
+#include "llcoros.h"
+#include LLCOROS_MUTEX_HEADER
+#include <boost/fiber/timed_mutex.hpp>
+#include LLCOROS_CONDVAR_HEADER
//
// A general queue exception.
@@ -88,18 +82,28 @@ public:
// Add an element to the front of queue (will block if the queue has
// reached capacity).
//
- // This call will raise an interrupt error if the queue is deleted while
+ // This call will raise an interrupt error if the queue is closed while
// the caller is blocked.
void pushFront(ElementT const & element);
- // Try to add an element to the front ofqueue without blocking. Returns
+ // Try to add an element to the front of queue without blocking. Returns
// true only if the element was actually added.
bool tryPushFront(ElementT const & element);
-
+
+ // Try to add an element to the front of queue, blocking if full but with
+ // timeout. Returns true if the element was added.
+ // There are potentially two different timeouts involved: how long to try
+ // to lock the mutex, versus how long to wait for the queue to stop being
+ // full. Careful settings for each timeout might be orders of magnitude
+ // apart. However, this method conflates them.
+ template <typename Rep, typename Period>
+ bool tryPushFrontFor(const std::chrono::duration<Rep, Period>& timeout,
+ ElementT const & element);
+
// Pop the element at the end of the queue (will block if the queue is
// empty).
//
- // This call will raise an interrupt error if the queue is deleted while
+ // This call will raise an interrupt error if the queue is closed while
// the caller is blocked.
ElementT popBack(void);
@@ -110,13 +114,29 @@ public:
// Returns the size of the queue.
size_t size();
+ // closes the queue:
+ // - every subsequent pushFront() call will throw LLThreadSafeQueueInterrupt
+ // - every subsequent tryPushFront() call will return false
+ // - popBack() calls will return normally until the queue is drained, then
+ // every subsequent popBack() will throw LLThreadSafeQueueInterrupt
+ // - tryPopBack() calls will return normally until the queue is drained,
+ // then every subsequent tryPopBack() call will return false
+ void close();
+
+ // detect closed state
+ bool isClosed();
+ // inverse of isClosed()
+ explicit operator bool();
+
private:
std::deque< ElementT > mStorage;
U32 mCapacity;
+ bool mClosed;
- std::mutex mLock;
- std::condition_variable mCapacityCond;
- std::condition_variable mEmptyCond;
+ boost::fibers::timed_mutex mLock;
+ typedef std::unique_lock<decltype(mLock)> lock_t;
+ boost::fibers::condition_variable_any mCapacityCond;
+ boost::fibers::condition_variable_any mEmptyCond;
};
// LLThreadSafeQueue
@@ -124,7 +144,8 @@ private:
template<typename ElementT>
LLThreadSafeQueue<ElementT>::LLThreadSafeQueue(U32 capacity) :
-mCapacity(capacity)
+ mCapacity(capacity),
+ mClosed(false)
{
}
@@ -132,13 +153,18 @@ mCapacity(capacity)
template<typename ElementT>
void LLThreadSafeQueue<ElementT>::pushFront(ElementT const & element)
{
+ lock_t lock1(mLock);
while (true)
{
- std::unique_lock<std::mutex> lock1(mLock);
+ if (mClosed)
+ {
+ LLTHROW(LLThreadSafeQueueInterrupt());
+ }
if (mStorage.size() < mCapacity)
{
mStorage.push_front(element);
+ lock1.unlock();
mEmptyCond.notify_one();
return;
}
@@ -149,17 +175,61 @@ void LLThreadSafeQueue<ElementT>::pushFront(ElementT const & element)
}
+template <typename ElementT>
+template <typename Rep, typename Period>
+bool LLThreadSafeQueue<ElementT>::tryPushFrontFor(const std::chrono::duration<Rep, Period>& timeout,
+ ElementT const & element)
+{
+ // Convert duration to time_point: passing the same timeout duration to
+ // each of multiple calls is wrong.
+ auto endpoint = std::chrono::steady_clock::now() + timeout;
+
+ lock_t lock1(mLock, std::defer_lock);
+ if (!lock1.try_lock_until(endpoint))
+ return false;
+
+ while (true)
+ {
+ if (mClosed)
+ {
+ return false;
+ }
+
+ if (mStorage.size() < mCapacity)
+ {
+ mStorage.push_front(element);
+ lock1.unlock();
+ mEmptyCond.notify_one();
+ return true;
+ }
+
+ // Storage Full. Wait for signal.
+ if (LLCoros::cv_status::timeout == mCapacityCond.wait_until(lock1, endpoint))
+ {
+ // timed out -- formally we might recheck both conditions above
+ return false;
+ }
+ // If we didn't time out, we were notified for some reason. Loop back
+ // to check.
+ }
+}
+
+
template<typename ElementT>
bool LLThreadSafeQueue<ElementT>::tryPushFront(ElementT const & element)
{
- std::unique_lock<std::mutex> lock1(mLock, std::defer_lock);
+ lock_t lock1(mLock, std::defer_lock);
if (!lock1.try_lock())
return false;
+ if (mClosed)
+ return false;
+
if (mStorage.size() >= mCapacity)
return false;
mStorage.push_front(element);
+ lock1.unlock();
mEmptyCond.notify_one();
return true;
}
@@ -168,18 +238,23 @@ bool LLThreadSafeQueue<ElementT>::tryPushFront(ElementT const & element)
template<typename ElementT>
ElementT LLThreadSafeQueue<ElementT>::popBack(void)
{
+ lock_t lock1(mLock);
while (true)
{
- std::unique_lock<std::mutex> lock1(mLock);
-
if (!mStorage.empty())
{
ElementT value = mStorage.back();
mStorage.pop_back();
+ lock1.unlock();
mCapacityCond.notify_one();
return value;
}
+ if (mClosed)
+ {
+ LLTHROW(LLThreadSafeQueueInterrupt());
+ }
+
// Storage empty. Wait for signal.
mEmptyCond.wait(lock1);
}
@@ -189,15 +264,18 @@ ElementT LLThreadSafeQueue<ElementT>::popBack(void)
template<typename ElementT>
bool LLThreadSafeQueue<ElementT>::tryPopBack(ElementT & element)
{
- std::unique_lock<std::mutex> lock1(mLock, std::defer_lock);
+ lock_t lock1(mLock, std::defer_lock);
if (!lock1.try_lock())
return false;
+ // no need to check mClosed: tryPopBack() behavior when the queue is
+ // closed is implemented by simple inability to push any new elements
if (mStorage.empty())
return false;
element = mStorage.back();
mStorage.pop_back();
+ lock1.unlock();
mCapacityCond.notify_one();
return true;
}
@@ -206,8 +284,34 @@ bool LLThreadSafeQueue<ElementT>::tryPopBack(ElementT & element)
template<typename ElementT>
size_t LLThreadSafeQueue<ElementT>::size(void)
{
- std::lock_guard<std::mutex> lock(mLock);
+ lock_t lock(mLock);
return mStorage.size();
}
+template<typename ElementT>
+void LLThreadSafeQueue<ElementT>::close()
+{
+ lock_t lock(mLock);
+ mClosed = true;
+ lock.unlock();
+ // wake up any blocked popBack() calls
+ mEmptyCond.notify_all();
+ // wake up any blocked pushFront() calls
+ mCapacityCond.notify_all();
+}
+
+template<typename ElementT>
+bool LLThreadSafeQueue<ElementT>::isClosed()
+{
+ lock_t lock(mLock);
+ return mClosed;
+}
+
+template<typename ElementT>
+LLThreadSafeQueue<ElementT>::operator bool()
+{
+ lock_t lock(mLock);
+ return ! mClosed;
+}
+
#endif
diff --git a/indra/llcommon/lltrace.h b/indra/llcommon/lltrace.h
index 79ff55b739..0d0cd6f581 100644
--- a/indra/llcommon/lltrace.h
+++ b/indra/llcommon/lltrace.h
@@ -57,7 +57,7 @@ class StatBase
{
public:
StatBase(const char* name, const char* description);
- virtual ~StatBase() LLINSTANCETRACKER_DTOR_NOEXCEPT {}
+ virtual ~StatBase() {}
virtual const char* getUnitLabel() const;
const std::string& getName() const { return mName; }
diff --git a/indra/llcommon/lltraceaccumulators.cpp b/indra/llcommon/lltraceaccumulators.cpp
index 385d31edd7..b1c23c6fb7 100644
--- a/indra/llcommon/lltraceaccumulators.cpp
+++ b/indra/llcommon/lltraceaccumulators.cpp
@@ -291,8 +291,8 @@ void EventAccumulator::reset( const EventAccumulator* other )
{
mNumSamples = 0;
mSum = 0;
- mMin = NaN;
- mMax = NaN;
+ mMin = F32(NaN);
+ mMax = F32(NaN);
mMean = NaN;
mSumOfSquares = 0;
mLastValue = other ? other->mLastValue : NaN;
diff --git a/indra/llcommon/lltraceaccumulators.h b/indra/llcommon/lltraceaccumulators.h
index 6f27b97dff..8eb5338a2a 100644
--- a/indra/llcommon/lltraceaccumulators.h
+++ b/indra/llcommon/lltraceaccumulators.h
@@ -242,8 +242,8 @@ namespace LLTrace
EventAccumulator()
: mSum(0),
- mMin(NaN),
- mMax(NaN),
+ mMin(F32(NaN)),
+ mMax(F32(NaN)),
mMean(NaN),
mSumOfSquares(0),
mNumSamples(0),
@@ -313,8 +313,8 @@ namespace LLTrace
SampleAccumulator()
: mSum(0),
- mMin(NaN),
- mMax(NaN),
+ mMin(F32(NaN)),
+ mMax(F32(NaN)),
mMean(NaN),
mSumOfSquares(0),
mLastSampleTimeStamp(0),
diff --git a/indra/llcommon/lltracethreadrecorder.cpp b/indra/llcommon/lltracethreadrecorder.cpp
index 181fc2f058..025dc57044 100644
--- a/indra/llcommon/lltracethreadrecorder.cpp
+++ b/indra/llcommon/lltracethreadrecorder.cpp
@@ -28,6 +28,7 @@
#include "lltracethreadrecorder.h"
#include "llfasttimer.h"
#include "lltrace.h"
+#include "llstl.h"
namespace LLTrace
{
@@ -64,16 +65,15 @@ void ThreadRecorder::init()
activate(&mThreadRecordingBuffers);
// initialize time block parent pointers
- for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(), end_it = BlockTimerStatHandle::instance_tracker_t::endInstances();
- it != end_it;
- ++it)
+ for (auto& base : BlockTimerStatHandle::instance_snapshot())
{
- BlockTimerStatHandle& time_block = static_cast<BlockTimerStatHandle&>(*it);
- TimeBlockTreeNode& tree_node = mTimeBlockTreeNodes[it->getIndex()];
+ // because of indirect derivation from LLInstanceTracker, have to downcast
+ BlockTimerStatHandle& time_block = static_cast<BlockTimerStatHandle&>(base);
+ TimeBlockTreeNode& tree_node = mTimeBlockTreeNodes[time_block.getIndex()];
tree_node.mBlock = &time_block;
tree_node.mParent = &root_time_block;
- it->getCurrentAccumulator().mParent = &root_time_block;
+ time_block.getCurrentAccumulator().mParent = &root_time_block;
}
mRootTimer = new BlockTimer(root_time_block);
diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp
index 8f33d789eb..b05630c6b5 100644
--- a/indra/llcommon/lluuid.cpp
+++ b/indra/llcommon/lluuid.cpp
@@ -43,6 +43,7 @@
#include "llstring.h"
#include "lltimer.h"
#include "llthread.h"
+#include "llmutex.h"
const LLUUID LLUUID::null;
const LLTransactionID LLTransactionID::tnull;
@@ -738,7 +739,7 @@ void LLUUID::getCurrentTime(uuid_time_t *timestamp)
getSystemTime(&time_last);
uuids_this_tick = uuids_per_tick;
init = TRUE;
- mMutex = new LLMutex();
+ mMutex = new LLMutex();
}
uuid_time_t time_now = {0,0};
diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h
index b1a6f61360..0387e75c65 100644
--- a/indra/llcommon/llworkerthread.h
+++ b/indra/llcommon/llworkerthread.h
@@ -34,6 +34,7 @@
#include "llqueuedthread.h"
#include "llatomic.h"
+#include "llmutex.h"
#define USE_FRAME_CALLBACK_MANAGER 0
diff --git a/indra/llcommon/lockstatic.h b/indra/llcommon/lockstatic.h
new file mode 100644
index 0000000000..96c53c6473
--- /dev/null
+++ b/indra/llcommon/lockstatic.h
@@ -0,0 +1,73 @@
+/**
+ * @file lockstatic.h
+ * @author Nat Goodspeed
+ * @date 2019-12-03
+ * @brief LockStatic class provides mutex-guarded access to the specified
+ * static data.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LOCKSTATIC_H)
+#define LL_LOCKSTATIC_H
+
+#include "mutex.h" // std::unique_lock
+
+namespace llthread
+{
+
+// Instantiate this template to obtain a pointer to the canonical static
+// instance of Static while holding a lock on that instance. Use of
+// Static::mMutex presumes that Static declares some suitable mMutex.
+template <typename Static>
+class LockStatic
+{
+ typedef std::unique_lock<decltype(Static::mMutex)> lock_t;
+public:
+ LockStatic():
+ mData(getStatic()),
+ mLock(mData->mMutex)
+ {}
+ Static* get() const { return mData; }
+ operator Static*() const { return get(); }
+ Static* operator->() const { return get(); }
+ // sometimes we must explicitly unlock...
+ void unlock()
+ {
+ // but once we do, access is no longer permitted
+ mData = nullptr;
+ mLock.unlock();
+ }
+protected:
+ Static* mData;
+ lock_t mLock;
+private:
+ Static* getStatic()
+ {
+ // Static::mMutex must be function-local static rather than class-
+ // static. Some of our consumers must function properly (therefore
+ // lock properly) even when the containing module's static variables
+ // have not yet been runtime-initialized. A mutex requires
+ // construction. A static class member might not yet have been
+ // constructed.
+ //
+ // We could store a dumb mutex_t*, notice when it's NULL and allocate a
+ // heap mutex -- but that's vulnerable to race conditions. And we can't
+ // defend the dumb pointer with another mutex.
+ //
+ // We could store a std::atomic<mutex_t*> -- but a default-constructed
+ // std::atomic<T> does not contain a valid T, even a default-constructed
+ // T! Which means std::atomic, too, requires runtime initialization.
+ //
+ // But a function-local static is guaranteed to be initialized exactly
+ // once: the first time control reaches that declaration.
+ static Static sData;
+ return &sData;
+ }
+};
+
+} // llthread namespace
+
+#endif /* ! defined(LL_LOCKSTATIC_H) */
diff --git a/indra/llcommon/mutex.h b/indra/llcommon/mutex.h
new file mode 100644
index 0000000000..90d0942270
--- /dev/null
+++ b/indra/llcommon/mutex.h
@@ -0,0 +1,22 @@
+/**
+ * @file mutex.h
+ * @author Nat Goodspeed
+ * @date 2019-12-03
+ * @brief Wrap <mutex> in odious boilerplate
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if LL_WINDOWS
+#pragma warning (push)
+#pragma warning (disable:4265)
+#endif
+// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual
+
+#include <mutex>
+
+#if LL_WINDOWS
+#pragma warning (pop)
+#endif
diff --git a/indra/llcommon/tests/llcond_test.cpp b/indra/llcommon/tests/llcond_test.cpp
new file mode 100644
index 0000000000..478149eacf
--- /dev/null
+++ b/indra/llcommon/tests/llcond_test.cpp
@@ -0,0 +1,67 @@
+/**
+ * @file llcond_test.cpp
+ * @author Nat Goodspeed
+ * @date 2019-07-18
+ * @brief Test for llcond.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llcond.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "../test/lltut.h"
+#include "llcoros.h"
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llcond_data
+ {
+ LLScalarCond<int> cond{0};
+ };
+ typedef test_group<llcond_data> llcond_group;
+ typedef llcond_group::object object;
+ llcond_group llcondgrp("llcond");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("Immediate gratification");
+ cond.set_one(1);
+ ensure("wait_for_equal() failed",
+ cond.wait_for_equal(F32Milliseconds(1), 1));
+ ensure("wait_for_unequal() should have failed",
+ ! cond.wait_for_unequal(F32Milliseconds(1), 1));
+ }
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("Simple two-coroutine test");
+ LLCoros::instance().launch(
+ "test<2>",
+ [this]()
+ {
+ // Lambda immediately entered -- control comes here first.
+ ensure_equals(cond.get(), 0);
+ cond.set_all(1);
+ cond.wait_equal(2);
+ ensure_equals(cond.get(), 2);
+ cond.set_all(3);
+ });
+ // Main coroutine is resumed only when the lambda waits.
+ ensure_equals(cond.get(), 1);
+ cond.set_all(2);
+ cond.wait_equal(3);
+ }
+} // namespace tut
diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp
index fa02d2bb1a..032923a108 100644
--- a/indra/llcommon/tests/lleventcoro_test.cpp
+++ b/indra/llcommon/tests/lleventcoro_test.cpp
@@ -26,102 +26,33 @@
* $/LicenseInfo$
*/
-/*****************************************************************************/
-// test<1>() is cloned from a Boost.Coroutine example program whose copyright
-// info is reproduced here:
-/*---------------------------------------------------------------------------*/
-// Copyright (c) 2006, Giovanni P. Deretta
-//
-// This code may be used under either of the following two licences:
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE. OF SUCH DAMAGE.
-//
-// Or:
-//
-// Distributed under the Boost Software License, Version 1.0.
-// (See accompanying file LICENSE_1_0.txt or copy at
-// http://www.boost.org/LICENSE_1_0.txt)
-/*****************************************************************************/
-
#define BOOST_RESULT_OF_USE_TR1 1
-// On some platforms, Boost.Coroutine must #define magic symbols before
-// #including platform-API headers. Naturally, that's ineffective unless the
-// Boost.Coroutine #include is the *first* #include of the platform header.
-// That means that client code must generally #include Boost.Coroutine headers
-// before anything else.
-#include <boost/dcoroutine/coroutine.hpp>
#include <boost/bind.hpp>
#include <boost/range.hpp>
#include <boost/utility.hpp>
#include <boost/shared_ptr.hpp>
+#include <boost/make_shared.hpp>
#include "linden_common.h"
#include <iostream>
#include <string>
+#include <typeinfo>
#include "../test/lltut.h"
+#include "../test/lltestapp.h"
#include "llsd.h"
#include "llsdutil.h"
#include "llevents.h"
-#include "tests/wrapllerrs.h"
-#include "stringize.h"
#include "llcoros.h"
+#include "lleventfilter.h"
#include "lleventcoro.h"
#include "../test/debug.h"
+#include "../test/sync.h"
using namespace llcoro;
/*****************************************************************************
-* from the banana.cpp example program borrowed for test<1>()
-*****************************************************************************/
-namespace coroutines = boost::dcoroutines;
-using coroutines::coroutine;
-
-template<typename Iter>
-bool match(Iter first, Iter last, std::string match) {
- std::string::iterator i = match.begin();
- for(; (first != last) && (i != match.end()); ++i) {
- if (*first != *i)
- return false;
- ++first;
- }
- return i == match.end();
-}
-
-template<typename BidirectionalIterator>
-BidirectionalIterator
-match_substring(BidirectionalIterator begin,
- BidirectionalIterator end,
- std::string xmatch,
- BOOST_DEDUCED_TYPENAME coroutine<BidirectionalIterator(void)>::self& self) {
-//BidirectionalIterator begin_ = begin;
- for(; begin != end; ++begin)
- if(match(begin, end, xmatch)) {
- self.yield(begin);
- }
- return end;
-}
-
-typedef coroutine<std::string::iterator(void)> match_coroutine_type;
-
-/*****************************************************************************
* Test helpers
*****************************************************************************/
/// Simulate an event API whose response is immediate: sent on receipt of the
@@ -131,8 +62,9 @@ typedef coroutine<std::string::iterator(void)> match_coroutine_type;
class ImmediateAPI
{
public:
- ImmediateAPI():
- mPump("immediate", true)
+ ImmediateAPI(Sync& sync):
+ mPump("immediate", true),
+ mSync(sync)
{
mPump.listen("API", boost::bind(&ImmediateAPI::operator(), this, _1));
}
@@ -141,20 +73,18 @@ public:
// Invoke this with an LLSD map containing:
// ["value"]: Integer value. We will reply with ["value"] + 1.
- // ["reply"]: Name of LLEventPump on which to send success response.
- // ["error"]: Name of LLEventPump on which to send error response.
- // ["fail"]: Presence of this key selects ["error"], else ["success"] as
- // the name of the pump on which to send the response.
+ // ["reply"]: Name of LLEventPump on which to send response.
bool operator()(const LLSD& event) const
{
+ mSync.bump();
LLSD::Integer value(event["value"]);
- LLSD::String replyPumpName(event.has("fail")? "error" : "reply");
- LLEventPumps::instance().obtain(event[replyPumpName]).post(value + 1);
+ LLEventPumps::instance().obtain(event["reply"]).post(value + 1);
return false;
}
private:
LLEventStream mPump;
+ Sync& mSync;
};
/*****************************************************************************
@@ -162,633 +92,247 @@ private:
*****************************************************************************/
namespace tut
{
- struct coroutine_data {};
- typedef test_group<coroutine_data> coroutine_group;
+ struct test_data
+ {
+ Sync mSync;
+ ImmediateAPI immediateAPI{mSync};
+ std::string replyName, errorName, threw, stringdata;
+ LLSD result, errordata;
+ int which;
+ LLTestApp testApp;
+
+ void explicit_wait(boost::shared_ptr<LLCoros::Promise<std::string>>& cbp);
+ void waitForEventOn1();
+ void coroPump();
+ void postAndWait1();
+ void coroPumpPost();
+ };
+ typedef test_group<test_data> coroutine_group;
typedef coroutine_group::object object;
coroutine_group coroutinegrp("coroutine");
- template<> template<>
- void object::test<1>()
- {
- set_test_name("From banana.cpp example program in Boost.Coroutine distro");
- std::string buffer = "banananana";
- std::string match = "nana";
- std::string::iterator begin = buffer.begin();
- std::string::iterator end = buffer.end();
-
-#if defined(BOOST_CORO_POSIX_IMPL)
-// std::cout << "Using Boost.Coroutine " << BOOST_CORO_POSIX_IMPL << '\n';
-#else
-// std::cout << "Using non-Posix Boost.Coroutine implementation" << std::endl;
-#endif
-
- typedef std::string::iterator signature(std::string::iterator,
- std::string::iterator,
- std::string,
- match_coroutine_type::self&);
-
- coroutine<std::string::iterator(void)> matcher
- (boost::bind(static_cast<signature*>(match_substring),
- begin,
- end,
- match,
- _1));
-
- std::string::iterator i = matcher();
-/*==========================================================================*|
- while(matcher && i != buffer.end()) {
- std::cout <<"Match at: "<< std::distance(buffer.begin(), i)<<'\n';
- i = matcher();
- }
-|*==========================================================================*/
- size_t matches[] = { 2, 4, 6 };
- for (size_t *mi(boost::begin(matches)), *mend(boost::end(matches));
- mi != mend; ++mi, i = matcher())
- {
- ensure("more", matcher);
- ensure("found", i != buffer.end());
- ensure_equals("value", std::distance(buffer.begin(), i), *mi);
- }
- ensure("done", ! matcher);
- }
-
- // use static data so we can intersperse coroutine functions with the
- // tests that engage them
- ImmediateAPI immediateAPI;
- std::string replyName, errorName, threw, stringdata;
- LLSD result, errordata;
- int which;
-
- // reinit vars at the start of each test
- void clear()
- {
- replyName.clear();
- errorName.clear();
- threw.clear();
- stringdata.clear();
- result = LLSD();
- errordata = LLSD();
- which = 0;
- }
-
- void explicit_wait(boost::shared_ptr<LLCoros::Future<std::string>::callback_t>& cbp)
+ void test_data::explicit_wait(boost::shared_ptr<LLCoros::Promise<std::string>>& cbp)
{
BEGIN
{
+ mSync.bump();
// The point of this test is to verify / illustrate suspending a
// coroutine for something other than an LLEventPump. In other
// words, this shows how to adapt to any async operation that
// provides a callback-style notification (and prove that it
// works).
- LLCoros::Future<std::string> future;
- // get the callback from that future
- LLCoros::Future<std::string>::callback_t callback(future.make_callback());
-
// Perhaps we would send a request to a remote server and arrange
- // for 'callback' to be called on response. Of course that might
- // involve an adapter object from the actual callback signature to
- // the signature of 'callback' -- in this case, void(std::string).
- // For test purposes, instead of handing 'callback' (or the
+ // for cbp->set_value() to be called on response.
+ // For test purposes, instead of handing 'callback' (or an
// adapter) off to some I/O subsystem, we'll just pass it back to
// our caller.
- cbp.reset(new LLCoros::Future<std::string>::callback_t(callback));
+ cbp = boost::make_shared<LLCoros::Promise<std::string>>();
+ LLCoros::Future<std::string> future = LLCoros::getFuture(*cbp);
- ensure("Not yet", ! future);
// calling get() on the future causes us to suspend
debug("about to suspend");
stringdata = future.get();
- ensure("Got it", bool(future));
+ mSync.bump();
+ ensure_equals("Got it", stringdata, "received");
}
END
}
template<> template<>
- void object::test<2>()
+ void object::test<1>()
{
- clear();
set_test_name("explicit_wait");
DEBUG;
// Construct the coroutine instance that will run explicit_wait.
- boost::shared_ptr<LLCoros::Future<std::string>::callback_t> respond;
- LLCoros::instance().launch("test<2>",
- boost::bind(explicit_wait, boost::ref(respond)));
+ boost::shared_ptr<LLCoros::Promise<std::string>> respond;
+ LLCoros::instance().launch("test<1>",
+ [this, &respond](){ explicit_wait(respond); });
+ mSync.bump();
// When the coroutine waits for the future, it returns here.
debug("about to respond");
- // Now we're the I/O subsystem delivering a result. This immediately
- // transfers control back to the coroutine.
- (*respond)("received");
+ // Now we're the I/O subsystem delivering a result. This should make
+ // the coroutine ready.
+ respond->set_value("received");
+ // but give it a chance to wake up
+ mSync.yield();
// ensure the coroutine ran and woke up again with the intended result
ensure_equals(stringdata, "received");
}
- void waitForEventOn1()
+ void test_data::waitForEventOn1()
{
BEGIN
{
+ mSync.bump();
result = suspendUntilEventOn("source");
+ mSync.bump();
}
END
}
template<> template<>
- void object::test<3>()
+ void object::test<2>()
{
- clear();
set_test_name("waitForEventOn1");
DEBUG;
- LLCoros::instance().launch("test<3>", waitForEventOn1);
+ LLCoros::instance().launch("test<2>", [this](){ waitForEventOn1(); });
+ mSync.bump();
debug("about to send");
LLEventPumps::instance().obtain("source").post("received");
+ // give waitForEventOn1() a chance to run
+ mSync.yield();
debug("back from send");
ensure_equals(result.asString(), "received");
}
- void waitForEventOn2()
- {
- BEGIN
- {
- LLEventWithID pair = suspendUntilEventOn("reply", "error");
- result = pair.first;
- which = pair.second;
- debug(STRINGIZE("result = " << result << ", which = " << which));
- }
- END
- }
-
- template<> template<>
- void object::test<4>()
- {
- clear();
- set_test_name("waitForEventOn2 reply");
- {
- DEBUG;
- LLCoros::instance().launch("test<4>", waitForEventOn2);
- debug("about to send");
- LLEventPumps::instance().obtain("reply").post("received");
- debug("back from send");
- }
- ensure_equals(result.asString(), "received");
- ensure_equals("which pump", which, 0);
- }
-
- template<> template<>
- void object::test<5>()
- {
- clear();
- set_test_name("waitForEventOn2 error");
- DEBUG;
- LLCoros::instance().launch("test<5>", waitForEventOn2);
- debug("about to send");
- LLEventPumps::instance().obtain("error").post("badness");
- debug("back from send");
- ensure_equals(result.asString(), "badness");
- ensure_equals("which pump", which, 1);
- }
-
- void coroPump()
+ void test_data::coroPump()
{
BEGIN
{
+ mSync.bump();
LLCoroEventPump waiter;
replyName = waiter.getName();
result = waiter.suspend();
+ mSync.bump();
}
END
}
template<> template<>
- void object::test<6>()
+ void object::test<3>()
{
- clear();
set_test_name("coroPump");
DEBUG;
- LLCoros::instance().launch("test<6>", coroPump);
+ LLCoros::instance().launch("test<3>", [this](){ coroPump(); });
+ mSync.bump();
debug("about to send");
LLEventPumps::instance().obtain(replyName).post("received");
+ // give coroPump() a chance to run
+ mSync.yield();
debug("back from send");
ensure_equals(result.asString(), "received");
}
- void coroPumps()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- replyName = waiter.getName0();
- errorName = waiter.getName1();
- LLEventWithID pair(waiter.suspend());
- result = pair.first;
- which = pair.second;
- }
- END
- }
-
- template<> template<>
- void object::test<7>()
- {
- clear();
- set_test_name("coroPumps reply");
- DEBUG;
- LLCoros::instance().launch("test<7>", coroPumps);
- debug("about to send");
- LLEventPumps::instance().obtain(replyName).post("received");
- debug("back from send");
- ensure_equals(result.asString(), "received");
- ensure_equals("which pump", which, 0);
- }
-
- template<> template<>
- void object::test<8>()
- {
- clear();
- set_test_name("coroPumps error");
- DEBUG;
- LLCoros::instance().launch("test<8>", coroPumps);
- debug("about to send");
- LLEventPumps::instance().obtain(errorName).post("badness");
- debug("back from send");
- ensure_equals(result.asString(), "badness");
- ensure_equals("which pump", which, 1);
- }
-
- void coroPumpsNoEx()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- replyName = waiter.getName0();
- errorName = waiter.getName1();
- result = waiter.suspendWithException();
- }
- END
- }
-
- template<> template<>
- void object::test<9>()
- {
- clear();
- set_test_name("coroPumpsNoEx");
- DEBUG;
- LLCoros::instance().launch("test<9>", coroPumpsNoEx);
- debug("about to send");
- LLEventPumps::instance().obtain(replyName).post("received");
- debug("back from send");
- ensure_equals(result.asString(), "received");
- }
-
- void coroPumpsEx()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- replyName = waiter.getName0();
- errorName = waiter.getName1();
- try
- {
- result = waiter.suspendWithException();
- debug("no exception");
- }
- catch (const LLErrorEvent& e)
- {
- debug(STRINGIZE("exception " << e.what()));
- errordata = e.getData();
- }
- }
- END
- }
-
- template<> template<>
- void object::test<10>()
- {
- clear();
- set_test_name("coroPumpsEx");
- DEBUG;
- LLCoros::instance().launch("test<10>", coroPumpsEx);
- debug("about to send");
- LLEventPumps::instance().obtain(errorName).post("badness");
- debug("back from send");
- ensure("no result", result.isUndefined());
- ensure_equals("got error", errordata.asString(), "badness");
- }
-
- void coroPumpsNoLog()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- replyName = waiter.getName0();
- errorName = waiter.getName1();
- result = waiter.suspendWithLog();
- }
- END
- }
-
- template<> template<>
- void object::test<11>()
- {
- clear();
- set_test_name("coroPumpsNoLog");
- DEBUG;
- LLCoros::instance().launch("test<11>", coroPumpsNoLog);
- debug("about to send");
- LLEventPumps::instance().obtain(replyName).post("received");
- debug("back from send");
- ensure_equals(result.asString(), "received");
- }
-
- void coroPumpsLog()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- replyName = waiter.getName0();
- errorName = waiter.getName1();
- WrapLLErrs capture;
- threw = capture.catch_llerrs([&waiter, &debug](){
- result = waiter.suspendWithLog();
- debug("no exception");
- });
- }
- END
- }
-
- template<> template<>
- void object::test<12>()
- {
- clear();
- set_test_name("coroPumpsLog");
- DEBUG;
- LLCoros::instance().launch("test<12>", coroPumpsLog);
- debug("about to send");
- LLEventPumps::instance().obtain(errorName).post("badness");
- debug("back from send");
- ensure("no result", result.isUndefined());
- ensure_contains("got error", threw, "badness");
- }
-
- void postAndWait1()
+ void test_data::postAndWait1()
{
BEGIN
{
+ mSync.bump();
result = postAndSuspend(LLSDMap("value", 17), // request event
immediateAPI.getPump(), // requestPump
"reply1", // replyPump
"reply"); // request["reply"] = name
+ mSync.bump();
}
END
}
template<> template<>
- void object::test<13>()
+ void object::test<4>()
{
- clear();
set_test_name("postAndWait1");
DEBUG;
- LLCoros::instance().launch("test<13>", postAndWait1);
+ LLCoros::instance().launch("test<4>", [this](){ postAndWait1(); });
ensure_equals(result.asInteger(), 18);
}
- void postAndWait2()
- {
- BEGIN
- {
- LLEventWithID pair = ::postAndSuspend2(LLSDMap("value", 18),
- immediateAPI.getPump(),
- "reply2",
- "error2",
- "reply",
- "error");
- result = pair.first;
- which = pair.second;
- debug(STRINGIZE("result = " << result << ", which = " << which));
- }
- END
- }
-
- template<> template<>
- void object::test<14>()
- {
- clear();
- set_test_name("postAndWait2");
- DEBUG;
- LLCoros::instance().launch("test<14>", postAndWait2);
- ensure_equals(result.asInteger(), 19);
- ensure_equals(which, 0);
- }
-
- void postAndWait2_1()
- {
- BEGIN
- {
- LLEventWithID pair = ::postAndSuspend2(LLSDMap("value", 18)("fail", LLSD()),
- immediateAPI.getPump(),
- "reply2",
- "error2",
- "reply",
- "error");
- result = pair.first;
- which = pair.second;
- debug(STRINGIZE("result = " << result << ", which = " << which));
- }
- END
- }
-
- template<> template<>
- void object::test<15>()
- {
- clear();
- set_test_name("postAndWait2_1");
- DEBUG;
- LLCoros::instance().launch("test<15>", postAndWait2_1);
- ensure_equals(result.asInteger(), 19);
- ensure_equals(which, 1);
- }
-
- void coroPumpPost()
+ void test_data::coroPumpPost()
{
BEGIN
{
+ mSync.bump();
LLCoroEventPump waiter;
result = waiter.postAndSuspend(LLSDMap("value", 17),
immediateAPI.getPump(), "reply");
+ mSync.bump();
}
END
}
template<> template<>
- void object::test<16>()
+ void object::test<5>()
{
- clear();
set_test_name("coroPumpPost");
DEBUG;
- LLCoros::instance().launch("test<16>", coroPumpPost);
+ LLCoros::instance().launch("test<5>", [this](){ coroPumpPost(); });
ensure_equals(result.asInteger(), 18);
}
- void coroPumpsPost()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- LLEventWithID pair(waiter.postAndSuspend(LLSDMap("value", 23),
- immediateAPI.getPump(), "reply", "error"));
- result = pair.first;
- which = pair.second;
- }
- END
- }
-
- template<> template<>
- void object::test<17>()
- {
- clear();
- set_test_name("coroPumpsPost reply");
- DEBUG;
- LLCoros::instance().launch("test<17>", coroPumpsPost);
- ensure_equals(result.asInteger(), 24);
- ensure_equals("which pump", which, 0);
- }
-
- void coroPumpsPost_1()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- LLEventWithID pair(
- waiter.postAndSuspend(LLSDMap("value", 23)("fail", LLSD()),
- immediateAPI.getPump(), "reply", "error"));
- result = pair.first;
- which = pair.second;
- }
- END
- }
-
- template<> template<>
- void object::test<18>()
- {
- clear();
- set_test_name("coroPumpsPost error");
- DEBUG;
- LLCoros::instance().launch("test<18>", coroPumpsPost_1);
- ensure_equals(result.asInteger(), 24);
- ensure_equals("which pump", which, 1);
- }
-
- void coroPumpsPostNoEx()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- result = waiter.postAndSuspendWithException(LLSDMap("value", 8),
- immediateAPI.getPump(), "reply", "error");
- }
- END
- }
-
- template<> template<>
- void object::test<19>()
- {
- clear();
- set_test_name("coroPumpsPostNoEx");
- DEBUG;
- LLCoros::instance().launch("test<19>", coroPumpsPostNoEx);
- ensure_equals(result.asInteger(), 9);
- }
-
- void coroPumpsPostEx()
- {
- BEGIN
+ template <class PUMP>
+ void test()
+ {
+ PUMP pump(typeid(PUMP).name());
+ bool running{false};
+ LLSD data{LLSD::emptyArray()};
+ // start things off by posting once before even starting the listener
+ // coro
+ LL_DEBUGS() << "test() posting first" << LL_ENDL;
+ LLSD first{LLSDMap("desc", "first")("value", 0)};
+ bool consumed = pump.post(first);
+ ensure("should not have consumed first", ! consumed);
+ // now launch the coro
+ LL_DEBUGS() << "test() launching listener coro" << LL_ENDL;
+ running = true;
+ LLCoros::instance().launch(
+ "listener",
+ [&pump, &running, &data](){
+ // important for this test that we consume posted values
+ LLCoros::instance().set_consuming(true);
+ // should immediately retrieve 'first' without waiting
+ LL_DEBUGS() << "listener coro waiting for first" << LL_ENDL;
+ data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
+ // Don't use ensure() from within the coro -- ensure() failure
+ // throws tut::fail, which won't propagate out to the main
+ // test driver, which will result in an odd failure.
+ // Wait for 'second' because it's not already pending.
+ LL_DEBUGS() << "listener coro waiting for second" << LL_ENDL;
+ data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
+ // and wait for 'third', which should involve no further waiting
+ LL_DEBUGS() << "listener coro waiting for third" << LL_ENDL;
+ data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
+ LL_DEBUGS() << "listener coro done" << LL_ENDL;
+ running = false;
+ });
+ // back from coro at the point where it's waiting for 'second'
+ LL_DEBUGS() << "test() posting second" << LL_ENDL;
+ LLSD second{llsd::map("desc", "second", "value", 1)};
+ consumed = pump.post(second);
+ ensure("should have consumed second", consumed);
+ // This is a key point: even though we've post()ed the value for which
+ // the coroutine is waiting, it's actually still suspended until we
+ // pause for some other reason. The coroutine will only pick up one
+ // value at a time from our 'pump'. It's important to exercise the
+ // case when we post() two values before it picks up either.
+ LL_DEBUGS() << "test() posting third" << LL_ENDL;
+ LLSD third{llsd::map("desc", "third", "value", 2)};
+ consumed = pump.post(third);
+ ensure("should NOT yet have consumed third", ! consumed);
+ // now just wait for coro to finish -- which it eventually will, given
+ // that all its suspend calls have short timeouts.
+ while (running)
{
- LLCoroEventPumps waiter;
- try
- {
- result = waiter.postAndSuspendWithException(
- LLSDMap("value", 9)("fail", LLSD()),
- immediateAPI.getPump(), "reply", "error");
- debug("no exception");
- }
- catch (const LLErrorEvent& e)
- {
- debug(STRINGIZE("exception " << e.what()));
- errordata = e.getData();
- }
+ LL_DEBUGS() << "test() waiting for coro done" << LL_ENDL;
+ llcoro::suspendUntilTimeout(0.1);
}
- END
+ // okay, verify expected results
+ ensure_equals("should have received three values", data,
+ llsd::array(first, second, third));
+ LL_DEBUGS() << "test() done" << LL_ENDL;
}
template<> template<>
- void object::test<20>()
- {
- clear();
- set_test_name("coroPumpsPostEx");
- DEBUG;
- LLCoros::instance().launch("test<20>", coroPumpsPostEx);
- ensure("no result", result.isUndefined());
- ensure_equals("got error", errordata.asInteger(), 10);
- }
-
- void coroPumpsPostNoLog()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- result = waiter.postAndSuspendWithLog(LLSDMap("value", 30),
- immediateAPI.getPump(), "reply", "error");
- }
- END
- }
-
- template<> template<>
- void object::test<21>()
- {
- clear();
- set_test_name("coroPumpsPostNoLog");
- DEBUG;
- LLCoros::instance().launch("test<21>", coroPumpsPostNoLog);
- ensure_equals(result.asInteger(), 31);
- }
-
- void coroPumpsPostLog()
+ void object::test<6>()
{
- BEGIN
- {
- LLCoroEventPumps waiter;
- WrapLLErrs capture;
- threw = capture.catch_llerrs(
- [&waiter, &debug](){
- result = waiter.postAndSuspendWithLog(
- LLSDMap("value", 31)("fail", LLSD()),
- immediateAPI.getPump(), "reply", "error");
- debug("no exception");
- });
- }
- END
+ set_test_name("LLEventMailDrop");
+ tut::test<LLEventMailDrop>();
}
template<> template<>
- void object::test<22>()
+ void object::test<7>()
{
- clear();
- set_test_name("coroPumpsPostLog");
- DEBUG;
- LLCoros::instance().launch("test<22>", coroPumpsPostLog);
- ensure("no result", result.isUndefined());
- ensure_contains("got error", threw, "32");
+ set_test_name("LLEventLogProxyFor<LLEventMailDrop>");
+ tut::test< LLEventLogProxyFor<LLEventMailDrop> >();
}
}
-
-/*==========================================================================*|
-#include <boost/context/guarded_stack_allocator.hpp>
-
-namespace tut
-{
- template<> template<>
- void object::test<23>()
- {
- set_test_name("stacksize");
- std::cout << "default_stacksize: " << boost::context::guarded_stack_allocator::default_stacksize() << '\n';
- }
-} // namespace tut
-|*==========================================================================*/
diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp
index a181d5c941..9da1ecfd67 100644
--- a/indra/llcommon/tests/lleventdispatcher_test.cpp
+++ b/indra/llcommon/tests/lleventdispatcher_test.cpp
@@ -23,6 +23,7 @@
#include "stringize.h"
#include "tests/wrapllerrs.h"
#include "../test/catch_and_store_what_in.h"
+#include "../test/debug.h"
#include <map>
#include <string>
@@ -46,15 +47,6 @@ using boost::lambda::var;
using namespace llsd;
/*****************************************************************************
-* Output control
-*****************************************************************************/
-#ifdef DEBUG_ON
-using std::cout;
-#else
-static std::ostringstream cout;
-#endif
-
-/*****************************************************************************
* Example data, functions, classes
*****************************************************************************/
// We don't need a whole lot of different arbitrary-params methods, just (no |
@@ -155,13 +147,13 @@ struct Vars
/*------------- no-args (non-const, const, static) methods -------------*/
void method0()
{
- cout << "method0()\n";
+ debug()("method0()");
i = 17;
}
void cmethod0() const
{
- cout << 'c';
+ debug()('c', NONL);
const_cast<Vars*>(this)->method0();
}
@@ -170,13 +162,13 @@ struct Vars
/*------------ Callable (non-const, const, static) methods -------------*/
void method1(const LLSD& obj)
{
- cout << "method1(" << obj << ")\n";
+ debug()("method1(", obj, ")");
llsd = obj;
}
void cmethod1(const LLSD& obj) const
{
- cout << 'c';
+ debug()('c', NONL);
const_cast<Vars*>(this)->method1(obj);
}
@@ -196,12 +188,12 @@ struct Vars
else
vcp = std::string("'") + cp + "'";
- cout << "methodna(" << b
- << ", " << i
- << ", " << f
- << ", " << d
- << ", " << vcp
- << ")\n";
+ debug()("methodna(", b,
+ ", ", i,
+ ", ", f,
+ ", ", d,
+ ", ", vcp,
+ ")");
this->b = b;
this->i = i;
@@ -218,12 +210,12 @@ struct Vars
vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte);
}
- cout << "methodnb(" << "'" << s << "'"
- << ", " << uuid
- << ", " << date
- << ", '" << uri << "'"
- << ", " << vbin.str()
- << ")\n";
+ debug()("methodnb(", "'", s, "'",
+ ", ", uuid,
+ ", ", date,
+ ", '", uri, "'",
+ ", ", vbin.str(),
+ ")");
this->s = s;
this->uuid = uuid;
@@ -234,18 +226,30 @@ struct Vars
void cmethodna(NPARAMSa) const
{
- cout << 'c';
+ debug()('c', NONL);
const_cast<Vars*>(this)->methodna(NARGSa);
}
void cmethodnb(NPARAMSb) const
{
- cout << 'c';
+ debug()('c', NONL);
const_cast<Vars*>(this)->methodnb(NARGSb);
}
static void smethodna(NPARAMSa);
static void smethodnb(NPARAMSb);
+
+ static Debug& debug()
+ {
+ // Lazily initialize this Debug instance so it can notice if main()
+ // has forcibly set LOGTEST. If it were simply a static member, it
+ // would already have examined the environment variable by the time
+ // main() gets around to checking command-line switches. Since we have
+ // a global static Vars instance, the same would be true of a plain
+ // non-static member.
+ static Debug sDebug("Vars");
+ return sDebug;
+ }
};
/*------- Global Vars instance for free functions and static methods -------*/
static Vars g;
@@ -253,25 +257,25 @@ static Vars g;
/*------------ Static Vars method implementations reference 'g' ------------*/
void Vars::smethod0()
{
- cout << "smethod0() -> ";
+ debug()("smethod0() -> ", NONL);
g.method0();
}
void Vars::smethod1(const LLSD& obj)
{
- cout << "smethod1(" << obj << ") -> ";
+ debug()("smethod1(", obj, ") -> ", NONL);
g.method1(obj);
}
void Vars::smethodna(NPARAMSa)
{
- cout << "smethodna(...) -> ";
+ debug()("smethodna(...) -> ", NONL);
g.methodna(NARGSa);
}
void Vars::smethodnb(NPARAMSb)
{
- cout << "smethodnb(...) -> ";
+ debug()("smethodnb(...) -> ", NONL);
g.methodnb(NARGSb);
}
@@ -284,25 +288,25 @@ void clear()
/*------------------- Free functions also reference 'g' --------------------*/
void free0()
{
- cout << "free0() -> ";
+ g.debug()("free0() -> ", NONL);
g.method0();
}
void free1(const LLSD& obj)
{
- cout << "free1(" << obj << ") -> ";
+ g.debug()("free1(", obj, ") -> ", NONL);
g.method1(obj);
}
void freena(NPARAMSa)
{
- cout << "freena(...) -> ";
+ g.debug()("freena(...) -> ", NONL);
g.methodna(NARGSa);
}
void freenb(NPARAMSb)
{
- cout << "freenb(...) -> ";
+ g.debug()("freenb(...) -> ", NONL);
g.methodnb(NARGSb);
}
@@ -313,6 +317,7 @@ namespace tut
{
struct lleventdispatcher_data
{
+ Debug debug{"test"};
WrapLLErrs redirect;
Dispatcher work;
Vars v;
@@ -431,12 +436,17 @@ namespace tut
// Same for freenb() et al.
params = LLSDMap("a", LLSDArray("b")("i")("f")("d")("cp"))
("b", LLSDArray("s")("uuid")("date")("uri")("bin"));
- cout << "params:\n" << params << "\nparams[\"a\"]:\n" << params["a"] << "\nparams[\"b\"]:\n" << params["b"] << std::endl;
+ debug("params:\n",
+ params, "\n"
+ "params[\"a\"]:\n",
+ params["a"], "\n"
+ "params[\"b\"]:\n",
+ params["b"]);
// default LLSD::Binary value
std::vector<U8> binary;
for (size_t ix = 0, h = 0xaa; ix < 6; ++ix, h += 0x11)
{
- binary.push_back(h);
+ binary.push_back((U8)h);
}
// Full defaults arrays. We actually don't care what the LLUUID or
// LLDate values are, as long as they're different from the
@@ -448,7 +458,8 @@ namespace tut
(LLDate::now())
(LLURI("http://www.ietf.org/rfc/rfc3986.txt"))
(binary));
- cout << "dft_array_full:\n" << dft_array_full << std::endl;
+ debug("dft_array_full:\n",
+ dft_array_full);
// Partial defaults arrays.
foreach(LLSD::String a, ab)
{
@@ -457,7 +468,8 @@ namespace tut
llsd_copy_array(dft_array_full[a].beginArray() + partition,
dft_array_full[a].endArray());
}
- cout << "dft_array_partial:\n" << dft_array_partial << std::endl;
+ debug("dft_array_partial:\n",
+ dft_array_partial);
foreach(LLSD::String a, ab)
{
@@ -473,7 +485,10 @@ namespace tut
dft_map_partial[a][params[a][ix].asString()] = dft_array_full[a][ix];
}
}
- cout << "dft_map_full:\n" << dft_map_full << "\ndft_map_partial:\n" << dft_map_partial << '\n';
+ debug("dft_map_full:\n",
+ dft_map_full, "\n"
+ "dft_map_partial:\n",
+ dft_map_partial);
// (Free function | static method) with (no | arbitrary) params,
// map style, no (empty array) defaults
@@ -918,7 +933,12 @@ namespace tut
params[a].endArray()),
dft_array_partial[a]);
}
- cout << "allreq:\n" << allreq << "\nleftreq:\n" << leftreq << "\nrightdft:\n" << rightdft << std::endl;
+ debug("allreq:\n",
+ allreq, "\n"
+ "leftreq:\n",
+ leftreq, "\n"
+ "rightdft:\n",
+ rightdft);
// Generate maps containing parameter names not provided by the
// dft_map_partial maps.
@@ -930,7 +950,8 @@ namespace tut
skipreq[a].erase(me.first);
}
}
- cout << "skipreq:\n" << skipreq << std::endl;
+ debug("skipreq:\n",
+ skipreq);
LLSD groups(LLSDArray // array of groups
@@ -975,7 +996,11 @@ namespace tut
LLSD names(grp[0]);
LLSD required(grp[1][0]);
LLSD optional(grp[1][1]);
- cout << "For " << names << ",\n" << "required:\n" << required << "\noptional:\n" << optional << std::endl;
+ debug("For ", names, ",\n",
+ "required:\n",
+ required, "\n"
+ "optional:\n",
+ optional);
// Loop through 'names'
foreach(LLSD nm, inArray(names))
@@ -1145,7 +1170,7 @@ namespace tut
std::vector<U8> binary;
for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i)
{
- binary.push_back(h);
+ binary.push_back((U8)h);
}
LLSD args(LLSDMap("a", LLSDArray(true)(17)(3.14)(123.456)("char*"))
("b", LLSDArray("string")
@@ -1163,7 +1188,7 @@ namespace tut
}
// Adjust expect["a"]["cp"] for special Vars::cp treatment.
expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'";
- cout << "expect: " << expect << '\n';
+ debug("expect: ", expect);
// Use substantially the same logic for args and argsplus
LLSD argsarrays(LLSDArray(args)(argsplus));
@@ -1218,7 +1243,8 @@ namespace tut
{
array_overfull[a].append("bogus");
}
- cout << "array_full: " << array_full << "\narray_overfull: " << array_overfull << std::endl;
+ debug("array_full: ", array_full, "\n"
+ "array_overfull: ", array_overfull);
// We rather hope that LLDate::now() will generate a timestamp
// distinct from the one it generated in the constructor, moments ago.
ensure_not_equals("Timestamps too close",
@@ -1233,7 +1259,8 @@ namespace tut
map_overfull[a] = map_full[a];
map_overfull[a]["extra"] = "ignore";
}
- cout << "map_full: " << map_full << "\nmap_overfull: " << map_overfull << std::endl;
+ debug("map_full: ", map_full, "\n"
+ "map_overfull: ", map_overfull);
LLSD expect(map_full);
// Twiddle the const char* param.
expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'";
@@ -1248,7 +1275,7 @@ namespace tut
// so won't bother returning it. Predict that behavior to match the
// LLSD values.
expect["a"].erase("b");
- cout << "expect: " << expect << std::endl;
+ debug("expect: ", expect);
// For this test, calling functions registered with different sets of
// parameter defaults should make NO DIFFERENCE WHATSOEVER. Every call
// should pass all params.
diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp
index 1875013794..fa2cb03e95 100644
--- a/indra/llcommon/tests/lleventfilter_test.cpp
+++ b/indra/llcommon/tests/lleventfilter_test.cpp
@@ -36,9 +36,12 @@
// other Linden headers
#include "../test/lltut.h"
#include "stringize.h"
+#include "llsdutil.h"
#include "listener.h"
#include "tests/wrapllerrs.h"
+#include <typeinfo>
+
/*****************************************************************************
* Test classes
*****************************************************************************/
@@ -401,6 +404,78 @@ namespace tut
throttle.post(";17");
ensure_equals("17", cat.result, "136;12;17"); // "17" delivered
}
+
+ template<class PUMP>
+ void test()
+ {
+ PUMP pump(typeid(PUMP).name());
+ LLSD data{LLSD::emptyArray()};
+ bool consumed{true};
+ // listener that appends to 'data'
+ // but that also returns the current value of 'consumed'
+ // Instantiate this separately because we're going to listen()
+ // multiple times with the same lambda: LLEventMailDrop only replays
+ // queued events on a new listen() call.
+ auto lambda =
+ [&data, &consumed](const LLSD& event)->bool
+ {
+ data.append(event);
+ return consumed;
+ };
+ {
+ LLTempBoundListener conn = pump.listen("lambda", lambda);
+ pump.post("first");
+ }
+ // first post() should certainly be received by listener
+ ensure_equals("first", data, llsd::array("first"));
+ // the question is, since consumed was true, did it queue the value?
+ data = LLSD::emptyArray();
+ {
+ // if it queued the value, it would be delivered on subsequent
+ // listen() call
+ LLTempBoundListener conn = pump.listen("lambda", lambda);
+ }
+ ensure_equals("empty1", data, LLSD::emptyArray());
+ data = LLSD::emptyArray();
+ // now let's NOT consume the posted data
+ consumed = false;
+ {
+ LLTempBoundListener conn = pump.listen("lambda", lambda);
+ pump.post("second");
+ pump.post("third");
+ }
+ // the two events still arrive
+ ensure_equals("second,third1", data, llsd::array("second", "third"));
+ data = LLSD::emptyArray();
+ {
+ // when we reconnect, these should be delivered again
+ // but this time they should be consumed
+ consumed = true;
+ LLTempBoundListener conn = pump.listen("lambda", lambda);
+ }
+ // unconsumed events were delivered again
+ ensure_equals("second,third2", data, llsd::array("second", "third"));
+ data = LLSD::emptyArray();
+ {
+ // when we reconnect this time, no more unconsumed events
+ LLTempBoundListener conn = pump.listen("lambda", lambda);
+ }
+ ensure_equals("empty2", data, LLSD::emptyArray());
+ }
+
+ template<> template<>
+ void filter_object::test<6>()
+ {
+ set_test_name("LLEventMailDrop");
+ tut::test<LLEventMailDrop>();
+ }
+
+ template<> template<>
+ void filter_object::test<7>()
+ {
+ set_test_name("LLEventLogProxyFor<LLEventMailDrop>");
+ tut::test< LLEventLogProxyFor<LLEventMailDrop> >();
+ }
} // namespace tut
/*****************************************************************************
diff --git a/indra/llcommon/tests/llexception_test.cpp b/indra/llcommon/tests/llexception_test.cpp
index 6bee1943c2..8ddf636cd1 100644
--- a/indra/llcommon/tests/llexception_test.cpp
+++ b/indra/llcommon/tests/llexception_test.cpp
@@ -305,4 +305,19 @@ namespace tut
std::cout << center("int", '=', margin) << std::endl;
catch_several(throw_int, "throw_int");
}
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("reporting exceptions");
+
+ try
+ {
+ LLTHROW(LLException("badness"));
+ }
+ catch (...)
+ {
+ LOG_UNHANDLED_EXCEPTION("llexception test<2>()");
+ }
+ }
} // namespace tut
diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp
index d94fc0c56d..9b89159625 100644
--- a/indra/llcommon/tests/llinstancetracker_test.cpp
+++ b/indra/llcommon/tests/llinstancetracker_test.cpp
@@ -41,7 +41,6 @@
#include <boost/scoped_ptr.hpp>
// other Linden headers
#include "../test/lltut.h"
-#include "wrapllerrs.h"
struct Badness: public std::runtime_error
{
@@ -112,24 +111,22 @@ namespace tut
void object::test<2>()
{
ensure_equals(Unkeyed::instanceCount(), 0);
- Unkeyed* dangling = NULL;
+ std::weak_ptr<Unkeyed> dangling;
{
Unkeyed one;
ensure_equals(Unkeyed::instanceCount(), 1);
- Unkeyed* found = Unkeyed::getInstance(&one);
- ensure_equals(found, &one);
+ std::weak_ptr<Unkeyed> found = one.getWeak();
+ ensure(! found.expired());
{
boost::scoped_ptr<Unkeyed> two(new Unkeyed);
ensure_equals(Unkeyed::instanceCount(), 2);
- Unkeyed* found = Unkeyed::getInstance(two.get());
- ensure_equals(found, two.get());
}
ensure_equals(Unkeyed::instanceCount(), 1);
- // store an unwise pointer to a temp Unkeyed instance
- dangling = &one;
+ // store a weak pointer to a temp Unkeyed instance
+ dangling = found;
} // make that instance vanish
// check the now-invalid pointer to the destroyed instance
- ensure("getInstance(T*) failed to track destruction", ! Unkeyed::getInstance(dangling));
+ ensure("weak_ptr<Unkeyed> failed to track destruction", dangling.expired());
ensure_equals(Unkeyed::instanceCount(), 0);
}
@@ -142,7 +139,8 @@ namespace tut
// reimplement LLInstanceTracker using, say, a hash map instead of a
// std::map. We DO insist that every key appear exactly once.
typedef std::vector<std::string> StringVector;
- StringVector keys(Keyed::beginKeys(), Keyed::endKeys());
+ auto snap = Keyed::key_snapshot();
+ StringVector keys(snap.begin(), snap.end());
std::sort(keys.begin(), keys.end());
StringVector::const_iterator ki(keys.begin());
ensure_equals(*ki++, "one");
@@ -153,17 +151,15 @@ namespace tut
ensure("didn't reach end", ki == keys.end());
// Use a somewhat different approach to order independence with
- // beginInstances(): explicitly capture the instances we know in a
+ // instance_snapshot(): explicitly capture the instances we know in a
// set, and delete them as we iterate through.
typedef std::set<Keyed*> InstanceSet;
InstanceSet instances;
instances.insert(&one);
instances.insert(&two);
instances.insert(&three);
- for (Keyed::instance_iter ii(Keyed::beginInstances()), iend(Keyed::endInstances());
- ii != iend; ++ii)
+ for (auto& ref : Keyed::instance_snapshot())
{
- Keyed& ref = *ii;
ensure_equals("spurious instance", instances.erase(&ref), 1);
}
ensure_equals("unreported instance", instances.size(), 0);
@@ -180,11 +176,10 @@ namespace tut
instances.insert(&two);
instances.insert(&three);
- for (Unkeyed::instance_iter ii(Unkeyed::beginInstances()), iend(Unkeyed::endInstances()); ii != iend; ++ii)
- {
- Unkeyed& ref = *ii;
- ensure_equals("spurious instance", instances.erase(&ref), 1);
- }
+ for (auto& ref : Unkeyed::instance_snapshot())
+ {
+ ensure_equals("spurious instance", instances.erase(&ref), 1);
+ }
ensure_equals("unreported instance", instances.size(), 0);
}
@@ -192,49 +187,49 @@ namespace tut
template<> template<>
void object::test<5>()
{
- set_test_name("delete Keyed with outstanding instance_iter");
- std::string what;
- Keyed* keyed = new Keyed("delete Keyed with outstanding instance_iter");
- {
- WrapLLErrs wrapper;
- Keyed::instance_iter i(Keyed::beginInstances());
- what = wrapper.catch_llerrs([&keyed](){
- delete keyed;
- });
- }
- ensure(! what.empty());
+ std::string desc("delete Keyed with outstanding instance_snapshot");
+ set_test_name(desc);
+ Keyed* keyed = new Keyed(desc);
+ // capture a snapshot but do not yet traverse it
+ auto snapshot = Keyed::instance_snapshot();
+ // delete the one instance
+ delete keyed;
+ // traversing the snapshot should reflect the deletion
+ // avoid ensure_equals() because it requires the ability to stream the
+ // two values to std::ostream
+ ensure(snapshot.begin() == snapshot.end());
}
template<> template<>
void object::test<6>()
{
- set_test_name("delete Keyed with outstanding key_iter");
- std::string what;
- Keyed* keyed = new Keyed("delete Keyed with outstanding key_it");
- {
- WrapLLErrs wrapper;
- Keyed::key_iter i(Keyed::beginKeys());
- what = wrapper.catch_llerrs([&keyed](){
- delete keyed;
- });
- }
- ensure(! what.empty());
+ std::string desc("delete Keyed with outstanding key_snapshot");
+ set_test_name(desc);
+ Keyed* keyed = new Keyed(desc);
+ // capture a snapshot but do not yet traverse it
+ auto snapshot = Keyed::key_snapshot();
+ // delete the one instance
+ delete keyed;
+ // traversing the snapshot should reflect the deletion
+ // avoid ensure_equals() because it requires the ability to stream the
+ // two values to std::ostream
+ ensure(snapshot.begin() == snapshot.end());
}
template<> template<>
void object::test<7>()
{
- set_test_name("delete Unkeyed with outstanding instance_iter");
+ set_test_name("delete Unkeyed with outstanding instance_snapshot");
std::string what;
Unkeyed* unkeyed = new Unkeyed;
- {
- WrapLLErrs wrapper;
- Unkeyed::instance_iter i(Unkeyed::beginInstances());
- what = wrapper.catch_llerrs([&unkeyed](){
- delete unkeyed;
- });
- }
- ensure(! what.empty());
+ // capture a snapshot but do not yet traverse it
+ auto snapshot = Unkeyed::instance_snapshot();
+ // delete the one instance
+ delete unkeyed;
+ // traversing the snapshot should reflect the deletion
+ // avoid ensure_equals() because it requires the ability to stream the
+ // two values to std::ostream
+ ensure(snapshot.begin() == snapshot.end());
}
template<> template<>
@@ -246,11 +241,9 @@ namespace tut
// We can't use the iterator-range InstanceSet constructor because
// beginInstances() returns an iterator that dereferences to an
// Unkeyed&, not an Unkeyed*.
- for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()),
- ukend(Unkeyed::endInstances());
- uki != ukend; ++uki)
+ for (auto& ref : Unkeyed::instance_snapshot())
{
- existing.insert(&*uki);
+ existing.insert(&ref);
}
try
{
@@ -273,11 +266,9 @@ namespace tut
// instances was also present in the original set. If that's not true,
// it's because our new Unkeyed ended up in the updated set despite
// its constructor exception.
- for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()),
- ukend(Unkeyed::endInstances());
- uki != ukend; ++uki)
+ for (auto& ref : Unkeyed::instance_snapshot())
{
- ensure("failed to remove instance", existing.find(&*uki) != existing.end());
+ ensure("failed to remove instance", existing.find(&ref) != existing.end());
}
}
} // namespace tut
diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp
index bf0a74d10d..9d71e327d8 100644
--- a/indra/llcommon/tests/llleap_test.cpp
+++ b/indra/llcommon/tests/llleap_test.cpp
@@ -49,24 +49,28 @@ const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte
#endif
-void waitfor(const std::vector<LLLeap*>& instances, int timeout=60)
+// capture std::weak_ptrs to LLLeap instances so we can tell when they expire
+typedef std::vector<std::weak_ptr<LLLeap>> LLLeapVector;
+
+void waitfor(const LLLeapVector& instances, int timeout=60)
{
int i;
for (i = 0; i < timeout; ++i)
{
// Every iteration, test whether any of the passed LLLeap instances
// still exist (are still running).
- std::vector<LLLeap*>::const_iterator vli(instances.begin()), vlend(instances.end());
- for ( ; vli != vlend; ++vli)
+ bool found = false;
+ for (auto& ptr : instances)
{
- // getInstance() returns NULL if it's terminated/gone, non-NULL if
- // it's still running
- if (LLLeap::getInstance(*vli))
+ if (! ptr.expired())
+ {
+ found = true;
break;
+ }
}
// If we made it through all of 'instances' without finding one that's
// still running, we're done.
- if (vli == vlend)
+ if (! found)
{
/*==========================================================================*|
std::cout << instances.size() << " LLLeap instances terminated in "
@@ -86,8 +90,8 @@ void waitfor(const std::vector<LLLeap*>& instances, int timeout=60)
void waitfor(LLLeap* instance, int timeout=60)
{
- std::vector<LLLeap*> instances;
- instances.push_back(instance);
+ LLLeapVector instances;
+ instances.push_back(instance->getWeak());
waitfor(instances, timeout);
}
@@ -218,11 +222,11 @@ namespace tut
NamedTempFile script("py",
"import time\n"
"time.sleep(1)\n");
- std::vector<LLLeap*> instances;
+ LLLeapVector instances;
instances.push_back(LLLeap::create(get_test_name(),
- sv(list_of(PYTHON)(script.getName()))));
+ sv(list_of(PYTHON)(script.getName())))->getWeak());
instances.push_back(LLLeap::create(get_test_name(),
- sv(list_of(PYTHON)(script.getName()))));
+ sv(list_of(PYTHON)(script.getName())))->getWeak());
// In this case we're simply establishing that two LLLeap instances
// can coexist without throwing exceptions or bombing in any other
// way. Wait for them to terminate.
diff --git a/indra/llcommon/tests/llmainthreadtask_test.cpp b/indra/llcommon/tests/llmainthreadtask_test.cpp
new file mode 100644
index 0000000000..69b11ccafb
--- /dev/null
+++ b/indra/llcommon/tests/llmainthreadtask_test.cpp
@@ -0,0 +1,137 @@
+/**
+ * @file llmainthreadtask_test.cpp
+ * @author Nat Goodspeed
+ * @date 2019-12-05
+ * @brief Test for llmainthreadtask.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llmainthreadtask.h"
+// STL headers
+// std headers
+#include <atomic>
+// external library headers
+// other Linden headers
+#include "../test/lltut.h"
+#include "../test/sync.h"
+#include "llthread.h" // on_main_thread()
+#include "lleventtimer.h"
+#include "lockstatic.h"
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llmainthreadtask_data
+ {
+ // 5-second timeout
+ Sync mSync{F32Milliseconds(5000.0f)};
+
+ llmainthreadtask_data()
+ {
+ // we're not testing the result; this is just to cache the
+ // initial thread as the main thread.
+ on_main_thread();
+ }
+ };
+ typedef test_group<llmainthreadtask_data> llmainthreadtask_group;
+ typedef llmainthreadtask_group::object object;
+ llmainthreadtask_group llmainthreadtaskgrp("llmainthreadtask");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("inline");
+ bool ran = false;
+ bool result = LLMainThreadTask::dispatch(
+ [&ran]()->bool{
+ ran = true;
+ return true;
+ });
+ ensure("didn't run lambda", ran);
+ ensure("didn't return result", result);
+ }
+
+ struct StaticData
+ {
+ std::mutex mMutex; // LockStatic looks for mMutex
+ bool ran{false};
+ };
+ typedef llthread::LockStatic<StaticData> LockStatic;
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("cross-thread");
+ skip("This test is prone to build-time hangs");
+ std::atomic_bool result(false);
+ // wrapping our thread lambda in a packaged_task will catch any
+ // exceptions it might throw and deliver them via future
+ std::packaged_task<void()> thread_work(
+ [this, &result](){
+ // unblock test<2>()'s yield_until(1)
+ mSync.set(1);
+ // dispatch work to main thread -- should block here
+ bool on_main(
+ LLMainThreadTask::dispatch(
+ []()->bool{
+ // have to lock static mutex to set static data
+ LockStatic()->ran = true;
+ // indicate whether task was run on the main thread
+ return on_main_thread();
+ }));
+ // wait for test<2>() to unblock us again
+ mSync.yield_until(3);
+ result = on_main;
+ });
+ auto thread_result = thread_work.get_future();
+ std::thread thread;
+ try
+ {
+ // run thread_work
+ thread = std::thread(std::move(thread_work));
+ // wait for thread to set(1)
+ mSync.yield_until(1);
+ // try to acquire the lock, should block because thread has it
+ LockStatic lk;
+ // wake up when dispatch() unlocks the static mutex
+ ensure("shouldn't have run yet", !lk->ran);
+ ensure("shouldn't have returned yet", !result);
+ // unlock so the task can acquire the lock
+ lk.unlock();
+ // run the task -- should unblock thread, which will immediately block
+ // on mSync
+ LLEventTimer::updateClass();
+ // 'lk', having unlocked, can no longer be used to access; relock with
+ // a new LockStatic instance
+ ensure("should now have run", LockStatic()->ran);
+ ensure("returned too early", !result);
+ // okay, let thread perform the assignment
+ mSync.set(3);
+ }
+ catch (...)
+ {
+ // A test failure exception anywhere in the try block can cause
+ // the test program to terminate without explanation when
+ // ~thread() finds that 'thread' is still joinable. We could
+ // either join() or detach() it -- but since it might be blocked
+ // waiting for something from the main thread that now can never
+ // happen, it's safer to detach it.
+ thread.detach();
+ throw;
+ }
+ // 'thread' should be all done now
+ thread.join();
+ // deliver any exception thrown by thread_work
+ thread_result.get();
+ ensure("ran changed", LockStatic()->ran);
+ ensure("didn't run on main thread", result);
+ }
+} // namespace tut
diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index 222d832084..f0eafa8201 100644
--- a/indra/llcommon/tests/llprocess_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -493,14 +493,18 @@ namespace tut
}
// std::cout << "child done: rv = " << rv << " (" << manager.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n';
aprchk_("apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT)", wi.rv, APR_CHILD_DONE);
- ensure_equals_(wi.why, APR_PROC_EXIT);
- ensure_equals_(wi.rc, 0);
// Beyond merely executing all the above successfully, verify that we
// obtained expected output -- and that we duly got control while
// waiting, proving the non-blocking nature of these pipes.
try
{
+ // Perform these ensure_equals_() within this try/catch so that if
+ // we don't get expected results, we'll dump whatever we did get
+ // to help diagnose.
+ ensure_equals_(wi.why, APR_PROC_EXIT);
+ ensure_equals_(wi.rc, 0);
+
unsigned i = 0;
ensure("blocking I/O on child pipe (0)", history[i].tries);
ensure_equals_(history[i].which, "out");
diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp
index 6ac974e659..642c1c3879 100644
--- a/indra/llcommon/tests/llsdserialize_test.cpp
+++ b/indra/llcommon/tests/llsdserialize_test.cpp
@@ -271,10 +271,10 @@ namespace tut
LLSD w;
mParser->reset(); // reset() call is needed since test code re-uses mParser
mParser->parse(stream, w, stream.str().size());
-
+
try
{
- ensure_equals(msg.c_str(), w, v);
+ ensure_equals(msg, w, v);
}
catch (...)
{
@@ -432,6 +432,7 @@ namespace tut
const char source[] = "it must be a blue moon again";
std::vector<U8> data;
+ // note, includes terminating '\0'
copy(&source[0], &source[sizeof(source)], back_inserter(data));
v = data;
@@ -468,28 +469,36 @@ namespace tut
checkRoundTrip(msg + " many nested maps", v);
}
- typedef tut::test_group<TestLLSDSerializeData> TestLLSDSerialzeGroup;
- typedef TestLLSDSerialzeGroup::object TestLLSDSerializeObject;
- TestLLSDSerialzeGroup gTestLLSDSerializeGroup("llsd serialization");
+ typedef tut::test_group<TestLLSDSerializeData> TestLLSDSerializeGroup;
+ typedef TestLLSDSerializeGroup::object TestLLSDSerializeObject;
+ TestLLSDSerializeGroup gTestLLSDSerializeGroup("llsd serialization");
template<> template<>
void TestLLSDSerializeObject::test<1>()
{
- mFormatter = new LLSDNotationFormatter();
+ mFormatter = new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_PRETTY_BINARY);
mParser = new LLSDNotationParser();
- doRoundTripTests("notation serialization");
+ doRoundTripTests("pretty binary notation serialization");
}
-
+
template<> template<>
void TestLLSDSerializeObject::test<2>()
{
+ mFormatter = new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_NONE);
+ mParser = new LLSDNotationParser();
+ doRoundTripTests("raw binary notation serialization");
+ }
+
+ template<> template<>
+ void TestLLSDSerializeObject::test<3>()
+ {
mFormatter = new LLSDXMLFormatter();
mParser = new LLSDXMLParser();
doRoundTripTests("xml serialization");
}
-
+
template<> template<>
- void TestLLSDSerializeObject::test<3>()
+ void TestLLSDSerializeObject::test<4>()
{
mFormatter = new LLSDBinaryFormatter();
mParser = new LLSDBinaryParser();
diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp
index 75ddff9d7d..15ffe68e67 100644
--- a/indra/llcommon/tests/llsingleton_test.cpp
+++ b/indra/llcommon/tests/llsingleton_test.cpp
@@ -143,8 +143,6 @@ namespace tut
\
(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); \
} \
@@ -159,10 +157,8 @@ namespace tut
\
(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); \
+ ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \
} \
\
template<> template<> \
@@ -175,10 +171,8 @@ namespace tut
\
(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); \
+ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \
} \
\
template<> template<> \
@@ -191,10 +185,8 @@ namespace tut
\
(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); \
+ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \
}
TESTS(A, B, 4, 5, 6, 7)