diff options
| author | Nat Goodspeed <nat@lindenlab.com> | 2019-10-16 09:15:47 -0400 | 
|---|---|---|
| committer | Nat Goodspeed <nat@lindenlab.com> | 2020-03-25 18:58:16 -0400 | 
| commit | 53aeea4d82801f5d624a4f6a62090195d3a24f2f (patch) | |
| tree | cb44db71ccdf56ef1f430e981c4fc58f73085db9 /indra/llcommon | |
| parent | 6945755e5299e071e46d53f09821ac73993f7afe (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.
Diffstat (limited to 'indra/llcommon')
| -rw-r--r-- | indra/llcommon/lleventfilter.cpp | 59 | ||||
| -rw-r--r-- | indra/llcommon/lleventfilter.h | 95 | ||||
| -rw-r--r-- | indra/llcommon/tests/lleventcoro_test.cpp | 78 | ||||
| -rw-r--r-- | indra/llcommon/tests/lleventfilter_test.cpp | 75 | 
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  /***************************************************************************** | 
