From 9f82b907f2f7d57580e17cef89bbd08be90f94fb Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Fri, 5 Dec 2025 16:29:04 +0200
Subject: #3612 "Copy SLURL" from Favorites bar not working
If region isn't online, can't get region position and form slurl
---
indra/newview/llfavoritesbar.cpp | 7 +++++++
indra/newview/skins/default/xui/en/menu_favorites.xml | 6 ++++++
2 files changed, 13 insertions(+)
diff --git a/indra/newview/llfavoritesbar.cpp b/indra/newview/llfavoritesbar.cpp
index 377710c170..acffc856bc 100644
--- a/indra/newview/llfavoritesbar.cpp
+++ b/indra/newview/llfavoritesbar.cpp
@@ -1395,6 +1395,13 @@ bool LLFavoritesBarCtrl::enableSelected(const LLSD& userdata)
{
return !LLAgentPicksInfo::getInstance()->isPickLimitReached();
}
+ else if (param == "copy_slurl"
+ || param == "show_on_map")
+ {
+ LLVector3d posGlobal;
+ LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal);
+ return !posGlobal.isExactlyZero();
+ }
return false;
}
diff --git a/indra/newview/skins/default/xui/en/menu_favorites.xml b/indra/newview/skins/default/xui/en/menu_favorites.xml
index 6345394b46..f82f705fb7 100644
--- a/indra/newview/skins/default/xui/en/menu_favorites.xml
+++ b/indra/newview/skins/default/xui/en/menu_favorites.xml
@@ -35,6 +35,9 @@
+
+
Date: Fri, 5 Dec 2025 18:01:28 +0200
Subject: #5109 LLExperienceCache crashes on a coroutine
---
indra/llmessage/llexperiencecache.cpp | 33 ++++++++++++++++++++-------------
indra/llmessage/llexperiencecache.h | 4 ++--
2 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/indra/llmessage/llexperiencecache.cpp b/indra/llmessage/llexperiencecache.cpp
index 149741b9f9..e4c7deb1c5 100644
--- a/indra/llmessage/llexperiencecache.cpp
+++ b/indra/llmessage/llexperiencecache.cpp
@@ -112,9 +112,7 @@ void LLExperienceCache::initSingleton()
constexpr size_t CORO_QUEUE_SIZE = 2048;
LLCoprocedureManager::instance().initializePool("ExpCache", CORO_QUEUE_SIZE);
- LLCoros::instance().launch("LLExperienceCache::idleCoro",
- boost::bind(&LLExperienceCache::idleCoro, this));
-
+ LLCoros::instance().launch("LLExperienceCache::idleCoro", LLExperienceCache::idleCoro);
}
void LLExperienceCache::cleanup()
@@ -246,6 +244,7 @@ const LLExperienceCache::cache_t& LLExperienceCache::getCached()
return mCache;
}
+// static because used by coroutine and can outlive the instance
void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, std::string url, RequestQueue_t requests)
{
LLCore::HttpRequest::ptr_t httpRequest = std::make_shared();
@@ -254,6 +253,13 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
+ if (sShutdown)
+ {
+ return;
+ }
+
+ LLExperienceCache* self = LLExperienceCache::getInstance();
+
LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
@@ -265,7 +271,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
// build dummy entries for the failed requests
for (RequestQueue_t::const_iterator it = requests.begin(); it != requests.end(); ++it)
{
- LLSD exp = get(*it);
+ LLSD exp = self->get(*it);
//leave the properties alone if we already have a cache entry for this xp
if (exp.isUndefined())
{
@@ -278,7 +284,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
exp["error"] = (LLSD::Integer)status.getType();
exp[QUOTA] = DEFAULT_QUOTA;
- processExperience(*it, exp);
+ self->processExperience(*it, exp);
}
return;
}
@@ -294,7 +300,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
LL_DEBUGS("ExperienceCache") << "Received result for " << public_key
<< " display '" << row[LLExperienceCache::NAME].asString() << "'" << LL_ENDL;
- processExperience(public_key, row);
+ self->processExperience(public_key, row);
}
LLSD error_ids = result["error_ids"];
@@ -310,7 +316,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
exp[MISSING] = true;
exp[QUOTA] = DEFAULT_QUOTA;
- processExperience(id, exp);
+ self->processExperience(id, exp);
LL_WARNS("ExperienceCache") << "LLExperienceResponder::result() error result for " << id << LL_ENDL;
}
@@ -361,7 +367,7 @@ void LLExperienceCache::requestExperiences()
if (mRequestQueue.empty() || (ostr.tellp() > EXP_URL_SEND_THRESHOLD))
{ // request is placed in the coprocedure pool for the ExpCache cache. Throttling is done by the pool itself.
LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "RequestExperiences",
- boost::bind(&LLExperienceCache::requestExperiencesCoro, this, _1, ostr.str(), requests) );
+ boost::bind(&LLExperienceCache::requestExperiencesCoro, _1, ostr.str(), requests) );
ostr.str(std::string());
ostr << urlBase << "?page_size=" << PAGE_SIZE1;
@@ -393,7 +399,7 @@ void LLExperienceCache::setCapabilityQuery(LLExperienceCache::CapabilityQuery_t
mCapability = queryfn;
}
-
+// static, because coro can outlive the instance
void LLExperienceCache::idleCoro()
{
const F32 SECS_BETWEEN_REQUESTS = 0.5f;
@@ -402,14 +408,15 @@ void LLExperienceCache::idleCoro()
LL_INFOS("ExperienceCache") << "Launching Experience cache idle coro." << LL_ENDL;
do
{
- if (mEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
+ LLExperienceCache* self = LLExperienceCache::getInstance();
+ if (self->mEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
{
- eraseExpired();
+ self->eraseExpired();
}
- if (!mRequestQueue.empty())
+ if (!self->mRequestQueue.empty())
{
- requestExperiences();
+ self->requestExperiences();
}
llcoro::suspendUntilTimeout(SECS_BETWEEN_REQUESTS);
diff --git a/indra/llmessage/llexperiencecache.h b/indra/llmessage/llexperiencecache.h
index 4b344347d5..9ecdb9efca 100644
--- a/indra/llmessage/llexperiencecache.h
+++ b/indra/llmessage/llexperiencecache.h
@@ -144,9 +144,9 @@ private:
std::string mCacheFileName;
static bool sShutdown; // control for coroutines, they exist out of LLExperienceCache's scope, so they need a static control
- void idleCoro();
+ static void idleCoro();
void eraseExpired();
- void requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, std::string, RequestQueue_t);
+ static void requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, std::string, RequestQueue_t);
void requestExperiences();
void fetchAssociatedExperienceCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, LLUUID, LLUUID, std::string, ExperienceGetFn_t);
--
cgit v1.3
From 3fd68bcc8290f2ce156ea8414983e51d2ef74e33 Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Fri, 5 Dec 2025 18:07:16 +0200
Subject: #4931 Fix missed name cache connection #2
---
indra/newview/llpanelplaceprofile.cpp | 4 ++--
indra/newview/llpanelplaceprofile.h | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/indra/newview/llpanelplaceprofile.cpp b/indra/newview/llpanelplaceprofile.cpp
index 87f05f2028..c380b6860f 100644
--- a/indra/newview/llpanelplaceprofile.cpp
+++ b/indra/newview/llpanelplaceprofile.cpp
@@ -517,7 +517,7 @@ void LLPanelPlaceProfile::displaySelectedParcelInfo(LLParcel* parcel,
std::string parcel_owner =
LLSLURL("agent", parcel->getOwnerID(), "inspect").getSLURLString();
mParcelOwner->setText(parcel_owner);
- LLAvatarNameCache::get(region->getOwner(), boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mRegionOwnerText));
+ mAvatarNameCacheConnection = LLAvatarNameCache::get(region->getOwner(), boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mRegionOwnerText));
mRegionGroupText->setText( getString("none_text"));
}
@@ -548,7 +548,7 @@ void LLPanelPlaceProfile::displaySelectedParcelInfo(LLParcel* parcel,
const LLUUID& auth_buyer_id = parcel->getAuthorizedBuyerID();
if(auth_buyer_id.notNull())
{
- LLAvatarNameCache::get(auth_buyer_id, boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mSaleToText));
+ mAvatarNameCacheConnection = LLAvatarNameCache::get(auth_buyer_id, boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mSaleToText));
// Show sales info to a specific person or a group he belongs to.
if (auth_buyer_id != gAgent.getID() && !gAgent.isInGroup(auth_buyer_id))
diff --git a/indra/newview/llpanelplaceprofile.h b/indra/newview/llpanelplaceprofile.h
index f562be0f5d..0c161198f8 100644
--- a/indra/newview/llpanelplaceprofile.h
+++ b/indra/newview/llpanelplaceprofile.h
@@ -118,6 +118,8 @@ private:
LLTextEditor* mResaleText;
LLTextBox* mSaleToText;
LLAccordionCtrl* mAccordionCtrl;
+
+ boost::signals2::scoped_connection mAvatarNameCacheConnection;
};
#endif // LL_LLPANELPLACEPROFILE_H
--
cgit v1.3
From 49c73ac7297ec7760a00bf36fa6339f2d0be1f95 Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Mon, 8 Dec 2025 12:21:25 +0200
Subject: #3612 "Copy SLURL" from Favorites bar not working #2
---
indra/newview/llfavoritesbar.cpp | 35 ++++++++++++++++++----
.../newview/skins/default/xui/en/notifications.xml | 11 +++++++
2 files changed, 40 insertions(+), 6 deletions(-)
diff --git a/indra/newview/llfavoritesbar.cpp b/indra/newview/llfavoritesbar.cpp
index acffc856bc..98b3ca820b 100644
--- a/indra/newview/llfavoritesbar.cpp
+++ b/indra/newview/llfavoritesbar.cpp
@@ -1398,9 +1398,15 @@ bool LLFavoritesBarCtrl::enableSelected(const LLSD& userdata)
else if (param == "copy_slurl"
|| param == "show_on_map")
{
- LLVector3d posGlobal;
- LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal);
- return !posGlobal.isExactlyZero();
+ LLViewerInventoryItem* item = gInventory.getItem(mSelectedItemID);
+ if (nullptr == item)
+ return false; // shouldn't happen as it is selected from existing items
+
+ const LLUUID& asset_id = item->getAssetUUID();
+
+ // Favorites are supposed to be loaded first, it should be here already
+ LLLandmark* landmark = gLandmarkList.getAsset(asset_id, NULL /*callback*/);
+ return nullptr != landmark;
}
return false;
@@ -1432,10 +1438,17 @@ void LLFavoritesBarCtrl::doToSelected(const LLSD& userdata)
LLVector3d posGlobal;
LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal);
+ // inventory item and asset exist, otherwise
+ // enableSelected wouldn't have let it get here,
+ // only need to check location validity
if (!posGlobal.isExactlyZero())
{
LLLandmarkActions::getSLURLfromPosGlobal(posGlobal, copy_slurl_to_clipboard_cb);
}
+ else
+ {
+ LLNotificationsUtil::add("LandmarkLocationUnknown");
+ }
}
else if (action == "show_on_map")
{
@@ -1444,10 +1457,20 @@ void LLFavoritesBarCtrl::doToSelected(const LLSD& userdata)
LLVector3d posGlobal;
LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal);
- if (!posGlobal.isExactlyZero() && worldmap_instance)
+ if (worldmap_instance)
{
- worldmap_instance->trackLocation(posGlobal);
- LLFloaterReg::showInstance("world_map", "center");
+ // inventory item and asset exist, otherwise
+ // enableSelected wouldn't have let it get here,
+ // only need to check location validity
+ if (!posGlobal.isExactlyZero())
+ {
+ worldmap_instance->trackLocation(posGlobal);
+ LLFloaterReg::showInstance("world_map", "center");
+ }
+ else
+ {
+ LLNotificationsUtil::add("LandmarkLocationUnknown");
+ }
}
}
else if (action == "create_pick")
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index dbd513afe8..d0261a930c 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -2465,6 +2465,17 @@ You already have a landmark for this location.
fail
+
+Viewer wasn't able to get region's location. Region might be temporarily unavailable or was removed.
+
+ fail
+
+
Date: Mon, 8 Dec 2025 21:15:04 +0200
Subject: #4604 Tweak LLImageBase's 'out of memory' handling
---
indra/llimage/llimage.cpp | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp
index ca8a4199e8..35bc7065b1 100644
--- a/indra/llimage/llimage.cpp
+++ b/indra/llimage/llimage.cpp
@@ -709,8 +709,20 @@ U8* LLImageBase::allocateData(S32 size)
mData = (U8*)ll_aligned_malloc_16(size);
if (!mData)
{
- LL_WARNS() << "Failed to allocate image data size [" << size << "]" << LL_ENDL;
- mBadBufferAllocation = true;
+ constexpr S32 MAX_TOLERANCE = 1024 * 1024 * 4; // 4 MB
+ if (size > MAX_TOLERANCE)
+ {
+ // If a big image failed to allocate, tollerate it for now.
+ // It's insightfull when crash logs without obvious cause are being analyzed,
+ // so a crash in a random location that normally is a mystery can get proper handling.
+ LL_WARNS() << "Failed to allocate image data size [" << size << "]" << LL_ENDL;
+ }
+ else
+ {
+ // We are too far gone if we can't allocate a small buffer.
+ LLError::LLUserWarningMsg::showOutOfMemory();
+ LL_ERRS() << "Failed to allocate image data size [" << size << "]" << LL_ENDL;
+ }
}
}
--
cgit v1.3
From 058ee5c66f850b01c255aa7b1de6342a6bf00c47 Mon Sep 17 00:00:00 2001
From: Maxim Nikolenko
Date: Wed, 10 Dec 2025 20:50:58 +0200
Subject: #5122 update version format
---
indra/newview/VIEWER_VERSION.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index 4703009f54..4df38dcc24 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-2026.01.0
+2026.1.0
--
cgit v1.3
From 2c69552453723dac41f3f644bd46f02f0406497c Mon Sep 17 00:00:00 2001
From: Maxim Nikolenko
Date: Thu, 11 Dec 2025 18:01:40 +0200
Subject: #5122 update version format #2
---
indra/newview/VIEWER_VERSION.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index 4df38dcc24..2aaedf9944 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-2026.1.0
+26.1.0
--
cgit v1.3
From 05282cfd45b89abbc8efae6ee7bdfe7122bbec10 Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Tue, 16 Dec 2025 04:57:11 +0200
Subject: #1112 Fix wrong option having size controls
in Snapshot to Inventory
---
indra/newview/llpanelsnapshotinventory.cpp | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/indra/newview/llpanelsnapshotinventory.cpp b/indra/newview/llpanelsnapshotinventory.cpp
index b81b891685..7396f079b0 100644
--- a/indra/newview/llpanelsnapshotinventory.cpp
+++ b/indra/newview/llpanelsnapshotinventory.cpp
@@ -110,7 +110,11 @@ void LLPanelSnapshotInventory::updateControls(const LLSD& info)
void LLPanelSnapshotInventory::onResolutionCommit(LLUICtrl* ctrl)
{
- bool current_window_selected = (getChild(getImageSizeComboName())->getCurrentIndex() == 3);
+ LLComboBox* combo = getChild(getImageSizeComboName());
+ // Current window likely won't ever change position from being the penultimate item
+ // Custom window is last item
+ S32 curent_window_index = combo->getItemCount() - 2;
+ bool current_window_selected = (combo->getCurrentIndex() == curent_window_index);
getChild(getWidthSpinnerName())->setVisible(!current_window_selected);
getChild(getHeightSpinnerName())->setVisible(!current_window_selected);
}
--
cgit v1.3
From de9a9111fa3269a7a6a2d966cf52869d6a711333 Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Wed, 17 Dec 2025 18:39:07 +0200
Subject: #5084 Improve watchdog's feedback
---
indra/newview/llappviewer.cpp | 28 +++++++++++++++++++++++-----
indra/newview/llwatchdog.cpp | 27 +++++++++++++++++++++++----
indra/newview/llwatchdog.h | 12 ++++++++++--
3 files changed, 56 insertions(+), 11 deletions(-)
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 569fd30b21..8358583c35 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -1239,7 +1239,7 @@ bool LLAppViewer::init()
/*----------------------------------------------------------------------*/
// nat 2016-06-29 moved the following here from the former mainLoop().
- mMainloopTimeout = new LLWatchdogTimeout();
+ mMainloopTimeout = new LLWatchdogTimeout("mainloop");
// Create IO Pump to use for HTTP Requests.
gServicePump = new LLPumpIO(gAPRPoolp);
@@ -1429,12 +1429,14 @@ bool LLAppViewer::doFrame()
{
LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df mainloop");
+ pingMainloopTimeout("df mainloop");
// canonical per-frame event
mainloop.post(newFrame);
}
{
LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df suspend");
+ pingMainloopTimeout("df suspend");
// give listeners a chance to run
llcoro::suspend();
// if one of our coroutines threw an uncaught exception, rethrow it now
@@ -1470,6 +1472,7 @@ bool LLAppViewer::doFrame()
{
{
LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df pauseMainloopTimeout");
+ pingMainloopTimeout("df idle"); // So that it will be aware of last state.
pauseMainloopTimeout(); // *TODO: Remove. Messages shouldn't be stalling for 20+ seconds!
}
@@ -1481,7 +1484,7 @@ bool LLAppViewer::doFrame()
{
LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df resumeMainloopTimeout");
- resumeMainloopTimeout();
+ resumeMainloopTimeout("df idle");
}
}
@@ -1496,7 +1499,7 @@ bool LLAppViewer::doFrame()
}
disconnectViewer();
- resumeMainloopTimeout();
+ resumeMainloopTimeout("df snapshot n disconnect");
}
// Render scene.
@@ -2301,7 +2304,22 @@ void errorHandler(const std::string& title_string, const std::string& message_st
}
if (!message_string.empty())
{
- OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK);
+ if (on_main_thread())
+ {
+ // Prevent watchdog from killing us while dialog is up.
+ // Can't do pauseMainloopTimeout, since this may be called
+ // from threads and we are not going to need watchdog now.
+ LLAppViewer::instance()->pauseMainloopTimeout();
+
+ // todo: might want to have non-crashing timeout for OOM cases
+ // and needs a way to pause main loop.
+ OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK);
+ LLAppViewer::instance()->resumeMainloopTimeout();
+ }
+ else
+ {
+ OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK);
+ }
}
}
@@ -5825,7 +5843,7 @@ void LLAppViewer::initMainloopTimeout(std::string_view state)
{
if (!mMainloopTimeout)
{
- mMainloopTimeout = new LLWatchdogTimeout();
+ mMainloopTimeout = new LLWatchdogTimeout("mainloop");
resumeMainloopTimeout(state);
}
}
diff --git a/indra/newview/llwatchdog.cpp b/indra/newview/llwatchdog.cpp
index 614d1afc2a..0984606456 100644
--- a/indra/newview/llwatchdog.cpp
+++ b/indra/newview/llwatchdog.cpp
@@ -67,7 +67,9 @@ private:
};
// LLWatchdogEntry
-LLWatchdogEntry::LLWatchdogEntry()
+LLWatchdogEntry::LLWatchdogEntry(const std::string& thread_name)
+ : mThreadName(thread_name)
+ , mThreadID(LLThread::currentID())
{
}
@@ -89,11 +91,16 @@ void LLWatchdogEntry::stop()
LLWatchdog::getInstance()->remove(this);
}
}
+std::string LLWatchdogEntry::getThreadName() const
+{
+ return mThreadName + llformat(": %d", mThreadID);
+}
// LLWatchdogTimeout
const std::string UNINIT_STRING = "uninitialized";
-LLWatchdogTimeout::LLWatchdogTimeout() :
+LLWatchdogTimeout::LLWatchdogTimeout(const std::string& thread_name) :
+ LLWatchdogEntry(thread_name),
mTimeout(0.0f),
mPingState(UNINIT_STRING)
{
@@ -249,9 +256,21 @@ void LLWatchdog::run()
{
LLAppViewer::instance()->createErrorMarker(LAST_EXEC_FROZE);
}
- // Todo1: warn user?
+ // Todo1: Warn user?
// Todo2: We probably want to report even if 5 seconds passed, just not error 'yet'.
- LL_ERRS() << "Watchdog timer expired; assuming viewer is hung and crashing" << LL_ENDL;
+ // Todo3: This will report crash as 'llerror', consider adding 'watchdog' reason.
+ std::string last_state = (*result)->getLastState();
+ if (last_state.empty())
+ {
+ LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName()
+ << " expired; assuming viewer is hung and crashing" << LL_ENDL;
+ }
+ else
+ {
+ LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName()
+ << " expired with state: " << last_state
+ << "; assuming viewer is hung and crashing" << LL_ENDL;
+ }
}
}
diff --git a/indra/newview/llwatchdog.h b/indra/newview/llwatchdog.h
index b7dd55577e..a8056f4337 100644
--- a/indra/newview/llwatchdog.h
+++ b/indra/newview/llwatchdog.h
@@ -36,7 +36,7 @@
class LLWatchdogEntry
{
public:
- LLWatchdogEntry();
+ LLWatchdogEntry(const std::string &thread_name);
virtual ~LLWatchdogEntry();
// isAlive is accessed by the watchdog thread.
@@ -46,12 +46,19 @@ public:
virtual void reset() = 0;
virtual void start();
virtual void stop();
+ virtual std::string getLastState() const { return std::string(); }
+ typedef std::thread::id id_t;
+ std::string getThreadName() const;
+
+private:
+ id_t mThreadID; // ID of the thread being watched
+ std::string mThreadName;
};
class LLWatchdogTimeout : public LLWatchdogEntry
{
public:
- LLWatchdogTimeout();
+ LLWatchdogTimeout(const std::string& thread_name);
virtual ~LLWatchdogTimeout();
bool isAlive() const override;
@@ -63,6 +70,7 @@ public:
void setTimeout(F32 d);
void ping(std::string_view state);
const std::string& getState() {return mPingState; }
+ std::string getLastState() const override { return mPingState; }
private:
LLTimer mTimer;
--
cgit v1.3
From 107ea4d84950e13be6b7291f506419b1839a0dda Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Fri, 19 Dec 2025 13:18:41 +0200
Subject: #5084 Cover window's thread with watchdog
---
indra/llcommon/CMakeLists.txt | 2 +
indra/llcommon/llwatchdog.cpp | 290 ++++++++++++++++++++++++++++++++++++++
indra/llcommon/llwatchdog.h | 118 ++++++++++++++++
indra/llwindow/llwindow.h | 2 +
indra/llwindow/llwindowwin32.cpp | 65 ++++++++-
indra/llwindow/llwindowwin32.h | 141 +++++++++----------
indra/newview/CMakeLists.txt | 2 -
indra/newview/llappviewer.cpp | 14 +-
indra/newview/llwatchdog.cpp | 295 ---------------------------------------
indra/newview/llwatchdog.h | 107 --------------
10 files changed, 560 insertions(+), 476 deletions(-)
create mode 100644 indra/llcommon/llwatchdog.cpp
create mode 100644 indra/llcommon/llwatchdog.h
delete mode 100644 indra/newview/llwatchdog.cpp
delete mode 100644 indra/newview/llwatchdog.h
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 2de9deea70..4d04c2c119 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -101,6 +101,7 @@ set(llcommon_SOURCE_FILES
lluri.cpp
lluriparser.cpp
lluuid.cpp
+ llwatchdog.cpp
llworkerthread.cpp
hbxxh.cpp
u64.cpp
@@ -240,6 +241,7 @@ set(llcommon_HEADER_FILES
lluri.h
lluriparser.h
lluuid.h
+ llwatchdog.h
llwin32headers.h
llworkerthread.h
hbxxh.h
diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp
new file mode 100644
index 0000000000..fa240a9ed7
--- /dev/null
+++ b/indra/llcommon/llwatchdog.cpp
@@ -0,0 +1,290 @@
+/**
+ * @file llthreadwatchdog.cpp
+ * @brief The LLThreadWatchdog class definitions
+ *
+ * $LicenseInfo:firstyear=2007&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+
+#include "llwatchdog.h"
+#include "llthread.h"
+
+constexpr U32 WATCHDOG_SLEEP_TIME_USEC = 1000000U;
+
+// This class runs the watchdog timing thread.
+class LLWatchdogTimerThread : public LLThread
+{
+public:
+ LLWatchdogTimerThread() :
+ LLThread("Watchdog"),
+ mSleepMsecs(0),
+ mStopping(false)
+ {
+ }
+
+ ~LLWatchdogTimerThread() {}
+
+ void setSleepTime(long ms) { mSleepMsecs = ms; }
+ void stop()
+ {
+ mStopping = true;
+ mSleepMsecs = 1;
+ }
+
+ void run() override
+ {
+ while(!mStopping)
+ {
+ LLWatchdog::getInstance()->run();
+ ms_sleep(mSleepMsecs);
+ }
+ }
+
+private:
+ long mSleepMsecs;
+ bool mStopping;
+};
+
+// LLWatchdogEntry
+LLWatchdogEntry::LLWatchdogEntry(const std::string& thread_name)
+ : mThreadName(thread_name)
+ , mThreadID(LLThread::currentID())
+{
+}
+
+LLWatchdogEntry::~LLWatchdogEntry()
+{
+ stop();
+}
+
+void LLWatchdogEntry::start()
+{
+ LLWatchdog::getInstance()->add(this);
+}
+
+void LLWatchdogEntry::stop()
+{
+ // this can happen very late in the shutdown sequence
+ if (!LLWatchdog::wasDeleted())
+ {
+ LLWatchdog::getInstance()->remove(this);
+ }
+}
+std::string LLWatchdogEntry::getThreadName() const
+{
+ return mThreadName + llformat(": %d", mThreadID);
+}
+
+// LLWatchdogTimeout
+const std::string UNINIT_STRING = "uninitialized";
+
+LLWatchdogTimeout::LLWatchdogTimeout(const std::string& thread_name) :
+ LLWatchdogEntry(thread_name),
+ mTimeout(0.0f),
+ mPingState(UNINIT_STRING)
+{
+}
+
+LLWatchdogTimeout::~LLWatchdogTimeout()
+{
+}
+
+bool LLWatchdogTimeout::isAlive() const
+{
+ return (mTimer.getStarted() && !mTimer.hasExpired());
+}
+
+void LLWatchdogTimeout::reset()
+{
+ mTimer.setTimerExpirySec(mTimeout);
+}
+
+void LLWatchdogTimeout::setTimeout(F32 d)
+{
+ mTimeout = d;
+}
+
+void LLWatchdogTimeout::start(std::string_view state)
+{
+ if (mTimeout == 0)
+ {
+ LL_WARNS() << "Cant' start watchdog entry - no timeout set" << LL_ENDL;
+ return;
+ }
+ // Order of operation is very important here.
+ // After LLWatchdogEntry::start() is called
+ // LLWatchdogTimeout::isAlive() will be called asynchronously.
+ ping(state);
+ mTimer.start();
+ mTimer.setTimerExpirySec(mTimeout); // timer expiration set to 0 by start()
+ LLWatchdogEntry::start();
+}
+
+void LLWatchdogTimeout::stop()
+{
+ LLWatchdogEntry::stop();
+ mTimer.stop();
+}
+
+void LLWatchdogTimeout::ping(std::string_view state)
+{
+ if (!state.empty())
+ {
+ mPingState = state;
+ }
+ reset();
+}
+
+// LLWatchdog
+LLWatchdog::LLWatchdog()
+ :mSuspectsAccessMutex()
+ ,mTimer(nullptr)
+ ,mLastClockCount(0)
+{
+}
+
+LLWatchdog::~LLWatchdog()
+{
+}
+
+void LLWatchdog::add(LLWatchdogEntry* e)
+{
+ lockThread();
+ mSuspects.insert(e);
+ unlockThread();
+}
+
+void LLWatchdog::remove(LLWatchdogEntry* e)
+{
+ lockThread();
+ mSuspects.erase(e);
+ unlockThread();
+}
+
+void LLWatchdog::init(func_t set_error_state_callback)
+{
+ if (!mSuspectsAccessMutex && !mTimer)
+ {
+ mSuspectsAccessMutex = new LLMutex();
+ mTimer = new LLWatchdogTimerThread();
+ mTimer->setSleepTime(WATCHDOG_SLEEP_TIME_USEC / 1000);
+ mLastClockCount = LLTimer::getTotalTime();
+
+ // mTimer->start() kicks off the thread, any code after
+ // start needs to use the mSuspectsAccessMutex
+ mTimer->start();
+ }
+ mCreateMarkerFnc = set_error_state_callback;
+}
+
+void LLWatchdog::cleanup()
+{
+ if (mTimer)
+ {
+ mTimer->stop();
+ delete mTimer;
+ mTimer = nullptr;
+ }
+
+ if (mSuspectsAccessMutex)
+ {
+ delete mSuspectsAccessMutex;
+ mSuspectsAccessMutex = nullptr;
+ }
+
+ mLastClockCount = 0;
+}
+
+void LLWatchdog::run()
+{
+ lockThread();
+
+ // Check the time since the last call to run...
+ // If the time elapsed is two times greater than the regualr sleep time
+ // reset the active timeouts.
+ constexpr U32 TIME_ELAPSED_MULTIPLIER = 2;
+ U64 current_time = LLTimer::getTotalTime();
+ U64 current_run_delta = current_time - mLastClockCount;
+ mLastClockCount = current_time;
+
+ if (current_run_delta > (WATCHDOG_SLEEP_TIME_USEC * TIME_ELAPSED_MULTIPLIER))
+ {
+ LL_INFOS() << "Watchdog thread delayed: resetting entries." << LL_ENDL;
+ for (const auto& suspect : mSuspects)
+ {
+ suspect->reset();
+ }
+ }
+ else
+ {
+ SuspectsRegistry::iterator result =
+ std::find_if(mSuspects.begin(),
+ mSuspects.end(),
+ [](const LLWatchdogEntry* suspect){ return ! suspect->isAlive(); });
+ if (result != mSuspects.end())
+ {
+ // error!!!
+ if(mTimer)
+ {
+ mTimer->stop();
+ }
+
+ // Sets error marker file
+ mCreateMarkerFnc();
+ // Todo1: Warn user?
+ // Todo2: We probably want to report even if 5 seconds passed, just not error 'yet'.
+ std::string last_state = (*result)->getLastState();
+ if (last_state.empty())
+ {
+ LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName()
+ << " expired; assuming viewer is hung and crashing" << LL_ENDL;
+ }
+ else
+ {
+ LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName()
+ << " expired with state: " << last_state
+ << "; assuming viewer is hung and crashing" << LL_ENDL;
+ }
+ }
+ }
+
+
+ unlockThread();
+}
+
+void LLWatchdog::lockThread()
+{
+ if (mSuspectsAccessMutex)
+ {
+ mSuspectsAccessMutex->lock();
+ }
+}
+
+void LLWatchdog::unlockThread()
+{
+ if (mSuspectsAccessMutex)
+ {
+ mSuspectsAccessMutex->unlock();
+ }
+}
diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h
new file mode 100644
index 0000000000..fded881bb8
--- /dev/null
+++ b/indra/llcommon/llwatchdog.h
@@ -0,0 +1,118 @@
+/**
+ * @file llthreadwatchdog.h
+ * @brief The LLThreadWatchdog class declaration
+ *
+ * $LicenseInfo:firstyear=2007&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLTHREADWATCHDOG_H
+#define LL_LLTHREADWATCHDOG_H
+
+#ifndef LL_TIMER_H
+ #include "lltimer.h"
+#endif
+#include "llmutex.h"
+#include "llsingleton.h"
+
+#include
+
+// LLWatchdogEntry is the interface used by the tasks that
+// need to be watched.
+class LLWatchdogEntry
+{
+public:
+ LLWatchdogEntry(const std::string &thread_name);
+ virtual ~LLWatchdogEntry();
+
+ // isAlive is accessed by the watchdog thread.
+ // This may mean that resources used by
+ // isAlive and other method may need synchronization.
+ virtual bool isAlive() const = 0;
+ virtual void reset() = 0;
+ virtual void start();
+ virtual void stop();
+ virtual std::string getLastState() const { return std::string(); }
+ typedef std::thread::id id_t;
+ std::string getThreadName() const;
+
+private:
+ id_t mThreadID; // ID of the thread being watched
+ std::string mThreadName;
+};
+
+class LLWatchdogTimeout : public LLWatchdogEntry
+{
+public:
+ LLWatchdogTimeout(const std::string& thread_name);
+ virtual ~LLWatchdogTimeout();
+
+ bool isAlive() const override;
+ void reset() override;
+ void start() override { start(""); }
+ void stop() override;
+
+ void start(std::string_view state);
+ void setTimeout(F32 d);
+ void ping(std::string_view state);
+ const std::string& getState() {return mPingState; }
+ std::string getLastState() const override { return mPingState; }
+
+private:
+ LLTimer mTimer;
+ F32 mTimeout;
+ std::string mPingState;
+};
+
+class LLWatchdogTimerThread; // Defined in the cpp
+class LLWatchdog : public LLSingleton
+{
+ LLSINGLETON(LLWatchdog);
+ ~LLWatchdog();
+
+public:
+ // Add an entry to the watchdog.
+ void add(LLWatchdogEntry* e);
+ void remove(LLWatchdogEntry* e);
+
+ typedef std::function func_t;
+ void init(func_t set_error_state_callback);
+ void run();
+ void cleanup();
+
+
+private:
+ void lockThread();
+ void unlockThread();
+
+ typedef std::set SuspectsRegistry;
+ SuspectsRegistry mSuspects;
+ LLMutex* mSuspectsAccessMutex;
+ LLWatchdogTimerThread* mTimer;
+ U64 mLastClockCount;
+
+ // At the moment watchdog expects app to set markers in mCreateMarkerFnc,
+ // but technically can be used to set any error states or do some cleanup
+ // or show warnings.
+ func_t mCreateMarkerFnc;
+};
+
+#endif // LL_LLTHREADWATCHDOG_H
diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h
index 7a5404e615..185940e32d 100644
--- a/indra/llwindow/llwindow.h
+++ b/indra/llwindow/llwindow.h
@@ -205,6 +205,8 @@ public:
};
virtual S32 getRefreshRate() { return mRefreshRate; }
+
+ virtual void initWatchdog() {} // windows runs window as a thread and it needs a watchdog
protected:
LLWindow(LLWindowCallbacks* callbacks, bool fullscreen, U32 flags);
virtual ~LLWindow();
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index f826a60ddd..9d05d7e5a4 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -49,6 +49,7 @@
#include "llthreadsafequeue.h"
#include "stringize.h"
#include "llframetimer.h"
+#include "llwatchdog.h"
// System includes
#include
@@ -364,7 +365,8 @@ static LLMonitorInfo sMonitorInfo;
// the containing class a friend.
struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
{
- static const int MAX_QUEUE_SIZE = 2048;
+ static constexpr int MAX_QUEUE_SIZE = 2048;
+ static constexpr F32 WINDOW_TIMEOUT_SEC = 90.f;
LLThreadSafeQueue mMessageQueue;
@@ -426,6 +428,50 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
PostMessage(windowHandle, WM_POST_FUNCTION_, wparam, LPARAM(ptr));
}
+ // Call from main thread.
+ void initTimeout()
+ {
+ // post into thread's queue to avoid threading issues
+ post([this]()
+ {
+ if (!mWindowTimeout)
+ {
+ mWindowTimeout = std::make_unique("mainloop");
+ // supposed to be executed within run(),
+ // so no point checking if thread is alive
+ resumeTimeout("TimeoutInit");
+ }
+ });
+ }
+private:
+ // These timeout related functions are strictly for the thread.
+ void resumeTimeout(std::string_view state)
+ {
+ if (mWindowTimeout)
+ {
+ mWindowTimeout->setTimeout(WINDOW_TIMEOUT_SEC);
+ mWindowTimeout->start(state);
+ }
+ }
+
+ void pauseTimeout()
+ {
+ if (mWindowTimeout)
+ {
+ mWindowTimeout->stop();
+ }
+ }
+
+ void pingTimeout(std::string_view state)
+ {
+ if (mWindowTimeout)
+ {
+ mWindowTimeout->setTimeout(WINDOW_TIMEOUT_SEC);
+ mWindowTimeout->ping(state);
+ }
+ }
+
+public:
using FuncType = std::function;
// call GetMessage() and pull enqueue messages for later processing
HWND mWindowHandleThrd = NULL;
@@ -436,6 +482,8 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
bool mGLReady = false;
bool mGotGLBuffer = false;
LLAtomicBool mDeleteOnExit = false;
+private:
+ std::unique_ptr mWindowTimeout;
};
@@ -4595,6 +4643,11 @@ bool LLWindowWin32::getInputDevices(U32 device_type_filter,
return false;
}
+void LLWindowWin32::initWatchdog()
+{
+ mWindowThread->initTimeout();
+}
+
F32 LLWindowWin32::getSystemUISize()
{
F32 scale_value = 1.f;
@@ -4732,6 +4785,8 @@ void LLWindowWin32::LLWindowWin32Thread::checkDXMem()
return;
}
+ pauseTimeout();
+
IDXGIFactory4* p_factory = nullptr;
HRESULT res = CreateDXGIFactory1(__uuidof(IDXGIFactory4), (void**)&p_factory);
@@ -4835,6 +4890,8 @@ void LLWindowWin32::LLWindowWin32Thread::checkDXMem()
}
mGotGLBuffer = true;
+
+ resumeTimeout("checkDXMem");
}
void LLWindowWin32::LLWindowWin32Thread::run()
@@ -4850,6 +4907,9 @@ void LLWindowWin32::LLWindowWin32Thread::run()
timeBeginPeriod(llclamp((U32) 1, tc.wPeriodMin, tc.wPeriodMax));
}
+ // Normally won't exist yet, but in case of re-init, make sure it's cleaned up
+ resumeTimeout("WindowThread");
+
while (! getQueue().done())
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
@@ -4859,6 +4919,7 @@ void LLWindowWin32::LLWindowWin32Thread::run()
if (mWindowHandleThrd != 0)
{
+ pingTimeout("messages");
MSG msg;
BOOL status;
if (mhDCThrd == 0)
@@ -4886,6 +4947,7 @@ void LLWindowWin32::LLWindowWin32Thread::run()
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - Function Queue");
+ pingTimeout("queue");
logger.onChange("runPending()");
//process any pending functions
getQueue().runPending();
@@ -4900,6 +4962,7 @@ void LLWindowWin32::LLWindowWin32Thread::run()
#endif
}
+ pauseTimeout();
destroyWindow();
if (mDeleteOnExit)
diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h
index 0fc93ad0b1..8159092794 100644
--- a/indra/llwindow/llwindowwin32.h
+++ b/indra/llwindow/llwindowwin32.h
@@ -45,83 +45,82 @@ typedef void (*LLW32MsgCallback)(const MSG &msg);
class LLWindowWin32 : public LLWindow
{
public:
- /*virtual*/ void show();
- /*virtual*/ void hide();
- /*virtual*/ void close();
- /*virtual*/ bool getVisible();
- /*virtual*/ bool getMinimized();
- /*virtual*/ bool getMaximized();
- /*virtual*/ bool maximize();
- /*virtual*/ void minimize();
- /*virtual*/ void restore();
- /*virtual*/ bool getFullscreen();
- /*virtual*/ bool getPosition(LLCoordScreen *position);
- /*virtual*/ bool getSize(LLCoordScreen *size);
- /*virtual*/ bool getSize(LLCoordWindow *size);
- /*virtual*/ bool setPosition(LLCoordScreen position);
- /*virtual*/ bool setSizeImpl(LLCoordScreen size);
- /*virtual*/ bool setSizeImpl(LLCoordWindow size);
- /*virtual*/ bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL);
- /*virtual*/ void setTitle(const std::string title);
+ void show() override;
+ void hide() override;
+ void close() override;
+ bool getVisible() override;
+ bool getMinimized() override;
+ bool getMaximized() override;
+ bool maximize() override;
+ void minimize() override;
+ void restore() override;
+ bool getFullscreen();
+ bool getPosition(LLCoordScreen *position) override;
+ bool getSize(LLCoordScreen *size) override;
+ bool getSize(LLCoordWindow *size) override;
+ bool setPosition(LLCoordScreen position) override;
+ bool setSizeImpl(LLCoordScreen size) override;
+ bool setSizeImpl(LLCoordWindow size) override;
+ bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL) override;
+ void setTitle(const std::string title) override;
void* createSharedContext() override;
void makeContextCurrent(void* context) override;
void destroySharedContext(void* context) override;
- /*virtual*/ void toggleVSync(bool enable_vsync);
- /*virtual*/ bool setCursorPosition(LLCoordWindow position);
- /*virtual*/ bool getCursorPosition(LLCoordWindow *position);
- /*virtual*/ bool getCursorDelta(LLCoordCommon* delta);
- /*virtual*/ bool isWrapMouse() const override { return !mAbsoluteCursorPosition; };
- /*virtual*/ void showCursor();
- /*virtual*/ void hideCursor();
- /*virtual*/ void showCursorFromMouseMove();
- /*virtual*/ void hideCursorUntilMouseMove();
- /*virtual*/ bool isCursorHidden();
- /*virtual*/ void updateCursor();
- /*virtual*/ ECursorType getCursor() const;
- /*virtual*/ void captureMouse();
- /*virtual*/ void releaseMouse();
- /*virtual*/ void setMouseClipping( bool b );
- /*virtual*/ bool isClipboardTextAvailable();
- /*virtual*/ bool pasteTextFromClipboard(LLWString &dst);
- /*virtual*/ bool copyTextToClipboard(const LLWString &src);
- /*virtual*/ void flashIcon(F32 seconds);
- /*virtual*/ F32 getGamma();
- /*virtual*/ bool setGamma(const F32 gamma); // Set the gamma
- /*virtual*/ void setFSAASamples(const U32 fsaa_samples);
- /*virtual*/ U32 getFSAASamples();
- /*virtual*/ bool restoreGamma(); // Restore original gamma table (before updating gamma)
- /*virtual*/ ESwapMethod getSwapMethod() { return mSwapMethod; }
- /*virtual*/ void gatherInput();
- /*virtual*/ void delayInputProcessing();
- /*virtual*/ void swapBuffers();
- /*virtual*/ void restoreGLContext() {};
+ void toggleVSync(bool enable_vsync) override;
+ bool setCursorPosition(LLCoordWindow position) override;
+ bool getCursorPosition(LLCoordWindow *position) override;
+ bool getCursorDelta(LLCoordCommon* delta) override;
+ bool isWrapMouse() const override { return !mAbsoluteCursorPosition; };
+ void showCursor() override;
+ void hideCursor() override;
+ void showCursorFromMouseMove() override;
+ void hideCursorUntilMouseMove() override;
+ bool isCursorHidden() override;
+ void updateCursor() override;
+ ECursorType getCursor() const override;
+ void captureMouse() override;
+ void releaseMouse() override;
+ void setMouseClipping( bool b ) override;
+ bool isClipboardTextAvailable() override;
+ bool pasteTextFromClipboard(LLWString &dst) override;
+ bool copyTextToClipboard(const LLWString &src) override;
+ void flashIcon(F32 seconds) override;
+ F32 getGamma() override;
+ bool setGamma(const F32 gamma) override; // Set the gamma
+ void setFSAASamples(const U32 fsaa_samples) override;
+ U32 getFSAASamples() override;
+ bool restoreGamma() override; // Restore original gamma table (before updating gamma)
+ ESwapMethod getSwapMethod() override { return mSwapMethod; }
+ void gatherInput() override;
+ void delayInputProcessing() override;
+ void swapBuffers() override;
// handy coordinate space conversion routines
- /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordWindow *to);
- /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordScreen *to);
- /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordGL *to);
- /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordWindow *to);
- /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordGL *to);
- /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordScreen *to);
+ bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override;
+ bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override;
+ bool convertCoords(LLCoordWindow from, LLCoordGL *to) override;
+ bool convertCoords(LLCoordGL from, LLCoordWindow *to) override;
+ bool convertCoords(LLCoordScreen from, LLCoordGL *to) override;
+ bool convertCoords(LLCoordGL from, LLCoordScreen *to) override;
- /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions);
- /*virtual*/ F32 getNativeAspectRatio();
- /*virtual*/ F32 getPixelAspectRatio();
- /*virtual*/ void setNativeAspectRatio(F32 ratio) { mOverrideAspectRatio = ratio; }
+ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override;
+ F32 getNativeAspectRatio() override;
+ F32 getPixelAspectRatio() override;
+ void setNativeAspectRatio(F32 ratio) override { mOverrideAspectRatio = ratio; }
- /*virtual*/ bool dialogColorPicker(F32 *r, F32 *g, F32 *b );
+ bool dialogColorPicker(F32 *r, F32 *g, F32 *b ) override;
- /*virtual*/ void *getPlatformWindow();
- /*virtual*/ void bringToFront();
- /*virtual*/ void focusClient();
+ void *getPlatformWindow() override;
+ void bringToFront() override;
+ void focusClient() override;
- /*virtual*/ void allowLanguageTextInput(LLPreeditor *preeditor, bool b);
- /*virtual*/ void setLanguageTextInput( const LLCoordGL & pos );
- /*virtual*/ void updateLanguageTextInputArea();
- /*virtual*/ void interruptLanguageTextInput();
- /*virtual*/ void spawnWebBrowser(const std::string& escaped_url, bool async);
+ void allowLanguageTextInput(LLPreeditor *preeditor, bool b) override;
+ void setLanguageTextInput( const LLCoordGL & pos ) override;
+ void updateLanguageTextInputArea() override;
+ void interruptLanguageTextInput() override;
+ void spawnWebBrowser(const std::string& escaped_url, bool async) override;
- /*virtual*/ F32 getSystemUISize();
+ F32 getSystemUISize() override;
LLWindowCallbacks::DragNDropResult completeDragNDropRequest( const LLCoordGL gl_coord, const MASK mask, LLWindowCallbacks::DragNDropAction action, const std::string url );
@@ -129,14 +128,16 @@ public:
static std::vector getDynamicFallbackFontList();
static void setDPIAwareness();
- /*virtual*/ void* getDirectInput8();
- /*virtual*/ bool getInputDevices(U32 device_type_filter,
+ void* getDirectInput8() override;
+ bool getInputDevices(U32 device_type_filter,
std::function osx_callback,
void* win_callback,
- void* userdata);
+ void* userdata) override;
U32 getRawWParam() { return mRawWParam; }
+ void initWatchdog() override;
+
protected:
LLWindowWin32(LLWindowCallbacks* callbacks,
const std::string& title, const std::string& name, int x, int y, int width, int height, U32 flags,
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 0c5f3f3fd9..0949a3b59f 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -736,7 +736,6 @@ set(viewer_SOURCE_FILES
llvovolume.cpp
llvowater.cpp
llvowlsky.cpp
- llwatchdog.cpp
llwearableitemslist.cpp
llwearablelist.cpp
llweb.cpp
@@ -1414,7 +1413,6 @@ set(viewer_HEADER_FILES
llvovolume.h
llvowater.h
llvowlsky.h
- llwatchdog.h
llwearableitemslist.h
llwearablelist.h
llweb.h
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 8358583c35..e711064455 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -3182,7 +3182,19 @@ bool LLAppViewer::initWindow()
if (use_watchdog)
{
- LLWatchdog::getInstance()->init();
+ LLWatchdog::getInstance()->init([]()
+ {
+ LLAppViewer* app = LLAppViewer::instance();
+ if (app->logoutRequestSent())
+ {
+ app->createErrorMarker(LAST_EXEC_LOGOUT_FROZE);
+ }
+ else
+ {
+ app->createErrorMarker(LAST_EXEC_FROZE);
+ }
+ });
+ gViewerWindow->getWindow()->initWatchdog();
}
LLNotificationsUI::LLNotificationManager::getInstance();
diff --git a/indra/newview/llwatchdog.cpp b/indra/newview/llwatchdog.cpp
deleted file mode 100644
index 0984606456..0000000000
--- a/indra/newview/llwatchdog.cpp
+++ /dev/null
@@ -1,295 +0,0 @@
-/**
- * @file llthreadwatchdog.cpp
- * @brief The LLThreadWatchdog class definitions
- *
- * $LicenseInfo:firstyear=2007&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, 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
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-
-#include "llviewerprecompiledheaders.h"
-#include "llwatchdog.h"
-#include "llthread.h"
-#include "llappviewer.h"
-
-constexpr U32 WATCHDOG_SLEEP_TIME_USEC = 1000000U;
-
-// This class runs the watchdog timing thread.
-class LLWatchdogTimerThread : public LLThread
-{
-public:
- LLWatchdogTimerThread() :
- LLThread("Watchdog"),
- mSleepMsecs(0),
- mStopping(false)
- {
- }
-
- ~LLWatchdogTimerThread() {}
-
- void setSleepTime(long ms) { mSleepMsecs = ms; }
- void stop()
- {
- mStopping = true;
- mSleepMsecs = 1;
- }
-
- void run() override
- {
- while(!mStopping)
- {
- LLWatchdog::getInstance()->run();
- ms_sleep(mSleepMsecs);
- }
- }
-
-private:
- long mSleepMsecs;
- bool mStopping;
-};
-
-// LLWatchdogEntry
-LLWatchdogEntry::LLWatchdogEntry(const std::string& thread_name)
- : mThreadName(thread_name)
- , mThreadID(LLThread::currentID())
-{
-}
-
-LLWatchdogEntry::~LLWatchdogEntry()
-{
- stop();
-}
-
-void LLWatchdogEntry::start()
-{
- LLWatchdog::getInstance()->add(this);
-}
-
-void LLWatchdogEntry::stop()
-{
- // this can happen very late in the shutdown sequence
- if (!LLWatchdog::wasDeleted())
- {
- LLWatchdog::getInstance()->remove(this);
- }
-}
-std::string LLWatchdogEntry::getThreadName() const
-{
- return mThreadName + llformat(": %d", mThreadID);
-}
-
-// LLWatchdogTimeout
-const std::string UNINIT_STRING = "uninitialized";
-
-LLWatchdogTimeout::LLWatchdogTimeout(const std::string& thread_name) :
- LLWatchdogEntry(thread_name),
- mTimeout(0.0f),
- mPingState(UNINIT_STRING)
-{
-}
-
-LLWatchdogTimeout::~LLWatchdogTimeout()
-{
-}
-
-bool LLWatchdogTimeout::isAlive() const
-{
- return (mTimer.getStarted() && !mTimer.hasExpired());
-}
-
-void LLWatchdogTimeout::reset()
-{
- mTimer.setTimerExpirySec(mTimeout);
-}
-
-void LLWatchdogTimeout::setTimeout(F32 d)
-{
- mTimeout = d;
-}
-
-void LLWatchdogTimeout::start(std::string_view state)
-{
- if (mTimeout == 0)
- {
- LL_WARNS() << "Cant' start watchdog entry - no timeout set" << LL_ENDL;
- return;
- }
- // Order of operation is very important here.
- // After LLWatchdogEntry::start() is called
- // LLWatchdogTimeout::isAlive() will be called asynchronously.
- ping(state);
- mTimer.start();
- mTimer.setTimerExpirySec(mTimeout); // timer expiration set to 0 by start()
- LLWatchdogEntry::start();
-}
-
-void LLWatchdogTimeout::stop()
-{
- LLWatchdogEntry::stop();
- mTimer.stop();
-}
-
-void LLWatchdogTimeout::ping(std::string_view state)
-{
- if (!state.empty())
- {
- mPingState = state;
- }
- reset();
-}
-
-// LLWatchdog
-LLWatchdog::LLWatchdog()
- :mSuspectsAccessMutex()
- ,mTimer(nullptr)
- ,mLastClockCount(0)
-{
-}
-
-LLWatchdog::~LLWatchdog()
-{
-}
-
-void LLWatchdog::add(LLWatchdogEntry* e)
-{
- lockThread();
- mSuspects.insert(e);
- unlockThread();
-}
-
-void LLWatchdog::remove(LLWatchdogEntry* e)
-{
- lockThread();
- mSuspects.erase(e);
- unlockThread();
-}
-
-void LLWatchdog::init()
-{
- if (!mSuspectsAccessMutex && !mTimer)
- {
- mSuspectsAccessMutex = new LLMutex();
- mTimer = new LLWatchdogTimerThread();
- mTimer->setSleepTime(WATCHDOG_SLEEP_TIME_USEC / 1000);
- mLastClockCount = LLTimer::getTotalTime();
-
- // mTimer->start() kicks off the thread, any code after
- // start needs to use the mSuspectsAccessMutex
- mTimer->start();
- }
-}
-
-void LLWatchdog::cleanup()
-{
- if (mTimer)
- {
- mTimer->stop();
- delete mTimer;
- mTimer = nullptr;
- }
-
- if (mSuspectsAccessMutex)
- {
- delete mSuspectsAccessMutex;
- mSuspectsAccessMutex = nullptr;
- }
-
- mLastClockCount = 0;
-}
-
-void LLWatchdog::run()
-{
- lockThread();
-
- // Check the time since the last call to run...
- // If the time elapsed is two times greater than the regualr sleep time
- // reset the active timeouts.
- constexpr U32 TIME_ELAPSED_MULTIPLIER = 2;
- U64 current_time = LLTimer::getTotalTime();
- U64 current_run_delta = current_time - mLastClockCount;
- mLastClockCount = current_time;
-
- if (current_run_delta > (WATCHDOG_SLEEP_TIME_USEC * TIME_ELAPSED_MULTIPLIER))
- {
- LL_INFOS() << "Watchdog thread delayed: resetting entries." << LL_ENDL;
- for (const auto& suspect : mSuspects)
- {
- suspect->reset();
- }
- }
- else
- {
- SuspectsRegistry::iterator result =
- std::find_if(mSuspects.begin(),
- mSuspects.end(),
- [](const LLWatchdogEntry* suspect){ return ! suspect->isAlive(); });
- if (result != mSuspects.end())
- {
- // error!!!
- if(mTimer)
- {
- mTimer->stop();
- }
- if (LLAppViewer::instance()->logoutRequestSent())
- {
- LLAppViewer::instance()->createErrorMarker(LAST_EXEC_LOGOUT_FROZE);
- }
- else
- {
- LLAppViewer::instance()->createErrorMarker(LAST_EXEC_FROZE);
- }
- // Todo1: Warn user?
- // Todo2: We probably want to report even if 5 seconds passed, just not error 'yet'.
- // Todo3: This will report crash as 'llerror', consider adding 'watchdog' reason.
- std::string last_state = (*result)->getLastState();
- if (last_state.empty())
- {
- LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName()
- << " expired; assuming viewer is hung and crashing" << LL_ENDL;
- }
- else
- {
- LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName()
- << " expired with state: " << last_state
- << "; assuming viewer is hung and crashing" << LL_ENDL;
- }
- }
- }
-
-
- unlockThread();
-}
-
-void LLWatchdog::lockThread()
-{
- if (mSuspectsAccessMutex)
- {
- mSuspectsAccessMutex->lock();
- }
-}
-
-void LLWatchdog::unlockThread()
-{
- if (mSuspectsAccessMutex)
- {
- mSuspectsAccessMutex->unlock();
- }
-}
diff --git a/indra/newview/llwatchdog.h b/indra/newview/llwatchdog.h
deleted file mode 100644
index a8056f4337..0000000000
--- a/indra/newview/llwatchdog.h
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * @file llthreadwatchdog.h
- * @brief The LLThreadWatchdog class declaration
- *
- * $LicenseInfo:firstyear=2007&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, 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
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#ifndef LL_LLTHREADWATCHDOG_H
-#define LL_LLTHREADWATCHDOG_H
-
-#ifndef LL_TIMER_H
- #include "lltimer.h"
-#endif
-
-// LLWatchdogEntry is the interface used by the tasks that
-// need to be watched.
-class LLWatchdogEntry
-{
-public:
- LLWatchdogEntry(const std::string &thread_name);
- virtual ~LLWatchdogEntry();
-
- // isAlive is accessed by the watchdog thread.
- // This may mean that resources used by
- // isAlive and other method may need synchronization.
- virtual bool isAlive() const = 0;
- virtual void reset() = 0;
- virtual void start();
- virtual void stop();
- virtual std::string getLastState() const { return std::string(); }
- typedef std::thread::id id_t;
- std::string getThreadName() const;
-
-private:
- id_t mThreadID; // ID of the thread being watched
- std::string mThreadName;
-};
-
-class LLWatchdogTimeout : public LLWatchdogEntry
-{
-public:
- LLWatchdogTimeout(const std::string& thread_name);
- virtual ~LLWatchdogTimeout();
-
- bool isAlive() const override;
- void reset() override;
- void start() override { start(""); }
- void stop() override;
-
- void start(std::string_view state);
- void setTimeout(F32 d);
- void ping(std::string_view state);
- const std::string& getState() {return mPingState; }
- std::string getLastState() const override { return mPingState; }
-
-private:
- LLTimer mTimer;
- F32 mTimeout;
- std::string mPingState;
-};
-
-class LLWatchdogTimerThread; // Defined in the cpp
-class LLWatchdog : public LLSingleton
-{
- LLSINGLETON(LLWatchdog);
- ~LLWatchdog();
-
-public:
- // Add an entry to the watchdog.
- void add(LLWatchdogEntry* e);
- void remove(LLWatchdogEntry* e);
-
- void init();
- void run();
- void cleanup();
-
-private:
- void lockThread();
- void unlockThread();
-
- typedef std::set SuspectsRegistry;
- SuspectsRegistry mSuspects;
- LLMutex* mSuspectsAccessMutex;
- LLWatchdogTimerThread* mTimer;
- U64 mLastClockCount;
-};
-
-#endif // LL_LLTHREADWATCHDOG_H
--
cgit v1.3
From f57c934676a928e84ae3af55b4886076decca2fc Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Sat, 20 Dec 2025 13:51:31 +0200
Subject: #3612 Log issues with landmarks and rethrown exceptions
---
indra/llcommon/llthread.cpp | 6 +++++-
indra/llcommon/workqueue.cpp | 7 ++++++-
indra/llcommon/workqueue.h | 6 +++++-
indra/newview/lllandmarklist.cpp | 15 ++++++++++-----
indra/newview/skins/default/xui/en/notifications.xml | 2 +-
5 files changed, 27 insertions(+), 9 deletions(-)
diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp
index 692941a892..8c12ee7f12 100644
--- a/indra/llcommon/llthread.cpp
+++ b/indra/llcommon/llthread.cpp
@@ -240,7 +240,11 @@ void LLThread::tryRun()
LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
main_queue->post(
// Bind the current exception, rethrow it in main loop.
- [exc = std::current_exception()]() { std::rethrow_exception(exc); });
+ [exc = std::current_exception(), name = mName]()
+ {
+ LL_INFOS("THREAD") << "Rethrowing exception from thread " << name << LL_ENDL;
+ std::rethrow_exception(exc);
+ });
}
#endif // else LL_WINDOWS
}
diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp
index 7efaebd569..0407d6c3e9 100644
--- a/indra/llcommon/workqueue.cpp
+++ b/indra/llcommon/workqueue.cpp
@@ -216,10 +216,15 @@ void LL::WorkQueueBase::callWork(const Work& work)
LL_WARNS("LLCoros") << "Capturing and rethrowing uncaught exception in WorkQueueBase "
<< getKey() << LL_ENDL;
+ std::string name = getKey();
LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
main_queue->post(
// Bind the current exception, rethrow it in main loop.
- [exc = std::current_exception()]() { std::rethrow_exception(exc); });
+ [exc = std::current_exception(), name]()
+ {
+ LL_INFOS("LLCoros") << "Rethrowing exception from WorkQueueBase::callWork " << name << LL_ENDL;
+ std::rethrow_exception(exc);
+ });
}
else
{
diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h
index 735ad38a26..573203a5b3 100644
--- a/indra/llcommon/workqueue.h
+++ b/indra/llcommon/workqueue.h
@@ -530,7 +530,11 @@ namespace LL
reply,
// Bind the current exception to transport back to the
// originating WorkQueue. Once there, rethrow it.
- [exc = std::current_exception()](){ std::rethrow_exception(exc); });
+ [exc = std::current_exception()]()
+ {
+ LL_INFOS("LLCoros") << "Rethrowing exception from WorkQueueBase::postTo" << LL_ENDL;
+ std::rethrow_exception(exc);
+ });
}
},
// if caller passed a TimePoint, pass it along to post()
diff --git a/indra/newview/lllandmarklist.cpp b/indra/newview/lllandmarklist.cpp
index 3fa0ab99f3..d67b5885f6 100644
--- a/indra/newview/lllandmarklist.cpp
+++ b/indra/newview/lllandmarklist.cpp
@@ -158,16 +158,21 @@ void LLLandmarkList::processGetAssetReply(
}
else
{
- // SJB: No use case for a notification here. Use LL_DEBUGS() instead
+ // SJB: No use case for a notification here.
+ //
+ // Todo: potentially cap getting obsolete due to a teleport
+ // can lead to this, so this might need a timeout or smarter
+ // handling to rerequest after a time instead of just failing
+ // al future requests.
if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status )
{
- LL_WARNS("Landmarks") << "Missing Landmark" << LL_ENDL;
- //LLNotificationsUtil::add("LandmarkMissing");
+ LL_WARNS("Landmarks") << "Missing Landmark " << uuid << LL_ENDL;
}
else
{
- LL_WARNS("Landmarks") << "Unable to load Landmark" << LL_ENDL;
- //LLNotificationsUtil::add("UnableToLoadLandmark");
+ LL_WARNS("Landmarks") << "Unable to load Landmark " << uuid
+ << ". asset status: " << status
+ << ". Extended status: " << (S64)ext_status << LL_ENDL;
}
gLandmarkList.mBadList.insert(uuid);
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index d0261a930c..82e2229d76 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -2469,7 +2469,7 @@ You already have a landmark for this location.
icon="alert.tga"
name="LandmarkLocationUnknown"
type="alert">
-Viewer wasn't able to get region's location. Region might be temporarily unavailable or was removed.
+Viewer wasn't able to get region's location. Region might be temporarily unavailable, was removed or landmark failed to load.
--
cgit v1.3
From dd0dbf205cfb97451b5180bbc1cab6d2b40cbfbc Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Mon, 22 Dec 2025 20:18:41 +0200
Subject: #3612 Handle missing capabilities instead of blocking further
downloads
Landmarks that failed due to missing caps were blocked from being
rerequested.
---
indra/llmessage/llassetstorage.cpp | 3 +++
indra/llmessage/llassetstorage.h | 1 +
indra/newview/lllandmarklist.cpp | 46 ++++++++++++++++++++++++++++------
indra/newview/lllandmarklist.h | 1 +
indra/newview/llviewerassetstorage.cpp | 17 ++++++++++---
5 files changed, 58 insertions(+), 10 deletions(-)
diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp
index 10fd56a68e..4c3acb27f4 100644
--- a/indra/llmessage/llassetstorage.cpp
+++ b/indra/llmessage/llassetstorage.cpp
@@ -1316,6 +1316,9 @@ const char* LLAssetStorage::getErrorString(S32 status)
case LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE:
return "Asset request: asset not found in database";
+ case LL_ERR_NO_CAP:
+ return "Asset request: region or asset capability not available";
+
case LL_ERR_EOF:
return "End of file";
diff --git a/indra/llmessage/llassetstorage.h b/indra/llmessage/llassetstorage.h
index 6d6526757d..d5daa0cb8f 100644
--- a/indra/llmessage/llassetstorage.h
+++ b/indra/llmessage/llassetstorage.h
@@ -57,6 +57,7 @@ const int LL_ERR_ASSET_REQUEST_FAILED = -1;
const int LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE = -3;
const int LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE = -4;
const int LL_ERR_INSUFFICIENT_PERMISSIONS = -5;
+const int LL_ERR_NO_CAP = -6;
const int LL_ERR_PRICE_MISMATCH = -23018;
// *TODO: these typedefs are passed into the cache via a legacy C function pointer
diff --git a/indra/newview/lllandmarklist.cpp b/indra/newview/lllandmarklist.cpp
index d67b5885f6..b25a42a938 100644
--- a/indra/newview/lllandmarklist.cpp
+++ b/indra/newview/lllandmarklist.cpp
@@ -147,24 +147,55 @@ void LLLandmarkList::processGetAssetReply(
else
{
// failed to parse, shouldn't happen
+ LL_WARNS("Landmarks") << "Failed to parse landmark " << uuid << LL_ENDL;
gLandmarkList.eraseCallbacks(uuid);
}
}
else
{
// got a good status, but no file, shouldn't happen
+ LL_WARNS("Landmarks") << "Empty buffer for landmark " << uuid << LL_ENDL;
gLandmarkList.eraseCallbacks(uuid);
}
+
+ // We got this asset, remove it from retry and bad lists.
+ gLandmarkList.mRetryList.erase(uuid);
+ gLandmarkList.mBadList.erase(uuid);
}
else
{
- // SJB: No use case for a notification here.
- //
- // Todo: potentially cap getting obsolete due to a teleport
- // can lead to this, so this might need a timeout or smarter
- // handling to rerequest after a time instead of just failing
- // al future requests.
- if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status )
+ if (LL_ERR_NO_CAP == status)
+ {
+ // A problem with asset cap, always allow retrying.
+ // Todo: should this reschedule?
+ gLandmarkList.mRequestedList.erase(uuid);
+ gLandmarkList.eraseCallbacks(uuid);
+ // If there was a previous request, it likely failed due to an obsolete cap
+ // so clear the retry marker to allow multiple retries.
+ gLandmarkList.mRetryList.erase(uuid);
+ return;
+ }
+ if (gLandmarkList.mBadList.find(uuid) != gLandmarkList.mBadList.end())
+ {
+ // Already on the 'bad' list, ignore
+ gLandmarkList.mRequestedList.erase(uuid);
+ gLandmarkList.eraseCallbacks(uuid);
+ return;
+ }
+ if (LL_ERR_ASSET_REQUEST_FAILED == status
+ && gLandmarkList.mRetryList.find(uuid) == gLandmarkList.mRetryList.end())
+ {
+ // There is a number of reasons why an asset request can fail,
+ // like a cap being obsolete due to user teleporting.
+ // Let viewer rerequest at least once more.
+ // Todo: should this reshchedule?
+ gLandmarkList.mRetryList.emplace(uuid);
+ gLandmarkList.mRequestedList.erase(uuid);
+ gLandmarkList.eraseCallbacks(uuid);
+ return;
+ }
+
+ if (LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status)
{
LL_WARNS("Landmarks") << "Missing Landmark " << uuid << LL_ENDL;
}
@@ -175,6 +206,7 @@ void LLLandmarkList::processGetAssetReply(
<< ". Extended status: " << (S64)ext_status << LL_ENDL;
}
+ gLandmarkList.mRetryList.erase(uuid);
gLandmarkList.mBadList.insert(uuid);
gLandmarkList.mRequestedList.erase(uuid); //mBadList effectively blocks any load, so no point keeping id in requests
gLandmarkList.eraseCallbacks(uuid);
diff --git a/indra/newview/lllandmarklist.h b/indra/newview/lllandmarklist.h
index fb8b5a1960..76b5b97211 100644
--- a/indra/newview/lllandmarklist.h
+++ b/indra/newview/lllandmarklist.h
@@ -72,6 +72,7 @@ protected:
typedef std::set landmark_uuid_list_t;
landmark_uuid_list_t mBadList;
+ landmark_uuid_list_t mRetryList;
typedef std::map landmark_requested_list_t;
landmark_requested_list_t mRequestedList;
diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp
index 141f370ecb..de6b2d9e7c 100644
--- a/indra/newview/llviewerassetstorage.cpp
+++ b/indra/newview/llviewerassetstorage.cpp
@@ -461,7 +461,7 @@ void LLViewerAssetStorage::assetRequestCoro(
if (!gAgent.getRegion())
{
LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: no region set" << LL_ENDL;
- result_code = LL_ERR_ASSET_REQUEST_FAILED;
+ result_code = LL_ERR_NO_CAP;
ext_status = LLExtStat::NONE;
removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0);
return;
@@ -475,13 +475,24 @@ void LLViewerAssetStorage::assetRequestCoro(
gAgent.getRegion()->setCapabilitiesReceivedCallback(
boost::bind(&LLViewerAssetStorage::capsRecvForRegion, this, _1, capsRecv.getName()));
- llcoro::suspendUntilEventOn(capsRecv);
+ F32Seconds timeout_seconds(LL_ASSET_STORAGE_TIMEOUT); // from minutes to seconds, by default 5 minutes
+ LLSD result = llcoro::suspendUntilEventOnWithTimeout(capsRecv, timeout_seconds, LLSDMap("timeout", LLSD::Boolean(true)));
if (LLApp::isExiting() || !gAssetStorage)
{
return;
}
+ if (result.has("timeout"))
+ {
+ // Caps failed to arrive in 5 minutes
+ LL_WARNS_ONCE("ViewerAsset") << "Asset " << uuid << " request fails : capabilities took too long to arrive" << LL_ENDL;
+ result_code = LL_ERR_NO_CAP;
+ ext_status = LLExtStat::NONE;
+ removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0);
+ return;
+ }
+
LL_WARNS_ONCE("ViewerAsset") << "capsRecv got event" << LL_ENDL;
LL_WARNS_ONCE("ViewerAsset") << "region " << gAgent.getRegion() << " mViewerAssetUrl " << mViewerAssetUrl << LL_ENDL;
}
@@ -492,7 +503,7 @@ void LLViewerAssetStorage::assetRequestCoro(
if (mViewerAssetUrl.empty())
{
LL_WARNS_ONCE("ViewerAsset") << "asset request fails: caps received but no viewer asset cap found" << LL_ENDL;
- result_code = LL_ERR_ASSET_REQUEST_FAILED;
+ result_code = LL_ERR_NO_CAP;
ext_status = LLExtStat::NONE;
removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0);
return;
--
cgit v1.3
From 793acc7f57b01edd86fdbfba3981027ce9498888 Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Tue, 23 Dec 2025 02:35:08 +0200
Subject: #3612 Don't immediately fail download on startup, wait for the region
It's probably smarter to make asset storage wait with requests in
general, but that would require a stronger separation from cache which
is functional already and can be loading assets, so I'm pausing
individual requests instead, there is a maximum of 12, so there should
be no 'bloat' from waiting coros.
---
indra/newview/llviewerassetstorage.cpp | 56 +++++++++++++++++++++++++++++-----
1 file changed, 48 insertions(+), 8 deletions(-)
diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp
index de6b2d9e7c..fd462fb225 100644
--- a/indra/newview/llviewerassetstorage.cpp
+++ b/indra/newview/llviewerassetstorage.cpp
@@ -42,6 +42,7 @@
#include "llcoproceduremanager.h"
#include "lleventcoro.h"
#include "llsdutil.h"
+#include "llstartup.h"
#include "llworld.h"
///----------------------------------------------------------------------------
@@ -460,23 +461,62 @@ void LLViewerAssetStorage::assetRequestCoro(
if (!gAgent.getRegion())
{
- LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: no region set" << LL_ENDL;
- result_code = LL_ERR_NO_CAP;
- ext_status = LLExtStat::NONE;
- removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0);
- return;
+ if (STATE_WORLD_INIT <= LLStartUp::getStartupState())
+ {
+ // Viewer isn't ready, wait for region to become available
+ LL_INFOS_ONCE("ViewerAsset") << "Waiting for agent region to be set" << LL_ENDL;
+
+ LLEventStream region_init("waitForRegion", true);
+ std::string pump_name = region_init.getName();
+
+ boost::signals2::connection region_conn =
+ gAgent.addRegionChangedCallback([pump_name]()
+ {
+ LLEventPumps::instance().obtain(pump_name).post(LLSD());
+ });
+ F32Seconds timeout_seconds(LL_ASSET_STORAGE_TIMEOUT);
+ llcoro::suspendUntilEventOnWithTimeout(region_init, timeout_seconds, LLSDMap("timeout", LLSD::Boolean(true)));
+ gAgent.removeRegionChangedCallback(region_conn);
+ region_conn.disconnect();
+
+ if (LLApp::isExiting() || !gAssetStorage)
+ {
+ return;
+ }
+
+ // recheck region whether suspend ended on timeout or not
+ if (!gAgent.getRegion())
+ {
+ LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: timeout reached while waiting for region" << LL_ENDL;
+ result_code = LL_ERR_NO_CAP;
+ ext_status = LLExtStat::NONE;
+ removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0);
+ return;
+ }
+ }
+ else
+ {
+ LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: no region set" << LL_ENDL;
+ result_code = LL_ERR_NO_CAP;
+ ext_status = LLExtStat::NONE;
+ removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0);
+ return;
+ }
}
- else if (!gAgent.getRegion()->capabilitiesReceived())
+
+ if (!gAgent.getRegion()->capabilitiesReceived())
{
LL_WARNS_ONCE("ViewerAsset") << "Waiting for capabilities" << LL_ENDL;
LLEventStream capsRecv("waitForCaps", true);
- gAgent.getRegion()->setCapabilitiesReceivedCallback(
- boost::bind(&LLViewerAssetStorage::capsRecvForRegion, this, _1, capsRecv.getName()));
+ boost::signals2::connection caps_conn =
+ gAgent.getRegion()->setCapabilitiesReceivedCallback(
+ boost::bind(&LLViewerAssetStorage::capsRecvForRegion, this, _1, capsRecv.getName()));
F32Seconds timeout_seconds(LL_ASSET_STORAGE_TIMEOUT); // from minutes to seconds, by default 5 minutes
LLSD result = llcoro::suspendUntilEventOnWithTimeout(capsRecv, timeout_seconds, LLSDMap("timeout", LLSD::Boolean(true)));
+ caps_conn.disconnect();
if (LLApp::isExiting() || !gAssetStorage)
{
--
cgit v1.3
From 2ba9383d0d008dc44a062fd8566d3d6a171b0dd8 Mon Sep 17 00:00:00 2001
From: Maxim Nikolenko
Date: Tue, 6 Jan 2026 20:12:51 +0200
Subject: revert #4735 Remove the word "Viewer" from application shortcut
---
indra/newview/viewer_manifest.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 94d234686a..109f00c9ae 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -259,9 +259,10 @@ class ViewerManifest(LLManifest):
global CHANNEL_VENDOR_BASE
channel_type=self.channel_type()
if channel_type == 'release':
- return CHANNEL_VENDOR_BASE
+ app_suffix='Viewer'
else:
- return CHANNEL_VENDOR_BASE + ' ' + self.channel_variant()
+ app_suffix=self.channel_variant()
+ return CHANNEL_VENDOR_BASE + ' ' + app_suffix
def exec_name(self):
return "SecondLifeViewer"
--
cgit v1.3
From aba287fd0cb0702292651234e87c441779b40e2f Mon Sep 17 00:00:00 2001
From: Maxim Nikolenko
Date: Wed, 7 Jan 2026 20:44:32 +0200
Subject: #5220 do not save 'Highlight Transparent Probes' setting between
sessions
---
indra/newview/app_settings/settings.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index fe31a00ba3..611c6932e2 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -9058,7 +9058,7 @@
Comment
Show reflection probes in the transparency debug view
Persist
- 1
+ 0
Type
Boolean
Value
--
cgit v1.3
From b26f62eb0ce72b9cdd83296e87ba1954ee1b8b04 Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Wed, 7 Jan 2026 23:53:15 +0200
Subject: #4991 Fix showing incorrect alpha
Due to _isAlpha alpha was considered as blend even if only one texture
had an alpha and was set to anything but blend.
---
indra/newview/llpanelface.cpp | 32 +++++++++++++++++++++++---------
indra/newview/llpanelface.h | 2 +-
2 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp
index de8ab95dee..bcb51b22ca 100644
--- a/indra/newview/llpanelface.cpp
+++ b/indra/newview/llpanelface.cpp
@@ -1201,7 +1201,7 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
// See if that's been overridden by a material setting for same...
//
- LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode, mIsAlpha);
+ LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode);
// it is invalid to have any alpha mode other than blend if transparency is greater than zero ...
// Want masking? Want emissive? Tough! You get BLEND!
@@ -1211,6 +1211,12 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
alpha_mode = mIsAlpha ? alpha_mode : LLMaterial::DIFFUSE_ALPHA_MODE_NONE;
mComboAlphaMode->getSelectionInterface()->selectNthItem(alpha_mode);
+ mComboAlphaMode->setTentative(!identical_alpha_mode);
+ if (!identical_alpha_mode)
+ {
+ std::string multiple = LLTrans::getString("multiple_textures");
+ mComboAlphaMode->setLabel(multiple);
+ }
updateAlphaControls();
mExcludeWater &= (LLMaterial::DIFFUSE_ALPHA_MODE_BLEND == alpha_mode);
@@ -5484,32 +5490,40 @@ void LLPanelFace::LLSelectedTEMaterial::getMaxNormalRepeats(F32& repeats, bool&
identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &max_norm_repeats_func, repeats);
}
-void LLPanelFace::LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical, bool diffuse_texture_has_alpha)
+void LLPanelFace::LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical)
{
struct LLSelectedTEGetDiffuseAlphaMode : public LLSelectedTEGetFunctor
{
- LLSelectedTEGetDiffuseAlphaMode() : _isAlpha(false) {}
- LLSelectedTEGetDiffuseAlphaMode(bool diffuse_texture_has_alpha) : _isAlpha(diffuse_texture_has_alpha) {}
+ LLSelectedTEGetDiffuseAlphaMode() {}
virtual ~LLSelectedTEGetDiffuseAlphaMode() {}
U8 get(LLViewerObject* object, S32 face)
{
- U8 diffuse_mode = _isAlpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE;
-
LLTextureEntry* tep = object->getTE(face);
if (tep)
{
LLMaterial* mat = tep->getMaterialParams().get();
if (mat)
{
- diffuse_mode = mat->getDiffuseAlphaMode();
+ return mat->getDiffuseAlphaMode();
+ }
+ }
+
+ bool has_alpha = false;
+ LLViewerTexture* image = object->getTEImage(face);
+ if (image)
+ {
+ LLGLenum format = image->getPrimaryFormat();
+ if (format == GL_RGBA || format == GL_ALPHA)
+ {
+ has_alpha = true;
}
}
+ U8 diffuse_mode = has_alpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE;
return diffuse_mode;
}
- bool _isAlpha; // whether or not the diffuse texture selected contains alpha information
- } get_diff_mode(diffuse_texture_has_alpha);
+ } get_diff_mode;
identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &get_diff_mode, diffuse_alpha_mode);
}
diff --git a/indra/newview/llpanelface.h b/indra/newview/llpanelface.h
index 63fee6bab8..82790ac54b 100644
--- a/indra/newview/llpanelface.h
+++ b/indra/newview/llpanelface.h
@@ -652,7 +652,7 @@ public:
static void getCurrent(LLMaterialPtr& material_ptr, bool& identical_material);
static void getMaxSpecularRepeats(F32& repeats, bool& identical);
static void getMaxNormalRepeats(F32& repeats, bool& identical);
- static void getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical, bool diffuse_texture_has_alpha);
+ static void getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical);
static void selectionNormalScaleAutofit(LLPanelFace* panel_face, F32 repeats_per_meter);
static void selectionSpecularScaleAutofit(LLPanelFace* panel_face, F32 repeats_per_meter);
--
cgit v1.3
From db014c8fc5249dd4067f769d2249382c6991db76 Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Wed, 7 Jan 2026 23:12:25 +0200
Subject: #5232 Crash on mHoverItem in context menu
---
indra/llui/llmenugl.cpp | 14 ++++++++++++++
indra/llui/llmenugl.h | 2 ++
2 files changed, 16 insertions(+)
diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp
index 2ca2454040..6ba31c251e 100644
--- a/indra/llui/llmenugl.cpp
+++ b/indra/llui/llmenugl.cpp
@@ -4404,3 +4404,17 @@ bool LLContextMenu::addChild(LLView* view, S32 tab_group)
return addContextChild(view, tab_group);
}
+void LLContextMenu::deleteAllChildren()
+{
+ mHoverItem = nullptr;
+ LLMenuGL::deleteAllChildren();
+}
+
+void LLContextMenu::removeChild(LLView* ctrl)
+{
+ if (ctrl == mHoverItem)
+ {
+ mHoverItem = nullptr;
+ }
+ LLMenuGL::removeChild(ctrl);
+}
diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h
index eacf2c59d4..bca0a731fc 100644
--- a/indra/llui/llmenugl.h
+++ b/indra/llui/llmenugl.h
@@ -730,6 +730,8 @@ public:
virtual bool handleRightMouseUp ( S32 x, S32 y, MASK mask );
virtual bool addChild (LLView* view, S32 tab_group = 0);
+ /*virtual*/ void deleteAllChildren();
+ /*virtual*/ void removeChild(LLView* ctrl);
LLHandle getHandle() { return getDerivedHandle(); }
--
cgit v1.3
From 31e909870b316b7b4f36396357f70e44c724e608 Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
Date: Thu, 8 Jan 2026 00:30:00 +0200
Subject: #5232 Crash in handleMessage when trying to access llsd's content
Crash at LLSD::Impl::assign
---
indra/newview/lleventpoll.cpp | 58 +++++++++++++++++++++++++++++--------------
1 file changed, 39 insertions(+), 19 deletions(-)
diff --git a/indra/newview/lleventpoll.cpp b/indra/newview/lleventpoll.cpp
index de3752d879..f1b46f0533 100644
--- a/indra/newview/lleventpoll.cpp
+++ b/indra/newview/lleventpoll.cpp
@@ -56,6 +56,7 @@ namespace Details
private:
void eventPollCoro(std::string url);
+ void handleMessage(const std::string& msg_name, const LLSD& body);
void handleMessage(const LLSD &content);
bool mDone;
@@ -95,21 +96,23 @@ namespace Details
mSenderIp = sender.getIPandPort();
}
+ void LLEventPollImpl::handleMessage(const std::string &msg_name, const LLSD &body)
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_APP;
+ LLSD message;
+ message["sender"] = mSenderIp;
+ message["body"] = body;
+
+ LLMessageSystem::dispatch(msg_name, message);
+ }
+
void LLEventPollImpl::handleMessage(const LLSD& content)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_APP;
std::string msg_name = content["message"].asString();
LLSD message;
- try
- {
- message["sender"] = mSenderIp;
- message["body"] = content["body"];
- }
- catch (std::bad_alloc&)
- {
- LLError::LLUserWarningMsg::showOutOfMemory();
- LL_ERRS("LLCoros") << "Bad memory allocation on message: " << msg_name << LL_ENDL;
- }
+ message["sender"] = mSenderIp;
+ message["body"] = content["body"];
LLMessageSystem::dispatch(msg_name, message);
}
@@ -194,7 +197,7 @@ namespace Details
break;
}
- LLSD httpResults = result["http_result"];
+ LLSD &httpResults = result["http_result"];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
if (!status)
@@ -299,7 +302,7 @@ namespace Details
}
acknowledge = result["id"];
- LLSD events = result["events"];
+ LLSD &events = result["events"];
if (acknowledge.isUndefined())
{
@@ -310,20 +313,37 @@ namespace Details
LL_DEBUGS("LLEventPollImpl") << " <" << counter << "> " << events.size() << "events (id " << acknowledge << ")" << LL_ENDL;
- LLSD::array_const_iterator i = events.beginArray();
- LLSD::array_const_iterator end = events.endArray();
+ LLSD::array_iterator i = events.beginArray();
+ LLSD::array_iterator end = events.endArray();
for (; i != end; ++i)
{
if (i->has("message"))
{
if (main_queue)
- { // shuttle to a sensible spot in the main thread instead
+ {
+ // Shuttle copy to a sensible spot in the main thread instead
// of wherever this coroutine happens to be executing
- const LLSD& msg = *i;
- main_queue->post([this, msg]()
+
+ LL::WorkQueue::Work work;
+ {
+ // LLSD is too smart for it's own good and may act like a smart
+ // pointer for the content of (*i), so instead of passing (*i)
+ // pass a prepared name and move ownership of "body",
+ // as we are not going to need "body" anywhere else.
+ std::string msg_name = (*i)["message"].asString();
+
+ // WARNING: This is a shallow copy!
+ // If something still retains the data (like in httpAdapter?) this might still
+ // result in a crash, if it does appear to be the case, make a deep copy or
+ // convert data to string and pass that string.
+ const LLSD body = (*i)["body"];
+ (*i)["body"].clear();
+ work = [this, msg_name, body]()
{
- handleMessage(msg);
- });
+ handleMessage(msg_name, body);
+ };
+ }
+ main_queue->post(work);
}
else
{
--
cgit v1.3