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