diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index ce09c566a9d..3a855bc480c 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -285,6 +285,7 @@ class LL_COMMON_API LLApp #ifdef LL_WINDOWS virtual bool reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { return false; } + virtual bool reportCustomToBugsplat(const std::string& description) { return false; } #endif public: diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp index d3242a6c96c..66b565c7634 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(); } @@ -180,10 +191,16 @@ void LLWatchdog::remove(LLWatchdogEntry* e) { lockThread(); mSuspects.erase(e); + mFrozeList.erase(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 +213,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 +272,47 @@ 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 + LLWatchdogEntry* froze_entry = *result; + mSuspects.erase(result); + mFrozeList.insert(froze_entry); + 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 + LLWatchdogEntry* froze_entry = *result; + mSuspects.erase(result); + mFrozeList.insert(froze_entry); + } } } } diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h index 2100a908798..f138fbccb05 100644 --- a/indra/llcommon/llwatchdog.h +++ b/indra/llcommon/llwatchdog.h @@ -93,8 +93,16 @@ class LLWatchdog : public LLSimpleton 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 @@ class LLWatchdog : public LLSimpleton 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/llwindow/llwindowcallbacks.cpp b/indra/llwindow/llwindowcallbacks.cpp index 7331f50ba0c..62674337513 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 59dcdd3adee..457087448f3 100644 --- a/indra/llwindow/llwindowcallbacks.h +++ b/indra/llwindow/llwindowcallbacks.h @@ -41,6 +41,8 @@ class LLWindowCallbacks 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 7e956983d02..dd4f6387672 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 afff3d5cb69..defa90d1a32 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -238,6 +238,7 @@ class LLWindowWin32 : public LLWindow 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 0b6ea72df4a..631804e13e1 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -383,6 +383,8 @@ 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"); +const std::string CLOSE_EVENT_MARKER_FILE_NAME("SecondLife.close_marker"); static std::string gLaunchFileOnQuit; //---------------------------------------------------------------------------- @@ -3235,20 +3237,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 +4063,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 +4117,30 @@ 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); + 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); 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; @@ -4109,6 +4153,55 @@ void LLAppViewer::processMarkerFiles() } LLAPRFile::remove(error_marker_file); } + else + { + 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, + // 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; + } + } + // 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) @@ -4152,6 +4245,8 @@ 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) + { + std::string error_marker = gDirUtilp->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 c977757e486..87c885e4be3 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -254,6 +254,11 @@ class LLAppViewer : public LLApp void createErrorMarker(eLastExecEvent error_code) const; bool errorMarkerExists() const; + void createCloseRequestMarker() const; + void removeCloseRequestMarker() 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 0dee17010ce..3dc98b83c4d 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 sBugsplatDescriptionField; 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 (!sBugsplatDescriptionField.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(sBugsplatDescriptionField)); + // This type of crash is not necessarily a crash, or final. + // Prepare for the next one. + sBugsplatDescriptionField.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) + { + sBugsplatDescriptionField = 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 d0a3f2f5ce2..ad61ae68d6e 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -44,6 +44,7 @@ class LLAppViewerWin32 : public LLAppViewer bool cleanup() override; bool reportCrashToBugsplat(void* pExcepInfo) override; + bool reportCustomToBugsplat(const std::string& description) 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/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 8695b969526..369a8871022 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 ec28a3fc4aa..ff5da371ec4 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -202,6 +202,7 @@ class LLViewerWindow : public LLWindowCallbacks /*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); diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 6a123d2a2a5..a4ff883c2b7 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.