meow-7.2.2
Nicky 2015-12-22 09:04:18 +01:00
commit aedbb55de4
7 changed files with 227 additions and 131 deletions

View File

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

View File

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

View File

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

View File

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

63
indra/llcommon/llmake.h Normal file
View File

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

View File

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

View File

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