diff options
27 files changed, 331 insertions, 1404 deletions
| diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake index 180a84dbcf..e05e3ca0e5 100644 --- a/indra/cmake/Boost.cmake +++ b/indra/cmake/Boost.cmake @@ -8,7 +8,7 @@ if (USESYSTEMLIBS)    include(FindBoost)    set(BOOST_CONTEXT_LIBRARY boost_context-mt) -  set(BOOST_COROUTINE_LIBRARY boost_coroutine-mt) +  set(BOOST_FIBER_LIBRARY boost_fiber-mt)    set(BOOST_FILESYSTEM_LIBRARY boost_filesystem-mt)    set(BOOST_PROGRAM_OPTIONS_LIBRARY boost_program_options-mt)    set(BOOST_REGEX_LIBRARY boost_regex-mt) @@ -49,9 +49,9 @@ else (USESYSTEMLIBS)        set(BOOST_CONTEXT_LIBRARY             optimized libboost_context-mt            debug libboost_context-mt-gd) -      set(BOOST_COROUTINE_LIBRARY  -          optimized libboost_coroutine-mt -          debug libboost_coroutine-mt-gd) +      set(BOOST_FIBER_LIBRARY  +          optimized libboost_fiber-mt +          debug libboost_fiber-mt-gd)        set(BOOST_FILESYSTEM_LIBRARY             optimized libboost_filesystem-mt            debug libboost_filesystem-mt-gd) @@ -75,9 +75,9 @@ else (USESYSTEMLIBS)      set(BOOST_CONTEXT_LIBRARY          optimized boost_context-mt          debug boost_context-mt-d) -    set(BOOST_COROUTINE_LIBRARY -        optimized boost_coroutine-mt -        debug boost_coroutine-mt-d) +    set(BOOST_FIBER_LIBRARY +        optimized boost_fiber-mt +        debug boost_fiber-mt-d)      set(BOOST_FILESYSTEM_LIBRARY          optimized boost_filesystem-mt          debug boost_filesystem-mt-d) @@ -100,9 +100,9 @@ else (USESYSTEMLIBS)      set(BOOST_CONTEXT_LIBRARY          optimized boost_context-mt          debug boost_context-mt-d) -    set(BOOST_COROUTINE_LIBRARY -        optimized boost_coroutine-mt -        debug boost_coroutine-mt-d) +    set(BOOST_FIBER_LIBRARY +        optimized boost_fiber-mt +        debug boost_fiber-mt-d)      set(BOOST_FILESYSTEM_LIBRARY          optimized boost_filesystem-mt          debug boost_filesystem-mt-d) diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake index b3f42c1a5e..ee6396e473 100644 --- a/indra/cmake/LLAddBuildTest.cmake +++ b/indra/cmake/LLAddBuildTest.cmake @@ -53,7 +53,7 @@ INCLUDE(GoogleMock)      ${GOOGLEMOCK_INCLUDE_DIRS}      )    SET(alltest_LIBRARIES -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      ${BOOST_SYSTEM_LIBRARY}      ${GOOGLEMOCK_LIBRARIES} @@ -201,7 +201,7 @@ FUNCTION(LL_ADD_INTEGRATION_TEST    SET(libraries      ${library_dependencies} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      ${BOOST_SYSTEM_LIBRARY}      ${GOOGLEMOCK_LIBRARIES} diff --git a/indra/cmake/LLAppearance.cmake b/indra/cmake/LLAppearance.cmake index ae265d07e3..675330ec72 100644 --- a/indra/cmake/LLAppearance.cmake +++ b/indra/cmake/LLAppearance.cmake @@ -18,7 +18,7 @@ endif (BUILD_HEADLESS)  set(LLAPPEARANCE_LIBRARIES llappearance      llmessage      llcorehttp -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      ${BOOST_SYSTEM_LIBRARY}      ) diff --git a/indra/cmake/LLCommon.cmake b/indra/cmake/LLCommon.cmake index 3e29297c58..8900419f9b 100644 --- a/indra/cmake/LLCommon.cmake +++ b/indra/cmake/LLCommon.cmake @@ -19,7 +19,7 @@ if (LINUX)      # specify all libraries that llcommon uses.      # llcommon uses `clock_gettime' which is provided by librt on linux.      set(LLCOMMON_LIBRARIES llcommon  -        ${BOOST_COROUTINE_LIBRARY}  +        ${BOOST_FIBER_LIBRARY}           ${BOOST_CONTEXT_LIBRARY}           ${BOOST_THREAD_LIBRARY}           ${BOOST_SYSTEM_LIBRARY}  @@ -27,7 +27,7 @@ if (LINUX)          )  else (LINUX)      set(LLCOMMON_LIBRARIES llcommon -        ${BOOST_COROUTINE_LIBRARY}  +        ${BOOST_FIBER_LIBRARY}           ${BOOST_CONTEXT_LIBRARY}           ${BOOST_THREAD_LIBRARY}           ${BOOST_SYSTEM_LIBRARY} ) diff --git a/indra/cmake/LLCoreHttp.cmake b/indra/cmake/LLCoreHttp.cmake index 379ae207de..613453ab5d 100644 --- a/indra/cmake/LLCoreHttp.cmake +++ b/indra/cmake/LLCoreHttp.cmake @@ -12,6 +12,6 @@ set(LLCOREHTTP_INCLUDE_DIRS      )  set(LLCOREHTTP_LIBRARIES llcorehttp -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      ${BOOST_SYSTEM_LIBRARY}) diff --git a/indra/linux_crash_logger/CMakeLists.txt b/indra/linux_crash_logger/CMakeLists.txt index 315aed8d11..d789c850a0 100644 --- a/indra/linux_crash_logger/CMakeLists.txt +++ b/indra/linux_crash_logger/CMakeLists.txt @@ -69,7 +69,7 @@ target_link_libraries(linux-crash-logger      ${LLMATH_LIBRARIES}      ${LLCOREHTTP_LIBRARIES}      ${LLCOMMON_LIBRARIES} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      ${UI_LIBRARIES}      ${DB_LIBRARIES} diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 55c44446b4..2f263cd830 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -44,7 +44,6 @@ set(llcommon_SOURCE_FILES      llcleanup.cpp      llcommon.cpp      llcommonutils.cpp -    llcoro_get_id.cpp      llcoros.cpp      llcrc.cpp      llcriticaldamp.cpp @@ -146,7 +145,6 @@ set(llcommon_HEADER_FILES      llcleanup.h      llcommon.h      llcommonutils.h -    llcoro_get_id.h      llcoros.h      llcrc.h      llcriticaldamp.h @@ -293,7 +291,7 @@ target_link_libraries(      ${JSONCPP_LIBRARIES}      ${ZLIB_LIBRARIES}      ${WINDOWS_LIBRARIES} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      ${BOOST_PROGRAM_OPTIONS_LIBRARY}      ${BOOST_REGEX_LIBRARY} @@ -322,7 +320,7 @@ if (LL_TESTS)        ${LLCOMMON_LIBRARIES}         ${WINDOWS_LIBRARIES}         ${GOOGLEMOCK_LIBRARIES}  -      ${BOOST_COROUTINE_LIBRARY}  +      ${BOOST_FIBER_LIBRARY}         ${BOOST_CONTEXT_LIBRARY}         ${BOOST_THREAD_LIBRARY}         ${BOOST_SYSTEM_LIBRARY}) diff --git a/indra/llcommon/llcoro_get_id.cpp b/indra/llcommon/llcoro_get_id.cpp deleted file mode 100644 index 24ed1fe0c9..0000000000 --- a/indra/llcommon/llcoro_get_id.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @file   llcoro_get_id.cpp - * @author Nat Goodspeed - * @date   2016-09-03 - * @brief  Implementation for llcoro_get_id. - *  - * $LicenseInfo:firstyear=2016&license=viewerlgpl$ - * Copyright (c) 2016, Linden Research, Inc. - * $/LicenseInfo$ - */ - -// Precompiled header -#include "linden_common.h" -// associated header -#include "llcoro_get_id.h" -// STL headers -// std headers -// external library headers -// other Linden headers -#include "llcoros.h" - -namespace llcoro -{ - -id get_id() -{ -    // An instance of Current can convert to LLCoros::CoroData*, which can -    // implicitly convert to void*, which is an llcoro::id. -    return LLCoros::Current(); -} - -} // llcoro diff --git a/indra/llcommon/llcoro_get_id.h b/indra/llcommon/llcoro_get_id.h deleted file mode 100644 index 4c1dca6f19..0000000000 --- a/indra/llcommon/llcoro_get_id.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @file   llcoro_get_id.h - * @author Nat Goodspeed - * @date   2016-09-03 - * @brief  Supplement the functionality in llcoro.h. - * - *         This is broken out as a separate header file to resolve - *         circularity: LLCoros isa LLSingleton, yet LLSingleton machinery - *         requires llcoro::get_id(). - * - *         Be very suspicious of anyone else #including this header. - *  - * $LicenseInfo:firstyear=2016&license=viewerlgpl$ - * Copyright (c) 2016, Linden Research, Inc. - * $/LicenseInfo$ - */ - -#if ! defined(LL_LLCORO_GET_ID_H) -#define LL_LLCORO_GET_ID_H - -namespace llcoro -{ - -/// Get an opaque, distinct token for the running coroutine (or main). -typedef void* id; -id get_id(); - -} // llcoro - -#endif /* ! defined(LL_LLCORO_GET_ID_H) */ diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index cc775775bf..f5ffd96cec 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -34,6 +34,17 @@  // std headers  // external library headers  #include <boost/bind.hpp> +#include <boost/fiber/fiber.hpp> +#ifndef BOOST_DISABLE_ASSERTS +#define UNDO_BOOST_DISABLE_ASSERTS +// with Boost 1.65.1, needed for Mac with this specific header +#define BOOST_DISABLE_ASSERTS +#endif +#include <boost/fiber/protected_fixedsize_stack.hpp> +#ifdef UNDO_BOOST_DISABLE_ASSERTS +#undef UNDO_BOOST_DISABLE_ASSERTS +#undef BOOST_DISABLE_ASSERTS +#endif  // other Linden headers  #include "lltimer.h"  #include "llevents.h" @@ -45,176 +56,69 @@  #include <excpt.h>  #endif -namespace { -void no_op() {} -} // anonymous namespace -// Do nothing, when we need nothing done. This is a static member of LLCoros -// because CoroData is a private nested class. -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. -LLCoros::Current::Current() +const LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) const  { -    // Use a function-static instance so this thread_specific_ptr is -    // instantiated on demand. Since we happen to know it's consumed by -    // LLSingleton, this is likely to happen before the runtime has finished -    // initializing module-static data. For the same reason, we can't package -    // this pointer in an LLSingleton. - -    // 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. -    static boost::thread_specific_ptr<LLCoros::CoroData> sCurrent(LLCoros::no_cleanup); - -    // If this is the first time we're accessing sCurrent for the running -    // thread, its get() will be NULL. This could be a problem, in that -    // llcoro::get_id() would return the same (NULL) token value for the "main -    // coroutine" in every thread, whereas what we really want is a distinct -    // value for every distinct stack in the process. So if get() is NULL, -    // give it a heap CoroData: this ensures that llcoro::get_id() will return -    // distinct values. -    // This tactic is "leaky": sCurrent explicitly does not destroy any -    // CoroData to which it points, and we do NOT enter these "main coroutine" -    // CoroData instances in the LLCoros::mCoros map. They are dummy entries, -    // and they will leak at process shutdown: one CoroData per thread. -    if (! sCurrent.get()) +    CoroData* current = mCurrent.get(); +    // For the main() coroutine, the one NOT explicitly launched by launch(), +    // we never explicitly set mCurrent. Use a static CoroData instance with +    // canonical values. +    if (! current)      {          // It's tempting to provide a distinct name for each thread's "main          // coroutine." But as getName() has always returned the empty string -        // to mean "not in a coroutine," empty string should suffice here -- -        // and truthfully the additional (thread-safe!) machinery to ensure -        // uniqueness just doesn't feel worth the trouble. -        // We use a no-op callable and a minimal stack size because, although -        // CoroData's constructor in fact initializes its mCoro with a -        // coroutine with that stack size, no one ever actually enters it by -        // calling mCoro(). -        sCurrent.reset(new CoroData(0,  // no prev -                                    "", // not a named coroutine -                                    no_op,  // no-op callable -                                    1024)); // stacksize moot +        // to mean "not in a coroutine," empty string should suffice here. +        static CoroData sMain(""); +        // We need not reset() the local_ptr to this read-only data: reuse the +        // same instance for every thread's main coroutine. +        current = &sMain;      } - -    mCurrent = &sCurrent; +    return *current;  } -//static  LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)  { -    CoroData* current = Current(); -    // With the dummy CoroData set in LLCoros::Current::Current(), this -    // pointer should never be NULL. -    llassert_always(current); -    return *current; +    // reuse const implementation, just cast away const-ness of result +    return const_cast<CoroData&>(const_cast<const LLCoros*>(this)->get_CoroData(caller));  }  //static -LLCoros::coro::self& LLCoros::get_self() +LLCoros::coro::id LLCoros::get_self()  { -    CoroData& current = get_CoroData("get_self()"); -    if (! current.mSelf) -    { -        LL_ERRS("LLCoros") << "Calling get_self() from non-coroutine context!" << LL_ENDL; -    } -    return *current.mSelf; +    return boost::this_fiber::get_id();  }  //static  void LLCoros::set_consuming(bool consuming)  { -    get_CoroData("set_consuming()").mConsuming = consuming; +    CoroData& data(LLCoros::instance().get_CoroData("set_consuming()")); +    // DO NOT call this on the main() coroutine. +    llassert_always(! data.mName.empty()); +    data.mConsuming = consuming;  }  //static  bool LLCoros::get_consuming()  { -    return get_CoroData("get_consuming()").mConsuming; -} - -llcoro::Suspending::Suspending() -{ -    LLCoros::Current current; -    // Remember currently-running coroutine: we're about to suspend it. -    mSuspended = current; -    // Revert Current to the value it had at the moment we last switched -    // into this coroutine. -    current.reset(mSuspended->mPrev); -} - -llcoro::Suspending::~Suspending() -{ -    LLCoros::Current current; -    // Okay, we're back, update our mPrev -    mSuspended->mPrev = current; -    // and reinstate our Current. -    current.reset(mSuspended); +    return LLCoros::instance().get_CoroData("get_consuming()").mConsuming;  }  LLCoros::LLCoros():      // MAINT-2724: default coroutine stack size too small on Windows.      // Previously we used      // boost::context::guarded_stack_allocator::default_stacksize(); -    // empirically this is 64KB on Windows and Linux. Try quadrupling. +    // empirically this is insufficient.  #if ADDRESS_SIZE == 64      mStackSize(512*1024)  #else      mStackSize(256*1024)  #endif  { -    // Register our cleanup() method for "mainloop" ticks -    LLEventPumps::instance().obtain("mainloop").listen( -        "LLCoros", boost::bind(&LLCoros::cleanup, this, _1)); -} - -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->mCoro.exited()) -        { -            if (previousName != mi->first) -            {  -                previousName = mi->first; -                previousCount = 1; -            } -            else -            { -                ++previousCount; -            } -                -            if ((previousCount < 5) || !(previousCount % 50)) -            { -                if (previousCount < 5) -                    LL_DEBUGS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL; -                else -                    LL_DEBUGS("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. -            mCoros.erase(mi++); -        } -        else -        { -            // Still live, just skip this entry as if incrementing at the top -            // of the loop as usual. -            ++mi; -        } -    } -    return false;  }  std::string LLCoros::generateDistinctName(const std::string& prefix) const  { -    static std::string previousName; -    static int previousCount = 0; +    static int unique = 0;      // Allowing empty name would make getName()'s not-found return ambiguous.      if (prefix.empty()) @@ -225,37 +129,15 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const      // If the specified name isn't already in the map, just use that.      std::string name(prefix); -    // Find the lowest numeric suffix that doesn't collide with an existing -    // entry. Start with 2 just to make it more intuitive for any interested -    // parties: e.g. "joe", "joe2", "joe3"... -    for (int i = 2; ; name = STRINGIZE(prefix << i++)) +    // Until we find an unused name, append a numeric suffix for uniqueness. +    while (mCoros.find(name) != mCoros.end())      { -        if (mCoros.find(name) == mCoros.end()) -        { -            if (previousName != name) -            { -                previousName = name; -                previousCount = 1; -            } -            else -            { -                ++previousCount; -            } - -            if ((previousCount < 5) || !(previousCount % 50)) -            { -                if (previousCount < 5) -                    LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL; -                else -                    LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << "(" << previousCount << ")" << LL_ENDL; - -            } - -            return name; -        } +        name = STRINGIZE(prefix << unique++);      } +    return name;  } +/*==========================================================================*|  bool LLCoros::kill(const std::string& name)  {      CoroMap::iterator found = mCoros.find(name); @@ -269,10 +151,11 @@ bool LLCoros::kill(const std::string& name)      mCoros.erase(found);      return true;  } +|*==========================================================================*/  std::string LLCoros::getName() const  { -    return Current()->mName; +    return get_CoroData("getName()").mName;  }  void LLCoros::setStackSize(S32 stacksize) @@ -300,6 +183,27 @@ void LLCoros::printActiveCoroutines()      }  } +std::string LLCoros::launch(const std::string& prefix, const callable_t& callable) +{ +    std::string name(generateDistinctName(prefix)); +    // 'dispatch' means: enter the new fiber immediately, returning here only +    // when the fiber yields for whatever reason. +    // std::allocator_arg is a flag to indicate that the following argument is +    // a StackAllocator. +    // protected_fixedsize_stack sets a guard page past the end of the new +    // stack so that stack underflow will result in an access violation +    // instead of weird, subtle, possibly undiagnosed memory stomps. +    boost::fibers::fiber newCoro(boost::fibers::launch::dispatch, +                                 std::allocator_arg, +                                 boost::fibers::protected_fixedsize_stack(mStackSize), +                                 [this, &name, &callable](){ toplevel(name, callable); }); +    // You have two choices with a fiber instance: you can join() it or you +    // can detach() it. If you try to destroy the instance before doing +    // either, the program silently terminates. We don't need this handle. +    newCoro.detach(); +    return name; +} +  #if LL_WINDOWS  static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific @@ -340,10 +244,14 @@ void LLCoros::winlevel(const callable_t& callable)  // Top-level wrapper around caller's coroutine callable. This function accepts  // the coroutine library's implicit coro::self& parameter and saves it, but  // does not pass it down to the caller's callable. -void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& callable) +void LLCoros::toplevel(const std::string& name, const callable_t& callable)  { -    // capture the 'self' param in CoroData -    data->mSelf = &self; +    CoroData* corodata = new CoroData(name); +    // Store it in our pointer map. Oddly, must cast away const-ness of key. +    mCoros.insert(const_cast<std::string&>(name), corodata); +    // also set it as current +    mCurrent.reset(corodata); +      // run the code the caller actually wants in the coroutine      try      { @@ -358,70 +266,41 @@ void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& calla          // Any uncaught exception derived from LLContinueError will be caught          // here and logged. This coroutine will terminate but the rest of the          // viewer will carry on. -        LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName)); +        LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << corodata->mName));      }      catch (...)      {          // Any OTHER kind of uncaught exception will cause the viewer to          // crash, hopefully informatively. -        CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName)); +        CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << corodata->mName));      } -    // This cleanup isn't perfectly symmetrical with the way we initially set -    // data->mPrev, but this is our last chance to reset Current. -    Current().reset(data->mPrev);  } -/***************************************************************************** -*   MUST BE LAST -*****************************************************************************/ -// 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. - -#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 - -LLCoros::CoroData::CoroData(CoroData* prev, const std::string& name, -                            const callable_t& callable, S32 stacksize): -    mPrev(prev), +LLCoros::CoroData::CoroData(const std::string& name):      mName(name), -    // Wrap the caller's callable in our toplevel() function so we can manage -    // Current 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),      mCreationTime(LLTimer::getTotalSeconds())  {  } -std::string LLCoros::launch(const std::string& prefix, const callable_t& callable) +void LLCoros::delete_CoroData(CoroData* cdptr)  { -    std::string name(generateDistinctName(prefix)); -    Current current; -    // pass the current value of Current as previous context -    CoroData* newCoro = new(std::nothrow) CoroData(current, name, callable, mStackSize); -    if (newCoro == NULL) +    // This custom cleanup function is necessarily static. Find and bind the +    // LLCoros instance. +    LLCoros& self(LLCoros::instance()); +    // We set mCurrent on entry to a new fiber, expecting that the +    // corresponding entry has already been stored in mCoros. It is an +    // error if we do not find that entry. +    CoroMap::iterator found = self.mCoros.find(cdptr->mName); +    if (found == self.mCoros.end())      { -        // Out of memory? -        printActiveCoroutines(); -        LL_ERRS("LLCoros") << "Failed to start coroutine: " << name << " Stacksize: " << mStackSize << " Total coroutines: " << mCoros.size() << LL_ENDL; +        LL_ERRS("LLCoros") << "Coroutine '" << cdptr->mName << "' terminated " +                           << "without being stored in LLCoros::mCoros" +                           << LL_ENDL;      } -    // Store it in our pointer map -    mCoros.insert(name, newCoro); -    // also set it as current -    current.reset(newCoro); -    /* Run the coroutine until its first wait, then return here */ -    (newCoro->mCoro)(std::nothrow); -    return name; -} -#if LL_MSVC -// reenable optimizations -#pragma optimize("", on) -#endif // LL_MSVC +    // Oh good, we found the mCoros entry. Erase it. Because it's a ptr_map, +    // that will implicitly delete this CoroData. +    self.mCoros.erase(found); +} diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index c551413811..678633497d 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -29,22 +29,13 @@  #if ! defined(LL_LLCOROS_H)  #define LL_LLCOROS_H -#include <boost/dcoroutine/coroutine.hpp> -#include <boost/dcoroutine/future.hpp> +#include <boost/fiber/fss.hpp> +#include <boost/fiber/future/promise.hpp> +#include <boost/fiber/future/future.hpp>  #include "llsingleton.h"  #include <boost/ptr_container/ptr_map.hpp>  #include <boost/function.hpp> -#include <boost/thread/tss.hpp> -#include <boost/noncopyable.hpp>  #include <string> -#include <stdexcept> -#include "llcoro_get_id.h"          // for friend declaration - -// forward-declare helper class -namespace llcoro -{ -class Suspending; -}  /**   * Registry of named Boost.Coroutine instances @@ -76,19 +67,20 @@ class Suspending;   * name prefix; from your prefix it generates a distinct name, registers the   * new coroutine and returns the actual name.   * - * The name can be used to kill off the coroutine prematurely, if needed. It - * can also provide diagnostic info: we can look up the name of the + * The name + * can provide diagnostic info: we can look up the name of the   * currently-running coroutine. - * - * Finally, the next frame ("mainloop" event) after the coroutine terminates, - * LLCoros will notice its demise and destroy it.   */  class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>  {      LLSINGLETON(LLCoros);  public: -    /// Canonical boost::dcoroutines::coroutine signature we use -    typedef boost::dcoroutines::coroutine<void()> coro; +    /// The viewer's use of the term "coroutine" became deeply embedded before +    /// the industry term "fiber" emerged to distinguish userland threads from +    /// simpler, more transient kinds of coroutines. Semantically they've +    /// always been fibers. But at this point in history, we're pretty much +    /// stuck with the term "coroutine." +    typedef boost::fibers::fiber coro;      /// Canonical callable type      typedef boost::function<void()> callable_t; @@ -119,10 +111,10 @@ public:       * DEV-32777 comments for an explanation.       *       * Pass a nullary callable. It works to directly pass a nullary free -     * function (or static method); for all other cases use boost::bind(). Of -     * course, for a non-static class method, the first parameter must be the -     * class instance. Any other parameters should be passed via the bind() -     * expression. +     * function (or static method); for other cases use a lambda expression, +     * std::bind() or boost::bind(). Of course, for a non-static class method, +     * the first parameter must be the class instance. Any other parameters +     * should be passed via the enclosing expression.       *       * launch() tweaks the suggested name so it won't collide with any       * existing coroutine instance, creates the coroutine instance, registers @@ -138,7 +130,7 @@ public:       * one prematurely. Returns @c true if the specified name was found and       * still running at the time.       */ -    bool kill(const std::string& name); +//  bool kill(const std::string& name);      /**       * From within a coroutine, look up the (tweaked) name string by which @@ -148,14 +140,18 @@ public:       */      std::string getName() const; -    /// for delayed initialization +    /** +     * For delayed initialization. To be clear, this will only affect +     * coroutines launched @em after this point. The underlying facility +     * provides no way to alter the stack size of any running coroutine. +     */      void setStackSize(S32 stacksize);      /// for delayed initialization      void printActiveCoroutines(); -    /// get the current coro::self& for those who really really care -    static coro::self& get_self(); +    /// get the current coro::id for those who really really care +    static coro::id get_self();      /**       * Most coroutines, most of the time, don't "consume" the events for which @@ -190,141 +186,57 @@ public:      };      /** -     * Please do NOT directly use boost::dcoroutines::future! It is essential -     * to maintain the "current" coroutine at every context switch. This -     * Future wraps the essential boost::dcoroutines::future functionality -     * with that maintenance. +     * Aliases for promise and future. An older underlying future implementation +     * required us to wrap future; that's no longer needed. However -- if it's +     * important to restore kill() functionality, we might need to provide a +     * proxy, so continue using the aliases.       */      template <typename T> -    class Future; +    using Promise = boost::fibers::promise<T>; +    template <typename T> +    using Future = boost::fibers::future<T>; +    template <typename T> +    static Future<T> getFuture(Promise<T>& promise) { return promise.get_future(); } + +    /// for data local to each running coroutine +    template <typename T> +    using local_ptr = boost::fibers::fiber_specific_ptr<T>;  private: -    friend class llcoro::Suspending; -    friend llcoro::id llcoro::get_id();      std::string generateDistinctName(const std::string& prefix) const; -    bool cleanup(const LLSD&); +    void toplevel(const std::string& name, const callable_t& callable);      struct CoroData; -    static void no_cleanup(CoroData*);  #if LL_WINDOWS      static void winlevel(const callable_t& callable);  #endif -    static void toplevel(coro::self& self, CoroData* data, const callable_t& callable); -    static CoroData& get_CoroData(const std::string& caller); +    CoroData& get_CoroData(const std::string& caller); +    const CoroData& get_CoroData(const std::string& caller) const;      S32 mStackSize;      // 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); +        CoroData(const std::string& name); -        // 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;          F64 mCreationTime; // since epoch      };      typedef boost::ptr_map<std::string, CoroData> CoroMap;      CoroMap mCoros; -    // Identify the current coroutine's CoroData. Use a little helper class so -    // a caller can either use a temporary instance, or instantiate a named -    // variable and access it multiple times. -    class Current -    { -    public: -        Current(); - -        operator LLCoros::CoroData*() { return get(); } -        LLCoros::CoroData* operator->() { return get(); } -        LLCoros::CoroData* get() { return mCurrent->get(); } -        void reset(LLCoros::CoroData* ptr) { mCurrent->reset(ptr); } - -    private: -        boost::thread_specific_ptr<LLCoros::CoroData>* mCurrent; -    }; -}; - -namespace llcoro -{ +    // Identify the current coroutine's CoroData. This local_ptr isn't static +    // because it's a member of an LLSingleton, and we rely on it being +    // cleaned up in proper dependency order. +    // As each coroutine terminates, use our custom cleanup function to remove +    // the corresponding entry from mCoros. +    local_ptr<CoroData> mCurrent{delete_CoroData}; -/// Instantiate one of these in a block surrounding any leaf point when -/// control literally switches away from this coroutine. -class Suspending: boost::noncopyable -{ -public: -    Suspending(); -    ~Suspending(); - -private: -    LLCoros::CoroData* mSuspended; -}; - -} // namespace llcoro - -template <typename T> -class LLCoros::Future -{ -    typedef boost::dcoroutines::future<T> dfuture; - -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; +    // Cleanup function for each fiber's instance of mCurrent. +    static void delete_CoroData(CoroData* cdptr);  };  #endif /* ! defined(LL_LLCOROS_H) */ diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 43e41f250d..47d99f0050 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -31,18 +31,15 @@  // associated header  #include "lleventcoro.h"  // STL headers -#include <map> +#include <chrono>  // std headers  // external library headers +#include <boost/fiber/operations.hpp>  // other Linden headers  #include "llsdserialize.h"  #include "llsdutil.h"  #include "llerror.h"  #include "llcoros.h" -#include "llmake.h" -#include "llexception.h" - -#include "lleventfilter.h"  namespace  { @@ -105,65 +102,47 @@ void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value)      llsd::drill(dest, path) = 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"); +    boost::this_fiber::yield();  }  void llcoro::suspendUntilTimeout(float seconds)  { -    LLEventTimeout timeout; - -    timeout.eventAfter(seconds, LLSD()); -    llcoro::suspendUntilEventOn(timeout); +    // The fact that we accept non-integer seconds means we should probably +    // use granularity finer than one second. However, given the overhead of +    // the rest of our processing, it seems silly to use granularity finer +    // than a millisecond. +    boost::this_fiber::sleep_for(std::chrono::milliseconds(long(seconds * 1000)));  } -LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump, -                 const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath) +namespace  { -    // declare the future -    LLCoros::Future<LLSD> future; + +LLBoundListener postAndSuspendSetup(const std::string& callerName, +                                    const std::string& listenerName, +                                    LLCoros::Promise<LLSD>& promise, +                                    const LLSD& event, +                                    const LLEventPumpOrPumpName& requestPump, +                                    const LLEventPumpOrPumpName& replyPump, +                                    const LLSD& replyPumpNamePath) +{ +    // Get the consuming attribute for THIS coroutine, the one that's about to +    // suspend. Don't call get_consuming() in the lambda body: that would +    // return the consuming attribute for some other coroutine, most likely +    // the main routine. +    bool consuming(LLCoros::get_consuming());      // 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( +    LLBoundListener connection(          replyPump.getPump().listen(listenerName, -                                   llmake<FutureListener>(future.make_callback()))); +                                   [&promise, consuming](const LLSD& result) +                                   { +                                       promise.set_value(result); +                                       return consuming; +                                   }));      // skip the "post" part if requestPump is default-constructed      if (requestPump)      { @@ -171,7 +150,7 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ          // request event.          LLSD modevent(event);          storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName()); -        LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName +        LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName                                   << " posting to " << requestPump.getPump().getName()                                   << LL_ENDL; @@ -179,158 +158,73 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ          //                         << ": " << modevent << LL_ENDL;          requestPump.getPump().post(modevent);      } -    LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName +    LL_DEBUGS("lleventcoro") << callerName << ": 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; -} - -LLSD llcoro::suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName,  -        F32 timeoutin, const LLSD &timeoutResult) -{ -    /** -     * The timeout pump is attached upstream of of the waiting pump and will  -     * pass the timeout event through it.  We CAN NOT attach downstream since -     * doing so will cause the suspendPump to fire any waiting events immediately  -     * and they will be lost.  This becomes especially problematic with the  -     * LLEventTimeout(pump) constructor which will also attempt to fire those -     * events using the virtual listen_impl method in the not yet fully constructed -     * timeoutPump. -     */ -    LLEventTimeout timeoutPump; -    LLEventPump &suspendPump = suspendPumpOrName.getPump(); - -    LLTempBoundListener timeoutListener(timeoutPump.listen(suspendPump.getName(),  -            boost::bind(&LLEventPump::post, &suspendPump, _1))); - -    timeoutPump.eventAfter(timeoutin, timeoutResult); -    return llcoro::suspendUntilEventOn(suspendPump); +    return connection;  } -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 +LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump, +                 const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath)  { +    LLCoros::Promise<LLSD> promise; +    std::string listenerName(listenerNameForCoro()); + +    // Store connection into an LLTempBoundListener so we implicitly +    // disconnect on return from this function. +    LLTempBoundListener connection = +        postAndSuspendSetup("postAndSuspend()", listenerName, promise, +                            event, requestPump, replyPump, replyPumpNamePath); -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; +    LLCoros::Future<LLSD> future = LLCoros::getFuture(promise);      // 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 +    LLSD value(future.get()); +    LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName +                             << " resuming with " << value << LL_ENDL; +    // returning should disconnect the connection      return value;  } -LLSD errorException(const LLEventWithID& result, const std::string& desc) +LLSD llcoro::postAndSuspendWithTimeout(const LLSD& event, +                                       const LLEventPumpOrPumpName& requestPump, +                                       const LLEventPumpOrPumpName& replyPump, +                                       const LLSD& replyPumpNamePath, +                                       F32 timeout, const LLSD& timeoutResult)  { -    // If the result arrived on the error pump (pump 1), instead of -    // returning it, deliver it via exception. -    if (result.second) +    LLCoros::Promise<LLSD> promise; +    std::string listenerName(listenerNameForCoro()); + +    // Store connection into an LLTempBoundListener so we implicitly +    // disconnect on return from this function. +    LLTempBoundListener connection = +        postAndSuspendSetup("postAndSuspendWithTimeout()", listenerName, promise, +                            event, requestPump, replyPump, replyPumpNamePath); + +    // declare the future +    LLCoros::Future<LLSD> future = LLCoros::getFuture(promise); +    // wait for specified timeout +    boost::fibers::future_status status = +        future.wait_for(std::chrono::milliseconds(long(timeout * 1000))); +    // if the future is NOT yet ready, return timeoutResult instead +    if (status == boost::fibers::future_status::timeout)      { -        LLTHROW(LLErrorEvent(desc, result.first)); +        LL_DEBUGS("lleventcoro") << "postAndSuspendWithTimeout(): coroutine " << listenerName +                                 << " timed out after " << timeout << " seconds," +                                 << " resuming with " << timeoutResult << LL_ENDL; +        return timeoutResult;      } -    // That way, our caller knows a simple return must be from the reply -    // pump (pump 0). -    return result.first; -} - -LLSD errorLog(const LLEventWithID& result, const std::string& desc) -{ -    // If the result arrived on the error pump (pump 1), log it as a fatal -    // error. -    if (result.second) +    else      { -        LL_ERRS("errorLog") << desc << ":" << std::endl; -        LLSDSerialize::toPrettyXML(result.first, LL_CONT); -        LL_CONT << LL_ENDL; +        llassert_always(status == boost::fibers::future_status::ready); + +        // future is now ready, no more waiting +        LLSD value(future.get()); +        LL_DEBUGS("lleventcoro") << "postAndSuspendWithTimeout(): coroutine " << listenerName +                                 << " resuming with " << value << LL_ENDL; +        // returning should disconnect the connection +        return value;      } -    // 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 84827aab4a..c0fe8b094f 100644 --- a/indra/llcommon/lleventcoro.h +++ b/indra/llcommon/lleventcoro.h @@ -29,12 +29,8 @@  #if ! defined(LL_LLEVENTCORO_H)  #define LL_LLEVENTCORO_H -#include <boost/optional.hpp>  #include <string> -#include <utility>                  // std::pair  #include "llevents.h" -#include "llerror.h" -#include "llexception.h"  /**   * Like LLListenerOrPumpName, this is a class intended for parameter lists: @@ -147,117 +143,29 @@ LLSD suspendUntilEventOn(const LLEventPumpOrPumpName& pump)      return postAndSuspend(LLSD(), LLEventPumpOrPumpName(), pump);  } +/// Like postAndSuspend(), but if we wait longer than @a timeout seconds, +/// stop waiting and return @a timeoutResult instead. +LLSD postAndSuspendWithTimeout(const LLSD& event, +                               const LLEventPumpOrPumpName& requestPump, +                               const LLEventPumpOrPumpName& replyPump, +                               const LLSD& replyPumpNamePath, +                               F32 timeout, const LLSD& timeoutResult); +  /// Suspend the coroutine until an event is fired on the identified pump  /// or the timeout duration has elapsed.  If the timeout duration   /// elapses the specified LLSD is returned. -LLSD suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName, F32 timeoutin, const LLSD &timeoutResult); - -} // namespace llcoro - -/// return type for two-pump variant of suspendUntilEventOn() -typedef std::pair<LLSD, int> LLEventWithID; - -namespace llcoro -{ - -/** - * This function waits for a reply on either of two specified LLEventPumps. - * 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 - * one, there's enough added complexity here to make it worthwhile to give the - * single-pump variant its own straightforward implementation. Conversely, - * though we could use preprocessor logic to generate n-pump overloads up to - * BOOST_COROUTINE_WAIT_MAX, we don't foresee a use case. This two-pump - * overload exists because certain event APIs are defined in terms of a reply - * LLEventPump and an error LLEventPump. - * - * The LLEventWithID return value provides not only the received event, but - * the index of the pump on which it arrived (0 or 1). - * - * @note - * I'd have preferred to overload the name postAndSuspend() for both signatures. - * But consider the following ambiguous call: - * @code - * 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 postAndSuspend2() than to write either - * LLSD("someString") or LLEventOrPumpName("someString"). - */ -LLEventWithID postAndSuspend2(const LLSD& event, -                           const LLEventPumpOrPumpName& requestPump, -                           const LLEventPumpOrPumpName& replyPump0, -                           const LLEventPumpOrPumpName& replyPump1, -                           const LLSD& replyPump0NamePath=LLSD(), -                           const LLSD& replyPump1NamePath=LLSD()); - -/** - * Wait for the next event on either of two specified LLEventPumps. - */  inline -LLEventWithID -suspendUntilEventOn(const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1) +LLSD suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName, +                                    F32 timeoutin, const LLSD &timeoutResult)  { -    // This is now a convenience wrapper for postAndSuspend2(). -    return postAndSuspend2(LLSD(), LLEventPumpOrPumpName(), pump0, pump1); +    return postAndSuspendWithTimeout(LLSD(),                  // event +                                     LLEventPumpOrPumpName(), // requestPump +                                     suspendPumpOrName,       // replyPump +                                     LLSD(),                  // replyPumpNamePath +                                     timeoutin, +                                     timeoutResult);  } -/** - * Helper for the two-pump variant of suspendUntilEventOn(), e.g.: - * - * @code - * LLSD reply = errorException(suspendUntilEventOn(replyPump, errorPump), - *                             "error response from login.cgi"); - * @endcode - * - * Examines an LLEventWithID, assuming that the second pump (pump 1) is - * listening for an error indication. If the incoming data arrived on pump 1, - * throw an LLErrorEvent exception. If the incoming data arrived on pump 0, - * just return it. Since a normal return can only be from pump 0, we no longer - * need the LLEventWithID's discriminator int; we can just return the LLSD. - * - * @note I'm not worried about introducing the (fairly generic) name - * errorException() into global namespace, because how many other overloads of - * the same name are going to accept an LLEventWithID parameter? - */ -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 - * announces an event that bears error information (for some other API). - */ -class LL_COMMON_API LLErrorEvent: public LLException -{ -public: -    LLErrorEvent(const std::string& what, const LLSD& data): -        LLException(what), -        mData(data) -    {} -    virtual ~LLErrorEvent() throw() {} - -    LLSD getData() const { return mData; } - -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  /** @@ -304,84 +212,4 @@ private:      LLEventStream mPump;  }; -/** - * Other event APIs require the names of two different LLEventPumps: one for - * success response, the other for error response. Extend LLCoroEventPump - * for the two-pump use case. - */ -class LL_COMMON_API LLCoroEventPumps -{ -public: -    LLCoroEventPumps(const std::string& name="coro", -                     const std::string& suff0="Reply", -                     const std::string& suff1="Error"): -        mPump0(name + suff0, true),   // allow tweaking the pump instance name -        mPump1(name + suff1, true) -    {} -    /// request pump 0's name -    std::string getName0() const { return mPump0.getName(); } -    /// request pump 1's name -    std::string getName1() const { return mPump1.getName(); } -    /// request both names -    std::pair<std::string, std::string> getNames() const -    { -        return std::pair<std::string, std::string>(mPump0.getName(), mPump1.getName()); -    } - -    /// request pump 0 -    LLEventPump& getPump0() { return mPump0; } -    /// request pump 1 -    LLEventPump& getPump1() { return mPump1; } - -    /// suspendUntilEventOn(either of our two LLEventPumps) -    LLEventWithID suspend() -    { -        return llcoro::suspendUntilEventOn(mPump0, mPump1); -    } - -    /// errorException(suspend()) -    LLSD suspendWithException() -    { -        return llcoro::errorException(suspend(), std::string("Error event on ") + getName1()); -    } - -    /// errorLog(suspend()) -    LLSD suspendWithLog() -    { -        return llcoro::errorLog(suspend(), std::string("Error event on ") + getName1()); -    } - -    LLEventWithID postAndSuspend(const LLSD& event, -                              const LLEventPumpOrPumpName& requestPump, -                              const LLSD& replyPump0NamePath=LLSD(), -                              const LLSD& replyPump1NamePath=LLSD()) -    { -        return llcoro::postAndSuspend2(event, requestPump, mPump0, mPump1, -                                    replyPump0NamePath, replyPump1NamePath); -    } - -    LLSD postAndSuspendWithException(const LLSD& event, -                                  const LLEventPumpOrPumpName& requestPump, -                                  const LLSD& replyPump0NamePath=LLSD(), -                                  const LLSD& replyPump1NamePath=LLSD()) -    { -        return llcoro::errorException(postAndSuspend(event, requestPump, -                                                  replyPump0NamePath, replyPump1NamePath), -                                      std::string("Error event on ") + getName1()); -    } - -    LLSD postAndSuspendWithLog(const LLSD& event, -                            const LLEventPumpOrPumpName& requestPump, -                            const LLSD& replyPump0NamePath=LLSD(), -                            const LLSD& replyPump1NamePath=LLSD()) -    { -        return llcoro::errorLog(postAndSuspend(event, requestPump, -                                            replyPump0NamePath, replyPump1NamePath), -                                std::string("Error event on ") + getName1()); -    } - -private: -    LLEventStream mPump0, mPump1; -}; -  #endif /* ! defined(LL_LLEVENTCORO_H) */ diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index f5f3aec270..356b896163 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -30,10 +30,9 @@  #include "llerror.h"  #include "llerrorcontrol.h"         // LLError::is_available()  #include "lldependencies.h" -#include "llcoro_get_id.h"  #include "llexception.h" +#include "llcoros.h"  #include <boost/foreach.hpp> -#include <boost/unordered_map.hpp>  #include <algorithm>  #include <iostream>                 // std::cerr in dire emergency  #include <sstream> @@ -115,19 +114,10 @@ private:      // initialized, either in the constructor or in initSingleton(). However,      // managing that as a stack depends on having a DISTINCT 'initializing'      // stack for every C++ stack in the process! And we have a distinct C++ -    // stack for every running coroutine. It would be interesting and cool to -    // implement a generic coroutine-local-storage mechanism and use that -    // here. The trouble is that LLCoros is itself an LLSingleton, so -    // depending on LLCoros functionality could dig us into infinite -    // recursion. (Moreover, when we reimplement LLCoros on top of -    // Boost.Fiber, that library already provides fiber_specific_ptr -- so -    // it's not worth a great deal of time and energy implementing a generic -    // equivalent on top of boost::dcoroutine, which is on its way out.) -    // Instead, use a map of llcoro::id to select the appropriate -    // coro-specific 'initializing' stack. llcoro::get_id() is carefully -    // implemented to avoid requiring LLCoros. -    typedef boost::unordered_map<llcoro::id, list_t> InitializingMap; -    InitializingMap mInitializing; +    // stack for every running coroutine. Therefore this stack must be based +    // on a coroutine-local pointer. +    // This local_ptr isn't static because it's a member of an LLSingleton. +    LLCoros::local_ptr<LLSingletonBase::list_t> mInitializing;  public:      // Instantiate this to obtain a reference to the coroutine-specific @@ -166,18 +156,23 @@ public:  private:      list_t& get_initializing_()      { -        // map::operator[] has find-or-create semantics, exactly what we need -        // here. It returns a reference to the selected mapped_type instance. -        return mInitializing[llcoro::get_id()]; +        LLSingletonBase::list_t* current = mInitializing.get(); +        if (! current) +        { +            // If the running coroutine doesn't already have an initializing +            // stack, allocate a new one and save it for future reference. +            current = new LLSingletonBase::list_t(); +            mInitializing.reset(current); +        } +        return *current;      } +    // By the time mInitializing is destroyed, its value for every coroutine +    // except the running one must have been reset() to nullptr. So every time +    // we pop the list to empty, reset() the running coroutine's local_ptr.      void cleanup_initializing_()      { -        InitializingMap::iterator found = mInitializing.find(llcoro::get_id()); -        if (found != mInitializing.end()) -        { -            mInitializing.erase(found); -        } +        mInitializing.reset(nullptr);      }  }; diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index fa02d2bb1a..2e4b6ba823 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -26,50 +26,12 @@   * $/LicenseInfo$   */ -/*****************************************************************************/ -//  test<1>() is cloned from a Boost.Coroutine example program whose copyright -//  info is reproduced here: -/*---------------------------------------------------------------------------*/ -//  Copyright (c) 2006, Giovanni P. Deretta -// -//  This code may be used under either of the following two licences: -// -//  Permission is hereby granted, free of charge, to any person obtaining a copy  -//  of this software and associated documentation files (the "Software"), to deal  -//  in the Software without restriction, including without limitation the rights  -//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  -//  copies of the Software, and to permit persons to whom the Software is  -//  furnished to do so, subject to the following conditions: -// -//  The above copyright notice and this permission notice shall be included in  -//  all copies or substantial portions of the Software. -// -//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  -//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  -//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  -//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  -//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  -//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN  -//  THE SOFTWARE. OF SUCH DAMAGE. -// -//  Or: -// -//  Distributed under the Boost Software License, Version 1.0. -//  (See accompanying file LICENSE_1_0.txt or copy at -//  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>  #include <boost/bind.hpp>  #include <boost/range.hpp>  #include <boost/utility.hpp>  #include <boost/shared_ptr.hpp> +#include <boost/make_shared.hpp>  #include "linden_common.h" @@ -80,8 +42,6 @@  #include "llsd.h"  #include "llsdutil.h"  #include "llevents.h" -#include "tests/wrapllerrs.h" -#include "stringize.h"  #include "llcoros.h"  #include "lleventcoro.h"  #include "../test/debug.h" @@ -89,39 +49,6 @@  using namespace llcoro;  /***************************************************************************** -*   from the banana.cpp example program borrowed for test<1>() -*****************************************************************************/ -namespace coroutines = boost::dcoroutines; -using coroutines::coroutine; - -template<typename Iter> -bool match(Iter first, Iter last, std::string match) { -  std::string::iterator i = match.begin(); -  for(; (first != last) && (i != match.end()); ++i) { -    if (*first != *i) -      return false; -    ++first; -  } -  return i == match.end(); -} - -template<typename BidirectionalIterator>  -BidirectionalIterator  -match_substring(BidirectionalIterator begin,  -		BidirectionalIterator end,  -		std::string xmatch, -		BOOST_DEDUCED_TYPENAME coroutine<BidirectionalIterator(void)>::self& self) {  -//BidirectionalIterator begin_ = begin; -  for(; begin != end; ++begin)  -    if(match(begin, end, xmatch)) { -      self.yield(begin); -    } -  return end; -}  - -typedef coroutine<std::string::iterator(void)> match_coroutine_type; - -/*****************************************************************************  *   Test helpers  *****************************************************************************/  /// Simulate an event API whose response is immediate: sent on receipt of the @@ -150,6 +77,8 @@ public:          LLSD::Integer value(event["value"]);          LLSD::String replyPumpName(event.has("fail")? "error" : "reply");          LLEventPumps::instance().obtain(event[replyPumpName]).post(value + 1); +        // give listener a chance to process +        llcoro::suspend();          return false;      } @@ -167,51 +96,6 @@ namespace tut      typedef coroutine_group::object object;      coroutine_group coroutinegrp("coroutine"); -    template<> template<> -    void object::test<1>() -    { -        set_test_name("From banana.cpp example program in Boost.Coroutine distro"); -        std::string buffer = "banananana";  -        std::string match = "nana";  -        std::string::iterator begin = buffer.begin(); -        std::string::iterator end = buffer.end(); - -#if defined(BOOST_CORO_POSIX_IMPL) -//      std::cout << "Using Boost.Coroutine " << BOOST_CORO_POSIX_IMPL << '\n'; -#else -//      std::cout << "Using non-Posix Boost.Coroutine implementation" << std::endl; -#endif - -        typedef std::string::iterator signature(std::string::iterator,  -                                                std::string::iterator,  -                                                std::string, -                                                match_coroutine_type::self&); - -        coroutine<std::string::iterator(void)> matcher -            (boost::bind(static_cast<signature*>(match_substring),  -                         begin,  -                         end,  -                         match,  -                         _1));  - -        std::string::iterator i = matcher(); -/*==========================================================================*| -        while(matcher && i != buffer.end()) { -            std::cout <<"Match at: "<< std::distance(buffer.begin(), i)<<'\n';  -            i = matcher(); -        } -|*==========================================================================*/ -        size_t matches[] = { 2, 4, 6 }; -        for (size_t *mi(boost::begin(matches)), *mend(boost::end(matches)); -             mi != mend; ++mi, i = matcher()) -        { -            ensure("more", matcher); -            ensure("found", i != buffer.end()); -            ensure_equals("value", std::distance(buffer.begin(), i), *mi); -        } -        ensure("done", ! matcher); -    } -      // use static data so we can intersperse coroutine functions with the      // tests that engage them      ImmediateAPI immediateAPI; @@ -231,7 +115,7 @@ namespace tut          which = 0;      } -    void explicit_wait(boost::shared_ptr<LLCoros::Future<std::string>::callback_t>& cbp) +    void explicit_wait(boost::shared_ptr<LLCoros::Promise<std::string>>& cbp)      {          BEGIN          { @@ -241,44 +125,40 @@ namespace tut              // 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 +            // for cbp->set_value() to be called on response. +            // For test purposes, instead of handing 'callback' (or an              // 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)); +            cbp = boost::make_shared<LLCoros::Promise<std::string>>(); +            LLCoros::Future<std::string> future = LLCoros::getFuture(*cbp); -            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)); +            ensure_equals("Got it", stringdata, "received");          }          END      }      template<> template<> -    void object::test<2>() +    void object::test<1>()      {          clear();          set_test_name("explicit_wait");          DEBUG;          // Construct the coroutine instance that will run explicit_wait. -        boost::shared_ptr<LLCoros::Future<std::string>::callback_t> respond; -        LLCoros::instance().launch("test<2>", +        boost::shared_ptr<LLCoros::Promise<std::string>> respond; +        LLCoros::instance().launch("test<1>",                                     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"); +        // Now we're the I/O subsystem delivering a result. This should make +        // the coroutine ready. +        respond->set_value("received"); +        // but give it a chance to wake up +        llcoro::suspend();          // ensure the coroutine ran and woke up again with the intended result          ensure_equals(stringdata, "received");      } @@ -293,60 +173,20 @@ namespace tut      }      template<> template<> -    void object::test<3>() +    void object::test<2>()      {          clear();          set_test_name("waitForEventOn1");          DEBUG; -        LLCoros::instance().launch("test<3>", waitForEventOn1); +        LLCoros::instance().launch("test<2>", waitForEventOn1);          debug("about to send");          LLEventPumps::instance().obtain("source").post("received"); +        // give waitForEventOn1() a chance to run +        llcoro::suspend();          debug("back from send");          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; -        LLCoros::instance().launch("test<4>", waitForEventOn2); -        debug("about to send"); -        LLEventPumps::instance().obtain("reply").post("received"); -        debug("back from send"); -        } -        ensure_equals(result.asString(), "received"); -        ensure_equals("which pump", which, 0); -    } - -    template<> template<> -    void object::test<5>() -    { -        clear(); -        set_test_name("waitForEventOn2 error"); -        DEBUG; -        LLCoros::instance().launch("test<5>", waitForEventOn2); -        debug("about to send"); -        LLEventPumps::instance().obtain("error").post("badness"); -        debug("back from send"); -        ensure_equals(result.asString(), "badness"); -        ensure_equals("which pump", which, 1); -    } -      void coroPump()      {          BEGIN @@ -359,175 +199,20 @@ namespace tut      }      template<> template<> -    void object::test<6>() +    void object::test<3>()      {          clear();          set_test_name("coroPump");          DEBUG; -        LLCoros::instance().launch("test<6>", coroPump); -        debug("about to send"); -        LLEventPumps::instance().obtain(replyName).post("received"); -        debug("back from send"); -        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; -        LLCoros::instance().launch("test<7>", coroPumps); -        debug("about to send"); -        LLEventPumps::instance().obtain(replyName).post("received"); -        debug("back from send"); -        ensure_equals(result.asString(), "received"); -        ensure_equals("which pump", which, 0); -    } - -    template<> template<> -    void object::test<8>() -    { -        clear(); -        set_test_name("coroPumps error"); -        DEBUG; -        LLCoros::instance().launch("test<8>", coroPumps); -        debug("about to send"); -        LLEventPumps::instance().obtain(errorName).post("badness"); -        debug("back from send"); -        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; -        LLCoros::instance().launch("test<9>", coroPumpsNoEx); -        debug("about to send"); -        LLEventPumps::instance().obtain(replyName).post("received"); -        debug("back from send"); -        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; -        LLCoros::instance().launch("test<10>", coroPumpsEx); -        debug("about to send"); -        LLEventPumps::instance().obtain(errorName).post("badness"); -        debug("back from send"); -        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; -        LLCoros::instance().launch("test<11>", coroPumpsNoLog); +        LLCoros::instance().launch("test<3>", coroPump);          debug("about to send");          LLEventPumps::instance().obtain(replyName).post("received"); +        // give coroPump() a chance to run +        llcoro::suspend();          debug("back from send");          ensure_equals(result.asString(), "received");      } -    void coroPumpsLog() -    { -        BEGIN -        { -            LLCoroEventPumps waiter; -            replyName = waiter.getName0(); -            errorName = waiter.getName1(); -            WrapLLErrs capture; -            threw = capture.catch_llerrs([&waiter, &debug](){ -                    result = waiter.suspendWithLog(); -                    debug("no exception"); -                }); -        } -        END -    } - -    template<> template<> -    void object::test<12>() -    { -        clear(); -        set_test_name("coroPumpsLog"); -        DEBUG; -        LLCoros::instance().launch("test<12>", coroPumpsLog); -        debug("about to send"); -        LLEventPumps::instance().obtain(errorName).post("badness"); -        debug("back from send"); -        ensure("no result", result.isUndefined()); -        ensure_contains("got error", threw, "badness"); -    } -      void postAndWait1()      {          BEGIN @@ -541,71 +226,17 @@ namespace tut      }      template<> template<> -    void object::test<13>() +    void object::test<4>()      {          clear();          set_test_name("postAndWait1");          DEBUG; -        LLCoros::instance().launch("test<13>", postAndWait1); +        LLCoros::instance().launch("test<4>", postAndWait1); +        // give postAndWait1() a chance to run +        llcoro::suspend();          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; -        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; -        LLCoros::instance().launch("test<15>", postAndWait2_1); -        ensure_equals(result.asInteger(), 19); -        ensure_equals(which, 1); -    } -      void coroPumpPost()      {          BEGIN @@ -618,177 +249,14 @@ namespace tut      }      template<> template<> -    void object::test<16>() +    void object::test<5>()      {          clear();          set_test_name("coroPumpPost");          DEBUG; -        LLCoros::instance().launch("test<16>", coroPumpPost); +        LLCoros::instance().launch("test<5>", coroPumpPost); +        // give coroPumpPost() a chance to run +        llcoro::suspend();          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; -        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; -        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; -        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; -        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; -        LLCoros::instance().launch("test<21>", coroPumpsPostNoLog); -        ensure_equals(result.asInteger(), 31); -    } - -    void coroPumpsPostLog() -    { -        BEGIN -        { -            LLCoroEventPumps waiter; -            WrapLLErrs capture; -            threw = capture.catch_llerrs( -                [&waiter, &debug](){ -                    result = waiter.postAndSuspendWithLog( -                        LLSDMap("value", 31)("fail", LLSD()), -                        immediateAPI.getPump(), "reply", "error"); -                    debug("no exception"); -                }); -        } -        END -    } - -    template<> template<> -    void object::test<22>() -    { -        clear(); -        set_test_name("coroPumpsPostLog"); -        DEBUG; -        LLCoros::instance().launch("test<22>", coroPumpsPostLog); -        ensure("no result", result.isUndefined()); -        ensure_contains("got error", threw, "32"); -    }  } - -/*==========================================================================*| -#include <boost/context/guarded_stack_allocator.hpp> - -namespace tut -{ -    template<> template<> -    void object::test<23>() -    { -        set_test_name("stacksize"); -        std::cout << "default_stacksize: " << boost::context::guarded_stack_allocator::default_stacksize() << '\n'; -    } -} // namespace tut -|*==========================================================================*/ diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index e0922c0667..a2a57ad740 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -217,7 +217,7 @@ target_link_libraries(    ${NGHTTP2_LIBRARIES}    ${XMLRPCEPI_LIBRARIES}    ${LLCOREHTTP_LIBRARIES} -  ${BOOST_COROUTINE_LIBRARY} +  ${BOOST_FIBER_LIBRARY}    ${BOOST_CONTEXT_LIBRARY}    ${BOOST_SYSTEM_LIBRARY}    rt @@ -235,7 +235,7 @@ target_link_libraries(    ${NGHTTP2_LIBRARIES}    ${XMLRPCEPI_LIBRARIES}    ${LLCOREHTTP_LIBRARIES} -  ${BOOST_COROUTINE_LIBRARY} +  ${BOOST_FIBER_LIBRARY}    ${BOOST_CONTEXT_LIBRARY}    ${BOOST_SYSTEM_LIBRARY}    ) @@ -264,7 +264,7 @@ if (LINUX)      ${LLMESSAGE_LIBRARIES}      ${LLCOREHTTP_LIBRARIES}      ${JSONCPP_LIBRARIES} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      rt      ${GOOGLEMOCK_LIBRARIES} @@ -280,7 +280,7 @@ else (LINUX)      ${LLMESSAGE_LIBRARIES}      ${LLCOREHTTP_LIBRARIES}      ${JSONCPP_LIBRARIES} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      ${GOOGLEMOCK_LIBRARIES}      ) diff --git a/indra/llmessage/llcoproceduremanager.cpp b/indra/llmessage/llcoproceduremanager.cpp index 74cdff2b00..4c85dd999a 100644 --- a/indra/llmessage/llcoproceduremanager.cpp +++ b/indra/llmessage/llcoproceduremanager.cpp @@ -203,6 +203,7 @@ void LLCoprocedureManager::cancelCoprocedure(const LLUUID &id)      LL_INFOS() << "Coprocedure not found." << LL_ENDL;  } +/*==========================================================================*|  void LLCoprocedureManager::shutdown(bool hardShutdown)  {      for (poolMap_t::const_iterator it = mPoolMap.begin(); it != mPoolMap.end(); ++it) @@ -211,6 +212,7 @@ void LLCoprocedureManager::shutdown(bool hardShutdown)      }      mPoolMap.clear();  } +|*==========================================================================*/  void LLCoprocedureManager::setPropertyMethods(SettingQuery_t queryfn, SettingUpdate_t updatefn)  { @@ -303,10 +305,13 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size):  LLCoprocedurePool::~LLCoprocedurePool()   { +/*==========================================================================*|      shutdown(); +|*==========================================================================*/  }  //------------------------------------------------------------------------- +/*==========================================================================*|  void LLCoprocedurePool::shutdown(bool hardShutdown)  {      CoroAdapterMap_t::iterator it; @@ -327,6 +332,7 @@ void LLCoprocedurePool::shutdown(bool hardShutdown)      mCoroMapping.clear();      mPendingCoprocs.clear();  } +|*==========================================================================*/  //-------------------------------------------------------------------------  LLUUID LLCoprocedurePool::enqueueCoprocedure(const std::string &name, LLCoprocedurePool::CoProcedure_t proc) diff --git a/indra/llmessage/llcoproceduremanager.h b/indra/llmessage/llcoproceduremanager.h index 7d0e83180c..ba6f97355c 100644 --- a/indra/llmessage/llcoproceduremanager.h +++ b/indra/llmessage/llcoproceduremanager.h @@ -59,9 +59,11 @@ public:      /// If it has not yet been dequeued it is simply removed from the queue.      void cancelCoprocedure(const LLUUID &id); +/*==========================================================================*|      /// Requests a shutdown of the upload manager. Passing 'true' will perform       /// an immediate kill on the upload coroutine.      void shutdown(bool hardShutdown = false); +|*==========================================================================*/      void setPropertyMethods(SettingQuery_t queryfn, SettingUpdate_t updatefn); diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index dd2e806dda..7b6d04b096 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -80,7 +80,7 @@ target_link_libraries(llprimitive      ${LLXML_LIBRARIES}      ${LLPHYSICSEXTENSIONS_LIBRARIES}      ${LLCHARACTER_LIBRARIES} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      ) diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index e44f57fa9f..2d2fa6588f 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -299,7 +299,7 @@ if(LL_TESTS)    set(test_libs llui llmessage llcorehttp llcommon        ${HUNSPELL_LIBRARY}        ${LLCOMMON_LIBRARIES} -      ${BOOST_COROUTINE_LIBRARY} ${BOOST_CONTEXT_LIBRARY} ${BOOST_SYSTEM_LIBRARY} +      ${BOOST_FIBER_LIBRARY} ${BOOST_CONTEXT_LIBRARY} ${BOOST_SYSTEM_LIBRARY}        ${WINDOWS_LIBRARIES})    if(NOT LINUX)      LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "${test_libs}") diff --git a/indra/mac_crash_logger/CMakeLists.txt b/indra/mac_crash_logger/CMakeLists.txt index f6c4dfb59d..95637c9a28 100644 --- a/indra/mac_crash_logger/CMakeLists.txt +++ b/indra/mac_crash_logger/CMakeLists.txt @@ -77,7 +77,7 @@ target_link_libraries(mac-crash-logger      ${LLCOREHTTP_LIBRARIES}      ${LLCOMMON_LIBRARIES}      ${BOOST_CONTEXT_LIBRARY} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      )  add_custom_command( diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index dc0d737540..45f4cb269c 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2004,7 +2004,7 @@ target_link_libraries(${VIEWER_BINARY_NAME}      ${viewer_LIBRARIES}      ${BOOST_PROGRAM_OPTIONS_LIBRARY}      ${BOOST_REGEX_LIBRARY} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      ${DBUSGLIB_LIBRARIES}      ${OPENGL_LIBRARIES} @@ -2484,7 +2484,7 @@ if (LL_TESTS)      ${OPENSSL_LIBRARIES}      ${CRYPTO_LIBRARIES}      ${LIBRT_LIBRARY} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}    ) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index af70751b37..db2db43ee1 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1422,6 +1422,8 @@ bool LLAppViewer::doFrame()  		// canonical per-frame event  		mainloop.post(newFrame); +		// give listeners a chance to run +		llcoro::suspend();  		if (!LLApp::isExiting())  		{ diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 8344cead57..4187076030 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -98,7 +98,7 @@ target_link_libraries(lltest      ${WINDOWS_LIBRARIES}      ${BOOST_PROGRAM_OPTIONS_LIBRARY}      ${BOOST_REGEX_LIBRARY} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      ${BOOST_SYSTEM_LIBRARY}      ${DL_LIBRARY} diff --git a/indra/viewer_components/login/CMakeLists.txt b/indra/viewer_components/login/CMakeLists.txt index 3bedeb7292..23518b791c 100644 --- a/indra/viewer_components/login/CMakeLists.txt +++ b/indra/viewer_components/login/CMakeLists.txt @@ -50,7 +50,7 @@ target_link_libraries(lllogin      ${LLMATH_LIBRARIES}      ${LLXML_LIBRARIES}      ${BOOST_THREAD_LIBRARY} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${BOOST_CONTEXT_LIBRARY}      ${BOOST_SYSTEM_LIBRARY}      ) @@ -62,7 +62,7 @@ if(LL_TESTS)    set_source_files_properties(      lllogin.cpp      PROPERTIES -    LL_TEST_ADDITIONAL_LIBRARIES "${LLMESSAGE_LIBRARIES};${LLCOREHTTP_LIBRARIES};${LLCOMMON_LIBRARIES};${BOOST_COROUTINE_LIBRARY};${BOOST_CONTEXT_LIBRARY};${BOOST_THREAD_LIBRARY};${BOOST_SYSTEM_LIBRARY}" +    LL_TEST_ADDITIONAL_LIBRARIES "${LLMESSAGE_LIBRARIES};${LLCOREHTTP_LIBRARIES};${LLCOMMON_LIBRARIES};${BOOST_FIBER_LIBRARY};${BOOST_CONTEXT_LIBRARY};${BOOST_THREAD_LIBRARY};${BOOST_SYSTEM_LIBRARY}"      )    LL_ADD_PROJECT_UNIT_TESTS(lllogin "${lllogin_TEST_SOURCE_FILES}") diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp index e96c495446..774823d735 100644 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -44,6 +44,7 @@  //#define DEBUG_ON  #include "../../../test/debug.h"  #include "llevents.h" +#include "lleventcoro.h"  #include "stringize.h"  #if LL_WINDOWS @@ -199,6 +200,7 @@ namespace tut  		credentials["passwd"] = "secret";  		login.connect("login.bar.com", credentials); +		llcoro::suspend();  		ensure_equals("Online state", listener.lastEvent()["state"].asString(), "online");  	} @@ -226,6 +228,7 @@ namespace tut  		credentials["passwd"] = "badpasswd";  		login.connect("login.bar.com", credentials); +		llcoro::suspend();  		ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating");  @@ -265,6 +268,7 @@ namespace tut  		credentials["passwd"] = "matter";  		login.connect("login.bar.com", credentials); +		llcoro::suspend();  		ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating");  @@ -300,6 +304,7 @@ namespace tut  		credentials["cfg_srv_timeout"] = 0.0f;  		login.connect("login.bar.com", credentials); +		llcoro::suspend();  		// Get the mainloop eventpump, which needs a pinging in order to drive the   		// SRV timeout. diff --git a/indra/win_crash_logger/CMakeLists.txt b/indra/win_crash_logger/CMakeLists.txt index 4fba26ab2f..1c3479bf69 100644 --- a/indra/win_crash_logger/CMakeLists.txt +++ b/indra/win_crash_logger/CMakeLists.txt @@ -83,7 +83,7 @@ target_link_libraries(windows-crash-logger      ${LLCOREHTTP_LIBRARIES}      ${LLCOMMON_LIBRARIES}      ${BOOST_CONTEXT_LIBRARY} -    ${BOOST_COROUTINE_LIBRARY} +    ${BOOST_FIBER_LIBRARY}      ${WINDOWS_LIBRARIES}      ${DXGUID_LIBRARY}      ${GOOGLE_PERFTOOLS_LIBRARIES} | 
