From 01317a2faded53c79db7e0814426f1d8b2fd12fc Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Tue, 30 Nov 2021 15:22:26 -0500
Subject: [PATCH] SL-16421: Destroy the "General" ThreadPool as soon as cleanup
 starts.

Introduce LLAppViewer::onCleanup(), a method that accepts a nullary callable
to execute once viewer shutdown begins. Fire the collected callables in
LLAppViewer::cleanup().

In llstartup.cpp, instead of declaring a static unique_ptr and relying on
static object destruction to clean up the "General" ThreadPool, bind the
pointer to the new ThreadPool into an onCleanup() lambda that will delete it
when called. ~ThreadPool() takes care of orderly shutdown.
---
 indra/newview/llappviewer.cpp |  5 +++++
 indra/newview/llappviewer.h   | 14 +++++++++++++-
 indra/newview/llstartup.cpp   | 23 +++++++++++++----------
 3 files changed, 31 insertions(+), 11 deletions(-)

diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 529db397b26..a5d32ba2431 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -1729,6 +1729,11 @@ void LLAppViewer::flushVFSIO()
 
 bool LLAppViewer::cleanup()
 {
+	// Since we don't know what functions are going to be queued by
+	// onCleanup(), we have to assume they might rely on some of the things
+	// we're about to destroy below. Run them first.
+	mOnCleanup();
+
 	LLAtmosphere::cleanupClass();
 
 	//ditch LLVOAvatarSelf instance
diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h
index d23a00be7f5..f456cbbd366 100644
--- a/indra/newview/llappviewer.h
+++ b/indra/newview/llappviewer.h
@@ -49,6 +49,8 @@
 #include "lltimer.h"
 #include "llappcorehttp.h"
 
+#include <boost/signals2.hpp>
+
 class LLCommandLineParser;
 class LLFrameTimer;
 class LLPumpIO;
@@ -189,10 +191,20 @@ class LLAppViewer : public LLApp
 	// On LoginCompleted callback
 	typedef boost::signals2::signal<void (void)> login_completed_signal_t;
 	login_completed_signal_t mOnLoginCompleted;
-	boost::signals2::connection setOnLoginCompletedCallback( const login_completed_signal_t::slot_type& cb ) { return mOnLoginCompleted.connect(cb); } 
+	boost::signals2::connection setOnLoginCompletedCallback( const login_completed_signal_t::slot_type& cb )
+	{
+		return mOnLoginCompleted.connect(cb);
+	}
 
 	void addOnIdleCallback(const boost::function<void()>& cb); // add a callback to fire (once) when idle
 
+	typedef boost::signals2::signal<void()> cleanup_signal_t;
+	cleanup_signal_t mOnCleanup;
+	boost::signals2::connection onCleanup(const cleanup_signal_t::slot_type& cb)
+	{
+		return mOnCleanup.connect(cb);
+	}
+
 	void purgeUserDataOnExit() { mPurgeUserDataOnExit = true; }
 	void purgeCache(); // Clear the local cache. 
 	void purgeCacheImmediate(); //clear local cache immediately.
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index adfa1b0c108..d8e68dbc1ed 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -259,7 +259,6 @@ const F32 STATE_AGENT_WAIT_TIMEOUT = 240; //seconds
 std::unique_ptr<LLEventPump> LLStartUp::sStateWatcher(new LLEventStream("StartupState"));
 std::unique_ptr<LLStartupListener> LLStartUp::sListener(new LLStartupListener());
 std::unique_ptr<LLViewerStats::PhaseMap> LLStartUp::sPhases(new LLViewerStats::PhaseMap);
-std::unique_ptr<LL::ThreadPool> gGeneralThreadPool;
 
 //
 // local function declaration
@@ -1495,15 +1494,19 @@ bool idle_startup()
 		display_startup();
 
 		// start up the ThreadPool we'll use for textures et al.
-		LLSD poolSizes{ gSavedSettings.getLLSD("ThreadPoolSizes") };
-		LLSD sizeSpec{ poolSizes["General"] };
-		LLSD::Integer poolSize{ sizeSpec.isInteger()? sizeSpec.asInteger() : 3 };
-		LL_DEBUGS("ThreadPool") << "Instantiating General pool with "
-								<< poolSize << " threads" << LL_ENDL;
-		// We don't want anyone, especially the main thread, to have to block
-		// due to this ThreadPool being full.
-		gGeneralThreadPool.reset(new LL::ThreadPool("General", poolSize, 1024*1024));
-		gGeneralThreadPool->start();
+		{
+			LLSD poolSizes{ gSavedSettings.getLLSD("ThreadPoolSizes") };
+			LLSD sizeSpec{ poolSizes["General"] };
+			LLSD::Integer poolSize{ sizeSpec.isInteger()? sizeSpec.asInteger() : 3 };
+			LL_DEBUGS("ThreadPool") << "Instantiating General pool with "
+									<< poolSize << " threads" << LL_ENDL;
+			// We don't want anyone, especially the main thread, to have to block
+			// due to this ThreadPool being full.
+			auto pool = new LL::ThreadPool("General", poolSize, 1024*1024);
+			pool->start();
+			// Once we start shutting down, destroy this ThreadPool.
+			LLAppViewer::instance()->onCleanup([pool](){ delete pool; });
+		}
 
 		// Initialize global class data needed for surfaces (i.e. textures)
 		LL_DEBUGS("AppInit") << "Initializing sky..." << LL_ENDL;
-- 
GitLab