From bd86aed0750647ede14b1aea9284171719d29406 Mon Sep 17 00:00:00 2001 From: Ross Barnes-Brown <128988672+Quinn-Elara@users.noreply.github.com> Date: Tue, 7 Jul 2026 12:12:14 +0100 Subject: [PATCH 1/4] Fix another outfit-related CTD Defers notifying observers to run on the main thread. Fixes a CTD wherein glyph creation could happen outside of the main thread which would cause a CTD if uncaught. --- indra/newview/llaisapi.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp index a1191e8a31..73dfa9910f 100644 --- a/indra/newview/llaisapi.cpp +++ b/indra/newview/llaisapi.cpp @@ -1683,7 +1683,11 @@ void AISUpdate::doUpdate() // fetching can receive massive amount of items and folders if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) { - gInventory.notifyObservers(); + LLAppViewer::instance()->postToMainCoro( + []() + { + gInventory.notifyObservers(); + }); checkTimeout(); } } @@ -1744,7 +1748,11 @@ void AISUpdate::doUpdate() // fetching can receive massive amount of items and folders if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) { - gInventory.notifyObservers(); + LLAppViewer::instance()->postToMainCoro( + []() + { + gInventory.notifyObservers(); + }); checkTimeout(); } } @@ -1815,6 +1823,10 @@ void AISUpdate::doUpdate() checkTimeout(); - gInventory.notifyObservers(); + LLAppViewer::instance()->postToMainCoro( + []() + { + gInventory.notifyObservers(); + }); } From 6c2da73f60e1637b4c7dab6ec053f533cb214bc0 Mon Sep 17 00:00:00 2001 From: Ross Barnes-Brown <128988672+Quinn-Elara@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:42:39 +0100 Subject: [PATCH 2/4] Address llaisapi review comments --- indra/newview/llaisapi.cpp | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp index 73dfa9910f..a5dca715cb 100644 --- a/indra/newview/llaisapi.cpp +++ b/indra/newview/llaisapi.cpp @@ -1044,6 +1044,13 @@ void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t ht U32 AISUpdate::sBatchFrameCount = 0; LLTimer AISUpdate::sBatchTimer; +// Coalesces the deferred gInventory::notifyObservers() calls issued below +// when the change backlog exceeds MAX_UPDATE_BACKLOG, so repeated iterations +// while the backlog remains large don't flood gMainloopWork with duplicate +// postToMainCoro callbacks. Cleared once the posted callback actually runs +// notifyObservers(). +static bool sAISNotifyObserversPending = false; + AISUpdate::AISUpdate(const LLSD& update, AISAPI::COMMAND_TYPE type, const LLSD& request_body) : mType(type) { @@ -1683,11 +1690,16 @@ void AISUpdate::doUpdate() // fetching can receive massive amount of items and folders if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) { - LLAppViewer::instance()->postToMainCoro( - []() - { - gInventory.notifyObservers(); - }); + if (!sAISNotifyObserversPending) + { + sAISNotifyObserversPending = true; + LLAppViewer::instance()->postToMainCoro( + []() + { + gInventory.notifyObservers(); + sAISNotifyObserversPending = false; + }); + } checkTimeout(); } } @@ -1748,11 +1760,16 @@ void AISUpdate::doUpdate() // fetching can receive massive amount of items and folders if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) { - LLAppViewer::instance()->postToMainCoro( - []() - { - gInventory.notifyObservers(); - }); + if (!sAISNotifyObserversPending) + { + sAISNotifyObserversPending = true; + LLAppViewer::instance()->postToMainCoro( + []() + { + gInventory.notifyObservers(); + sAISNotifyObserversPending = false; + }); + } checkTimeout(); } } @@ -1829,4 +1846,3 @@ void AISUpdate::doUpdate() gInventory.notifyObservers(); }); } - From aa6b43b7f1a4b34caf948a80ac5f73711cd458bf Mon Sep 17 00:00:00 2001 From: Ross Barnes-Brown <128988672+Quinn-Elara@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:03:16 +0100 Subject: [PATCH 3/4] fix: respect do_notify_observers in LLInventoryModel::deleteObject The early notifyObservers() call before tree cleanup was unconditional, ignoring the do_notify_observers parameter. This caused observers to fire from non-main coroutines (e.g. the AIS REMOVEITEM coro), which triggered UI updates and GL texture operations that assert they must run on the main coroutine. Deferred notification via idleNotifyObservers() handles the AIS path correctly on the next main loop tick. --- indra/newview/llinventorymodel.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index c6bfcb5b8e..c141ea3259 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -2174,7 +2174,10 @@ void LLInventoryModel::deleteObject(const LLUUID& id, bool fix_broken_links, boo // Note : We need to tell the inventory observers that those things are going to be deleted *before* the tree is cleared or they won't know what to delete (in views and view models) addChangedMask(LLInventoryObserver::REMOVE, id); - gInventory.notifyObservers(); + if (do_notify_observers) + { + notifyObservers(); + } if (item_array_t* item_list = getUnlockedItemArray(id)) { From f85891cb70c092e62cf4bf8fbd8b999d83105af6 Mon Sep 17 00:00:00 2001 From: Ross Barnes-Brown <128988672+Quinn-Elara@users.noreply.github.com> Date: Mon, 13 Jul 2026 23:03:52 +0100 Subject: [PATCH 4/4] refactor(llaisapi): extract AIS notify observer scheduling into helper Extract the duplicated sAISNotifyObserversPending coalescing logic from the category and item creation backlog paths into a shared static helper aisScheduleNotifyObservers(). Guard the final postToMainCoro at the end of AISUpdate::doUpdate() with !sAISNotifyObserversPending to avoid posting a redundant notifyObservers() when the backlog path has already scheduled one. No behaviour change. Addresses review nitpicks. --- indra/newview/llaisapi.cpp | 49 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp index a5dca715cb..dc411f8a94 100644 --- a/indra/newview/llaisapi.cpp +++ b/indra/newview/llaisapi.cpp @@ -1051,6 +1051,20 @@ LLTimer AISUpdate::sBatchTimer; // notifyObservers(). static bool sAISNotifyObserversPending = false; +static void aisScheduleNotifyObservers() +{ + if (!sAISNotifyObserversPending) + { + sAISNotifyObserversPending = true; + LLAppViewer::instance()->postToMainCoro( + []() + { + gInventory.notifyObservers(); + sAISNotifyObserversPending = false; + }); + } +} + AISUpdate::AISUpdate(const LLSD& update, AISAPI::COMMAND_TYPE type, const LLSD& request_body) : mType(type) { @@ -1690,16 +1704,7 @@ void AISUpdate::doUpdate() // fetching can receive massive amount of items and folders if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) { - if (!sAISNotifyObserversPending) - { - sAISNotifyObserversPending = true; - LLAppViewer::instance()->postToMainCoro( - []() - { - gInventory.notifyObservers(); - sAISNotifyObserversPending = false; - }); - } + aisScheduleNotifyObservers(); checkTimeout(); } } @@ -1760,16 +1765,7 @@ void AISUpdate::doUpdate() // fetching can receive massive amount of items and folders if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) { - if (!sAISNotifyObserversPending) - { - sAISNotifyObserversPending = true; - LLAppViewer::instance()->postToMainCoro( - []() - { - gInventory.notifyObservers(); - sAISNotifyObserversPending = false; - }); - } + aisScheduleNotifyObservers(); checkTimeout(); } } @@ -1840,9 +1836,12 @@ void AISUpdate::doUpdate() checkTimeout(); - LLAppViewer::instance()->postToMainCoro( - []() - { - gInventory.notifyObservers(); - }); + if (!sAISNotifyObserversPending) + { + LLAppViewer::instance()->postToMainCoro( + []() + { + gInventory.notifyObservers(); + }); + } }