diff options
| author | Jonathan "Geenz" Goodman <geenz@lindenlab.com> | 2026-10-13 14:30:21 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-10-13 14:30:21 -0400 |
| commit | 8ba5d1a8adc47c21b23d9482e72de9517f9a98fc (patch) | |
| tree | f97d67c8ee574c6ebaac8d0d8ba120d041dca051 /indra/newview/llappviewer.cpp | |
| parent | 18648fc51e2a0c750182d8aa3814ed5b6bf8445e (diff) | |
| parent | efc7d2d7bb542ac905cb7ebd82ee329523f95608 (diff) | |
Merge pull request #6193 from secondlife/release/26.4
Release/26.4
Diffstat (limited to 'indra/newview/llappviewer.cpp')
| -rw-r--r-- | indra/newview/llappviewer.cpp | 595 |
1 files changed, 478 insertions, 117 deletions
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 0a2b8a7d3d..3e715ecf93 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" @@ -114,6 +115,7 @@ #include "lllocalbitmaps.h" #include "llperfstats.h" #include "llgltfmateriallist.h" +#include "llwebsocketmgr.h" // Linden library includes #include "llavatarnamecache.h" @@ -136,15 +138,17 @@ #include "stringize.h" #include "llcoros.h" #include "llexception.h" -#if !LL_LINUX #include "cef/dullahan_version.h" #include "vlc/libvlc_version.h" -#endif // LL_LINUX #if LL_DARWIN #include "llwindowmacosx.h" #endif +#if LL_SDL_WINDOW +#include "llwindowsdl.h" +#endif + // Third party library includes #include <boost/bind.hpp> #include <boost/algorithm/string.hpp> @@ -186,7 +190,6 @@ #include "lltracker.h" #include "llviewerparcelmgr.h" #include "llworldmapview.h" -#include "llpostprocess.h" #include "lldebugview.h" #include "llconsole.h" @@ -269,10 +272,6 @@ using namespace LL; // define a self-registering event API object #include "llappviewerlistener.h" -#if LL_LINUX && LL_GTK -#include "glib.h" -#endif // (LL_LINUX) && LL_GTK - #ifdef LL_DISCORD #define DISCORDPP_IMPLEMENTATION #include <discordpp.h> @@ -298,10 +297,17 @@ extern bool gRandomizeFramerate; extern bool gPeriodicSlowFrame; extern bool gDebugGL; -#if LL_DARWIN +#if LL_DARWIN || LL_SDL_WINDOW extern bool gHiDPISupport; #endif +#if LL_WINDOWS +extern bool gGPUBenchmarkMode; +#else +static constexpr bool gGPUBenchmarkMode = false; +#endif // LL_WINDOWS + + //////////////////////////////////////////////////////////// // All from the last globals push... @@ -384,6 +390,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; //---------------------------------------------------------------------------- @@ -550,7 +558,7 @@ static void settings_to_globals() 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; @@ -576,6 +584,9 @@ static void settings_to_globals() #if LL_DARWIN LLWindowMacOSX::sUseMultGL = gSavedSettings.getBOOL("RenderAppleUseMultGL"); +#endif + +#if LL_DARWIN || LL_SDL_WINDOW gHiDPISupport = gSavedSettings.getBOOL("RenderHiDPI"); #endif } @@ -689,6 +700,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 @@ -841,6 +858,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(); @@ -939,6 +958,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 ; @@ -1119,17 +1148,17 @@ bool LLAppViewer::init() gGLActive = false; -//#if LL_RELEASE_FOR_DOWNLOAD - // Launch VVM update check +#if !LL_LINUX + // Launch VVM update check (Linux updater is future work: Velopack port) if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") && !gNonInteractive) { 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 @@ -1727,7 +1756,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; @@ -1838,6 +1875,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 @@ -1858,8 +1898,6 @@ bool LLAppViewer::cleanup() SUBSYSTEM_CLEANUP(LLAvatarAppearance); - SUBSYSTEM_CLEANUP(LLPostProcess); - LLTracker::cleanupInstance(); // *FIX: This is handled in LLAppViewerWin32::cleanup(). @@ -2028,7 +2066,7 @@ bool LLAppViewer::cleanup() } LLSplashScreen::show(); - LLSplashScreen::update(LLTrans::getString("ShuttingDown")); + LLSplashScreen::update(LLTrans::getString("ShutdownCleanup")); LL_INFOS() << "Cleaning up Keyboard & Joystick" << LL_ENDL; @@ -2057,7 +2095,7 @@ bool LLAppViewer::cleanup() if (sTextureFetch) { sTextureFetch->shutdown(); - sTextureFetch->waitOnPending(); + sTextureFetch->waitOnPending(10.f); delete sTextureFetch; sTextureFetch = NULL; } @@ -2102,7 +2140,10 @@ bool LLAppViewer::cleanup() gSavedSettings.cleanup(); LLUIColorTable::instance().clear(); - LLWatchdog::getInstance()->cleanup(); + if (LLWatchdog::instanceExists()) + { + LLWatchdog::getInstance()->cleanup(); + } LLViewerAssetStatsFF::cleanup(); @@ -2127,6 +2168,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(); @@ -2138,6 +2183,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 @@ -2254,7 +2301,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 @@ -2282,6 +2329,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; } @@ -2306,6 +2356,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() { // @@ -2322,22 +2378,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 @@ -2384,7 +2443,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()) @@ -2523,6 +2582,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. @@ -2895,6 +3024,8 @@ bool LLAppViewer::initConfiguration() } } + LLGridManager::createInstance(); + LLSLURL start_slurl; if (!starting_location.empty()) { @@ -2937,19 +3068,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(); @@ -2978,12 +3129,20 @@ 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; } @@ -3024,6 +3183,57 @@ 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 @@ -3043,13 +3253,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"; } @@ -3438,7 +3646,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 @@ -3468,7 +3676,6 @@ LLSD LLAppViewer::getViewerInfo() const info["VOICE_VERSION"] = LLTrans::getString("NotConnected"); } -#if !LL_LINUX std::ostringstream cef_ver_codec; cef_ver_codec << "Dullahan: "; cef_ver_codec << DULLAHAN_VERSION_MAJOR; @@ -3494,11 +3701,7 @@ LLSD LLAppViewer::getViewerInfo() const cef_ver_codec << CHROME_VERSION_PATCH; info["LIBCEF_VERSION"] = cef_ver_codec.str(); -#else - info["LIBCEF_VERSION"] = "Undefined"; -#endif -#if !LL_LINUX std::ostringstream vlc_ver_codec; vlc_ver_codec << LIBVLC_VERSION_MAJOR; vlc_ver_codec << "."; @@ -3506,14 +3709,12 @@ 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(); } @@ -3566,6 +3767,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() @@ -3698,8 +3906,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>()); @@ -3943,18 +4155,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) @@ -4041,7 +4265,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)) { @@ -4053,14 +4277,16 @@ 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); } // Refine based on whether or not a marker created during // a crash is found or if wathdog caught a freeze. // Bugsplat will set correct state in bugsplatSendLog. std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); std::string watchdog_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); - if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) + 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) @@ -4088,8 +4314,8 @@ void LLAppViewer::processMarkerFiles() { LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' marker found, but versions did not match" << LL_ENDL; } - LLAPRFile::remove(error_marker_file); - if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + 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. @@ -4098,11 +4324,13 @@ void LLAppViewer::processMarkerFiles() } else { - // so only check watchdog marker if there is no error marker. - if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + if (LAST_EXEC_UNKNOWN == gLastExecEvent + || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent) { - if (LAST_EXEC_UNKNOWN == gLastExecEvent - || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent) + // If viewer crashed after a freeze was detected, + // crash still takes precendence. + // So only check watchdog marker if there is no error marker. + if (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, @@ -4114,9 +4342,49 @@ void LLAppViewer::processMarkerFiles() << LL_ENDL; } } - removeWatchdogMarker(); + // 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 if (!mSecondInstance && gLastExecEvent != LAST_EXEC_NORMAL) @@ -4142,7 +4410,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 @@ -4153,13 +4421,14 @@ 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 @@ -4219,6 +4488,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(); } @@ -4319,6 +4595,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 @@ -4380,7 +4664,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; } @@ -4666,7 +4950,7 @@ void LLAppViewer::purgeCacheImmediate() 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::rmdir(cache_dir); + LLFile::remove(cache_dir); } } @@ -4942,6 +5226,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(); @@ -4949,6 +5238,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 @@ -5598,7 +5888,60 @@ 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 @@ -5616,12 +5959,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); } } @@ -5687,7 +6031,6 @@ void LLAppViewer::idleNetwork() pingMainloopTimeout("idleNetwork"); gObjectList.mNumNewObjects = 0; - S32 total_decoded = 0; static LLCachedControl<bool> speed_test(gSavedSettings, "SpeedTest", false); if (!speed_test()) @@ -5695,64 +6038,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)) + ++total_decoded; + + // Time-box processing of network packets to prevent framerate catastrophe + if (check_message_timer.getElapsedTimeF32() >= CheckMessagesMaxTime) { - if (gDoDisconnect) + // 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) { - // 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; + // 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 } - - total_decoded++; - - if (total_decoded > MESSAGE_MAX_PER_FRAME) + else if (num_buffered_packets == 0) { - needs_drain = true; - break; - } - - // 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) - { - 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); @@ -6073,6 +6408,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(); |
