Automated merge with ssh://bitbucket.org/lindenlab/viewer-release
commit
d2c581c344
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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} )
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ set(LLCOREHTTP_INCLUDE_DIRS
|
|||
)
|
||||
|
||||
set(LLCOREHTTP_LIBRARIES llcorehttp
|
||||
${BOOST_COROUTINE_LIBRARY}
|
||||
${BOOST_FIBER_LIBRARY}
|
||||
${BOOST_CONTEXT_LIBRARY}
|
||||
${BOOST_SYSTEM_LIBRARY})
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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) */
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|*==========================================================================*/
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ target_link_libraries(llprimitive
|
|||
${LLXML_LIBRARIES}
|
||||
${LLPHYSICSEXTENSIONS_LIBRARIES}
|
||||
${LLCHARACTER_LIBRARIES}
|
||||
${BOOST_COROUTINE_LIBRARY}
|
||||
${BOOST_FIBER_LIBRARY}
|
||||
${BOOST_CONTEXT_LIBRARY}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Reference in New Issue