diff options
Diffstat (limited to 'indra/newview/llappviewer.cpp')
| -rw-r--r-- | indra/newview/llappviewer.cpp | 522 |
1 files changed, 451 insertions, 71 deletions
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 2519fbffbb..03c8d51168 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" @@ -382,6 +383,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; //---------------------------------------------------------------------------- @@ -632,6 +635,12 @@ bool LLAppViewer::sendURLToOtherInstance(const std::string& url) return false; } +//virtual +void LLAppViewer::setOSHibernationMode(eHibernationMode mode) +{ + // See OS specific files +} + //---------------------------------------------------------------------------- // LLAppViewer definition @@ -690,6 +699,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 @@ -842,6 +857,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(); @@ -940,6 +957,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 ; @@ -1120,17 +1147,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 @@ -1315,6 +1342,8 @@ bool LLAppViewer::doFrame() } #endif + LL_RECORD_BLOCK_TIME(FTM_FRAME); + LL_PROFILE_GPU_ZONE("Frame"); { // and now adjust the visuals from previous frame. if(LLPerfStats::tunables.userAutoTuneEnabled && LLPerfStats::tunables.tuningFlag != LLPerfStats::Tunables::Nothing) @@ -1726,7 +1755,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; @@ -2028,7 +2065,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 +2094,7 @@ bool LLAppViewer::cleanup() if (sTextureFetch) { sTextureFetch->shutdown(); - sTextureFetch->waitOnPending(); + sTextureFetch->waitOnPending(10.f); delete sTextureFetch; sTextureFetch = NULL; } @@ -2102,7 +2139,10 @@ bool LLAppViewer::cleanup() gSavedSettings.cleanup(); LLUIColorTable::instance().clear(); - LLWatchdog::getInstance()->cleanup(); + if (LLWatchdog::instanceExists()) + { + LLWatchdog::getInstance()->cleanup(); + } LLViewerAssetStatsFF::cleanup(); @@ -2127,6 +2167,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 +2182,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 @@ -2282,6 +2327,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 +2354,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() { // @@ -2331,13 +2385,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 @@ -2384,7 +2435,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 +2574,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. @@ -2937,7 +3058,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; } } @@ -2984,18 +3121,30 @@ 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; } if (mSecondInstance) { - // This is the second instance of SL. Mute voice, - // but make sure the setting is *not* persisted. + // 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); } } @@ -3017,6 +3166,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 @@ -3493,10 +3693,11 @@ LLSD LLAppViewer::getViewerInfo() const vlc_ver_codec << LIBVLC_VERSION_REVISION; info["LIBVLC_VERSION"] = vlc_ver_codec.str(); - 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(); } @@ -3549,6 +3750,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() @@ -3931,7 +4139,12 @@ void LLAppViewer::processMarkerFiles() // - 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; @@ -4043,6 +4256,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(LLFile::isfile(error_marker_file)) { S32 marker_code = getMarkerErrorCode(error_marker_file); @@ -4081,11 +4296,13 @@ void LLAppViewer::processMarkerFiles() } else { - // so only check watchdog marker if there is no error marker. - if (LLFile::isfile(watchdog_marker_file)) + 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, @@ -4097,9 +4314,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) @@ -4143,6 +4400,7 @@ void LLAppViewer::removeMarkerFiles() { LL_WARNS("MarkerFile") << "logout marker '"<<mLogoutMarkerFileName<<"' not open"<< LL_ENDL; } + removeCloseRequestMarker(); removeWatchdogMarker(); } else @@ -4202,6 +4460,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(); } @@ -4302,6 +4567,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 @@ -4937,6 +5210,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 @@ -5589,6 +5863,59 @@ bool LLAppViewer::errorMarkerExists() const 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) @@ -5604,12 +5931,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); } } @@ -5631,6 +5959,29 @@ void LLAppViewer::outOfMemorySoftQuit() } } +void LLAppViewer::setPermitOSHibernation(bool permit) +{ + if (permit) + { + if (mCurrentHibernationMode != LL_HIBERNATE_MODE_DEFAULT) + { + // Will call OS specific code to let OS hibernate when idle + setOSHibernationMode(LL_HIBERNATE_MODE_DEFAULT); + mCurrentHibernationMode = LL_HIBERNATE_MODE_DEFAULT; + } + } + else + { + static LLCachedControl<S32> os_hibernation_mode(gSavedSettings, "OSHibernationMode", 0); + eHibernationMode mode = static_cast<eHibernationMode>(os_hibernation_mode()); + if (mode != LL_HIBERNATE_MODE_DEFAULT && mCurrentHibernationMode != mode) + { + setOSHibernationMode(mode); + mCurrentHibernationMode = mode; + } + } +} + void LLAppViewer::idleNameCache() { // Neither old nor new name cache can function before agent has a region @@ -5675,7 +6026,6 @@ void LLAppViewer::idleNetwork() pingMainloopTimeout("idleNetwork"); gObjectList.mNumNewObjects = 0; - S32 total_decoded = 0; static LLCachedControl<bool> speed_test(gSavedSettings, "SpeedTest", false); if (!speed_test()) @@ -5683,64 +6033,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); @@ -5847,6 +6189,9 @@ void LLAppViewer::disconnectViewer() // Pass the connection state to LLUrlEntryParcel not to attempt // parcel info requests while disconnected. LLUrlEntryParcel::setDisconnected(gDisconnected); + + // Restore default OS hibernation mode + setPermitOSHibernation(true); } void LLAppViewer::forceErrorLLError() @@ -6061,6 +6406,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(); @@ -6112,6 +6483,15 @@ void LLAppViewer::handleLoginComplete() // we logged in successfully, so save settings on logout LL_INFOS() << "Login successful, per account settings will be saved on log out." << LL_ENDL; mSavePerAccountSettings=true; + + // Don't allow hibernation while we're running + setPermitOSHibernation(false); + // Track 'hibernation' mode changes + mOSHibernationModeChangeConnection = gSavedSettings.getControl("OSHibernationMode")->getSignal()->connect([](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) + { + // setPermitOSHibernation will sort itself out based on new mode. + LLAppViewer::instance()->setPermitOSHibernation(false); + }); } //virtual |
