From de9a9111fa3269a7a6a2d966cf52869d6a711333 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:39:07 +0200 Subject: #5084 Improve watchdog's feedback --- indra/newview/llappviewer.cpp | 28 +++++++++++++++++++++++----- indra/newview/llwatchdog.cpp | 27 +++++++++++++++++++++++---- indra/newview/llwatchdog.h | 12 ++++++++++-- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 569fd30b21..8358583c35 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1239,7 +1239,7 @@ bool LLAppViewer::init() /*----------------------------------------------------------------------*/ // nat 2016-06-29 moved the following here from the former mainLoop(). - mMainloopTimeout = new LLWatchdogTimeout(); + mMainloopTimeout = new LLWatchdogTimeout("mainloop"); // Create IO Pump to use for HTTP Requests. gServicePump = new LLPumpIO(gAPRPoolp); @@ -1429,12 +1429,14 @@ bool LLAppViewer::doFrame() { LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df mainloop"); + pingMainloopTimeout("df mainloop"); // canonical per-frame event mainloop.post(newFrame); } { LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df suspend"); + pingMainloopTimeout("df suspend"); // give listeners a chance to run llcoro::suspend(); // if one of our coroutines threw an uncaught exception, rethrow it now @@ -1470,6 +1472,7 @@ bool LLAppViewer::doFrame() { { LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df pauseMainloopTimeout"); + pingMainloopTimeout("df idle"); // So that it will be aware of last state. pauseMainloopTimeout(); // *TODO: Remove. Messages shouldn't be stalling for 20+ seconds! } @@ -1481,7 +1484,7 @@ bool LLAppViewer::doFrame() { LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df resumeMainloopTimeout"); - resumeMainloopTimeout(); + resumeMainloopTimeout("df idle"); } } @@ -1496,7 +1499,7 @@ bool LLAppViewer::doFrame() } disconnectViewer(); - resumeMainloopTimeout(); + resumeMainloopTimeout("df snapshot n disconnect"); } // Render scene. @@ -2301,7 +2304,22 @@ void errorHandler(const std::string& title_string, const std::string& message_st } if (!message_string.empty()) { - OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK); + if (on_main_thread()) + { + // Prevent watchdog from killing us while dialog is up. + // Can't do pauseMainloopTimeout, since this may be called + // from threads and we are not going to need watchdog now. + LLAppViewer::instance()->pauseMainloopTimeout(); + + // todo: might want to have non-crashing timeout for OOM cases + // and needs a way to pause main loop. + OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK); + LLAppViewer::instance()->resumeMainloopTimeout(); + } + else + { + OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK); + } } } @@ -5825,7 +5843,7 @@ void LLAppViewer::initMainloopTimeout(std::string_view state) { if (!mMainloopTimeout) { - mMainloopTimeout = new LLWatchdogTimeout(); + mMainloopTimeout = new LLWatchdogTimeout("mainloop"); resumeMainloopTimeout(state); } } diff --git a/indra/newview/llwatchdog.cpp b/indra/newview/llwatchdog.cpp index 614d1afc2a..0984606456 100644 --- a/indra/newview/llwatchdog.cpp +++ b/indra/newview/llwatchdog.cpp @@ -67,7 +67,9 @@ private: }; // LLWatchdogEntry -LLWatchdogEntry::LLWatchdogEntry() +LLWatchdogEntry::LLWatchdogEntry(const std::string& thread_name) + : mThreadName(thread_name) + , mThreadID(LLThread::currentID()) { } @@ -89,11 +91,16 @@ void LLWatchdogEntry::stop() LLWatchdog::getInstance()->remove(this); } } +std::string LLWatchdogEntry::getThreadName() const +{ + return mThreadName + llformat(": %d", mThreadID); +} // LLWatchdogTimeout const std::string UNINIT_STRING = "uninitialized"; -LLWatchdogTimeout::LLWatchdogTimeout() : +LLWatchdogTimeout::LLWatchdogTimeout(const std::string& thread_name) : + LLWatchdogEntry(thread_name), mTimeout(0.0f), mPingState(UNINIT_STRING) { @@ -249,9 +256,21 @@ void LLWatchdog::run() { LLAppViewer::instance()->createErrorMarker(LAST_EXEC_FROZE); } - // Todo1: warn user? + // Todo1: Warn user? // Todo2: We probably want to report even if 5 seconds passed, just not error 'yet'. - LL_ERRS() << "Watchdog timer expired; assuming viewer is hung and crashing" << LL_ENDL; + // Todo3: This will report crash as 'llerror', consider adding 'watchdog' reason. + 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; + } } } diff --git a/indra/newview/llwatchdog.h b/indra/newview/llwatchdog.h index b7dd55577e..a8056f4337 100644 --- a/indra/newview/llwatchdog.h +++ b/indra/newview/llwatchdog.h @@ -36,7 +36,7 @@ class LLWatchdogEntry { public: - LLWatchdogEntry(); + LLWatchdogEntry(const std::string &thread_name); virtual ~LLWatchdogEntry(); // isAlive is accessed by the watchdog thread. @@ -46,12 +46,19 @@ public: 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(); + LLWatchdogTimeout(const std::string& thread_name); virtual ~LLWatchdogTimeout(); bool isAlive() const override; @@ -63,6 +70,7 @@ public: 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; -- cgit v1.3 From 107ea4d84950e13be6b7291f506419b1839a0dda Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:18:41 +0200 Subject: #5084 Cover window's thread with watchdog --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/llwatchdog.cpp | 290 ++++++++++++++++++++++++++++++++++++++ indra/llcommon/llwatchdog.h | 118 ++++++++++++++++ indra/llwindow/llwindow.h | 2 + indra/llwindow/llwindowwin32.cpp | 65 ++++++++- indra/llwindow/llwindowwin32.h | 141 +++++++++---------- indra/newview/CMakeLists.txt | 2 - indra/newview/llappviewer.cpp | 14 +- indra/newview/llwatchdog.cpp | 295 --------------------------------------- indra/newview/llwatchdog.h | 107 -------------- 10 files changed, 560 insertions(+), 476 deletions(-) create mode 100644 indra/llcommon/llwatchdog.cpp create mode 100644 indra/llcommon/llwatchdog.h delete mode 100644 indra/newview/llwatchdog.cpp delete mode 100644 indra/newview/llwatchdog.h diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 2de9deea70..4d04c2c119 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -101,6 +101,7 @@ set(llcommon_SOURCE_FILES lluri.cpp lluriparser.cpp lluuid.cpp + llwatchdog.cpp llworkerthread.cpp hbxxh.cpp u64.cpp @@ -240,6 +241,7 @@ set(llcommon_HEADER_FILES lluri.h lluriparser.h lluuid.h + llwatchdog.h llwin32headers.h llworkerthread.h hbxxh.h diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp new file mode 100644 index 0000000000..fa240a9ed7 --- /dev/null +++ b/indra/llcommon/llwatchdog.cpp @@ -0,0 +1,290 @@ +/** + * @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 "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 + +// 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 +{ + LLSINGLETON(LLWatchdog); + ~LLWatchdog(); + +public: + // Add an entry to the watchdog. + void add(LLWatchdogEntry* e); + void remove(LLWatchdogEntry* e); + + typedef std::function func_t; + void init(func_t set_error_state_callback); + void run(); + void cleanup(); + + +private: + void lockThread(); + void unlockThread(); + + typedef std::set 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/llwindow/llwindow.h b/indra/llwindow/llwindow.h index 7a5404e615..185940e32d 100644 --- a/indra/llwindow/llwindow.h +++ b/indra/llwindow/llwindow.h @@ -205,6 +205,8 @@ public: }; virtual S32 getRefreshRate() { return mRefreshRate; } + + virtual void initWatchdog() {} // windows runs window as a thread and it needs a watchdog protected: LLWindow(LLWindowCallbacks* callbacks, bool fullscreen, U32 flags); virtual ~LLWindow(); diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index f826a60ddd..9d05d7e5a4 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -49,6 +49,7 @@ #include "llthreadsafequeue.h" #include "stringize.h" #include "llframetimer.h" +#include "llwatchdog.h" // System includes #include @@ -364,7 +365,8 @@ static LLMonitorInfo sMonitorInfo; // the containing class a friend. struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool { - static const int MAX_QUEUE_SIZE = 2048; + static constexpr int MAX_QUEUE_SIZE = 2048; + static constexpr F32 WINDOW_TIMEOUT_SEC = 90.f; LLThreadSafeQueue mMessageQueue; @@ -426,6 +428,50 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool PostMessage(windowHandle, WM_POST_FUNCTION_, wparam, LPARAM(ptr)); } + // Call from main thread. + void initTimeout() + { + // post into thread's queue to avoid threading issues + post([this]() + { + if (!mWindowTimeout) + { + mWindowTimeout = std::make_unique("mainloop"); + // supposed to be executed within run(), + // so no point checking if thread is alive + resumeTimeout("TimeoutInit"); + } + }); + } +private: + // These timeout related functions are strictly for the thread. + void resumeTimeout(std::string_view state) + { + if (mWindowTimeout) + { + mWindowTimeout->setTimeout(WINDOW_TIMEOUT_SEC); + mWindowTimeout->start(state); + } + } + + void pauseTimeout() + { + if (mWindowTimeout) + { + mWindowTimeout->stop(); + } + } + + void pingTimeout(std::string_view state) + { + if (mWindowTimeout) + { + mWindowTimeout->setTimeout(WINDOW_TIMEOUT_SEC); + mWindowTimeout->ping(state); + } + } + +public: using FuncType = std::function; // call GetMessage() and pull enqueue messages for later processing HWND mWindowHandleThrd = NULL; @@ -436,6 +482,8 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool bool mGLReady = false; bool mGotGLBuffer = false; LLAtomicBool mDeleteOnExit = false; +private: + std::unique_ptr mWindowTimeout; }; @@ -4595,6 +4643,11 @@ bool LLWindowWin32::getInputDevices(U32 device_type_filter, return false; } +void LLWindowWin32::initWatchdog() +{ + mWindowThread->initTimeout(); +} + F32 LLWindowWin32::getSystemUISize() { F32 scale_value = 1.f; @@ -4732,6 +4785,8 @@ void LLWindowWin32::LLWindowWin32Thread::checkDXMem() return; } + pauseTimeout(); + IDXGIFactory4* p_factory = nullptr; HRESULT res = CreateDXGIFactory1(__uuidof(IDXGIFactory4), (void**)&p_factory); @@ -4835,6 +4890,8 @@ void LLWindowWin32::LLWindowWin32Thread::checkDXMem() } mGotGLBuffer = true; + + resumeTimeout("checkDXMem"); } void LLWindowWin32::LLWindowWin32Thread::run() @@ -4850,6 +4907,9 @@ void LLWindowWin32::LLWindowWin32Thread::run() timeBeginPeriod(llclamp((U32) 1, tc.wPeriodMin, tc.wPeriodMax)); } + // Normally won't exist yet, but in case of re-init, make sure it's cleaned up + resumeTimeout("WindowThread"); + while (! getQueue().done()) { LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32; @@ -4859,6 +4919,7 @@ void LLWindowWin32::LLWindowWin32Thread::run() if (mWindowHandleThrd != 0) { + pingTimeout("messages"); MSG msg; BOOL status; if (mhDCThrd == 0) @@ -4886,6 +4947,7 @@ void LLWindowWin32::LLWindowWin32Thread::run() { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - Function Queue"); + pingTimeout("queue"); logger.onChange("runPending()"); //process any pending functions getQueue().runPending(); @@ -4900,6 +4962,7 @@ void LLWindowWin32::LLWindowWin32Thread::run() #endif } + pauseTimeout(); destroyWindow(); if (mDeleteOnExit) diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index 0fc93ad0b1..8159092794 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -45,83 +45,82 @@ typedef void (*LLW32MsgCallback)(const MSG &msg); class LLWindowWin32 : public LLWindow { public: - /*virtual*/ void show(); - /*virtual*/ void hide(); - /*virtual*/ void close(); - /*virtual*/ bool getVisible(); - /*virtual*/ bool getMinimized(); - /*virtual*/ bool getMaximized(); - /*virtual*/ bool maximize(); - /*virtual*/ void minimize(); - /*virtual*/ void restore(); - /*virtual*/ bool getFullscreen(); - /*virtual*/ bool getPosition(LLCoordScreen *position); - /*virtual*/ bool getSize(LLCoordScreen *size); - /*virtual*/ bool getSize(LLCoordWindow *size); - /*virtual*/ bool setPosition(LLCoordScreen position); - /*virtual*/ bool setSizeImpl(LLCoordScreen size); - /*virtual*/ bool setSizeImpl(LLCoordWindow size); - /*virtual*/ bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL); - /*virtual*/ void setTitle(const std::string title); + void show() override; + void hide() override; + void close() override; + bool getVisible() override; + bool getMinimized() override; + bool getMaximized() override; + bool maximize() override; + void minimize() override; + void restore() override; + bool getFullscreen(); + bool getPosition(LLCoordScreen *position) override; + bool getSize(LLCoordScreen *size) override; + bool getSize(LLCoordWindow *size) override; + bool setPosition(LLCoordScreen position) override; + bool setSizeImpl(LLCoordScreen size) override; + bool setSizeImpl(LLCoordWindow size) override; + bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL) override; + void setTitle(const std::string title) override; void* createSharedContext() override; void makeContextCurrent(void* context) override; void destroySharedContext(void* context) override; - /*virtual*/ void toggleVSync(bool enable_vsync); - /*virtual*/ bool setCursorPosition(LLCoordWindow position); - /*virtual*/ bool getCursorPosition(LLCoordWindow *position); - /*virtual*/ bool getCursorDelta(LLCoordCommon* delta); - /*virtual*/ bool isWrapMouse() const override { return !mAbsoluteCursorPosition; }; - /*virtual*/ void showCursor(); - /*virtual*/ void hideCursor(); - /*virtual*/ void showCursorFromMouseMove(); - /*virtual*/ void hideCursorUntilMouseMove(); - /*virtual*/ bool isCursorHidden(); - /*virtual*/ void updateCursor(); - /*virtual*/ ECursorType getCursor() const; - /*virtual*/ void captureMouse(); - /*virtual*/ void releaseMouse(); - /*virtual*/ void setMouseClipping( bool b ); - /*virtual*/ bool isClipboardTextAvailable(); - /*virtual*/ bool pasteTextFromClipboard(LLWString &dst); - /*virtual*/ bool copyTextToClipboard(const LLWString &src); - /*virtual*/ void flashIcon(F32 seconds); - /*virtual*/ F32 getGamma(); - /*virtual*/ bool setGamma(const F32 gamma); // Set the gamma - /*virtual*/ void setFSAASamples(const U32 fsaa_samples); - /*virtual*/ U32 getFSAASamples(); - /*virtual*/ bool restoreGamma(); // Restore original gamma table (before updating gamma) - /*virtual*/ ESwapMethod getSwapMethod() { return mSwapMethod; } - /*virtual*/ void gatherInput(); - /*virtual*/ void delayInputProcessing(); - /*virtual*/ void swapBuffers(); - /*virtual*/ void restoreGLContext() {}; + void toggleVSync(bool enable_vsync) override; + bool setCursorPosition(LLCoordWindow position) override; + bool getCursorPosition(LLCoordWindow *position) override; + bool getCursorDelta(LLCoordCommon* delta) override; + bool isWrapMouse() const override { return !mAbsoluteCursorPosition; }; + void showCursor() override; + void hideCursor() override; + void showCursorFromMouseMove() override; + void hideCursorUntilMouseMove() override; + bool isCursorHidden() override; + void updateCursor() override; + ECursorType getCursor() const override; + void captureMouse() override; + void releaseMouse() override; + void setMouseClipping( bool b ) override; + bool isClipboardTextAvailable() override; + bool pasteTextFromClipboard(LLWString &dst) override; + bool copyTextToClipboard(const LLWString &src) override; + void flashIcon(F32 seconds) override; + F32 getGamma() override; + bool setGamma(const F32 gamma) override; // Set the gamma + void setFSAASamples(const U32 fsaa_samples) override; + U32 getFSAASamples() override; + bool restoreGamma() override; // Restore original gamma table (before updating gamma) + ESwapMethod getSwapMethod() override { return mSwapMethod; } + void gatherInput() override; + void delayInputProcessing() override; + void swapBuffers() override; // handy coordinate space conversion routines - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordWindow *to); - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordScreen *to); - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordGL *to); - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordWindow *to); - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordGL *to); - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordScreen *to); + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override; + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override; + bool convertCoords(LLCoordWindow from, LLCoordGL *to) override; + bool convertCoords(LLCoordGL from, LLCoordWindow *to) override; + bool convertCoords(LLCoordScreen from, LLCoordGL *to) override; + bool convertCoords(LLCoordGL from, LLCoordScreen *to) override; - /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions); - /*virtual*/ F32 getNativeAspectRatio(); - /*virtual*/ F32 getPixelAspectRatio(); - /*virtual*/ void setNativeAspectRatio(F32 ratio) { mOverrideAspectRatio = ratio; } + LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override; + F32 getNativeAspectRatio() override; + F32 getPixelAspectRatio() override; + void setNativeAspectRatio(F32 ratio) override { mOverrideAspectRatio = ratio; } - /*virtual*/ bool dialogColorPicker(F32 *r, F32 *g, F32 *b ); + bool dialogColorPicker(F32 *r, F32 *g, F32 *b ) override; - /*virtual*/ void *getPlatformWindow(); - /*virtual*/ void bringToFront(); - /*virtual*/ void focusClient(); + void *getPlatformWindow() override; + void bringToFront() override; + void focusClient() override; - /*virtual*/ void allowLanguageTextInput(LLPreeditor *preeditor, bool b); - /*virtual*/ void setLanguageTextInput( const LLCoordGL & pos ); - /*virtual*/ void updateLanguageTextInputArea(); - /*virtual*/ void interruptLanguageTextInput(); - /*virtual*/ void spawnWebBrowser(const std::string& escaped_url, bool async); + void allowLanguageTextInput(LLPreeditor *preeditor, bool b) override; + void setLanguageTextInput( const LLCoordGL & pos ) override; + void updateLanguageTextInputArea() override; + void interruptLanguageTextInput() override; + void spawnWebBrowser(const std::string& escaped_url, bool async) override; - /*virtual*/ F32 getSystemUISize(); + F32 getSystemUISize() override; LLWindowCallbacks::DragNDropResult completeDragNDropRequest( const LLCoordGL gl_coord, const MASK mask, LLWindowCallbacks::DragNDropAction action, const std::string url ); @@ -129,14 +128,16 @@ public: static std::vector getDynamicFallbackFontList(); static void setDPIAwareness(); - /*virtual*/ void* getDirectInput8(); - /*virtual*/ bool getInputDevices(U32 device_type_filter, + void* getDirectInput8() override; + bool getInputDevices(U32 device_type_filter, std::function osx_callback, void* win_callback, - void* userdata); + void* userdata) override; U32 getRawWParam() { return mRawWParam; } + void initWatchdog() override; + protected: LLWindowWin32(LLWindowCallbacks* callbacks, const std::string& title, const std::string& name, int x, int y, int width, int height, U32 flags, diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 0c5f3f3fd9..0949a3b59f 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -736,7 +736,6 @@ set(viewer_SOURCE_FILES llvovolume.cpp llvowater.cpp llvowlsky.cpp - llwatchdog.cpp llwearableitemslist.cpp llwearablelist.cpp llweb.cpp @@ -1414,7 +1413,6 @@ set(viewer_HEADER_FILES llvovolume.h llvowater.h llvowlsky.h - llwatchdog.h llwearableitemslist.h llwearablelist.h llweb.h diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 8358583c35..e711064455 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -3182,7 +3182,19 @@ bool LLAppViewer::initWindow() if (use_watchdog) { - LLWatchdog::getInstance()->init(); + LLWatchdog::getInstance()->init([]() + { + LLAppViewer* app = LLAppViewer::instance(); + if (app->logoutRequestSent()) + { + app->createErrorMarker(LAST_EXEC_LOGOUT_FROZE); + } + else + { + app->createErrorMarker(LAST_EXEC_FROZE); + } + }); + gViewerWindow->getWindow()->initWatchdog(); } LLNotificationsUI::LLNotificationManager::getInstance(); diff --git a/indra/newview/llwatchdog.cpp b/indra/newview/llwatchdog.cpp deleted file mode 100644 index 0984606456..0000000000 --- a/indra/newview/llwatchdog.cpp +++ /dev/null @@ -1,295 +0,0 @@ -/** - * @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$ - */ - - -#include "llviewerprecompiledheaders.h" -#include "llwatchdog.h" -#include "llthread.h" -#include "llappviewer.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() -{ - 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(); - } -} - -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(); - } - if (LLAppViewer::instance()->logoutRequestSent()) - { - LLAppViewer::instance()->createErrorMarker(LAST_EXEC_LOGOUT_FROZE); - } - else - { - LLAppViewer::instance()->createErrorMarker(LAST_EXEC_FROZE); - } - // Todo1: Warn user? - // Todo2: We probably want to report even if 5 seconds passed, just not error 'yet'. - // Todo3: This will report crash as 'llerror', consider adding 'watchdog' reason. - 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/newview/llwatchdog.h b/indra/newview/llwatchdog.h deleted file mode 100644 index a8056f4337..0000000000 --- a/indra/newview/llwatchdog.h +++ /dev/null @@ -1,107 +0,0 @@ -/** - * @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 - -// 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 -{ - LLSINGLETON(LLWatchdog); - ~LLWatchdog(); - -public: - // Add an entry to the watchdog. - void add(LLWatchdogEntry* e); - void remove(LLWatchdogEntry* e); - - void init(); - void run(); - void cleanup(); - -private: - void lockThread(); - void unlockThread(); - - typedef std::set SuspectsRegistry; - SuspectsRegistry mSuspects; - LLMutex* mSuspectsAccessMutex; - LLWatchdogTimerThread* mTimer; - U64 mLastClockCount; -}; - -#endif // LL_LLTHREADWATCHDOG_H -- cgit v1.3 From f57c934676a928e84ae3af55b4886076decca2fc Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:51:31 +0200 Subject: #3612 Log issues with landmarks and rethrown exceptions --- indra/llcommon/llthread.cpp | 6 +++++- indra/llcommon/workqueue.cpp | 7 ++++++- indra/llcommon/workqueue.h | 6 +++++- indra/newview/lllandmarklist.cpp | 15 ++++++++++----- indra/newview/skins/default/xui/en/notifications.xml | 2 +- 5 files changed, 27 insertions(+), 9 deletions(-) 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/workqueue.cpp b/indra/llcommon/workqueue.cpp index 7efaebd569..0407d6c3e9 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 { 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() diff --git a/indra/newview/lllandmarklist.cpp b/indra/newview/lllandmarklist.cpp index 3fa0ab99f3..d67b5885f6 100644 --- a/indra/newview/lllandmarklist.cpp +++ b/indra/newview/lllandmarklist.cpp @@ -158,16 +158,21 @@ void LLLandmarkList::processGetAssetReply( } else { - // SJB: No use case for a notification here. Use LL_DEBUGS() instead + // SJB: No use case for a notification here. + // + // Todo: potentially cap getting obsolete due to a teleport + // can lead to this, so this might need a timeout or smarter + // handling to rerequest after a time instead of just failing + // al future requests. if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ) { - LL_WARNS("Landmarks") << "Missing Landmark" << LL_ENDL; - //LLNotificationsUtil::add("LandmarkMissing"); + LL_WARNS("Landmarks") << "Missing Landmark " << uuid << LL_ENDL; } else { - LL_WARNS("Landmarks") << "Unable to load Landmark" << LL_ENDL; - //LLNotificationsUtil::add("UnableToLoadLandmark"); + LL_WARNS("Landmarks") << "Unable to load Landmark " << uuid + << ". asset status: " << status + << ". Extended status: " << (S64)ext_status << LL_ENDL; } gLandmarkList.mBadList.insert(uuid); diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index d0261a930c..82e2229d76 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -2469,7 +2469,7 @@ You already have a landmark for this location. icon="alert.tga" name="LandmarkLocationUnknown" type="alert"> -Viewer wasn't able to get region's location. Region might be temporarily unavailable or was removed. +Viewer wasn't able to get region's location. Region might be temporarily unavailable, was removed or landmark failed to load. -- cgit v1.3 From dd0dbf205cfb97451b5180bbc1cab6d2b40cbfbc Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 22 Dec 2025 20:18:41 +0200 Subject: #3612 Handle missing capabilities instead of blocking further downloads Landmarks that failed due to missing caps were blocked from being rerequested. --- indra/llmessage/llassetstorage.cpp | 3 +++ indra/llmessage/llassetstorage.h | 1 + indra/newview/lllandmarklist.cpp | 46 ++++++++++++++++++++++++++++------ indra/newview/lllandmarklist.h | 1 + indra/newview/llviewerassetstorage.cpp | 17 ++++++++++--- 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp index 10fd56a68e..4c3acb27f4 100644 --- a/indra/llmessage/llassetstorage.cpp +++ b/indra/llmessage/llassetstorage.cpp @@ -1316,6 +1316,9 @@ const char* LLAssetStorage::getErrorString(S32 status) case LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE: return "Asset request: asset not found in database"; + case LL_ERR_NO_CAP: + return "Asset request: region or asset capability not available"; + case LL_ERR_EOF: return "End of file"; diff --git a/indra/llmessage/llassetstorage.h b/indra/llmessage/llassetstorage.h index 6d6526757d..d5daa0cb8f 100644 --- a/indra/llmessage/llassetstorage.h +++ b/indra/llmessage/llassetstorage.h @@ -57,6 +57,7 @@ const int LL_ERR_ASSET_REQUEST_FAILED = -1; const int LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE = -3; const int LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE = -4; const int LL_ERR_INSUFFICIENT_PERMISSIONS = -5; +const int LL_ERR_NO_CAP = -6; const int LL_ERR_PRICE_MISMATCH = -23018; // *TODO: these typedefs are passed into the cache via a legacy C function pointer diff --git a/indra/newview/lllandmarklist.cpp b/indra/newview/lllandmarklist.cpp index d67b5885f6..b25a42a938 100644 --- a/indra/newview/lllandmarklist.cpp +++ b/indra/newview/lllandmarklist.cpp @@ -147,24 +147,55 @@ void LLLandmarkList::processGetAssetReply( else { // failed to parse, shouldn't happen + LL_WARNS("Landmarks") << "Failed to parse landmark " << uuid << LL_ENDL; gLandmarkList.eraseCallbacks(uuid); } } else { // got a good status, but no file, shouldn't happen + LL_WARNS("Landmarks") << "Empty buffer for landmark " << uuid << LL_ENDL; gLandmarkList.eraseCallbacks(uuid); } + + // We got this asset, remove it from retry and bad lists. + gLandmarkList.mRetryList.erase(uuid); + gLandmarkList.mBadList.erase(uuid); } else { - // SJB: No use case for a notification here. - // - // Todo: potentially cap getting obsolete due to a teleport - // can lead to this, so this might need a timeout or smarter - // handling to rerequest after a time instead of just failing - // al future requests. - if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ) + if (LL_ERR_NO_CAP == status) + { + // A problem with asset cap, always allow retrying. + // Todo: should this reschedule? + gLandmarkList.mRequestedList.erase(uuid); + gLandmarkList.eraseCallbacks(uuid); + // If there was a previous request, it likely failed due to an obsolete cap + // so clear the retry marker to allow multiple retries. + gLandmarkList.mRetryList.erase(uuid); + return; + } + if (gLandmarkList.mBadList.find(uuid) != gLandmarkList.mBadList.end()) + { + // Already on the 'bad' list, ignore + gLandmarkList.mRequestedList.erase(uuid); + gLandmarkList.eraseCallbacks(uuid); + return; + } + if (LL_ERR_ASSET_REQUEST_FAILED == status + && gLandmarkList.mRetryList.find(uuid) == gLandmarkList.mRetryList.end()) + { + // There is a number of reasons why an asset request can fail, + // like a cap being obsolete due to user teleporting. + // Let viewer rerequest at least once more. + // Todo: should this reshchedule? + gLandmarkList.mRetryList.emplace(uuid); + gLandmarkList.mRequestedList.erase(uuid); + gLandmarkList.eraseCallbacks(uuid); + return; + } + + if (LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status) { LL_WARNS("Landmarks") << "Missing Landmark " << uuid << LL_ENDL; } @@ -175,6 +206,7 @@ void LLLandmarkList::processGetAssetReply( << ". Extended status: " << (S64)ext_status << LL_ENDL; } + gLandmarkList.mRetryList.erase(uuid); gLandmarkList.mBadList.insert(uuid); gLandmarkList.mRequestedList.erase(uuid); //mBadList effectively blocks any load, so no point keeping id in requests gLandmarkList.eraseCallbacks(uuid); diff --git a/indra/newview/lllandmarklist.h b/indra/newview/lllandmarklist.h index fb8b5a1960..76b5b97211 100644 --- a/indra/newview/lllandmarklist.h +++ b/indra/newview/lllandmarklist.h @@ -72,6 +72,7 @@ protected: typedef std::set landmark_uuid_list_t; landmark_uuid_list_t mBadList; + landmark_uuid_list_t mRetryList; typedef std::map landmark_requested_list_t; landmark_requested_list_t mRequestedList; diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp index 141f370ecb..de6b2d9e7c 100644 --- a/indra/newview/llviewerassetstorage.cpp +++ b/indra/newview/llviewerassetstorage.cpp @@ -461,7 +461,7 @@ void LLViewerAssetStorage::assetRequestCoro( if (!gAgent.getRegion()) { LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: no region set" << LL_ENDL; - result_code = LL_ERR_ASSET_REQUEST_FAILED; + result_code = LL_ERR_NO_CAP; ext_status = LLExtStat::NONE; removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0); return; @@ -475,13 +475,24 @@ void LLViewerAssetStorage::assetRequestCoro( gAgent.getRegion()->setCapabilitiesReceivedCallback( boost::bind(&LLViewerAssetStorage::capsRecvForRegion, this, _1, capsRecv.getName())); - llcoro::suspendUntilEventOn(capsRecv); + F32Seconds timeout_seconds(LL_ASSET_STORAGE_TIMEOUT); // from minutes to seconds, by default 5 minutes + LLSD result = llcoro::suspendUntilEventOnWithTimeout(capsRecv, timeout_seconds, LLSDMap("timeout", LLSD::Boolean(true))); if (LLApp::isExiting() || !gAssetStorage) { return; } + if (result.has("timeout")) + { + // Caps failed to arrive in 5 minutes + LL_WARNS_ONCE("ViewerAsset") << "Asset " << uuid << " request fails : capabilities took too long to arrive" << LL_ENDL; + result_code = LL_ERR_NO_CAP; + ext_status = LLExtStat::NONE; + removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0); + return; + } + LL_WARNS_ONCE("ViewerAsset") << "capsRecv got event" << LL_ENDL; LL_WARNS_ONCE("ViewerAsset") << "region " << gAgent.getRegion() << " mViewerAssetUrl " << mViewerAssetUrl << LL_ENDL; } @@ -492,7 +503,7 @@ void LLViewerAssetStorage::assetRequestCoro( if (mViewerAssetUrl.empty()) { LL_WARNS_ONCE("ViewerAsset") << "asset request fails: caps received but no viewer asset cap found" << LL_ENDL; - result_code = LL_ERR_ASSET_REQUEST_FAILED; + result_code = LL_ERR_NO_CAP; ext_status = LLExtStat::NONE; removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0); return; -- cgit v1.3 From 793acc7f57b01edd86fdbfba3981027ce9498888 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 23 Dec 2025 02:35:08 +0200 Subject: #3612 Don't immediately fail download on startup, wait for the region It's probably smarter to make asset storage wait with requests in general, but that would require a stronger separation from cache which is functional already and can be loading assets, so I'm pausing individual requests instead, there is a maximum of 12, so there should be no 'bloat' from waiting coros. --- indra/newview/llviewerassetstorage.cpp | 56 +++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp index de6b2d9e7c..fd462fb225 100644 --- a/indra/newview/llviewerassetstorage.cpp +++ b/indra/newview/llviewerassetstorage.cpp @@ -42,6 +42,7 @@ #include "llcoproceduremanager.h" #include "lleventcoro.h" #include "llsdutil.h" +#include "llstartup.h" #include "llworld.h" ///---------------------------------------------------------------------------- @@ -460,23 +461,62 @@ void LLViewerAssetStorage::assetRequestCoro( if (!gAgent.getRegion()) { - LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: no region set" << LL_ENDL; - result_code = LL_ERR_NO_CAP; - ext_status = LLExtStat::NONE; - removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0); - return; + if (STATE_WORLD_INIT <= LLStartUp::getStartupState()) + { + // Viewer isn't ready, wait for region to become available + LL_INFOS_ONCE("ViewerAsset") << "Waiting for agent region to be set" << LL_ENDL; + + LLEventStream region_init("waitForRegion", true); + std::string pump_name = region_init.getName(); + + boost::signals2::connection region_conn = + gAgent.addRegionChangedCallback([pump_name]() + { + LLEventPumps::instance().obtain(pump_name).post(LLSD()); + }); + F32Seconds timeout_seconds(LL_ASSET_STORAGE_TIMEOUT); + llcoro::suspendUntilEventOnWithTimeout(region_init, timeout_seconds, LLSDMap("timeout", LLSD::Boolean(true))); + gAgent.removeRegionChangedCallback(region_conn); + region_conn.disconnect(); + + if (LLApp::isExiting() || !gAssetStorage) + { + return; + } + + // recheck region whether suspend ended on timeout or not + if (!gAgent.getRegion()) + { + LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: timeout reached while waiting for region" << LL_ENDL; + result_code = LL_ERR_NO_CAP; + ext_status = LLExtStat::NONE; + removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0); + return; + } + } + else + { + LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: no region set" << LL_ENDL; + result_code = LL_ERR_NO_CAP; + ext_status = LLExtStat::NONE; + removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0); + return; + } } - else if (!gAgent.getRegion()->capabilitiesReceived()) + + if (!gAgent.getRegion()->capabilitiesReceived()) { LL_WARNS_ONCE("ViewerAsset") << "Waiting for capabilities" << LL_ENDL; LLEventStream capsRecv("waitForCaps", true); - gAgent.getRegion()->setCapabilitiesReceivedCallback( - boost::bind(&LLViewerAssetStorage::capsRecvForRegion, this, _1, capsRecv.getName())); + boost::signals2::connection caps_conn = + gAgent.getRegion()->setCapabilitiesReceivedCallback( + boost::bind(&LLViewerAssetStorage::capsRecvForRegion, this, _1, capsRecv.getName())); F32Seconds timeout_seconds(LL_ASSET_STORAGE_TIMEOUT); // from minutes to seconds, by default 5 minutes LLSD result = llcoro::suspendUntilEventOnWithTimeout(capsRecv, timeout_seconds, LLSDMap("timeout", LLSD::Boolean(true))); + caps_conn.disconnect(); if (LLApp::isExiting() || !gAssetStorage) { -- cgit v1.3 From 2ba9383d0d008dc44a062fd8566d3d6a171b0dd8 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Tue, 6 Jan 2026 20:12:51 +0200 Subject: revert #4735 Remove the word "Viewer" from application shortcut --- indra/newview/viewer_manifest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 94d234686a..109f00c9ae 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -259,9 +259,10 @@ class ViewerManifest(LLManifest): global CHANNEL_VENDOR_BASE channel_type=self.channel_type() if channel_type == 'release': - return CHANNEL_VENDOR_BASE + app_suffix='Viewer' else: - return CHANNEL_VENDOR_BASE + ' ' + self.channel_variant() + app_suffix=self.channel_variant() + return CHANNEL_VENDOR_BASE + ' ' + app_suffix def exec_name(self): return "SecondLifeViewer" -- cgit v1.3 From aba287fd0cb0702292651234e87c441779b40e2f Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 7 Jan 2026 20:44:32 +0200 Subject: #5220 do not save 'Highlight Transparent Probes' setting between sessions --- indra/newview/app_settings/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index fe31a00ba3..611c6932e2 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -9058,7 +9058,7 @@ Comment Show reflection probes in the transparency debug view Persist - 1 + 0 Type Boolean Value -- cgit v1.3 From b26f62eb0ce72b9cdd83296e87ba1954ee1b8b04 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 7 Jan 2026 23:53:15 +0200 Subject: #4991 Fix showing incorrect alpha Due to _isAlpha alpha was considered as blend even if only one texture had an alpha and was set to anything but blend. --- indra/newview/llpanelface.cpp | 32 +++++++++++++++++++++++--------- indra/newview/llpanelface.h | 2 +- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index de8ab95dee..bcb51b22ca 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -1201,7 +1201,7 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/) // See if that's been overridden by a material setting for same... // - LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode, mIsAlpha); + LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode); // it is invalid to have any alpha mode other than blend if transparency is greater than zero ... // Want masking? Want emissive? Tough! You get BLEND! @@ -1211,6 +1211,12 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/) alpha_mode = mIsAlpha ? alpha_mode : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; mComboAlphaMode->getSelectionInterface()->selectNthItem(alpha_mode); + mComboAlphaMode->setTentative(!identical_alpha_mode); + if (!identical_alpha_mode) + { + std::string multiple = LLTrans::getString("multiple_textures"); + mComboAlphaMode->setLabel(multiple); + } updateAlphaControls(); mExcludeWater &= (LLMaterial::DIFFUSE_ALPHA_MODE_BLEND == alpha_mode); @@ -5484,32 +5490,40 @@ void LLPanelFace::LLSelectedTEMaterial::getMaxNormalRepeats(F32& repeats, bool& identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &max_norm_repeats_func, repeats); } -void LLPanelFace::LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical, bool diffuse_texture_has_alpha) +void LLPanelFace::LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical) { struct LLSelectedTEGetDiffuseAlphaMode : public LLSelectedTEGetFunctor { - LLSelectedTEGetDiffuseAlphaMode() : _isAlpha(false) {} - LLSelectedTEGetDiffuseAlphaMode(bool diffuse_texture_has_alpha) : _isAlpha(diffuse_texture_has_alpha) {} + LLSelectedTEGetDiffuseAlphaMode() {} virtual ~LLSelectedTEGetDiffuseAlphaMode() {} U8 get(LLViewerObject* object, S32 face) { - U8 diffuse_mode = _isAlpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; - LLTextureEntry* tep = object->getTE(face); if (tep) { LLMaterial* mat = tep->getMaterialParams().get(); if (mat) { - diffuse_mode = mat->getDiffuseAlphaMode(); + return mat->getDiffuseAlphaMode(); + } + } + + bool has_alpha = false; + LLViewerTexture* image = object->getTEImage(face); + if (image) + { + LLGLenum format = image->getPrimaryFormat(); + if (format == GL_RGBA || format == GL_ALPHA) + { + has_alpha = true; } } + U8 diffuse_mode = has_alpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; return diffuse_mode; } - bool _isAlpha; // whether or not the diffuse texture selected contains alpha information - } get_diff_mode(diffuse_texture_has_alpha); + } get_diff_mode; identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &get_diff_mode, diffuse_alpha_mode); } diff --git a/indra/newview/llpanelface.h b/indra/newview/llpanelface.h index 63fee6bab8..82790ac54b 100644 --- a/indra/newview/llpanelface.h +++ b/indra/newview/llpanelface.h @@ -652,7 +652,7 @@ public: static void getCurrent(LLMaterialPtr& material_ptr, bool& identical_material); static void getMaxSpecularRepeats(F32& repeats, bool& identical); static void getMaxNormalRepeats(F32& repeats, bool& identical); - static void getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical, bool diffuse_texture_has_alpha); + static void getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical); static void selectionNormalScaleAutofit(LLPanelFace* panel_face, F32 repeats_per_meter); static void selectionSpecularScaleAutofit(LLPanelFace* panel_face, F32 repeats_per_meter); -- cgit v1.3 From db014c8fc5249dd4067f769d2249382c6991db76 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 7 Jan 2026 23:12:25 +0200 Subject: #5232 Crash on mHoverItem in context menu --- indra/llui/llmenugl.cpp | 14 ++++++++++++++ indra/llui/llmenugl.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 2ca2454040..6ba31c251e 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -4404,3 +4404,17 @@ bool LLContextMenu::addChild(LLView* view, S32 tab_group) return addContextChild(view, tab_group); } +void LLContextMenu::deleteAllChildren() +{ + mHoverItem = nullptr; + LLMenuGL::deleteAllChildren(); +} + +void LLContextMenu::removeChild(LLView* ctrl) +{ + if (ctrl == mHoverItem) + { + mHoverItem = nullptr; + } + LLMenuGL::removeChild(ctrl); +} diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index eacf2c59d4..bca0a731fc 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -730,6 +730,8 @@ public: virtual bool handleRightMouseUp ( S32 x, S32 y, MASK mask ); virtual bool addChild (LLView* view, S32 tab_group = 0); + /*virtual*/ void deleteAllChildren(); + /*virtual*/ void removeChild(LLView* ctrl); LLHandle getHandle() { return getDerivedHandle(); } -- cgit v1.3 From 31e909870b316b7b4f36396357f70e44c724e608 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 8 Jan 2026 00:30:00 +0200 Subject: #5232 Crash in handleMessage when trying to access llsd's content Crash at LLSD::Impl::assign --- indra/newview/lleventpoll.cpp | 58 +++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/indra/newview/lleventpoll.cpp b/indra/newview/lleventpoll.cpp index de3752d879..f1b46f0533 100644 --- a/indra/newview/lleventpoll.cpp +++ b/indra/newview/lleventpoll.cpp @@ -56,6 +56,7 @@ namespace Details private: void eventPollCoro(std::string url); + void handleMessage(const std::string& msg_name, const LLSD& body); void handleMessage(const LLSD &content); bool mDone; @@ -95,21 +96,23 @@ namespace Details mSenderIp = sender.getIPandPort(); } + void LLEventPollImpl::handleMessage(const std::string &msg_name, const LLSD &body) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; + LLSD message; + message["sender"] = mSenderIp; + message["body"] = body; + + LLMessageSystem::dispatch(msg_name, message); + } + void LLEventPollImpl::handleMessage(const LLSD& content) { LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; std::string msg_name = content["message"].asString(); LLSD message; - try - { - message["sender"] = mSenderIp; - message["body"] = content["body"]; - } - catch (std::bad_alloc&) - { - LLError::LLUserWarningMsg::showOutOfMemory(); - LL_ERRS("LLCoros") << "Bad memory allocation on message: " << msg_name << LL_ENDL; - } + message["sender"] = mSenderIp; + message["body"] = content["body"]; LLMessageSystem::dispatch(msg_name, message); } @@ -194,7 +197,7 @@ namespace Details break; } - LLSD httpResults = result["http_result"]; + LLSD &httpResults = result["http_result"]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) @@ -299,7 +302,7 @@ namespace Details } acknowledge = result["id"]; - LLSD events = result["events"]; + LLSD &events = result["events"]; if (acknowledge.isUndefined()) { @@ -310,20 +313,37 @@ namespace Details LL_DEBUGS("LLEventPollImpl") << " <" << counter << "> " << events.size() << "events (id " << acknowledge << ")" << LL_ENDL; - LLSD::array_const_iterator i = events.beginArray(); - LLSD::array_const_iterator end = events.endArray(); + LLSD::array_iterator i = events.beginArray(); + LLSD::array_iterator end = events.endArray(); for (; i != end; ++i) { if (i->has("message")) { if (main_queue) - { // shuttle to a sensible spot in the main thread instead + { + // Shuttle copy to a sensible spot in the main thread instead // of wherever this coroutine happens to be executing - const LLSD& msg = *i; - main_queue->post([this, msg]() + + LL::WorkQueue::Work work; + { + // LLSD is too smart for it's own good and may act like a smart + // pointer for the content of (*i), so instead of passing (*i) + // pass a prepared name and move ownership of "body", + // as we are not going to need "body" anywhere else. + std::string msg_name = (*i)["message"].asString(); + + // WARNING: This is a shallow copy! + // If something still retains the data (like in httpAdapter?) this might still + // result in a crash, if it does appear to be the case, make a deep copy or + // convert data to string and pass that string. + const LLSD body = (*i)["body"]; + (*i)["body"].clear(); + work = [this, msg_name, body]() { - handleMessage(msg); - }); + handleMessage(msg_name, body); + }; + } + main_queue->post(work); } else { -- cgit v1.3 From d718b2e5bd7e94660a1104d467a69d2714f12f0c Mon Sep 17 00:00:00 2001 From: Rye Date: Fri, 9 Jan 2026 14:57:39 -0500 Subject: secondlife/viewer#5083 Fix external editor default open handling failing to work on mac and windows --- indra/newview/llexternaleditor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp index 193a42f9f6..c3aad4ee65 100644 --- a/indra/newview/llexternaleditor.cpp +++ b/indra/newview/llexternaleditor.cpp @@ -46,9 +46,13 @@ LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env { LL_INFOS() << "Editor command is empty or not set, falling back to OS open handler" << LL_ENDL; #if LL_WINDOWS - static const std::string os_cmd = "%SystemRoot%\\explorer.exe \"%s\""; + std::string os_cmd = LLStringUtil::getenv("SystemRoot", ""); + if (!os_cmd.empty()) + { + os_cmd.append("\\explorer.exe \"%s\""); + } #elif LL_DARWIN - static const std::string os_cmd = "/usr/bin/open \"%s\""; + static const std::string os_cmd = "/usr/bin/open -t \"%s\""; #elif LL_LINUX static const std::string os_cmd = "/usr/bin/xdg-open \"%s\""; #endif -- cgit v1.3 From 7d30aea27edffd5793660fae66dcc500e8d97a0a Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:57:12 +0200 Subject: #3612 "Copy SLURL" from Favorites bar not working #3 --- indra/newview/llinventorybridge.cpp | 9 ++++++++- indra/newview/llinventorygallerymenu.cpp | 21 ++++++++++++++------- indra/newview/lllandmarkactions.cpp | 9 ++++++++- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 848f28f933..aa884b8e9f 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -1860,7 +1860,14 @@ void LLItemBridge::performAction(LLInventoryModel* model, std::string action) { LLVector3d global_pos; landmark->getGlobalPos(global_pos); - LLLandmarkActions::getSLURLfromPosGlobal(global_pos, ©_slurl_to_clipboard_callback_inv, true); + if (!global_pos.isExactlyZero()) + { + LLLandmarkActions::getSLURLfromPosGlobal(global_pos, ©_slurl_to_clipboard_callback_inv, true); + } + else + { + LLNotificationsUtil::add("LandmarkLocationUnknown"); + } } } } diff --git a/indra/newview/llinventorygallerymenu.cpp b/indra/newview/llinventorygallerymenu.cpp index fe007a78cd..2576da0a75 100644 --- a/indra/newview/llinventorygallerymenu.cpp +++ b/indra/newview/llinventorygallerymenu.cpp @@ -338,14 +338,21 @@ void LLInventoryGalleryContextMenu::doToSelected(const LLSD& userdata) { LLVector3d global_pos; landmark->getGlobalPos(global_pos); - boost::function copy_slurl_to_clipboard_cb = [](const std::string& slurl) + if (!global_pos.isExactlyZero()) { - gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); - LLSD args; - args["SLURL"] = slurl; - LLNotificationsUtil::add("CopySLURL", args); - }; - LLLandmarkActions::getSLURLfromPosGlobal(global_pos, copy_slurl_to_clipboard_cb, true); + boost::function copy_slurl_to_clipboard_cb = [](const std::string& slurl) + { + gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); + LLSD args; + args["SLURL"] = slurl; + LLNotificationsUtil::add("CopySLURL", args); + }; + LLLandmarkActions::getSLURLfromPosGlobal(global_pos, copy_slurl_to_clipboard_cb, true); + } + else + { + LLNotificationsUtil::add("LandmarkLocationUnknown"); + } }; LLLandmark* landmark = LLLandmarkActions::getLandmark(mUUIDs.front(), copy_slurl_cb); if (landmark) diff --git a/indra/newview/lllandmarkactions.cpp b/indra/newview/lllandmarkactions.cpp index 73425e9f4c..741d0622c5 100644 --- a/indra/newview/lllandmarkactions.cpp +++ b/indra/newview/lllandmarkactions.cpp @@ -385,7 +385,14 @@ void LLLandmarkActions::copySLURLtoClipboard(const LLUUID& landmarkInventoryItem { LLVector3d global_pos; landmark->getGlobalPos(global_pos); - LLLandmarkActions::getSLURLfromPosGlobal(global_pos,©_slurl_to_clipboard_callback,true); + if (!global_pos.isExactlyZero()) + { + LLLandmarkActions::getSLURLfromPosGlobal(global_pos, ©_slurl_to_clipboard_callback, true); + } + else + { + LLNotificationsUtil::add("LandmarkLocationUnknown"); + } } } -- cgit v1.3 From 6fb4efe6417290f5e08c1a3e1e383046893dd59b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 15 Jan 2026 01:36:40 +0200 Subject: #5086 Update links from settings to https --- indra/newview/app_settings/settings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 611c6932e2..a0a47216f5 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -641,7 +641,7 @@ Type String Value - http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/vawp/index.html + https://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/vawp/index.html AvatarBakedTextureUploadTimeout @@ -2467,7 +2467,7 @@ Type String Value - http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/guide.html + https://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/guide.html DisableCameraConstraints @@ -2775,7 +2775,7 @@ Type String Value - http://events.[GRID]/viewer/embed/event/[EVENT_ID] + https://events.[GRID]/viewer/embed/event/[EVENT_ID] MainWorkTime @@ -3432,7 +3432,7 @@ Type String Value - http://viewer-help.secondlife.com/[LANGUAGE]/[CHANNEL]/[VERSION]/[TOPIC][DEBUG_MODE] + https://viewer-help.secondlife.com/[LANGUAGE]/[CHANNEL]/[VERSION]/[TOPIC][DEBUG_MODE] HowToHelpURL @@ -3443,7 +3443,7 @@ Type String Value - http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/howto/index.html + https://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/howto/index.html HomeSidePanelURL @@ -3476,7 +3476,7 @@ Type String Value - http://guidebooks.secondlife.io/welcome/index.html + https://guidebooks.secondlife.io/welcome/index.html HighResSnapshot @@ -4059,7 +4059,7 @@ Type String Value - http://wiki.secondlife.com/wiki/[LSL_STRING] + https://wiki.secondlife.com/wiki/[LSL_STRING] LSLFontSizeName @@ -14710,7 +14710,7 @@ Type String Value - http://common-flash-secondlife-com.s3.amazonaws.com/viewer/v2.6/agni/404.html + https://common-flash-secondlife-com.s3.amazonaws.com/viewer/v2.6/agni/404.html OpenIMOnVoice -- cgit v1.3 From c2c4489dbb690615e071aa463a4ad2af02892274 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Sat, 15 Feb 2025 09:51:22 +0200 Subject: #2639 Reapplied GPU benchmark fix First test returns quarter to a half the throughput, do two tests. May be caused by driver, may be some 'energy saving', but not important enough to spend time investingating. It was working the same way prior to ExtraFPS, but viewer was running an extra CPU test that 'preheated' the system. Also increasing minimum throughput as numerous new features, like mirrors and pbr were added and requirements are now higher. --- indra/newview/app_settings/settings.xml | 2 +- indra/newview/llglsandbox.cpp | 66 +++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index a0a47216f5..b9ea0277f8 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -7335,7 +7335,7 @@ Type F32 Value - 32.0 + 48.0 RenderCPUBasis diff --git a/indra/newview/llglsandbox.cpp b/indra/newview/llglsandbox.cpp index 112008172e..5484ce6276 100644 --- a/indra/newview/llglsandbox.cpp +++ b/indra/newview/llglsandbox.cpp @@ -903,6 +903,39 @@ private: }; +F32 shader_timer_benchmark(std::vector & dest, TextureHolder & texHolder, U32 textures_count, LLVertexBuffer * buff, F32 &seconds) +{ + // run GPU timer benchmark + + //number of samples to take + const S32 samples = 64; + + { + ShaderProfileHelper initProfile; + dest[0].bindTarget(); + gBenchmarkProgram.bind(); + for (S32 c = 0; c < samples; ++c) + { + for (U32 i = 0; i < textures_count; ++i) + { + texHolder.bind(i); + buff->setBuffer(); + buff->drawArrays(LLRender::TRIANGLES, 0, 3); + } + } + gBenchmarkProgram.unbind(); + dest[0].flush(); + } + + F32 ms = gBenchmarkProgram.mTimeElapsed / 1000000.f; + seconds = ms / 1000.f; + + F64 samples_drawn = (F64)gBenchmarkProgram.mSamplesDrawn; + F64 gpixels_drawn = samples_drawn / 1000000000.0; + F32 samples_sec = (F32)(gpixels_drawn / seconds); + return samples_sec * 4; // 4 bytes per sample +} + //----------------------------------------------------------------------------- // gpu_benchmark() // returns measured memory bandwidth of GPU in gigabytes per second @@ -944,9 +977,6 @@ F32 gpu_benchmark() //number of textures const U32 count = 32; - //number of samples to take - const S32 samples = 64; - //time limit, allocation operations shouldn't take longer then 30 seconds, same for actual benchmark. const F32 time_limit = 30; @@ -1036,33 +1066,15 @@ F32 gpu_benchmark() LLGLSLShader::unbind(); - // run GPU timer benchmark - { - ShaderProfileHelper initProfile; - dest[0].bindTarget(); - gBenchmarkProgram.bind(); - for (S32 c = 0; c < samples; ++c) - { - for (U32 i = 0; i < count; ++i) - { - texHolder.bind(i); - buff->setBuffer(); - buff->drawArrays(LLRender::TRIANGLES, 0, 3); - } - } - gBenchmarkProgram.unbind(); - dest[0].flush(); - } + // run GPU timer benchmark twice + F32 seconds = 0; + F32 gbps = shader_timer_benchmark(dest, texHolder, count, buff.get(), seconds); - F32 ms = gBenchmarkProgram.mTimeElapsed/1000000.f; - F32 seconds = ms/1000.f; + LL_INFOS("Benchmark") << "Memory bandwidth, 1st run is " << llformat("%.3f", gbps) << " GB/sec according to ARB_timer_query, total time " << seconds << " seconds" << LL_ENDL; - F64 samples_drawn = (F64)gBenchmarkProgram.mSamplesDrawn; - F64 gpixels_drawn = samples_drawn / 1000000000.0; - F32 samples_sec = (F32)(gpixels_drawn/seconds); - F32 gbps = samples_sec*4; // 4 bytes per sample + gbps = shader_timer_benchmark(dest, texHolder, count, buff.get(), seconds); - LL_INFOS("Benchmark") << "Memory bandwidth is " << llformat("%.3f", gbps) << " GB/sec according to ARB_timer_query, total time " << seconds << " seconds" << LL_ENDL; + LL_INFOS("Benchmark") << "Memory bandwidth, final run is " << llformat("%.3f", gbps) << " GB/sec according to ARB_timer_query, total time " << seconds << " seconds" << LL_ENDL; return gbps; } -- cgit v1.3 From 4e705f8886272d02281f2d0bd7cbed7ad7080c00 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 15 Jan 2026 23:30:25 +0200 Subject: #5275 Optimize hasParcelLandmark Implemented result caching. hasParcelLandmark can get repeatedly called when performing operations on landmarks en masse, which was causing repeated inventory searches and leads to stalls with large collections of landmarks. --- indra/newview/lllandmarkactions.cpp | 43 +++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/indra/newview/lllandmarkactions.cpp b/indra/newview/lllandmarkactions.cpp index 741d0622c5..020eab381f 100644 --- a/indra/newview/lllandmarkactions.cpp +++ b/indra/newview/lllandmarkactions.cpp @@ -197,12 +197,51 @@ bool LLLandmarkActions::landmarkAlreadyExists() //static bool LLLandmarkActions::hasParcelLandmark() { + static LLUUID sLastItemID; + static S32 sLastFrame = -1; + if (sLastItemID.notNull()) + { + LLInventoryItem* item = gInventory.getItem(sLastItemID); + if (item) + { + LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID()); + if (landmark) + { + LLVector3d landmark_global_pos; + if (landmark->getGlobalPos(landmark_global_pos) + && LLViewerParcelMgr::getInstance()->inAgentParcel(landmark_global_pos)) + { + return true; + } + } + } + // Cached landmark does not match current parcel anymore, + // repeat inventory search to find a replacement landmark + // or to make sure there are none. + sLastItemID.setNull(); + sLastFrame = -1; + } + + if (sLastFrame == LLFrameTimer::getFrameCount()) + { + // Ideally this should also check parcel change and landmark additions, + // not just frame change. + // But should be sufficient to check only frame as this is used + // after inventory and parcel operations. + return false; + } + sLastFrame = LLFrameTimer::getFrameCount(); + LLFirstAgentParcelLandmark get_first_agent_landmark; LLInventoryModel::cat_array_t cats; LLInventoryModel::item_array_t items; fetch_landmarks(cats, items, get_first_agent_landmark); - return !items.empty(); - + if (!items.empty()) + { + sLastItemID = items[0]->getUUID(); + return true; + } + return false; } // *TODO: This could be made more efficient by only fetching the FIRST -- cgit v1.3 From 026d5a5bb721eabe40ca735544bdc6debab2710a Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 15 Jan 2026 23:43:16 +0200 Subject: #5275 Make landmark search by position cheaper by stopping after first item was found. --- indra/newview/lllandmarkactions.cpp | 44 +++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/indra/newview/lllandmarkactions.cpp b/indra/newview/lllandmarkactions.cpp index 020eab381f..b9d1c7ba18 100644 --- a/indra/newview/lllandmarkactions.cpp +++ b/indra/newview/lllandmarkactions.cpp @@ -80,6 +80,40 @@ public: } }; +class LLFetchFirstLandmarkByPos : public LLInventoryCollectFunctor +{ +private: + LLVector3d mPos; + bool mFound = false; +public: + LLFetchFirstLandmarkByPos(const LLVector3d& pos) : + mPos(pos), mFound(false) + { + } + + /*virtual*/ bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + if (mFound || !item || item->getType() != LLAssetType::AT_LANDMARK) + return false; + + LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID()); + if (!landmark) // the landmark not been loaded yet + return false; + + LLVector3d landmark_global_pos; + if (!landmark->getGlobalPos(landmark_global_pos)) + return false; + //we have to round off each coordinates to compare positions properly + mFound = ll_round(mPos.mdV[VX]) == ll_round(landmark_global_pos.mdV[VX]) + && ll_round(mPos.mdV[VY]) == ll_round(landmark_global_pos.mdV[VY]) + && ll_round(mPos.mdV[VZ]) == ll_round(landmark_global_pos.mdV[VZ]); + return mFound; + } + + // only care about first found landmark, so stop when found + /*virtual*/ bool exceedsLimit() { return mFound; } +}; + class LLFetchLandmarksByName : public LLInventoryCollectFunctor { private: @@ -155,6 +189,9 @@ public: mFounded = LLViewerParcelMgr::getInstance()->inAgentParcel(landmark_global_pos); return mFounded; } + + // only care about first found landmark, so stop when found + /*virtual*/ bool exceedsLimit() { return mFounded; } }; static void fetch_landmarks(LLInventoryModel::cat_array_t& cats, @@ -244,15 +281,14 @@ bool LLLandmarkActions::hasParcelLandmark() return false; } -// *TODO: This could be made more efficient by only fetching the FIRST -// landmark that meets the criteria LLViewerInventoryItem* LLLandmarkActions::findLandmarkForGlobalPos(const LLVector3d &pos) { // Determine whether there are landmarks pointing to the current parcel. + // Will stop after first found landmark. LLInventoryModel::cat_array_t cats; LLInventoryModel::item_array_t items; - LLFetchlLandmarkByPos is_current_pos_landmark(pos); - fetch_landmarks(cats, items, is_current_pos_landmark); + LLFetchFirstLandmarkByPos get_landmark_from_pos(pos); + fetch_landmarks(cats, items, get_landmark_from_pos); if(items.empty()) { -- cgit v1.3 From 1d4a31c12473ba228e06a1360602af668e3e3d2c Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:27:04 +0200 Subject: #4267 Slight logging improvement for mute list --- indra/newview/llmutelist.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index 9157e34833..f47a8cd241 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -803,6 +803,10 @@ void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) std::string unclean_filename; msg->getStringFast(_PREHASH_MuteData, _PREHASH_Filename, unclean_filename); std::string filename = LLDir::getScrubbedFileName(unclean_filename); + if (filename.empty()) + { + LL_WARNS() << "Received empty mute list filename." << LL_ENDL; + } LLMuteList* mute_list = getInstance(); mute_list->mLoadState = ML_REQUESTED; @@ -835,16 +839,16 @@ void LLMuteList::processUseCachedMuteList(LLMessageSystem* msg, void**) void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_status) { - LL_INFOS() << "LLMuteList::processMuteListFile()" << LL_ENDL; - std::string* local_filename_and_path = (std::string*)user_data; if(local_filename_and_path && !local_filename_and_path->empty() && (error_code == 0)) { + LL_INFOS() << "Received mute list from server" << LL_ENDL; LLMuteList::getInstance()->loadFromFile(*local_filename_and_path); LLFile::remove(*local_filename_and_path); } else { + LL_INFOS() << "LLMuteList xfer failed with code " << error_code << LL_ENDL; LLMuteList::getInstance()->mLoadState = ML_FAILED; } delete local_filename_and_path; -- cgit v1.3 From 6579ff6282f86ddc2ae235f421c5ccdda4871a78 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:56:17 +0200 Subject: #5084 Adjust watchdog to avoid false-positives And increase allowed time to be more in tune with disconnects. --- indra/newview/app_settings/settings.xml | 2 +- indra/newview/llappviewer.cpp | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index b9ea0277f8..54ac53fd52 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4501,7 +4501,7 @@ Type F32 Value - 30.0 + 60.0 MapScale diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index e711064455..35edbf089e 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1346,6 +1346,7 @@ bool LLAppViewer::frame() bool LLAppViewer::doFrame() { + resumeMainloopTimeout("Main:doFrameStart"); #ifdef LL_DISCORD { LL_PROFILE_ZONE_NAMED("discord_callbacks"); @@ -1659,6 +1660,11 @@ bool LLAppViewer::doFrame() LL_INFOS() << "Exiting main_loop" << LL_ENDL; } }LLPerfStats::StatsRecorder::endFrame(); + + // Not viewer's fault if something outside frame + // pauses viewer (ex: macOS doesn't call oneFrame), + // so stop tracking on exit. + pauseMainloopTimeout(); LL_PROFILER_FRAME_END; return ! LLApp::isRunning(); @@ -5903,7 +5909,8 @@ F32 LLAppViewer::getMainloopTimeoutSec() const if (LLStartUp::getStartupState() == STATE_STARTED && gAgent.getTeleportState() == LLAgent::TELEPORT_NONE) { - static LLCachedControl mainloop_started(gSavedSettings, "MainloopTimeoutStarted", 30.f); + // consider making this value match 'disconnected' timout. + static LLCachedControl mainloop_started(gSavedSettings, "MainloopTimeoutStarted", 60.f); return mainloop_started(); } else -- cgit v1.3 From ca6601cbb8d4ef9828466c8d1316ae28d0c7b7b8 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Tue, 20 Jan 2026 21:17:35 +0200 Subject: #5304 fix UI inconsistencies related to maturity setting in Legacy Search floater --- indra/newview/llfloaterpreference.cpp | 16 ++++++++++++++++ indra/newview/llpaneldirbrowser.cpp | 24 ------------------------ indra/newview/llpaneldirevents.cpp | 11 +++++------ indra/newview/llpaneldirland.cpp | 14 -------------- indra/newview/llpaneldirplaces.cpp | 11 +++++------ 5 files changed, 26 insertions(+), 50 deletions(-) diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index c5c1e01538..6e27fd694a 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -1735,6 +1735,22 @@ void LLFloaterPreference::onChangeMaturity() || sim_access == SIM_ACCESS_ADULT); getChild("rating_icon_adult")->setVisible(sim_access == SIM_ACCESS_ADULT); + + // Update Legacy Search maturity settings + bool can_access_mature = gAgent.canAccessMature(); + bool can_access_adult = gAgent.canAccessAdult(); + if (!can_access_mature) + { + gSavedSettings.setBOOL("ShowMatureSims", false); + gSavedSettings.setBOOL("ShowMatureLand", false); + gSavedSettings.setBOOL("ShowMatureClassifieds", false); + } + if (!can_access_adult) + { + gSavedSettings.setBOOL("ShowAdultSims", false); + gSavedSettings.setBOOL("ShowAdultLand", false); + gSavedSettings.setBOOL("ShowAdultClassifieds", false); + } } void LLFloaterPreference::onChangeComplexityMode(const LLSD& newvalue) diff --git a/indra/newview/llpaneldirbrowser.cpp b/indra/newview/llpaneldirbrowser.cpp index 8c981cad55..69329c4ea4 100644 --- a/indra/newview/llpaneldirbrowser.cpp +++ b/indra/newview/llpaneldirbrowser.cpp @@ -559,9 +559,6 @@ void LLPanelDirBrowser::processDirEventsReply(LLMessageSystem* msg, void**) LLUUID owner_id; std::string name; std::string date; - bool show_pg = gSavedSettings.getBOOL("ShowPGEvents"); - bool show_mature = gSavedSettings.getBOOL("ShowMatureEvents"); - bool show_adult = gSavedSettings.getBOOL("ShowAdultEvents"); msg->getUUID("AgentData", "AgentID", agent_id); msg->getUUID("QueryData", "QueryID", query_id ); @@ -618,27 +615,6 @@ void LLPanelDirBrowser::processDirEventsReply(LLMessageSystem* msg, void**) LL_WARNS() << "skipped event due to owner_id null, event_id " << event_id << LL_ENDL; continue; } - - // skip events that don't match the flags - // there's no PG flag, so we make sure neither adult nor mature is set - if (((event_flags & (EVENT_FLAG_ADULT | EVENT_FLAG_MATURE)) == EVENT_FLAG_NONE) && !show_pg) - { - //llwarns << "Skipped pg event because we're not showing pg, event_id " << event_id << llendl; - continue; - } - - if ((event_flags & EVENT_FLAG_MATURE) && !show_mature) - { - //llwarns << "Skipped mature event because we're not showing mature, event_id " << event_id << llendl; - continue; - } - - if ((event_flags & EVENT_FLAG_ADULT) && !show_adult) - { - //llwarns << "Skipped adult event because we're not showing adult, event_id " << event_id << llendl; - continue; - } - LLSD content; content["type"] = EVENT_CODE; diff --git a/indra/newview/llpaneldirevents.cpp b/indra/newview/llpaneldirevents.cpp index 227ed877cd..7ac1229637 100644 --- a/indra/newview/llpaneldirevents.cpp +++ b/indra/newview/llpaneldirevents.cpp @@ -143,18 +143,17 @@ void LLPanelDirEvents::performQueryOrDelete(U32 event_id) static LLUICachedControl incpg("ShowPGEvents", true); static LLUICachedControl incmature("ShowMatureEvents", false); static LLUICachedControl incadult("ShowAdultEvents", false); + if (!(incpg || incmature || incadult)) + { + LLNotificationsUtil::add("NoContentToSearch"); + return; + } U32 scope = DFQ_DATE_EVENTS; if (incpg) scope |= DFQ_INC_PG; if (incmature && gAgent.canAccessMature()) scope |= DFQ_INC_MATURE; if (incadult && gAgent.canAccessAdult()) scope |= DFQ_INC_ADULT; - if ( !( scope & (DFQ_INC_PG | DFQ_INC_MATURE | DFQ_INC_ADULT ))) - { - LLNotificationsUtil::add("NoContentToSearch"); - return; - } - setupNewSearch(); std::ostringstream params; diff --git a/indra/newview/llpaneldirland.cpp b/indra/newview/llpaneldirland.cpp index 53c58d8fa9..f85a8b948e 100644 --- a/indra/newview/llpaneldirland.cpp +++ b/indra/newview/llpaneldirland.cpp @@ -62,20 +62,6 @@ bool LLPanelDirLand::postBuild() childSetValue("type", gSavedSettings.getString("FindLandType")); - bool adult_enabled = gAgent.canAccessAdult(); - bool mature_enabled = gAgent.canAccessMature(); - childSetVisible("incpg", true); - if (!mature_enabled) - { - childSetValue("incmature", false); - childDisable("incmature"); - } - if (!adult_enabled) - { - childSetValue("incadult", false); - childDisable("incadult"); - } - childSetCommitCallback("pricecheck", onCommitPrice, this); childSetCommitCallback("areacheck", onCommitArea, this); diff --git a/indra/newview/llpaneldirplaces.cpp b/indra/newview/llpaneldirplaces.cpp index 2d54566038..3501baf697 100644 --- a/indra/newview/llpaneldirplaces.cpp +++ b/indra/newview/llpaneldirplaces.cpp @@ -121,6 +121,11 @@ void LLPanelDirPlaces::performQuery() static LLUICachedControl inc_pg("ShowPGSims", true); static LLUICachedControl inc_mature("ShowMatureSims", false); static LLUICachedControl inc_adult("ShowAdultSims", false); + if (!(inc_pg || inc_mature || inc_adult)) + { + LLNotificationsUtil::add("NoContentToSearch"); + return; + } if (inc_pg) { @@ -137,12 +142,6 @@ void LLPanelDirPlaces::performQuery() flags |= DFQ_INC_ADULT; } - if (0x0 == flags) - { - LLNotificationsUtil::add("NoContentToSearch"); - return; - } - queryCore(query_string, category, flags); } -- cgit v1.3 From e8a09bbab68716800ae0d3f5725bbd0d0a956d73 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:29:32 +0200 Subject: #5086 Restore guidebook to use http --- indra/newview/app_settings/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 54ac53fd52..9058220820 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -3476,7 +3476,7 @@ Type String Value - https://guidebooks.secondlife.io/welcome/index.html + http://guidebooks.secondlife.io/welcome/index.html HighResSnapshot -- cgit v1.3 From 10201dbbd36d70a6a291c4cb64c1044d99cc46a5 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 23 Jan 2026 10:54:44 -0800 Subject: Fix failure to reconnect after disconnect and occasional dropout issue (#5322) * Fix failure to reconnect after disconnect and occasional dropout issue We were occasionally seeing dropouts which may have been caused by ICE renegotiate requests. The code is there to reconnect in that case, but there were a few bugs, some of which were likely due to the webrtc upgrade. Also, we were seeing failures to reconnect after voice server restart. There were some issues with the PTT button that came up after the above issue was fixed. * Added a clarification as part of CR * We need to set mute state for p2p/adhoc/group calls as well --- indra/llwebrtc/llwebrtc.cpp | 3 ++- indra/newview/llvoicewebrtc.cpp | 14 ++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp index d1bae49784..8e08239ee6 100644 --- a/indra/llwebrtc/llwebrtc.cpp +++ b/indra/llwebrtc/llwebrtc.cpp @@ -814,6 +814,7 @@ LLWebRTCPeerConnectionImpl::~LLWebRTCPeerConnectionImpl() { mSignalingObserverList.clear(); mDataObserverList.clear(); + mPeerConnectionFactory.release(); if (mPendingJobs > 0) { RTC_LOG(LS_ERROR) << __FUNCTION__ << "Destroying a connection that has " << std::to_string(mPendingJobs) << " unfinished jobs that might cause workers to crash"; @@ -877,7 +878,6 @@ void LLWebRTCPeerConnectionImpl::terminate() } mPendingJobs--; }); - mPeerConnectionFactory.release(); } void LLWebRTCPeerConnectionImpl::setSignalingObserver(LLWebRTCSignalingObserver *observer) { mSignalingObserverList.emplace_back(observer); } @@ -1004,6 +1004,7 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti } webrtc::PeerConnectionInterface::RTCOfferAnswerOptions offerOptions; + this->AddRef(); // CreateOffer will deref this when it's done. Without this, the callbacks never get called. mPeerConnection->CreateOffer(this, offerOptions); mPendingJobs--; }); diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 3efcd763e3..80a0e3e5c0 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -2549,10 +2549,7 @@ void LLVoiceWebRTCConnection::OnRenegotiationNeeded() LL::WorkQueue::postMaybe(mMainQueue, [=, this] { LL_DEBUGS("Voice") << "Voice channel requires renegotiation." << LL_ENDL; - if (!mShutDown) - { - setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); - } + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); mCurrentStatus = LLVoiceClientStatusObserver::ERROR_UNKNOWN; }); } @@ -2898,9 +2895,10 @@ bool LLVoiceWebRTCConnection::connectionStateMachine() // this connection. // For spatial this connection will come up as muted, but will be set to the appropriate // value later on when we determine the regions we connect to. - if (!isSpatial()) + if (isSpatial()) { - mWebRTCAudioInterface->setMute(mMuted); + // we'll determine primary state later and set mute accordinly + mPrimary = false; } mWebRTCAudioInterface->setReceiveVolume(mSpeakerVolume); LLWebRTCVoiceClient::getInstance()->OnConnectionEstablished(mChannelID, mRegionID); @@ -2924,6 +2922,10 @@ bool LLVoiceWebRTCConnection::connectionStateMachine() LLWebRTCVoiceClient::getInstance()->updatePosition(); LLWebRTCVoiceClient::getInstance()->sendPositionUpdate(true); } + else + { + mWebRTCAudioInterface->setMute(mMuted); + } } break; } -- cgit v1.3 From e42ec63bfd7ada1087a00ca3ccc39c95679b7fab Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:51:27 +0200 Subject: #5046 Fix a long freeze when fetching inventory --- indra/llcommon/lleventcoro.cpp | 12 ++++++++++++ indra/llcommon/lleventcoro.h | 5 +++++ indra/newview/llaisapi.cpp | 7 ++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index e1fc4764f6..bb2cd4fb2e 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -137,6 +137,18 @@ void llcoro::suspendUntilTimeout(float seconds) suspendUntilEventOnWithTimeout(bogus, seconds, timedout); } +void llcoro::suspendUntilNextFrame() +{ + LLCoros::checkStop(); + LLCoros::TempStatus st("waiting for next frame"); + + // Listen for the next event on the "mainloop" event pump. + // Once per frame we get mainloop.post(newFrame); + LLEventPumpOrPumpName mainloop_pump("mainloop"); + // Wait for the next event (the event data is ignored). + suspendUntilEventOn(mainloop_pump); +} + namespace { diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h index 492563bb98..25a8a98e36 100644 --- a/indra/llcommon/lleventcoro.h +++ b/indra/llcommon/lleventcoro.h @@ -84,6 +84,11 @@ void suspend(); */ void suspendUntilTimeout(float seconds); +/** + * Yield control from a coroutine until the next mainloop's newFrame event. + */ +void suspendUntilNextFrame(); + /** * Post specified LLSD event on the specified LLEventPump, then suspend for a * response on specified other LLEventPump. This is more than mere diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp index 1da1647fe8..9c76f56ef3 100644 --- a/indra/newview/llaisapi.cpp +++ b/indra/newview/llaisapi.cpp @@ -1060,7 +1060,12 @@ void AISUpdate::checkTimeout() { if (mTimer.hasExpired()) { - llcoro::suspend(); + // If we are taking too long, don't starve other tasks, + // yield to mainloop. + // If we use normal suspend(), there will be a chance of + // waking up from other suspends, before main coro had + // a chance, so wait for a frame tick instead. + llcoro::suspendUntilNextFrame(); LLCoros::checkStop(); mTimer.setTimerExpirySec(AIS_EXPIRY_SECONDS); } -- cgit v1.3 From 2c0729cf07dd808070bc29319d30bee45240bc6a Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Wed, 6 Nov 2024 11:48:35 +0100 Subject: #2997 The 'Reset Skeleton' option is missing in the right-click menu --- indra/newview/llviewermenu.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index abaf813530..27185cb69f 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -6666,11 +6666,8 @@ class LLAvatarEnableResetSkeleton : public view_listener_t { bool handleEvent(const LLSD& userdata) { - if (LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject())) - { - return true; - } - return false; + LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + return obj && obj->getAvatar(); } }; -- cgit v1.3 From 3caf20f4c43173393c10b9cecbb266a4f7ff3702 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 28 Jan 2026 20:49:47 +0200 Subject: #2639 Fix benchmarked render level being too high for Apple Silicon After benchmark fixes, render level jumped from lowest to high+, cap it at med+ until shadows get fixed to have less impact --- indra/newview/llfeaturemanager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index e9bee93a19..c8692224f1 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -495,7 +495,9 @@ bool LLFeatureManager::loadGPUClass() { mGPUClass = GPU_CLASS_2; } - else if (gbps <= class1_gbps*4.f) + else if ((gbps <= class1_gbps*4.f) + // Cap silicon's GPUs at med+ as they have high throughput, low capability + || gGLManager.mIsApple) { mGPUClass = GPU_CLASS_3; } -- cgit v1.3 From b4a6af57a667d355e7347f1a3c13173b44b361ff Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Fri, 30 Jan 2026 06:51:22 +0200 Subject: #2997 Fix Reset Skeleton not working on animesh objects --- indra/newview/llviewermenu.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 27185cb69f..c9f10d845f 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -8712,6 +8712,12 @@ LLVOAvatar* find_avatar_from_object(LLViewerObject* object) } else if( !object->isAvatar() ) { + // Check for animesh objects (animated objects with a control avatar) + LLVOAvatar* avatar = object->getAvatar(); + if (avatar) + { + return avatar; + } object = NULL; } } -- cgit v1.3 From 1594fe9466bb2171910369af80d014d6c1c3a817 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Thu, 12 Feb 2026 16:08:42 -0800 Subject: In WebRTC.lib, Switch WASAPI playout to timer-driven mode with AUTOCONVERTPCM Use WAVEFORMATEXTENSIBLE and AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM to support devices whose native format differs from 48kHz stereo PCM, such as Bluetooth HFP endpoints (16kHz mono) and surround sound devices (5.1/7.1). Replace the event-driven render loop (AUDCLNT_STREAMFLAGS_EVENTCALLBACK) with a timer-driven polling loop to avoid a known issue where AUTOCONVERTPCM combined with EVENTCALLBACK causes the audio engine to stop signaling render events, resulting in premature thread termination. Also adds 192kHz to the supported sample rate list, uses the first closest-match format from IsFormatSupported as a fallback when no exact match is found, and applies the same changes to the recording path. --- autobuild.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index d58a785b6b..7333583415 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2633,11 +2633,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 43c5f93517794aeade550e4266b959d1f0cfcb7f + 72ed1f6d469a8ffaffd69be39b7af186d7c3b1d7 hash_algorithm sha1 url - https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.20-universal/webrtc-m137.7151.04.20-universal.17630578914-darwin64-17630578914.tar.zst + https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.22/webrtc-m137.7151.04.22.21966754211-darwin64-21966754211.tar.zst name darwin64 @@ -2647,11 +2647,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - efc5b176d878cfc16b8f82445d82ddb96815b6ab + b4d0c836d99491841c3816ff93bb2655a2817bd3 hash_algorithm sha1 url - https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.20-universal/webrtc-m137.7151.04.20-universal.17630578914-linux64-17630578914.tar.zst + https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.22/webrtc-m137.7151.04.22.21966754211-linux64-21966754211.tar.zst name linux64 @@ -2661,11 +2661,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 1e36f100de32c7c71325497a672fb1659b3f206d + ab2bddd77b1568b22b50ead13c1c33da94f4d59a hash_algorithm sha1 url - https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.20-universal/webrtc-m137.7151.04.20-universal.17630578914-windows64-17630578914.tar.zst + https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.22/webrtc-m137.7151.04.22.21966754211-windows64-21966754211.tar.zst name windows64 @@ -2678,7 +2678,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors copyright Copyright (c) 2011, The WebRTC project authors. All rights reserved. version - m137.7151.04.20-universal.17630578914 + m137.7151.04.22.21966754211 name webrtc vcs_branch -- cgit v1.3 From 60651640a3114dd2c6a8c0f880234921506ab207 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:10:45 +0200 Subject: #5084 Adjust Window's watchdog to only run after login like mainloop does --- indra/llwindow/llwindowwin32.cpp | 2 +- indra/newview/llappviewer.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 9d05d7e5a4..2bd9dd053c 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -436,7 +436,7 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool { if (!mWindowTimeout) { - mWindowTimeout = std::make_unique("mainloop"); + mWindowTimeout = std::make_unique("WindowThread"); // supposed to be executed within run(), // so no point checking if thread is alive resumeTimeout("TimeoutInit"); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 35edbf089e..93a0cd9d43 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -3200,7 +3200,6 @@ bool LLAppViewer::initWindow() app->createErrorMarker(LAST_EXEC_FROZE); } }); - gViewerWindow->getWindow()->initWatchdog(); } LLNotificationsUI::LLNotificationManager::getInstance(); @@ -5924,6 +5923,11 @@ void LLAppViewer::handleLoginComplete() { gLoggedInTime.start(); initMainloopTimeout("Mainloop Init"); + LLWindow* viewer_window = gViewerWindow->getWindow(); + if (viewer_window) // in case of a headless client + { + viewer_window->initWatchdog(); + } // Store some data to DebugInfo in case of a freeze. gDebugInfo["ClientInfo"]["Name"] = LLVersionInfo::instance().getChannel(); -- cgit v1.3 From 44e21aa718211cf34a59a036f2831c2e80fef8e1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:23:37 +0200 Subject: #5084 Adjust watchdog for bettet tracking of logout --- indra/newview/llappviewer.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 93a0cd9d43..9a421972e5 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1641,17 +1641,20 @@ bool LLAppViewer::doFrame() if (LLApp::isExiting()) { + pingMainloopTimeout("Main:qSnapshot"); // Save snapshot for next time, if we made it through initialization if (STATE_STARTED == LLStartUp::getStartupState()) { saveFinalSnapshot(); } + pingMainloopTimeout("Main:TerminateVoice"); if (LLVoiceClient::instanceExists()) { LLVoiceClient::getInstance()->terminate(); } + pingMainloopTimeout("Main:TerminatePump"); delete gServicePump; gServicePump = NULL; @@ -4179,6 +4182,7 @@ void LLAppViewer::requestQuit() return; } + pingMainloopTimeout("Main:qMetrics"); // Try to send metrics back to the grid metricsSend(!gDisconnected); @@ -4194,6 +4198,7 @@ void LLAppViewer::requestQuit() LLHUDManager::getInstance()->sendEffects(); effectp->markDead() ;//remove it. + pingMainloopTimeout("Main:qFloaters"); // Attempt to close all floaters that might be // editing things. if (gFloaterView) @@ -4202,6 +4207,7 @@ void LLAppViewer::requestQuit() gFloaterView->closeAllChildren(true); mClosingFloaters = true; } + pingMainloopTimeout("Main:qStats"); // Send preferences once, when exiting bool include_preferences = true; @@ -4209,6 +4215,7 @@ void LLAppViewer::requestQuit() gLogoutTimer.reset(); mQuitRequested = true; + pingMainloopTimeout("Main:LoggingOut"); } static bool finish_quit(const LLSD& notification, const LLSD& response) @@ -5489,6 +5496,7 @@ void LLAppViewer::outOfMemorySoftQuit() LLLFSThread::sLocal->pause(); gLogoutTimer.reset(); mQuitRequested = true; + destroyMainloopTimeout(); LLError::LLUserWarningMsg::showOutOfMemory(); } @@ -5905,6 +5913,11 @@ void LLAppViewer::pingMainloopTimeout(std::string_view state) F32 LLAppViewer::getMainloopTimeoutSec() const { + if (isQuitting() || mQuitRequested) + { + constexpr F32 QUITTING_SECONDS = 240.f; + return QUITTING_SECONDS; + } if (LLStartUp::getStartupState() == STATE_STARTED && gAgent.getTeleportState() == LLAgent::TELEPORT_NONE) { -- cgit v1.3