From 95d8085fa42ca73773a06f2bd5622f69aef0e598 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 10 May 2021 15:21:51 -0400 Subject: SL-10297: Make LLSingletonBase::llerrs() et al. runtime variadic. Instead of accepting a fixed list of (const char* p1="", etc.), accept (std::initializer_list). Accepting a std::initializer_list in your parameter list allows coding (e.g.) func({T0, T1, T2, ... }); -- in other words, you can pass the initializer_list a brace-enclosed list of an arbitrary number of instances of T. Using std::string_view instead of const char* means we can pass *either* const char* or std::string. string_view is cheaply constructed from either, allowing uniform treatment within the function. Constructing string_view from std::string simply extracts the pointer and length from the std::string. Constructing string_view from const char* (e.g. a "string literal") requires scanning the string for its terminating nul byte -- but that would be necessary even if the scan were deferred until the function body. Since string_view stores the length, the scan still happens only once. --- indra/llcommon/llsingleton.cpp | 66 +++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 30 deletions(-) (limited to 'indra/llcommon/llsingleton.cpp') diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index ad933154c2..d0dcd463ff 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -39,8 +39,7 @@ #include namespace { -void log(LLError::ELevel level, - const char* p1, const char* p2, const char* p3, const char* p4); +void log(LLError::ELevel level, std::initializer_list); } // anonymous namespace // Our master list of all LLSingletons is itself an LLSingleton. We used to @@ -218,8 +217,8 @@ void LLSingletonBase::pop_initializing() if (list.empty()) { - logerrs("Underflow in stack of currently-initializing LLSingletons at ", - classname(this).c_str(), "::getInstance()"); + logerrs({"Underflow in stack of currently-initializing LLSingletons at ", + classname(this), "::getInstance()"}); } // Now we know list.back() exists: capture it @@ -240,9 +239,9 @@ void LLSingletonBase::pop_initializing() // Now validate the newly-popped LLSingleton. if (back != this) { - logerrs("Push/pop mismatch in stack of currently-initializing LLSingletons: ", - classname(this).c_str(), "::getInstance() trying to pop ", - classname(back).c_str()); + logerrs({"Push/pop mismatch in stack of currently-initializing LLSingletons: ", + classname(this), "::getInstance() trying to pop ", + classname(back)}); } // log AFTER popping so logging singletons don't cry circularity @@ -331,15 +330,15 @@ void LLSingletonBase::capture_dependency() // // Example: LLNotifications singleton initializes default channels. // Channels register themselves with singleton once done. - logdebugs("LLSingleton circularity: ", out.str().c_str(), - classname(this).c_str(), ""); + logdebugs({"LLSingleton circularity: ", out.str(), + classname(this)}); } else { // Actual circularity with other singleton (or single singleton is used extensively). // Dependency can be unclear. - logwarns("LLSingleton circularity: ", out.str().c_str(), - classname(this).c_str(), ""); + logwarns({"LLSingleton circularity: ", out.str(), + classname(this)}); } } else @@ -352,8 +351,8 @@ void LLSingletonBase::capture_dependency() if (current->mDepends.insert(this).second) { // only log the FIRST time we hit this dependency! - logdebugs(classname(current).c_str(), - " depends on ", classname(this).c_str()); + logdebugs({classname(current), + " depends on ", classname(this)}); } } } @@ -401,7 +400,7 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort() void LLSingletonBase::cleanup_() { - logdebugs("calling ", classname(this).c_str(), "::cleanupSingleton()"); + logdebugs({"calling ", classname(this), "::cleanupSingleton()"}); try { cleanupSingleton(); @@ -427,23 +426,23 @@ void LLSingletonBase::deleteAll() if (! sp->mDeleteSingleton) { // This Should Not Happen... but carry on. - logwarns(name.c_str(), "::mDeleteSingleton not initialized!"); + logwarns({name, "::mDeleteSingleton not initialized!"}); } else { // properly initialized: call it. - logdebugs("calling ", name.c_str(), "::deleteSingleton()"); + logdebugs({"calling ", name, "::deleteSingleton()"}); // From this point on, DO NOT DEREFERENCE sp! sp->mDeleteSingleton(); } } catch (const std::exception& e) { - logwarns("Exception in ", name.c_str(), "::deleteSingleton(): ", e.what()); + logwarns({"Exception in ", name, "::deleteSingleton(): ", e.what()}); } catch (...) { - logwarns("Unknown exception in ", name.c_str(), "::deleteSingleton()"); + logwarns({"Unknown exception in ", name, "::deleteSingleton()"}); } } } @@ -451,40 +450,47 @@ void LLSingletonBase::deleteAll() /*---------------------------- Logging helpers -----------------------------*/ namespace { -void log(LLError::ELevel level, - const char* p1, const char* p2, const char* p3, const char* p4) +void log(LLError::ELevel level, std::initializer_list args) { - LL_VLOGS(level, "LLSingleton") << p1 << p2 << p3 << p4 << LL_ENDL; + LL_VLOGS(level, "LLSingleton"); + for (auto arg : args) + { + LL_CONT << arg; + } + LL_ENDL; } } // anonymous namespace //static -void LLSingletonBase::logwarns(const char* p1, const char* p2, const char* p3, const char* p4) +void LLSingletonBase::logwarns(std::initializer_list args) { - log(LLError::LEVEL_WARN, p1, p2, p3, p4); + log(LLError::LEVEL_WARN, args); } //static -void LLSingletonBase::loginfos(const char* p1, const char* p2, const char* p3, const char* p4) +void LLSingletonBase::loginfos(std::initializer_list args) { - log(LLError::LEVEL_INFO, p1, p2, p3, p4); + log(LLError::LEVEL_INFO, args); } //static -void LLSingletonBase::logdebugs(const char* p1, const char* p2, const char* p3, const char* p4) +void LLSingletonBase::logdebugs(std::initializer_list args) { - log(LLError::LEVEL_DEBUG, p1, p2, p3, p4); + log(LLError::LEVEL_DEBUG, args); } //static -void LLSingletonBase::logerrs(const char* p1, const char* p2, const char* p3, const char* p4) +void LLSingletonBase::logerrs(std::initializer_list args) { - log(LLError::LEVEL_ERROR, p1, p2, p3, p4); + log(LLError::LEVEL_ERROR, args); // The other important side effect of LL_ERRS() is // https://www.youtube.com/watch?v=OMG7paGJqhQ (emphasis on OMG) std::ostringstream out; - out << p1 << p2 << p3 << p4; + for (auto arg : args) + { + out << arg; + } auto crash = LLError::getFatalFunction(); if (crash) { -- cgit v1.2.3 From c9fc4349b7d4ab1f5a7bfc0125014a96a07e51a3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 11 May 2021 21:42:14 -0400 Subject: SL-10297: Move LL_ERRS crash location into the LL_ERRS macro itself. Introduce Oz's LLERROR_CRASH macro analogous to the old LLError::crashAndLoop() function. Change LL_ENDL macro so that, after calling flush(), if the CallSite is for LEVEL_ERROR, we invoke LLERROR_CRASH right there. Change the meaning of LLError::FatalFunction. It used to be responsible for the actual crash (hence crashAndLoop()). Now, instead, its role is to disrupt control flow in some other way if you DON'T want to crash: throw an exception, or call exit() or some such. Any FatalFunction that returns normally will fall into the new crash in LL_ENDL. Accordingly, the new default FatalFunction is a no-op lambda. This eliminates the need to test for empty (not set) FatalFunction in Log::flush(). Remove LLError::crashAndLoop() because the official LL_ERRS crash is now in LL_ENDL. One of the two common use cases for setFatalFunction() used to be to intercept control in the last moments before crashing -- not to crash or to avoid crashing, but to capture the LL_ERRS message in some way. Especially when that's temporary, though (e.g. LLLeap), saving and restoring the previous FatalFunction only works when the lifespans of the relevant objects are strictly LIFO. Either way, that's a misuse of FatalFunction. Fortunately the Recorder mechanism exactly addresses that case. Introduce a GenericRecorder template subclass, with LLError::addGenericRecorder(callable) that accepts a callable with suitable (level, message) signature, instantiates a GenericRecorder, adds it to the logging machinery and returns the RecorderPtr for possible later use with removeRecorder(). Change llappviewer.cpp's errorCallback() to an addGenericRecorder() callable. Its role was simply to update gDebugInfo["FatalMessage"] with the LL_ERRS message, then call writeDebugInfo(), before calling crashAndLoop() to finish crashing. Remove the crashAndLoop() call, retaining the gDebugInfo logic. Pass errorCallback() to LLError::addGenericRecorder() instead of setFatalFunction(). Oddly, errorCallback()'s crashAndLoop() call was conditional on a compile-time SHADER_CRASH_NONFATAL symbol. The new mechanism provides no way to support SHADER_CRASH_NONFATAL -- it is a Bad Idea to return normally from any LL_ERRS invocation! Rename LLLeapImpl::fatalFunction() to onError(). Instead of passing it to LLError::setFatalFunction(), pass it to addGenericRecorder(). Capture the returned RecorderPtr in mRecorder, replacing mPrevFatalFunction. Then ~LLLeapImpl() calls removeRecorder(mRecorder) instead of restoring mPrevFatalFunction (which, as noted above, was order-sensitive). Of course, every enabled Recorder is called with every log message. onError() and errorCallback() must specifically test for calls with LEVEL_ERROR. LLSingletonBase::logerrs() used to call LLError::getFatalFunction(), check the return and call it if non-empty, else call LLError::crashAndLoop(). Replace all that with LLERROR_CRASH. Remove from llappviewer.cpp the watchdog_llerrs_callback() and watchdog_killer_callback() functions. watchdog_killer_callback(), passed to Watchdog::init(), used to setFatalFunction(watchdog_llerrs_callback) and then invoke LL_ERRS() -- which seems a bit roundabout. watchdog_llerrs_callback(), in turn, replicated much of the logic in the primary errorCallback() function before replicating the crash from llwatchdog.cpp's default_killer_callback(). Instead, pass LLWatchdog::init() a lambda that invokes the LL_ERRS() message formerly found in watchdog_killer_callback(). It no longer needs to override FatalFunction with watchdog_llerrs_callback() because errorCallback() will still be called as a Recorder, obviating watchdog_llerrs_callback()'s first half; and LL_ENDL will handle the crash, obviating the second half. Remove from llappviewer.cpp the static fast_exit() function, which was simply an alias for _exit() acceptable to boost::bind(). Use a lambda directly calling _exit() instead of using boost::bind() at all. In the CaptureLog class in llcommon/tests/wrapllerrs.h, instead of statically referencing the wouldHaveCrashed() function from test.cpp, simply save and restore the current FatalFunction across the LLError::saveAndResetSettings() call. llerror_test.cpp calls setFatalFunction(fatalCall), where fatalCall() was a function that simply set a fatalWasCalled bool rather than actually crashing in any way. Of course, that implementation would now lead to crashing the test program. Make fatalCall() throw a new FatalWasCalled exception. Introduce a CATCH(LL_ERRS("tag"), "message") macro that expands to: LL_ERRS("tag") << "message" << LL_ENDL; within a try/catch block that catches FatalWasCalled and sets the same bool. Change all existing LL_ERRS() in llerror_test.cpp to corresponding CATCH() calls. In fact there's also an LL_DEBUGS(bad tag) invocation that exercises an LL_ERRS internal to llerror.cpp; wrap that too. --- indra/llcommon/llsingleton.cpp | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) (limited to 'indra/llcommon/llsingleton.cpp') diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index d0dcd463ff..4b1666563e 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -486,20 +486,7 @@ void LLSingletonBase::logerrs(std::initializer_list args) log(LLError::LEVEL_ERROR, args); // The other important side effect of LL_ERRS() is // https://www.youtube.com/watch?v=OMG7paGJqhQ (emphasis on OMG) - std::ostringstream out; - for (auto arg : args) - { - out << arg; - } - auto crash = LLError::getFatalFunction(); - if (crash) - { - crash(out.str()); - } - else - { - LLError::crashAndLoop(out.str()); - } + LLERROR_CRASH; } std::string LLSingletonBase::demangle(const char* mangled) -- cgit v1.2.3 From 147c66d67ce83778fc3f102dd132ec095e6032fc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 12 May 2021 09:46:49 -0400 Subject: SL-10297: Simplify implementation of LLSingletonBase::logwarns() etc. Introduce 'string_params' typedef for std::initialization_list, and make logwarns(), loginfos(), logdebugs() and logerrs() accept const string_params&. Eliminate the central log() function in llsingleton.cpp that used LL_VLOGS(). To cache the result of a (moderately expensive) Log::shouldLog() call, LL_VLOGS() wants its CallSite object to be static -- but of course the shouldLog() result will differ for different ELevel values, so LL_VLOGS() instantiates a static array of CallSite instances. It seems silly to funnel distinct logwarns(), etc., functions through a common log() function only to have LL_VLOGS() tease apart ELevel values again. Instead, make logwarns() directly invoke LL_WARNS(), and similarly for the rest. To reduce boilerplate in these distinct functions, teach std::ostream how to stream a string_params instance by looping over its elements. Then each logwarns(), etc., function can simply stream its string_params argument to LL_WARNS() or whichever. In particular, eliminate the LLERROR_CRASH macro in logerrs(). The fact that it invokes LL_ERRS() ensures that its LL_ENDL macro will crash the viewer. --- indra/llcommon/llsingleton.cpp | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) (limited to 'indra/llcommon/llsingleton.cpp') diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index 4b1666563e..6b1986d0e9 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -38,10 +38,6 @@ #include #include -namespace { -void log(LLError::ELevel level, std::initializer_list); -} // anonymous namespace - // Our master list of all LLSingletons is itself an LLSingleton. We used to // store it in a function-local static, but that could get destroyed before // the last of the LLSingletons -- and ~LLSingletonBase() definitely wants to @@ -450,43 +446,40 @@ void LLSingletonBase::deleteAll() /*---------------------------- Logging helpers -----------------------------*/ namespace { -void log(LLError::ELevel level, std::initializer_list args) +std::ostream& operator<<(std::ostream& out, const LLSingletonBase::string_params& args) { - LL_VLOGS(level, "LLSingleton"); - for (auto arg : args) - { - LL_CONT << arg; - } - LL_ENDL; + // However many args there are in args, stream each of them to 'out'. + for (auto arg : args) + { + out << arg; + } + return out; } } // anonymous namespace //static -void LLSingletonBase::logwarns(std::initializer_list args) +void LLSingletonBase::logwarns(const string_params& args) { - log(LLError::LEVEL_WARN, args); + LL_WARNS("LLSingleton") << args << LL_ENDL; } //static -void LLSingletonBase::loginfos(std::initializer_list args) +void LLSingletonBase::loginfos(const string_params& args) { - log(LLError::LEVEL_INFO, args); + LL_INFOS("LLSingleton") << args << LL_ENDL; } //static -void LLSingletonBase::logdebugs(std::initializer_list args) +void LLSingletonBase::logdebugs(const string_params& args) { - log(LLError::LEVEL_DEBUG, args); + LL_DEBUGS("LLSingleton") << args << LL_ENDL; } //static -void LLSingletonBase::logerrs(std::initializer_list args) +void LLSingletonBase::logerrs(const string_params& args) { - log(LLError::LEVEL_ERROR, args); - // The other important side effect of LL_ERRS() is - // https://www.youtube.com/watch?v=OMG7paGJqhQ (emphasis on OMG) - LLERROR_CRASH; + LL_ERRS("LLSingleton") << args << LL_ENDL; } std::string LLSingletonBase::demangle(const char* mangled) -- cgit v1.2.3