diff options
Diffstat (limited to 'indra/llcommon')
| -rw-r--r-- | indra/llcommon/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | indra/llcommon/llapp.cpp | 1 | ||||
| -rw-r--r-- | indra/llcommon/llapp.h | 2 | ||||
| -rwxr-xr-x | indra/llcommon/llpointer.cpp | 43 | ||||
| -rw-r--r-- | indra/llcommon/llpointer.h | 291 | ||||
| -rw-r--r-- | indra/llcommon/llprocess.cpp | 13 | ||||
| -rw-r--r-- | indra/llcommon/llthread.cpp | 6 | ||||
| -rw-r--r-- | indra/llcommon/llwatchdog.cpp | 291 | ||||
| -rw-r--r-- | indra/llcommon/llwatchdog.h | 118 | ||||
| -rw-r--r-- | indra/llcommon/workqueue.cpp | 29 | ||||
| -rw-r--r-- | indra/llcommon/workqueue.h | 6 |
11 files changed, 570 insertions, 233 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 2de9deea70..32226a62e6 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -68,6 +68,7 @@ set(llcommon_SOURCE_FILES llmetricperformancetester.cpp llmortician.cpp llmutex.cpp + llpointer.cpp llptrto.cpp llpredicate.cpp llprocess.cpp @@ -101,6 +102,7 @@ set(llcommon_SOURCE_FILES lluri.cpp lluriparser.cpp lluuid.cpp + llwatchdog.cpp llworkerthread.cpp hbxxh.cpp u64.cpp @@ -240,6 +242,7 @@ set(llcommon_HEADER_FILES lluri.h lluriparser.h lluuid.h + llwatchdog.h llwin32headers.h llworkerthread.h hbxxh.h diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index c532620daa..f92bb98ba6 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -93,6 +93,7 @@ bool LLApp::sDisableCrashlogger = false; LLScalarCond<LLApp::EAppStatus> LLApp::sStatus{LLApp::APP_STATUS_STOPPED}; LLAppErrorHandler LLApp::sErrorHandler = NULL; +bool gDisconnected = false; LLApp::LLApp() { diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index 57f5a112d9..ce09c566a9 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -50,6 +50,8 @@ void clear_signals(); #endif +extern bool gDisconnected; + class LL_COMMON_API LLApp { public: diff --git a/indra/llcommon/llpointer.cpp b/indra/llcommon/llpointer.cpp new file mode 100755 index 0000000000..1bb7055b3a --- /dev/null +++ b/indra/llcommon/llpointer.cpp @@ -0,0 +1,43 @@ +/** + * @file llpointer.cpp + * @author Nat Goodspeed + * @date 2024-09-26 + * @brief Implementation for llpointer. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llpointer.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llerror.h" + +void LLPointerBase::wild_dtor(std::string_view msg) +{ +// LL_WARNS() << msg << LL_ENDL; + llassert_msg(false, msg); +} diff --git a/indra/llcommon/llpointer.h b/indra/llcommon/llpointer.h index 048547e4cc..d2dcf530e5 100644 --- a/indra/llcommon/llpointer.h +++ b/indra/llcommon/llpointer.h @@ -26,8 +26,9 @@ #ifndef LLPOINTER_H #define LLPOINTER_H -#include "llerror.h" // *TODO: consider eliminating this -#include "llmutex.h" +#include <functional> +#include <string_view> +#include <utility> // std::swap() //---------------------------------------------------------------------------- // RefCount objects should generally only be accessed by way of LLPointer<>'s @@ -42,8 +43,18 @@ //---------------------------------------------------------------------------- +class LLPointerBase +{ +protected: + // alert the coder that a referenced type's destructor did something very + // strange -- this is in a non-template base class so we can hide the + // implementation in llpointer.cpp + static void wild_dtor(std::string_view msg); +}; + // Note: relies on Type having ref() and unref() methods -template <class Type> class LLPointer +template <class Type> +class LLPointer: public LLPointerBase { public: template<typename Subclass> @@ -60,6 +71,13 @@ public: ref(); } + // Even though the template constructors below accepting + // (const LLPointer<Subclass>&) and (LLPointer<Subclass>&&) appear to + // subsume these specific (const LLPointer<Type>&) and (LLPointer<Type>&&) + // constructors, the compiler recognizes these as The Copy Constructor and + // The Move Constructor, respectively. In other words, even in the + // presence of the LLPointer<Subclass> constructors, we still must specify + // the LLPointer<Type> constructors. LLPointer(const LLPointer<Type>& ptr) : mPointer(ptr.mPointer) { @@ -98,39 +116,52 @@ public: const Type& operator*() const { return *mPointer; } Type& operator*() { return *mPointer; } - operator BOOL() const { return (mPointer != nullptr); } operator bool() const { return (mPointer != nullptr); } bool operator!() const { return (mPointer == nullptr); } bool isNull() const { return (mPointer == nullptr); } bool notNull() const { return (mPointer != nullptr); } operator Type*() const { return mPointer; } - bool operator !=(Type* ptr) const { return (mPointer != ptr); } - bool operator ==(Type* ptr) const { return (mPointer == ptr); } - bool operator ==(const LLPointer<Type>& ptr) const { return (mPointer == ptr.mPointer); } - bool operator < (const LLPointer<Type>& ptr) const { return (mPointer < ptr.mPointer); } - bool operator > (const LLPointer<Type>& ptr) const { return (mPointer > ptr.mPointer); } + template <typename Type1> + bool operator !=(Type1* ptr) const { return (mPointer != ptr); } + template <typename Type1> + bool operator ==(Type1* ptr) const { return (mPointer == ptr); } + template <typename Type1> + bool operator !=(const LLPointer<Type1>& ptr) const { return (mPointer != ptr.mPointer); } + template <typename Type1> + bool operator ==(const LLPointer<Type1>& ptr) const { return (mPointer == ptr.mPointer); } + bool operator < (const LLPointer<Type>& ptr) const { return (mPointer < ptr.mPointer); } + bool operator > (const LLPointer<Type>& ptr) const { return (mPointer > ptr.mPointer); } LLPointer<Type>& operator =(Type* ptr) { - assign(ptr); + // copy-and-swap idiom, see http://gotw.ca/gotw/059.htm + LLPointer temp(ptr); + using std::swap; // per Swappable convention + swap(*this, temp); return *this; } + // Even though the template assignment operators below accepting + // (const LLPointer<Subclass>&) and (LLPointer<Subclass>&&) appear to + // subsume these specific (const LLPointer<Type>&) and (LLPointer<Type>&&) + // assignment operators, the compiler recognizes these as Copy Assignment + // and Move Assignment, respectively. In other words, even in the presence + // of the LLPointer<Subclass> assignment operators, we still must specify + // the LLPointer<Type> operators. LLPointer<Type>& operator =(const LLPointer<Type>& ptr) { - assign(ptr); + LLPointer temp(ptr); + using std::swap; // per Swappable convention + swap(*this, temp); return *this; } LLPointer<Type>& operator =(LLPointer<Type>&& ptr) { - if (mPointer != ptr.mPointer) - { - unref(); - mPointer = ptr.mPointer; - ptr.mPointer = nullptr; - } + LLPointer temp(std::move(ptr)); + using std::swap; // per Swappable convention + swap(*this, temp); return *this; } @@ -138,28 +169,32 @@ public: template<typename Subclass> LLPointer<Type>& operator =(const LLPointer<Subclass>& ptr) { - assign(ptr.get()); + LLPointer temp(ptr); + using std::swap; // per Swappable convention + swap(*this, temp); return *this; } template<typename Subclass> LLPointer<Type>& operator =(LLPointer<Subclass>&& ptr) { - if (mPointer != ptr.mPointer) - { - unref(); - mPointer = ptr.mPointer; - ptr.mPointer = nullptr; - } + LLPointer temp(std::move(ptr)); + using std::swap; // per Swappable convention + swap(*this, temp); return *this; } // Just exchange the pointers, which will not change the reference counts. static void swap(LLPointer<Type>& a, LLPointer<Type>& b) { - Type* temp = a.mPointer; - a.mPointer = b.mPointer; - b.mPointer = temp; + using std::swap; // per Swappable convention + swap(a.mPointer, b.mPointer); + } + + // Put swap() overload in the global namespace, per Swappable convention + friend void swap(LLPointer<Type>& a, LLPointer<Type>& b) + { + LLPointer<Type>::swap(a, b); } protected: @@ -184,191 +219,19 @@ protected: temp->unref(); if (mPointer != nullptr) { - LL_WARNS() << "Unreference did assignment to non-NULL because of destructor" << LL_ENDL; + wild_dtor("Unreference did assignment to non-NULL because of destructor"); unref(); } } } #endif // LL_LIBRARY_INCLUDE - void assign(const LLPointer<Type>& ptr) - { - if (mPointer != ptr.mPointer) - { - unref(); - mPointer = ptr.mPointer; - ref(); - } - } - protected: Type* mPointer; }; -template <class Type> class LLConstPointer -{ - template<typename Subclass> - friend class LLConstPointer; -public: - LLConstPointer() : - mPointer(nullptr) - { - } - - LLConstPointer(const Type* ptr) : - mPointer(ptr) - { - ref(); - } - - LLConstPointer(const LLConstPointer<Type>& ptr) : - mPointer(ptr.mPointer) - { - ref(); - } - - LLConstPointer(LLConstPointer<Type>&& ptr) noexcept - { - mPointer = ptr.mPointer; - ptr.mPointer = nullptr; - } - - // support conversion up the type hierarchy. See Item 45 in Effective C++, 3rd Ed. - template<typename Subclass> - LLConstPointer(const LLConstPointer<Subclass>& ptr) : - mPointer(ptr.get()) - { - ref(); - } - - template<typename Subclass> - LLConstPointer(LLConstPointer<Subclass>&& ptr) noexcept : - mPointer(ptr.get()) - { - ptr.mPointer = nullptr; - } - - ~LLConstPointer() - { - unref(); - } - - const Type* get() const { return mPointer; } - const Type* operator->() const { return mPointer; } - const Type& operator*() const { return *mPointer; } - - operator BOOL() const { return (mPointer != nullptr); } - operator bool() const { return (mPointer != nullptr); } - bool operator!() const { return (mPointer == nullptr); } - bool isNull() const { return (mPointer == nullptr); } - bool notNull() const { return (mPointer != nullptr); } - - operator const Type*() const { return mPointer; } - bool operator !=(const Type* ptr) const { return (mPointer != ptr); } - bool operator ==(const Type* ptr) const { return (mPointer == ptr); } - bool operator ==(const LLConstPointer<Type>& ptr) const { return (mPointer == ptr.mPointer); } - bool operator < (const LLConstPointer<Type>& ptr) const { return (mPointer < ptr.mPointer); } - bool operator > (const LLConstPointer<Type>& ptr) const { return (mPointer > ptr.mPointer); } - - LLConstPointer<Type>& operator =(const Type* ptr) - { - if( mPointer != ptr ) - { - unref(); - mPointer = ptr; - ref(); - } - - return *this; - } - - LLConstPointer<Type>& operator =(const LLConstPointer<Type>& ptr) - { - if( mPointer != ptr.mPointer ) - { - unref(); - mPointer = ptr.mPointer; - ref(); - } - return *this; - } - - LLConstPointer<Type>& operator =(LLConstPointer<Type>&& ptr) - { - if (mPointer != ptr.mPointer) - { - unref(); - mPointer = ptr.mPointer; - ptr.mPointer = nullptr; - } - return *this; - } - - // support assignment up the type hierarchy. See Item 45 in Effective C++, 3rd Ed. - template<typename Subclass> - LLConstPointer<Type>& operator =(const LLConstPointer<Subclass>& ptr) - { - if( mPointer != ptr.get() ) - { - unref(); - mPointer = ptr.get(); - ref(); - } - return *this; - } - - template<typename Subclass> - LLConstPointer<Type>& operator =(LLConstPointer<Subclass>&& ptr) - { - if (mPointer != ptr.mPointer) - { - unref(); - mPointer = ptr.mPointer; - ptr.mPointer = nullptr; - } - return *this; - } - - // Just exchange the pointers, which will not change the reference counts. - static void swap(LLConstPointer<Type>& a, LLConstPointer<Type>& b) - { - const Type* temp = a.mPointer; - a.mPointer = b.mPointer; - b.mPointer = temp; - } - -protected: -#ifdef LL_LIBRARY_INCLUDE - void ref(); - void unref(); -#else // LL_LIBRARY_INCLUDE - void ref() - { - if (mPointer) - { - mPointer->ref(); - } - } - - void unref() - { - if (mPointer) - { - const Type *temp = mPointer; - mPointer = nullptr; - temp->unref(); - if (mPointer != nullptr) - { - LL_WARNS() << "Unreference did assignment to non-NULL because of destructor" << LL_ENDL; - unref(); - } - } - } -#endif // LL_LIBRARY_INCLUDE - -protected: - const Type* mPointer; -}; +template <typename Type> +using LLConstPointer = LLPointer<const Type>; template<typename Type> class LLCopyOnWritePointer : public LLPointer<Type> @@ -418,38 +281,26 @@ private: bool mStayUnique; }; -template<typename Type> -bool operator!=(Type* lhs, const LLPointer<Type>& rhs) +template<typename Type0, typename Type1> +bool operator!=(Type0* lhs, const LLPointer<Type1>& rhs) { return (lhs != rhs.get()); } -template<typename Type> -bool operator==(Type* lhs, const LLPointer<Type>& rhs) +template<typename Type0, typename Type1> +bool operator==(Type0* lhs, const LLPointer<Type1>& rhs) { return (lhs == rhs.get()); } -// boost hash adapter -template <class Type> -struct boost::hash<LLPointer<Type>> -{ - typedef LLPointer<Type> argument_type; - typedef std::size_t result_type; - result_type operator()(argument_type const& s) const - { - return (std::size_t) s.get(); - } -}; - -// Adapt boost hash to std hash +// Specialize for std::hash namespace std { template<class Type> struct hash<LLPointer<Type>> { std::size_t operator()(LLPointer<Type> const& s) const noexcept { - return boost::hash<LLPointer<Type>>()(s); + return std::hash<Type*>()(s.get()); } }; } diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 670b740133..4a01ec567e 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -176,13 +176,13 @@ public: // In general, our streambuf might contain a number of different // physical buffers; iterate over those. bool keepwriting = true; - for (const_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); + for (auto bufi(boost::asio::buffer_sequence_begin(bufs)), bufend(boost::asio::buffer_sequence_end(bufs)); bufi != bufend && keepwriting; ++bufi) { // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents // Although apr_file_write() accepts const void*, we // manipulate const char* so we can increment the pointer. - const char* remainptr = boost::asio::buffer_cast<const char*>(*bufi); + const char* remainptr = static_cast<const char*>(bufi->data()); std::size_t remainlen = boost::asio::buffer_size(*bufi); while (remainlen) { @@ -377,14 +377,14 @@ public: // In general, the mutable_buffer_sequence returned by prepare() might // contain a number of different physical buffers; iterate over those. std::size_t tocommit(0); - for (mutable_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); + for (auto bufi(boost::asio::buffer_sequence_begin(bufs)), bufend(boost::asio::buffer_sequence_end(bufs)); bufi != bufend; ++bufi) { // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents std::size_t toread(boost::asio::buffer_size(*bufi)); apr_size_t gotten(toread); apr_status_t err = apr_file_read(mPipe, - boost::asio::buffer_cast<void*>(*bufi), + bufi->data(), &gotten); // EAGAIN is exactly what we want from a nonblocking pipe. // Rather than waiting for data, it should return immediately. @@ -657,10 +657,7 @@ LLProcess::LLProcess(const LLSDOrParams& params): // case), e.g. by calling operator(), returns a reference to *the same // instance* of the wrapped type that's stored in our Block subclass. // That's important! We know 'params' persists throughout this method - // call; but without that guarantee, we'd have to assume that converting - // one of its members to std::string might return a different (temp) - // instance. Capturing the c_str() from a temporary std::string is Bad Bad - // Bad. But armed with this knowledge, when you see params.cwd().c_str(), + // call; but without that guarantee, when you see params.cwd().c_str(), // grit your teeth and smile and carry on. if (params.cwd.isProvided()) diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 692941a892..8c12ee7f12 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -240,7 +240,11 @@ void LLThread::tryRun() LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); main_queue->post( // Bind the current exception, rethrow it in main loop. - [exc = std::current_exception()]() { std::rethrow_exception(exc); }); + [exc = std::current_exception(), name = mName]() + { + LL_INFOS("THREAD") << "Rethrowing exception from thread " << name << LL_ENDL; + std::rethrow_exception(exc); + }); } #endif // else LL_WINDOWS } diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp new file mode 100644 index 0000000000..1bc1283d0b --- /dev/null +++ b/indra/llcommon/llwatchdog.cpp @@ -0,0 +1,291 @@ +/** + * @file llthreadwatchdog.cpp + * @brief The LLThreadWatchdog class definitions + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" + +#include "llwatchdog.h" +#include "llmutex.h" +#include "llthread.h" + +constexpr U32 WATCHDOG_SLEEP_TIME_USEC = 1000000U; + +// This class runs the watchdog timing thread. +class LLWatchdogTimerThread : public LLThread +{ +public: + LLWatchdogTimerThread() : + LLThread("Watchdog"), + mSleepMsecs(0), + mStopping(false) + { + } + + ~LLWatchdogTimerThread() {} + + void setSleepTime(long ms) { mSleepMsecs = ms; } + void stop() + { + mStopping = true; + mSleepMsecs = 1; + } + + void run() override + { + while(!mStopping) + { + LLWatchdog::getInstance()->run(); + ms_sleep(mSleepMsecs); + } + } + +private: + long mSleepMsecs; + bool mStopping; +}; + +// LLWatchdogEntry +LLWatchdogEntry::LLWatchdogEntry(const std::string& thread_name) + : mThreadName(thread_name) + , mThreadID(LLThread::currentID()) +{ +} + +LLWatchdogEntry::~LLWatchdogEntry() +{ + stop(); +} + +void LLWatchdogEntry::start() +{ + LLWatchdog::getInstance()->add(this); +} + +void LLWatchdogEntry::stop() +{ + // this can happen very late in the shutdown sequence + if (!LLWatchdog::wasDeleted()) + { + LLWatchdog::getInstance()->remove(this); + } +} +std::string LLWatchdogEntry::getThreadName() const +{ + return mThreadName + llformat(": %d", mThreadID); +} + +// LLWatchdogTimeout +const std::string UNINIT_STRING = "uninitialized"; + +LLWatchdogTimeout::LLWatchdogTimeout(const std::string& thread_name) : + LLWatchdogEntry(thread_name), + mTimeout(0.0f), + mPingState(UNINIT_STRING) +{ +} + +LLWatchdogTimeout::~LLWatchdogTimeout() +{ +} + +bool LLWatchdogTimeout::isAlive() const +{ + return (mTimer.getStarted() && !mTimer.hasExpired()); +} + +void LLWatchdogTimeout::reset() +{ + mTimer.setTimerExpirySec(mTimeout); +} + +void LLWatchdogTimeout::setTimeout(F32 d) +{ + mTimeout = d; +} + +void LLWatchdogTimeout::start(std::string_view state) +{ + if (mTimeout == 0) + { + LL_WARNS() << "Cant' start watchdog entry - no timeout set" << LL_ENDL; + return; + } + // Order of operation is very important here. + // After LLWatchdogEntry::start() is called + // LLWatchdogTimeout::isAlive() will be called asynchronously. + ping(state); + mTimer.start(); + mTimer.setTimerExpirySec(mTimeout); // timer expiration set to 0 by start() + LLWatchdogEntry::start(); +} + +void LLWatchdogTimeout::stop() +{ + LLWatchdogEntry::stop(); + mTimer.stop(); +} + +void LLWatchdogTimeout::ping(std::string_view state) +{ + if (!state.empty()) + { + mPingState = state; + } + reset(); +} + +// LLWatchdog +LLWatchdog::LLWatchdog() + :mSuspectsAccessMutex() + ,mTimer(nullptr) + ,mLastClockCount(0) +{ +} + +LLWatchdog::~LLWatchdog() +{ +} + +void LLWatchdog::add(LLWatchdogEntry* e) +{ + lockThread(); + mSuspects.insert(e); + unlockThread(); +} + +void LLWatchdog::remove(LLWatchdogEntry* e) +{ + lockThread(); + mSuspects.erase(e); + unlockThread(); +} + +void LLWatchdog::init(func_t set_error_state_callback) +{ + if (!mSuspectsAccessMutex && !mTimer) + { + mSuspectsAccessMutex = new LLMutex(); + mTimer = new LLWatchdogTimerThread(); + mTimer->setSleepTime(WATCHDOG_SLEEP_TIME_USEC / 1000); + mLastClockCount = LLTimer::getTotalTime(); + + // mTimer->start() kicks off the thread, any code after + // start needs to use the mSuspectsAccessMutex + mTimer->start(); + } + mCreateMarkerFnc = set_error_state_callback; +} + +void LLWatchdog::cleanup() +{ + if (mTimer) + { + mTimer->stop(); + delete mTimer; + mTimer = nullptr; + } + + if (mSuspectsAccessMutex) + { + delete mSuspectsAccessMutex; + mSuspectsAccessMutex = nullptr; + } + + mLastClockCount = 0; +} + +void LLWatchdog::run() +{ + lockThread(); + + // Check the time since the last call to run... + // If the time elapsed is two times greater than the regualr sleep time + // reset the active timeouts. + constexpr U32 TIME_ELAPSED_MULTIPLIER = 2; + U64 current_time = LLTimer::getTotalTime(); + U64 current_run_delta = current_time - mLastClockCount; + mLastClockCount = current_time; + + if (current_run_delta > (WATCHDOG_SLEEP_TIME_USEC * TIME_ELAPSED_MULTIPLIER)) + { + LL_INFOS() << "Watchdog thread delayed: resetting entries." << LL_ENDL; + for (const auto& suspect : mSuspects) + { + suspect->reset(); + } + } + else + { + SuspectsRegistry::iterator result = + std::find_if(mSuspects.begin(), + mSuspects.end(), + [](const LLWatchdogEntry* suspect){ return ! suspect->isAlive(); }); + if (result != mSuspects.end()) + { + // error!!! + if(mTimer) + { + mTimer->stop(); + } + + // Sets error marker file + mCreateMarkerFnc(); + // Todo1: Warn user? + // Todo2: We probably want to report even if 5 seconds passed, just not error 'yet'. + std::string last_state = (*result)->getLastState(); + if (last_state.empty()) + { + LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName() + << " expired; assuming viewer is hung and crashing" << LL_ENDL; + } + else + { + LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName() + << " expired with state: " << last_state + << "; assuming viewer is hung and crashing" << LL_ENDL; + } + } + } + + + unlockThread(); +} + +void LLWatchdog::lockThread() +{ + if (mSuspectsAccessMutex) + { + mSuspectsAccessMutex->lock(); + } +} + +void LLWatchdog::unlockThread() +{ + if (mSuspectsAccessMutex) + { + mSuspectsAccessMutex->unlock(); + } +} diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h new file mode 100644 index 0000000000..fded881bb8 --- /dev/null +++ b/indra/llcommon/llwatchdog.h @@ -0,0 +1,118 @@ +/** + * @file llthreadwatchdog.h + * @brief The LLThreadWatchdog class declaration + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTHREADWATCHDOG_H +#define LL_LLTHREADWATCHDOG_H + +#ifndef LL_TIMER_H + #include "lltimer.h" +#endif +#include "llmutex.h" +#include "llsingleton.h" + +#include <functional> + +// LLWatchdogEntry is the interface used by the tasks that +// need to be watched. +class LLWatchdogEntry +{ +public: + LLWatchdogEntry(const std::string &thread_name); + virtual ~LLWatchdogEntry(); + + // isAlive is accessed by the watchdog thread. + // This may mean that resources used by + // isAlive and other method may need synchronization. + virtual bool isAlive() const = 0; + virtual void reset() = 0; + virtual void start(); + virtual void stop(); + virtual std::string getLastState() const { return std::string(); } + typedef std::thread::id id_t; + std::string getThreadName() const; + +private: + id_t mThreadID; // ID of the thread being watched + std::string mThreadName; +}; + +class LLWatchdogTimeout : public LLWatchdogEntry +{ +public: + LLWatchdogTimeout(const std::string& thread_name); + virtual ~LLWatchdogTimeout(); + + bool isAlive() const override; + void reset() override; + void start() override { start(""); } + void stop() override; + + void start(std::string_view state); + void setTimeout(F32 d); + void ping(std::string_view state); + const std::string& getState() {return mPingState; } + std::string getLastState() const override { return mPingState; } + +private: + LLTimer mTimer; + F32 mTimeout; + std::string mPingState; +}; + +class LLWatchdogTimerThread; // Defined in the cpp +class LLWatchdog : public LLSingleton<LLWatchdog> +{ + LLSINGLETON(LLWatchdog); + ~LLWatchdog(); + +public: + // Add an entry to the watchdog. + void add(LLWatchdogEntry* e); + void remove(LLWatchdogEntry* e); + + typedef std::function<void()> func_t; + void init(func_t set_error_state_callback); + void run(); + void cleanup(); + + +private: + void lockThread(); + void unlockThread(); + + typedef std::set<LLWatchdogEntry*> SuspectsRegistry; + SuspectsRegistry mSuspects; + LLMutex* mSuspectsAccessMutex; + LLWatchdogTimerThread* mTimer; + U64 mLastClockCount; + + // At the moment watchdog expects app to set markers in mCreateMarkerFnc, + // but technically can be used to set any error states or do some cleanup + // or show warnings. + func_t mCreateMarkerFnc; +}; + +#endif // LL_LLTHREADWATCHDOG_H diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index 7efaebd569..67c23358ed 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -216,10 +216,15 @@ void LL::WorkQueueBase::callWork(const Work& work) LL_WARNS("LLCoros") << "Capturing and rethrowing uncaught exception in WorkQueueBase " << getKey() << LL_ENDL; + std::string name = getKey(); LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); main_queue->post( // Bind the current exception, rethrow it in main loop. - [exc = std::current_exception()]() { std::rethrow_exception(exc); }); + [exc = std::current_exception(), name]() + { + LL_INFOS("LLCoros") << "Rethrowing exception from WorkQueueBase::callWork " << name << LL_ENDL; + std::rethrow_exception(exc); + }); } else { @@ -276,12 +281,30 @@ bool LL::WorkQueue::done() bool LL::WorkQueue::post(const Work& callable) { - return mQueue.pushIfOpen(callable); + try + { + return mQueue.pushIfOpen(callable); + } + catch (std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("LLCoros") << "Bad memory allocation in WorkQueue::post" << LL_ENDL; + return false; + } } bool LL::WorkQueue::tryPost(const Work& callable) { - return mQueue.tryPush(callable); + try + { + return mQueue.tryPush(callable); + } + catch (std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("LLCoros") << "Bad memory allocation in WorkQueue::tryPost" << LL_ENDL; + return false; + } } LL::WorkQueue::Work LL::WorkQueue::pop_() diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 735ad38a26..573203a5b3 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -530,7 +530,11 @@ namespace LL reply, // Bind the current exception to transport back to the // originating WorkQueue. Once there, rethrow it. - [exc = std::current_exception()](){ std::rethrow_exception(exc); }); + [exc = std::current_exception()]() + { + LL_INFOS("LLCoros") << "Rethrowing exception from WorkQueueBase::postTo" << LL_ENDL; + std::rethrow_exception(exc); + }); } }, // if caller passed a TimePoint, pass it along to post() |
