Merge.
commit
aedbb55de4
|
|
@ -142,6 +142,6 @@ endif (USESYSTEMLIBS)
|
|||
|
||||
if (LINUX)
|
||||
set(BOOST_SYSTEM_LIBRARY ${BOOST_SYSTEM_LIBRARY} rt)
|
||||
set(BOOST_SYSTEM_LIBRARY ${BOOST_THREAD_LIBRARY} rt)
|
||||
set(BOOST_THREAD_LIBRARY ${BOOST_THREAD_LIBRARY} rt)
|
||||
endif (LINUX)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,14 +29,8 @@
|
|||
#if ! defined(LL_LLCOROS_H)
|
||||
#define LL_LLCOROS_H
|
||||
|
||||
// <FS:TS> This silliness is needed because LL renamed the Boost coroutines
|
||||
// functions to dcoroutines.
|
||||
#if LL_USESYSTEMLIBS
|
||||
#include <boost/coroutine/coroutine.hpp>
|
||||
#define dcoroutines coroutines
|
||||
#else
|
||||
#include <boost/dcoroutine/coroutine.hpp>
|
||||
#endif
|
||||
#include <boost/dcoroutine/future.hpp>
|
||||
#include "llsingleton.h"
|
||||
#include <boost/ptr_container/ptr_map.hpp>
|
||||
#include <boost/function.hpp>
|
||||
|
|
@ -169,6 +163,15 @@ public:
|
|||
static void set_consuming(bool consuming);
|
||||
static bool get_consuming();
|
||||
|
||||
/**
|
||||
* Please do NOT directly use boost::dcoroutines::future! It is essential
|
||||
* to maintain the "current" coroutine at every context switch. This
|
||||
* Future wraps the essential boost::dcoroutines::future functionality
|
||||
* with that maintenance.
|
||||
*/
|
||||
template <typename T>
|
||||
class Future;
|
||||
|
||||
private:
|
||||
LLCoros();
|
||||
friend class LLSingleton<LLCoros>;
|
||||
|
|
@ -240,4 +243,42 @@ private:
|
|||
|
||||
} // 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);
|
||||
}
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return bool(mFuture);
|
||||
}
|
||||
|
||||
bool operator!() const
|
||||
{
|
||||
return ! mFuture;
|
||||
}
|
||||
|
||||
T get()
|
||||
{
|
||||
// instantiate Suspending to manage the "current" coroutine
|
||||
llcoro::Suspending suspended;
|
||||
return *mFuture;
|
||||
}
|
||||
|
||||
private:
|
||||
dfuture mFuture;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLCOROS_H) */
|
||||
|
|
|
|||
|
|
@ -34,12 +34,11 @@
|
|||
#include <map>
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/dcoroutine/coroutine.hpp>
|
||||
#include <boost/dcoroutine/future.hpp>
|
||||
// other Linden headers
|
||||
#include "llsdserialize.h"
|
||||
#include "llerror.h"
|
||||
#include "llcoros.h"
|
||||
#include "llmake.h"
|
||||
|
||||
#include "lleventfilter.h"
|
||||
|
||||
|
|
@ -145,6 +144,36 @@ 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()
|
||||
|
|
@ -167,13 +196,13 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ
|
|||
const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath)
|
||||
{
|
||||
// declare the future
|
||||
boost::dcoroutines::future<LLSD_consumed> future(LLCoros::get_self());
|
||||
LLCoros::Future<LLSD> future;
|
||||
// make a callback that will assign a value to the future, and listen on
|
||||
// the specified LLEventPump with that callback
|
||||
std::string listenerName(listenerNameForCoro());
|
||||
LLTempBoundListener connection(
|
||||
replyPump.getPump().listen(listenerName,
|
||||
voidlistener(boost::dcoroutines::make_callback(future))));
|
||||
llmake<FutureListener>(future.make_callback())));
|
||||
// skip the "post" part if requestPump is default-constructed
|
||||
if (requestPump)
|
||||
{
|
||||
|
|
@ -192,66 +221,55 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ
|
|||
LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
|
||||
<< " about to wait on LLEventPump " << replyPump.getPump().getName()
|
||||
<< LL_ENDL;
|
||||
// trying to dereference ("resolve") the future makes us wait for it
|
||||
LLSD_consumed value;
|
||||
{
|
||||
// instantiate Suspending to manage the "current" coroutine
|
||||
llcoro::Suspending suspended;
|
||||
value = *future;
|
||||
} // destroy Suspending as soon as we're back
|
||||
// calling get() on the future makes us wait for it
|
||||
LLSD value(future.get());
|
||||
LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
|
||||
<< " resuming with " << value.first << LL_ENDL;
|
||||
// immediately set consumed according to consuming
|
||||
*value.second = LLCoros::get_consuming();
|
||||
<< " resuming with " << value << LL_ENDL;
|
||||
// returning should disconnect the connection
|
||||
return value.first;
|
||||
return value;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
typedef std::pair<LLEventWithID, bool*> LLEventWithID_consumed;
|
||||
|
||||
/**
|
||||
* This helper is specifically for the two-pump version of suspendUntilEventOn().
|
||||
* 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 VoidListener for the purpose. The
|
||||
* basic idea is that we construct a distinct instance of WaitForEventOnHelper
|
||||
* -- binding different instance data -- for each of the pumps. Then, when a
|
||||
* pump delivers an LLSD value to either WaitForEventOnHelper, it can combine
|
||||
* that LLSD with its discriminator to feed the future object.
|
||||
* 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>
|
||||
class WaitForEventOnHelper
|
||||
template <typename LISTENER, typename DISCRIM>
|
||||
class FutureListener2: public FutureListener<LISTENER>
|
||||
{
|
||||
typedef FutureListener<LISTENER> super;
|
||||
|
||||
public:
|
||||
WaitForEventOnHelper(const LISTENER& listener, int discriminator):
|
||||
mListener(listener),
|
||||
// instantiated on coroutine stack: the stack about to suspend
|
||||
FutureListener2(const LISTENER& listener, DISCRIM discriminator):
|
||||
super(listener),
|
||||
mDiscrim(discriminator)
|
||||
{}
|
||||
|
||||
// this signature is required for an LLEventPump listener
|
||||
// called on main stack: the stack on which event is fired
|
||||
bool operator()(const LLSD& event)
|
||||
{
|
||||
bool consumed = false;
|
||||
// our future object is defined to accept LLEventWithID_consumed
|
||||
mListener(LLEventWithID_consumed(LLEventWithID(event, mDiscrim), &consumed));
|
||||
// our future object is defined to accept LLEventWithID
|
||||
super::mListener(LLEventWithID(event, mDiscrim));
|
||||
// tell LLEventPump whether or not event was consumed
|
||||
return consumed;
|
||||
return super::mConsume;
|
||||
}
|
||||
private:
|
||||
LISTENER mListener;
|
||||
const int mDiscrim;
|
||||
};
|
||||
|
||||
/// WaitForEventOnHelper type-inference helper
|
||||
template <typename LISTENER>
|
||||
WaitForEventOnHelper<LISTENER> wfeoh(const LISTENER& listener, int discriminator)
|
||||
{
|
||||
return WaitForEventOnHelper<LISTENER>(listener, discriminator);
|
||||
}
|
||||
private:
|
||||
const DISCRIM mDiscrim;
|
||||
};
|
||||
|
||||
} // anonymous
|
||||
|
||||
|
|
@ -266,16 +284,18 @@ LLEventWithID postAndSuspend2(const LLSD& event,
|
|||
const LLSD& replyPump1NamePath)
|
||||
{
|
||||
// declare the future
|
||||
boost::dcoroutines::future<LLEventWithID_consumed> future(LLCoros::get_self());
|
||||
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",
|
||||
wfeoh(boost::dcoroutines::make_callback(future), 0)));
|
||||
replyPump0.getPump().listen(
|
||||
name + "a",
|
||||
llmake<FutureListener2>(future.make_callback(), 0)));
|
||||
LLTempBoundListener connection1(
|
||||
replyPump1.getPump().listen(name + "b",
|
||||
wfeoh(boost::dcoroutines::make_callback(future), 1)));
|
||||
replyPump1.getPump().listen(
|
||||
name + "b",
|
||||
llmake<FutureListener2>(future.make_callback(), 1)));
|
||||
// skip the "post" part if requestPump is default-constructed
|
||||
if (requestPump)
|
||||
{
|
||||
|
|
@ -294,20 +314,13 @@ LLEventWithID postAndSuspend2(const LLSD& event,
|
|||
LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name
|
||||
<< " about to wait on LLEventPumps " << replyPump0.getPump().getName()
|
||||
<< ", " << replyPump1.getPump().getName() << LL_ENDL;
|
||||
// trying to dereference ("resolve") the future makes us wait for it
|
||||
LLEventWithID_consumed value;
|
||||
{
|
||||
// instantiate Suspending to manage "current" coroutine
|
||||
llcoro::Suspending suspended;
|
||||
value = *future;
|
||||
} // destroy Suspending as soon as we're back
|
||||
// calling get() on the future makes us wait for it
|
||||
LLEventWithID value(future.get());
|
||||
LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << name
|
||||
<< " resuming with (" << value.first.first
|
||||
<< ", " << value.first.second << ")" << LL_ENDL;
|
||||
// tell LLEventPump whether we're consuming
|
||||
*value.second = LLCoros::get_consuming();
|
||||
<< " resuming with (" << value.first << ", " << value.second << ")"
|
||||
<< LL_ENDL;
|
||||
// returning should disconnect both connections
|
||||
return value.first;
|
||||
return value;
|
||||
}
|
||||
|
||||
LLSD errorException(const LLEventWithID& result, const std::string& desc)
|
||||
|
|
|
|||
|
|
@ -76,36 +76,6 @@ private:
|
|||
namespace llcoro
|
||||
{
|
||||
|
||||
typedef std::pair<LLSD, bool*> LLSD_consumed;
|
||||
|
||||
/// This is an adapter for a signature like void LISTENER(const LLSD&), which
|
||||
/// isn't a valid LLEventPump listener: such listeners should return bool.
|
||||
template <typename LISTENER>
|
||||
class VoidListener
|
||||
{
|
||||
public:
|
||||
VoidListener(const LISTENER& listener):
|
||||
mListener(listener)
|
||||
{}
|
||||
|
||||
bool operator()(const LLSD& event)
|
||||
{
|
||||
bool consumed = false;
|
||||
mListener(LLSD_consumed(event, &consumed));
|
||||
// tell upstream LLEventPump whether listener consumed
|
||||
return consumed;
|
||||
}
|
||||
private:
|
||||
LISTENER mListener;
|
||||
};
|
||||
|
||||
/// VoidListener helper function to infer the type of the LISTENER
|
||||
template <typename LISTENER>
|
||||
VoidListener<LISTENER> voidlistener(const LISTENER& listener)
|
||||
{
|
||||
return VoidListener<LISTENER>(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Yield control from a coroutine for one "mainloop" tick. If your coroutine
|
||||
* runs without suspending for nontrivial time, sprinkle in calls to this
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* @file llmake.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2015-12-18
|
||||
* @brief Generic llmake<Template>(arg) function to instantiate
|
||||
* Template<decltype(arg)>(arg).
|
||||
*
|
||||
* Many of our class templates have an accompanying helper function to
|
||||
* make an instance with arguments of arbitrary type. llmake()
|
||||
* eliminates the need to declare a new helper function for every such
|
||||
* class template.
|
||||
*
|
||||
* also relevant:
|
||||
*
|
||||
* Template parameter deduction for constructors
|
||||
* http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html
|
||||
*
|
||||
* https://github.com/viboes/std-make
|
||||
*
|
||||
* but obviously we're not there yet.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2015&license=viewerlgpl$
|
||||
* Copyright (c) 2015, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLMAKE_H)
|
||||
#define LL_LLMAKE_H
|
||||
|
||||
/*==========================================================================*|
|
||||
// When we allow ourselves to compile with C++11 features enabled, this form
|
||||
// should generically handle an arbitrary number of arguments.
|
||||
|
||||
template <template<typename...> class CLASS_TEMPLATE, typename... ARGS>
|
||||
CLASS_TEMPLATE<ARGS...> llmake(ARGS && ... args)
|
||||
{
|
||||
return CLASS_TEMPLATE<ARGS...>(std::forward<ARGS>(args)...);
|
||||
}
|
||||
|*==========================================================================*/
|
||||
|
||||
// As of 2015-12-18, this is what we'll use instead. Add explicit overloads
|
||||
// for different numbers of template parameters as use cases arise.
|
||||
|
||||
/**
|
||||
* Usage: llmake<SomeTemplate>(arg)
|
||||
*
|
||||
* Deduces the type T of 'arg' and returns an instance of SomeTemplate<T>
|
||||
* initialized with 'arg'. Assumes a constructor accepting T (by value,
|
||||
* reference or whatever).
|
||||
*/
|
||||
template <template<typename> class CLASS_TEMPLATE, typename ARG1>
|
||||
CLASS_TEMPLATE<ARG1> llmake(const ARG1& arg1)
|
||||
{
|
||||
return CLASS_TEMPLATE<ARG1>(arg1);
|
||||
}
|
||||
|
||||
template <template<typename, typename> class CLASS_TEMPLATE, typename ARG1, typename ARG2>
|
||||
CLASS_TEMPLATE<ARG1, ARG2> llmake(const ARG1& arg1, const ARG2& arg2)
|
||||
{
|
||||
return CLASS_TEMPLATE<ARG1, ARG2>(arg1, arg2);
|
||||
}
|
||||
|
||||
#endif /* ! defined(LL_LLMAKE_H) */
|
||||
|
|
@ -65,14 +65,11 @@
|
|||
// 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.
|
||||
#define BOOST_RESULT_OF_USE_TR1 1
|
||||
#include <boost/dcoroutine/coroutine.hpp>
|
||||
// Normally, lleventcoro.h obviates future.hpp. We only include this because
|
||||
// we implement a "by hand" test of future functionality.
|
||||
#include <boost/dcoroutine/future.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/range.hpp>
|
||||
#include <boost/utility.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
|
|
@ -218,7 +215,7 @@ namespace tut
|
|||
// use static data so we can intersperse coroutine functions with the
|
||||
// tests that engage them
|
||||
ImmediateAPI immediateAPI;
|
||||
std::string replyName, errorName, threw;
|
||||
std::string replyName, errorName, threw, stringdata;
|
||||
LLSD result, errordata;
|
||||
int which;
|
||||
|
||||
|
|
@ -228,28 +225,40 @@ namespace tut
|
|||
replyName.clear();
|
||||
errorName.clear();
|
||||
threw.clear();
|
||||
stringdata.clear();
|
||||
result = LLSD();
|
||||
errordata = LLSD();
|
||||
which = 0;
|
||||
}
|
||||
|
||||
void explicit_wait(boost::dcoroutines::coroutine<void()>::self& self)
|
||||
void explicit_wait(boost::shared_ptr<LLCoros::Future<std::string>::callback_t>& cbp)
|
||||
{
|
||||
BEGIN
|
||||
{
|
||||
// ... do whatever preliminary stuff must happen ...
|
||||
// The point of this test is to verify / illustrate suspending a
|
||||
// coroutine for something other than an LLEventPump. In other
|
||||
// words, this shows how to adapt to any async operation that
|
||||
// provides a callback-style notification (and prove that it
|
||||
// works).
|
||||
|
||||
LLCoros::Future<std::string> future;
|
||||
// get the callback from that future
|
||||
LLCoros::Future<std::string>::callback_t callback(future.make_callback());
|
||||
|
||||
// Perhaps we would send a request to a remote server and arrange
|
||||
// for 'callback' to be called on response. Of course that might
|
||||
// involve an adapter object from the actual callback signature to
|
||||
// the signature of 'callback' -- in this case, void(std::string).
|
||||
// For test purposes, instead of handing 'callback' (or the
|
||||
// adapter) off to some I/O subsystem, we'll just pass it back to
|
||||
// our caller.
|
||||
cbp.reset(new LLCoros::Future<std::string>::callback_t(callback));
|
||||
|
||||
// declare the future
|
||||
boost::dcoroutines::future<llcoro::LLSD_consumed> future(self);
|
||||
// tell the future what to suspend for
|
||||
LLTempBoundListener connection(
|
||||
LLEventPumps::instance().obtain("source").listen("coro", voidlistener(boost::dcoroutines::make_callback(future))));
|
||||
ensure("Not yet", ! future);
|
||||
// attempting to dereference ("resolve") the future causes the calling
|
||||
// coroutine to suspend for it
|
||||
// calling get() on the future causes us to suspend
|
||||
debug("about to suspend");
|
||||
result = (*future).first;
|
||||
ensure("Got it", future);
|
||||
stringdata = future.get();
|
||||
ensure("Got it", bool(future));
|
||||
}
|
||||
END
|
||||
}
|
||||
|
|
@ -262,19 +271,16 @@ namespace tut
|
|||
DEBUG;
|
||||
|
||||
// Construct the coroutine instance that will run explicit_wait.
|
||||
// Pass the ctor a callable that accepts the coroutine_type::self
|
||||
// param passed by the library.
|
||||
boost::dcoroutines::coroutine<void()> coro(explicit_wait);
|
||||
// Start the coroutine
|
||||
coro(std::nothrow);
|
||||
// When the coroutine waits for the event pump, it returns here.
|
||||
debug("about to send");
|
||||
// Satisfy the suspend.
|
||||
LLEventPumps::instance().obtain("source").post("received");
|
||||
// Now suspend for the coroutine to complete.
|
||||
ensure("coroutine complete", ! coro);
|
||||
boost::shared_ptr<LLCoros::Future<std::string>::callback_t> respond;
|
||||
LLCoros::instance().launch("test<2>",
|
||||
boost::bind(explicit_wait, boost::ref(respond)));
|
||||
// When the coroutine waits for the future, it returns here.
|
||||
debug("about to respond");
|
||||
// Now we're the I/O subsystem delivering a result. This immediately
|
||||
// transfers control back to the coroutine.
|
||||
(*respond)("received");
|
||||
// ensure the coroutine ran and woke up again with the intended result
|
||||
ensure_equals(result.asString(), "received");
|
||||
ensure_equals(stringdata, "received");
|
||||
}
|
||||
|
||||
void waitForEventOn1()
|
||||
|
|
|
|||
|
|
@ -69,13 +69,17 @@ target_link_libraries(llupdaterservice
|
|||
)
|
||||
|
||||
if(LL_TESTS)
|
||||
if (NOT LINUX)
|
||||
SET(llupdater_service_TEST_SOURCE_FILES
|
||||
llupdaterservice.cpp
|
||||
)
|
||||
|
||||
set(test_libs
|
||||
${LLCOMMON_LIBRARIES})
|
||||
|
||||
${LLCOMMON_LIBRARIES}
|
||||
${BOOST_COROUTINE_LIBRARY}
|
||||
${BOOST_CONTEXT_LIBRARY}
|
||||
${BOOST_THREAD_LIBRARY}
|
||||
${BOOST_SYSTEM_LIBRARY})
|
||||
|
||||
set_source_files_properties(
|
||||
llupdaterservice.cpp
|
||||
|
|
@ -87,7 +91,6 @@ set_source_files_properties(
|
|||
# LL_TEST_ADDITIONAL_CFLAGS "-Dllifstream=llus_mock_llifstream"
|
||||
)
|
||||
|
||||
if (NOT LINUX)
|
||||
LL_ADD_PROJECT_UNIT_TESTS(llupdaterservice "${llupdater_service_TEST_SOURCE_FILES}" ${test_libs})
|
||||
endif (NOT LINUX)
|
||||
endif(LL_TESTS)
|
||||
|
|
|
|||
Loading…
Reference in New Issue