From 7af4a49835880fc92461882d5354c0ff12a9672a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 23 Mar 2017 22:39:31 -0400 Subject: MAINT-6789: Add LLEventBatch, LLEventThrottle, LLEventBatchThrottle. These classes are as yet untested: they are straw people for API review, based on email conversations with Caladbolg and Rider. --- indra/llcommon/lleventfilter.cpp | 115 +++++++++++++++++++++++++++++++++++++++ indra/llcommon/lleventfilter.h | 104 +++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp index 64ab58adcd..1fd29ea9e5 100644 --- a/indra/llcommon/lleventfilter.cpp +++ b/indra/llcommon/lleventfilter.cpp @@ -148,6 +148,11 @@ bool LLEventTimeoutBase::tick(const LLSD&) return false; // show event to other listeners } +bool LLEventTimeoutBase::running() const +{ + return mMainloop.connected(); +} + LLEventTimeout::LLEventTimeout() {} LLEventTimeout::LLEventTimeout(LLEventPump& source): @@ -164,3 +169,113 @@ bool LLEventTimeout::countdownElapsed() const { return mTimer.hasExpired(); } + +LLEventBatch::LLEventBatch(std::size_t size): + LLEventFilter("batch"), + mBatchSize(size) +{} + +LLEventBatch::LLEventBatch(LLEventPump& source, std::size_t size): + LLEventFilter(source, "batch"), + mBatchSize(size) +{} + +void LLEventBatch::flush() +{ + // copy and clear mBatch BEFORE posting to avoid weird circularity effects + LLSD batch(mBatch); + mBatch.clear(); + LLEventStream::post(batch); +} + +bool LLEventBatch::post(const LLSD& event) +{ + mBatch.append(event); + if (mBatch.size() >= mBatchSize) + { + flush(); + } + return false; +} + +LLEventThrottle::LLEventThrottle(F32 interval): + LLEventFilter("throttle"), + mInterval(interval), + mPosts(0) +{} + +LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval): + LLEventFilter(source, "throttle"), + mInterval(interval), + mPosts(0) +{} + +void LLEventThrottle::flush() +{ + // flush() is a no-op unless there's something pending. + // Don't test mPending because there's no requirement that the consumer + // post() anything but an isUndefined(). This is what mPosts is for. + if (mPosts) + { + mPosts = 0; + mAlarm.cancel(); + // This is not to set our alarm; we are not yet requesting + // any notification. This is just to track whether subsequent post() + // calls fall within this mInterval or not. + mTimer.setTimerExpirySec(mInterval); + // copy and clear mPending BEFORE posting to avoid weird circularity + // effects + LLSD pending = mPending; + mPending.clear(); + LLEventStream::post(pending); + } +} + +LLSD LLEventThrottle::pending() const +{ + return mPending; +} + +bool LLEventThrottle::post(const LLSD& event) +{ + // Always capture most recent post() event data. If caller wants to + // aggregate multiple events, let them retrieve pending() and modify + // before calling post(). + mPending = event; + // Always increment mPosts. Unless we count this call, flush() does + // nothing. + ++mPosts; + // We reset mTimer on every flush() call to let us know if we're still + // within the same mInterval. So -- are we? + F32 timeRemaining = mTimer.getRemainingTimeF32(); + if (! timeRemaining) + { + // more than enough time has elapsed, immediately flush() + flush(); + } + else + { + // still within mInterval of the last flush() call: have to defer + if (! mAlarm.running()) + { + // timeRemaining tells us how much longer it will be until + // mInterval seconds since the last flush() call. At that time, + // flush() deferred events. + mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this)); + } + } +} + +LLEventBatchThrottle::LLEventBatchThrottle(F32 interval): + LLEventThrottle(interval) +{} + +LLEventBatchThrottle::LLEventBatchThrottle(LLEventPump& source, F32 interval): + LLEventThrottle(source, interval) +{} + +bool LLEventBatchThrottle::post(const LLSD& event) +{ + // simply retrieve pending value and append the new event to it + return LLEventThrottle::post(pending().append(event)); +} diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index 66f3c14869..1445b8a3b7 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -177,6 +177,9 @@ public: /// Cancel timer without event void cancel(); + /// Is this timer currently running? + bool running() const; + protected: virtual void setCountdown(F32 seconds) = 0; virtual bool countdownElapsed() const = 0; @@ -215,4 +218,105 @@ private: LLTimer mTimer; }; +/** + * LLEventBatch: accumulate post() events (LLSD blobs) into an LLSD Array + * until the array reaches a certain size, then call listeners with the Array + * and clear it back to empty. + */ +class LL_COMMON_API LLEventBatch: public LLEventFilter +{ +public: + // pass batch size + LLEventBatch(std::size_t size); + // construct and connect + LLEventBatch(LLEventPump& source, std::size_t size); + + // force out the pending batch + void flush(); + + // accumulate an event and flush() when big enough + virtual bool post(const LLSD& event); + +private: + LLSD mBatch; + std::size_t mBatchSize; +}; + +/** + * LLEventThrottle: construct with a time interval. Regardless of how + * frequently you call post(), LLEventThrottle will pass on an event to + * its listeners no more often than once per specified interval. + * + * A new event after more than the specified interval will immediately be + * passed along to listeners. But subsequent events will be delayed until at + * least one time interval since listeners were last called. Consider the + * sequence below. Suppose we have an LLEventThrottle constructed with an + * interval of 3 seconds. The numbers on the left are timestamps in seconds + * relative to an arbitrary reference point. + * + * 1: post(): event immediately passed to listeners, next no sooner than 4 + * 2: post(): deferred: waiting for 3 seconds to elapse + * 3: post(): deferred + * 4: no post() call, but event delivered to listeners; next no sooner than 7 + * 6: post(): deferred + * 7: no post() call, but event delivered; next no sooner than 10 + * 12: post(): immediately passed to listeners, next no sooner than 15 + * 17: post(): immediately passed to listeners, next no sooner than 20 + * + * For a deferred event, the LLSD blob delivered to listeners is from the most + * recent deferred post() call. However, you may obtain the previous event + * blob by calling pending(), modify it however you want and post() the new + * value. Each time an event is delivered to listeners, the pending() value is + * reset to isUndefined(). + * + * You may also call flush() to immediately pass along any deferred events to + * all listeners. + */ +class LL_COMMON_API LLEventThrottle: public LLEventFilter +{ +public: + // pass time interval + LLEventThrottle(F32 interval); + // construct and connect + LLEventThrottle(LLEventPump& source, F32 interval); + + // force out any deferred events + void flush(); + + // retrieve (aggregate) deferred event since last event sent to listeners + LLSD pending() const; + + // register an event, may be either passed through or deferred + virtual bool post(const LLSD& event); + +private: + // remember throttle interval + F32 mInterval; + // count post() calls since last flush() + std::size_t mPosts; + // pending event data from most recent deferred event + LLSD mPending; + // use this to arrange a deferred flush() call + LLEventTimeout mAlarm; + // use this to track whether we're within mInterval of last flush() + LLTimer mTimer; +}; + +/** + * LLEventBatchThrottle: like LLEventThrottle, it refuses to pass events to + * listeners more often than once per specified time interval. + * Like LLEventBatch, it accumulates pending events into an LLSD Array. + */ +class LLEventBatchThrottle: public LLEventThrottle +{ +public: + // pass time interval + LLEventBatchThrottle(F32 interval); + // construct and connect + LLEventBatchThrottle(LLEventPump& source, F32 interval); + + // append a new event to current batch + virtual bool post(const LLSD& event); +}; + #endif /* ! defined(LL_LLEVENTFILTER_H) */ -- cgit v1.2.3 From 2fe4f6b9187d9153e335bf54ea43fc89a7987459 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 24 Mar 2017 18:25:16 -0400 Subject: MAINT-6789: Add LLEventThrottle::getInterval(), setInterval() plus LLEventBatch::getSize(), setSize() plus LLEventThrottle::getPostCount() and getDelay(). The interesting thing about LLEventThrottle::setInterval() and LLEventBatch::setSize() is that either might cause an immediate flush(). --- indra/llcommon/lleventfilter.cpp | 49 ++++++++++++++++++++++++++++++++++++++++ indra/llcommon/lleventfilter.h | 14 ++++++++++++ 2 files changed, 63 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp index 1fd29ea9e5..87eb583cb6 100644 --- a/indra/llcommon/lleventfilter.cpp +++ b/indra/llcommon/lleventfilter.cpp @@ -198,6 +198,16 @@ bool LLEventBatch::post(const LLSD& event) return false; } +void LLEventBatch::setSize(std::size_t size) +{ + mBatchSize = size; + // changing the size might mean that we have to flush NOW + if (mBatch.size() >= mBatchSize) + { + flush(); + } +} + LLEventThrottle::LLEventThrottle(F32 interval): LLEventFilter("throttle"), mInterval(interval), @@ -264,6 +274,45 @@ bool LLEventThrottle::post(const LLSD& event) mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this)); } } + return false; +} + +void LLEventThrottle::setInterval(F32 interval) +{ + F32 oldInterval = mInterval; + mInterval = interval; + // If we are not now within oldInterval of the last flush(), we're done: + // this will only affect behavior starting with the next flush(). + F32 timeRemaining = mTimer.getRemainingTimeF32(); + if (timeRemaining) + { + // We are currently within oldInterval of the last flush(). Figure out + // how much time remains until (the new) mInterval of the last + // flush(). Bt we don't actually store a timestamp for the last + // flush(); it's implicit. There are timeRemaining seconds until what + // used to be the end of the interval. Move that endpoint by the + // difference between the new interval and the old. + timeRemaining += (mInterval - oldInterval); + // If we're called with a larger interval, the difference is positive + // and timeRemaining increases. + // If we're called with a smaller interval, the difference is negative + // and timeRemaining decreases. The interesting case is when it goes + // nonpositive: when the new interval means we can flush immediately. + if (timeRemaining <= 0.0f) + { + flush(); + } + else + { + // immediately reset mTimer + mTimer.setTimerExpirySec(timeRemaining); + // and if mAlarm is running, reset that too + if (mAlarm.running()) + { + mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this)); + } + } + } } LLEventBatchThrottle::LLEventBatchThrottle(F32 interval): diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index 1445b8a3b7..68890846a7 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -237,6 +237,10 @@ public: // accumulate an event and flush() when big enough virtual bool post(const LLSD& event); + // query or reset batch size + std::size_t getSize() const { return mBatchSize; } + void setSize(std::size_t size); + private: LLSD mBatch; std::size_t mBatchSize; @@ -289,6 +293,16 @@ public: // register an event, may be either passed through or deferred virtual bool post(const LLSD& event); + // query or reset interval + F32 getInterval() const { return mInterval; } + void setInterval(F32 interval); + + // deferred posts + std::size_t getPostCount() const { return mPosts; } + + // time until next event would be passed through, 0.0 if now + F32 getDelay() const { return mTimer.getRemainingTimeF32(); } + private: // remember throttle interval F32 mInterval; -- cgit v1.2.3 From c27dbc62148f51ff5848f63573f5816235f0cc35 Mon Sep 17 00:00:00 2001 From: mnikolenko Date: Mon, 3 Apr 2017 02:21:18 +0300 Subject: MAINT-6404 FIXED When pasting text with mac linebreak into a notecard, it shouldn't be removed --- indra/llcommon/llstring.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index a40db0f8cc..2255e638c2 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -336,6 +336,7 @@ public: static void addCRLF(string_type& string); static void removeCRLF(string_type& string); + static void removeWindowsCR(string_type& string); static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab ); static void replaceNonstandardASCII( string_type& string, T replacement ); @@ -1322,6 +1323,28 @@ void LLStringUtilBase::removeCRLF(string_type& string) //static template +void LLStringUtilBase::removeWindowsCR(string_type& string) +{ + const T LF = 10; + const T CR = 13; + + size_type cr_count = 0; + size_type len = string.size(); + size_type i; + for( i = 0; i < len - cr_count - 1; i++ ) + { + if( string[i+cr_count] == CR && string[i+cr_count+1] == LF) + { + cr_count++; + } + + string[i] = string[i+cr_count]; + } + string.erase(i, cr_count); +} + +//static +template void LLStringUtilBase::replaceChar( string_type& string, T target, T replacement ) { size_type found_pos = 0; -- cgit v1.2.3 From 10213f994f64de1f50e1ff6b9b5f43be549d19cb Mon Sep 17 00:00:00 2001 From: andreykproductengine Date: Fri, 7 Apr 2017 20:29:24 +0300 Subject: MAINT-6283 Names that contain 'http:' or 'https:' were causing new line in chat history --- indra/llcommon/lluri.cpp | 7 ++++--- indra/llcommon/lluri.h | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp index 9f12d49244..758b98e143 100644 --- a/indra/llcommon/lluri.cpp +++ b/indra/llcommon/lluri.cpp @@ -40,7 +40,8 @@ #include #include -void encode_character(std::ostream& ostr, std::string::value_type val) +// static +void LLURI::encodeCharacter(std::ostream& ostr, std::string::value_type val) { ostr << "%" @@ -95,7 +96,7 @@ std::string LLURI::escape( } else { - encode_character(ostr, c); + encodeCharacter(ostr, c); } } } @@ -106,7 +107,7 @@ std::string LLURI::escape( c = *it; if(allowed.find(c) == std::string::npos) { - encode_character(ostr, c); + encodeCharacter(ostr, c); } else { diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h index c82a666e48..9e44cc7da2 100644 --- a/indra/llcommon/lluri.h +++ b/indra/llcommon/lluri.h @@ -120,6 +120,14 @@ public: /** @name Escaping Utilities */ //@{ + /** + * @brief 'Escape' symbol into stream + * + * @param ostr Output stream. + * @param val Symbol to encode. + */ + static void encodeCharacter(std::ostream& ostr, std::string::value_type val); + /** * @brief Escape the string passed except for unreserved * -- cgit v1.2.3 From 0dcb423cf3dc42e11621eece972801b036657e91 Mon Sep 17 00:00:00 2001 From: andreykproductengine Date: Tue, 25 Apr 2017 17:48:34 +0300 Subject: MAINT-7145 Eliminate LLSingleton circular references --- indra/llcommon/llsingleton.cpp | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index 9025e53bb2..a3a87edd88 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -220,6 +220,9 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt std::find(initializing.begin(), initializing.end(), this); if (found != initializing.end()) { + list_t::const_iterator it_next = found; + it_next++; + // Report the circularity. Requiring the coder to dig through the // logic to diagnose exactly how we got here is less than helpful. std::ostringstream out; @@ -238,11 +241,30 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt // otherwise we'd be returning a pointer to a partially- // constructed object! But from initSingleton() is okay: that // method exists specifically to support circularity. - // Decide which log helper to call based on initState. They have - // identical signatures. - ((initState == CONSTRUCTING)? logerrs : logwarns) - ("LLSingleton circularity: ", out.str().c_str(), - demangle(typeid(*this).name()).c_str(), ""); + // Decide which log helper to call. + if (initState == CONSTRUCTING) + { + logerrs("LLSingleton circularity in Constructor: ", out.str().c_str(), + demangle(typeid(*this).name()).c_str(), ""); + } + else if (it_next == initializing.end()) + { + // Points to self after construction, but during initialization. + // Singletons can initialize other classes that depend onto them, + // so this is expected. + // + // Example: LLNotifications singleton initializes default channels. + // Channels register themselves with singleton once done. + logdebugs("LLSingleton circularity: ", out.str().c_str(), + demangle(typeid(*this).name()).c_str(), ""); + } + else + { + // Actual circularity with other singleton (or single singleton is used extensively). + // Dependency can be unclear. + logwarns("LLSingleton circularity: ", out.str().c_str(), + demangle(typeid(*this).name()).c_str(), ""); + } } else { -- cgit v1.2.3 From 9c66072cacae0b3b86a277780e3a19e94accd6bc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 10 May 2017 15:04:18 -0400 Subject: Add LLEventThrottle tests; actually *all* lleventfilter.cpp tests. For some reason there wasn't an entry in indra/llcommon/CMakeLists.txt to run the tests in indra/llcommon/tests/lleventfilter_test.cpp. It seems likely that at some point it existed, since all previous tests built and ran successfully. In any case, (re-)add lleventfilter_test.cpp to the set of llcommon tests. Also alphabetize them to make it easier to find a particular test invocation. Also add new tests for LLEventThrottle. To support this, refactor the concrete LLEventThrottle class into LLEventThrottleBase containing all the tricky logic, with pure virtual methods for access to LLTimer and LLEventTimeout, and an LLEventThrottle subclass containing the LLTimer and LLEventTimeout instances and corresponding implementations of the new pure virtual methods. That permits us to introduce TestEventThrottle, an alternate subclass with dummy implementations of the methods related to LLTimer and LLEventTimeout. In particular, we can explicitly advance simulated realtime to simulate particular LLTimer and LLEventTimeout behaviors. Finally, introduce Concat, a test LLEventPump listener class whose function is to concatenate received string event data into a composite string so we can readily test for particular sequences of events. --- indra/llcommon/CMakeLists.txt | 15 ++-- indra/llcommon/lleventfilter.cpp | 96 +++++++++++++++++---- indra/llcommon/lleventfilter.h | 49 +++++++++-- indra/llcommon/tests/listener.h | 11 +++ indra/llcommon/tests/lleventfilter_test.cpp | 124 +++++++++++++++++++++++++++- 5 files changed, 262 insertions(+), 33 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 3493f80556..aa76a57f1d 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -324,26 +324,27 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llerror "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(lleventfilter "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llframetimer "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lltrace "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llunits "" "${test_libs}") LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}") ## llexception_test.cpp isn't a regression test, and doesn't need to be run ## every build. It's to help a developer make implementation choices about diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp index 87eb583cb6..ddd7d5547a 100644 --- a/indra/llcommon/lleventfilter.cpp +++ b/indra/llcommon/lleventfilter.cpp @@ -38,12 +38,18 @@ #include "llerror.h" // LL_ERRS #include "llsdutil.h" // llsd_matches() +/***************************************************************************** +* LLEventFilter +*****************************************************************************/ LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak): LLEventStream(name, tweak), mSource(source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1))) { } +/***************************************************************************** +* LLEventMatching +*****************************************************************************/ LLEventMatching::LLEventMatching(const LLSD& pattern): LLEventFilter("matching"), mPattern(pattern) @@ -64,6 +70,9 @@ bool LLEventMatching::post(const LLSD& event) return LLEventStream::post(event); } +/***************************************************************************** +* LLEventTimeoutBase +*****************************************************************************/ LLEventTimeoutBase::LLEventTimeoutBase(): LLEventFilter("timeout") { @@ -153,6 +162,9 @@ bool LLEventTimeoutBase::running() const return mMainloop.connected(); } +/***************************************************************************** +* LLEventTimeout +*****************************************************************************/ LLEventTimeout::LLEventTimeout() {} LLEventTimeout::LLEventTimeout(LLEventPump& source): @@ -170,6 +182,9 @@ bool LLEventTimeout::countdownElapsed() const return mTimer.hasExpired(); } +/***************************************************************************** +* LLEventBatch +*****************************************************************************/ LLEventBatch::LLEventBatch(std::size_t size): LLEventFilter("batch"), mBatchSize(size) @@ -208,19 +223,22 @@ void LLEventBatch::setSize(std::size_t size) } } -LLEventThrottle::LLEventThrottle(F32 interval): +/***************************************************************************** +* LLEventThrottleBase +*****************************************************************************/ +LLEventThrottleBase::LLEventThrottleBase(F32 interval): LLEventFilter("throttle"), mInterval(interval), mPosts(0) {} -LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval): +LLEventThrottleBase::LLEventThrottleBase(LLEventPump& source, F32 interval): LLEventFilter(source, "throttle"), mInterval(interval), mPosts(0) {} -void LLEventThrottle::flush() +void LLEventThrottleBase::flush() { // flush() is a no-op unless there's something pending. // Don't test mPending because there's no requirement that the consumer @@ -228,11 +246,11 @@ void LLEventThrottle::flush() if (mPosts) { mPosts = 0; - mAlarm.cancel(); + alarmCancel(); // This is not to set our alarm; we are not yet requesting // any notification. This is just to track whether subsequent post() // calls fall within this mInterval or not. - mTimer.setTimerExpirySec(mInterval); + timerSet(mInterval); // copy and clear mPending BEFORE posting to avoid weird circularity // effects LLSD pending = mPending; @@ -241,12 +259,12 @@ void LLEventThrottle::flush() } } -LLSD LLEventThrottle::pending() const +LLSD LLEventThrottleBase::pending() const { return mPending; } -bool LLEventThrottle::post(const LLSD& event) +bool LLEventThrottleBase::post(const LLSD& event) { // Always capture most recent post() event data. If caller wants to // aggregate multiple events, let them retrieve pending() and modify @@ -257,7 +275,7 @@ bool LLEventThrottle::post(const LLSD& event) ++mPosts; // We reset mTimer on every flush() call to let us know if we're still // within the same mInterval. So -- are we? - F32 timeRemaining = mTimer.getRemainingTimeF32(); + F32 timeRemaining = timerGetRemaining(); if (! timeRemaining) { // more than enough time has elapsed, immediately flush() @@ -266,24 +284,24 @@ bool LLEventThrottle::post(const LLSD& event) else { // still within mInterval of the last flush() call: have to defer - if (! mAlarm.running()) + if (! alarmRunning()) { // timeRemaining tells us how much longer it will be until // mInterval seconds since the last flush() call. At that time, // flush() deferred events. - mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this)); + alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this)); } } return false; } -void LLEventThrottle::setInterval(F32 interval) +void LLEventThrottleBase::setInterval(F32 interval) { F32 oldInterval = mInterval; mInterval = interval; // If we are not now within oldInterval of the last flush(), we're done: // this will only affect behavior starting with the next flush(). - F32 timeRemaining = mTimer.getRemainingTimeF32(); + F32 timeRemaining = timerGetRemaining(); if (timeRemaining) { // We are currently within oldInterval of the last flush(). Figure out @@ -305,16 +323,60 @@ void LLEventThrottle::setInterval(F32 interval) else { // immediately reset mTimer - mTimer.setTimerExpirySec(timeRemaining); + timerSet(timeRemaining); // and if mAlarm is running, reset that too - if (mAlarm.running()) + if (alarmRunning()) { - mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this)); + alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this)); } } } } +F32 LLEventThrottleBase::getDelay() const +{ + return timerGetRemaining(); +} + +/***************************************************************************** +* LLEventThrottle implementation +*****************************************************************************/ +LLEventThrottle::LLEventThrottle(F32 interval): + LLEventThrottleBase(interval) +{} + +LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval): + LLEventThrottleBase(source, interval) +{} + +void LLEventThrottle::alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) +{ + mAlarm.actionAfter(interval, action); +} + +bool LLEventThrottle::alarmRunning() const +{ + return mAlarm.running(); +} + +void LLEventThrottle::alarmCancel() +{ + return mAlarm.cancel(); +} + +void LLEventThrottle::timerSet(F32 interval) +{ + mTimer.setTimerExpirySec(interval); +} + +F32 LLEventThrottle::timerGetRemaining() const +{ + return mTimer.getRemainingTimeF32(); +} + +/***************************************************************************** +* LLEventBatchThrottle +*****************************************************************************/ LLEventBatchThrottle::LLEventBatchThrottle(F32 interval): LLEventThrottle(interval) {} @@ -326,5 +388,7 @@ LLEventBatchThrottle::LLEventBatchThrottle(LLEventPump& source, F32 interval): bool LLEventBatchThrottle::post(const LLSD& event) { // simply retrieve pending value and append the new event to it - return LLEventThrottle::post(pending().append(event)); + LLSD partial = pending(); + partial.append(event); + return LLEventThrottle::post(partial); } diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index 68890846a7..cae18bfd86 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -247,7 +247,7 @@ private: }; /** - * LLEventThrottle: construct with a time interval. Regardless of how + * LLEventThrottleBase: construct with a time interval. Regardless of how * frequently you call post(), LLEventThrottle will pass on an event to * its listeners no more often than once per specified interval. * @@ -268,21 +268,24 @@ private: * 17: post(): immediately passed to listeners, next no sooner than 20 * * For a deferred event, the LLSD blob delivered to listeners is from the most - * recent deferred post() call. However, you may obtain the previous event - * blob by calling pending(), modify it however you want and post() the new - * value. Each time an event is delivered to listeners, the pending() value is - * reset to isUndefined(). + * recent deferred post() call. However, a sender may obtain the previous + * event blob by calling pending(), modifying it as desired and post()ing the + * new value. Each time an event is delivered to listeners, the pending() + * value is reset to isUndefined(). * * You may also call flush() to immediately pass along any deferred events to * all listeners. + * + * @NOTE This is an abstract base class so that, for testing, we can use an + * alternate "timer" that doesn't actually consume real time. */ -class LL_COMMON_API LLEventThrottle: public LLEventFilter +class LL_COMMON_API LLEventThrottleBase: public LLEventFilter { public: // pass time interval - LLEventThrottle(F32 interval); + LLEventThrottleBase(F32 interval); // construct and connect - LLEventThrottle(LLEventPump& source, F32 interval); + LLEventThrottleBase(LLEventPump& source, F32 interval); // force out any deferred events void flush(); @@ -301,7 +304,17 @@ public: std::size_t getPostCount() const { return mPosts; } // time until next event would be passed through, 0.0 if now - F32 getDelay() const { return mTimer.getRemainingTimeF32(); } + F32 getDelay() const; + +protected: + // Implement these time-related methods for a valid LLEventThrottleBase + // subclass (see LLEventThrottle). For testing, we use a subclass that + // doesn't involve actual elapsed time. + virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) = 0; + virtual bool alarmRunning() const = 0; + virtual void alarmCancel() = 0; + virtual void timerSet(F32 interval) = 0; + virtual F32 timerGetRemaining() const = 0; private: // remember throttle interval @@ -310,6 +323,24 @@ private: std::size_t mPosts; // pending event data from most recent deferred event LLSD mPending; +}; + +/** + * Production implementation of LLEventThrottle. + */ +class LLEventThrottle: public LLEventThrottleBase +{ +public: + LLEventThrottle(F32 interval); + LLEventThrottle(LLEventPump& source, F32 interval); + +private: + virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) override; + virtual bool alarmRunning() const override; + virtual void alarmCancel() override; + virtual void timerSet(F32 interval) override; + virtual F32 timerGetRemaining() const override; + // use this to arrange a deferred flush() call LLEventTimeout mAlarm; // use this to track whether we're within mInterval of last flush() diff --git a/indra/llcommon/tests/listener.h b/indra/llcommon/tests/listener.h index 9c5c18a150..6072060bb6 100644 --- a/indra/llcommon/tests/listener.h +++ b/indra/llcommon/tests/listener.h @@ -138,4 +138,15 @@ struct Collect StringVec result; }; +struct Concat +{ + bool operator()(const LLSD& event) + { + result += event.asString(); + return false; + } + void clear() { result.clear(); } + std::string result; +}; + #endif /* ! defined(LL_LISTENER_H) */ diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp index 2cdfb52f2f..712864bf63 100644 --- a/indra/llcommon/tests/lleventfilter_test.cpp +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -70,6 +70,85 @@ private: bool mElapsed; }; +// Similar remarks about LLEventThrottle: we're actually testing the logic in +// LLEventThrottleBase, dummying out the LLTimer and LLEventTimeout used by +// the production LLEventThrottle class. +class TestEventThrottle: public LLEventThrottleBase +{ +public: + TestEventThrottle(F32 interval): + LLEventThrottleBase(interval), + mAlarmRemaining(-1), + mTimerRemaining(-1) + {} + TestEventThrottle(LLEventPump& source, F32 interval): + LLEventThrottleBase(source, interval), + mAlarmRemaining(-1), + mTimerRemaining(-1) + {} + + /*----- implementation of LLEventThrottleBase timing functionality -----*/ + virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) override + { + mAlarmRemaining = interval; + mAlarmAction = action; + } + + virtual bool alarmRunning() const override + { + // decrementing to exactly 0 should mean the alarm fires + return mAlarmRemaining > 0; + } + + virtual void alarmCancel() override + { + mAlarmRemaining = -1; + } + + virtual void timerSet(F32 interval) override + { + mTimerRemaining = interval; + } + + virtual F32 timerGetRemaining() const override + { + // LLTimer.getRemainingTimeF32() never returns negative; 0.0 means expired + return (mTimerRemaining > 0.0)? mTimerRemaining : 0.0; + } + + /*------------------- methods for manipulating time --------------------*/ + void alarmAdvance(F32 delta) + { + bool wasRunning = alarmRunning(); + mAlarmRemaining -= delta; + if (wasRunning && ! alarmRunning()) + { + mAlarmAction(); + } + } + + void timerAdvance(F32 delta) + { + // This simple implementation, like alarmAdvance(), completely ignores + // HOW negative mTimerRemaining might go. All that matters is whether + // it's negative. We trust that no test method in this source will + // drive it beyond the capacity of an F32. Seems like a safe assumption. + mTimerRemaining -= delta; + } + + void advance(F32 delta) + { + // Advance the timer first because it has no side effects. + // alarmAdvance() might call flush(), which will need to see the + // change in the timer. + timerAdvance(delta); + alarmAdvance(delta); + } + + F32 mAlarmRemaining, mTimerRemaining; + LLEventTimeoutBase::Action mAlarmAction; +}; + /***************************************************************************** * TUT *****************************************************************************/ @@ -116,7 +195,9 @@ namespace tut listener0.listenTo(driver)); // Construct a pattern LLSD: desired Event must have a key "foo" // containing string "bar" - LLEventMatching filter(driver, LLSD().insert("foo", "bar")); + LLSD pattern; + pattern.insert("foo", "bar"); + LLEventMatching filter(driver, pattern); listener1.reset(0); LLTempBoundListener temp2( listener1.listenTo(filter)); @@ -285,6 +366,47 @@ namespace tut mainloop.post(17); check_listener("no timeout 3", listener0, LLSD(0)); } + + template<> template<> + void filter_object::test<5>() + { + set_test_name("LLEventThrottle"); + TestEventThrottle throttle(3); + Concat cat; + throttle.listen("concat", boost::ref(cat)); + + // (sequence taken from LLEventThrottleBase Doxygen comments) + // 1: post(): event immediately passed to listeners, next no sooner than 4 + throttle.advance(1); + throttle.post("1"); + ensure_equals("1", cat.result, "1"); // delivered immediately + // 2: post(): deferred: waiting for 3 seconds to elapse + throttle.advance(1); + throttle.post("2"); + ensure_equals("2", cat.result, "1"); // "2" not yet delivered + // 3: post(): deferred + throttle.advance(1); + throttle.post("3"); + ensure_equals("3", cat.result, "1"); // "3" not yet delivered + // 4: no post() call, but event delivered to listeners; next no sooner than 7 + throttle.advance(1); + ensure_equals("4", cat.result, "13"); // "3" delivered + // 6: post(): deferred + throttle.advance(2); + throttle.post("6"); + ensure_equals("6", cat.result, "13"); // "6" not yet delivered + // 7: no post() call, but event delivered; next no sooner than 10 + throttle.advance(1); + ensure_equals("7", cat.result, "136"); // "6" delivered + // 12: post(): immediately passed to listeners, next no sooner than 15 + throttle.advance(5); + throttle.post(";12"); + ensure_equals("12", cat.result, "136;12"); // "12" delivered + // 17: post(): immediately passed to listeners, next no sooner than 20 + throttle.advance(5); + throttle.post(";17"); + ensure_equals("17", cat.result, "136;12;17"); // "17" delivered + } } // namespace tut /***************************************************************************** -- cgit v1.2.3 From 4d87ded8862f032682a66704b9c68ef5626779ea Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 10 May 2017 17:37:06 -0400 Subject: Add size limit to LLEventBatchThrottle like LLEventBatch. The new behavior is that it will flush when either the pending batch has grown to the specified size, or the time interval has expired. --- indra/llcommon/lleventfilter.cpp | 35 ++++++++++++++++++++++++++--------- indra/llcommon/lleventfilter.h | 30 +++++++++++++++++++++--------- 2 files changed, 47 insertions(+), 18 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp index ddd7d5547a..9fb18dc67d 100644 --- a/indra/llcommon/lleventfilter.cpp +++ b/indra/llcommon/lleventfilter.cpp @@ -206,10 +206,8 @@ void LLEventBatch::flush() bool LLEventBatch::post(const LLSD& event) { mBatch.append(event); - if (mBatch.size() >= mBatchSize) - { - flush(); - } + // calling setSize(same) performs the very check we want + setSize(mBatchSize); return false; } @@ -377,12 +375,14 @@ F32 LLEventThrottle::timerGetRemaining() const /***************************************************************************** * LLEventBatchThrottle *****************************************************************************/ -LLEventBatchThrottle::LLEventBatchThrottle(F32 interval): - LLEventThrottle(interval) +LLEventBatchThrottle::LLEventBatchThrottle(F32 interval, std::size_t size): + LLEventThrottle(interval), + mBatchSize(size) {} -LLEventBatchThrottle::LLEventBatchThrottle(LLEventPump& source, F32 interval): - LLEventThrottle(source, interval) +LLEventBatchThrottle::LLEventBatchThrottle(LLEventPump& source, F32 interval, std::size_t size): + LLEventThrottle(source, interval), + mBatchSize(size) {} bool LLEventBatchThrottle::post(const LLSD& event) @@ -390,5 +390,22 @@ bool LLEventBatchThrottle::post(const LLSD& event) // simply retrieve pending value and append the new event to it LLSD partial = pending(); partial.append(event); - return LLEventThrottle::post(partial); + bool ret = LLEventThrottle::post(partial); + // The post() call above MIGHT have called flush() already. If it did, + // then pending() was reset to empty. If it did not, though, but the batch + // size has grown to the limit, flush() anyway. If there's a limit at all, + // of course. Calling setSize(same) performs the very check we want. + setSize(mBatchSize); + return ret; +} + +void LLEventBatchThrottle::setSize(std::size_t size) +{ + mBatchSize = size; + // Changing the size might mean that we have to flush NOW. Don't forget + // that 0 means unlimited. + if (mBatchSize && pending().size() >= mBatchSize) + { + flush(); + } } diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index cae18bfd86..ccbbc8bd25 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -270,14 +270,15 @@ private: * For a deferred event, the LLSD blob delivered to listeners is from the most * recent deferred post() call. However, a sender may obtain the previous * event blob by calling pending(), modifying it as desired and post()ing the - * new value. Each time an event is delivered to listeners, the pending() - * value is reset to isUndefined(). + * new value. (See LLEventBatchThrottle.) Each time an event is delivered to + * listeners, the pending() value is reset to isUndefined(). * * You may also call flush() to immediately pass along any deferred events to * all listeners. * * @NOTE This is an abstract base class so that, for testing, we can use an - * alternate "timer" that doesn't actually consume real time. + * alternate "timer" that doesn't actually consume real time. See + * LLEventThrottle. */ class LL_COMMON_API LLEventThrottleBase: public LLEventFilter { @@ -348,20 +349,31 @@ private: }; /** - * LLEventBatchThrottle: like LLEventThrottle, it refuses to pass events to - * listeners more often than once per specified time interval. - * Like LLEventBatch, it accumulates pending events into an LLSD Array. + * LLEventBatchThrottle: like LLEventThrottle, it's reluctant to pass events + * to listeners more often than once per specified time interval -- but only + * reluctant, since exceeding the specified batch size limit can cause it to + * deliver accumulated events sooner. Like LLEventBatch, it accumulates + * pending events into an LLSD Array, optionally flushing when the batch grows + * to a certain size. */ class LLEventBatchThrottle: public LLEventThrottle { public: - // pass time interval - LLEventBatchThrottle(F32 interval); + // pass time interval and (optionally) max batch size; 0 means batch can + // grow arbitrarily large + LLEventBatchThrottle(F32 interval, std::size_t size = 0); // construct and connect - LLEventBatchThrottle(LLEventPump& source, F32 interval); + LLEventBatchThrottle(LLEventPump& source, F32 interval, std::size_t size = 0); // append a new event to current batch virtual bool post(const LLSD& event); + + // query or reset batch size + std::size_t getSize() const { return mBatchSize; } + void setSize(std::size_t size); + +private: + std::size_t mBatchSize; }; #endif /* ! defined(LL_LLEVENTFILTER_H) */ -- cgit v1.2.3 From 4a90678cc7406b0c2c8dad5691cdc2555186931b Mon Sep 17 00:00:00 2001 From: AndreyL ProductEngine Date: Thu, 18 May 2017 03:13:57 +0300 Subject: Linux buildfix; this should be reverted after gcc update to 4.7+ --- indra/llcommon/lleventfilter.h | 10 +++++----- indra/llcommon/tests/lleventfilter_test.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index ccbbc8bd25..ff8fc9bc7f 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -336,11 +336,11 @@ public: LLEventThrottle(LLEventPump& source, F32 interval); private: - virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) override; - virtual bool alarmRunning() const override; - virtual void alarmCancel() override; - virtual void timerSet(F32 interval) override; - virtual F32 timerGetRemaining() const override; + virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) /*override*/; + virtual bool alarmRunning() const /*override*/; + virtual void alarmCancel() /*override*/; + virtual void timerSet(F32 interval) /*override*/; + virtual F32 timerGetRemaining() const /*override*/; // use this to arrange a deferred flush() call LLEventTimeout mAlarm; diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp index 712864bf63..eb98b12ef5 100644 --- a/indra/llcommon/tests/lleventfilter_test.cpp +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -88,29 +88,29 @@ public: {} /*----- implementation of LLEventThrottleBase timing functionality -----*/ - virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) override + virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) /*override*/ { mAlarmRemaining = interval; mAlarmAction = action; } - virtual bool alarmRunning() const override + virtual bool alarmRunning() const /*override*/ { // decrementing to exactly 0 should mean the alarm fires return mAlarmRemaining > 0; } - virtual void alarmCancel() override + virtual void alarmCancel() /*override*/ { mAlarmRemaining = -1; } - virtual void timerSet(F32 interval) override + virtual void timerSet(F32 interval) /*override*/ { mTimerRemaining = interval; } - virtual F32 timerGetRemaining() const override + virtual F32 timerGetRemaining() const /*override*/ { // LLTimer.getRemainingTimeF32() never returns negative; 0.0 means expired return (mTimerRemaining > 0.0)? mTimerRemaining : 0.0; -- cgit v1.2.3