DRTVWR-476: Infrastructure to help manage long-lived coroutines.
Introduce LLCoros::Stop exception, with subclasses Stopping, Stopped and Shutdown. Add LLCoros::checkStop(), intended to be called periodically by any coroutine with nontrivial lifespan. It checks the LLApp status and, unless isRunning(), throws one of these new exceptions. Make LLCoros::toplevel() catch Stop specially and log forcible coroutine termination. Now that LLApp status matters even in a test program, introduce a trivial LLTestApp subclass whose sole function is to make isRunning() true. (LLApp::setStatus() is protected: only a subclass can call it.) Add LLTestApp instances to lleventcoro_test.cpp and lllogin_test.cpp. Make LLCoros::toplevel() accept parameters by value rather than by const reference so we can continue using them even after context switches. Make private LLCoros::get_CoroData() static. Given that we've observed some coroutines living past LLCoros destruction, making the caller call LLCoros::instance() is more dangerous than encapsulating it within a static method -- since the encapsulated call can check LLCoros::wasDeleted() first and do something reasonable instead. This also eliminates the need for both a const and non-const overload. Defend LLCoros::delete_CoroData() (cleanup function for fiber_specific_ptr for CoroData, implicitly called after coroutine termination) against calls after ~LLCoros(). Add a status string to coroutine-local data, with LLCoro::setStatus(), getStatus() and RAII class TempStatus. Add an optional 'when' string argument to LLCoros::printActiveCoroutines(). Make ~LLCoros() print the coroutines still active at destruction.master
parent
253e360062
commit
28a54c2f7b
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) */
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue