Automated merge with ssh://bitbucket.org/lindenlab/viewer-release

master
Nat Goodspeed 2018-12-28 14:46:49 -05:00
commit d2c581c344
28 changed files with 367 additions and 1445 deletions

View File

@ -166,9 +166,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>d318c25353e41215f1f523d58cacfd44</string>
<string>d9d2b71071c2168863696d2e2e52f364</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/893/1984/boost-1.57-darwin64-500883.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9651/45454/boost-1.65.1-darwin64-509640.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -190,9 +190,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>8e7ee97c3083f44385b09420655ebd04</string>
<string>038853b97307a9b65de20c4c50098023</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/892/1989/boost-1.57-linux64-500883.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9675/45694/boost-1.65.1-linux64-509640.tar.bz2</string>
</map>
<key>name</key>
<string>linux64</string>
@ -202,9 +202,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>80b1963d635e883cb5ed223e94406adb</string>
<string>f55c6b984d687646619a52937f3d1d9a</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/894/1976/boost-1.57-windows-500883.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9652/45331/boost-1.65.1-windows-509640.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
@ -214,16 +214,16 @@
<key>archive</key>
<map>
<key>hash</key>
<string>3d6a6373ed0daa490cdb4f92db45de52</string>
<string>baf1254595362a3f93ce45204b30ad02</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/895/1979/boost-1.57-windows64-500883.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9653/45336/boost-1.65.1-windows64-509640.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>1.57</string>
<string>1.65.1</string>
</map>
<key>chardet</key>
<map>
@ -284,9 +284,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>fa93a9a10fa379091e3e7b85665690d9</string>
<string>fe6c94c11d736288035c2d0bb3e8542d</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/913/2026/colladadom-2.3.500902-darwin64-500902.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9694/45723/colladadom-2.3.509683-darwin64-509683.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -308,9 +308,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>868127582794d6fd32fa69c9be4e83e4</string>
<string>c90613240ba3e3a171d3379275ae4ee3</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/912/2031/colladadom-2.3.500902-linux64-500902.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9695/45732/colladadom-2.3.509683-linux64-509683.tar.bz2</string>
</map>
<key>name</key>
<string>linux64</string>
@ -320,9 +320,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>5bd7875e16e7f88e21f4c44fe7c6433f</string>
<string>f4ea0cd1b64964e4e31d6e3437ad36a8</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/915/2035/colladadom-2.3.500902-windows-500902.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9699/45747/colladadom-2.3.509683-windows-509683.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
@ -332,16 +332,16 @@
<key>archive</key>
<map>
<key>hash</key>
<string>8a647129a0a0a31594557785ea85f840</string>
<string>91905d5293ae568a6ff58cfc644bb141</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/914/2034/colladadom-2.3.500902-windows64-500902.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9696/45739/colladadom-2.3.509683-windows64-509683.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>2.3.500902</string>
<string>2.3.509683</string>
</map>
<key>curl</key>
<map>
@ -1236,9 +1236,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>1a8081953bdf1bbbc9b8a8e6e062c02d</string>
<string>51f0cff72d1673772d70a6fab3154041</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/919/2048/googlemock-1.7.0.500908-darwin64-500908.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9698/45738/googlemock-1.7.0.509686-darwin64-509686.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -1260,9 +1260,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>0f606bf01f933f00edeb9bf9a2530930</string>
<string>ff459b58695c76838782847a0b792104</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/918/2056/googlemock-1.7.0.500908-linux64-500908.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9697/45717/googlemock-1.7.0.509686-linux64-509686.tar.bz2</string>
</map>
<key>name</key>
<string>linux64</string>
@ -1272,9 +1272,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>d01c9b12be6c5bb0749441495d45cba3</string>
<string>d69ba513a9cb2e13fa1addbd1003932c</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/920/2051/googlemock-1.7.0.500908-windows-500908.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9704/45766/googlemock-1.7.0.509686-windows-509686.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
@ -1284,16 +1284,16 @@
<key>archive</key>
<map>
<key>hash</key>
<string>e508a2ac7900853cc551666d0cf06541</string>
<string>49b089c60f869cf4f1010c9a44f20579</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/921/2059/googlemock-1.7.0.500908-windows64-500908.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9701/45760/googlemock-1.7.0.509686-windows64-509686.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>1.7.0.500908</string>
<string>1.7.0.509686</string>
</map>
<key>gstreamer</key>
<map>

View File

@ -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)

View File

@ -52,7 +52,7 @@ INCLUDE(GoogleMock)
${GOOGLEMOCK_INCLUDE_DIRS}
)
SET(alltest_LIBRARIES
${BOOST_COROUTINE_LIBRARY}
${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
${GOOGLEMOCK_LIBRARIES}
@ -200,7 +200,7 @@ FUNCTION(LL_ADD_INTEGRATION_TEST
SET(libraries
${library_dependencies}
${BOOST_COROUTINE_LIBRARY}
${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
${GOOGLEMOCK_LIBRARIES}

View File

@ -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}
)

View File

@ -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} )

View File

@ -12,6 +12,6 @@ set(LLCOREHTTP_INCLUDE_DIRS
)
set(LLCOREHTTP_LIBRARIES llcorehttp
${BOOST_COROUTINE_LIBRARY}
${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY})

View File

@ -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}

View File

@ -43,7 +43,6 @@ set(llcommon_SOURCE_FILES
llcleanup.cpp
llcommon.cpp
llcommonutils.cpp
llcoro_get_id.cpp
llcoros.cpp
llcrc.cpp
llcriticaldamp.cpp
@ -144,7 +143,6 @@ set(llcommon_HEADER_FILES
llcleanup.h
llcommon.h
llcommonutils.h
llcoro_get_id.h
llcoros.h
llcrc.h
llcriticaldamp.h
@ -284,7 +282,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}
@ -313,7 +311,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})

View File

@ -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

View File

@ -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) */

View File

@ -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;
}
//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;
}
//static
LLCoros::coro::self& LLCoros::get_self()
LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
{
CoroData& current = get_CoroData("get_self()");
if (! current.mSelf)
{
LL_ERRS("LLCoros") << "Calling get_self() from non-coroutine context!" << LL_ENDL;
}
return *current.mSelf;
// 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::id LLCoros::get_self()
{
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,20 @@ 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(std::nothrow) CoroData(name);
if (corodata == NULL)
{
// Out of memory?
printActiveCoroutines();
LL_ERRS("LLCoros") << "Failed to start coroutine: " << name << " Stacksize: " << mStackSize << " Total coroutines: " << mCoros.size() << LL_ENDL;
}
// 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 +272,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);
}

View File

@ -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
@ -170,141 +166,57 @@ public:
static bool get_consuming();
/**
* Please do NOT directly use boost::dcoroutines::future! It is essential
* to maintain the "current" coroutine at every context switch. This
* Future wraps the essential boost::dcoroutines::future functionality
* with that maintenance.
* 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();
// 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};
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
{
/// 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) */

View File

@ -31,17 +31,14 @@
// 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 "llerror.h"
#include "llcoros.h"
#include "llmake.h"
#include "llexception.h"
#include "lleventfilter.h"
namespace
{
@ -145,65 +142,47 @@ void storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value)
*pdest = value;
}
/// For LLCoros::Future<LLSD>::make_callback(), the callback has a signature
/// like void callback(LLSD), which isn't a valid LLEventPump listener: such
/// listeners must return bool.
template <typename LISTENER>
class FutureListener
{
public:
// FutureListener is instantiated on the coroutine stack: the stack, in
// other words, that wants to suspend.
FutureListener(const LISTENER& listener):
mListener(listener),
// Capture the suspending coroutine's flag as a consuming or
// non-consuming listener.
mConsume(LLCoros::get_consuming())
{}
// operator()() is called on the main stack: the stack on which the
// expected event is fired.
bool operator()(const LLSD& event)
{
mListener(event);
// tell upstream LLEventPump whether listener consumed
return mConsume;
}
protected:
LISTENER mListener;
bool mConsume;
};
} // anonymous
void llcoro::suspend()
{
// By viewer convention, we post an event on the "mainloop" LLEventPump
// each iteration of the main event-handling loop. So waiting for a single
// event on "mainloop" gives us a one-frame suspend.
suspendUntilEventOn("mainloop");
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)
{
@ -211,7 +190,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;
@ -219,9 +198,28 @@ 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;
return connection;
}
} // anonymous
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);
// declare the future
LLCoros::Future<LLSD> future = LLCoros::getFuture(promise);
// calling get() on the future makes us wait for it
LLSD value(future.get());
LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
@ -230,147 +228,43 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ
return value;
}
LLSD llcoro::suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName,
F32 timeoutin, const LLSD &timeoutResult)
LLSD llcoro::postAndSuspendWithTimeout(const LLSD& event,
const LLEventPumpOrPumpName& requestPump,
const LLEventPumpOrPumpName& replyPump,
const LLSD& replyPumpNamePath,
F32 timeout, 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();
LLCoros::Promise<LLSD> promise;
std::string listenerName(listenerNameForCoro());
LLTempBoundListener timeoutListener(timeoutPump.listen(suspendPump.getName(),
boost::bind(&LLEventPump::post, &suspendPump, _1)));
// Store connection into an LLTempBoundListener so we implicitly
// disconnect on return from this function.
LLTempBoundListener connection =
postAndSuspendSetup("postAndSuspendWithTimeout()", listenerName, promise,
event, requestPump, replyPump, replyPumpNamePath);
timeoutPump.eventAfter(timeoutin, timeoutResult);
return llcoro::suspendUntilEventOn(suspendPump);
}
namespace
{
/**
* This helper is specifically for postAndSuspend2(). We use a single future
* object, but we want to listen on two pumps with it. Since we must still
* adapt from the callable constructed by boost::dcoroutines::make_callback()
* (void return) to provide an event listener (bool return), we've adapted
* FutureListener for the purpose. The basic idea is that we construct a
* distinct instance of FutureListener2 -- binding different instance data --
* for each of the pumps. Then, when a pump delivers an LLSD value to either
* FutureListener2, it can combine that LLSD with its discriminator to feed
* the future object.
*
* DISCRIM is a template argument so we can use llmake() rather than
* having to write our own argument-deducing helper function.
*/
template <typename LISTENER, typename DISCRIM>
class FutureListener2: public FutureListener<LISTENER>
{
typedef FutureListener<LISTENER> super;
public:
// instantiated on coroutine stack: the stack about to suspend
FutureListener2(const LISTENER& listener, DISCRIM discriminator):
super(listener),
mDiscrim(discriminator)
{}
// called on main stack: the stack on which event is fired
bool operator()(const LLSD& event)
{
// our future object is defined to accept LLEventWithID
super::mListener(LLEventWithID(event, mDiscrim));
// tell LLEventPump whether or not event was consumed
return super::mConsume;
}
private:
const DISCRIM mDiscrim;
};
} // anonymous
namespace llcoro
{
LLEventWithID postAndSuspend2(const LLSD& event,
const LLEventPumpOrPumpName& requestPump,
const LLEventPumpOrPumpName& replyPump0,
const LLEventPumpOrPumpName& replyPump1,
const LLSD& replyPump0NamePath,
const LLSD& replyPump1NamePath)
{
// declare the future
LLCoros::Future<LLEventWithID> future;
// either callback will assign a value to this future; listen on
// each specified LLEventPump with a callback
std::string name(listenerNameForCoro());
LLTempBoundListener connection0(
replyPump0.getPump().listen(
name + "a",
llmake<FutureListener2>(future.make_callback(), 0)));
LLTempBoundListener connection1(
replyPump1.getPump().listen(
name + "b",
llmake<FutureListener2>(future.make_callback(), 1)));
// skip the "post" part if requestPump is default-constructed
if (requestPump)
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)
{
// 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") << "postAndSuspendWithTimeout(): coroutine " << listenerName
<< " timed out after " << timeout << " seconds,"
<< " resuming with " << timeoutResult << LL_ENDL;
return timeoutResult;
}
LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name
<< " about to wait on LLEventPumps " << replyPump0.getPump().getName()
<< ", " << replyPump1.getPump().getName() << LL_ENDL;
// calling get() on the future makes us wait for it
LLEventWithID value(future.get());
LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << name
<< " resuming with (" << value.first << ", " << value.second << ")"
<< LL_ENDL;
// returning should disconnect both connections
return value;
}
LLSD errorException(const LLEventWithID& result, const std::string& desc)
{
// If the result arrived on the error pump (pump 1), instead of
// returning it, deliver it via exception.
if (result.second)
else
{
LLTHROW(LLErrorEvent(desc, result.first));
}
// That way, our caller knows a simple return must be from the reply
// pump (pump 0).
return result.first;
}
llassert_always(status == boost::fibers::future_status::ready);
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)
{
LL_ERRS("errorLog") << desc << ":" << std::endl;
LLSDSerialize::toPrettyXML(result.first, LL_CONT);
LL_CONT << LL_ENDL;
// 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

View File

@ -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) */

View File

@ -30,9 +30,8 @@
#include "llerror.h"
#include "llerrorcontrol.h" // LLError::is_available()
#include "lldependencies.h"
#include "llcoro_get_id.h"
#include "llcoros.h"
#include <boost/foreach.hpp>
#include <boost/unordered_map.hpp>
#include <algorithm>
#include <iostream> // std::cerr in dire emergency
#include <sstream>
@ -72,35 +71,31 @@ public:
// 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, LLSingletonBase::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;
// non-static method, cf. LLSingletonBase::get_initializing()
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);
}
};

View File

@ -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,47 +42,12 @@
#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"
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
*****************************************************************************/
@ -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,181 +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);
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 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);
debug("about to send");
LLEventPumps::instance().obtain(replyName).post("received");
debug("back from send");
ensure_equals(result.asString(), "received");
}
void coroPumpsLog()
{
BEGIN
{
LLCoroEventPumps waiter;
replyName = waiter.getName0();
errorName = waiter.getName1();
WrapLLErrs capture;
try
{
result = waiter.suspendWithLog();
debug("no exception");
}
catch (const WrapLLErrs::FatalException& e)
{
debug(STRINGIZE("exception " << e.what()));
threw = e.what();
}
}
END
}
template<> template<>
void object::test<12>()
{
clear();
set_test_name("coroPumpsLog");
DEBUG;
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
@ -547,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
@ -624,182 +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;
try
{
result = waiter.postAndSuspendWithLog(
LLSDMap("value", 31)("fail", LLSD()),
immediateAPI.getPump(), "reply", "error");
debug("no exception");
}
catch (const WrapLLErrs::FatalException& e)
{
debug(STRINGIZE("exception " << e.what()));
threw = e.what();
}
}
END
}
template<> template<>
void object::test<22>()
{
clear();
set_test_name("coroPumpsPostLog");
DEBUG;
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
|*==========================================================================*/

View File

@ -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}
)

View File

@ -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)

View File

@ -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);

View File

@ -80,7 +80,7 @@ target_link_libraries(llprimitive
${LLXML_LIBRARIES}
${LLPHYSICSEXTENSIONS_LIBRARIES}
${LLCHARACTER_LIBRARIES}
${BOOST_COROUTINE_LIBRARY}
${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
)

View File

@ -298,7 +298,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}")

View File

@ -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(

View File

@ -1963,7 +1963,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}
@ -2338,7 +2338,7 @@ if (LL_TESTS)
${OPENSSL_LIBRARIES}
${CRYPTO_LIBRARIES}
${LIBRT_LIBRARY}
${BOOST_COROUTINE_LIBRARY}
${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
)

View File

@ -1400,6 +1400,8 @@ bool LLAppViewer::doFrame()
// canonical per-frame event
mainloop.post(newFrame);
// give listeners a chance to run
llcoro::suspend();
if (!LLApp::isExiting())
{

View File

@ -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}

View File

@ -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}")

View File

@ -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.

View File

@ -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}