summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/llcoros.cpp105
-rw-r--r--indra/llcommon/llcoros.h66
-rw-r--r--indra/llcommon/tests/lleventcoro_test.cpp2
-rw-r--r--indra/test/lltestapp.h34
-rw-r--r--indra/viewer_components/login/tests/lllogin_test.cpp2
5 files changed, 181 insertions, 28 deletions
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index 37bcfc3242..78a0c5d225 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -48,6 +48,7 @@
#undef BOOST_DISABLE_ASSERTS
#endif
// other Linden headers
+#include "llapp.h"
#include "lltimer.h"
#include "llevents.h"
#include "llerror.h"
@@ -58,10 +59,15 @@
#include <excpt.h>
#endif
-
-const LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) const
+// static
+LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
{
- CoroData* current = mCurrent.get();
+ CoroData* current{ nullptr };
+ // be careful about attempted accesses in the final throes of app shutdown
+ if (! wasDeleted())
+ {
+ 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.
@@ -78,12 +84,6 @@ const LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) const
return *current;
}
-LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
-{
- // reuse const implementation, just cast away const-ness of result
- return const_cast<CoroData&>(const_cast<const LLCoros*>(this)->get_CoroData(caller));
-}
-
//static
LLCoros::coro::id LLCoros::get_self()
{
@@ -93,7 +93,7 @@ LLCoros::coro::id LLCoros::get_self()
//static
void LLCoros::set_consuming(bool consuming)
{
- CoroData& data(LLCoros::instance().get_CoroData("set_consuming()"));
+ CoroData& data(get_CoroData("set_consuming()"));
// DO NOT call this on the main() coroutine.
llassert_always(! data.mName.empty());
data.mConsuming = consuming;
@@ -102,7 +102,19 @@ void LLCoros::set_consuming(bool consuming)
//static
bool LLCoros::get_consuming()
{
- return LLCoros::instance().get_CoroData("get_consuming()").mConsuming;
+ return get_CoroData("get_consuming()").mConsuming;
+}
+
+// static
+void LLCoros::setStatus(const std::string& status)
+{
+ get_CoroData("setStatus()").mStatus = status;
+}
+
+// static
+std::string LLCoros::getStatus()
+{
+ return get_CoroData("getStatus()").mStatus;
}
LLCoros::LLCoros():
@@ -118,6 +130,11 @@ LLCoros::LLCoros():
{
}
+LLCoros::~LLCoros()
+{
+ printActiveCoroutines("at LLCoros destruction");
+}
+
std::string LLCoros::generateDistinctName(const std::string& prefix) const
{
static int unique = 0;
@@ -166,19 +183,19 @@ 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;
+ LL_INFOS("LLCoros") << "Number of active coroutines " << when
+ << ": " << (S32)mCoros.size() << LL_ENDL;
if (mCoros.size() > 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 (const auto& pair : mCoros)
{
- F64 life_time = time - iter->second->mCreationTime;
- LL_CONT << LL_NEWLINE << "Name: " << iter->first << " life: " << life_time;
+ F64 life_time = time - pair.second->mCreationTime;
+ LL_CONT << LL_NEWLINE << pair.first << ' ' << pair.second->mStatus
+ << " life: " << life_time;
}
LL_CONT << LL_ENDL;
LL_INFOS("LLCoros") << "-----------------------------------------------------" << LL_ENDL;
@@ -244,11 +261,19 @@ void LLCoros::winlevel(const callable_t& callable)
#endif
// Top-level wrapper around caller's coroutine callable.
-void LLCoros::toplevel(const std::string& name, const callable_t& 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)
{
CoroData* corodata = new CoroData(name);
- // Store it in our pointer map. Oddly, must cast away const-ness of key.
- mCoros.insert(const_cast<std::string&>(name), corodata);
+ if (corodata == 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, corodata);
// also set it as current
mCurrent.reset(corodata);
@@ -261,18 +286,39 @@ void LLCoros::toplevel(const std::string& name, const callable_t& callable)
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 " << corodata->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 " << corodata->mName));
+ CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name));
+ }
+}
+
+void LLCoros::checkStop()
+{
+ if (wasDeleted())
+ {
+ LLTHROW(Shutdown("LLCoros was deleted"));
+ }
+ if (LLApp::isStopped())
+ {
+ LLTHROW(Stopped("viewer is stopped"));
+ }
+ if (! LLApp::isRunning())
+ {
+ LLTHROW(Stopping("viewer is stopping"));
}
}
@@ -288,6 +334,19 @@ void LLCoros::delete_CoroData(CoroData* cdptr)
{
// This custom cleanup function is necessarily static. Find and bind the
// LLCoros instance.
+ // In case the LLCoros instance has already been deleted, just warn and
+ // scoot. We do NOT want to reinstantiate LLCoros during shutdown!
+ if (wasDeleted())
+ {
+ // The LLSingletons involved in logging may have been deleted too.
+ // This warning may help developers track down coroutines that have
+ // not yet been cleaned up.
+ // But cdptr is very likely a dangling pointer by this time, so don't
+ // try to dereference mName.
+ logwarns("Coroutine terminating after LLCoros instance deleted");
+ return;
+ }
+
LLCoros& self(LLCoros::instance());
// We set mCurrent on entry to a new fiber, expecting that the
// corresponding entry has already been stored in mCoros. It is an
diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h
index dedb6c8eca..de7b691284 100644
--- a/indra/llcommon/llcoros.h
+++ b/indra/llcommon/llcoros.h
@@ -29,6 +29,7 @@
#if ! defined(LL_LLCOROS_H)
#define LL_LLCOROS_H
+#include "llexception.h"
#include <boost/fiber/fss.hpp>
#include <boost/fiber/future/promise.hpp>
#include <boost/fiber/future/future.hpp>
@@ -74,6 +75,7 @@
class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
{
LLSINGLETON(LLCoros);
+ ~LLCoros();
public:
/// The viewer's use of the term "coroutine" became deeply embedded before
/// the industry term "fiber" emerged to distinguish userland threads from
@@ -147,8 +149,8 @@ public:
*/
void setStackSize(S32 stacksize);
- /// for delayed initialization
- void printActiveCoroutines();
+ /// diagnostic
+ void printActiveCoroutines(const std::string& when=std::string());
/// get the current coro::id for those who really really care
static coro::id get_self();
@@ -176,6 +178,7 @@ public:
{
set_consuming(consuming);
}
+ OverrideConsuming(const OverrideConsuming&) = delete;
~OverrideConsuming()
{
set_consuming(mPrevConsuming);
@@ -185,6 +188,58 @@ 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()
+ 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();
+
/**
* Aliases for promise and future. An older underlying future implementation
* required us to wrap future; that's no longer needed. However -- if it's
@@ -204,13 +259,12 @@ public:
private:
std::string generateDistinctName(const std::string& prefix) const;
- void toplevel(const std::string& name, const callable_t& callable);
+ void toplevel(std::string name, callable_t callable);
struct CoroData;
#if LL_WINDOWS
static void winlevel(const callable_t& callable);
#endif
- CoroData& get_CoroData(const std::string& caller);
- const CoroData& get_CoroData(const std::string& caller) const;
+ static CoroData& get_CoroData(const std::string& caller);
S32 mStackSize;
@@ -223,6 +277,8 @@ private:
const std::string mName;
// set_consuming() state
bool mConsuming;
+ // setStatus() state
+ std::string mStatus;
F64 mCreationTime; // since epoch
};
typedef boost::ptr_map<std::string, CoroData> CoroMap;
diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp
index c13920eefd..032923a108 100644
--- a/indra/llcommon/tests/lleventcoro_test.cpp
+++ b/indra/llcommon/tests/lleventcoro_test.cpp
@@ -40,6 +40,7 @@
#include <typeinfo>
#include "../test/lltut.h"
+#include "../test/lltestapp.h"
#include "llsd.h"
#include "llsdutil.h"
#include "llevents.h"
@@ -98,6 +99,7 @@ namespace tut
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();
diff --git a/indra/test/lltestapp.h b/indra/test/lltestapp.h
new file mode 100644
index 0000000000..382516cd2b
--- /dev/null
+++ b/indra/test/lltestapp.h
@@ -0,0 +1,34 @@
+/**
+ * @file lltestapp.h
+ * @author Nat Goodspeed
+ * @date 2019-10-21
+ * @brief LLApp subclass useful for testing.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLTESTAPP_H)
+#define LL_LLTESTAPP_H
+
+#include "llapp.h"
+
+/**
+ * LLTestApp is a dummy LLApp that simply sets LLApp::isRunning() for anyone
+ * who cares.
+ */
+class LLTestApp: public LLApp
+{
+public:
+ LLTestApp()
+ {
+ setStatus(APP_STATUS_RUNNING);
+ }
+
+ bool init() { return true; }
+ bool cleanup() { return true; }
+ bool frame() { return true; }
+};
+
+#endif /* ! defined(LL_LLTESTAPP_H) */
diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp
index 23db8d0fe3..0255e10e53 100644
--- a/indra/viewer_components/login/tests/lllogin_test.cpp
+++ b/indra/viewer_components/login/tests/lllogin_test.cpp
@@ -42,6 +42,7 @@
// other Linden headers
#include "llsd.h"
#include "../../../test/lltut.h"
+#include "../../../test/lltestapp.h"
//#define DEBUG_ON
#include "../../../test/debug.h"
#include "llevents.h"
@@ -201,6 +202,7 @@ namespace tut
pumps.clear();
}
LLEventPumps& pumps;
+ LLTestApp testApp;
};
typedef test_group<llviewerlogin_data> llviewerlogin_group;