DRTVWR-476: Add LLEventLogProxy, LLEventLogProxyFor<T>.
LLEventLogProxy can be introduced to serve as a logging proxy for an existing LLEventPump subclass instance. Access through the LLEventLogProxy will be logged; access directly to the underlying LLEventPump will not. LLEventLogProxyFor<LLEventPumpSubclass> functions as a drop-in replacement for the original LLEventPumpSubclass instance. It internally instantiates LLEventPumpSubclass and serves as a proxy for that instance. Add unit tests for LLEventMailDrop and LLEventLogProxyFor<LLEventMailDrop>, both "plain" (events only) and via lleventcoro.h synchronization.master
parent
6945755e52
commit
53aeea4d82
|
|
@ -37,6 +37,7 @@
|
|||
// other Linden headers
|
||||
#include "llerror.h" // LL_ERRS
|
||||
#include "llsdutil.h" // llsd_matches()
|
||||
#include "stringize.h"
|
||||
|
||||
/*****************************************************************************
|
||||
* LLEventFilter
|
||||
|
|
@ -409,3 +410,61 @@ void LLEventBatchThrottle::setSize(std::size_t size)
|
|||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* LLEventLogProxy
|
||||
*****************************************************************************/
|
||||
LLEventLogProxy::LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak):
|
||||
// note: we are NOT using the constructor that implicitly connects!
|
||||
LLEventFilter(name, tweak),
|
||||
// instead we simply capture a reference to the subject LLEventPump
|
||||
mPump(source)
|
||||
{
|
||||
}
|
||||
|
||||
bool LLEventLogProxy::post(const LLSD& event) /* override */
|
||||
{
|
||||
auto counter = mCounter++;
|
||||
auto eventplus = event;
|
||||
if (eventplus.type() == LLSD::TypeMap)
|
||||
{
|
||||
eventplus["_cnt"] = counter;
|
||||
}
|
||||
std::string hdr{STRINGIZE(getName() << ": post " << counter)};
|
||||
LL_INFOS("LogProxy") << hdr << ": " << event << LL_ENDL;
|
||||
bool result = mPump.post(eventplus);
|
||||
LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL;
|
||||
return result;
|
||||
}
|
||||
|
||||
LLBoundListener LLEventLogProxy::listen_impl(const std::string& name,
|
||||
const LLEventListener& target,
|
||||
const NameList& after,
|
||||
const NameList& before)
|
||||
{
|
||||
LL_DEBUGS("LogProxy") << "LLEventLogProxy('" << getName() << "').listen('"
|
||||
<< name << "')" << LL_ENDL;
|
||||
return mPump.listen(name,
|
||||
[this, name, target](const LLSD& event)->bool
|
||||
{ return listener(name, target, event); },
|
||||
after,
|
||||
before);
|
||||
}
|
||||
|
||||
bool LLEventLogProxy::listener(const std::string& name,
|
||||
const LLEventListener& target,
|
||||
const LLSD& event) const
|
||||
{
|
||||
auto eventminus = event;
|
||||
std::string counter{"**"};
|
||||
if (eventminus.has("_cnt"))
|
||||
{
|
||||
counter = stringize(eventminus["_cnt"].asInteger());
|
||||
eventminus.erase("_cnt");
|
||||
}
|
||||
std::string hdr{STRINGIZE(getName() << " to " << name << " " << counter)};
|
||||
LL_INFOS("LogProxy") << hdr << ": " << eventminus << LL_ENDL;
|
||||
bool result = target(eventminus);
|
||||
LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL;
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -427,4 +427,99 @@ private:
|
|||
const bool mConsume;
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* LLEventLogProxy
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* LLEventLogProxy is a little different than the other LLEventFilter
|
||||
* subclasses declared in this header file, in that it completely wraps the
|
||||
* passed LLEventPump (both input and output) instead of simply processing its
|
||||
* output. Of course, if someone directly posts to the wrapped LLEventPump by
|
||||
* looking up its string name in LLEventPumps, LLEventLogProxy can't intercept
|
||||
* that post() call. But as long as consuming code is willing to access the
|
||||
* LLEventLogProxy instance instead of the wrapped LLEventPump, all event data
|
||||
* both post()ed and received is logged.
|
||||
*
|
||||
* The proxy role means that LLEventLogProxy intercepts more of LLEventPump's
|
||||
* API than a typical LLEventFilter subclass.
|
||||
*/
|
||||
class LLEventLogProxy: public LLEventFilter
|
||||
{
|
||||
typedef LLEventFilter super;
|
||||
public:
|
||||
/**
|
||||
* Construct LLEventLogProxy, wrapping the specified LLEventPump.
|
||||
* Unlike a typical LLEventFilter subclass, the name parameter is @emph
|
||||
* not optional because typically you want LLEventLogProxy to completely
|
||||
* replace the wrapped LLEventPump. So you give the subject LLEventPump
|
||||
* some other name and give the LLEventLogProxy the name that would have
|
||||
* been used for the subject LLEventPump.
|
||||
*/
|
||||
LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak=false);
|
||||
|
||||
/// register a new listener
|
||||
LLBoundListener listen_impl(const std::string& name, const LLEventListener& target,
|
||||
const NameList& after, const NameList& before);
|
||||
|
||||
/// Post an event to all listeners
|
||||
virtual bool post(const LLSD& event) /* override */;
|
||||
|
||||
private:
|
||||
/// This method intercepts each call to any target listener. We pass it
|
||||
/// the listener name and the caller's intended target listener plus the
|
||||
/// posted LLSD event.
|
||||
bool listener(const std::string& name,
|
||||
const LLEventListener& target,
|
||||
const LLSD& event) const;
|
||||
|
||||
LLEventPump& mPump;
|
||||
LLSD::Integer mCounter{0};
|
||||
};
|
||||
|
||||
/**
|
||||
* LLEventPumpHolder<T> is a helper for LLEventLogProxyFor<T>. It simply
|
||||
* stores an instance of T, presumably a subclass of LLEventPump. We derive
|
||||
* LLEventLogProxyFor<T> from LLEventPumpHolder<T>, ensuring that
|
||||
* LLEventPumpHolder's contained mWrappedPump is fully constructed before
|
||||
* passing it to LLEventLogProxyFor's LLEventLogProxy base class constructor.
|
||||
* But since LLEventPumpHolder<T> presents none of the LLEventPump API,
|
||||
* LLEventLogProxyFor<T> inherits its methods unambiguously from
|
||||
* LLEventLogProxy.
|
||||
*/
|
||||
template <class T>
|
||||
class LLEventPumpHolder
|
||||
{
|
||||
protected:
|
||||
LLEventPumpHolder(const std::string& name, bool tweak=false):
|
||||
mWrappedPump(name, tweak)
|
||||
{}
|
||||
T mWrappedPump;
|
||||
};
|
||||
|
||||
/**
|
||||
* LLEventLogProxyFor<T> is a wrapper around any of the LLEventPump subclasses.
|
||||
* Instantiating an LLEventLogProxy<T> instantiates an internal T. Otherwise
|
||||
* it behaves like LLEventLogProxy.
|
||||
*/
|
||||
template <class T>
|
||||
class LLEventLogProxyFor: private LLEventPumpHolder<T>, public LLEventLogProxy
|
||||
{
|
||||
// We derive privately from LLEventPumpHolder because it's an
|
||||
// implementation detail of LLEventLogProxyFor. The only reason it's a
|
||||
// base class at all is to guarantee that it's constructed first so we can
|
||||
// pass it to our LLEventLogProxy base class constructor.
|
||||
typedef LLEventPumpHolder<T> holder;
|
||||
typedef LLEventLogProxy super;
|
||||
|
||||
public:
|
||||
LLEventLogProxyFor(const std::string& name, bool tweak=false):
|
||||
// our wrapped LLEventPump subclass instance gets a name suffix
|
||||
// because that's not the LLEventPump we want consumers to obtain when
|
||||
// they ask LLEventPumps for this name
|
||||
holder(name + "-", tweak),
|
||||
// it's our LLEventLogProxy that gets the passed name
|
||||
super(holder::mWrappedPump, name, tweak)
|
||||
{}
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLEVENTFILTER_H) */
|
||||
|
|
|
|||
|
|
@ -37,12 +37,14 @@
|
|||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <typeinfo>
|
||||
|
||||
#include "../test/lltut.h"
|
||||
#include "llsd.h"
|
||||
#include "llsdutil.h"
|
||||
#include "llevents.h"
|
||||
#include "llcoros.h"
|
||||
#include "lleventfilter.h"
|
||||
#include "lleventcoro.h"
|
||||
#include "../test/debug.h"
|
||||
#include "../test/sync.h"
|
||||
|
|
@ -255,4 +257,80 @@ namespace tut
|
|||
LLCoros::instance().launch("test<5>", [this](){ coroPumpPost(); });
|
||||
ensure_equals(result.asInteger(), 18);
|
||||
}
|
||||
|
||||
template <class PUMP>
|
||||
void test()
|
||||
{
|
||||
PUMP pump(typeid(PUMP).name());
|
||||
bool running{false};
|
||||
LLSD data{LLSD::emptyArray()};
|
||||
// start things off by posting once before even starting the listener
|
||||
// coro
|
||||
LL_DEBUGS() << "test() posting first" << LL_ENDL;
|
||||
LLSD first{LLSDMap("desc", "first")("value", 0)};
|
||||
bool consumed = pump.post(first);
|
||||
ensure("should not have consumed first", ! consumed);
|
||||
// now launch the coro
|
||||
LL_DEBUGS() << "test() launching listener coro" << LL_ENDL;
|
||||
running = true;
|
||||
LLCoros::instance().launch(
|
||||
"listener",
|
||||
[&pump, &running, &data](){
|
||||
// important for this test that we consume posted values
|
||||
LLCoros::instance().set_consuming(true);
|
||||
// should immediately retrieve 'first' without waiting
|
||||
LL_DEBUGS() << "listener coro waiting for first" << LL_ENDL;
|
||||
data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
|
||||
// Don't use ensure() from within the coro -- ensure() failure
|
||||
// throws tut::fail, which won't propagate out to the main
|
||||
// test driver, which will result in an odd failure.
|
||||
// Wait for 'second' because it's not already pending.
|
||||
LL_DEBUGS() << "listener coro waiting for second" << LL_ENDL;
|
||||
data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
|
||||
// and wait for 'third', which should involve no further waiting
|
||||
LL_DEBUGS() << "listener coro waiting for third" << LL_ENDL;
|
||||
data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
|
||||
LL_DEBUGS() << "listener coro done" << LL_ENDL;
|
||||
running = false;
|
||||
});
|
||||
// back from coro at the point where it's waiting for 'second'
|
||||
LL_DEBUGS() << "test() posting second" << LL_ENDL;
|
||||
LLSD second{llsd::map("desc", "second", "value", 1)};
|
||||
consumed = pump.post(second);
|
||||
ensure("should have consumed second", consumed);
|
||||
// This is a key point: even though we've post()ed the value for which
|
||||
// the coroutine is waiting, it's actually still suspended until we
|
||||
// pause for some other reason. The coroutine will only pick up one
|
||||
// value at a time from our 'pump'. It's important to exercise the
|
||||
// case when we post() two values before it picks up either.
|
||||
LL_DEBUGS() << "test() posting third" << LL_ENDL;
|
||||
LLSD third{llsd::map("desc", "third", "value", 2)};
|
||||
consumed = pump.post(third);
|
||||
ensure("should NOT yet have consumed third", ! consumed);
|
||||
// now just wait for coro to finish -- which it eventually will, given
|
||||
// that all its suspend calls have short timeouts.
|
||||
while (running)
|
||||
{
|
||||
LL_DEBUGS() << "test() waiting for coro done" << LL_ENDL;
|
||||
llcoro::suspendUntilTimeout(0.1);
|
||||
}
|
||||
// okay, verify expected results
|
||||
ensure_equals("should have received three values", data,
|
||||
llsd::array(first, second, third));
|
||||
LL_DEBUGS() << "test() done" << LL_ENDL;
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<6>()
|
||||
{
|
||||
set_test_name("LLEventMailDrop");
|
||||
tut::test<LLEventMailDrop>();
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<7>()
|
||||
{
|
||||
set_test_name("LLEventLogProxyFor<LLEventMailDrop>");
|
||||
tut::test< LLEventLogProxyFor<LLEventMailDrop> >();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,9 +36,12 @@
|
|||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "stringize.h"
|
||||
#include "llsdutil.h"
|
||||
#include "listener.h"
|
||||
#include "tests/wrapllerrs.h"
|
||||
|
||||
#include <typeinfo>
|
||||
|
||||
/*****************************************************************************
|
||||
* Test classes
|
||||
*****************************************************************************/
|
||||
|
|
@ -401,6 +404,78 @@ namespace tut
|
|||
throttle.post(";17");
|
||||
ensure_equals("17", cat.result, "136;12;17"); // "17" delivered
|
||||
}
|
||||
|
||||
template<class PUMP>
|
||||
void test()
|
||||
{
|
||||
PUMP pump(typeid(PUMP).name());
|
||||
LLSD data{LLSD::emptyArray()};
|
||||
bool consumed{true};
|
||||
// listener that appends to 'data'
|
||||
// but that also returns the current value of 'consumed'
|
||||
// Instantiate this separately because we're going to listen()
|
||||
// multiple times with the same lambda: LLEventMailDrop only replays
|
||||
// queued events on a new listen() call.
|
||||
auto lambda =
|
||||
[&data, &consumed](const LLSD& event)->bool
|
||||
{
|
||||
data.append(event);
|
||||
return consumed;
|
||||
};
|
||||
{
|
||||
LLTempBoundListener conn = pump.listen("lambda", lambda);
|
||||
pump.post("first");
|
||||
}
|
||||
// first post() should certainly be received by listener
|
||||
ensure_equals("first", data, llsd::array("first"));
|
||||
// the question is, since consumed was true, did it queue the value?
|
||||
data = LLSD::emptyArray();
|
||||
{
|
||||
// if it queued the value, it would be delivered on subsequent
|
||||
// listen() call
|
||||
LLTempBoundListener conn = pump.listen("lambda", lambda);
|
||||
}
|
||||
ensure_equals("empty1", data, LLSD::emptyArray());
|
||||
data = LLSD::emptyArray();
|
||||
// now let's NOT consume the posted data
|
||||
consumed = false;
|
||||
{
|
||||
LLTempBoundListener conn = pump.listen("lambda", lambda);
|
||||
pump.post("second");
|
||||
pump.post("third");
|
||||
}
|
||||
// the two events still arrive
|
||||
ensure_equals("second,third1", data, llsd::array("second", "third"));
|
||||
data = LLSD::emptyArray();
|
||||
{
|
||||
// when we reconnect, these should be delivered again
|
||||
// but this time they should be consumed
|
||||
consumed = true;
|
||||
LLTempBoundListener conn = pump.listen("lambda", lambda);
|
||||
}
|
||||
// unconsumed events were delivered again
|
||||
ensure_equals("second,third2", data, llsd::array("second", "third"));
|
||||
data = LLSD::emptyArray();
|
||||
{
|
||||
// when we reconnect this time, no more unconsumed events
|
||||
LLTempBoundListener conn = pump.listen("lambda", lambda);
|
||||
}
|
||||
ensure_equals("empty2", data, LLSD::emptyArray());
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void filter_object::test<6>()
|
||||
{
|
||||
set_test_name("LLEventMailDrop");
|
||||
tut::test<LLEventMailDrop>();
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void filter_object::test<7>()
|
||||
{
|
||||
set_test_name("LLEventLogProxyFor<LLEventMailDrop>");
|
||||
tut::test< LLEventLogProxyFor<LLEventMailDrop> >();
|
||||
}
|
||||
} // namespace tut
|
||||
|
||||
/*****************************************************************************
|
||||
|
|
|
|||
Loading…
Reference in New Issue