From 9505e14efe65f426ebf4cf85ad4b876c20d0be64 Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <andreykproductengine@lindenlab.com>
Date: Wed, 10 Mar 2021 18:04:22 +0200
Subject: [PATCH] SL-14975 SL-14384 viewer crashes because of large chat groups

1. Due to desync participant can be NULL - added NULL checks
2. With large backlog of events, closing and then opening a goup session was causing a crash due to obsolete events - added cleanup for backlog
3. In some cases events were accumulating faster than they were processed - ensured that after certain point event processing scales up with a backlog
---
 indra/newview/llconversationmodel.cpp   |  4 +-
 indra/newview/llconversationview.cpp    | 11 ++--
 indra/newview/llfloaterimcontainer.cpp  | 69 +++++++++++++++++++------
 indra/newview/llfloaterimcontainer.h    |  5 +-
 indra/newview/llfloaterimsessiontab.cpp |  5 +-
 5 files changed, 70 insertions(+), 24 deletions(-)

diff --git a/indra/newview/llconversationmodel.cpp b/indra/newview/llconversationmodel.cpp
index 4aa74a550cb..27b1f3d20ac 100644
--- a/indra/newview/llconversationmodel.cpp
+++ b/indra/newview/llconversationmodel.cpp
@@ -355,7 +355,7 @@ LLConversationItemParticipant* LLConversationItemSession::findParticipant(const
 	for (iter = mChildren.begin(); iter != mChildren.end(); iter++)
 	{
 		participant = dynamic_cast<LLConversationItemParticipant*>(*iter);
-		if (participant->hasSameValue(participant_id))
+		if (participant && participant->hasSameValue(participant_id))
 		{
 			break;
 		}
@@ -466,7 +466,7 @@ const bool LLConversationItemSession::getTime(F64& time) const
 	{
 		participant = dynamic_cast<LLConversationItemParticipant*>(*iter);
 		F64 participant_time;
-		if (participant->getTime(participant_time))
+		if (participant && participant->getTime(participant_time))
 		{
 			has_time = true;
 			most_recent_time = llmax(most_recent_time,participant_time);
diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp
index 093e772abec..7c1db98b3fc 100644
--- a/indra/newview/llconversationview.cpp
+++ b/indra/newview/llconversationview.cpp
@@ -617,10 +617,13 @@ void LLConversationViewParticipant::refresh()
 {
 	// Refresh the participant view from its model data
 	LLConversationItemParticipant* participant_model = dynamic_cast<LLConversationItemParticipant*>(getViewModelItem());
-	participant_model->resetRefresh();
-	
-	// *TODO: We should also do something with vmi->isModerator() to echo that state in the UI somewhat
-	mSpeakingIndicator->setIsModeratorMuted(participant_model->isModeratorMuted());
+    if (participant_model)
+    {
+        participant_model->resetRefresh();
+
+        // *TODO: We should also do something with vmi->isModerator() to echo that state in the UI somewhat
+        mSpeakingIndicator->setIsModeratorMuted(participant_model->isModeratorMuted());
+    }
 
 	// Do the regular upstream refresh
 	LLFolderViewItem::refresh();
diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp
index c6e9069d090..007c0d3b22c 100644
--- a/indra/newview/llfloaterimcontainer.cpp
+++ b/indra/newview/llfloaterimcontainer.cpp
@@ -59,7 +59,9 @@
 #include "boost/foreach.hpp"
 
 
-const S32 EVENTS_PER_IDLE_LOOP = 100;
+const S32 EVENTS_PER_IDLE_LOOP_CURRENT_SESSION = 80;
+const S32 EVENTS_PER_IDLE_LOOP_BACKGROUND = 40;
+const F32 EVENTS_PER_IDLE_LOOP_MIN_PERCENTAGE = 0.01f; // process a minimum of 1% of total events per frame
 
 //
 // LLFloaterIMContainer
@@ -416,8 +418,11 @@ void LLFloaterIMContainer::processParticipantsStyleUpdate()
 		while (current_participant_model != end_participant_model)
 		{
 			LLConversationItemParticipant* participant_model = dynamic_cast<LLConversationItemParticipant*>(*current_participant_model);
-			// Get the avatar name for this participant id from the cache and update the model
-			participant_model->updateName();
+            if (participant_model)
+            {
+                // Get the avatar name for this participant id from the cache and update the model
+                participant_model->updateName();
+            }
 			// Next participant
 			current_participant_model++;
 		}
@@ -464,8 +469,11 @@ void LLFloaterIMContainer::idleUpdate()
                 while (current_participant_model != end_participant_model)
                 {
                     LLConversationItemParticipant* participant_model = dynamic_cast<LLConversationItemParticipant*>(*current_participant_model);
-                    participant_model->setModeratorOptionsVisible(is_moderator);
-                    participant_model->setGroupBanVisible(can_ban && participant_model->getUUID() != gAgentID);
+                    if (participant_model)
+                    {
+                        participant_model->setModeratorOptionsVisible(is_moderator);
+                        participant_model->setGroupBanVisible(can_ban && participant_model->getUUID() != gAgentID);
+                    }
 
                     current_participant_model++;
                 }
@@ -498,20 +506,49 @@ void LLFloaterIMContainer::idleUpdate()
 
 void LLFloaterIMContainer::idleProcessEvents()
 {
-	if (!mConversationEventQueue.empty())
-	{
-		S32 events_to_handle = llmin((S32)mConversationEventQueue.size(), EVENTS_PER_IDLE_LOOP);
-		for (S32 i = 0; i < events_to_handle; i++)
-		{
-			handleConversationModelEvent(mConversationEventQueue.back());
-			mConversationEventQueue.pop_back();
-		}
-	}
+    LLUUID current_session_id = getSelectedSession();
+    conversations_items_deque::iterator iter = mConversationEventQueue.begin();
+    conversations_items_deque::iterator end = mConversationEventQueue.end();
+    while (iter != end)
+    {
+        std::deque<LLSD> &events = iter->second;
+        if (!events.empty())
+        {
+            S32 events_to_handle;
+            S32 query_size = (S32)events.size();
+            if (current_session_id == iter->first)
+            {
+                events_to_handle = EVENTS_PER_IDLE_LOOP_CURRENT_SESSION;
+            }
+            else
+            {
+                events_to_handle = EVENTS_PER_IDLE_LOOP_BACKGROUND;
+            }
+
+            if (events_to_handle <= query_size)
+            {
+                // Some groups can be very large and can generate huge amount of updates, scale processing up to keep up
+                events_to_handle = llmax(events_to_handle, (S32)(query_size * EVENTS_PER_IDLE_LOOP_MIN_PERCENTAGE));
+            }
+            else
+            {
+                events_to_handle = query_size;
+            }
+
+            for (S32 i = 0; i < events_to_handle; i++)
+            {
+                handleConversationModelEvent(events.back());
+                events.pop_back();
+            }
+        }
+        iter++;
+    }
 }
 
 bool LLFloaterIMContainer::onConversationModelEvent(const LLSD& event)
 {
-	mConversationEventQueue.push_front(event);
+	LLUUID id = event.get("session_uuid").asUUID();
+	mConversationEventQueue[id].push_front(event);
 	return true;
 }
 
@@ -1822,6 +1859,8 @@ bool LLFloaterIMContainer::removeConversationListItem(const LLUUID& uuid, bool c
 	// Suppress the conversation items and widgets from their respective maps
 	mConversationsItems.erase(uuid);
 	mConversationsWidgets.erase(uuid);
+	// Clear event query (otherwise reopening session in some way can bombard session with stale data)
+	mConversationEventQueue.erase(uuid);
 	
 	// Don't let the focus fall IW, select and refocus on the first conversation in the list
 	if (change_focus)
diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h
index 468b47f1f1b..b4a9d377ab9 100644
--- a/indra/newview/llfloaterimcontainer.h
+++ b/indra/newview/llfloaterimcontainer.h
@@ -229,9 +229,10 @@ class LLFloaterIMContainer
 	conversations_widgets_map mConversationsWidgets;
 	LLConversationViewModel mConversationViewModel;
 	LLFolderView* mConversationsRoot;
-	LLEventStream mConversationsEventStream; 
+	LLEventStream mConversationsEventStream;
 
-	std::deque<LLSD> mConversationEventQueue;
+	typedef std::map<LLUUID, std::deque<LLSD> > conversations_items_deque;
+	conversations_items_deque mConversationEventQueue;
 
 	LLTimer mParticipantRefreshTimer;
 };
diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp
index d604d0a789f..80165f9b9a7 100644
--- a/indra/newview/llfloaterimsessiontab.cpp
+++ b/indra/newview/llfloaterimsessiontab.cpp
@@ -492,7 +492,10 @@ void LLFloaterIMSessionTab::buildConversationViewParticipant()
 	while (current_participant_model != end_participant_model)
 	{
 		LLConversationItem* participant_model = dynamic_cast<LLConversationItem*>(*current_participant_model);
-		addConversationViewParticipant(participant_model);
+        if (participant_model)
+        {
+            addConversationViewParticipant(participant_model);
+        }
 		current_participant_model++;
 	}
 }
-- 
GitLab