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.h191
1 files changed, 154 insertions, 37 deletions
diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h
index 01ee11da1a..39316ed0e6 100644
--- a/indra/llcommon/llcoros.h
+++ b/indra/llcommon/llcoros.h
@@ -30,14 +30,20 @@
#define LL_LLCOROS_H
#include <boost/dcoroutine/coroutine.hpp>
+#include <boost/dcoroutine/future.hpp>
#include "llsingleton.h"
#include <boost/ptr_container/ptr_map.hpp>
+#include <boost/function.hpp>
+#include <boost/thread/tss.hpp>
#include <string>
-#include <boost/preprocessor/repetition/enum_params.hpp>
-#include <boost/preprocessor/repetition/enum_binary_params.hpp>
-#include <boost/preprocessor/iteration/local.hpp>
#include <stdexcept>
+// forward-declare helper class
+namespace llcoro
+{
+class Suspending;
+}
+
/**
* Registry of named Boost.Coroutine instances
*
@@ -80,8 +86,8 @@ class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
public:
/// Canonical boost::dcoroutines::coroutine signature we use
typedef boost::dcoroutines::coroutine<void()> coro;
- /// Canonical 'self' type
- typedef coro::self self;
+ /// Canonical callable type
+ typedef boost::function<void()> callable_t;
/**
* Create and start running a new coroutine with specified name. The name
@@ -94,39 +100,33 @@ public:
* {
* public:
* ...
- * // Do NOT NOT NOT accept reference params other than 'self'!
+ * // Do NOT NOT NOT accept reference params!
* // Pass by value only!
- * void myCoroutineMethod(LLCoros::self& self, std::string, LLSD);
+ * void myCoroutineMethod(std::string, LLSD);
* ...
* };
* ...
* std::string name = LLCoros::instance().launch(
- * "mycoro", boost::bind(&MyClass::myCoroutineMethod, this, _1,
+ * "mycoro", boost::bind(&MyClass::myCoroutineMethod, this,
* "somestring", LLSD(17));
* @endcode
*
- * Your function/method must accept LLCoros::self& as its first parameter.
- * It can accept any other parameters you want -- but ONLY BY VALUE!
- * Other reference parameters are a BAD IDEA! You Have Been Warned. See
+ * Your function/method can accept any parameters you want -- but ONLY BY
+ * VALUE! Reference parameters are a BAD IDEA! You Have Been Warned. See
* DEV-32777 comments for an explanation.
*
- * Pass a callable that accepts the single LLCoros::self& parameter. It
- * may work to pass a free function whose only parameter is 'self'; for
- * all other cases use boost::bind(). Of course, for a non-static class
- * method, the first parameter must be the class instance. Use the
- * placeholder _1 for the 'self' parameter. Any other parameters should be
- * passed via the bind() expression.
+ * 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.
*
* launch() tweaks the suggested name so it won't collide with any
* existing coroutine instance, creates the coroutine instance, registers
* it with the tweaked name and runs it until its first wait. At that
* point it returns the tweaked name.
*/
- template <typename CALLABLE>
- std::string launch(const std::string& prefix, const CALLABLE& callable)
- {
- return launchImpl(prefix, new coro(callable, mStackSize));
- }
+ std::string launch(const std::string& prefix, const callable_t& callable);
/**
* Abort a running coroutine by name. Normally, when a coroutine either
@@ -138,33 +138,150 @@ public:
bool kill(const std::string& name);
/**
- * From within a coroutine, pass its @c self object to 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()).
+ * 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()).
*/
- template <typename COROUTINE_SELF>
- std::string getName(const COROUTINE_SELF& self) const
- {
- return getNameByID(self.get_id());
- }
-
- /// getName() by self.get_id()
- std::string getNameByID(const void* self_id) const;
+ std::string getName() const;
/// for delayed initialization
void setStackSize(S32 stacksize);
+ /// get the current coro::self& for those who really really care
+ static coro::self& get_self();
+
+ /**
+ * Most coroutines, most of the time, don't "consume" the events for which
+ * they're suspending. This way, an arbitrary number of listeners (whether
+ * coroutines or simple callbacks) can be registered on a particular
+ * LLEventPump, every listener responding to each of the events on that
+ * LLEventPump. But a particular coroutine can assert that it will consume
+ * each event for which it suspends. (See also llcoro::postAndSuspend(),
+ * llcoro::VoidListener)
+ */
+ static void set_consuming(bool consuming);
+ static bool get_consuming();
+
+ /**
+ * 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.
+ */
+ template <typename T>
+ class Future;
+
private:
- friend class LLSingleton<LLCoros>;
LLCoros();
- std::string launchImpl(const std::string& prefix, coro* newCoro);
+ friend class LLSingleton<LLCoros>;
+ friend class llcoro::Suspending;
std::string generateDistinctName(const std::string& prefix) const;
bool cleanup(const LLSD&);
+ struct CoroData;
+ static void no_cleanup(CoroData*);
+ static void toplevel(coro::self& self, CoroData* data, const callable_t& callable);
+ static CoroData& get_CoroData(const std::string& caller);
S32 mStackSize;
- typedef boost::ptr_map<std::string, coro> CoroMap;
+
+ // coroutine-local storage, as it were: one per coro we track
+ struct CoroData
+ {
+ 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;
+ // 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;
+ };
+ typedef boost::ptr_map<std::string, CoroData> CoroMap;
CoroMap mCoros;
+
+ // identify the current coroutine's CoroData
+ static boost::thread_specific_ptr<LLCoros::CoroData> sCurrentCoro;
+};
+
+namespace llcoro
+{
+
+/// Instantiate one of these in a block surrounding any leaf point when
+/// control literally switches away from this coroutine.
+class Suspending
+{
+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;
+ }
+
+ T get()
+ {
+ // instantiate Suspending to manage the "current" coroutine
+ llcoro::Suspending suspended;
+ return *mFuture;
+ }
+
+private:
+ dfuture mFuture;
};
#endif /* ! defined(LL_LLCOROS_H) */