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.h280
1 files changed, 141 insertions, 139 deletions
diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h
index c551413811..6c0bec3ef9 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,125 @@ 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&);
- struct CoroData;
- static void no_cleanup(CoroData*);
#if LL_WINDOWS
- static void winlevel(const callable_t& callable);
+ void winlevel(const std::string& name, const callable_t& callable);
#endif
- static void toplevel(coro::self& self, CoroData* data, const callable_t& callable);
+ void toplevelTryWrapper(const std::string& name, const callable_t& callable);
+ void toplevel(std::string name, callable_t callable);
+ struct CoroData;
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;
+inline
+std::string logname() { return LLCoros::logname(); }
-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;
- }
-
- T get()
- {
- // instantiate Suspending to manage the "current" coroutine
- llcoro::Suspending suspended;
- return *mFuture;
- }
-
-private:
- dfuture mFuture;
-};
+} // llcoro
#endif /* ! defined(LL_LLCOROS_H) */