From a596c1ad2864d639bbab13e7f13fd9f029cf5d93 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Aug 2024 15:58:03 -0400 Subject: Add Throttle and LogThrottle classes to manage throttled APIs. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/throttle.cpp | 34 +++++++++ indra/llcommon/throttle.h | 137 ++++++++++++++++++++++++++++++++++++ indra/newview/groupchatlistener.cpp | 19 +++-- indra/newview/groupchatlistener.h | 4 +- 5 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 indra/llcommon/throttle.cpp create mode 100644 indra/llcommon/throttle.h diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 20670d7ebe..4c4a676531 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -112,6 +112,7 @@ set(llcommon_SOURCE_FILES lua_function.cpp lualistener.cpp threadpool.cpp + throttle.cpp u64.cpp workqueue.cpp StackWalker.cpp @@ -263,6 +264,7 @@ set(llcommon_HEADER_FILES threadpool.h threadpool_fwd.h threadsafeschedule.h + throttle.h timer.h tuple.h u64.h diff --git a/indra/llcommon/throttle.cpp b/indra/llcommon/throttle.cpp new file mode 100644 index 0000000000..d5f7d5dcab --- /dev/null +++ b/indra/llcommon/throttle.cpp @@ -0,0 +1,34 @@ +/** + * @file throttle.cpp + * @author Nat Goodspeed + * @date 2024-08-12 + * @brief Implementation for ThrottleBase. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "throttle.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "lltimer.h" + +bool ThrottleBase::too_fast() +{ + F64 now = LLTimer::getElapsedSeconds(); + if (now < mNext) + { + return true; + } + else + { + mNext = now + mInterval; + return false; + } +} diff --git a/indra/llcommon/throttle.h b/indra/llcommon/throttle.h new file mode 100644 index 0000000000..abf595e3c4 --- /dev/null +++ b/indra/llcommon/throttle.h @@ -0,0 +1,137 @@ +/** + * @file throttle.h + * @author Nat Goodspeed + * @date 2024-08-12 + * @brief Throttle class + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_THROTTLE_H) +#define LL_THROTTLE_H + +#include "apply.h" // LL::bind_front() +#include "llerror.h" +#include +#include // std::quoted() + +class ThrottleBase +{ +public: + ThrottleBase(F64 interval): + mInterval(interval) + {} + +protected: + bool too_fast(); // not const: we update mNext + F64 mInterval, mNext{ 0. }; +}; + +/** + * An instance of Throttle mediates calls to some other specified function, + * ensuring that it's called no more often than the specified time interval. + * Throttle is an abstract base class that delegates the behavior when the + * specified interval is exceeded. + */ +template +class Throttle: public ThrottleBase +{ +public: + Throttle(const std::string& desc, + const std::function& func, + F64 interval): + ThrottleBase(interval), + mDesc(desc), + mFunc(func) + {} + // Constructing Throttle with a member function pointer but without an + // instance pointer requires you to pass the instance pointer/reference as + // the first argument to operator()(). + template + Throttle(const std::string& desc, R C::* method, F64 interval): + Throttle(desc, std::mem_fn(method), interval) + {} + template + Throttle(const std::string& desc, R C::* method, C* instance, F64 interval): + Throttle(desc, LL::bind_front(method, instance), interval) + {} + template + Throttle(const std::string& desc, R C::* method, const C* instance, F64 interval): + Throttle(desc, LL::bind_front(method, instance), interval) + {} + + template + auto operator()(ARGS... args) + { + if (too_fast()) + { + suppress(); + using rtype = decltype(mFunc(std::forward(args)...)); + if constexpr (! std::is_same_v) + { + return rtype{}; + } + } + else + { + return mFunc(std::forward(args)...); + } + } + +protected: + // override with desired behavior when calls come too often + virtual void suppress() = 0; + const std::string mDesc; + +private: + std::function mFunc; +}; + +/** + * An instance of LogThrottle mediates calls to some other specified function, + * ensuring that it's called no more often than the specified time interval. + * When that interval is exceeded, it logs a message at the specified log + * level. It uses LL_MUMBLES_ONCE() logic to prevent spamming, since a too- + * frequent call may well be spammy. + */ +template +class LogThrottle: public Throttle +{ + using super = Throttle; +public: + LogThrottle(const std::string& desc, + const std::function& func, + F64 interval): + super(desc, func, interval) + {} + template + LogThrottle(const std::string& desc, R C::* method, F64 interval): + super(desc, method, interval) + {} + template + LogThrottle(const std::string& desc, R C::* method, C* instance, F64 interval): + super(desc, method, instance, interval) + {} + template + LogThrottle(const std::string& desc, R C::* method, const C* instance, F64 interval): + super(desc, method, instance, interval) + {} + +private: + void suppress() override + { + // Using lllog(), the macro underlying LL_WARNS() et al., allows + // specifying compile-time LOGLEVEL. It does NOT support a variable + // LOGLEVEL, which is why LOGLEVEL is a non-type template parameter. + // See llvlog() for variable support, which is a bit more expensive. + // true = only print the log message once + lllog(LOGLEVEL, true, "LogThrottle") << std::quoted(super::mDesc) + << " called more than once per " + << super::mInterval + << LL_ENDL; + } +}; + +#endif /* ! defined(LL_THROTTLE_H) */ diff --git a/indra/newview/groupchatlistener.cpp b/indra/newview/groupchatlistener.cpp index 09951ba1cc..298f41ff8c 100644 --- a/indra/newview/groupchatlistener.cpp +++ b/indra/newview/groupchatlistener.cpp @@ -43,7 +43,8 @@ static const F32 GROUP_CHAT_THROTTLE_PERIOD = 1.f; LLGroupChatListener::LLGroupChatListener(): LLEventAPI("GroupChat", "API to enter, leave, send and intercept group chat messages"), - mLastThrottleTime(0) + mIMThrottle("sendGroupIM", &LLGroupChatListener::sendGroupIM_, this, + GROUP_CHAT_THROTTLE_PERIOD) { add("startGroupChat", "Enter a group chat in group with UUID [\"group_id\"]\n" @@ -101,18 +102,14 @@ void LLGroupChatListener::sendGroupIM(LLSD const &data) return; } - F64 cur_time = LLTimer::getElapsedSeconds(); - - if (cur_time < mLastThrottleTime + GROUP_CHAT_THROTTLE_PERIOD) - { - LL_DEBUGS("LLGroupChatListener") << "'sendGroupIM' was throttled" << LL_ENDL; - return; - } - mLastThrottleTime = cur_time; + mIMThrottle(data["group_id"], data["message"]); +} - LLUUID group_id(data["group_id"]); - LLIMModel::sendMessage(LUA_PREFIX + data["message"].asString(), +void LLGroupChatListener::sendGroupIM_(const LLUUID& group_id, const std::string& message) +{ + LLIMModel::sendMessage(LUA_PREFIX + message, gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id), group_id, IM_SESSION_SEND); } + diff --git a/indra/newview/groupchatlistener.h b/indra/newview/groupchatlistener.h index 35afc5766c..a75fecb254 100644 --- a/indra/newview/groupchatlistener.h +++ b/indra/newview/groupchatlistener.h @@ -30,6 +30,7 @@ #define LL_LLGROUPCHATLISTENER_H #include "lleventapi.h" +#include "throttle.h" class LLGroupChatListener: public LLEventAPI { @@ -40,8 +41,9 @@ private: void startGroupChat(LLSD const &data); void leaveGroupChat(LLSD const &data); void sendGroupIM(LLSD const &data); + void sendGroupIM_(const LLUUID& group_id, const std::string& message); - F64 mLastThrottleTime {0.0}; + LogThrottle mIMThrottle; }; #endif /* ! defined(LL_LLGROUPCHATLISTENER_H) */ -- cgit v1.2.3 From 153b0573cd91361a0f674c0ce1b9d7cb47e2bbc0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Aug 2024 16:28:44 -0400 Subject: Add virtual destructor to Throttle class. --- indra/llcommon/throttle.h | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/llcommon/throttle.h b/indra/llcommon/throttle.h index abf595e3c4..481d1d8f72 100644 --- a/indra/llcommon/throttle.h +++ b/indra/llcommon/throttle.h @@ -61,6 +61,7 @@ public: Throttle(const std::string& desc, R C::* method, const C* instance, F64 interval): Throttle(desc, LL::bind_front(method, instance), interval) {} + virtual ~Throttle() {} template auto operator()(ARGS... args) -- cgit v1.2.3