summaryrefslogtreecommitdiff
path: root/indra/llcommon/llcoros.h
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/llcoros.h')
-rw-r--r--indra/llcommon/llcoros.h209
1 files changed, 112 insertions, 97 deletions
diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h
index c3820ae987..1edcb7e387 100644
--- a/indra/llcommon/llcoros.h
+++ b/indra/llcommon/llcoros.h
@@ -29,63 +29,46 @@
#if ! defined(LL_LLCOROS_H)
#define LL_LLCOROS_H
+#include "llcoromutex.h"
+#include "llevents.h"
#include "llexception.h"
-#include <boost/fiber/fss.hpp>
-#include <boost/fiber/future/future.hpp>
-#include <boost/fiber/future/promise.hpp>
-#include <boost/fiber/recursive_mutex.hpp>
-#include "mutex.h"
-#include "llsingleton.h"
#include "llinstancetracker.h"
-#include <boost/function.hpp>
-#include <string>
+#include "llsingleton.h"
+#include <boost/fiber/fss.hpp>
#include <exception>
+#include <functional>
+#include <map>
#include <queue>
+#include <string>
+#include <unordered_map>
-// 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;
- }
+namespace llcoro
+{
+class scheduler;
}
/**
- * Registry of named Boost.Coroutine instances
- *
- * The Boost.Coroutine library supports the general case of a coroutine
- * accepting arbitrary parameters and yielding multiple (sets of) results. For
- * such use cases, it's natural for the invoking code to retain the coroutine
- * instance: the consumer repeatedly calls into the coroutine, perhaps passing
- * new parameter values, prompting it to yield its next result.
+ * Registry of named Boost.Fiber instances
*
- * Our typical coroutine usage is different, though. For us, coroutines
- * provide an alternative to the @c Responder pattern. Our typical coroutine
- * has @c void return, invoked in fire-and-forget mode: the handler for some
- * user gesture launches the coroutine and promptly returns to the main loop.
- * The coroutine initiates some action that will take multiple frames (e.g. a
- * capability request), waits for its result, processes it and silently steals
- * away.
+ * When the viewer first introduced the semi-independent execution agents now
+ * called fibers, the term "fiber" had not yet become current, and the only
+ * available libraries used the term "coroutine" instead. Within the viewer we
+ * continue to use the term "coroutines," though at present they are actually
+ * boost::fibers::fiber instances.
*
- * This usage poses two (related) problems:
- *
- * # Who should own the coroutine instance? If it's simply local to the
- * handler code that launches it, return from the handler will destroy the
- * coroutine object, terminating the coroutine.
- * # Once the coroutine terminates, in whatever way, who's responsible for
- * cleaning up the coroutine object?
+ * Coroutines provide an alternative to the @c Responder pattern. Our typical
+ * coroutine has @c void return, invoked in fire-and-forget mode: the handler
+ * for some user gesture launches the coroutine and promptly returns to the
+ * main loop. The coroutine initiates some action that will take multiple
+ * frames (e.g. a capability request), waits for its result, processes it and
+ * silently steals away.
*
* LLCoros is a Singleton collection of currently-active coroutine instances.
* Each has a name. You ask LLCoros to launch a new coroutine with a suggested
* name prefix; from your prefix it generates a distinct name, registers the
* new coroutine and returns the actual name.
*
- * The name
- * can 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.
*/
class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
@@ -105,14 +88,10 @@ public:
// llassert(LLCoros::on_main_thread_main_coro())
static bool on_main_thread_main_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;
+ typedef coro::id id;
/// Canonical callable type
- typedef boost::function<void()> callable_t;
+ typedef std::function<void()> callable_t;
/**
* Create and start running a new coroutine with specified name. The name
@@ -154,23 +133,26 @@ public:
std::string launch(const std::string& prefix, const callable_t& callable);
/**
- * Abort a running coroutine by name. Normally, when a coroutine either
+ * Ask the named coroutine to abort. Normally, when a coroutine either
* runs to completion or terminates with an exception, LLCoros quietly
* cleans it up. This is for use only when you must explicitly interrupt
* one prematurely. Returns @c true if the specified name was found and
* still running at the time.
*/
-// bool kill(const std::string& name);
+ bool killreq(const std::string& name);
/**
* From within a coroutine, look up the (tweaked) name string by which
- * this coroutine is registered. Returns the empty string if not found
- * (e.g. if the coroutine was launched by hand rather than using
- * LLCoros::launch()).
+ * this coroutine is registered.
*/
static std::string getName();
/**
+ * Given an id, return the name of that coroutine.
+ */
+ static std::string getName(id);
+
+ /**
* rethrow() is called by the thread's main fiber to propagate an
* exception from any coroutine into the main fiber, where it can engage
* the normal unhandled-exception machinery, up to and including crash
@@ -184,13 +166,6 @@ public:
void rethrow();
/**
- * 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.
@@ -201,7 +176,7 @@ public:
void printActiveCoroutines(const std::string& when=std::string());
/// get the current coro::id for those who really really care
- static coro::id get_self();
+ static id get_self();
/**
* Most coroutines, most of the time, don't "consume" the events for which
@@ -250,6 +225,7 @@ public:
setStatus(status);
}
TempStatus(const TempStatus&) = delete;
+ TempStatus& operator=(const TempStatus&) = delete;
~TempStatus()
{
setStatus(mOldStatus);
@@ -262,15 +238,21 @@ public:
/// 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.
+ // not immediately crash the viewer. Stop and its subclasses are to tell
+ // coroutines to terminate, e.g. because the viewer is shutting down. We
+ // do not want any such exception to crash the viewer.
struct Stop: public LLContinueError
{
Stop(const std::string& what): LLContinueError(what) {}
};
- /// early stages
+ /// someone wants to kill this specific coroutine
+ struct Killed: public Stop
+ {
+ Killed(const std::string& what): Stop(what) {}
+ };
+
+ /// early shutdown stages
struct Stopping: public Stop
{
Stopping(const std::string& what): Stop(what) {}
@@ -289,46 +271,68 @@ public:
};
/// 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();
+ /// still be running at application shutdown. Throws one of the Stop
+ /// subclasses if the caller needs to terminate. Pass a cleanup function
+ /// if you need to execute that cleanup before terminating.
+ /// Of course, if your cleanup function throws, that will be the exception
+ /// propagated by checkStop().
+ static void checkStop(callable_t cleanup={});
+
+ /// Call getStopListener() at the source end of a queue, promise or other
+ /// resource on which coroutines will wait, so that shutdown can wake up
+ /// consuming coroutines. @a caller should distinguish who's calling. The
+ /// passed @a cleanup function must close the queue, break the promise or
+ /// otherwise cause waiting consumers to wake up in an abnormal way. It's
+ /// advisable to store the returned LLBoundListener in an
+ /// LLTempBoundListener, or otherwise arrange to disconnect it.
+ static LLBoundListener getStopListener(const std::string& caller, LLVoidListener cleanup);
+
+ /// This getStopListener() overload is like the two-argument one, for use
+ /// when we know the name of the only coroutine that will wait on the
+ /// resource in question. Pass @a consumer as the empty string if the
+ /// consumer coroutine is the same as the calling coroutine. Unlike the
+ /// two-argument getStopListener(), this one also responds to
+ /// killreq(target).
+ static LLBoundListener getStopListener(const std::string& caller,
+ const std::string& consumer,
+ LLVoidListener cleanup);
/**
- * 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.
+ * LLCoros aliases for promise and future, for backwards compatibility.
+ * These have been hoisted out to the llcoro namespace.
*/
template <typename T>
- using Promise = boost::fibers::promise<T>;
+ using Promise = llcoro::Promise<T>;
template <typename T>
- using Future = boost::fibers::future<T>;
+ using Future = llcoro::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 RMutex = boost::fibers::recursive_mutex;
- // With C++17, LockType is deprecated: at this point we can directly
- // declare 'std::unique_lock lk(some_mutex)' without explicitly stating
- // the mutex type. Sadly, making LockType an alias template for
- // std::unique_lock doesn't work the same way: Class Template Argument
- // Deduction only works for class templates, not alias templates.
- using LockType = std::unique_lock<Mutex>;
- using cv_status = boost::fibers::cv_status;
- using ConditionVariable = boost::fibers::condition_variable;
+ using Mutex = llcoro::Mutex;
+ using RMutex = llcoro::RMutex;
+ // LockType is deprecated; see llcoromutex.h
+ using LockType = llcoro::LockType;
+ using cv_status = llcoro::cv_status;
+ using ConditionVariable = llcoro::ConditionVariable;
/// for data local to each running coroutine
template <typename T>
using local_ptr = boost::fibers::fiber_specific_ptr<T>;
private:
+ friend class llcoro::scheduler;
+
std::string generateDistinctName(const std::string& prefix) const;
void toplevel(std::string name, callable_t callable);
struct CoroData;
- static CoroData& get_CoroData(const std::string& caller);
+ static CoroData& get_CoroData();
+ static CoroData& get_CoroData(id);
+ static CoroData& main_CoroData();
void saveException(const std::string& name, std::exception_ptr exc);
+ LLTempBoundListener mConn;
+
struct ExceptionData
{
ExceptionData(const std::string& nm, std::exception_ptr exc):
@@ -345,32 +349,43 @@ private:
S32 mStackSize;
// coroutine-local storage, as it were: one per coro we track
- struct CoroData: public LLInstanceTracker<CoroData, std::string>
+ struct CoroData: public LLInstanceTracker<CoroData, id>
{
+ using super = LLInstanceTracker<CoroData, id>;
+
CoroData(const std::string& name);
- CoroData(int n);
+ ~CoroData();
+ std::string getName() const;
+
+ bool isMain{ false };
// tweaked name of the current coroutine
- const std::string mName;
- // set_consuming() state
- bool mConsuming;
+ std::string mName;
+ // set_consuming() state -- don't consume events unless specifically directed
+ bool mConsuming{ false };
+ // killed by which coroutine
+ std::string mKilledBy;
// setStatus() state
std::string mStatus;
F64 mCreationTime; // since epoch
+ // Histogram of how many times this coroutine's timeslice exceeds
+ // certain thresholds. mHistogram is pre-populated with those
+ // thresholds as keys. If k0 is one threshold key and k1 is the next,
+ // mHistogram[k0] is the number of times a coroutine timeslice tn ran
+ // (k0 <= tn < k1). A timeslice less than mHistogram.begin()->first is
+ // fine; we don't need to record those.
+ std::map<F64, U32> mHistogram;
};
// 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
-{
-
-inline
-std::string logname() { return LLCoros::logname(); }
-
-} // llcoro
+ // ensure name uniqueness
+ static thread_local std::unordered_map<std::string, int> mPrefixMap;
+ // lookup by name
+ static thread_local std::unordered_map<std::string, id> mNameMap;
+};
#endif /* ! defined(LL_LLCOROS_H) */