From 72f73ff62a6adef5caf5418a6c7d03a5c9867648 Mon Sep 17 00:00:00 2001 From: Maycon bekkers Date: Mon, 27 Apr 2026 15:16:31 -0300 Subject: #1681 Respect custom logfile during startup --- indra/newview/llappviewer.cpp | 86 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 6 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 46e64c2a4e..0b6ea72df4 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -66,6 +66,7 @@ #include "llconversationlog.h" #if LL_WINDOWS #include "lldxhardware.h" +#include #endif #include "lltexturestats.h" #include "lltrace.h" @@ -2298,6 +2299,12 @@ void errorHandler(const std::string& title_string, const std::string& message_st } } +namespace +{ + std::string getStartupLogFileName(); + std::string getOldLogFileName(const std::string& log_file); +} + void LLAppViewer::initLoggingAndGetLastDuration() { // @@ -2323,13 +2330,10 @@ void LLAppViewer::initLoggingAndGetLastDuration() else { // Remove the last ".old" log file. - std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, - "SecondLife.old"); + std::string log_file = getStartupLogFileName(); + std::string old_log_file = getOldLogFileName(log_file); LLFile::remove(old_log_file); - // Get name of the log file - std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, - "SecondLife.log"); /* * Before touching any log files, compute the duration of the last run * by comparing the ctime of the previous start marker file with the ctime @@ -2376,7 +2380,7 @@ void LLAppViewer::initLoggingAndGetLastDuration() // Rename current log file to ".old" LLFile::rename(log_file, old_log_file); - // Set the log file to SecondLife.log + // Set the log file. LLError::logToFile(log_file); LL_INFOS() << "Started logging to " << log_file << LL_ENDL; if (!duration_log_msg.empty()) @@ -2515,6 +2519,76 @@ namespace LLStringUtil::null, OSMB_OK); } + + std::string getStartupLogFileName() + { + if (LLControlVariable* user_log_file = gSavedSettings.getControl("UserLogFile")) + { + std::string log_file = user_log_file->getValue().asString(); + if (!log_file.empty()) + { + return log_file; + } + } + +#if LL_WINDOWS + int argc = 0; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (argv) + { + std::string log_file; + for (int i = 1; i < argc; ++i) + { + std::string option = ll_convert_wide_to_string(argv[i]); + if ((option == "--logfile" || option == "-logfile" || option == "/logfile") && + i + 1 < argc) + { + log_file = ll_convert_wide_to_string(argv[i + 1]); + } + else if (option.compare(0, 10, "--logfile=") == 0) + { + log_file = option.substr(10); + } + else if (option.compare(0, 9, "-logfile=") == 0) + { + log_file = option.substr(9); + } + else if (option.compare(0, 9, "/logfile:") == 0) + { + log_file = option.substr(9); + } + } + LocalFree(argv); + + if (!log_file.empty()) + { + return log_file; + } + } +#endif + + return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife.log"); + } + + std::string getOldLogFileName(const std::string& log_file) + { + std::string old_log_file = log_file; + size_t separator = old_log_file.find_last_of("/\\"); + size_t basename_start = (separator == std::string::npos) ? 0 : separator + 1; + size_t extension = old_log_file.find_last_of('.'); + + if (extension != std::string::npos && + extension > basename_start) + { + old_log_file.replace(extension, std::string::npos, ".old"); + } + else + { + old_log_file += ".old"; + } + + return old_log_file; + } } // anonymous namespace // Set a named control temporarily for this session, as when set via the command line --set option. -- cgit v1.3 From 998c2bc8d924fe9179f4ee914ef2b65c6ae78302 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:20:55 +0200 Subject: #5084 Improve watchdog's behavior --- indra/llcommon/llapp.h | 1 + indra/llcommon/llwatchdog.cpp | 68 +++++++++--- indra/llcommon/llwatchdog.h | 19 +++- indra/newview/llappviewer.cpp | 142 ++++++++++++++++++++----- indra/newview/llappviewer.h | 3 + indra/newview/llappviewerwin32.cpp | 50 ++++++++- indra/newview/llappviewerwin32.h | 1 + indra/newview/skins/default/xui/en/strings.xml | 3 + 8 files changed, 246 insertions(+), 41 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index ce09c566a9..fef7dc80b3 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -285,6 +285,7 @@ public: #ifdef LL_WINDOWS virtual bool reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { return false; } + virtual bool reportCustomToBugsplat(const std::string& desription) { return false; } #endif public: diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp index d3242a6c96..1622aeb180 100644 --- a/indra/llcommon/llwatchdog.cpp +++ b/indra/llcommon/llwatchdog.cpp @@ -173,6 +173,17 @@ void LLWatchdog::add(LLWatchdogEntry* e) { lockThread(); mSuspects.insert(e); + + if (!mFrozeList.empty()) + { + mFrozeList.erase(e); + if (mFrozeList.empty()) + { + // Clear error marker file if there is no frozen threads, + // viewer is responsive again. + mClearMarkerFnc(); + } + } unlockThread(); } @@ -183,7 +194,12 @@ void LLWatchdog::remove(LLWatchdogEntry* e) unlockThread(); } -void LLWatchdog::init(func_t set_error_state_callback) +void LLWatchdog::init( + create_marker_func_t error_state_callback, + clear_marker_func_t clear_marker_callback, + report_func_t report_callback, + notify_func_t notify_callback, + bool crash_on_freeze) { if (!mSuspectsAccessMutex && !mTimer) { @@ -196,7 +212,11 @@ void LLWatchdog::init(func_t set_error_state_callback) // start needs to use the mSuspectsAccessMutex mTimer->start(); } - mCreateMarkerFnc = set_error_state_callback; + mCreateMarkerFnc = error_state_callback; + mClearMarkerFnc = clear_marker_callback; + mCrashReportFnc = report_callback; + mNotifyFnc = notify_callback; + mCrashOnFreeze = crash_on_freeze; } void LLWatchdog::cleanup() @@ -251,21 +271,45 @@ void LLWatchdog::run() 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()) + std::string description = "Watchdog timer for thread " + (*result)->getThreadName() + " expired"; + if (!last_state.empty()) { - LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName() - << " expired; assuming viewer is hung and crashing" << LL_ENDL; + description += " with state: " + last_state; + } + description += "; assuming viewer is hung and crashing"; + + if (!mCrashOnFreeze) + { + // Sets watchdog marker file + mCreateMarkerFnc(false); + // If it's mainloop and it somehow recovers, it will re-add itself + mSuspects.erase(*result); + mFrozeList.insert(*result); + LL_WARNS() << description << LL_ENDL; } else { - LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName() - << " expired with state: " << last_state - << "; assuming viewer is hung and crashing" << LL_ENDL; + + if (!mCrashReportFnc(description)) + { + // Sets error marker file + mCreateMarkerFnc(true); + // If false is returned, then we failed to report the issue to bugsplat, + // instead, Notify user, then crash viewer. + // Todo: ask user if viewer should quit or wait? + mNotifyFnc(); + LL_ERRS() << description << LL_ENDL; + } + else + { + // Sets watchdog marker file + mCreateMarkerFnc(false); + // Already reported, don't report again. + // If it's mainloop and it somehow recovers, it will re-add itself + mSuspects.erase(result); + mFrozeList.insert(*result); + } } } } diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h index 2100a90879..f138fbccb0 100644 --- a/indra/llcommon/llwatchdog.h +++ b/indra/llcommon/llwatchdog.h @@ -93,8 +93,16 @@ public: void add(LLWatchdogEntry* e); void remove(LLWatchdogEntry* e); - typedef std::function func_t; - void init(func_t set_error_state_callback); + typedef std::function create_marker_func_t; + typedef std::function clear_marker_func_t; + typedef std::function report_func_t; + typedef std::function notify_func_t; + void init( + create_marker_func_t error_state_callback, + clear_marker_func_t clear_marker_callback, + report_func_t report_callback, + notify_func_t notify_callback, + bool crash_on_freeze); void run(); void cleanup(); @@ -105,14 +113,19 @@ private: typedef std::set SuspectsRegistry; SuspectsRegistry mSuspects; + SuspectsRegistry mFrozeList; LLMutex* mSuspectsAccessMutex; LLWatchdogTimerThread* mTimer; U64 mLastClockCount; + bool mCrashOnFreeze; // 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; + create_marker_func_t mCreateMarkerFnc; + clear_marker_func_t mClearMarkerFnc; + report_func_t mCrashReportFnc; + notify_func_t mNotifyFnc; }; #endif // LL_LLTHREADWATCHDOG_H diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 0b6ea72df4..4aaf8411cc 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -383,6 +383,7 @@ const std::string MARKER_FILE_NAME("SecondLife.exec_marker"); const std::string START_MARKER_FILE_NAME("SecondLife.start_marker"); const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker"); const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker"); +const std::string WATCHDOG_MARKER_FILE_NAME("SecondLife.watchdog_marker"); static std::string gLaunchFileOnQuit; //---------------------------------------------------------------------------- @@ -3235,20 +3236,60 @@ bool LLAppViewer::initWindow() << " (setting = " << watchdog_enabled_setting << ")" << LL_ENDL; - if (use_watchdog) + // Watchdog reports to statistics via marker files, that is + // pointless without ability to write (!mSecondInstance) those files. + // If use_watchdog is set, watchdog also reports to bugspat. + if (use_watchdog || !mSecondInstance) { - LLWatchdog::getInstance()->init([]() - { - LLAppViewer* app = LLAppViewer::instance(); - if (app->logoutRequestSent()) + LLWatchdog::getInstance()->init( + [](bool final_marker) { - app->createErrorMarker(LAST_EXEC_LOGOUT_FROZE); - } - else + LLAppViewer* app = LLAppViewer::instance(); + // Without watchdog everything will be counted as + // either 'unknown' (no crash marker) or based of present crash marker + if (final_marker) + { + // watchdog is going to crash viewer, so crate a 'crash' marker + if (app->logoutRequestSent()) + { + app->createErrorMarker(LAST_EXEC_LOGOUT_FROZE); + } + else + { + app->createErrorMarker(LAST_EXEC_FROZE); + } + } + else + { + // not going to crash, just create a 'watchdog' marker + app->createWatchdogMarker(); + } + }, + []() { - app->createErrorMarker(LAST_EXEC_FROZE); - } - }); + LLAppViewer* app = LLAppViewer::instance(); + // in case process recovered from freeze, remove watchdog marker. + app->removeWatchdogMarker(); + }, + [](std::string &desc) + { +#if LL_WINDOWS && LL_BUGSPLAT + LLAppViewer* app = LLAppViewer::instance(); + app->writeDebugInfo(); + return app->reportCustomToBugsplat(desc); +#else + return false; +#endif + }, + []() + { + LLAppViewer* app = LLAppViewer::instance(); + app->sendLogoutRequest(); + // Might be better to ask user if user wants to terminate the app or wait. + OSMessageBox(LLTrans::getString("MBFreezeDetected"), LLTrans::getString("MBFatalError"), OSMB_OK); + }, + use_watchdog); + } LLNotificationsUI::LLNotificationManager::getInstance(); @@ -4021,13 +4062,8 @@ void LLAppViewer::processMarkerFiles() { // the file existed, is ours, and matched our version, so we can report on what it says LL_INFOS("MarkerFile") << "Exec marker '"<< mMarkerFileName << "' found; last exec crashed or froze" << LL_ENDL; -#if LL_WINDOWS && LL_BUGSPLAT - // bugsplat will set correct state in bugsplatSendLog - // Might be more accurate to rename this one into 'unknown' + // App terminated unexpectedly or froze, we don't know the cause yet. gLastExecEvent = LAST_EXEC_UNKNOWN; -#else - gLastExecEvent = LAST_EXEC_OTHER_CRASH; -#endif // LL_WINDOWS } else @@ -4080,23 +4116,29 @@ void LLAppViewer::processMarkerFiles() } LLAPRFile::remove(logout_marker_file); } - // and last refine based on whether or not a marker created during a non-llerr crash is found + // Refine based on whether or not a marker created during + // a crash is found or if wathdog caught a freeze. + // Bugsplat will set correct state in bugsplatSendLog. std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); + std::string watchdog_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) { S32 marker_code = getMarkerErrorCode(error_marker_file); if (marker_code >= 0) { - if (gLastExecEvent == LAST_EXEC_LOGOUT_FROZE) - { - gLastExecEvent = LAST_EXEC_LOGOUT_CRASH; - LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' crashed, setting LastExecEvent to LOGOUT_CRASH" << LL_ENDL; - } - else if (marker_code > 0 && marker_code < (S32)LAST_EXEC_COUNT) + if (marker_code > 0 && marker_code < (S32)LAST_EXEC_COUNT) { + // If we have a code, it takes precendence gLastExecEvent = (eLastExecEvent)marker_code; LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' crashed, setting LastExecEvent to " << gLastExecEvent << LL_ENDL; } + // if we have the marker, even without a code, it's a crash. + else if (gLastExecEvent == LAST_EXEC_LOGOUT_UNKNOWN + || gLastExecEvent == LAST_EXEC_LOGOUT_FROZE) + { + gLastExecEvent = LAST_EXEC_LOGOUT_CRASH; + LL_INFOS("MarkerFile") << "Error marker '" << error_marker_file << "' crashed, setting LastExecEvent to LOGOUT_CRASH" << LL_ENDL; + } else { gLastExecEvent = LAST_EXEC_OTHER_CRASH; @@ -4108,6 +4150,33 @@ void LLAppViewer::processMarkerFiles() LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' marker found, but versions did not match" << LL_ENDL; } LLAPRFile::remove(error_marker_file); + if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + { + // If viewer crashed after a freeze was detected, + // crash still takes precendence. Just clear watchdog. + removeWatchdogMarker(); + } + } + else + { + // so only check watchdog marker if there is no error marker. + if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + { + if (LAST_EXEC_UNKNOWN == gLastExecEvent + || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent) + { + // watchdog marker gets created if we detect a freeze, + // so if viwer did not stop gracefully, and we know it wasn't a crash, + // we have no other info, check watchdog. + if (markerIsSameVersion(watchdog_marker_file)) + { + gLastExecEvent = LAST_EXEC_UNKNOWN == gLastExecEvent ? LAST_EXEC_FROZE : LAST_EXEC_LOGOUT_FROZE; + LL_INFOS("MarkerFile") << "Watchdog marker '" << watchdog_marker_file << "' found, setting LastExecEvent to FROZE" + << LL_ENDL; + } + } + removeWatchdogMarker(); + } } #if LL_DARWIN @@ -4152,6 +4221,7 @@ void LLAppViewer::removeMarkerFiles() { LL_WARNS("MarkerFile") << "logout marker '"<getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); + + LLAPRFile file; + file.open(error_marker, LL_APR_WB); + if (file.getFileHandle()) + { + recordMarkerVersion(file); + file.close(); + } + } +} +void LLAppViewer::removeWatchdogMarker() const +{ + if (!mSecondInstance) + { + std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); + LLFile::remove(error_marker_file); + } +} + void LLAppViewer::outOfMemorySoftQuit() { if (!mQuitRequested) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index c977757e48..d76e5015e9 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -254,6 +254,9 @@ public: void createErrorMarker(eLastExecEvent error_code) const; bool errorMarkerExists() const; + void createWatchdogMarker() const; + void removeWatchdogMarker() const; + // Attempt a 'soft' quit with disconnect and saving of settings/cache. // Intended to be thread safe. // Good chance of viewer crashing either way, but better than alternatives. diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 0dee17010c..a78a7fb95f 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -119,6 +119,7 @@ namespace // MiniDmpSender pointer. As things stand, though, we must define an // actual function and store the pointer statically. static MiniDmpSender *sBugSplatSender = nullptr; + static std::string sBugsplatDesriptionField; bool bugsplatSendLog(UINT nCode, LPVOID lpVal1, LPVOID lpVal2) { @@ -155,8 +156,21 @@ namespace WCSTR(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "settings_per_account.xml"))); } - // LL_ERRS message, when there is one - sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + if (!sBugsplatDesriptionField.empty()) + { + // Can be set by watchdog or other code that detects a problem + // and wants to add some context to the crash report. + // Will be visible in the BugSplat web UI. + sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + // This type of crash is not nessesarily a crash, or final. + // Prepare for the next one. + sBugsplatDesriptionField.clear(); + } + else + { + // LL_ERRS message, when there is one + sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + } sBugSplatSender->setAttribute(WCSTR(L"OS"), WCSTR(LLOSInfo::instance().getOSStringSimple())); // In case we ever stop using email for this sBugSplatSender->setAttribute(WCSTR(L"AppState"), WCSTR(LLStartUp::getStartupStateString())); @@ -862,6 +876,38 @@ bool LLAppViewerWin32::reportCrashToBugsplat(void* pExcepInfo) return false; } +#if defined(LL_BUGSPLAT) +static int reportCustomToBugsplatFilter(EXCEPTION_POINTERS* pExcepInfo) +{ + if (sBugSplatSender) + { + sBugSplatSender->createReport(pExcepInfo); + } + return EXCEPTION_EXECUTE_HANDLER; +} +#endif + +bool LLAppViewerWin32::reportCustomToBugsplat(const std::string &description) +{ +#if defined(LL_BUGSPLAT) + if (sBugSplatSender) + { + sBugsplatDesriptionField = description; + + __try + { + // Generate a custom exception code + RaiseException(0xE0000001, 0, 0, NULL); + } + __except (reportCustomToBugsplatFilter(GetExceptionInformation())) + { + } + return true; + } +#endif // LL_BUGSPLAT + return false; +} + bool LLAppViewerWin32::initWindow() { // This is a workaround/hotfix for a change in Windows 11 24H2 (and possibly later) diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h index d0a3f2f5ce..ece4fef6fd 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -44,6 +44,7 @@ public: bool cleanup() override; bool reportCrashToBugsplat(void* pExcepInfo) override; + bool reportCustomToBugsplat(const std::string& desription) override; // returns true if other windows were found and are still running. static bool sendShutdownToOtherInstances(const std::wstring& install_dir); diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 6a123d2a2a..a4ff883c2b 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -3008,6 +3008,9 @@ If this message persists, restart your computer. [APP_NAME] appears to have frozen or crashed on the previous run. Would you like to send a crash report? + + [APP_NAME] appears to have frozen. If this issue occurs regularly, please contact support at https://support.secondlife.com. + Notification [APP_NAME] is unable to detect DirectX 9.0b or greater. -- cgit v1.3 From e129eac3401f6af629ef96e91f4db62b3528cbc9 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 29 Apr 2026 01:20:11 +0300 Subject: #5719 Partial detection of shutdown from task manager We can't be absolutely certain about the source in this case, but at least try to distinguish termination caused by task manager from 'unknowns'. --- indra/llwindow/llwindowcallbacks.cpp | 4 ++ indra/llwindow/llwindowcallbacks.h | 2 + indra/llwindow/llwindowwin32.cpp | 61 ++++++++++++++++++++++++++--- indra/llwindow/llwindowwin32.h | 1 + indra/newview/llappviewer.cpp | 76 ++++++++++++++++++++++++++++++------ indra/newview/llappviewer.h | 2 + indra/newview/llviewerwindow.cpp | 21 ++++++++++ indra/newview/llviewerwindow.h | 1 + 8 files changed, 152 insertions(+), 16 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llwindow/llwindowcallbacks.cpp b/indra/llwindow/llwindowcallbacks.cpp index 7331f50ba0..6267433751 100644 --- a/indra/llwindow/llwindowcallbacks.cpp +++ b/indra/llwindow/llwindowcallbacks.cpp @@ -68,6 +68,10 @@ void LLWindowCallbacks::handleMouseLeave(LLWindow *window) return; } +void LLWindowCallbacks::handlePreCloseRequest() +{ +} + bool LLWindowCallbacks::handleCloseRequest(LLWindow *window, bool from_user) { //allow the window to close diff --git a/indra/llwindow/llwindowcallbacks.h b/indra/llwindow/llwindowcallbacks.h index 59dcdd3ade..457087448f 100644 --- a/indra/llwindow/llwindowcallbacks.h +++ b/indra/llwindow/llwindowcallbacks.h @@ -41,6 +41,8 @@ public: virtual bool handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); virtual bool handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); virtual void handleMouseLeave(LLWindow *window); + // Called before close request is processed (ex: to create marker file in case OS is about to kill app). + virtual void handlePreCloseRequest(); // return true to allow window to close, which will then cause handleQuit to be called virtual bool handleCloseRequest(LLWindow *window, bool from_user); virtual bool handleSessionExit(LLWindow* window); diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 7e956983d0..dd4f638767 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -508,6 +508,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks, : LLWindow(callbacks, fullscreen, flags), mAbsoluteCursorPosition(false), + mReceivedSCClose(false), mMaxGLVersion(max_gl_version), mMaxCores(max_cores) { @@ -2524,8 +2525,15 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_SYSCOMMAND: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SYSCOMMAND"); - switch (w_param) + switch (w_param & 0xFFF0) { + case SC_CLOSE: + // User clicked close from system menu/taskbar or 'end process' from task manager + // Do nothing, will cause WM_CLOSE. + // If we don't get this message before WM_CLOSE, we are likely getting + // a kill from some external program. Win11 task manager Does cause SC_CLOSE. + window_imp->mReceivedSCClose = true; + break; case SC_KEYMENU: // Disallow the ALT key from triggering the default system menu. return 0; @@ -2540,9 +2548,30 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_CLOSE: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_CLOSE"); - // todo: WM_CLOSE can be caused by user and by task manager, - // distinguish these cases. - // For now assume it is always user. + + window_imp->mCallbacks->handlePreCloseRequest(); // mark app as potentially closing + if (!window_imp->mReceivedSCClose) + { + // Some external program is trying to close the app. + // Assume that it's going to destroy process if it fails + // and try to fast-quit without confirmation or cleanup. + window_imp->post([=]() + { + // Check if app needs cleanup or can be closed immediately. + if (window_imp->mCallbacks->handleSessionExit(window_imp)) + { + // Get the app to initiate cleanup. + window_imp->mCallbacks->handleQuit(window_imp); + } + }); + return 0; + } + window_imp->mReceivedSCClose = false; + + // There is no way to tell the difference between a user issued + // WM_CLOSE or task manager's WM_CLOSE. + // Assume it is a user and ask for confirmation, but create a marker file. + // If App keeps doing something after a second, or gets 'destroy' message clear the marker. window_imp->post([=]() { // Will the app allow the window to close? @@ -2564,6 +2593,16 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ } return 0; } + case WM_NCDESTROY: + LL_INFOS("Window") << "Received WM_NCDESTROY" << LL_ENDL; + break; + case WM_WTSSESSION_CHANGE: + { + // Detects Remote Desktop disconnects, fast user switching, session logoff + // w_param: WTS_CONSOLE_CONNECT, WTS_CONSOLE_DISCONNECT, WTS_SESSION_LOGOFF, etc. + LL_INFOS("Window") << "Received WM_WTSSESSION_CHANGE with wParam: " << (U32)w_param << LL_ENDL; + break; + } case WM_QUERYENDSESSION: { // Generally means that OS is going to shut down or user is going to log off. @@ -2585,6 +2624,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ || (end_session_flags & ENDSESSION_CRITICAL) // will shutdown regardless of app state || (end_session_flags & ENDSESSION_LOGOFF)) // logoff, can delay shutdown { + window_imp->mCallbacks->handlePreCloseRequest(); // mark app as closing window_imp->post([=]() { LL_INFOS("Window") << "Shutting down due to session terminating" << LL_ENDL; @@ -3318,7 +3358,18 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_DISPLAYCHANGE: { - WINDOW_IMP_POST(window_imp->mCallbacks->handleDisplayChanged()); + LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DISPLAYCHANGE"); + window_imp->post([=]() { + window_imp->mCallbacks->handleDisplayChanged(); + // Note: WM_DISPLAYCHANGE was passing to WM_SETFOCUS + // which might have been unintended and was messing with zones. + // handleFocus was copied over and return 0 added, but + // handleFocus might be not needed here. + // handleFocus resets mouse, closes popups and keys, which + // we probablt should do on 'display change'. + window_imp->mCallbacks->handleFocus(window_imp); + }); + return 0; } case WM_SETFOCUS: diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index afff3d5cb6..defa90d1a3 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -238,6 +238,7 @@ protected: LPWSTR mIconResource; LPWSTR mIconSmallResource; bool mInputProcessingPaused; + bool mReceivedSCClose; // received SC_CLOSE and expecting WM_CLOSE // The following variables are for Language Text Input control. // They are all static, since one context is shared by all LLWindowWin32 diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 4aaf8411cc..631804e13e 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -384,6 +384,7 @@ const std::string START_MARKER_FILE_NAME("SecondLife.start_marker"); const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker"); const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker"); const std::string WATCHDOG_MARKER_FILE_NAME("SecondLife.watchdog_marker"); +const std::string CLOSE_EVENT_MARKER_FILE_NAME("SecondLife.close_marker"); static std::string gLaunchFileOnQuit; //---------------------------------------------------------------------------- @@ -4121,6 +4122,7 @@ void LLAppViewer::processMarkerFiles() // Bugsplat will set correct state in bugsplatSendLog. std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); std::string watchdog_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); + std::string close_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) { S32 marker_code = getMarkerErrorCode(error_marker_file); @@ -4150,20 +4152,16 @@ void LLAppViewer::processMarkerFiles() LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' marker found, but versions did not match" << LL_ENDL; } LLAPRFile::remove(error_marker_file); - if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) - { - // If viewer crashed after a freeze was detected, - // crash still takes precendence. Just clear watchdog. - removeWatchdogMarker(); - } } else { - // so only check watchdog marker if there is no error marker. - if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + if (LAST_EXEC_UNKNOWN == gLastExecEvent + || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent) { - if (LAST_EXEC_UNKNOWN == gLastExecEvent - || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent) + // If viewer crashed after a freeze was detected, + // crash still takes precendence. + // So only check watchdog marker if there is no error marker. + if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) { // watchdog marker gets created if we detect a freeze, // so if viwer did not stop gracefully, and we know it wasn't a crash, @@ -4175,9 +4173,35 @@ void LLAppViewer::processMarkerFiles() << LL_ENDL; } } - removeWatchdogMarker(); + // If 'close' marker is found, viewer either started shutdown but + // failed, or viewer got killed by task manager. + // Marker does not indicate that viewer was closed or is closing, + // just that 'close' was requested before viewer died. + else if (LLAPRFile::isExist(close_marker_file, NULL, LL_APR_RB)) + { + // For now treat as 'other' cause. + // Unfortunately we can't for certain distinguish task + // manager's case from other shutdown problems, so we + // have to report both. + // Todo: if this bears noticeable fruits, make a new state later. + // New categories need server/web side support. + if (markerIsSameVersion(close_marker_file)) + { + gLastExecEvent = LAST_EXEC_UNKNOWN == gLastExecEvent ? LAST_EXEC_OTHER_CRASH : LAST_EXEC_LOGOUT_CRASH; + LL_INFOS("MarkerFile") << "'Close' marker '" << close_marker_file << "' found, setting LastExecEvent to CRASH" + << LL_ENDL; + } + } } } + if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + { + removeWatchdogMarker(); + } + if (LLAPRFile::isExist(close_marker_file, NULL, LL_APR_RB)) + { + removeCloseRequestMarker(); + } #if LL_DARWIN if (!mSecondInstance && gLastExecEvent != LAST_EXEC_NORMAL) @@ -4221,6 +4245,7 @@ void LLAppViewer::removeMarkerFiles() { LL_WARNS("MarkerFile") << "logout marker '"<getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); + + LLAPRFile file; + file.open(close_marker, LL_APR_WB); + if (file.getFileHandle()) + { + recordMarkerVersion(file); + file.close(); + } + } +} + +void LLAppViewer::removeCloseRequestMarker() const +{ + if (!mSecondInstance) + { + std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); + LLFile::remove(error_marker_file); + } +} + void LLAppViewer::createWatchdogMarker() const { if (!mSecondInstance) @@ -5624,6 +5677,7 @@ void LLAppViewer::createWatchdogMarker() const } } } + void LLAppViewer::removeWatchdogMarker() const { if (!mSecondInstance) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index d76e5015e9..87c885e4be 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -254,6 +254,8 @@ public: void createErrorMarker(eLastExecEvent error_code) const; bool errorMarkerExists() const; + void createCloseRequestMarker() const; + void removeCloseRequestMarker() const; void createWatchdogMarker() const; void removeWatchdogMarker() const; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 8695b96952..369a887102 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -38,6 +38,7 @@ #include "llagent.h" #include "llagentcamera.h" +#include "llcallbacklist.h" #include "llcommandhandler.h" #include "llcommunicationchannel.h" #include "llfloaterreg.h" @@ -1466,12 +1467,32 @@ void LLViewerWindow::handleMouseLeave(LLWindow *window) LLToolTipMgr::instance().blockToolTips(); } +void LLViewerWindow::handlePreCloseRequest() +{ + // WINDOW THREAD! since we need this to act fast. + if (!LLApp::isExiting() && !LLApp::isStopped()) + { + LLAppViewer::instance()->createCloseRequestMarker(); + } + +} + bool LLViewerWindow::handleCloseRequest(LLWindow *window, bool from_user) { if (!LLApp::isExiting() && !LLApp::isStopped()) { if (from_user) { + // Task naamger kills viewer after 1 second, 3 seconds + // is overkill, but decided to be on a safe side. + doAfterInterval([]() + { + // if user quits, marker will be cleaned by cleanup, + // if user cancels quit, marker will be cleaned here, + // but if task manager kills us, marker stays. + LLAppViewer::instance()->removeCloseRequestMarker(); + }, 3.0f); + // User has indicated they want to close, but we may need to ask // about modified documents. LLAppViewer::instance()->userQuit(); diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index ec28a3fc4a..ff5da371ec 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -202,6 +202,7 @@ public: /*virtual*/ bool handleUnicodeChar(llwchar uni_char, MASK mask); // NOT going to handle extended /*virtual*/ bool handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); /*virtual*/ bool handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ void handlePreCloseRequest(); /*virtual*/ bool handleCloseRequest(LLWindow *window, bool from_user); /*virtual*/ bool handleSessionExit(LLWindow* window); /*virtual*/ void handleQuit(LLWindow *window); -- cgit v1.3 From c828dc35db7d5673a629590da3ec2064d1394ad4 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 4 May 2026 23:26:16 +0300 Subject: #5084 Fix watchdog's cleanup --- indra/newview/llappviewer.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 631804e13e..f9f94b8f28 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2135,6 +2135,7 @@ bool LLAppViewer::cleanup() LLWorld::deleteSingleton(); LLVoiceClient::deleteSingleton(); LLUI::deleteSingleton(); + LLWatchdog::deleteSingleton(); // It's not at first obvious where, in this long sequence, a generic cleanup // call OUGHT to go. So let's say this: as we migrate cleanup from -- cgit v1.3 From 5b5c4e5f385dfdcbe001e20200b4d381ddcab379 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 4 May 2026 23:31:14 +0300 Subject: #5756 Improve LLUICtrlFactory's performance --- indra/llui/lluictrlfactory.cpp | 2 +- indra/llui/lluictrlfactory.h | 11 ++++++++--- indra/newview/llappviewer.cpp | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp index 33ffc3dfc6..740f187ed8 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -67,7 +67,7 @@ public: static LLDefaultChildRegistry::Register r1("locate"); // Build time optimization, generate this once in .cpp file -template class LLUICtrlFactory* LLSingleton::getInstance(); +template class LLUICtrlFactory* LLSimpleton::getInstance(); //----------------------------------------------------------------------------- // LLUICtrlFactory() diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h index f44b4ba4dc..75cee5f004 100644 --- a/indra/llui/lluictrlfactory.h +++ b/indra/llui/lluictrlfactory.h @@ -81,12 +81,13 @@ class LLWidgetNameRegistry // Build time optimization, generate this once in .cpp file #ifndef LLUICTRLFACTORY_CPP -extern template class LLUICtrlFactory* LLSingleton::getInstance(); +extern template class LLUICtrlFactory* LLSimpleton::getInstance(); #endif -class LLUICtrlFactory : public LLSingleton +class LLUICtrlFactory : public LLSimpleton { - LLSINGLETON(LLUICtrlFactory); +public: + LLUICtrlFactory(); ~LLUICtrlFactory(); // only partial specialization allowed in inner classes, so use extra dummy parameter @@ -313,6 +314,10 @@ template LLChildRegistry::Register::Register(const char* tag, LLWidgetCreatorFunc func) : LLChildRegistry::StaticRegistrar(tag, func == nullptr ? (LLWidgetCreatorFunc)&LLUICtrlFactory::defaultBuilder : func) { + if (!LLUICtrlFactory::instanceExists()) + { + LLUICtrlFactory::createInstance(); + } // add this widget to various registries LLUICtrlFactory::instance().registerWidget(typeid(T), typeid(typename T::Params), tag); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index f9f94b8f28..2e42855079 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2148,6 +2148,8 @@ bool LLAppViewer::cleanup() // deleteSingleton() methods. LLSingletonBase::deleteAll(); + LLUICtrlFactory::deleteSingleton(); + LLSplashScreen::hide(); LL_INFOS() << "Goodbye!" << LL_ENDL; -- cgit v1.3 From 0422a389ad0289880dc5cead1c8d0788fa881e73 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 13 May 2026 23:29:23 +0300 Subject: #5810 Improve early exit's cleanup --- indra/newview/llappviewer.cpp | 39 ++++++++++++++++++++++++++++++++++---- indra/newview/llappviewer.h | 3 ++- indra/newview/llappviewerwin32.cpp | 18 +++++++++++------- indra/newview/llappviewerwin32.h | 4 +++- 4 files changed, 51 insertions(+), 13 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 2e42855079..cb140bc523 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -940,6 +940,16 @@ bool LLAppViewer::init() LL_WARNS("InitInfo") << "initHardwareTest() failed." << LL_ENDL; // quit immediately LL_PROFILER_FRAME_END; + LLSingletonBase::deleteAll(); + cleanupConsole(); + delete mSettingsLocationList; + if (!mSecondInstance) + { + // Stats from previous session will likely be lost, but this should + // be fine as this is likely first run for this version. + // Todo: Might be smarter to have an exit code for a cleaner shutdown + removeMarkerFiles(); + } return false; } LL_INFOS("InitInfo") << "Hardware test initialization done." << LL_ENDL ; @@ -3008,7 +3018,23 @@ bool LLAppViewer::initConfiguration() { if (sendURLToOtherInstance(start_slurl.getSLURLString())) { - // successfully handed off URL to existing instance, exit + // Successfully handed off URL to existing instance. + // Returning 'false' gets treated as a failure to init, + // without cleanup, so instead clear markers and app here. + // Do not save settings. + // Might be smarter to have an exit code for a more reliable + // "early exit, needs cleanup" case. + LLSingletonBase::deleteAll(); + cleanupConsole(); + delete mSettingsLocationList; + if (!mSecondInstance) + { + // Todo: Unfortunately, if we are doing this, stats and + // markers from previous session were already processed, + // cleared yet haven't been reported and will be lost. + // Consider a way to save those. + removeMarkerFiles(); + } return false; } } @@ -3055,6 +3081,11 @@ bool LLAppViewer::initConfiguration() LLTrans::getString("MBAlreadyRunning"), LLStringUtil::null, OSMB_OK); + + // Since returning 'false' is basically an error without cleanup, + // do cleanup here. No need to worry about marker files here. + LLSingletonBase::deleteAll(); + cleanupConsole(); return false; } @@ -5661,7 +5692,7 @@ void LLAppViewer::removeCloseRequestMarker() const if (!mSecondInstance) { std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); - LLFile::remove(error_marker_file); + LLFile::remove(error_marker_file, ENOENT); } } @@ -5686,7 +5717,7 @@ void LLAppViewer::removeWatchdogMarker() const if (!mSecondInstance) { std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); - LLFile::remove(error_marker_file); + LLFile::remove(error_marker_file, ENOENT); } } diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 87c885e4be..13c4c5f6c0 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -17,7 +17,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -276,6 +276,7 @@ protected: virtual bool initWindow(); // Initialize the viewer's window. virtual void initLoggingAndGetLastDuration(); // Initialize log files, logging system virtual void initConsole() {}; // Initialize OS level debugging console. + virtual void cleanupConsole() {}; // Cleanup OS level debugging console. virtual bool initHardwareTest() { return true; } // A false result indicates the app should quit. virtual bool initSLURLHandler(); virtual bool sendURLToOtherInstance(const std::string& url); diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 3dc98b83c4..4e443ee189 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -854,12 +854,7 @@ bool LLAppViewerWin32::cleanup() bool result = LLAppViewer::cleanup(); gDXHardware.cleanup(); - - if (mIsConsoleAllocated) - { - FreeConsole(); - mIsConsoleAllocated = false; - } + cleanupConsole(); return result; } @@ -943,6 +938,15 @@ void LLAppViewerWin32::initConsole() return LLAppViewer::initConsole(); } +void LLAppViewerWin32::cleanupConsole() +{ + if (mIsConsoleAllocated) + { + FreeConsole(); + mIsConsoleAllocated = false; + } +} + void write_debug_dx(const char* str) { std::string value = gDebugInfo["DXInfo"].asString(); diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h index ad61ae68d6..971907694a 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -53,6 +53,8 @@ protected: bool initWindow() override; // Override to initialize the viewer's window. void initLoggingAndGetLastDuration() override; // Override to clean stack_trace info. void initConsole() override; // Initialize OS level debugging console. + void cleanupConsole() override; + bool initHardwareTest() override; // Win32 uses DX9 to test hardware. bool initParseCommandLine(LLCommandLineParser& clp) override; -- cgit v1.3 From ba619ac58c4d52a204445f6386b9339b9ec226d9 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 14 May 2026 23:17:29 +0300 Subject: #5810 Differentiate startup crashes from runtime crashes --- indra/llcommon/llerror.h | 1 + indra/llwindow/llwindowwin32.cpp | 24 ++++++++--------- indra/newview/llappviewer.cpp | 57 ++++++++++++++++++++++++++++++++++++++-- indra/newview/llappviewer.h | 4 ++- indra/newview/llstartup.cpp | 1 + 5 files changed, 72 insertions(+), 15 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 41893a35e5..0083865cf7 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -314,6 +314,7 @@ namespace LLError ERROR_OTHER = 0, ERROR_BAD_ALLOC = 1, ERROR_MISSING_FILES = 2, + ERROR_INIT_FAILED = 3, } eLastExecEvent; // tittle, message and error code to include in error marker file diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index dd4f638767..52e0cdd5d5 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -1421,7 +1421,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo catch (...) { LOG_UNHANDLED_EXCEPTION("ChoosePixelFormat"); - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1432,7 +1432,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), &pfd)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1470,7 +1470,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo if (!SetPixelFormat(mhDC, pixel_format, &pfd)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1478,14 +1478,14 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo if (!(mhRC = SafeCreateContext(mhDC))) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } if (!wglMakeCurrent(mhDC, mhRC)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1691,14 +1691,14 @@ const S32 max_format = (S32)num_formats - 1; if (!mhDC) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBDevContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBDevContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } if (!SetPixelFormat(mhDC, pixel_format, &pfd)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1730,7 +1730,7 @@ const S32 max_format = (S32)num_formats - 1; { LL_WARNS("Window") << "No wgl_ARB_pixel_format extension!" << LL_ENDL; // cannot proceed without wgl_ARB_pixel_format extension, shutdown same as any other gGLManager.initGL() failure - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1739,7 +1739,7 @@ const S32 max_format = (S32)num_formats - 1; if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), &pfd)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1761,14 +1761,14 @@ const S32 max_format = (S32)num_formats - 1; if (!wglMakeCurrent(mhDC, mhRC)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } if (!gGLManager.initGL()) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1976,7 +1976,7 @@ void* LLWindowWin32::createSharedContext() if (!rc && !(rc = wglCreateContext(mhDC))) { close(); - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); } return rc; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index cb140bc523..098a345a9c 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -384,6 +384,7 @@ const std::string START_MARKER_FILE_NAME("SecondLife.start_marker"); const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker"); const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker"); const std::string WATCHDOG_MARKER_FILE_NAME("SecondLife.watchdog_marker"); +const std::string INITED_MARKER_FILE_NAME("SecondLife.inited_marker"); const std::string CLOSE_EVENT_MARKER_FILE_NAME("SecondLife.close_marker"); static std::string gLaunchFileOnQuit; @@ -690,6 +691,12 @@ LLAppViewer::LLAppViewer() gLoggedInTime.stop(); + // Locking this early is needed to prevent multiple instances and + // to log, but it also means that early paths such as SLURL handling + // can invoke processMarkerFiles() and potentially clear markers + // from previous runs before those stats are reported. + // Todo: improve this. Perhaps store stats 'permanently' to be reported + // on next login and only login cleans stats up? processMarkerFiles(); // // OK to write stuff to logs now, we've now crash reported if necessary @@ -2290,6 +2297,9 @@ void errorHandler(const std::string& title_string, const std::string& message_st case LLError::LLUserWarningMsg::ERROR_MISSING_FILES: LLAppViewer::instance()->createErrorMarker(LAST_EXEC_MISSING_FILES); break; + case LLError::LLUserWarningMsg::ERROR_INIT_FAILED: + LLAppViewer::instance()->createErrorMarker(LAST_EXEC_INIT); + break; default: break; } @@ -4044,6 +4054,9 @@ void LLAppViewer::processMarkerFiles() // - Freeze (SecondLife.exec_marker present, not locked) // - LLError Crash (SecondLife.llerror_marker present) // - Other Crash (SecondLife.error_marker present) + // - Watchdog freeze (SecondLife.watchdog_marker present) + // - Failed to initialize (SecondLife.inited_marker not present) + // - Potentially killed by task manager (SecondLife.close_marker present) // These checks should also remove these files for the last 2 cases if they currently exist std::ostringstream marker_log_stream; @@ -4156,6 +4169,7 @@ void LLAppViewer::processMarkerFiles() // Bugsplat will set correct state in bugsplatSendLog. std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); std::string watchdog_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); + std::string inited_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME); std::string close_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) { @@ -4226,12 +4240,26 @@ void LLAppViewer::processMarkerFiles() << LL_ENDL; } } + else if ((LAST_EXEC_UNKNOWN == gLastExecEvent) + && !LLAPRFile::isExist(inited_marker_file, NULL, LL_APR_RB)) + { + // Viewer didn't get to a login screen. + gLastExecEvent = LAST_EXEC_INIT; + LL_INFOS("MarkerFile") << "'Inited' marker '" + << inited_marker_file + << "' not found, assuming that init crashed." + << LL_ENDL; + } } } if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) { removeWatchdogMarker(); } + if (LLAPRFile::isExist(inited_marker_file, NULL, LL_APR_RB)) + { + removeInitedMarker(); + } if (LLAPRFile::isExist(close_marker_file, NULL, LL_APR_RB)) { removeCloseRequestMarker(); @@ -5691,8 +5719,33 @@ void LLAppViewer::removeCloseRequestMarker() const { if (!mSecondInstance) { - std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); - LLFile::remove(error_marker_file, ENOENT); + std::string close_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); + LLFile::remove(close_marker, ENOENT); + } +} + +void LLAppViewer::createInitedMarker() const +{ + if (!mSecondInstance) + { + std::string inited_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME); + + LLAPRFile file; + file.open(inited_marker, LL_APR_WB); + if (file.getFileHandle()) + { + recordMarkerVersion(file); + file.close(); + } + } +} + +void LLAppViewer::removeInitedMarker() const +{ + if (!mSecondInstance) + { + std::string inited_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME); + LLFile::remove(inited_marker, ENOENT); } } diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 13c4c5f6c0..b404ec2c03 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -76,7 +76,7 @@ typedef enum LAST_EXEC_LOGOUT_CRASH, LAST_EXEC_BAD_ALLOC, LAST_EXEC_MISSING_FILES, - LAST_EXEC_GRAPHICS_INIT, + LAST_EXEC_INIT, LAST_EXEC_UNKNOWN, LAST_EXEC_LOGOUT_UNKNOWN, LAST_EXEC_COUNT @@ -256,6 +256,8 @@ public: void createCloseRequestMarker() const; void removeCloseRequestMarker() const; + void createInitedMarker() const; + void removeInitedMarker() const; void createWatchdogMarker() const; void removeWatchdogMarker() const; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 9bdfbaedc8..fb55f2d206 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -832,6 +832,7 @@ bool idle_startup() set_startup_status(0.03f, msg.c_str(), gAgent.mMOTD.c_str()); do_startup_frame(); // LLViewerMedia::initBrowser(); + LLAppViewer::instance()->createInitedMarker(); LLStartUp::setStartupState( STATE_LOGIN_SHOW ); return false; } -- cgit v1.3