diff options
Diffstat (limited to 'indra/llcommon')
-rw-r--r-- | indra/llcommon/CMakeLists.txt | 19 | ||||
-rw-r--r-- | indra/llcommon/fix_macros.h | 4 | ||||
-rw-r--r-- | indra/llcommon/linden_common.h | 1 | ||||
-rw-r--r-- | indra/llcommon/llcoros.cpp | 159 | ||||
-rw-r--r-- | indra/llcommon/llcoros.h | 191 | ||||
-rw-r--r-- | indra/llcommon/llerror.h | 1 | ||||
-rw-r--r-- | indra/llcommon/lleventcoro.cpp | 239 | ||||
-rw-r--r-- | indra/llcommon/lleventcoro.h | 333 | ||||
-rw-r--r-- | indra/llcommon/llevents.cpp | 48 | ||||
-rw-r--r-- | indra/llcommon/llevents.h | 61 | ||||
-rw-r--r-- | indra/llcommon/llmake.h | 63 | ||||
-rw-r--r-- | indra/llcommon/llprocess.cpp | 6 | ||||
-rw-r--r-- | indra/llcommon/llrefcount.h | 9 | ||||
-rw-r--r-- | indra/llcommon/llsdjson.cpp | 126 | ||||
-rw-r--r-- | indra/llcommon/llsdjson.h | 77 | ||||
-rw-r--r-- | indra/llcommon/llstring.h | 2 | ||||
-rw-r--r-- | indra/llcommon/tests/lleventcoro_test.cpp | 752 |
17 files changed, 1362 insertions, 729 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 5863310162..907dbab8f8 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -8,6 +8,7 @@ include(LLCommon) include(Linking) include(Boost) include(LLSharedLibs) +include(JsonCpp) include(GoogleBreakpad) include(GooglePerfTools) include(Copy3rdPartyLibs) @@ -17,6 +18,7 @@ include(URIPARSER) include_directories( ${EXPAT_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} + ${JSONCPP_INCLUDE_DIR} ${ZLIB_INCLUDE_DIRS} ${BREAKPAD_INCLUDE_DIRECTORIES} ${URIPARSER_INCLUDE_DIRS} @@ -86,6 +88,7 @@ set(llcommon_SOURCE_FILES llrefcount.cpp llrun.cpp llsd.cpp + llsdjson.cpp llsdparam.cpp llsdserialize.cpp llsdserialize_xml.cpp @@ -195,6 +198,7 @@ set(llcommon_HEADER_FILES llrefcount.h llsafehandle.h llsd.h + llsdjson.h llsdparam.h llsdserialize.h llsdserialize_xml.h @@ -262,10 +266,14 @@ target_link_libraries( ${APRUTIL_LIBRARIES} ${APR_LIBRARIES} ${EXPAT_LIBRARIES} + ${JSONCPP_LIBRARIES} ${ZLIB_LIBRARIES} ${WINDOWS_LIBRARIES} + ${BOOST_COROUTINE_LIBRARY} + ${BOOST_CONTEXT_LIBRARY} ${BOOST_PROGRAM_OPTIONS_LIBRARY} ${BOOST_REGEX_LIBRARY} + ${BOOST_SYSTEM_LIBRARY} ${GOOGLE_PERFTOOLS_LIBRARIES} ${URIPARSER_LIBRARIES} ) @@ -286,7 +294,14 @@ if (LL_TESTS) LL_ADD_PROJECT_UNIT_TESTS(llcommon "${llcommon_TEST_SOURCE_FILES}") #set(TEST_DEBUG on) - set(test_libs llcommon ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES} ${GOOGLEMOCK_LIBRARIES}) + set(test_libs llcommon + ${LLCOMMON_LIBRARIES} + ${WINDOWS_LIBRARIES} + ${GOOGLEMOCK_LIBRARIES} + ${BOOST_COROUTINE_LIBRARY} + ${BOOST_CONTEXT_LIBRARY} + ${BOOST_THREAD_LIBRARY} + ${BOOST_SYSTEM_LIBRARY}) LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}") LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}") @@ -308,7 +323,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(llunits "" "${test_libs}") LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs};${BOOST_CONTEXT_LIBRARY};${BOOST_THREAD_LIBRARY};${BOOST_COROUTINE_LIBRARY};${BOOST_SYSTEM_LIBRARY}") + LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") diff --git a/indra/llcommon/fix_macros.h b/indra/llcommon/fix_macros.h index ef959decff..43c09c54bc 100644 --- a/indra/llcommon/fix_macros.h +++ b/indra/llcommon/fix_macros.h @@ -16,10 +16,6 @@ // these macros all over again. // who injects MACROS with such generic names?! Grr. -#ifdef equivalent -#undef equivalent -#endif - #ifdef check #undef check #endif diff --git a/indra/llcommon/linden_common.h b/indra/llcommon/linden_common.h index 5cfcdab41c..e5a913a6a9 100644 --- a/indra/llcommon/linden_common.h +++ b/indra/llcommon/linden_common.h @@ -51,6 +51,7 @@ #include <cstdlib> #include <ctime> #include <iosfwd> +#include <memory> // Linden only libs in alpha-order other than stdtypes.h // *NOTE: Please keep includes here to a minimum, see above. diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index baaddcaed1..d16bf0160b 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -39,6 +39,62 @@ #include "llerror.h" #include "stringize.h" +// do nothing, when we need nothing done +void LLCoros::no_cleanup(CoroData*) {} + +// CoroData for the currently-running coroutine. Use a thread_specific_ptr +// because each thread potentially has its own distinct pool of coroutines. +// This thread_specific_ptr does NOT own the CoroData object! That's owned by +// LLCoros::mCoros. It merely identifies it. For this reason we instantiate +// it with a no-op cleanup function. +boost::thread_specific_ptr<LLCoros::CoroData> +LLCoros::sCurrentCoro(LLCoros::no_cleanup); + +//static +LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) +{ + CoroData* current = sCurrentCoro.get(); + if (! current) + { + LL_ERRS("LLCoros") << "Calling " << caller << " from non-coroutine context!" << LL_ENDL; + } + return *current; +} + +//static +LLCoros::coro::self& LLCoros::get_self() +{ + return *get_CoroData("get_self()").mSelf; +} + +//static +void LLCoros::set_consuming(bool consuming) +{ + get_CoroData("set_consuming()").mConsuming = consuming; +} + +//static +bool LLCoros::get_consuming() +{ + return get_CoroData("get_consuming()").mConsuming; +} + +llcoro::Suspending::Suspending(): + mSuspended(LLCoros::sCurrentCoro.get()) +{ + // Revert mCurrentCoro to the value it had at the moment we last switched + // into this coroutine. + LLCoros::sCurrentCoro.reset(mSuspended->mPrev); +} + +llcoro::Suspending::~Suspending() +{ + // Okay, we're back, update our mPrev + mSuspended->mPrev = LLCoros::sCurrentCoro.get(); + // and reinstate our sCurrentCoro. + LLCoros::sCurrentCoro.reset(mSuspended); +} + LLCoros::LLCoros(): // MAINT-2724: default coroutine stack size too small on Windows. // Previously we used @@ -53,14 +109,33 @@ LLCoros::LLCoros(): bool LLCoros::cleanup(const LLSD&) { + static std::string previousName; + static int previousCount = 0; // Walk the mCoros map, checking and removing completed coroutines. for (CoroMap::iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; ) { // Has this coroutine exited (normal return, exception, exit() call) // since last tick? - if (mi->second->exited()) + if (mi->second->mCoro.exited()) { - LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL; + if (previousName != mi->first) + { + previousName = mi->first; + previousCount = 1; + } + else + { + ++previousCount; + } + + if ((previousCount < 5) || !(previousCount % 50)) + { + if (previousCount < 5) + LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL; + else + LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << "("<< previousCount << ")" << LL_ENDL; + + } // The erase() call will invalidate its passed iterator value -- // so increment mi FIRST -- but pass its original value to // erase(). This is what postincrement is all about. @@ -78,6 +153,9 @@ bool LLCoros::cleanup(const LLSD&) std::string LLCoros::generateDistinctName(const std::string& prefix) const { + static std::string previousName; + static int previousCount = 0; + // Allowing empty name would make getName()'s not-found return ambiguous. if (prefix.empty()) { @@ -94,7 +172,25 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const { if (mCoros.find(name) == mCoros.end()) { - LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL; + if (previousName != name) + { + previousName = name; + previousCount = 1; + } + else + { + ++previousCount; + } + + if ((previousCount < 5) || !(previousCount % 50)) + { + if (previousCount < 5) + LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL; + else + LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << "(" << previousCount << ")" << LL_ENDL; + + } + return name; } } @@ -114,20 +210,15 @@ bool LLCoros::kill(const std::string& name) return true; } -std::string LLCoros::getNameByID(const void* self_id) const +std::string LLCoros::getName() const { - // Walk the existing coroutines, looking for one from which the 'self_id' - // passed to us comes. - for (CoroMap::const_iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; ++mi) + CoroData* current = sCurrentCoro.get(); + if (! current) { - namespace coro_private = boost::dcoroutines::detail; - if (static_cast<void*>(coro_private::coroutine_accessor::get_impl(const_cast<coro&>(*mi->second)).get()) - == self_id) - { - return mi->first; - } + // not in a coroutine + return ""; } - return ""; + return current->mName; } void LLCoros::setStackSize(S32 stacksize) @@ -136,10 +227,24 @@ void LLCoros::setStackSize(S32 stacksize) mStackSize = stacksize; } +// Top-level wrapper around caller's coroutine callable. This function accepts +// the coroutine library's implicit coro::self& parameter and sets sCurrentSelf +// but does not pass it down to the caller's callable. +void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& callable) +{ + // capture the 'self' param in CoroData + data->mSelf = &self; + // run the code the caller actually wants in the coroutine + callable(); + // This cleanup isn't perfectly symmetrical with the way we initially set + // data->mPrev, but this is our last chance to reset mCurrentCoro. + sCurrentCoro.reset(data->mPrev); +} + /***************************************************************************** * MUST BE LAST *****************************************************************************/ -// Turn off MSVC optimizations for just LLCoros::launchImpl() -- see +// Turn off MSVC optimizations for just LLCoros::launch() -- see // DEV-32777. But MSVC doesn't support push/pop for optimization flags as it // does for warning suppression, and we really don't want to force // optimization ON for other code even in Debug or RelWithDebInfo builds. @@ -147,15 +252,35 @@ void LLCoros::setStackSize(S32 stacksize) #if LL_MSVC // work around broken optimizations #pragma warning(disable: 4748) +#pragma warning(disable: 4355) // 'this' used in initializer list: yes, intentionally #pragma optimize("", off) #endif // LL_MSVC -std::string LLCoros::launchImpl(const std::string& prefix, coro* newCoro) +LLCoros::CoroData::CoroData(CoroData* prev, const std::string& name, + const callable_t& callable, S32 stacksize): + mPrev(prev), + mName(name), + // Wrap the caller's callable in our toplevel() function so we can manage + // sCurrentCoro appropriately at startup and shutdown of each coroutine. + mCoro(boost::bind(toplevel, _1, this, callable), stacksize), + // don't consume events unless specifically directed + mConsuming(false), + mSelf(0) +{ +} + +std::string LLCoros::launch(const std::string& prefix, const callable_t& callable) { std::string name(generateDistinctName(prefix)); + // pass the current value of sCurrentCoro as previous context + CoroData* newCoro = new CoroData(sCurrentCoro.get(), name, + callable, mStackSize); + // Store it in our pointer map mCoros.insert(name, newCoro); + // also set it as current + sCurrentCoro.reset(newCoro); /* Run the coroutine until its first wait, then return here */ - (*newCoro)(std::nothrow); + (newCoro->mCoro)(std::nothrow); return name; } 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) */ diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 73544cb914..3beef65723 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -354,6 +354,7 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG; #define LL_WARNS(...) lllog(LLError::LEVEL_WARN, false, ##__VA_ARGS__) #define LL_ERRS(...) lllog(LLError::LEVEL_ERROR, false, ##__VA_ARGS__) // alternative to llassert_always that prints explanatory message +#define LL_WARNS_IF(exp, ...) if (exp) LL_WARNS(##__VA_ARGS__) << "(" #exp ")" #define LL_ERRS_IF(exp, ...) if (exp) LL_ERRS(##__VA_ARGS__) << "(" #exp ")" // Only print the log message once (good for warnings or infos that would otherwise diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 81cc33fbba..578a2b62c8 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -38,34 +38,60 @@ #include "llsdserialize.h" #include "llerror.h" #include "llcoros.h" +#include "llmake.h" -std::string LLEventDetail::listenerNameForCoroImpl(const void* self_id) +#include "lleventfilter.h" + +namespace { - // First, if this coroutine was launched by LLCoros::launch(), find that name. - std::string name(LLCoros::instance().getNameByID(self_id)); + +/** + * suspendUntilEventOn() permits a coroutine to temporarily listen on an + * LLEventPump any number of times. We don't really want to have to ask + * the caller to label each such call with a distinct string; the whole + * point of suspendUntilEventOn() is to present a nice sequential interface to + * the underlying LLEventPump-with-named-listeners machinery. So we'll use + * LLEventPump::inventName() to generate a distinct name for each + * temporary listener. On the other hand, because a given coroutine might + * call suspendUntilEventOn() any number of times, we don't really want to + * consume an arbitrary number of generated inventName()s: that namespace, + * though large, is nonetheless finite. So we memoize an invented name for + * each distinct coroutine instance. + */ +std::string listenerNameForCoro() +{ + // If this coroutine was launched by LLCoros::launch(), find that name. + std::string name(LLCoros::instance().getName()); if (! name.empty()) { return name; } - // Apparently this coroutine wasn't launched by LLCoros::launch(). Check - // whether we have a memo for this self_id. - typedef std::map<const void*, std::string> MapType; - static MapType memo; - MapType::const_iterator found = memo.find(self_id); - if (found != memo.end()) - { - // this coroutine instance has called us before, reuse same name - return found->second; - } // this is the first time we've been called for this coroutine instance name = LLEventPump::inventName("coro"); - memo[self_id] = name; - LL_INFOS("LLEventCoro") << "listenerNameForCoroImpl(" << self_id << "): inventing coro name '" + LL_INFOS("LLEventCoro") << "listenerNameForCoro(): inventing coro name '" << name << "'" << LL_ENDL; return name; } -void LLEventDetail::storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value) +/** + * Implement behavior described for postAndSuspend()'s @a replyPumpNamePath + * parameter: + * + * * If <tt>path.isUndefined()</tt>, do nothing. + * * If <tt>path.isString()</tt>, @a dest is an LLSD map: store @a value + * into <tt>dest[path.asString()]</tt>. + * * If <tt>path.isInteger()</tt>, @a dest is an LLSD array: store @a + * value into <tt>dest[path.asInteger()]</tt>. + * * If <tt>path.isArray()</tt>, iteratively apply the rules above to step + * down through the structure of @a dest. The last array entry in @a + * path specifies the entry in the lowest-level structure in @a dest + * into which to store @a value. + * + * @note + * In the degenerate case in which @a path is an empty array, @a dest will + * @em become @a value rather than @em containing it. + */ +void storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value) { if (rawPath.isUndefined()) { @@ -118,6 +144,185 @@ void LLEventDetail::storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& *pdest = value; } +/// For LLCoros::Future<LLSD>::make_callback(), the callback has a signature +/// like void callback(LLSD), which isn't a valid LLEventPump listener: such +/// listeners must return bool. +template <typename LISTENER> +class FutureListener +{ +public: + // FutureListener is instantiated on the coroutine stack: the stack, in + // other words, that wants to suspend. + FutureListener(const LISTENER& listener): + mListener(listener), + // Capture the suspending coroutine's flag as a consuming or + // non-consuming listener. + mConsume(LLCoros::get_consuming()) + {} + + // operator()() is called on the main stack: the stack on which the + // expected event is fired. + bool operator()(const LLSD& event) + { + mListener(event); + // tell upstream LLEventPump whether listener consumed + return mConsume; + } + +protected: + LISTENER mListener; + bool mConsume; +}; + +} // anonymous + +void llcoro::suspend() +{ + // By viewer convention, we post an event on the "mainloop" LLEventPump + // each iteration of the main event-handling loop. So waiting for a single + // event on "mainloop" gives us a one-frame suspend. + suspendUntilEventOn("mainloop"); +} + +void llcoro::suspendUntilTimeout(float seconds) +{ + LLEventTimeout timeout; + + timeout.eventAfter(seconds, LLSD()); + llcoro::suspendUntilEventOn(timeout); +} + +LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath) +{ + // declare the future + LLCoros::Future<LLSD> future; + // make a callback that will assign a value to the future, and listen on + // the specified LLEventPump with that callback + std::string listenerName(listenerNameForCoro()); + LLTempBoundListener connection( + replyPump.getPump().listen(listenerName, + llmake<FutureListener>(future.make_callback()))); + // skip the "post" part if requestPump is default-constructed + if (requestPump) + { + // If replyPumpNamePath is non-empty, store the replyPump name in the + // request event. + LLSD modevent(event); + storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName()); + LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName + << " posting to " << requestPump.getPump().getName() + << LL_ENDL; + + // *NOTE:Mani - Removed because modevent could contain user's hashed passwd. + // << ": " << modevent << LL_ENDL; + requestPump.getPump().post(modevent); + } + LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName + << " about to wait on LLEventPump " << replyPump.getPump().getName() + << LL_ENDL; + // calling get() on the future makes us wait for it + LLSD value(future.get()); + LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName + << " resuming with " << value << LL_ENDL; + // returning should disconnect the connection + return value; +} + +namespace +{ + +/** + * This helper is specifically for postAndSuspend2(). We use a single future + * object, but we want to listen on two pumps with it. Since we must still + * adapt from the callable constructed by boost::dcoroutines::make_callback() + * (void return) to provide an event listener (bool return), we've adapted + * FutureListener for the purpose. The basic idea is that we construct a + * distinct instance of FutureListener2 -- binding different instance data -- + * for each of the pumps. Then, when a pump delivers an LLSD value to either + * FutureListener2, it can combine that LLSD with its discriminator to feed + * the future object. + * + * DISCRIM is a template argument so we can use llmake() rather than + * having to write our own argument-deducing helper function. + */ +template <typename LISTENER, typename DISCRIM> +class FutureListener2: public FutureListener<LISTENER> +{ + typedef FutureListener<LISTENER> super; + +public: + // instantiated on coroutine stack: the stack about to suspend + FutureListener2(const LISTENER& listener, DISCRIM discriminator): + super(listener), + mDiscrim(discriminator) + {} + + // called on main stack: the stack on which event is fired + bool operator()(const LLSD& event) + { + // our future object is defined to accept LLEventWithID + super::mListener(LLEventWithID(event, mDiscrim)); + // tell LLEventPump whether or not event was consumed + return super::mConsume; + } + +private: + const DISCRIM mDiscrim; +}; + +} // anonymous + +namespace llcoro +{ + +LLEventWithID postAndSuspend2(const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump0, + const LLEventPumpOrPumpName& replyPump1, + const LLSD& replyPump0NamePath, + const LLSD& replyPump1NamePath) +{ + // declare the future + LLCoros::Future<LLEventWithID> future; + // either callback will assign a value to this future; listen on + // each specified LLEventPump with a callback + std::string name(listenerNameForCoro()); + LLTempBoundListener connection0( + replyPump0.getPump().listen( + name + "a", + llmake<FutureListener2>(future.make_callback(), 0))); + LLTempBoundListener connection1( + replyPump1.getPump().listen( + name + "b", + llmake<FutureListener2>(future.make_callback(), 1))); + // skip the "post" part if requestPump is default-constructed + if (requestPump) + { + // If either replyPumpNamePath is non-empty, store the corresponding + // replyPump name in the request event. + LLSD modevent(event); + storeToLLSDPath(modevent, replyPump0NamePath, + replyPump0.getPump().getName()); + storeToLLSDPath(modevent, replyPump1NamePath, + replyPump1.getPump().getName()); + LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name + << " posting to " << requestPump.getPump().getName() + << ": " << modevent << LL_ENDL; + requestPump.getPump().post(modevent); + } + LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name + << " about to wait on LLEventPumps " << replyPump0.getPump().getName() + << ", " << replyPump1.getPump().getName() << LL_ENDL; + // calling get() on the future makes us wait for it + LLEventWithID value(future.get()); + LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << name + << " resuming with (" << value.first << ", " << value.second << ")" + << LL_ENDL; + // returning should disconnect both connections + return value; +} + LLSD errorException(const LLEventWithID& result, const std::string& desc) { // If the result arrived on the error pump (pump 1), instead of @@ -144,3 +349,5 @@ LLSD errorLog(const LLEventWithID& result, const std::string& desc) // A simple return must therefore be from the reply pump (pump 0). return result.first; } + +} // namespace llcoro diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h index abbeeaa373..2105faf861 100644 --- a/indra/llcommon/lleventcoro.h +++ b/indra/llcommon/lleventcoro.h @@ -29,11 +29,10 @@ #if ! defined(LL_LLEVENTCORO_H) #define LL_LLEVENTCORO_H -#include <boost/dcoroutine/coroutine.hpp> -#include <boost/dcoroutine/future.hpp> #include <boost/optional.hpp> #include <string> #include <stdexcept> +#include <utility> // std::pair #include "llevents.h" #include "llerror.h" @@ -74,111 +73,46 @@ private: boost::optional<LLEventPump&> mPump; }; -/// This is an adapter for a signature like void LISTENER(const LLSD&), which -/// isn't a valid LLEventPump listener: such listeners should return bool. -template <typename LISTENER> -class LLVoidListener +namespace llcoro { -public: - LLVoidListener(const LISTENER& listener): - mListener(listener) - {} - bool operator()(const LLSD& event) - { - mListener(event); - // don't swallow the event, let other listeners see it - return false; - } -private: - LISTENER mListener; -}; - -/// LLVoidListener helper function to infer the type of the LISTENER -template <typename LISTENER> -LLVoidListener<LISTENER> voidlistener(const LISTENER& listener) -{ - return LLVoidListener<LISTENER>(listener); -} - -namespace LLEventDetail -{ - /// Implementation for listenerNameForCoro(), see below - LL_COMMON_API std::string listenerNameForCoroImpl(const void* self_id); - /** - * waitForEventOn() permits a coroutine to temporarily listen on an - * LLEventPump any number of times. We don't really want to have to ask - * the caller to label each such call with a distinct string; the whole - * point of waitForEventOn() is to present a nice sequential interface to - * the underlying LLEventPump-with-named-listeners machinery. So we'll use - * LLEventPump::inventName() to generate a distinct name for each - * temporary listener. On the other hand, because a given coroutine might - * call waitForEventOn() any number of times, we don't really want to - * consume an arbitrary number of generated inventName()s: that namespace, - * though large, is nonetheless finite. So we memoize an invented name for - * each distinct coroutine instance (each different 'self' object). We - * can't know the type of 'self', because it depends on the coroutine - * body's signature. So we cast its address to void*, looking for distinct - * pointer values. Yes, that means that an early coroutine could cache a - * value here, then be destroyed, only to be supplanted by a later - * coroutine (of the same or different type), and we'll end up - * "recognizing" the second one and reusing the listener name -- but - * that's okay, since it won't collide with any listener name used by the - * earlier coroutine since that earlier coroutine no longer exists. - */ - template <typename COROUTINE_SELF> - std::string listenerNameForCoro(COROUTINE_SELF& self) - { - return listenerNameForCoroImpl(self.get_id()); - } +/** + * Yield control from a coroutine for one "mainloop" tick. If your coroutine + * runs without suspending for nontrivial time, sprinkle in calls to this + * function to avoid stalling the rest of the viewer processing. + */ +void suspend(); - /** - * Implement behavior described for postAndWait()'s @a replyPumpNamePath - * parameter: - * - * * If <tt>path.isUndefined()</tt>, do nothing. - * * If <tt>path.isString()</tt>, @a dest is an LLSD map: store @a value - * into <tt>dest[path.asString()]</tt>. - * * If <tt>path.isInteger()</tt>, @a dest is an LLSD array: store @a - * value into <tt>dest[path.asInteger()]</tt>. - * * If <tt>path.isArray()</tt>, iteratively apply the rules above to step - * down through the structure of @a dest. The last array entry in @a - * path specifies the entry in the lowest-level structure in @a dest - * into which to store @a value. - * - * @note - * In the degenerate case in which @a path is an empty array, @a dest will - * @em become @a value rather than @em containing it. - */ - LL_COMMON_API void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value); -} // namespace LLEventDetail +/** + * Yield control from a coroutine for at least the specified number of seconds + */ +void suspendUntilTimeout(float seconds); /** - * Post specified LLSD event on the specified LLEventPump, then wait for a + * Post specified LLSD event on the specified LLEventPump, then suspend for a * response on specified other LLEventPump. This is more than mere * convenience: the difference between this function and the sequence * @code * requestPump.post(myEvent); - * LLSD reply = waitForEventOn(self, replyPump); + * LLSD reply = suspendUntilEventOn(replyPump); * @endcode * is that the sequence above fails if the reply is posted immediately on * @a replyPump, that is, before <tt>requestPump.post()</tt> returns. In the * sequence above, the running coroutine isn't even listening on @a replyPump - * until <tt>requestPump.post()</tt> returns and @c waitForEventOn() is + * until <tt>requestPump.post()</tt> returns and @c suspendUntilEventOn() is * entered. Therefore, the coroutine completely misses an immediate reply - * event, making it wait indefinitely. + * event, making it suspend indefinitely. * - * By contrast, postAndWait() listens on the @a replyPump @em before posting + * By contrast, postAndSuspend() listens on the @a replyPump @em before posting * the specified LLSD event on the specified @a requestPump. * - * @param self The @c self object passed into a coroutine * @param event LLSD data to be posted on @a requestPump * @param requestPump an LLEventPump on which to post @a event. Pass either * the LLEventPump& or its string name. However, if you pass a * default-constructed @c LLEventPumpOrPumpName, we skip the post() call. - * @param replyPump an LLEventPump on which postAndWait() will listen for a + * @param replyPump an LLEventPump on which postAndSuspend() will listen for a * reply. Pass either the LLEventPump& or its string name. The calling - * coroutine will wait until that reply arrives. (If you're concerned about a + * coroutine will suspend until that reply arrives. (If you're concerned about a * reply that might not arrive, please see also LLEventTimeout.) * @param replyPumpNamePath specifies the location within @a event in which to * store <tt>replyPump.getName()</tt>. This is a strictly optional convenience @@ -201,101 +135,29 @@ namespace LLEventDetail * @a replyPumpNamePath specifies the entry in the lowest-level structure in * @a event into which to store <tt>replyPump.getName()</tt>. */ -template <typename SELF> -LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, - const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD()) -{ - // declare the future - boost::dcoroutines::future<LLSD> future(self); - // make a callback that will assign a value to the future, and listen on - // the specified LLEventPump with that callback - std::string listenerName(LLEventDetail::listenerNameForCoro(self)); - LLTempBoundListener connection( - replyPump.getPump().listen(listenerName, - voidlistener(boost::dcoroutines::make_callback(future)))); - // skip the "post" part if requestPump is default-constructed - if (requestPump) - { - // If replyPumpNamePath is non-empty, store the replyPump name in the - // request event. - LLSD modevent(event); - LLEventDetail::storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName()); - LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName - << " posting to " << requestPump.getPump().getName() - << LL_ENDL; - - // *NOTE:Mani - Removed because modevent could contain user's hashed passwd. - // << ": " << modevent << LL_ENDL; - requestPump.getPump().post(modevent); - } - LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName - << " about to wait on LLEventPump " << replyPump.getPump().getName() - << LL_ENDL; - // trying to dereference ("resolve") the future makes us wait for it - LLSD value(*future); - LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName - << " resuming with " << value << LL_ENDL; - // returning should disconnect the connection - return value; -} +LLSD postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD()); /// Wait for the next event on the specified LLEventPump. Pass either the /// LLEventPump& or its string name. -template <typename SELF> -LLSD waitForEventOn(SELF& self, const LLEventPumpOrPumpName& pump) +inline +LLSD suspendUntilEventOn(const LLEventPumpOrPumpName& pump) { - // This is now a convenience wrapper for postAndWait(). - return postAndWait(self, LLSD(), LLEventPumpOrPumpName(), pump); + // This is now a convenience wrapper for postAndSuspend(). + return postAndSuspend(LLSD(), LLEventPumpOrPumpName(), pump); } -/// return type for two-pump variant of waitForEventOn() +} // namespace llcoro + +/// return type for two-pump variant of suspendUntilEventOn() typedef std::pair<LLSD, int> LLEventWithID; -namespace LLEventDetail +namespace llcoro { - /** - * This helper is specifically for the two-pump version of waitForEventOn(). - * We use a single future object, but we want to listen on two pumps with it. - * Since we must still adapt from (the callable constructed by) - * boost::dcoroutines::make_callback() (void return) to provide an event - * listener (bool return), we've adapted LLVoidListener for the purpose. The - * basic idea is that we construct a distinct instance of WaitForEventOnHelper - * -- binding different instance data -- for each of the pumps. Then, when a - * pump delivers an LLSD value to either WaitForEventOnHelper, it can combine - * that LLSD with its discriminator to feed the future object. - */ - template <typename LISTENER> - class WaitForEventOnHelper - { - public: - WaitForEventOnHelper(const LISTENER& listener, int discriminator): - mListener(listener), - mDiscrim(discriminator) - {} - // this signature is required for an LLEventPump listener - bool operator()(const LLSD& event) - { - // our future object is defined to accept LLEventWithID - mListener(LLEventWithID(event, mDiscrim)); - // don't swallow the event, let other listeners see it - return false; - } - private: - LISTENER mListener; - const int mDiscrim; - }; - - /// WaitForEventOnHelper type-inference helper - template <typename LISTENER> - WaitForEventOnHelper<LISTENER> wfeoh(const LISTENER& listener, int discriminator) - { - return WaitForEventOnHelper<LISTENER>(listener, discriminator); - } -} // namespace LLEventDetail /** * This function waits for a reply on either of two specified LLEventPumps. - * Otherwise, it closely resembles postAndWait(); please see the documentation + * Otherwise, it closely resembles postAndSuspend(); please see the documentation * for that function for detailed parameter info. * * While we could have implemented the single-pump variant in terms of this @@ -310,81 +172,41 @@ namespace LLEventDetail * the index of the pump on which it arrived (0 or 1). * * @note - * I'd have preferred to overload the name postAndWait() for both signatures. + * I'd have preferred to overload the name postAndSuspend() for both signatures. * But consider the following ambiguous call: * @code - * postAndWait(self, LLSD(), requestPump, replyPump, "someString"); + * postAndSuspend(LLSD(), requestPump, replyPump, "someString"); * @endcode * "someString" could be converted to either LLSD (@a replyPumpNamePath for * the single-pump function) or LLEventOrPumpName (@a replyPump1 for two-pump * function). * - * It seems less burdensome to write postAndWait2() than to write either + * It seems less burdensome to write postAndSuspend2() than to write either * LLSD("someString") or LLEventOrPumpName("someString"). */ -template <typename SELF> -LLEventWithID postAndWait2(SELF& self, const LLSD& event, +LLEventWithID postAndSuspend2(const LLSD& event, const LLEventPumpOrPumpName& requestPump, const LLEventPumpOrPumpName& replyPump0, const LLEventPumpOrPumpName& replyPump1, const LLSD& replyPump0NamePath=LLSD(), - const LLSD& replyPump1NamePath=LLSD()) -{ - // declare the future - boost::dcoroutines::future<LLEventWithID> future(self); - // either callback will assign a value to this future; listen on - // each specified LLEventPump with a callback - std::string name(LLEventDetail::listenerNameForCoro(self)); - LLTempBoundListener connection0( - replyPump0.getPump().listen(name + "a", - LLEventDetail::wfeoh(boost::dcoroutines::make_callback(future), 0))); - LLTempBoundListener connection1( - replyPump1.getPump().listen(name + "b", - LLEventDetail::wfeoh(boost::dcoroutines::make_callback(future), 1))); - // skip the "post" part if requestPump is default-constructed - if (requestPump) - { - // If either replyPumpNamePath is non-empty, store the corresponding - // replyPump name in the request event. - LLSD modevent(event); - LLEventDetail::storeToLLSDPath(modevent, replyPump0NamePath, - replyPump0.getPump().getName()); - LLEventDetail::storeToLLSDPath(modevent, replyPump1NamePath, - replyPump1.getPump().getName()); - LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name - << " posting to " << requestPump.getPump().getName() - << ": " << modevent << LL_ENDL; - requestPump.getPump().post(modevent); - } - LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name - << " about to wait on LLEventPumps " << replyPump0.getPump().getName() - << ", " << replyPump1.getPump().getName() << LL_ENDL; - // trying to dereference ("resolve") the future makes us wait for it - LLEventWithID value(*future); - LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << name - << " resuming with (" << value.first << ", " << value.second << ")" - << LL_ENDL; - // returning should disconnect both connections - return value; -} + const LLSD& replyPump1NamePath=LLSD()); /** * Wait for the next event on either of two specified LLEventPumps. */ -template <typename SELF> +inline LLEventWithID -waitForEventOn(SELF& self, - const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1) +suspendUntilEventOn(const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1) { - // This is now a convenience wrapper for postAndWait2(). - return postAndWait2(self, LLSD(), LLEventPumpOrPumpName(), pump0, pump1); + // This is now a convenience wrapper for postAndSuspend2(). + return postAndSuspend2(LLSD(), LLEventPumpOrPumpName(), pump0, pump1); } /** - * Helper for the two-pump variant of waitForEventOn(), e.g.: + * Helper for the two-pump variant of suspendUntilEventOn(), e.g.: * * @code - * LLSD reply = errorException(waitForEventOn(self, replyPump, errorPump), + * LLSD reply = errorException(suspendUntilEventOn(replyPump, errorPump), * "error response from login.cgi"); * @endcode * @@ -400,6 +222,8 @@ waitForEventOn(SELF& self, */ LLSD errorException(const LLEventWithID& result, const std::string& desc); +} // namespace llcoro + /** * Exception thrown by errorException(). We don't call this LLEventError * because it's not an error in event processing: rather, this exception @@ -420,12 +244,17 @@ private: LLSD mData; }; +namespace llcoro +{ + /** * Like errorException(), save that this trips a fatal error using LL_ERRS * rather than throwing an exception. */ LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc); +} // namespace llcoro + /** * Certain event APIs require the name of an LLEventPump on which they should * post results. While it works to invent a distinct name and let @@ -437,7 +266,7 @@ LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc * 2. Provide its actual name to the event API in question as the name of the * reply LLEventPump. * 3. Initiate the request to the event API. - * 4. Call your LLEventTempStream's wait() method to wait for the reply. + * 4. Call your LLEventTempStream's suspend() method to suspend for the reply. * 5. Let the LLCoroEventPump go out of scope. */ class LL_COMMON_API LLCoroEventPump @@ -454,26 +283,16 @@ public: /** * Wait for an event on this LLEventPump. - * - * @note - * The other major usage pattern we considered was to bind @c self at - * LLCoroEventPump construction time, which would avoid passing the - * parameter to each wait() call. But if we were going to bind @c self as - * a class member, we'd need to specify a class template parameter - * indicating its type. The big advantage of passing it to the wait() call - * is that the type can be implicit. */ - template <typename SELF> - LLSD wait(SELF& self) + LLSD suspend() { - return waitForEventOn(self, mPump); + return llcoro::suspendUntilEventOn(mPump); } - template <typename SELF> - LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, + LLSD postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump, const LLSD& replyPumpNamePath=LLSD()) { - return ::postAndWait(self, event, requestPump, mPump, replyPumpNamePath); + return llcoro::postAndSuspend(event, requestPump, mPump, replyPumpNamePath); } private: @@ -509,57 +328,51 @@ public: /// request pump 1 LLEventPump& getPump1() { return mPump1; } - /// waitForEventOn(self, either of our two LLEventPumps) - template <typename SELF> - LLEventWithID wait(SELF& self) + /// suspendUntilEventOn(either of our two LLEventPumps) + LLEventWithID suspend() { - return waitForEventOn(self, mPump0, mPump1); + return llcoro::suspendUntilEventOn(mPump0, mPump1); } - /// errorException(wait(self)) - template <typename SELF> - LLSD waitWithException(SELF& self) + /// errorException(suspend()) + LLSD suspendWithException() { - return errorException(wait(self), std::string("Error event on ") + getName1()); + return llcoro::errorException(suspend(), std::string("Error event on ") + getName1()); } - /// errorLog(wait(self)) - template <typename SELF> - LLSD waitWithLog(SELF& self) + /// errorLog(suspend()) + LLSD suspendWithLog() { - return errorLog(wait(self), std::string("Error event on ") + getName1()); + return llcoro::errorLog(suspend(), std::string("Error event on ") + getName1()); } - template <typename SELF> - LLEventWithID postAndWait(SELF& self, const LLSD& event, + LLEventWithID postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump, const LLSD& replyPump0NamePath=LLSD(), const LLSD& replyPump1NamePath=LLSD()) { - return postAndWait2(self, event, requestPump, mPump0, mPump1, - replyPump0NamePath, replyPump1NamePath); + return llcoro::postAndSuspend2(event, requestPump, mPump0, mPump1, + replyPump0NamePath, replyPump1NamePath); } - template <typename SELF> - LLSD postAndWaitWithException(SELF& self, const LLSD& event, + LLSD postAndSuspendWithException(const LLSD& event, const LLEventPumpOrPumpName& requestPump, const LLSD& replyPump0NamePath=LLSD(), const LLSD& replyPump1NamePath=LLSD()) { - return errorException(postAndWait(self, event, requestPump, - replyPump0NamePath, replyPump1NamePath), - std::string("Error event on ") + getName1()); + return llcoro::errorException(postAndSuspend(event, requestPump, + replyPump0NamePath, replyPump1NamePath), + std::string("Error event on ") + getName1()); } - template <typename SELF> - LLSD postAndWaitWithLog(SELF& self, const LLSD& event, + LLSD postAndSuspendWithLog(const LLSD& event, const LLEventPumpOrPumpName& requestPump, const LLSD& replyPump0NamePath=LLSD(), const LLSD& replyPump1NamePath=LLSD()) { - return errorLog(postAndWait(self, event, requestPump, - replyPump0NamePath, replyPump1NamePath), - std::string("Error event on ") + getName1()); + return llcoro::errorLog(postAndSuspend(event, requestPump, + replyPump0NamePath, replyPump1NamePath), + std::string("Error event on ") + getName1()); } private: diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 1c928b3db8..645c29d770 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -132,6 +132,17 @@ LLEventPump& LLEventPumps::obtain(const std::string& name) return *newInstance; } +bool LLEventPumps::post(const std::string&name, const LLSD&message) +{ + PumpMap::iterator found = mPumpMap.find(name); + + if (found == mPumpMap.end()) + return false; + + return (*found).second->post(message); +} + + void LLEventPumps::flush() { // Flush every known LLEventPump instance. Leave it up to each instance to @@ -497,6 +508,43 @@ bool LLEventStream::post(const LLSD& event) } /***************************************************************************** + * LLEventMailDrop + *****************************************************************************/ +bool LLEventMailDrop::post(const LLSD& event) +{ + bool posted = false; + + if (!mSignal->empty()) + posted = LLEventStream::post(event); + + if (!posted) + { // if the event was not handled we will save it for later so that it can + // be posted to any future listeners when they attach. + mEventHistory.push_back(event); + } + + return posted; +} + +LLBoundListener LLEventMailDrop::listen_impl(const std::string& name, + const LLEventListener& listener, + const NameList& after, + const NameList& before) +{ + if (!mEventHistory.empty()) + { + if (listener(mEventHistory.front())) + { + mEventHistory.pop_front(); + } + } + + return LLEventStream::listen_impl(name, listener, after, before); + +} + + +/***************************************************************************** * LLEventQueue *****************************************************************************/ bool LLEventQueue::post(const LLSD& event) diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 0cbd1da32d..6175329a9d 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -217,6 +217,18 @@ public: * an instance without conferring @em ownership. */ LLEventPump& obtain(const std::string& name); + + /** + * Find the named LLEventPump instance. If it exists post the message to it. + * If the pump does not exist, do nothing. + * + * returns the result of the LLEventPump::post. If no pump exists returns false. + * + * This is syntactically similar to LLEventPumps::instance().post(name, message), + * however if the pump does not already exist it will not be created. + */ + bool post(const std::string&, const LLSD&); + /** * Flush all known LLEventPump instances */ @@ -496,7 +508,7 @@ public: // at the boost::bind object itself before that happens. return LLEventDetail::visit_and_connect(name, listener, - boost::bind(&LLEventPump::listen_impl, + boost::bind(&LLEventPump::listen_invoke, this, name, _1, @@ -541,13 +553,23 @@ private: virtual void reset(); + + private: - virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, - const NameList& after, - const NameList& before); + LLBoundListener listen_invoke(const std::string& name, const LLEventListener& listener, + const NameList& after, + const NameList& before) + { + return this->listen_impl(name, listener, after, before); + } + std::string mName; protected: + virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + const NameList& after, + const NameList& before); + /// implement the dispatching boost::shared_ptr<LLStandardSignal> mSignal; @@ -586,10 +608,39 @@ public: }; /***************************************************************************** + * LLEventMailDrop + *****************************************************************************/ +/** + * LLEventMailDrop is a specialization of LLEventStream. Events are posted normally, + * however if no listeners return that they have handled the event it is placed in + * a queue. Subsequent attaching listeners will receive stored events from the queue + * until a listener indicates that the event has been handled. In order to receive + * multiple events from a mail drop the listener must disconnect and reconnect. + */ +class LL_COMMON_API LLEventMailDrop : public LLEventStream +{ +public: + LLEventMailDrop(const std::string& name, bool tweak = false) : LLEventStream(name, tweak) {} + virtual ~LLEventMailDrop() {} + + /// Post an event to all listeners + virtual bool post(const LLSD& event); + +protected: + virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + const NameList& after, + const NameList& before); + +private: + typedef std::list<LLSD> EventList; + EventList mEventHistory; +}; + +/***************************************************************************** * LLEventQueue *****************************************************************************/ /** - * LLEventQueue isa LLEventPump whose post() method defers calling registered + * LLEventQueue is a LLEventPump whose post() method defers calling registered * listeners until flush() is called. */ class LL_COMMON_API LLEventQueue: public LLEventPump diff --git a/indra/llcommon/llmake.h b/indra/llcommon/llmake.h new file mode 100644 index 0000000000..9a662a0640 --- /dev/null +++ b/indra/llcommon/llmake.h @@ -0,0 +1,63 @@ +/** + * @file llmake.h + * @author Nat Goodspeed + * @date 2015-12-18 + * @brief Generic llmake<Template>(arg) function to instantiate + * Template<decltype(arg)>(arg). + * + * Many of our class templates have an accompanying helper function to + * make an instance with arguments of arbitrary type. llmake() + * eliminates the need to declare a new helper function for every such + * class template. + * + * also relevant: + * + * Template parameter deduction for constructors + * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html + * + * https://github.com/viboes/std-make + * + * but obviously we're not there yet. + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Copyright (c) 2015, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLMAKE_H) +#define LL_LLMAKE_H + +/*==========================================================================*| +// When we allow ourselves to compile with C++11 features enabled, this form +// should generically handle an arbitrary number of arguments. + +template <template<typename...> class CLASS_TEMPLATE, typename... ARGS> +CLASS_TEMPLATE<ARGS...> llmake(ARGS && ... args) +{ + return CLASS_TEMPLATE<ARGS...>(std::forward<ARGS>(args)...); +} +|*==========================================================================*/ + +// As of 2015-12-18, this is what we'll use instead. Add explicit overloads +// for different numbers of template parameters as use cases arise. + +/** + * Usage: llmake<SomeTemplate>(arg) + * + * Deduces the type T of 'arg' and returns an instance of SomeTemplate<T> + * initialized with 'arg'. Assumes a constructor accepting T (by value, + * reference or whatever). + */ +template <template<typename> class CLASS_TEMPLATE, typename ARG1> +CLASS_TEMPLATE<ARG1> llmake(const ARG1& arg1) +{ + return CLASS_TEMPLATE<ARG1>(arg1); +} + +template <template<typename, typename> class CLASS_TEMPLATE, typename ARG1, typename ARG2> +CLASS_TEMPLATE<ARG1, ARG2> llmake(const ARG1& arg1, const ARG2& arg2) +{ + return CLASS_TEMPLATE<ARG1, ARG2>(arg1, arg2); +} + +#endif /* ! defined(LL_LLMAKE_H) */ diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index e0aa30cc1a..44f56daf2d 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -710,7 +710,7 @@ LLProcess::LLProcess(const LLSDOrParams& params): // Tie the lifespan of this child process to the lifespan of our APR // pool: on destruction of the pool, forcibly kill the process. Tell - // APR to try SIGTERM and wait 3 seconds. If that didn't work, use + // APR to try SIGTERM and suspend 3 seconds. If that didn't work, use // SIGKILL. apr_pool_note_subprocess(gAPRPoolp, &mProcess, APR_KILL_AFTER_TIMEOUT); |*==========================================================================*/ @@ -989,9 +989,9 @@ void LLProcess::handle_status(int reason, int status) // wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT); // It's just wrong to call apr_proc_wait() here. The only way APR knows to // call us with APR_OC_REASON_DEATH is that it's already reaped this child - // process, so calling wait() will only produce "huh?" from the OS. We + // process, so calling suspend() will only produce "huh?" from the OS. We // must rely on the status param passed in, which unfortunately comes - // straight from the OS wait() call, which means we have to decode it by + // straight from the OS suspend() call, which means we have to decode it by // hand. mStatus = interpret_status(status); LL_INFOS("LLProcess") << getStatusString() << LL_ENDL; diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h index 3836a9b5fb..1107973569 100644 --- a/indra/llcommon/llrefcount.h +++ b/indra/llcommon/llrefcount.h @@ -144,14 +144,9 @@ private: }; /** - * intrusive pointer support - * this allows you to use boost::intrusive_ptr with any LLRefCount-derived type - */ -/** * intrusive pointer support for LLThreadSafeRefCount * this allows you to use boost::intrusive_ptr with any LLThreadSafeRefCount-derived type */ - inline void intrusive_ptr_add_ref(LLThreadSafeRefCount* p) { p->ref(); @@ -162,6 +157,10 @@ inline void intrusive_ptr_release(LLThreadSafeRefCount* p) p->unref(); } +/** + * intrusive pointer support + * this allows you to use boost::intrusive_ptr with any LLRefCount-derived type + */ inline void intrusive_ptr_add_ref(LLRefCount* p) { p->ref(); diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp new file mode 100644 index 0000000000..8caaaee534 --- /dev/null +++ b/indra/llcommon/llsdjson.cpp @@ -0,0 +1,126 @@ +/** + * @file llsdjson.cpp + * @brief LLSD flexible data system + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2015, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Must turn on conditional declarations in header file so definitions end up +// with proper linkage. +#define LLSD_DEBUG_INFO +#include "linden_common.h" + +#include "llsdjson.h" + +#include "llerror.h" +#include "../llmath/llmath.h" + +//========================================================================= +LLSD LlsdFromJson(const Json::Value &val) +{ + LLSD result; + + switch (val.type()) + { + default: + case Json::nullValue: + break; + case Json::intValue: + result = LLSD(static_cast<LLSD::Integer>(val.asInt())); + break; + case Json::uintValue: + result = LLSD(static_cast<LLSD::Integer>(val.asUInt())); + break; + case Json::realValue: + result = LLSD(static_cast<LLSD::Real>(val.asDouble())); + break; + case Json::stringValue: + result = LLSD(static_cast<LLSD::String>(val.asString())); + break; + case Json::booleanValue: + result = LLSD(static_cast<LLSD::Boolean>(val.asBool())); + break; + case Json::arrayValue: + result = LLSD::emptyArray(); + for (Json::ValueConstIterator it = val.begin(); it != val.end(); ++it) + { + result.append(LlsdFromJson((*it))); + } + break; + case Json::objectValue: + result = LLSD::emptyMap(); + for (Json::ValueConstIterator it = val.begin(); it != val.end(); ++it) + { + result[it.memberName()] = LlsdFromJson((*it)); + } + break; + } + return result; +} + +//========================================================================= +Json::Value LlsdToJson(const LLSD &val) +{ + Json::Value result; + + switch (val.type()) + { + case LLSD::TypeUndefined: + result = Json::Value::null; + break; + case LLSD::TypeBoolean: + result = Json::Value(static_cast<bool>(val.asBoolean())); + break; + case LLSD::TypeInteger: + result = Json::Value(static_cast<int>(val.asInteger())); + break; + case LLSD::TypeReal: + result = Json::Value(static_cast<double>(val.asReal())); + break; + case LLSD::TypeURI: + case LLSD::TypeDate: + case LLSD::TypeUUID: + case LLSD::TypeString: + result = Json::Value(val.asString()); + break; + case LLSD::TypeMap: + result = Json::Value(Json::objectValue); + for (LLSD::map_const_iterator it = val.beginMap(); it != val.endMap(); ++it) + { + result[it->first] = LlsdToJson(it->second); + } + break; + case LLSD::TypeArray: + result = Json::Value(Json::arrayValue); + for (LLSD::array_const_iterator it = val.beginArray(); it != val.endArray(); ++it) + { + result.append(LlsdToJson(*it)); + } + break; + case LLSD::TypeBinary: + default: + LL_ERRS("LlsdToJson") << "Unsupported conversion to JSON from LLSD type (" << val.type() << ")." << LL_ENDL; + break; + } + + return result; +} diff --git a/indra/llcommon/llsdjson.h b/indra/llcommon/llsdjson.h new file mode 100644 index 0000000000..2be7112404 --- /dev/null +++ b/indra/llcommon/llsdjson.h @@ -0,0 +1,77 @@ +/** + * @file llsdjson.cpp + * @brief LLSD flexible data system + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2015, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSDJSON_H +#define LL_LLSDJSON_H + +#include <map> +#include <string> +#include <vector> + +#include "stdtypes.h" + +#include "llsd.h" +#include "value.h" + +/// Convert a parsed JSON structure into LLSD maintaining member names and +/// array indexes. +/// JSON/JavaScript types are converted as follows: +/// +/// JSON Type | LLSD Type +/// --------------+-------------- +/// null | undefined +/// integer | LLSD::Integer +/// unsigned | LLSD::Integer +/// real/numeric | LLSD::Real +/// string | LLSD::String +/// boolean | LLSD::Boolean +/// array | LLSD::Array +/// object | LLSD::Map +/// +/// For maps and arrays child entries will be converted and added to the structure. +/// Order is preserved for an array but not for objects. +LLSD LlsdFromJson(const Json::Value &val); + +/// Convert an LLSD object into Parsed JSON object maintaining member names and +/// array indexs. +/// +/// Types are converted as follows: +/// LLSD Type | JSON Type +/// --------------+---------------- +/// TypeUndefined | null +/// TypeBoolean | boolean +/// TypeInteger | integer +/// TypeReal | real/numeric +/// TypeString | string +/// TypeURI | string +/// TypeDate | string +/// TypeUUID | string +/// TypeMap | object +/// TypeArray | array +/// TypeBinary | unsupported +Json::Value LlsdToJson(const LLSD &val); + +#endif // LL_LLSDJSON_H diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 0177f48bf5..393f6d7a8c 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -1394,6 +1394,7 @@ BOOL LLStringUtilBase<T>::containsNonprintable(const string_type& string) return rv; } +// *TODO: reimplement in terms of algorithm //static template<class T> void LLStringUtilBase<T>::stripNonprintable(string_type& string) @@ -1427,6 +1428,7 @@ void LLStringUtilBase<T>::stripNonprintable(string_type& string) delete []c_string; } +// *TODO: reimplement in terms of algorithm template<class T> std::basic_string<T> LLStringUtilBase<T>::quote(const string_type& str, const string_type& triggers, diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index 2096807e53..a459d17fb8 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -59,17 +59,17 @@ // http://www.boost.org/LICENSE_1_0.txt) /*****************************************************************************/ +#define BOOST_RESULT_OF_USE_TR1 1 // On some platforms, Boost.Coroutine must #define magic symbols before // #including platform-API headers. Naturally, that's ineffective unless the // Boost.Coroutine #include is the *first* #include of the platform header. // That means that client code must generally #include Boost.Coroutine headers // before anything else. #include <boost/dcoroutine/coroutine.hpp> -// Normally, lleventcoro.h obviates future.hpp. We only include this because -// we implement a "by hand" test of future functionality. -#include <boost/dcoroutine/future.hpp> #include <boost/bind.hpp> #include <boost/range.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> #include "linden_common.h" @@ -82,9 +82,12 @@ #include "llevents.h" #include "tests/wrapllerrs.h" #include "stringize.h" +#include "llcoros.h" #include "lleventcoro.h" #include "../test/debug.h" +using namespace llcoro; + /***************************************************************************** * from the banana.cpp example program borrowed for test<1>() *****************************************************************************/ @@ -121,13 +124,10 @@ typedef coroutine<std::string::iterator(void)> match_coroutine_type; /***************************************************************************** * Test helpers *****************************************************************************/ -// I suspect this will be typical of coroutines used in Linden software -typedef boost::dcoroutines::coroutine<void()> coroutine_type; - /// Simulate an event API whose response is immediate: sent on receipt of the /// initial request, rather than after some delay. This is the case that -/// distinguishes postAndWait() from calling post(), then calling -/// waitForEventOn(). +/// distinguishes postAndSuspend() from calling post(), then calling +/// suspendUntilEventOn(). class ImmediateAPI { public: @@ -162,306 +162,7 @@ private: *****************************************************************************/ namespace tut { - struct coroutine_data - { - // Define coroutine bodies as methods here so they can use ensure*() - - void explicit_wait(coroutine_type::self& self) - { - BEGIN - { - // ... do whatever preliminary stuff must happen ... - - // declare the future - boost::dcoroutines::future<LLSD> future(self); - // tell the future what to wait for - LLTempBoundListener connection( - LLEventPumps::instance().obtain("source").listen("coro", voidlistener(boost::dcoroutines::make_callback(future)))); - ensure("Not yet", ! future); - // attempting to dereference ("resolve") the future causes the calling - // coroutine to wait for it - debug("about to wait"); - result = *future; - ensure("Got it", future); - } - END - } - - void waitForEventOn1(coroutine_type::self& self) - { - BEGIN - { - result = waitForEventOn(self, "source"); - } - END - } - - void waitForEventOn2(coroutine_type::self& self) - { - BEGIN - { - LLEventWithID pair = waitForEventOn(self, "reply", "error"); - result = pair.first; - which = pair.second; - debug(STRINGIZE("result = " << result << ", which = " << which)); - } - END - } - - void postAndWait1(coroutine_type::self& self) - { - BEGIN - { - result = postAndWait(self, - LLSDMap("value", 17), // request event - immediateAPI.getPump(), // requestPump - "reply1", // replyPump - "reply"); // request["reply"] = name - } - END - } - - void postAndWait2(coroutine_type::self& self) - { - BEGIN - { - LLEventWithID pair = ::postAndWait2(self, - LLSDMap("value", 18), - immediateAPI.getPump(), - "reply2", - "error2", - "reply", - "error"); - result = pair.first; - which = pair.second; - debug(STRINGIZE("result = " << result << ", which = " << which)); - } - END - } - - void postAndWait2_1(coroutine_type::self& self) - { - BEGIN - { - LLEventWithID pair = ::postAndWait2(self, - LLSDMap("value", 18)("fail", LLSD()), - immediateAPI.getPump(), - "reply2", - "error2", - "reply", - "error"); - result = pair.first; - which = pair.second; - debug(STRINGIZE("result = " << result << ", which = " << which)); - } - END - } - - void coroPump(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPump waiter; - replyName = waiter.getName(); - result = waiter.wait(self); - } - END - } - - void coroPumpPost(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPump waiter; - result = waiter.postAndWait(self, LLSDMap("value", 17), - immediateAPI.getPump(), "reply"); - } - END - } - - void coroPumps(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPumps waiter; - replyName = waiter.getName0(); - errorName = waiter.getName1(); - LLEventWithID pair(waiter.wait(self)); - result = pair.first; - which = pair.second; - } - END - } - - void coroPumpsNoEx(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPumps waiter; - replyName = waiter.getName0(); - errorName = waiter.getName1(); - result = waiter.waitWithException(self); - } - END - } - - void coroPumpsEx(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPumps waiter; - replyName = waiter.getName0(); - errorName = waiter.getName1(); - try - { - result = waiter.waitWithException(self); - debug("no exception"); - } - catch (const LLErrorEvent& e) - { - debug(STRINGIZE("exception " << e.what())); - errordata = e.getData(); - } - } - END - } - - void coroPumpsNoLog(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPumps waiter; - replyName = waiter.getName0(); - errorName = waiter.getName1(); - result = waiter.waitWithLog(self); - } - END - } - - void coroPumpsLog(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPumps waiter; - replyName = waiter.getName0(); - errorName = waiter.getName1(); - WrapLLErrs capture; - try - { - result = waiter.waitWithLog(self); - debug("no exception"); - } - catch (const WrapLLErrs::FatalException& e) - { - debug(STRINGIZE("exception " << e.what())); - threw = e.what(); - } - } - END - } - - void coroPumpsPost(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPumps waiter; - LLEventWithID pair(waiter.postAndWait(self, LLSDMap("value", 23), - immediateAPI.getPump(), "reply", "error")); - result = pair.first; - which = pair.second; - } - END - } - - void coroPumpsPost_1(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPumps waiter; - LLEventWithID pair( - waiter.postAndWait(self, LLSDMap("value", 23)("fail", LLSD()), - immediateAPI.getPump(), "reply", "error")); - result = pair.first; - which = pair.second; - } - END - } - - void coroPumpsPostNoEx(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPumps waiter; - result = waiter.postAndWaitWithException(self, LLSDMap("value", 8), - immediateAPI.getPump(), "reply", "error"); - } - END - } - - void coroPumpsPostEx(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPumps waiter; - try - { - result = waiter.postAndWaitWithException(self, - LLSDMap("value", 9)("fail", LLSD()), - immediateAPI.getPump(), "reply", "error"); - debug("no exception"); - } - catch (const LLErrorEvent& e) - { - debug(STRINGIZE("exception " << e.what())); - errordata = e.getData(); - } - } - END - } - - void coroPumpsPostNoLog(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPumps waiter; - result = waiter.postAndWaitWithLog(self, LLSDMap("value", 30), - immediateAPI.getPump(), "reply", "error"); - } - END - } - - void coroPumpsPostLog(coroutine_type::self& self) - { - BEGIN - { - LLCoroEventPumps waiter; - WrapLLErrs capture; - try - { - result = waiter.postAndWaitWithLog(self, - LLSDMap("value", 31)("fail", LLSD()), - immediateAPI.getPump(), "reply", "error"); - debug("no exception"); - } - catch (const WrapLLErrs::FatalException& e) - { - debug(STRINGIZE("exception " << e.what())); - threw = e.what(); - } - } - END - } - - void ensure_done(coroutine_type& coro) - { - ensure("coroutine complete", ! coro); - } - - ImmediateAPI immediateAPI; - std::string replyName, errorName, threw; - LLSD result, errordata; - int which; - }; + struct coroutine_data {}; typedef test_group<coroutine_data> coroutine_group; typedef coroutine_group::object object; coroutine_group coroutinegrp("coroutine"); @@ -511,54 +212,122 @@ namespace tut ensure("done", ! matcher); } + // use static data so we can intersperse coroutine functions with the + // tests that engage them + ImmediateAPI immediateAPI; + std::string replyName, errorName, threw, stringdata; + LLSD result, errordata; + int which; + + // reinit vars at the start of each test + void clear() + { + replyName.clear(); + errorName.clear(); + threw.clear(); + stringdata.clear(); + result = LLSD(); + errordata = LLSD(); + which = 0; + } + + void explicit_wait(boost::shared_ptr<LLCoros::Future<std::string>::callback_t>& cbp) + { + BEGIN + { + // The point of this test is to verify / illustrate suspending a + // coroutine for something other than an LLEventPump. In other + // words, this shows how to adapt to any async operation that + // provides a callback-style notification (and prove that it + // works). + + LLCoros::Future<std::string> future; + // get the callback from that future + LLCoros::Future<std::string>::callback_t callback(future.make_callback()); + + // Perhaps we would send a request to a remote server and arrange + // for 'callback' to be called on response. Of course that might + // involve an adapter object from the actual callback signature to + // the signature of 'callback' -- in this case, void(std::string). + // For test purposes, instead of handing 'callback' (or the + // adapter) off to some I/O subsystem, we'll just pass it back to + // our caller. + cbp.reset(new LLCoros::Future<std::string>::callback_t(callback)); + + ensure("Not yet", ! future); + // calling get() on the future causes us to suspend + debug("about to suspend"); + stringdata = future.get(); + ensure("Got it", bool(future)); + } + END + } + template<> template<> void object::test<2>() { + clear(); set_test_name("explicit_wait"); DEBUG; // Construct the coroutine instance that will run explicit_wait. - // Pass the ctor a callable that accepts the coroutine_type::self - // param passed by the library. - coroutine_type coro(boost::bind(&coroutine_data::explicit_wait, this, _1)); - // Start the coroutine - coro(std::nothrow); - // When the coroutine waits for the event pump, it returns here. - debug("about to send"); - // Satisfy the wait. - LLEventPumps::instance().obtain("source").post("received"); - // Now wait for the coroutine to complete. - ensure_done(coro); + boost::shared_ptr<LLCoros::Future<std::string>::callback_t> respond; + LLCoros::instance().launch("test<2>", + boost::bind(explicit_wait, boost::ref(respond))); + // When the coroutine waits for the future, it returns here. + debug("about to respond"); + // Now we're the I/O subsystem delivering a result. This immediately + // transfers control back to the coroutine. + (*respond)("received"); // ensure the coroutine ran and woke up again with the intended result - ensure_equals(result.asString(), "received"); + ensure_equals(stringdata, "received"); + } + + void waitForEventOn1() + { + BEGIN + { + result = suspendUntilEventOn("source"); + } + END } template<> template<> void object::test<3>() { + clear(); set_test_name("waitForEventOn1"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn1, this, _1)); - coro(std::nothrow); + LLCoros::instance().launch("test<3>", waitForEventOn1); debug("about to send"); LLEventPumps::instance().obtain("source").post("received"); debug("back from send"); - ensure_done(coro); ensure_equals(result.asString(), "received"); } + void waitForEventOn2() + { + BEGIN + { + LLEventWithID pair = suspendUntilEventOn("reply", "error"); + result = pair.first; + which = pair.second; + debug(STRINGIZE("result = " << result << ", which = " << which)); + } + END + } + template<> template<> void object::test<4>() { + clear(); set_test_name("waitForEventOn2 reply"); { DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn2, this, _1)); - coro(std::nothrow); + LLCoros::instance().launch("test<4>", waitForEventOn2); debug("about to send"); LLEventPumps::instance().obtain("reply").post("received"); debug("back from send"); - ensure_done(coro); } ensure_equals(result.asString(), "received"); ensure_equals("which pump", which, 0); @@ -567,43 +336,65 @@ namespace tut template<> template<> void object::test<5>() { + clear(); set_test_name("waitForEventOn2 error"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn2, this, _1)); - coro(std::nothrow); + LLCoros::instance().launch("test<5>", waitForEventOn2); debug("about to send"); LLEventPumps::instance().obtain("error").post("badness"); debug("back from send"); - ensure_done(coro); ensure_equals(result.asString(), "badness"); ensure_equals("which pump", which, 1); } + void coroPump() + { + BEGIN + { + LLCoroEventPump waiter; + replyName = waiter.getName(); + result = waiter.suspend(); + } + END + } + template<> template<> void object::test<6>() { + clear(); set_test_name("coroPump"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPump, this, _1)); - coro(std::nothrow); + LLCoros::instance().launch("test<6>", coroPump); debug("about to send"); LLEventPumps::instance().obtain(replyName).post("received"); debug("back from send"); - ensure_done(coro); ensure_equals(result.asString(), "received"); } + void coroPumps() + { + BEGIN + { + LLCoroEventPumps waiter; + replyName = waiter.getName0(); + errorName = waiter.getName1(); + LLEventWithID pair(waiter.suspend()); + result = pair.first; + which = pair.second; + } + END + } + template<> template<> void object::test<7>() { + clear(); set_test_name("coroPumps reply"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumps, this, _1)); - coro(std::nothrow); + LLCoros::instance().launch("test<7>", coroPumps); debug("about to send"); LLEventPumps::instance().obtain(replyName).post("received"); debug("back from send"); - ensure_done(coro); ensure_equals(result.asString(), "received"); ensure_equals("which pump", which, 0); } @@ -611,188 +402,389 @@ namespace tut template<> template<> void object::test<8>() { + clear(); set_test_name("coroPumps error"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumps, this, _1)); - coro(std::nothrow); + LLCoros::instance().launch("test<8>", coroPumps); debug("about to send"); LLEventPumps::instance().obtain(errorName).post("badness"); debug("back from send"); - ensure_done(coro); ensure_equals(result.asString(), "badness"); ensure_equals("which pump", which, 1); } + void coroPumpsNoEx() + { + BEGIN + { + LLCoroEventPumps waiter; + replyName = waiter.getName0(); + errorName = waiter.getName1(); + result = waiter.suspendWithException(); + } + END + } + template<> template<> void object::test<9>() { + clear(); set_test_name("coroPumpsNoEx"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumpsNoEx, this, _1)); - coro(std::nothrow); + LLCoros::instance().launch("test<9>", coroPumpsNoEx); debug("about to send"); LLEventPumps::instance().obtain(replyName).post("received"); debug("back from send"); - ensure_done(coro); ensure_equals(result.asString(), "received"); } + void coroPumpsEx() + { + BEGIN + { + LLCoroEventPumps waiter; + replyName = waiter.getName0(); + errorName = waiter.getName1(); + try + { + result = waiter.suspendWithException(); + debug("no exception"); + } + catch (const LLErrorEvent& e) + { + debug(STRINGIZE("exception " << e.what())); + errordata = e.getData(); + } + } + END + } + template<> template<> void object::test<10>() { + clear(); set_test_name("coroPumpsEx"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumpsEx, this, _1)); - coro(std::nothrow); + LLCoros::instance().launch("test<10>", coroPumpsEx); debug("about to send"); LLEventPumps::instance().obtain(errorName).post("badness"); debug("back from send"); - ensure_done(coro); ensure("no result", result.isUndefined()); ensure_equals("got error", errordata.asString(), "badness"); } + void coroPumpsNoLog() + { + BEGIN + { + LLCoroEventPumps waiter; + replyName = waiter.getName0(); + errorName = waiter.getName1(); + result = waiter.suspendWithLog(); + } + END + } + template<> template<> void object::test<11>() { + clear(); set_test_name("coroPumpsNoLog"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumpsNoLog, this, _1)); - coro(std::nothrow); + LLCoros::instance().launch("test<11>", coroPumpsNoLog); debug("about to send"); LLEventPumps::instance().obtain(replyName).post("received"); debug("back from send"); - ensure_done(coro); ensure_equals(result.asString(), "received"); } + void coroPumpsLog() + { + BEGIN + { + LLCoroEventPumps waiter; + replyName = waiter.getName0(); + errorName = waiter.getName1(); + WrapLLErrs capture; + try + { + result = waiter.suspendWithLog(); + debug("no exception"); + } + catch (const WrapLLErrs::FatalException& e) + { + debug(STRINGIZE("exception " << e.what())); + threw = e.what(); + } + } + END + } + template<> template<> void object::test<12>() { + clear(); set_test_name("coroPumpsLog"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumpsLog, this, _1)); - coro(std::nothrow); + LLCoros::instance().launch("test<12>", coroPumpsLog); debug("about to send"); LLEventPumps::instance().obtain(errorName).post("badness"); debug("back from send"); - ensure_done(coro); ensure("no result", result.isUndefined()); ensure_contains("got error", threw, "badness"); } + void postAndWait1() + { + BEGIN + { + result = postAndSuspend(LLSDMap("value", 17), // request event + immediateAPI.getPump(), // requestPump + "reply1", // replyPump + "reply"); // request["reply"] = name + } + END + } + template<> template<> void object::test<13>() { + clear(); set_test_name("postAndWait1"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::postAndWait1, this, _1)); - coro(std::nothrow); - ensure_done(coro); + LLCoros::instance().launch("test<13>", postAndWait1); ensure_equals(result.asInteger(), 18); } + void postAndWait2() + { + BEGIN + { + LLEventWithID pair = ::postAndSuspend2(LLSDMap("value", 18), + immediateAPI.getPump(), + "reply2", + "error2", + "reply", + "error"); + result = pair.first; + which = pair.second; + debug(STRINGIZE("result = " << result << ", which = " << which)); + } + END + } + template<> template<> void object::test<14>() { + clear(); set_test_name("postAndWait2"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::postAndWait2, this, _1)); - coro(std::nothrow); - ensure_done(coro); + LLCoros::instance().launch("test<14>", postAndWait2); ensure_equals(result.asInteger(), 19); ensure_equals(which, 0); } + void postAndWait2_1() + { + BEGIN + { + LLEventWithID pair = ::postAndSuspend2(LLSDMap("value", 18)("fail", LLSD()), + immediateAPI.getPump(), + "reply2", + "error2", + "reply", + "error"); + result = pair.first; + which = pair.second; + debug(STRINGIZE("result = " << result << ", which = " << which)); + } + END + } + template<> template<> void object::test<15>() { + clear(); set_test_name("postAndWait2_1"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::postAndWait2_1, this, _1)); - coro(std::nothrow); - ensure_done(coro); + LLCoros::instance().launch("test<15>", postAndWait2_1); ensure_equals(result.asInteger(), 19); ensure_equals(which, 1); } + void coroPumpPost() + { + BEGIN + { + LLCoroEventPump waiter; + result = waiter.postAndSuspend(LLSDMap("value", 17), + immediateAPI.getPump(), "reply"); + } + END + } + template<> template<> void object::test<16>() { + clear(); set_test_name("coroPumpPost"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumpPost, this, _1)); - coro(std::nothrow); - ensure_done(coro); + LLCoros::instance().launch("test<16>", coroPumpPost); ensure_equals(result.asInteger(), 18); } + void coroPumpsPost() + { + BEGIN + { + LLCoroEventPumps waiter; + LLEventWithID pair(waiter.postAndSuspend(LLSDMap("value", 23), + immediateAPI.getPump(), "reply", "error")); + result = pair.first; + which = pair.second; + } + END + } + template<> template<> void object::test<17>() { + clear(); set_test_name("coroPumpsPost reply"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPost, this, _1)); - coro(std::nothrow); - ensure_done(coro); + LLCoros::instance().launch("test<17>", coroPumpsPost); ensure_equals(result.asInteger(), 24); ensure_equals("which pump", which, 0); } + void coroPumpsPost_1() + { + BEGIN + { + LLCoroEventPumps waiter; + LLEventWithID pair( + waiter.postAndSuspend(LLSDMap("value", 23)("fail", LLSD()), + immediateAPI.getPump(), "reply", "error")); + result = pair.first; + which = pair.second; + } + END + } + template<> template<> void object::test<18>() { + clear(); set_test_name("coroPumpsPost error"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPost_1, this, _1)); - coro(std::nothrow); - ensure_done(coro); + LLCoros::instance().launch("test<18>", coroPumpsPost_1); ensure_equals(result.asInteger(), 24); ensure_equals("which pump", which, 1); } + void coroPumpsPostNoEx() + { + BEGIN + { + LLCoroEventPumps waiter; + result = waiter.postAndSuspendWithException(LLSDMap("value", 8), + immediateAPI.getPump(), "reply", "error"); + } + END + } + template<> template<> void object::test<19>() { + clear(); set_test_name("coroPumpsPostNoEx"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostNoEx, this, _1)); - coro(std::nothrow); - ensure_done(coro); + LLCoros::instance().launch("test<19>", coroPumpsPostNoEx); ensure_equals(result.asInteger(), 9); } + void coroPumpsPostEx() + { + BEGIN + { + LLCoroEventPumps waiter; + try + { + result = waiter.postAndSuspendWithException( + LLSDMap("value", 9)("fail", LLSD()), + immediateAPI.getPump(), "reply", "error"); + debug("no exception"); + } + catch (const LLErrorEvent& e) + { + debug(STRINGIZE("exception " << e.what())); + errordata = e.getData(); + } + } + END + } + template<> template<> void object::test<20>() { + clear(); set_test_name("coroPumpsPostEx"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostEx, this, _1)); - coro(std::nothrow); - ensure_done(coro); + LLCoros::instance().launch("test<20>", coroPumpsPostEx); ensure("no result", result.isUndefined()); ensure_equals("got error", errordata.asInteger(), 10); } + void coroPumpsPostNoLog() + { + BEGIN + { + LLCoroEventPumps waiter; + result = waiter.postAndSuspendWithLog(LLSDMap("value", 30), + immediateAPI.getPump(), "reply", "error"); + } + END + } + template<> template<> void object::test<21>() { + clear(); set_test_name("coroPumpsPostNoLog"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostNoLog, this, _1)); - coro(std::nothrow); - ensure_done(coro); + LLCoros::instance().launch("test<21>", coroPumpsPostNoLog); ensure_equals(result.asInteger(), 31); } + void coroPumpsPostLog() + { + BEGIN + { + LLCoroEventPumps waiter; + WrapLLErrs capture; + try + { + result = waiter.postAndSuspendWithLog( + LLSDMap("value", 31)("fail", LLSD()), + immediateAPI.getPump(), "reply", "error"); + debug("no exception"); + } + catch (const WrapLLErrs::FatalException& e) + { + debug(STRINGIZE("exception " << e.what())); + threw = e.what(); + } + } + END + } + template<> template<> void object::test<22>() { + clear(); set_test_name("coroPumpsPostLog"); DEBUG; - coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostLog, this, _1)); - coro(std::nothrow); - ensure_done(coro); + LLCoros::instance().launch("test<22>", coroPumpsPostLog); ensure("no result", result.isUndefined()); ensure_contains("got error", threw, "32"); } |