summaryrefslogtreecommitdiff
path: root/indra/newview/llappviewer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llappviewer.cpp')
-rw-r--r--indra/newview/llappviewer.cpp998
1 files changed, 756 insertions, 242 deletions
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 05be94b488..f8c687b0fc 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"
@@ -98,6 +99,11 @@
#include "llurlmatch.h"
#include "lltextutil.h"
#include "lllogininstance.h"
+#include "llvvmquery.h"
+
+#if LL_VELOPACK
+#include "llvelopack.h"
+#endif
#include "llprogressview.h"
#include "llvocache.h"
#include "lldiskcache.h"
@@ -109,6 +115,7 @@
#include "lllocalbitmaps.h"
#include "llperfstats.h"
#include "llgltfmateriallist.h"
+#include "llwebsocketmgr.h"
// Linden library includes
#include "llavatarnamecache.h"
@@ -131,17 +138,19 @@
#include "stringize.h"
#include "llcoros.h"
#include "llexception.h"
-#if !_M_ARM64 // !LL_LINUX
+#if defined(LL_CEF)
#include "cef/dullahan_version.h"
+#endif
+#if defined(LL_VLC)
#include "vlc/libvlc_version.h"
-#endif // LL_LINUX
+#endif
-#if LL_DARWIN
-#if LL_SDL
-#include "llwindowsdl.h"
-#else
+#if LL_DARWIN && !LL_SDL_WINDOW
#include "llwindowmacosx.h"
-#endif // LL_SDL
+#endif
+
+#if LL_SDL_WINDOW
+#include "llwindowsdl.h"
#endif
// Third party library includes
@@ -149,6 +158,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>
#include <boost/throw_exception.hpp>
+#include <chrono>
#if LL_WINDOWS
# include <share.h> // For _SH_DENYWR in processMarkerFiles
@@ -184,7 +194,6 @@
#include "lltracker.h"
#include "llviewerparcelmgr.h"
#include "llworldmapview.h"
-#include "llpostprocess.h"
#include "lldebugview.h"
#include "llconsole.h"
@@ -272,12 +281,13 @@ using namespace LL;
// define a self-registering event API object
#include "llappviewerlistener.h"
-#if LL_MSVC
-// disable boost::lexical_cast warning
-#pragma warning (disable:4702)
-#endif
-
#ifdef LL_DISCORD
+#ifdef None
+#undef None
+#endif
+#ifdef BadRequest
+#undef BadRequest
+#endif
#define DISCORDPP_IMPLEMENTATION
#include <discordpp.h>
static std::shared_ptr<discordpp::Client> gDiscordClient;
@@ -296,17 +306,24 @@ static LLAppViewerListener sAppViewerListener(LLAppViewer::instance);
#if LL_DARWIN
extern void init_apple_menu(const char* product);
+extern bool gHDRDisplaySupport;
#endif // LL_DARWIN
extern bool gRandomizeFramerate;
extern bool gPeriodicSlowFrame;
extern bool gDebugGL;
-#if LL_DARWIN
+#if LL_DARWIN || LL_SDL_WINDOW
extern bool gHiDPISupport;
-extern bool gHDRDisplaySupport;
#endif
+#if LL_WINDOWS
+extern bool gGPUBenchmarkMode;
+#else
+static constexpr bool gGPUBenchmarkMode = false;
+#endif // LL_WINDOWS
+
+
////////////////////////////////////////////////////////////
// All from the last globals push...
@@ -356,9 +373,6 @@ F32 gLogoutMaxTime = LOGOUT_REQUEST_TIME;
S32 gPendingMetricsUploads = 0;
-
-bool gDisconnected = false;
-
// Used to restore texture state after a mode switch
LLFrameTimer gRestoreGLTimer;
bool gRestoreGL = false;
@@ -393,12 +407,11 @@ 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 INITED_MARKER_FILE_NAME("SecondLife.inited_marker");
+const std::string CLOSE_EVENT_MARKER_FILE_NAME("SecondLife.close_marker");
static std::string gLaunchFileOnQuit;
-// Used on Win32 for other apps to identify our window (eg, win_setup)
-const char* const VIEWER_WINDOW_CLASSNAME = "Second Life";
-
-
//----------------------------------------------------------------------------
// List of entries from strings.xml to always replace
@@ -557,15 +570,13 @@ static void settings_to_globals()
{
LLSurface::setTextureSize(gSavedSettings.getU32("RegionTextureSize"));
-//#if LL_DARWIN
+#if LL_DARWIN
LLRender::sGLCoreProfile = true;
-/*
#else
LLRender::sGLCoreProfile = gSavedSettings.getBOOL("RenderGLContextCoreProfile");
#endif
-*/
LLRender::sNsightDebugSupport = gSavedSettings.getBOOL("RenderNsightDebugSupport");
- LLImageGL::sGlobalUseAnisotropic = gSavedSettings.getBOOL("RenderAnisotropic");
+ LLRender::sAnisotropicFilteringLevel = static_cast<F32>(gSavedSettings.getU32("RenderAnisotropicLevel"));
LLImageGL::sCompressTextures = gSavedSettings.getBOOL("RenderCompressTextures");
LLVOVolume::sLODFactor = llclamp(gSavedSettings.getF32("RenderVolumeLODFactor"), 0.01f, MAX_LOD_FACTOR);
LLVOVolume::sDistanceFactor = 1.f-LLVOVolume::sLODFactor * 0.1f;
@@ -590,14 +601,17 @@ static void settings_to_globals()
LLWorldMapView::setScaleSetting(gSavedSettings.getF32("MapScale"));
#if LL_DARWIN
-#if LL_SDL
+#if LL_SDL_WINDOW
LLWindowSDL::sUseMultGL = gSavedSettings.getBOOL("RenderAppleUseMultGL");
#else
LLWindowMacOSX::sUseMultGL = gSavedSettings.getBOOL("RenderAppleUseMultGL");
-#endif // LL_SDL
- gHiDPISupport = gSavedSettings.getBOOL("RenderHiDPI");
+#endif
gHDRDisplaySupport = gSavedSettings.getBOOL("MPHDRDisplay");
#endif
+
+#if LL_DARWIN || LL_SDL_WINDOW
+ gHiDPISupport = gSavedSettings.getBOOL("RenderHiDPI");
+#endif
}
static void settings_modify()
@@ -679,7 +693,6 @@ LLAppViewer::LLAppViewer()
mPurgeCacheOnExit(false),
mPurgeUserDataOnExit(false),
mSecondInstance(false),
- mUpdaterNotFound(false),
mSavedFinalSnapshot(false),
mSavePerAccountSettings(false), // don't save settings on logout unless login succeeded.
mQuitRequested(false),
@@ -712,6 +725,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
@@ -758,6 +777,8 @@ public:
bool LLAppViewer::init()
{
+ LL_PROFILE_ZONE_SCOPED;
+
setupErrorHandling(mSecondInstance);
//
@@ -862,6 +883,8 @@ bool LLAppViewer::init()
// that use findSkinnedFilenames(), will include the localized files.
gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), LLUI::getLanguage());
+ loadLocalizedSettingsComments();
+
// Setup LLTrans after LLUI::initClass has been called.
initStrings();
@@ -959,6 +982,17 @@ bool LLAppViewer::init()
// Early out from user choice.
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 ;
@@ -977,12 +1011,14 @@ bool LLAppViewer::init()
OSMessageBox(msg.c_str(), LLStringUtil::null, OSMB_OK);
LL_WARNS("InitInfo") << "Failed to init cache" << LL_ENDL;
// quit immediately
+ LL_PROFILER_FRAME_END;
return false;
}
LL_INFOS("InitInfo") << "Cache initialization is done." << LL_ENDL ;
// Initialize event recorder
LLViewerEventRecorder::createInstance();
+ LLWatchdog::createInstance();
//
// Initialize the window
@@ -1012,10 +1048,11 @@ bool LLAppViewer::init()
// Already handled with a MBVideoDrvErr
LL_WARNS("InitInfo") << "gGLManager.mHasRequirements is false." << LL_ENDL;
// quit immediately
+ LL_PROFILER_FRAME_END;
return false;
}
-#if defined(__i386__) || defined(__x86_64__) || defined(__amd64__) || _M_X64
+#if defined(LL_X86) || defined(LL_X86_64)
// Without SSE2 support we will crash almost immediately, warn here.
if (!gSysCPU.hasSSE2())
{
@@ -1025,6 +1062,7 @@ bool LLAppViewer::init()
OSMessageBox(msg.c_str(), LLStringUtil::null, OSMB_OK);
LL_WARNS("InitInfo") << "SSE2 is not supported" << LL_ENDL;
// quit immediately
+ LL_PROFILER_FRAME_END;
return false;
}
#endif
@@ -1135,68 +1173,17 @@ bool LLAppViewer::init()
gGLActive = false;
-#if 0 // LL_RELEASE_FOR_DOWNLOAD && !LL_LINUX
- // Skip updater if this is a non-interactive instance
+#if 0
+ // Launch VVM update check (Linux updater is future work: Velopack port)
if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") && !gNonInteractive)
{
- LLProcess::Params updater;
- updater.desc = "updater process";
- // Because it's the updater, it MUST persist beyond the lifespan of the
- // viewer itself.
- updater.autokill = false;
- std::string updater_file;
-#if LL_WINDOWS
- updater_file = "SLVersionChecker.exe";
- updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file);
-#elif LL_DARWIN
- updater_file = "SLVersionChecker";
- updater.executable = gDirUtilp->add(gDirUtilp->getAppRODataDir(), "updater", updater_file);
-#else
- updater_file = "SLVersionChecker";
- updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file);
-#endif
- // add LEAP mode command-line argument to whichever of these we selected
- updater.args.add("leap");
- // UpdaterServiceSettings
- if (gSavedSettings.getBOOL("FirstLoginThisInstall"))
- {
- // Befor first login, treat this as 'manual' updates,
- // updater won't install anything, but required updates
- updater.args.add("0");
- }
- else
- {
- updater.args.add(stringize(gSavedSettings.getU32("UpdaterServiceSetting")));
- }
- // channel
- updater.args.add(LLVersionInfo::instance().getChannel());
- // testok
- updater.args.add(stringize(gSavedSettings.getBOOL("UpdaterWillingToTest")));
- // ForceAddressSize
- updater.args.add(stringize(gSavedSettings.getU32("ForceAddressSize")));
-
- try
- {
- // Run the updater. An exception from launching the updater should bother us.
- LLLeap::create(updater, true);
- mUpdaterNotFound = false;
- }
- catch (...)
- {
- LLUIString details = LLNotifications::instance().getGlobalString("LLLeapUpdaterFailure");
- details.setArg("[UPDATER_APP]", updater_file);
- OSMessageBox(
- details.getString(),
- LLStringUtil::null,
- OSMB_OK);
- mUpdaterNotFound = true;
- }
+ initVVMUpdateCheck();
}
else
+#endif
{
LL_WARNS("InitInfo") << "Skipping updater check." << LL_ENDL;
}
-#endif //LL_RELEASE_FOR_DOWNLOAD
{
// Iterate over --leap command-line options. But this is a bit tricky: if
@@ -1295,7 +1282,7 @@ bool LLAppViewer::init()
gDirUtilp->deleteDirAndContents(gDirUtilp->getDumpLogsDirPath());
}
#endif
-
+ LL_PROFILER_FRAME_END;
return true;
}
@@ -1314,15 +1301,11 @@ void LLAppViewer::initMaxHeapSize()
//------------------------------------------------------------------------------------------
//currently SL is built under 32-bit setting, we set its max heap size no more than 1.6 GB.
-/*
-#ifndef LL_X86_64
+ #if !defined(LL_X86_64) && !defined(LL_ARM64)
F32Gigabytes max_heap_size_gb = (F32Gigabytes)gSavedSettings.getF32("MaxHeapSize") ;
#else
-*/
F32Gigabytes max_heap_size_gb = (F32Gigabytes)gSavedSettings.getF32("MaxHeapSize64");
-/*
#endif
-*/
LLMemory::initMaxHeapSizeGB(max_heap_size_gb);
}
@@ -1360,7 +1343,11 @@ bool LLAppViewer::frame()
{
try
{
+ const auto start = std::chrono::steady_clock::now();
ret = doFrame();
+ const auto end = std::chrono::steady_clock::now();
+ const U64 doframe_time_us = (U64)std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
+ LLTrace::sample(LLStatViewer::DOFRAME_TIME_US, doframe_time_us);
}
catch (const LLContinueError&)
{
@@ -1373,6 +1360,14 @@ bool LLAppViewer::frame()
bool LLAppViewer::doFrame()
{
+ // SIGTERM asks for a normal logout. Quitting immediately skips
+ // LogoutRequest and leaves the agent in the region.
+ if (LLApp::consumeSignalQuit() && !mQuitRequested && !isQuitting())
+ {
+ LL_INFOS() << "Terminate signal, logging out before exit" << LL_ENDL;
+ requestQuit();
+ }
+
resumeMainloopTimeout("Main:doFrameStart");
U32 fpsLimitMaxFps = (U32)gSavedSettings.getU32("MaxFPS");
@@ -1770,6 +1765,16 @@ void LLAppViewer::flushLFSIO()
bool LLAppViewer::cleanup()
{
+#if LL_VELOPACK
+ // Apply any pending Velopack update before shutdown
+ if (velopack_is_update_pending())
+ {
+ LL_INFOS("AppInit") << "Applying pending Velopack update on shutdown..." << LL_ENDL;
+ velopack_apply_pending_update(velopack_should_restart_after_update());
+ }
+ velopack_cleanup();
+#endif
+
//ditch LLVOAvatarSelf instance
gAgentAvatarp = NULL;
@@ -1819,7 +1824,15 @@ bool LLAppViewer::cleanup()
// Give any remaining SLPlugin instances a chance to exit cleanly.
LLPluginProcessParent::shutdown();
+ if (LLWatchdog::instanceExists())
+ {
+ // Signal a stop early, so that it will be out
+ // of 1s sleep loop by the time we get to clean it.
+ LLWatchdog::getInstance()->shutdown();
+ }
+
disconnectViewer();
+
LLViewerCamera::deleteSingleton();
LL_INFOS() << "Viewer disconnected" << LL_ENDL;
@@ -1930,6 +1943,9 @@ bool LLAppViewer::cleanup()
if( gViewerWindow)
gViewerWindow->shutdownViews();
+ // Model previews release their decomposition handles while the UI is destroyed.
+ gMeshRepo.shutdownDecomposition();
+
LL_INFOS() << "Cleaning up Inventory" << LL_ENDL;
// Cleanup Inventory after the UI since it will delete any remaining observers
@@ -1950,8 +1966,6 @@ bool LLAppViewer::cleanup()
SUBSYSTEM_CLEANUP(LLAvatarAppearance);
- SUBSYSTEM_CLEANUP(LLPostProcess);
-
LLTracker::cleanupInstance();
// *FIX: This is handled in LLAppViewerWin32::cleanup().
@@ -2120,7 +2134,7 @@ bool LLAppViewer::cleanup()
}
LLSplashScreen::show();
- LLSplashScreen::update(LLTrans::getString("ShuttingDown"));
+ LLSplashScreen::update(LLTrans::getString("ShutdownCleanup"));
LL_INFOS() << "Cleaning up Keyboard & Joystick" << LL_ENDL;
@@ -2149,7 +2163,7 @@ bool LLAppViewer::cleanup()
if (sTextureFetch)
{
sTextureFetch->shutdown();
- sTextureFetch->waitOnPending();
+ sTextureFetch->waitOnPending(10.f);
delete sTextureFetch;
sTextureFetch = NULL;
}
@@ -2194,7 +2208,10 @@ bool LLAppViewer::cleanup()
gSavedSettings.cleanup();
LLUIColorTable::instance().clear();
- LLWatchdog::getInstance()->cleanup();
+ if (LLWatchdog::instanceExists())
+ {
+ LLWatchdog::getInstance()->cleanup();
+ }
LLViewerAssetStatsFF::cleanup();
@@ -2219,6 +2236,10 @@ bool LLAppViewer::cleanup()
SUBSYSTEM_CLEANUP(LLProxy);
LLCore::LLHttp::cleanup();
+ LLSplashScreen::update(LLTrans::getString("CompressingInventoryCache"));
+ LLInventoryModel::waitForPendingCacheWrites();
+ LLSplashScreen::update(LLTrans::getString("ShuttingDown"));
+
ll_close_fail_log();
LLError::LLCallStacks::cleanup();
@@ -2230,6 +2251,8 @@ bool LLAppViewer::cleanup()
LLWorld::deleteSingleton();
LLVoiceClient::deleteSingleton();
LLUI::deleteSingleton();
+ LLGridManager::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
@@ -2242,6 +2265,8 @@ bool LLAppViewer::cleanup()
// deleteSingleton() methods.
LLSingletonBase::deleteAll();
+ LLUICtrlFactory::deleteSingleton();
+
LLSplashScreen::hide();
LL_INFOS() << "Goodbye!" << LL_ENDL;
@@ -2265,6 +2290,8 @@ void LLAppViewer::initGeneralThread()
bool LLAppViewer::initThreads()
{
+ LL_PROFILE_ZONE_SCOPED;
+
static const bool enable_threads = true;
LLImage::initClass(gSavedSettings.getBOOL("TextureNewByteRange"),gSavedSettings.getS32("TextureReverseByteRange"));
@@ -2342,7 +2369,7 @@ void errorCallback(LLError::ELevel level, const std::string &error_string)
LLAppViewer::instance()->writeDebugInfo();
std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME);
- if (!LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB))
+ if (!LLFile::isfile(error_marker_file))
{
// If marker doesn't exist, create a marker with llerror code for next launch
// otherwise don't override existing file
@@ -2370,6 +2397,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;
}
@@ -2394,6 +2424,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()
{
//
@@ -2410,22 +2446,25 @@ void LLAppViewer::initLoggingAndGetLastDuration()
if (mSecondInstance)
{
- LLFile::mkdir(gDirUtilp->getDumpLogsDirPath());
+ if (!gGPUBenchmarkMode)
+ {
+ LLFile::mkdir(gDirUtilp->getDumpLogsDirPath());
- LLUUID uid;
- uid.generate();
- LLError::logToFile(gDirUtilp->getDumpLogsDirPath(uid.asString() + ".log"));
+ LLUUID uid;
+ uid.generate();
+ // Is this even useful?
+ // Originally this wa used to store states, but I don't think it's practical with bugsplat attributes.
+ // So it just spams files now.
+ LLError::logToFile(gDirUtilp->getDumpLogsDirPath(uid.asString() + ".log"));
+ }
}
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
@@ -2472,7 +2511,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())
@@ -2544,7 +2583,10 @@ bool LLAppViewer::loadSettingsFromDirectory(const std::string& location_key,
full_settings_path = gDirUtilp->getExpandedFilename((ELLPath)path_index, file.file_name());
}
- if(settings_group->loadFromFile(full_settings_path, set_defaults, file.persistent))
+ // Be softer for files in the user's folders, user can't just reinstall those
+ bool error_when_no_comment = !set_defaults && location_key != "User";
+
+ if(settings_group->loadFromFile(full_settings_path, set_defaults, file.persistent, error_when_no_comment))
{ // success!
LL_INFOS("Settings") << "Loaded settings file " << full_settings_path << LL_ENDL;
}
@@ -2608,6 +2650,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.
@@ -2980,6 +3092,8 @@ bool LLAppViewer::initConfiguration()
}
}
+ LLGridManager::createInstance();
+
LLSLURL start_slurl;
if (!starting_location.empty())
{
@@ -3022,19 +3136,39 @@ 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.
+ LLGridManager::deleteSingleton();
+ 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;
}
}
// Display splash screen. Must be after above check for previous
// crash as this dialog is always frontmost.
- std::string splash_msg;
- LLStringUtil::format_map_t args;
- args["[APP_NAME]"] = LLTrans::getString("SECOND_LIFE");
- splash_msg = LLTrans::getString("StartupLoading", args);
- LLSplashScreen::show();
- LLSplashScreen::update(splash_msg);
+ if (!gGPUBenchmarkMode)
+ {
+ std::string splash_msg;
+ LLStringUtil::format_map_t args;
+ args["[APP_NAME]"] = LLTrans::getString("SECOND_LIFE");
+ splash_msg = LLTrans::getString("StartupLoading", args);
+ LLSplashScreen::show();
+ LLSplashScreen::update(splash_msg);
+ }
//LLVolumeMgr::initClass();
LLVolumeMgr* volume_manager = new LLVolumeMgr();
@@ -3063,25 +3197,39 @@ bool LLAppViewer::initConfiguration()
// This happens AFTER LLSplashScreen::show(). That may or may not be
// important.
//
- if (mSecondInstance && !gSavedSettings.getBOOL("AllowMultipleViewers"))
+ if (mSecondInstance
+ && !gGPUBenchmarkMode
+ && !gSavedSettings.getBOOL("AllowMultipleViewers"))
{
OSMessageBox(
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.
+ LLGridManager::deleteSingleton();
+ LLSingletonBase::deleteAll();
+ cleanupConsole();
return false;
}
if (mSecondInstance)
{
- // This is the second instance of SL. Mute voice,
- // but make sure the setting is *not* persisted.
- // Also see LLVivoxVoiceClient::voiceEnabled()
+ // This is the second concurrent instance of SL.
+ // Disable voice for this session only, user should
+ // be able to enable voice manually, after that it
+ // works the same way as on primary instance.
LLControlVariable* enable_voice = gSavedSettings.getControl("EnableVoiceChat");
- if (enable_voice)
+ if (enable_voice && enable_voice->getValue().asBoolean())
{
+ LL_DEBUGS("AppInit") << "Disabling voice for this session only" << LL_ENDL;
+ // Will be saved as mValues[2] which does not get written to the file.
+ // This feels like a hack, but otherwise way too many controls have to
+ // be tracked manually instead of using xmls' control_name.
const bool DO_NOT_PERSIST = false;
- enable_voice->setValue(LLSD(false), DO_NOT_PERSIST);
+ LLSD::Boolean new_value = false;
+ enable_voice->setValue(new_value, DO_NOT_PERSIST);
}
}
@@ -3103,12 +3251,65 @@ bool LLAppViewer::initConfiguration()
return true; // Config was successful.
}
+static void apply_localized_comments(LLControlGroup& group, const LLSD& comments)
+{
+ for (LLSD::map_const_iterator it = comments.beginMap(); it != comments.endMap(); ++it)
+ {
+ LLControlVariablePtr control = group.getControl(it->first);
+ if (control.notNull())
+ {
+ const std::string comment = it->second.asString();
+ if (!comment.empty())
+ {
+ control->setComment(comment);
+ }
+ }
+ }
+}
+
+void LLAppViewer::loadLocalizedSettingsComments()
+{
+ std::string lang = LLUI::getLanguage();
+ if (lang.empty() || lang == "en")
+ {
+ return;
+ }
+
+ std::string path = gDirUtilp->findSkinnedFilename(
+ LLDir::XUI, "settings_comments.xml", LLDir::CURRENT_SKIN);
+ if (path.empty())
+ {
+ return;
+ }
+
+ llifstream infile;
+ infile.open(path.c_str());
+ if (!infile.is_open())
+ {
+ return;
+ }
+
+ LLSD comments;
+ if (LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXML(comments, infile))
+ {
+ infile.close();
+ LL_WARNS("Settings") << "Failed to parse localized settings comments file: " << path << LL_ENDL;
+ return;
+ }
+ infile.close();
+
+ apply_localized_comments(gSavedSettings, comments);
+ apply_localized_comments(gSavedPerAccountSettings, comments);
+}
+
// The following logic is replicated in initConfiguration() (to be able to get
// some initial strings before we've finished initializing enough to know the
// current language) and also in init() (to initialize for real). Somehow it
// keeps growing, necessitating a method all its own.
void LLAppViewer::initStrings()
{
+ LL_PROFILE_ZONE_SCOPED;
+
std::string strings_file = "strings.xml";
std::string strings_path_full = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, strings_file);
if (strings_path_full.empty() || !LLFile::isfile(strings_path_full))
@@ -3120,13 +3321,11 @@ void LLAppViewer::initStrings()
}
else
{
- llstat st;
- int rc = LLFile::stat(strings_path_full, &st);
- if (rc != 0)
+ if (!LLFile::exists(strings_path_full))
{
- crash_reason = "The file '" + strings_path_full + "' failed to get status. Error code: " + std::to_string(rc);
+ crash_reason = "The file '" + strings_path_full + "' doesn't seem to exist";
}
- else if (S_ISDIR(st.st_mode))
+ else if (LLFile::isdir(strings_path_full))
{
crash_reason = "The filename '" + strings_path_full + "' is a directory name";
}
@@ -3198,6 +3397,7 @@ void LLAppViewer::sendOutOfDiskSpaceNotification()
bool LLAppViewer::initWindow()
{
+ LL_PROFILE_ZONE_SCOPED;
LL_INFOS("AppInit") << "Initializing window..." << LL_ENDL;
// store setting in a global for easy access and modification
@@ -3209,7 +3409,7 @@ bool LLAppViewer::initWindow()
LLViewerWindow::Params window_params;
window_params
.title(gWindowTitle)
- .name(VIEWER_WINDOW_CLASSNAME)
+ .name(sWindowClass)
.x(gSavedSettings.getS32("WindowX"))
.y(gSavedSettings.getS32("WindowY"))
.width(gSavedSettings.getU32("WindowWidth"))
@@ -3251,20 +3451,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();
@@ -3334,16 +3574,6 @@ bool LLAppViewer::initWindow()
return true;
}
-bool LLAppViewer::isUpdaterMissing()
-{
- return mUpdaterNotFound;
-}
-
-bool LLAppViewer::waitForUpdater()
-{
- return !gSavedSettings.getBOOL("CmdLineSkipUpdater") && !mUpdaterNotFound && !gNonInteractive;
-}
-
void LLAppViewer::writeDebugInfo(bool isStatic)
{
#if LL_WINDOWS && LL_BUGSPLAT
@@ -3402,7 +3632,7 @@ LLSD LLAppViewer::getViewerInfo() const
url = LLTrans::getString("RELEASE_NOTES_BASE_URL");
if (!LLStringUtil::endsWith(url, "/"))
url += "/";
- url += LLURI::escape(versionInfo.getVersion()) + ".html";
+ url += "#" + LLURI::escape(versionInfo.getVersion()); // + ".html";
}
info["VIEWER_RELEASE_NOTES_URL"] = url;
@@ -3485,7 +3715,7 @@ LLSD LLAppViewer::getViewerInfo() const
info["RENDER_QUALITY"] = (F32)gSavedSettings.getU32("RenderQualityPerformance");
info["TEXTURE_MEMORY"] = LLSD::Integer(gGLManager.mVRAM);
-#if LL_DARWIN
+#if LL_DARWIN || LL_SDL_WINDOW
info["HIDPI"] = gHiDPISupport;
#endif
@@ -3515,7 +3745,7 @@ LLSD LLAppViewer::getViewerInfo() const
info["VOICE_VERSION"] = LLTrans::getString("NotConnected");
}
-#if !_M_ARM64 // !LL_LINUX
+#if defined(LL_CEF)
std::ostringstream cef_ver_codec;
cef_ver_codec << "Dullahan: ";
cef_ver_codec << DULLAHAN_VERSION_MAJOR;
@@ -3541,11 +3771,9 @@ LLSD LLAppViewer::getViewerInfo() const
cef_ver_codec << CHROME_VERSION_PATCH;
info["LIBCEF_VERSION"] = cef_ver_codec.str();
-#else
- info["LIBCEF_VERSION"] = "Undefined";
#endif
-#if !_M_ARM64 // !LL_LINUX
+#if defined(LL_VLC)
std::ostringstream vlc_ver_codec;
vlc_ver_codec << LIBVLC_VERSION_MAJOR;
vlc_ver_codec << ".";
@@ -3553,14 +3781,13 @@ LLSD LLAppViewer::getViewerInfo() const
vlc_ver_codec << ".";
vlc_ver_codec << LIBVLC_VERSION_REVISION;
info["LIBVLC_VERSION"] = vlc_ver_codec.str();
-#else
- info["LIBVLC_VERSION"] = "Undefined";
#endif
- S32 packets_in = (S32)LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_IN);
+ LLTrace::Recording& recording = LLViewerStats::instance().getRecording();
+ S32 packets_in = (S32)recording.getSum(LLStatViewer::PACKETS_IN);
if (packets_in > 0)
{
- info["PACKETS_LOST"] = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_LOST);
+ info["PACKETS_LOST"] = recording.getSum(LLStatViewer::PACKETS_LOST);
info["PACKETS_IN"] = packets_in;
info["PACKETS_PCT"] = 100.f*info["PACKETS_LOST"].asReal() / info["PACKETS_IN"].asReal();
}
@@ -3613,6 +3840,13 @@ std::string LLAppViewer::getViewerInfoString(bool default_string) const
{
args[ii->first] = LLTrans::getString("none_text", default_string);
}
+ else if (ii->second.isBoolean())
+ {
+ // LLSD intentionally renders false as an empty string to
+ // preserve string-to-boolean round trips. Viewer info is
+ // human-readable, so show both boolean states explicitly.
+ args[ii->first] = ii->second.asBoolean() ? "true" : "false";
+ }
else
{
// don't forget to render value asString()
@@ -3745,8 +3979,12 @@ void LLAppViewer::writeSystemInfo()
gDebugInfo["CPUInfo"]["CPUFamily"] = gSysCPU.getFamily();
gDebugInfo["CPUInfo"]["CPUMhz"] = (S32)gSysCPU.getMHz();
gDebugInfo["CPUInfo"]["CPUAltivec"] = gSysCPU.hasAltivec();
- gDebugInfo["CPUInfo"]["CPUSSE"] = gSysCPU.hasSSE();
- gDebugInfo["CPUInfo"]["CPUSSE2"] = gSysCPU.hasSSE2();
+ gDebugInfo["CPUInfo"]["CPUSSE42"] = gSysCPU.hasSSE42();
+ gDebugInfo["CPUInfo"]["CPUSSE4a"] = gSysCPU.hasSSE4a();
+ gDebugInfo["CPUInfo"]["CPUAVX"] = gSysCPU.hasAVX();
+ gDebugInfo["CPUInfo"]["CPUAVX2"] = gSysCPU.hasAVX2();
+ gDebugInfo["CPUInfo"]["CPUAVX512F"] = gSysCPU.hasAVX512F();
+
gDebugInfo["RAMInfo"]["Physical"] = LLSD::Integer(gSysMemory.getPhysicalMemoryKB().value());
gDebugInfo["RAMInfo"]["Allocated"] = LLSD::Integer(gMemoryAllocated.valueInUnits<LLUnits::Kilobytes>());
@@ -3990,18 +4228,30 @@ bool LLAppViewer::getMarkerData(const std::string& marker_name, std::string& dat
void LLAppViewer::processMarkerFiles()
{
+ if (gGPUBenchmarkMode)
+ {
+ // Skipping marker file processing in GPU benchmark mode
+ mSecondInstance = true;
+ initLoggingAndGetLastDuration();
+ return;
+ }
//We've got 4 things to test for here
// - Other Process Running (SecondLife.exec_marker present, locked)
// - Freeze (SecondLife.exec_marker present, not locked)
// - LLError Crash (SecondLife.llerror_marker present)
// - Other Crash (SecondLife.error_marker present)
- // These checks should also remove these files for the last 2 cases if they currently exist
+ // - Watchdog freeze (SecondLife.watchdog_marker present)
+ // - Failed to initialize (SecondLife.inited_marker not present)
+ // - Potentially killed by task manager or computer
+ // didn't recover from hibernation (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;
bool marker_is_same_version = true;
// first, look for the marker created at startup and deleted on a clean exit
mMarkerFileName = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,MARKER_FILE_NAME);
- if (LLAPRFile::isExist(mMarkerFileName, NULL, LL_APR_RB))
+ if (LLFile::isfile(mMarkerFileName))
{
// File exists...
// first, read it to see if it was created by the same version (we need this later)
@@ -4048,13 +4298,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
@@ -4093,7 +4338,7 @@ void LLAppViewer::processMarkerFiles()
// check for any last exec event report based on whether or not it happened during logout
// (the logout marker is created when logout begins)
std::string logout_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, LOGOUT_MARKER_FILE_NAME);
- if(LLAPRFile::isExist(logout_marker_file, NULL, LL_APR_RB))
+ if(LLFile::isfile(logout_marker_file))
{
if (markerIsSameVersion(logout_marker_file))
{
@@ -4105,25 +4350,33 @@ void LLAppViewer::processMarkerFiles()
{
LL_INFOS("MarkerFile") << "Logout crash marker '"<< logout_marker_file << "' found, but versions did not match" << LL_ENDL;
}
- LLAPRFile::remove(logout_marker_file);
+ LLFile::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);
- if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB))
+ 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(LLFile::isfile(error_marker_file))
{
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;
@@ -4134,7 +4387,76 @@ 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);
+ LLFile::remove(error_marker_file);
+ if (LLFile::isfile(watchdog_marker_file))
+ {
+ // If viewer crashed after a freeze was detected,
+ // crash still takes precendence. Just clear watchdog.
+ 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 (LLFile::isfile(watchdog_marker_file))
+ {
+ // 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, OS did not recover from hibernation 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 (LLFile::isfile(close_marker_file))
+ {
+ // Unfortunately we can't reliably distinguish
+ // task manager's case from genuine shutdown, so we
+ // have to report all of them as the same thing.
+ // Todo: but we can distinguish hibernation, might want
+ // to simply not report it as an issue.
+ if (markerIsSameVersion(close_marker_file))
+ {
+ gLastExecEvent = LAST_EXEC_OS_EVENT;
+ LL_INFOS("MarkerFile") << "'Close' marker '" << close_marker_file << "' found, setting LastExecEvent to OS_EVENT"
+ << LL_ENDL;
+ }
+ }
+ else if ((LAST_EXEC_UNKNOWN == gLastExecEvent)
+ && !LLFile::isfile(inited_marker_file))
+ {
+ // 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 (LLFile::isfile(watchdog_marker_file))
+ {
+ removeWatchdogMarker();
+ }
+ if (LLFile::isfile(inited_marker_file))
+ {
+ removeInitedMarker();
+ }
+ if (LLFile::isfile(close_marker_file))
+ {
+ removeCloseRequestMarker();
}
#if LL_DARWIN
@@ -4161,7 +4483,7 @@ void LLAppViewer::removeMarkerFiles()
if (mMarkerFile.getFileHandle())
{
mMarkerFile.close() ;
- LLAPRFile::remove( mMarkerFileName );
+ LLFile::remove( mMarkerFileName );
LL_DEBUGS("MarkerFile") << "removed exec marker '"<<mMarkerFileName<<"'"<< LL_ENDL;
}
else
@@ -4172,13 +4494,15 @@ void LLAppViewer::removeMarkerFiles()
if (mLogoutMarkerFile.getFileHandle())
{
mLogoutMarkerFile.close();
- LLAPRFile::remove( mLogoutMarkerFileName );
+ LLFile::remove( mLogoutMarkerFileName );
LL_DEBUGS("MarkerFile") << "removed logout marker '"<<mLogoutMarkerFileName<<"'"<< LL_ENDL;
}
else
{
LL_WARNS("MarkerFile") << "logout marker '"<<mLogoutMarkerFileName<<"' not open"<< LL_ENDL;
}
+ removeCloseRequestMarker();
+ removeWatchdogMarker();
}
else
{
@@ -4237,6 +4561,13 @@ void LLAppViewer::requestQuit()
// This prevents the halfway-logged-in avatar from hanging around inworld for a couple minutes.
if (region)
{
+ // We probably don't have caps to do it, and it might
+ // arrive after logout finishes, but attempt to send stats
+ if (region->capabilitiesReceived())
+ {
+ constexpr bool include_preferences = true;
+ send_viewer_stats(include_preferences);
+ }
sendLogoutRequest();
}
@@ -4337,6 +4668,14 @@ void LLAppViewer::abortQuit()
mClosingFloaters = false;
}
+void LLAppViewer::sendViewerStatistics(bool include_preferences)
+{
+ if (!gDisconnected)
+ {
+ send_viewer_stats(include_preferences);
+ }
+}
+
void LLAppViewer::migrateCacheDirectory()
{
#if LL_WINDOWS || LL_DARWIN
@@ -4398,7 +4737,7 @@ void LLAppViewer::migrateCacheDirectory()
LLFile::remove(ds_store);
}
#endif
- if (LLFile::rmdir(old_cache_dir) != 0)
+ if (LLFile::remove(old_cache_dir) != 0)
{
LL_WARNS() << "could not delete old cache directory " << old_cache_dir << LL_ENDL;
}
@@ -4440,6 +4779,7 @@ U32 LLAppViewer::getObjectCacheVersion()
bool LLAppViewer::initCache()
{
+ LL_PROFILE_ZONE_SCOPED;
mPurgeCache = false;
bool read_only = mSecondInstance;
LLAppViewer::getTextureCache()->setReadOnly(read_only) ;
@@ -4533,19 +4873,22 @@ bool LLAppViewer::initCache()
if (mPurgeCache)
{
- LLSplashScreen::update(LLTrans::getString("StartupClearingCache"));
- purgeCache();
+ LLSplashScreen::update(LLTrans::getString("StartupClearingCache"));
+ purgeCache();
// clear the new C++ file system based cache
LLDiskCache::getInstance()->clearCache();
- }
- else
+ }
+ else if (gSavedSettings.getBOOL("PurgeDiskCacheOnStartup"))
{
// purge excessive files from the new file system based cache
LLDiskCache::getInstance()->purge();
}
+
+ // Start disk cache purge thread to
+ // purge excessive files from the file system based cache
+ LLAppViewer::getPurgeDiskCacheThread()->start();
}
- LLAppViewer::getPurgeDiskCacheThread()->start();
LLSplashScreen::update(LLTrans::getString("StartupInitializingTextureCache"));
@@ -4588,15 +4931,69 @@ void LLAppViewer::loadKeyBindings()
// As per GHI #4498, remove old, stale CEF cache folders from previous sessions
void LLAppViewer::purgeCefStaleCaches()
{
+ LL_PROFILE_ZONE_SCOPED;
// TODO: we really shouldn't use a hard coded name for the cache folder here...
const std::string browser_parent_cache = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "cef_cache");
- if (LLFile::isdir(browser_parent_cache))
+ if (!LLFile::isdir(browser_parent_cache))
+ {
+ return;
+ }
+ // We are using a fixed name to not leave stale folders
+ // around in case something goes wrong on startup.
+ const std::string holder_cache_name = browser_parent_cache + "_rename";
+
+ // Try to rename the entire directory first
+ if (LLFile::rename(browser_parent_cache, holder_cache_name) == 0)
+ {
+ LL_DEBUGS("AppInit") << "Successfully renamed CEF cache folder for deletion" << LL_ENDL;
+ }
+ else
+ {
+ // Rename failed (likely another instance has files open in the cache)
+ // Create holder folder and move individual subfolders instead
+ LL_DEBUGS("AppInit") << "Could not rename CEF cache folder (may be in use), moving individual folders" << LL_ENDL;
+
+ if (!LLFile::isdir(holder_cache_name) && LLFile::mkdir(holder_cache_name) != 0)
+ {
+ LL_WARNS() << "Failed to create holder folder: " << holder_cache_name << LL_ENDL;
+ // Attept normal cleanup
+ gDirUtilp->deleteDirAndContents(browser_parent_cache);
+ return;
+ }
+
+ // Iterate through subdirectories in the cache folder
+ LLDirIterator dir_iter(browser_parent_cache, "*");
+ std::string subfolder_name;
+ while (dir_iter.next(subfolder_name))
+ {
+ if (subfolder_name == "." || subfolder_name == "..")
+ {
+ continue;
+ }
+
+ std::string source_path = browser_parent_cache + gDirUtilp->getDirDelimiter() + subfolder_name;
+ std::string dest_path = holder_cache_name + gDirUtilp->getDirDelimiter() + subfolder_name;
+
+ // If folder is in use, move will fail, don't delete it.
+ LLFile::rename(source_path, dest_path);
+ }
+ }
+
+ // Post deletion task to the General work queue to avoid blocking the main thread
+ if (auto queue = LL::WorkQueue::getInstance("General"))
+ {
+ // Alternatively throw it at LLPurgeDiskCacheThread to clean
+ // it during periodic purges.
+ queue->post([holder_cache_name]()
+ {
+ LL_PROFILE_ZONE_NAMED("cef_cache_cleanup");
+ gDirUtilp->deleteDirAndContents(holder_cache_name);
+ });
+ }
+ else
{
- // This is a sledgehammer approach - nukes the cef_cache dir entirely
- // which is then recreated the first time a CEF instance creates an
- // individual cache folder. If we ever decide to retain some folders
- // e.g. Search UI cache - then we will need a more granular approach.
- gDirUtilp->deleteDirAndContents(browser_parent_cache);
+ LL_WARNS() << "Failed to get General work queue, deleting CEF cache synchronously" << LL_ENDL;
+ gDirUtilp->deleteDirAndContents(holder_cache_name);
}
}
@@ -4615,7 +5012,19 @@ void LLAppViewer::purgeCacheImmediate()
{
LL_INFOS("AppCache") << "Purging Object Cache and Texture Cache immediately..." << LL_ENDL;
LLAppViewer::getTextureCache()->purgeCache(LL_PATH_CACHE, false);
- LLVOCache::getInstance()->removeCache(LL_PATH_CACHE, true);
+ if (LLVOCache::instanceExists())
+ {
+ LLVOCache::getInstance()->removeCache(LL_PATH_CACHE, true);
+ }
+ else if (!mSecondInstance)
+ {
+ // LLVOCache requires parameters to be initialized, if it's not there, try manually
+ std::string mask = "*";
+ std::string cache_dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "objectcache");
+ LL_INFOS() << "Removing cache at " << cache_dir << LL_ENDL;
+ gDirUtilp->deleteFilesInDir(cache_dir, mask); //delete all files
+ LLFile::remove(cache_dir);
+ }
}
std::string LLAppViewer::getSecondLifeTitle() const
@@ -4783,6 +5192,8 @@ std::string get_name_cache_filename(const std::string &base_file, const std::str
void LLAppViewer::loadNameCache()
{
+ LL_PROFILE_ZONE_SCOPED;
+
// display names cache
std::string filename = get_name_cache_filename("avatar_name_cache", "xml");
LL_INFOS("AvNameCache") << filename << LL_ENDL;
@@ -4888,6 +5299,11 @@ void LLAppViewer::idle()
LLMortician::updateClass();
LLFilePickerThread::clearDead(); //calls LLFilePickerThread::notify()
LLDirPickerThread::clearDead();
+
+ if (LLWebsocketMgr::instanceExists())
+ {
+ LLWebsocketMgr::instance().update();
+ }
F32 dt_raw = idle_timer.getElapsedTimeAndResetF32();
LLGLTFMaterialList::flushUpdates();
@@ -4895,6 +5311,7 @@ void LLAppViewer::idle()
static LLCachedControl<U32> downscale_method(gSavedSettings, "RenderDownScaleMethod");
gGLManager.mDownScaleMethod = downscale_method;
LLImageGL::updateClass();
+ LLUIImage::updateClass();
// Service the WorkQueue we use for replies from worker threads.
// Use function statics for the timeslice setting so we only have to fetch
@@ -5544,7 +5961,85 @@ void LLAppViewer::createErrorMarker(eLastExecEvent error_code) const
bool LLAppViewer::errorMarkerExists() const
{
std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME);
- return LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB);
+ return LLFile::isfile(error_marker_file);
+}
+
+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)
+ {
+ 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, ENOENT);
+ }
}
void LLAppViewer::outOfMemorySoftQuit()
@@ -5609,7 +6104,6 @@ void LLAppViewer::idleNetwork()
pingMainloopTimeout("idleNetwork");
gObjectList.mNumNewObjects = 0;
- S32 total_decoded = 0;
static LLCachedControl<bool> speed_test(gSavedSettings, "SpeedTest", false);
if (!speed_test())
@@ -5617,64 +6111,56 @@ void LLAppViewer::idleNetwork()
LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("idle network"); //LL_RECORD_BLOCK_TIME(FTM_IDLE_NETWORK); // decode
LLTimer check_message_timer;
- // Read all available packets from network
const S64 frame_count = gFrameCount; // U32->S64
- F32 total_time = 0.0f;
+ S32 total_decoded = 0;
+ // Process packets from network
+ LockMessageChecker lmc(gMessageSystem);
+ while (lmc.checkAllMessages(frame_count, gServicePump))
{
- bool needs_drain = false;
- LockMessageChecker lmc(gMessageSystem);
- while (lmc.checkAllMessages(frame_count, gServicePump))
- {
- if (gDoDisconnect)
- {
- // We're disconnecting, don't process any more messages from the server
- // We're usually disconnecting due to either network corruption or a
- // server going down, so this is OK.
- break;
- }
-
- total_decoded++;
+ ++total_decoded;
- if (total_decoded > MESSAGE_MAX_PER_FRAME)
+ // Time-box processing of network packets to prevent framerate catastrophe
+ if (check_message_timer.getElapsedTimeF32() >= CheckMessagesMaxTime)
+ {
+ // Drain the socket buffer so we know how many messages remain to process
+ S32 num_buffered_packets = gMessageSystem->drainUdpSocket();
+ if (num_buffered_packets > total_decoded)
{
- needs_drain = true;
- break;
+ // Grow CheckMessagesMaxTime until we process more packets each frame than arrive.
+ // This might spiral out of control on very slow computers on fast networks when
+ // the bandwidth settings are too high. There is a mechanism for providing backpressure
+ // to network bandwidth but it may be inadequate for the task
+ // (see LLViewerThrottle::updateDynamicThrottle() for more details).
+ CheckMessagesMaxTime *= 1.035f; // 3.5% ~= 2x in 20 frames, ~8x in 60 frames
}
-
- // Prevent slow packets from completely destroying the frame rate.
- // This usually happens due to clumps of avatars taking huge amount
- // of network processing time (which needs to be fixed, but this is
- // a good limit anyway).
- total_time = check_message_timer.getElapsedTimeF32();
- if (total_time >= CheckMessagesMaxTime)
+ else if (num_buffered_packets == 0)
{
- needs_drain = true;
- break;
+ // Reset CheckMessagesMaxTime to default value
+ CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME;
}
+ break;
}
- if (needs_drain || gMessageSystem->mPacketRing.getNumBufferedPackets() > 0)
+
+ if (total_decoded > MESSAGE_MAX_PER_FRAME)
{
- // Rather than allow packets to silently backup on the socket
- // we drain them into our own buffer so we know how many exist.
- S32 num_buffered_packets = gMessageSystem->drainUdpSocket();
- if (num_buffered_packets > 0)
- {
- // Increase CheckMessagesMaxTime so that we will eventually catch up
- CheckMessagesMaxTime *= 1.035f; // 3.5% ~= 2x in 20 frames, ~8x in 60 frames
- }
+ // MESSAGE_MAX_PER_FRAME is very high (400)
+ // We expect to run out of time before reaching here, but just in case...
+ gMessageSystem->drainUdpSocket();
+ break;
}
- else
+
+ if (gDoDisconnect)
{
- // Reset CheckMessagesMaxTime to default value
- CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME;
+ // We're disconnecting so no need to process packets.
+ break;
}
+ }
- // Handle per-frame message system processing.
+ // Handle per-frame message system processing.
- static LLCachedControl<F32> ack_collection_time(gSavedSettings, "AckCollectTime", 0.1f);
- lmc.processAcks(ack_collection_time());
- }
+ static LLCachedControl<F32> ack_collection_time(gSavedSettings, "AckCollectTime", 0.1f);
+ lmc.processAcks(ack_collection_time());
}
add(LLStatViewer::NUM_NEW_OBJECTS, gObjectList.mNumNewObjects);
@@ -5801,11 +6287,13 @@ void LLAppViewer::forceErrorBreakpoint()
LL_WARNS() << "Forcing a deliberate breakpoint" << LL_ENDL;
#ifdef LL_WINDOWS
DebugBreak();
-#elif __i386__ || __x86_64__
+#else
+#if defined(LL_X86) || defined(LL_X86_64)
asm ("int $3");
#else
__builtin_trap();
#endif
+#endif
return;
}
@@ -5993,6 +6481,32 @@ F32 LLAppViewer::getMainloopTimeoutSec() const
}
}
+std::string LLAppViewer::getMainloopWatchdogState() const
+{
+ if (!mMainloopTimeout)
+ {
+ return std::string();
+ }
+ std::string state = mMainloopTimeout->getState();
+
+ if (mMainloopTimeout->hasExpired())
+ {
+ return "Expired at " + state;
+ }
+
+ // Check if the watchdog is currently active (timer started)
+ if (!mMainloopTimeout->isAlive())
+ {
+ // Timer is not running, meaning watchdog is paused/stopped
+ if (state.empty())
+ {
+ return "Paused";
+ }
+ return "Paused at " + state;
+ }
+ return state;
+}
+
void LLAppViewer::handleLoginComplete()
{
gLoggedInTime.start();