summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2019-10-16 09:15:47 -0400
committerNat Goodspeed <nat@lindenlab.com>2020-03-25 18:58:16 -0400
commit53aeea4d82801f5d624a4f6a62090195d3a24f2f (patch)
treecb44db71ccdf56ef1f430e981c4fc58f73085db9
parent6945755e5299e071e46d53f09821ac73993f7afe (diff)
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.
-rw-r--r--indra/llcommon/lleventfilter.cpp59
-rw-r--r--indra/llcommon/lleventfilter.h95
-rw-r--r--indra/llcommon/tests/lleventcoro_test.cpp78
-rw-r--r--indra/llcommon/tests/lleventfilter_test.cpp75
4 files changed, 307 insertions, 0 deletions
diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp
index 9fb18dc67d..06b3cb769e 100644
--- a/indra/llcommon/lleventfilter.cpp
+++ b/indra/llcommon/lleventfilter.cpp
@@ -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;
+}
diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h
index 8e7c075581..79319353a7 100644
--- a/indra/llcommon/lleventfilter.h
+++ b/indra/llcommon/lleventfilter.h
@@ -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) */
diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp
index 4e774b27d9..c13920eefd 100644
--- a/indra/llcommon/tests/lleventcoro_test.cpp
+++ b/indra/llcommon/tests/lleventcoro_test.cpp
@@ -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> >();
+ }
}
diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp
index 1875013794..fa2cb03e95 100644
--- a/indra/llcommon/tests/lleventfilter_test.cpp
+++ b/indra/llcommon/tests/lleventfilter_test.cpp
@@ -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
/*****************************************************************************