summaryrefslogtreecommitdiff
path: root/indra/newview/llappviewer.cpp
diff options
context:
space:
mode:
authorJonathan "Geenz" Goodman <geenz@lindenlab.com>2026-05-20 12:05:18 -0400
committerGitHub <noreply@github.com>2026-05-20 12:05:18 -0400
commit40cd1617a6a663fb921265101307454b490ec4ba (patch)
tree2f13741295c385172364e3fa940d7591f32fac4e /indra/newview/llappviewer.cpp
parent9c2ee024bef564ebb6e2e8942b43ffae1d99232c (diff)
parentdf844170600608b37c2725f7a263f1de3b055618 (diff)
Merge branch 'develop' into geenz/26.3-to-develop
Diffstat (limited to 'indra/newview/llappviewer.cpp')
-rw-r--r--indra/newview/llappviewer.cpp258
1 files changed, 249 insertions, 9 deletions
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 59056381e1..11e6767980 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
@@ -66,6 +66,7 @@
#include "llconversationlog.h"
#if LL_WINDOWS
#include "lldxhardware.h"
+#include <shellapi.h>
#endif
#include "lltexturestats.h"
#include "lltrace.h"
@@ -383,6 +384,8 @@ 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;
//----------------------------------------------------------------------------
@@ -688,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
@@ -938,6 +947,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 ;
@@ -2133,6 +2152,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
@@ -2277,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;
}
@@ -2301,6 +2324,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()
{
//
@@ -2326,13 +2355,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
@@ -2379,7 +2405,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())
@@ -2518,6 +2544,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.
@@ -2932,7 +3028,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;
}
}
@@ -2979,6 +3091,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;
}
@@ -3936,6 +4053,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;
@@ -4048,6 +4168,8 @@ 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))
{
S32 marker_code = getMarkerErrorCode(error_marker_file);
@@ -4105,6 +4227,69 @@ void LLAppViewer::processMarkerFiles()
removeWatchdogMarker();
}
}
+ 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;
+ }
+ }
+ 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();
+ }
#if LL_DARWIN
if (!mSecondInstance && gLastExecEvent != LAST_EXEC_NORMAL)
@@ -4148,6 +4333,7 @@ void LLAppViewer::removeMarkerFiles()
{
LL_WARNS("MarkerFile") << "logout marker '"<<mLogoutMarkerFileName<<"' not open"<< LL_ENDL;
}
+ removeCloseRequestMarker();
removeWatchdogMarker();
}
else
@@ -5589,6 +5775,59 @@ bool LLAppViewer::errorMarkerExists() const
return LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB);
}
+void LLAppViewer::createCloseRequestMarker() const
+{
+ // WINDOW THREAD! since we need this to act fast.
+ // This does not indicate that viewer was closed or is closing,
+ // but that 'close' was requested.
+ if (!mSecondInstance)
+ {
+ std::string close_marker = gDirUtilp->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 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);
+ }
+}
+
void LLAppViewer::createWatchdogMarker() const
{
if (!mSecondInstance)
@@ -5604,12 +5843,13 @@ void LLAppViewer::createWatchdogMarker() const
}
}
}
+
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);
}
}