diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1334c3fbc4d452f50482d04ad07a116ddc945256..44d26507c1b09d23ef934e0aa746cfdf14b39b8d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,7 +12,7 @@ repos:
       - id: indent-with-spaces
         files: \.(cpp|c|h|inl|py|glsl|cmake)$
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.6.0
+    rev: v5.0.0
     hooks:
       - id: check-xml
       - id: mixed-line-ending
diff --git a/autobuild.xml b/autobuild.xml
index a5e0412de0e4a011a200c544eda0b41d5713cec4..322aa8fcd16370ab43ec3d2852738008d8d66eab 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -9,6 +9,16 @@
     <map>
       <key>SDL2</key>
       <map>
+        <key>copyright</key>
+        <string>Copyright (C) 1997-2022 Sam Lantinga (slouken@libsdl.org)</string>
+        <key>description</key>
+        <string>Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer.</string>
+        <key>license</key>
+        <string>lgpl</string>
+        <key>license_file</key>
+        <string>LICENSES/SDL2.txt</string>
+        <key>name</key>
+        <string>SDL2</string>
         <key>platforms</key>
         <map>
           <key>darwin64</key>
@@ -16,11 +26,11 @@
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>b2981cbc72e966b75ef587fb55c5e8fcf829aaa390fcd3365de209b4366bb056cca04f02eb3179193ca941a0214d6bc9362b9a281179976b8c4cb0053caca23e</string>
+              <string>a6aa829c1e7f193b624f05415588e4d727454c8f</string>
               <key>hash_algorithm</key>
-              <string>blake2b</string>
+              <string>sha1</string>
               <key>url</key>
-              <string>https://github.com/AlchemyViewer/3p-sdl2/releases/download/v2.30.0-958285a/SDL2-2.30.0-darwin64-9459243145.tar.zst</string>
+              <string>https://github.com/secondlife/3p-sdl2/releases/download/v2.30.9-r1/SDL2-2.30.9-r1-darwin64-11943333704.tar.zst</string>
             </map>
             <key>name</key>
             <string>darwin64</string>
@@ -30,11 +40,11 @@
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>fe47b8bff02bba3bff2d07194769bd79d80900509f30fdabbf5f73849a582ced9ccbeaa4a4f0bd724df34b6dc0e354109a8bde81b389a1568c3ce822f36cde48</string>
+              <string>ad1b738a7cef16d5cec63ab99b1c80146626f47b</string>
               <key>hash_algorithm</key>
-              <string>blake2b</string>
+              <string>sha1</string>
               <key>url</key>
-              <string>https://github.com/AlchemyViewer/3p-sdl2/releases/download/v2.30.0-958285a/SDL2-2.30.0-linux64-9459243145.tar.zst</string>
+              <string>https://github.com/secondlife/3p-sdl2/releases/download/v2.30.9-r1/SDL2-2.30.9-r1-linux64-11943333704.tar.zst</string>
             </map>
             <key>name</key>
             <string>linux64</string>
@@ -44,28 +54,18 @@
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>d94e4a57dedf71905365c56f9ecffd12aa94d30c9bd9de70874bfd26d6e9805a988fb537db6678d7f807bd7f21d648faf491c93b0da9e43d6a4c4b71a5cb6694</string>
+              <string>97153f9d302f964142df971b57680724824f458f</string>
               <key>hash_algorithm</key>
-              <string>blake2b</string>
+              <string>sha1</string>
               <key>url</key>
-              <string>https://github.com/AlchemyViewer/3p-sdl2/releases/download/v2.30.0-958285a/SDL2-2.30.0-windows64-9459243145.tar.zst</string>
+              <string>https://github.com/secondlife/3p-sdl2/releases/download/v2.30.9-r1/SDL2-2.30.9-r1-windows64-11943333704.tar.zst</string>
             </map>
             <key>name</key>
             <string>windows64</string>
           </map>
         </map>
-        <key>license</key>
-        <string>lgpl</string>
-        <key>license_file</key>
-        <string>LICENSES/SDL2.txt</string>
-        <key>copyright</key>
-        <string>Copyright (C) 1997-2024 Sam Lantinga (slouken@libsdl.org)</string>
         <key>version</key>
-        <string>2.30.0</string>
-        <key>name</key>
-        <string>SDL2</string>
-        <key>description</key>
-        <string>Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer.</string>
+        <string>2.30.9-r1</string>
       </map>
       <key>apr_suite</key>
       <map>
@@ -1970,11 +1970,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>0f97bd114ccbcb67f15caa16c837e7b294d720c05d6a2cd99c939f5618b8a65798772c0a157c2367b1f6b50c1b2f7bb209f03ddddd9ca2c2dc16eb66634e875c</string>
+              <string>a93b63fc4e5aa3e226b819fb02ce9a05b00dfea1</string>
               <key>hash_algorithm</key>
-              <string>blake2b</string>
+              <string>sha1</string>
               <key>url</key>
-              <string>https://github.com/AlchemyViewer/3p-open-libndofdev/releases/download/v0.14.0-a377d1a/open_libndofdev-0.14.0-linux64-9459328666.tar.zst</string>
+              <string>https://github.com/secondlife/3p-open-libndofdev/releases/download/v1.14-r5/open_libndofdev-0.14.11968684513-linux64-11968684513.tar.zst</string>
             </map>
             <key>name</key>
             <string>linux64</string>
@@ -1987,7 +1987,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
         <key>copyright</key>
         <string>Copyright (c) 2008, Jan Ciger (jan.ciger (at) gmail.com)</string>
         <key>version</key>
-        <string>0.14.0</string>
+        <string>0.14.11968684513</string>
         <key>name</key>
         <string>open-libndofdev</string>
         <key>description</key>
@@ -2002,11 +2002,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>1b876fc7046470ddfda2fb74ef7d653643a40c4668f8ca276f56535afa1d93fafb05cae77ccadbffebf7ee1ce3dab90c4b7d4f4a56b8461688b61f19550751ac</string>
+              <string>9250e12d4d88f0a437e49be2b32d107f6b4270d0</string>
               <key>hash_algorithm</key>
-              <string>blake2b</string>
+              <string>sha1</string>
               <key>url</key>
-              <string>https://github.com/AlchemyViewer/3p-openal-soft/releases/download/v1.23.1-13e162a/openal-1.23.1-darwin64-9458147120.tar.zst</string>
+              <string>https://github.com/secondlife/3p-openal-soft/releases/download/v1.24.0-r1/openal-1.24.0-r1-darwin64-11968917548.tar.zst</string>
             </map>
             <key>name</key>
             <string>darwin64</string>
@@ -2016,11 +2016,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>2552fc42e102e38d506569aa22b88a06712c976b93b8cfb2644c4decbf11bc463d239061d0a1bc5e76337f9dec9f5efe00bbaf9102ea926949b930cb2a92b6ce</string>
+              <string>1e50db24586fba434a2be62f94fdc687569379ca</string>
               <key>hash_algorithm</key>
-              <string>blake2b</string>
+              <string>sha1</string>
               <key>url</key>
-              <string>https://github.com/AlchemyViewer/3p-openal-soft/releases/download/v1.23.1-13e162a/openal-1.23.1-linux64-9458147120.tar.zst</string>
+              <string>https://github.com/secondlife/3p-openal-soft/releases/download/v1.24.0-r1/openal-1.24.0-r1-linux64-11968917548.tar.zst</string>
             </map>
             <key>name</key>
             <string>linux64</string>
@@ -2030,11 +2030,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>c2f9c3551b1691d0b8cf49560ef10fd48e1fb515c2d9a0f777ffb71709bb9efcf7b12a625391bbc3fc369888061c8a0f989ec58a3887b658e6c1353053aa6ea7</string>
+              <string>2ec7b2b1f4c37e5bd1b473e4a26935ec374ee208</string>
               <key>hash_algorithm</key>
-              <string>blake2b</string>
+              <string>sha1</string>
               <key>url</key>
-              <string>https://github.com/AlchemyViewer/3p-openal-soft/releases/download/v1.23.1-13e162a/openal-1.23.1-windows64-9458147120.tar.zst</string>
+              <string>https://github.com/secondlife/3p-openal-soft/releases/download/v1.24.0-r1/openal-1.24.0-r1-windows64-11968917548.tar.zst</string>
             </map>
             <key>name</key>
             <string>windows64</string>
@@ -2047,7 +2047,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
         <key>copyright</key>
         <string>Copyright (C) 1999-2007 by authors.</string>
         <key>version</key>
-        <string>1.23.1</string>
+        <string>1.24.0-r1</string>
         <key>name</key>
         <string>openal</string>
         <key>description</key>
@@ -2676,11 +2676,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>3570b6442d472cd97bad8622c2ec2571d72218a0</string>
+              <string>6314fdcee81a3538a7d960178ade66301c2fa002</string>
               <key>hash_algorithm</key>
               <string>sha1</string>
               <key>url</key>
-              <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.72-test/webrtc-m114.5735.08.72-test.10444682919-darwin64-10444682919.tar.zst</string>
+              <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.73-alpha/webrtc-m114.5735.08.73-alpha.11958809572-darwin64-11958809572.tar.zst</string>
             </map>
             <key>name</key>
             <string>darwin64</string>
@@ -2690,11 +2690,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>eadf6aa99313940ded11801d42c11375669f1628</string>
+              <string>95d7730a3d6955697e043f3fdf20ebdcc0c71fc0</string>
               <key>hash_algorithm</key>
               <string>sha1</string>
               <key>url</key>
-              <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.72-test/webrtc-m114.5735.08.72-test.10444682919-linux64-10444682919.tar.zst</string>
+              <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.73-alpha/webrtc-m114.5735.08.73-alpha.11958809572-linux64-11958809572.tar.zst</string>
             </map>
             <key>name</key>
             <string>linux64</string>
@@ -2704,11 +2704,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>0081fd35290adbc8e66dd366535fb6cd8a966f1e</string>
+              <string>c7b329d6409576af6eb5b80655b007f52639c43b</string>
               <key>hash_algorithm</key>
               <string>sha1</string>
               <key>url</key>
-              <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.72-test/webrtc-m114.5735.08.72-test.10444682919-windows64-10444682919.tar.zst</string>
+              <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.73-alpha/webrtc-m114.5735.08.73-alpha.11958809572-windows64-11958809572.tar.zst</string>
             </map>
             <key>name</key>
             <string>windows64</string>
@@ -2721,7 +2721,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
         <key>copyright</key>
         <string>Copyright (c) 2011, The WebRTC project authors. All rights reserved.</string>
         <key>version</key>
-        <string>m114.5735.08.72-test.10444682919</string>
+        <string>m114.5735.08.73-alpha.11958809572</string>
         <key>name</key>
         <string>webrtc</string>
         <key>vcs_branch</key>
diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp
index f53c74c4a3fe4f22d6171bbebaeddea63a0c393f..22fc774479594b15d0442edb82d2e9188197fbec 100644
--- a/indra/llcommon/llapp.cpp
+++ b/indra/llcommon/llapp.cpp
@@ -678,6 +678,10 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *)
     // We do the somewhat sketchy operation of blocking in here until the error handler
     // has gracefully stopped the app.
 
+    // FIXME(brad) - we are using this handler for asynchronous signals as well, so sLogInSignal is currently
+    // disabled for safety.  we need to find a way to selectively reenable it when it is safe.
+    // see issue secondlife/viewer#2566
+
     if (LLApp::sLogInSignal)
     {
         LL_INFOS() << "Signal handler - Got signal " << signum << " - " << apr_signal_description_get(signum) << LL_ENDL;
@@ -687,9 +691,10 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *)
     switch (signum)
     {
     case SIGCHLD:
+    case SIGHUP:
         if (LLApp::sLogInSignal)
         {
-            LL_INFOS() << "Signal handler - Got SIGCHLD from " << info->si_pid << LL_ENDL;
+            LL_INFOS() << "Signal handler - Got SIGCHLD or SIGHUP from " << info->si_pid << LL_ENDL;
         }
 
         return;
@@ -704,11 +709,10 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *)
         raise(signum);
         return;
     case SIGINT:
-    case SIGHUP:
     case SIGTERM:
         if (LLApp::sLogInSignal)
         {
-            LL_WARNS() << "Signal handler - Got SIGINT, HUP, or TERM, exiting gracefully" << LL_ENDL;
+            LL_WARNS() << "Signal handler - Got SIGINT, or TERM, exiting gracefully" << LL_ENDL;
         }
         // Graceful exit
         // Just set our state to quitting, not error
@@ -755,6 +759,7 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *)
             {
                 LL_WARNS() << "Signal handler - Handling fatal signal!" << LL_ENDL;
             }
+
             if (LLApp::isError())
             {
                 // Received second fatal signal while handling first, just die right now
@@ -792,11 +797,11 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *)
             clear_signals();
             raise(signum);
             return;
-        } else {
-            if (LLApp::sLogInSignal)
-            {
-                LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL;
-            }
+        }
+
+        if (LLApp::sLogInSignal)
+        {
+            LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL;
         }
     }
 }
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index 7636884f679708c39765087efa8915753e4b152f..f406d80851bb99106b4a794df727c6e2202b2302 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -61,6 +61,23 @@
 #include <excpt.h>
 #endif
 
+// static
+bool LLCoros::on_main_coro()
+{
+    if (!LLCoros::instanceExists() || LLCoros::getName().empty())
+    {
+        return true;
+    }
+
+    return false;
+}
+
+// static
+bool LLCoros::on_main_thread_main_coro()
+{
+    return on_main_coro() && on_main_thread();
+}
+
 // static
 LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
 {
@@ -362,7 +379,8 @@ void LLCoros::toplevel(std::string name, callable_t callable)
     {
         // Stash any OTHER kind of uncaught exception in the rethrow() queue
         // to be rethrown by the main fiber.
-        LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name));
+        LL_WARNS("LLCoros") << "Capturing uncaught exception in coroutine "
+                            << name << LL_ENDL;
         LLCoros::instance().saveException(name, std::current_exception());
     }
 }
diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h
index fdd00cf62c729ea41ef02c92100b0930c15af2df..f32e90e0f87d7b553cdd5a7f3478a7992900534a 100644
--- a/indra/llcommon/llcoros.h
+++ b/indra/llcommon/llcoros.h
@@ -31,8 +31,9 @@
 
 #include "llexception.h"
 #include <boost/fiber/fss.hpp>
-#include <boost/fiber/future/promise.hpp>
 #include <boost/fiber/future/future.hpp>
+#include <boost/fiber/future/promise.hpp>
+#include <boost/fiber/recursive_mutex.hpp>
 #include "mutex.h"
 #include "llsingleton.h"
 #include "llinstancetracker.h"
@@ -94,6 +95,16 @@ class LL_COMMON_API LLCoros final : public LLSingleton<LLCoros>
 
     void cleanupSingleton() override;
 public:
+    // For debugging, return true if on the main coroutine for the current thread
+    // Code that should not be executed from a coroutine should be protected by
+    // llassert(LLCoros::on_main_coro())
+    static bool on_main_coro();
+
+    // For debugging, return true if on the main thread and not in a coroutine
+    // Non-thread-safe code in the main loop should be protected by
+    // llassert(LLCoros::on_main_thread_main_coro())
+    static bool on_main_thread_main_coro();
+
     /// The viewer's use of the term "coroutine" became deeply embedded before
     /// the industry term "fiber" emerged to distinguish userland threads from
     /// simpler, more transient kinds of coroutines. Semantically they've
@@ -297,6 +308,12 @@ class LL_COMMON_API LLCoros final : public LLSingleton<LLCoros>
 
     // use mutex, lock, condition_variable suitable for coroutines
     using Mutex = boost::fibers::mutex;
+    using RMutex = boost::fibers::recursive_mutex;
+    // With C++17, LockType is deprecated: at this point we can directly
+    // declare 'std::unique_lock lk(some_mutex)' without explicitly stating
+    // the mutex type. Sadly, making LockType an alias template for
+    // std::unique_lock doesn't work the same way: Class Template Argument
+    // Deduction only works for class templates, not alias templates.
     using LockType = std::unique_lock<Mutex>;
     using cv_status = boost::fibers::cv_status;
     using ConditionVariable = boost::fibers::condition_variable;
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index ac32c67a74ccb58cc1b5168a6c78058c64c5b025..3c68ca12ef7d28f0b554e6a887d3eaabf48a1c1c 100644
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -526,7 +526,7 @@ namespace
         LLError::TimeFunction               mTimeFunction;
 
         Recorders                           mRecorders;
-        LLMutex                             mRecorderMutex;
+        LLCoros::RMutex                     mRecorderMutex;
 
         int                                 mShouldLogCallCounter;
 
@@ -549,7 +549,6 @@ namespace
         mCrashFunction(NULL),
         mTimeFunction(NULL),
         mRecorders(),
-        mRecorderMutex(),
         mShouldLogCallCounter(0)
     {
     }
@@ -706,7 +705,7 @@ namespace LLError
 
     CallSite::~CallSite()
     {
-        delete[] mTags;
+        delete []mTags;
     }
 
     void CallSite::invalidate()
@@ -720,7 +719,7 @@ namespace
     bool shouldLogToStderr()
     {
 #if LL_DARWIN
-        // On Mac OS X, stderr from apps launched from the Finder goes to the
+        // On macOS, stderr from apps launched from the Finder goes to the
         // console log.  It's generally considered bad form to spam too much
         // there. That scenario can be detected by noticing that stderr is a
         // character device (S_IFCHR).
@@ -1059,7 +1058,7 @@ namespace LLError
             return;
         }
         SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
-        LLMutexLock lock(&s->mRecorderMutex);
+        std::unique_lock lock(s->mRecorderMutex);
         s->mRecorders.push_back(std::move(recorder));
     }
 
@@ -1070,7 +1069,7 @@ namespace LLError
             return;
         }
         SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
-        LLMutexLock lock(&s->mRecorderMutex);
+        std::unique_lock lock(s->mRecorderMutex);
         s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder),
                             s->mRecorders.end());
     }
@@ -1119,7 +1118,7 @@ namespace LLError
     std::shared_ptr<RECORDER> findRecorder()
     {
         SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
-        LLMutexLock lock(&s->mRecorderMutex);
+        std::unique_lock lock(s->mRecorderMutex);
         return findRecorderPos<RECORDER>(s).first;
     }
 
@@ -1130,7 +1129,7 @@ namespace LLError
     bool removeRecorder()
     {
         SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
-        LLMutexLock lock(&s->mRecorderMutex);
+        std::unique_lock lock(s->mRecorderMutex);
         auto found = findRecorderPos<RECORDER>(s);
         if (found.first)
         {
@@ -1236,7 +1235,7 @@ namespace
 
         std::string escaped_message;
 
-        LLMutexLock lock(&s->mRecorderMutex);
+        std::unique_lock lock(s->mRecorderMutex);
         for (LLError::RecorderPtr& r : s->mRecorders)
         {
             if (!r->enabled())
@@ -1505,6 +1504,7 @@ namespace LLError
         static std::string newview_prefix = "newview/../";
         f = removePrefix(f, newview_prefix);
 #endif
+
         return f;
     }
 
diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp
index 16ab2d5a453f419554cf844a4c2f6c41a87e49cb..542a3c26e34daba87f31897fbb5fb86ebe156494 100644
--- a/indra/llcommon/llsdjson.cpp
+++ b/indra/llcommon/llsdjson.cpp
@@ -26,11 +26,12 @@
 
 // Must turn on conditional declarations in header file so definitions end up
 // with proper linkage.
-
+#define LLSD_DEBUG_INFO
 #include "linden_common.h"
 
 #include "llsdjson.h"
 
+#include "llsdutil.h"
 #include "llerror.h"
 
 #include <boost/json/src.hpp>
@@ -59,12 +60,21 @@ LLSD LlsdFromJson(const boost::json::value& val)
         result = LLSD(val.as_bool());
         break;
     case boost::json::kind::array:
+    {
         result = LLSD::emptyArray();
-        for (const auto &element : val.as_array())
+        const boost::json::array& array = val.as_array();
+        size_t size = array.size();
+        // allocate elements 0 .. (size() - 1) to avoid incremental allocation
+        if (! array.empty())
         {
-            result.append(LlsdFromJson(element));
+            result[size - 1] = LLSD();
+        }
+        for (size_t i = 0; i < size; i++)
+        {
+            result[i] = (LlsdFromJson(array[i]));
         }
         break;
+    }
     case boost::json::kind::object:
         result = LLSD::emptyMap();
         for (const auto& element : val.as_object())
@@ -104,7 +114,8 @@ boost::json::value LlsdToJson(const LLSD &val)
     case LLSD::TypeMap:
     {
         boost::json::object& obj = result.emplace_object();
-        for (const auto& llsd_dat : val.asMap())
+        obj.reserve(val.size());
+        for (const auto& llsd_dat : llsd::inMap(val))
         {
             obj[llsd_dat.first] = LlsdToJson(llsd_dat.second);
         }
@@ -113,7 +124,8 @@ boost::json::value LlsdToJson(const LLSD &val)
     case LLSD::TypeArray:
     {
         boost::json::array& json_array = result.emplace_array();
-        for (const auto& llsd_dat : val.asArray())
+        json_array.reserve(val.size());
+        for (const auto& llsd_dat : llsd::inArray(val))
         {
             json_array.push_back(LlsdToJson(llsd_dat));
         }
@@ -121,7 +133,8 @@ boost::json::value LlsdToJson(const LLSD &val)
     }
     case LLSD::TypeBinary:
     default:
-        LL_ERRS("LlsdToJson") << "Unsupported conversion to JSON from LLSD type (" << val.type() << ")." << LL_ENDL;
+        LL_ERRS("LlsdToJson") << "Unsupported conversion to JSON from LLSD type ("
+                              << val.type() << ")." << LL_ENDL;
         break;
     }
 
diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp
index 302bbe6f8d26a1962f003204bb88062df6fde683..451e60c0832eaa0f4f8f8bcb6e268cd59d441110 100644
--- a/indra/llcommon/threadpool.cpp
+++ b/indra/llcommon/threadpool.cpp
@@ -110,6 +110,10 @@ void LL::ThreadPoolBase::start()
 LL::ThreadPoolBase::~ThreadPoolBase()
 {
     close();
+    if (!LLEventPumps::wasDeleted())
+    {
+        LLEventPumps::instance().obtain("LLApp").stopListening(mName);
+    }
 }
 
 void LL::ThreadPoolBase::close()
diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp
index 18f796b3816122394a2b994fc17d8805c477c94e..a6a30cebb9658f05c0522c2b8502bde2571308e8 100644
--- a/indra/llrender/llshadermgr.cpp
+++ b/indra/llrender/llshadermgr.cpp
@@ -66,7 +66,7 @@ BOOL LLShaderMgr::attachShaderFeatures(LLGLSLShader * shader)
     //////////////////////////////////////
 
     // NOTE order of shader object attaching is VERY IMPORTANT!!!
-    if (features->calculatesAtmospherics)
+    if (features->calculatesAtmospherics || features->hasGamma || features->isDeferred)
     {
         if (!shader->attachVertexObject("windlight/atmosphericsVarsV.glsl"))
         {
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 4e85ceaf1697a3bb72133d2275499bafd08f9f68..b2fd864629b2ff525b007f91969bf9f88856953a 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -5147,10 +5147,7 @@ void LLAppViewer::sendLogoutRequest()
         gLogoutMaxTime = LOGOUT_REQUEST_TIME;
         mLogoutRequestSent = TRUE;
 
-        if(LLVoiceClient::instanceExists())
-        {
-            LLVoiceClient::getInstance()->setVoiceEnabled(false);
-        }
+        LLVoiceClient::setVoiceEnabled(false);
     }
 }
 
diff --git a/indra/newview/llavatarpropertiesprocessor.cpp b/indra/newview/llavatarpropertiesprocessor.cpp
index 715649e8443917fe7e9924aaee3b4a15267d7548..c33e70bd579082d9bae38e0c1072764b6cd5edc2 100644
--- a/indra/newview/llavatarpropertiesprocessor.cpp
+++ b/indra/newview/llavatarpropertiesprocessor.cpp
@@ -31,6 +31,7 @@
 // Viewer includes
 #include "llagent.h"
 #include "llagentpicksinfo.h"
+#include "llappviewer.h"
 #include "lldateutil.h"
 #include "llviewergenericmessage.h"
 #include "llstartup.h"
@@ -41,6 +42,7 @@
 #include "lltrans.h"
 #include "llui.h"               // LLUI::getLanguage()
 #include "message.h"
+#include "llappviewer.h"
 
 LLAvatarPropertiesProcessor::LLAvatarPropertiesProcessor()
 {
@@ -367,7 +369,11 @@ void LLAvatarPropertiesProcessor::requestAvatarPropertiesCoro(std::string cap_ur
         avatar_data.picks_list.emplace_back(pick_data["id"].asUUID(), pick_data["name"].asString());
     }
 
-    inst.notifyObservers(avatar_id, &avatar_data, type);
+    LLAppViewer::instance()->postToMainCoro([=]()
+        {
+            LLAvatarData av_data = avatar_data;
+            instance().notifyObservers(avatar_id, &av_data, type);
+        });
 }
 
 void LLAvatarPropertiesProcessor::processAvatarLegacyPropertiesReply(LLMessageSystem* msg, void**)
diff --git a/indra/newview/llavatarrenderinfoaccountant.cpp b/indra/newview/llavatarrenderinfoaccountant.cpp
index a68a28738d9089a7828f292a326e7e3ca4f30fd1..3e2a2fa6f1cb58e4473f177216efd06dd245bcbe 100644
--- a/indra/newview/llavatarrenderinfoaccountant.cpp
+++ b/indra/newview/llavatarrenderinfoaccountant.cpp
@@ -231,13 +231,11 @@ void LLAvatarRenderInfoAccountant::avatarRenderInfoReportCoro(std::string url, U
     {
         LLVOAvatar* avatar = static_cast<LLVOAvatar*>(*iter);
         if (avatar &&
-            avatar->getRezzedStatus() >= 2 &&                   // Mostly rezzed (maybe without baked textures downloaded)
             !avatar->isDead() &&                                // Not dead yet
             !avatar->isControlAvatar() &&                       // Not part of an animated object
+            avatar->getRezzedStatus() >= 2 &&                   // Mostly rezzed (maybe without baked textures downloaded)
             avatar->getObjectHost() == regionp->getHost())      // Ensure it's on the same region
         {
-            avatar->calculateUpdateRenderComplexity();          // Make sure the numbers are up-to-date
-
             LLSD info = LLSD::emptyMap();
             U32 avatar_complexity = avatar->getVisualComplexity();
             if (avatar_complexity > 0)
@@ -245,11 +243,11 @@ void LLAvatarRenderInfoAccountant::avatarRenderInfoReportCoro(std::string url, U
                 // the weight/complexity is unsigned, but LLSD only stores signed integers,
                 // so if it's over that (which would be ridiculously high), just store the maximum signed int value
                 info[KEY_WEIGHT] = (S32)(avatar_complexity < S32_MAX ? avatar_complexity : S32_MAX);
-                info[KEY_TOO_COMPLEX]  = LLSD::Boolean(avatar->isTooComplex());
+                info[KEY_TOO_COMPLEX] = LLSD::Boolean(avatar->isTooComplex());
                 agents[avatar->getID().asString()] = info;
 
                 LL_DEBUGS("AvatarRenderInfo") << "Sending avatar render info for " << avatar->getID()
-                                              << ": " << info << LL_ENDL;
+                    << ": " << info << LL_ENDL;
                 num_avs++;
             }
         }
diff --git a/indra/newview/llconversationlog.cpp b/indra/newview/llconversationlog.cpp
index f8f3425a4a5823b19062a3c790363ff4c8206b0f..35c207133a54edd33eaf4022be9de45a857d0260 100644
--- a/indra/newview/llconversationlog.cpp
+++ b/indra/newview/llconversationlog.cpp
@@ -112,12 +112,17 @@ void LLConversation::onIMFloaterShown(const LLUUID& session_id)
 // static
 const std::string LLConversation::createTimestamp(const U64Seconds& utc_time)
 {
-    static const std::string time_fmt_str = fmt::format(FMT_STRING("[{:s}]/[{:s}]/[{:s}] [{:s}]:[{:s}]"), LLTrans::getString("TimeMonth"), LLTrans::getString("TimeDay"), LLTrans::getString("TimeYear"), LLTrans::getString("TimeHour"), LLTrans::getString("TimeMin"));
-
-    std::string timeStr = time_fmt_str;
+    std::string timeStr;
     LLSD substitution;
     substitution["datetime"] = (S32)utc_time.value();
 
+    timeStr = "["+LLTrans::getString ("TimeMonth")+"]/["
+                 +LLTrans::getString ("TimeDay")+"]/["
+                 +LLTrans::getString ("TimeYear")+"] ["
+                 +LLTrans::getString ("TimeHour")+"]:["
+                 +LLTrans::getString ("TimeMin")+"]";
+
+
     LLStringUtil::format (timeStr, substitution);
     return timeStr;
 }
@@ -183,6 +188,22 @@ LLConversationLog::LLConversationLog() :
 {
 }
 
+LLConversationLog::~LLConversationLog()
+{
+    if (mLoggingEnabled)
+    {
+        if (LLIMMgr::instanceExists())
+        {
+            LLIMMgr::instance().removeSessionObserver(this);
+        }
+        LLAvatarTracker::instance().removeObserver(mFriendObserver);
+    }
+    if (mAvatarNameCacheConnection.connected())
+    {
+        mAvatarNameCacheConnection.disconnect();
+    }
+}
+
 void LLConversationLog::enableLogging(S32 log_mode)
 {
     mLoggingEnabled = log_mode > 0;
diff --git a/indra/newview/llconversationlog.h b/indra/newview/llconversationlog.h
index 598e98ecbd854741b5a4dde4dc6628186ec20ab4..befb03460ade1f3a4155b839a1394886ca9ba856 100644
--- a/indra/newview/llconversationlog.h
+++ b/indra/newview/llconversationlog.h
@@ -126,10 +126,10 @@ class LLConversationLog final : public LLSingleton<LLConversationLog>, LLIMSessi
 
     // LLIMSessionObserver triggers
     virtual void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, BOOL has_offline_msg) override;
-    virtual void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id)  override {}; // Stub
-    virtual void sessionRemoved(const LLUUID& session_id) override {}                                           // Stub
-    virtual void sessionVoiceOrIMStarted(const LLUUID& session_id) override {};                             // Stub
-    virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) override {};  // Stub
+    virtual void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) override {}; // Stub
+    virtual void sessionRemoved(const LLUUID& session_id) override{}                                            // Stub
+    virtual void sessionVoiceOrIMStarted(const LLUUID& session_id) override{};                              // Stub
+    virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) override{};   // Stub
 
     void notifyObservers();
 
@@ -166,13 +166,7 @@ class LLConversationLog final : public LLSingleton<LLConversationLog>, LLIMSessi
 
 private:
 
-    virtual ~LLConversationLog()
-    {
-        if (mAvatarNameCacheConnection.connected())
-        {
-            mAvatarNameCacheConnection.disconnect();
-        }
-    }
+    virtual ~LLConversationLog();
 
     void enableLogging(S32 log_mode);
 
diff --git a/indra/newview/llenvironment.cpp b/indra/newview/llenvironment.cpp
index 46e7949e5624effb68a52ecbb4bb83fb0c1216af..d9d50a21902c9a4cd2029b48a38e366d2beed24b 100644
--- a/indra/newview/llenvironment.cpp
+++ b/indra/newview/llenvironment.cpp
@@ -1971,7 +1971,10 @@ void LLEnvironment::requestParcel(S32 parcel_id, environment_apply_fn cb)
             LLEnvironmentRequest::initiate(cb);
         }
         else if (cb)
+        {
             cb(parcel_id, EnvironmentInfo::ptr_t());
+        }
+
         return;
     }
 
@@ -1981,16 +1984,14 @@ void LLEnvironment::requestParcel(S32 parcel_id, environment_apply_fn cb)
         cb = [this, transition](S32 pid, EnvironmentInfo::ptr_t envinfo) { recordEnvironment(pid, envinfo, transition); };
     }
 
-    std::string coroname =
-        LLCoros::instance().launch("LLEnvironment::coroRequestEnvironment",
+    LLCoros::instance().launch("LLEnvironment::coroRequestEnvironment",
         [this, parcel_id, cb]() { coroRequestEnvironment(parcel_id, cb); });
 }
 
 void LLEnvironment::updateParcel(S32 parcel_id, const LLUUID &asset_id, std::string display_name, S32 track_num, S32 day_length, S32 day_offset, U32 flags, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb)
 {
     UpdateInfo::ptr_t updates(std::make_shared<UpdateInfo>(asset_id, display_name, day_length, day_offset, altitudes, flags));
-    std::string coroname =
-        LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment",
+    LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment",
         [this, parcel_id, track_num, updates, cb]() { coroUpdateEnvironment(parcel_id, track_num, updates, cb); });
 }
 
@@ -2040,8 +2041,7 @@ void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsDay::ptr_t &pday
 {
     UpdateInfo::ptr_t updates(std::make_shared<UpdateInfo>(pday, day_length, day_offset, altitudes));
 
-    std::string coroname =
-        LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment",
+    LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment",
         [this, parcel_id, track_num, updates, cb]() { coroUpdateEnvironment(parcel_id, track_num, updates, cb); });
 }
 
@@ -2050,12 +2050,9 @@ void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsDay::ptr_t &pday
     updateParcel(parcel_id, pday, NO_TRACK, day_length, day_offset, altitudes, cb);
 }
 
-
-
 void LLEnvironment::resetParcel(S32 parcel_id, environment_apply_fn cb)
 {
-    std::string coroname =
-        LLCoros::instance().launch("LLEnvironment::coroResetEnvironment",
+    LLCoros::instance().launch("LLEnvironment::coroResetEnvironment",
         [this, parcel_id, cb]() { coroResetEnvironment(parcel_id, NO_TRACK, cb); });
 }
 
@@ -2074,8 +2071,10 @@ void LLEnvironment::coroRequestEnvironment(S32 parcel_id, LLEnvironment::environ
 
     if (parcel_id != INVALID_PARCEL_ID)
     {
-        url.append("?parcelid=");
-        url.append(fmt::to_string(parcel_id));
+        std::stringstream query;
+
+        query << "?parcelid=" << parcel_id;
+        url += query.str();
     }
 
     LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
@@ -2096,11 +2095,13 @@ void LLEnvironment::coroRequestEnvironment(S32 parcel_id, LLEnvironment::environ
         LLSD environment = result[KEY_ENVIRONMENT];
         if (environment.isDefined() && apply)
         {
-            EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment);
-            apply(parcel_id, envinfo);
+            LLAppViewer::instance()->postToMainCoro([=]()
+                {
+                    EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment);
+                    apply(parcel_id, envinfo);
+                });
         }
     }
-
 }
 
 void LLEnvironment::coroUpdateEnvironment(S32 parcel_id, S32 track_no, UpdateInfo::ptr_t updates, environment_apply_fn apply)
@@ -2120,9 +2121,14 @@ void LLEnvironment::coroUpdateEnvironment(S32 parcel_id, S32 track_no, UpdateInf
     if (track_no == NO_TRACK)
     {   // day length and offset are only applicable if we are addressing the entire day cycle.
         if (updates->mDayLength > 0)
+        {
             body[KEY_ENVIRONMENT][KEY_DAYLENGTH] = updates->mDayLength;
+        }
+
         if (updates->mDayOffset > 0)
+        {
             body[KEY_ENVIRONMENT][KEY_DAYOFFSET] = updates->mDayOffset;
+        }
 
         if ((parcel_id == INVALID_PARCEL_ID) && (updates->mAltitudes.size() == 3))
         {   // only test for altitude changes if we are changing the region.
@@ -2135,12 +2141,16 @@ void LLEnvironment::coroUpdateEnvironment(S32 parcel_id, S32 track_no, UpdateInf
     }
 
     if (updates->mDayp)
+    {
         body[KEY_ENVIRONMENT][KEY_DAYCYCLE] = updates->mDayp->getSettings();
+    }
     else if (!updates->mSettingsAsset.isNull())
     {
         body[KEY_ENVIRONMENT][KEY_DAYASSET] = updates->mSettingsAsset;
         if (!updates->mDayName.empty())
+        {
             body[KEY_ENVIRONMENT][KEY_DAYNAME] = updates->mDayName;
+        }
     }
 
     body[KEY_ENVIRONMENT][KEY_FLAGS] = LLSD::Integer(updates->mFlags);
@@ -2158,22 +2168,27 @@ void LLEnvironment::coroUpdateEnvironment(S32 parcel_id, S32 track_no, UpdateInf
             if (track_no != NO_TRACK)
                 query << "&";
         }
+
         if (track_no != NO_TRACK)
         {
             query << "trackno=" << track_no;
         }
+
         url += query.str();
     }
 
     LLSD result = httpAdapter->putAndSuspend(httpRequest, url, body);
     // results that come back may contain the new settings
 
+    if (LLApp::isExiting())
+        return;
+
     LLSD notify;
 
     LLSD httpResults = result["http_result"];
     LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
 
-    if ((!status) || !result["success"].asBoolean())
+    if (!status || !result["success"].asBoolean())
     {
         LL_WARNS("ENVIRONMENT") << "Couldn't update Windlight settings for " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL;
 
@@ -2188,10 +2203,6 @@ void LLEnvironment::coroUpdateEnvironment(S32 parcel_id, S32 track_no, UpdateInf
             notify["FAIL_REASON"] = reason;
         }
     }
-    else if (LLApp::isExiting())
-    {
-        return;
-    }
     else
     {
         LLSD environment = result[KEY_ENVIRONMENT];
@@ -2242,34 +2253,26 @@ void LLEnvironment::coroResetEnvironment(S32 parcel_id, S32 track_no, environmen
     LLSD result = httpAdapter->deleteAndSuspend(httpRequest, url);
     // results that come back may contain the new settings
 
+    if (LLApp::isExiting())
+        return;
+
     LLSD notify;
 
     LLSD httpResults = result["http_result"];
     LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
 
-    if ((!status) || !result["success"].asBoolean())
+    if (!status || !result["success"].asBoolean())
     {
         LL_WARNS("ENVIRONMENT") << "Couldn't reset Windlight settings in " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL;
 
         notify = LLSD::emptyMap();
         std::string reason = result["message"].asString();
-        if (reason.empty())
-        {
-            notify["FAIL_REASON"] = status.toString();
-        }
-        else
-        {
-            notify["FAIL_REASON"] = reason;
-        }
+        notify["FAIL_REASON"] = reason.empty() ? status.toString() : reason;
     }
-    else if (LLApp::isExiting())
+    else if (apply)
     {
-        return;
-    }
-    else
-    {
-       LLSD environment = result[KEY_ENVIRONMENT];
-        if (environment.isDefined() && apply)
+        LLSD environment = result[KEY_ENVIRONMENT];
+        if (environment.isDefined())
         {
             EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment);
             apply(parcel_id, envinfo);
@@ -2281,7 +2284,6 @@ void LLEnvironment::coroResetEnvironment(S32 parcel_id, S32 track_no, environmen
         LLNotificationsUtil::add("WLRegionApplyFail", notify);
         //LLEnvManagerNew::instance().onRegionSettingsApplyResponse(false);
     }
-
 }
 
 
@@ -2407,7 +2409,6 @@ LLEnvironment::EnvironmentInfo::ptr_t LLEnvironment::EnvironmentInfo::extractLeg
     pinfo->mAltitudes[2] = 10002;
     pinfo->mAltitudes[3] = 10003;
 
-
     return pinfo;
 }
 
diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp
index 3e547e6a6227f8988e26d8544aaa3314cca188f3..ab1fc3a645ec76e813d2baddb003dfaeb19188ac 100644
--- a/indra/newview/llfloaterimsession.cpp
+++ b/indra/newview/llfloaterimsession.cpp
@@ -790,6 +790,7 @@ void LLFloaterIMSession::onClose(bool app_quitting)
     // Last change:
     // EXT-3516 X Button should end IM session, _ button should hide
     gIMMgr->leaveSession(mSessionID);
+    mSession = nullptr; // leaveSession should have deleted it.
     // *TODO: Study why we need to restore the floater before we close it.
     // Might be because we want to save some state data in some clean open state.
     LLFloaterIMSessionTab::restoreFloater();
diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp
index aec6310a42711f116ec197bcc33938d2c12d9354..bc4cf60600e049b7e18bd008f2ce3196bd1070b4 100644
--- a/indra/newview/llfloaterimsessiontab.cpp
+++ b/indra/newview/llfloaterimsessiontab.cpp
@@ -80,6 +80,7 @@ LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id)
 {
     setAutoFocus(FALSE);
     mSession = LLIMModel::getInstance()->findIMSession(mSessionID);
+    LLIMMgr::instance().addSessionObserver(this);
 
     mCommitCallbackRegistrar.add("IMSession.Menu.Action",
             boost::bind(&LLFloaterIMSessionTab::onIMSessionMenuItemClicked,  this, _2));
@@ -102,6 +103,7 @@ LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id)
 LLFloaterIMSessionTab::~LLFloaterIMSessionTab()
 {
     delete mRefreshTimer;
+    LLIMMgr::instance().removeSessionObserver(this);
 
     LLFloaterIMContainer* im_container = LLFloaterIMContainer::findInstance();
     if (im_container)
@@ -441,7 +443,10 @@ void LLFloaterIMSessionTab::enableDisableCallBtn()
 
     bool enable = false;
 
-    if (mSessionID.notNull() && mSession && mSession->mSessionInitialized && mSession->mCallBackEnabled)
+    if (mSessionID.notNull()
+        && mSession
+        && mSession->mSessionInitialized
+        && mSession->mCallBackEnabled)
     {
         if (mVoiceButtonHangUpMode)
         {
@@ -451,8 +456,10 @@ void LLFloaterIMSessionTab::enableDisableCallBtn()
         else
         {
             // We allow to start call from this state only
-            if (mSession->mVoiceChannel->getState() == LLVoiceChannel::STATE_NO_CHANNEL_INFO &&
-                LLVoiceClient::instanceExists())
+            if (LLVoiceClient::instanceExists() &&
+                mSession->mVoiceChannel  &&
+                !mSession->mVoiceChannel->callStarted()
+                )
             {
                 LLVoiceClient* client = LLVoiceClient::getInstance();
                 if (client->voiceEnabled() && client->isVoiceWorking())
@@ -495,10 +502,7 @@ void LLFloaterIMSessionTab::onCallButtonClicked()
     }
     else
     {
-        LLVoiceChannel::EState channel_state = mSession && mSession->mVoiceChannel ?
-            mSession->mVoiceChannel->getState() : LLVoiceChannel::STATE_NO_CHANNEL_INFO;
-        // We allow to start call from this state only
-        if (channel_state == LLVoiceChannel::STATE_NO_CHANNEL_INFO)
+        if (mSession->mVoiceChannel && !mSession->mVoiceChannel->callStarted())
         {
             gIMMgr->startCall(mSessionID);
         }
@@ -1408,6 +1412,14 @@ LLView* LLFloaterIMSessionTab::getChatHistory()
     return mChatHistory;
 }
 
+void LLFloaterIMSessionTab::sessionRemoved(const LLUUID& session_id)
+{
+    if (session_id == mSessionID)
+    {
+        mSession = nullptr;
+    }
+}
+
 void LLFloaterIMSessionTab::applyMUPose(std::string& text)
 {
     if (!gSavedSettings.getBOOL("EnableMUPoseChat"))
diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h
index 61cc4f0614e71424cfb0ee60ab08003c8205757a..8478fd879c792b19eea2a243e57100584cac066a 100644
--- a/indra/newview/llfloaterimsessiontab.h
+++ b/indra/newview/llfloaterimsessiontab.h
@@ -48,6 +48,7 @@ class LLPanelEmojiComplete;
 
 class LLFloaterIMSessionTab
     : public LLTransientDockableFloater
+    , public LLIMSessionObserver
 {
 // [RLVa:KB] - @shownames
     friend struct RlvCommandHandler<RLV_TYPE_ADDREM, RLV_BHVR_SHOWNAMES>;
@@ -83,13 +84,13 @@ class LLFloaterIMSessionTab
     bool isNearbyChat() {return mIsNearbyChat;}
 
     // LLFloater overrides
-    /*virtual*/ void onOpen(const LLSD& key);
-    /*virtual*/ BOOL postBuild();
-    /*virtual*/ void draw();
-    /*virtual*/ void setVisible(BOOL visible);
-    /*virtual*/ void setFocus(BOOL focus);
-    /*virtual*/ void closeFloater(bool app_quitting = false);
-    /*virtual*/ void deleteAllChildren();
+    void onOpen(const LLSD& key) override;
+    BOOL postBuild() override;
+    void draw() override;
+    void setVisible(BOOL visible) override;
+    void setFocus(BOOL focus) override;
+    void closeFloater(bool app_quitting = false) override;
+    void deleteAllChildren() override;
 
     // Handle the left hand participant list widgets
     void addConversationViewParticipant(LLConversationItem* item, bool update_view = true);
@@ -115,6 +116,13 @@ class LLFloaterIMSessionTab
 
     LLView* getChatHistory();
 
+    // LLIMSessionObserver triggers
+    virtual void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, BOOL has_offline_msg) override {}; // Stub
+    virtual void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) override {}; // Stub
+    virtual void sessionRemoved(const LLUUID& session_id) override;
+    virtual void sessionVoiceOrIMStarted(const LLUUID& session_id) override {};                              // Stub
+    virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) override {};   // Stub
+
 protected:
 
     // callback for click on any items of the visual states menu
@@ -146,8 +154,8 @@ class LLFloaterIMSessionTab
     virtual void enableDisableCallBtn();
 
     // process focus events to set a currently active session
-    /* virtual */ void onFocusReceived();
-    /* virtual */ void onFocusLost();
+    void onFocusReceived() override;
+    void onFocusLost() override;
 
     // prepare chat's params and out one message to chatHistory
     void appendMessage(const LLChat& chat, const LLSD& args = LLSD());
@@ -225,7 +233,7 @@ class LLFloaterIMSessionTab
     void getSelectedUUIDs(uuid_vec_t& selected_uuids);
 
     /// Refreshes the floater at a constant rate.
-    virtual void refresh() = 0;
+    virtual void refresh() override = 0;
 
     /**
      * Adjusts chat history height to fit vertically with input chat field
diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp
index 0c2ff1bb32969dd46cdab7b36c19caef960a90f4..cf2e35f3c79a0764a0a419e604cc785586f92e4a 100644
--- a/indra/newview/llimprocessing.cpp
+++ b/indra/newview/llimprocessing.cpp
@@ -1963,23 +1963,29 @@ void LLIMProcessing::requestOfflineMessagesCoro(std::string url)
         {
             session_id = message_data["asset_id"].asUUID();
         }
-        LLIMProcessing::processNewMessage(
-            message_data["from_agent_id"].asUUID(),
-            from_group,
-            message_data["to_agent_id"].asUUID(),
-            message_data.has("offline") ? static_cast<U8>(message_data["offline"].asInteger()) : IM_OFFLINE,
-            dialog,
-            session_id,
-            static_cast<U32>(message_data["timestamp"].asInteger()),
-            message_data["from_agent_name"].asString(),
-            message_data["message"].asString(),
-            static_cast<U32>((message_data.has("parent_estate_id")) ? message_data["parent_estate_id"].asInteger() : 1), // 1 - IMMainland
-            message_data["region_id"].asUUID(),
-            position,
-            bin_bucket.data(),
-            bin_bucket.size(),
-            sender,
-            message_data["asset_id"].asUUID());
+
+        LLAppViewer::instance()->postToMainCoro([=]()
+            {
+                std::vector<U8> local_bin_bucket = bin_bucket;
+                LLHost local_sender = sender;
+                LLIMProcessing::processNewMessage(
+                    message_data["from_agent_id"].asUUID(),
+                    from_group,
+                    message_data["to_agent_id"].asUUID(),
+                    message_data.has("offline") ? static_cast<U8>(message_data["offline"].asInteger()) : IM_OFFLINE,
+                    dialog,
+                    session_id,
+                    static_cast<U32>(message_data["timestamp"].asInteger()),
+                    message_data["from_agent_name"].asString(),
+                    message_data["message"].asString(),
+                    static_cast<U32>((message_data.has("parent_estate_id")) ? message_data["parent_estate_id"].asInteger() : 1), // 1 - IMMainland
+                    message_data["region_id"].asUUID(),
+                    position,
+                    local_bin_bucket.data(),
+                    S32(local_bin_bucket.size()),
+                    local_sender,
+                    message_data["asset_id"].asUUID());
+            });
 
     }
 }
diff --git a/indra/newview/llreflectionmapmanager.cpp b/indra/newview/llreflectionmapmanager.cpp
index 69da7b965e97cf8d7d12014189726a61e66034ca..156352759ddbf943b083919a47c722117ce803c3 100644
--- a/indra/newview/llreflectionmapmanager.cpp
+++ b/indra/newview/llreflectionmapmanager.cpp
@@ -607,7 +607,7 @@ void LLReflectionMapManager::deleteProbe(U32 i)
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
     LLReflectionMap* probe = mProbes[i];
 
-    llassert(probe != mDefaultProbe);
+    llassert(probe != mDefaultProbe.get());
 
     if (probe->mCubeIndex != -1)
     { // mark the cube index used by this probe as being free
diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp
index b27f7898e367eadae57e61dcc254eca1eea3d7a6..00dc2595a4aa208a67f0c1ba60320f29173d2b44 100644
--- a/indra/newview/llspeakers.cpp
+++ b/indra/newview/llspeakers.cpp
@@ -538,7 +538,7 @@ void LLSpeakerMgr::updateSpeakerList()
                     mSpeakerListUpdated = true;
                 }
             }
-            else if (mSpeakers.empty())
+            else if (mSpeakers.size() == 0)
             {
                 // For all other session type (ad-hoc, P2P), we use the initial participants targets list
                 for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin();it!=session->mInitialTargetIDs.end();++it)
@@ -598,7 +598,7 @@ bool LLSpeakerMgr::removeSpeaker(const LLUUID& speaker_id)
 LLPointer<LLSpeaker> LLSpeakerMgr::findSpeaker(const LLUUID& speaker_id)
 {
     //In some conditions map causes crash if it is empty(Windows only), adding check (EK)
-    if (mSpeakers.empty())
+    if (mSpeakers.size() == 0)
         return NULL;
     speaker_map_t::iterator found_it = mSpeakers.find(speaker_id);
     if (found_it == mSpeakers.end())
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index f4fe10b4b128f43abcdc04942ee2d4e120c9aa74..f82b912dbb67f606ec898f2b224b1396d57a3cc3 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -347,10 +347,18 @@ void update_texture_fetch()
 
 void set_flags_and_update_appearance()
 {
-    LLAppearanceMgr::instance().setAttachmentInvLinkEnable(true);
-    LLAppearanceMgr::instance().updateAppearanceFromCOF(true, true, no_op);
+    // this may be called from a coroutine but has many side effects
+    // in non-thread-safe classes, post to main loop
+    auto work = []()
+        {
+            LLAppearanceMgr::instance().setAttachmentInvLinkEnable(true);
+            LLAppearanceMgr::instance().updateAppearanceFromCOF(true, true, no_op);
+
+            LLInventoryModelBackgroundFetch::instance().start();
+        };
+
+    LLAppViewer::instance()->postToMainCoro(work);
 
-    LLInventoryModelBackgroundFetch::instance().start();
 }
 
 // Returns false to skip other idle processing. Should only return
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index 576a17fc2f00b92190b63247fb430f53bfea0223..8e2032e2e9145c382887ccccdbde86875cb6f7c9 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -1249,42 +1249,46 @@ void LLViewerMedia::getOpenIDCookieCoro(std::string url)
         hostEnd = authority.size();
     }
 
-    LLViewerMedia* inst = getInstance();
     if (url.length())
     {
-        LLMediaCtrl* media_instance = LLFloaterReg::getInstance("destinations")->getChild<LLMediaCtrl>("destination_guide_contents");
-        if (media_instance)
-        {
-            std::string cookie_host = authority.substr(hostStart, hostEnd - hostStart);
-            std::string cookie_name = "";
-            std::string cookie_value = "";
-            std::string cookie_path = "";
-            bool httponly = true;
-            bool secure = true;
-            if (inst->parseRawCookie(inst->mOpenIDCookie, cookie_name, cookie_value, cookie_path, httponly, secure) &&
-                media_instance->getMediaPlugin())
+        LLAppViewer::instance()->postToMainCoro([=]()
             {
-                // MAINT-5711 - inexplicably, the CEF setCookie function will no longer set the cookie if the
-                // url and domain are not the same. This used to be my.sl.com and id.sl.com respectively and worked.
-                // For now, we use the URL for the OpenID POST request since it will have the same authority
-                // as the domain field.
-                // (Feels like there must be a less dirty way to construct a URL from component LLURL parts)
-                // MAINT-6392 - Rider: Do not change, however, the original URI requested, since it is used further
-                // down.
-                std::string cefUrl(std::string(inst->mOpenIDURL.mURI) + "://" + std::string(inst->mOpenIDURL.mAuthority));
-
-                media_instance->getMediaPlugin()->setCookie(cefUrl, cookie_name, cookie_value, cookie_host,
-                    cookie_path, httponly, secure);
-
-                // Now that we have parsed the raw cookie, we must store it so that each new media instance
-                // can also get a copy and faciliate logging into internal SL sites.
-                media_instance->getMediaPlugin()->storeOpenIDCookie(cefUrl, cookie_name, cookie_value,
-                    cookie_host, cookie_path, httponly, secure);
-            }
-        }
-        LLFloaterReg::hideInstance("destinations");
+                LLMediaCtrl* media_instance = LLFloaterReg::getInstance("destinations")->getChild<LLMediaCtrl>("destination_guide_contents");
+                if (media_instance)
+                {
+                    LLViewerMedia* inst = getInstance();
+                    std::string cookie_host = authority.substr(hostStart, hostEnd - hostStart);
+                    std::string cookie_name = "";
+                    std::string cookie_value = "";
+                    std::string cookie_path = "";
+                    bool httponly = true;
+                    bool secure = true;
+                    if (inst->parseRawCookie(inst->mOpenIDCookie, cookie_name, cookie_value, cookie_path, httponly, secure) &&
+                        media_instance->getMediaPlugin())
+                    {
+                        // MAINT-5711 - inexplicably, the CEF setCookie function will no longer set the cookie if the
+                        // url and domain are not the same. This used to be my.sl.com and id.sl.com respectively and worked.
+                        // For now, we use the URL for the OpenID POST request since it will have the same authority
+                        // as the domain field.
+                        // (Feels like there must be a less dirty way to construct a URL from component LLURL parts)
+                        // MAINT-6392 - Rider: Do not change, however, the original URI requested, since it is used further
+                        // down.
+                        std::string cefUrl(std::string(inst->mOpenIDURL.mURI) + "://" + std::string(inst->mOpenIDURL.mAuthority));
+
+                        media_instance->getMediaPlugin()->setCookie(cefUrl, cookie_name, cookie_value, cookie_host,
+                            cookie_path, httponly, secure);
+
+                        // Now that we have parsed the raw cookie, we must store it so that each new media instance
+                        // can also get a copy and faciliate logging into internal SL sites.
+                        media_instance->getMediaPlugin()->storeOpenIDCookie(cefUrl, cookie_name, cookie_value,
+                            cookie_host, cookie_path, httponly, secure);
+                    }
+                }
+            });
     }
 
+    LLViewerMedia* inst = getInstance();
+
     // Note: Rider: MAINT-6392 - Some viewer code requires access to the my.sl.com openid cookie for such
     // actions as posting snapshots to the feed.  This is handled through HTTPCore rather than CEF and so
     // we must learn to SHARE the cookies.
@@ -2680,7 +2684,13 @@ void LLViewerMediaImpl::mimeDiscoveryCoro(std::string url)
         {
             if (initializeMedia(mimeType))
             {
-                loadURI();
+                ref();
+                LLAppViewer::instance()->postToMainCoro([this]()
+                    {
+                        loadURI();
+                        unref();
+                    });
+
             }
         }
 
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index e6ca095c8eaedd8420a6f976e41bbbc8a392da9a..c1af50147bcdaca488d70497d53114ce3eef4ddc 100755
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -2677,11 +2677,8 @@ void LLViewerRegion::setSimulatorFeatures(const LLSD& sim_features)
             }
         };
 
-    auto workqueue = LL::WorkQueue::getInstance("mainloop");
-    if (workqueue)
-    {
-        LL::WorkQueue::postMaybe(workqueue, work);
-    }
+
+    LLAppViewer::instance()->postToMainCoro(work);
 }
 
 //this is called when the parent is not cacheable.
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 754f5107703384e94cdb701df7ccfa01c325340b..54188a8c38c689323a0ad5148e8ba5b6bffb0000 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -2360,6 +2360,8 @@ void LLViewerWindow::initBase()
     // Hide the toolbars for the moment: we'll make them visible after logging in world (see LLViewerWindow::initWorldUI())
     gToolBarView->setVisible(FALSE);
 
+    mFloaterSnapRegion = gToolBarView->getChild<LLView>("floater_snap_region");
+    mChicletContainer = gToolBarView->getChild<LLPanel>("chiclet_container");
     // Constrain floaters to inside the menu and status bar regions.
     gFloaterView = main_view->getChild<LLFloaterView>("Floater View");
     for (S32 i = 0; i < LLToolBarEnums::TOOLBAR_COUNT; ++i)
@@ -2370,8 +2372,6 @@ void LLViewerWindow::initBase()
             toolbarp->getCenterLayoutPanel()->setReshapeCallback(boost::bind(&LLFloaterView::setToolbarRect, gFloaterView, _1, _2));
         }
     }
-    mChicletContainer = getRootView()->getChild<LLPanel>("chiclet_container");
-    mFloaterSnapRegion = main_view->getChild<LLView>("floater_snap_region");
     gFloaterView->setFloaterSnapView(mFloaterSnapRegion->getHandle());
     gSnapshotFloaterView = main_view->getChild<LLSnapshotFloaterView>("Snapshot Floater View");
 
@@ -2463,25 +2463,33 @@ void LLViewerWindow::initWorldUI()
     // Force gFloaterTools to initialize
     LLFloaterReg::getInstance("build");
 
-    // Status bar
-    mStatusBarContainer = getRootView()->getChild<LLPanel>("status_bar_container");
-    gStatusBar = new LLStatusBar(mStatusBarContainer->getLocalRect());
-    gStatusBar->setFollows(FOLLOWS_LEFT | FOLLOWS_TOP | FOLLOWS_RIGHT);
-    gStatusBar->setShape(mStatusBarContainer->getLocalRect());
-    // sync bg color with menu bar
-    gStatusBar->setBackgroundColor( gMenuBarView->getBackgroundColor().get() );
-    // add InBack so that gStatusBar won't be drawn over menu
-    mStatusBarContainer->addChildInBack(gStatusBar, 2/*tab order, after menu*/);
-    mStatusBarContainer->setVisible(TRUE);
-
-    // Navigation bar
-    mNavBarBarContainer = getRootView()->getChild<LLView>("nav_bar_container");
-
     LLNavigationBar* navbar = LLNavigationBar::getInstance();
-    navbar->setShape(mNavBarBarContainer->getLocalRect());
-    navbar->setBackgroundColor(gMenuBarView->getBackgroundColor().get());
-    mNavBarBarContainer->addChild(navbar);
-    mNavBarBarContainer->setVisible(TRUE);
+    if (!gStatusBar)
+    {
+        // Status bar
+	    mStatusBarContainer = getRootView()->getChild<LLPanel>("status_bar_container");
+        gStatusBar = new LLStatusBar(mStatusBarContainer->getLocalRect());
+        gStatusBar->setFollows(FOLLOWS_LEFT | FOLLOWS_TOP | FOLLOWS_RIGHT);
+        gStatusBar->setShape(mStatusBarContainer->getLocalRect());
+        // sync bg color with menu bar
+	    gStatusBar->setBackgroundColor( gMenuBarView->getBackgroundColor().get() );
+        // add InBack so that gStatusBar won't be drawn over menu
+        mStatusBarContainer->addChildInBack(gStatusBar, 2/*tab order, after menu*/);
+	    mStatusBarContainer->setVisible(TRUE);
+
+        // Navigation bar
+	    mNavBarBarContainer = getRootView()->getChild<LLView>("nav_bar_container");
+
+	    navbar->setShape(mNavBarBarContainer->getLocalRect());
+	    navbar->setBackgroundColor(gMenuBarView->getBackgroundColor().get());
+	    mNavBarBarContainer->addChild(navbar);
+	    mNavBarBarContainer->setVisible(TRUE);
+    }
+    else
+    {
+        mStatusBarContainer->setVisible(true);
+        mNavBarBarContainer->setVisible(true);
+    }
 
     const U32 location_bar = gSavedSettings.getU32("NavigationBarStyle");
     if (location_bar != 2)
@@ -2823,12 +2831,11 @@ void LLViewerWindow::setNormalControlsVisible( BOOL visible )
         gStatusBar->setEnabled( visible );
     }
 
-    LLNavigationBar* navbarp = LLUI::getRootView()->findChild<LLNavigationBar>("navigation_bar");
-    if (navbarp)
+    if (mNavBarBarContainer)
     {
         // when it's time to show navigation bar we need to ensure that the user wants to see it
         // i.e. ShowNavbarNavigationPanel option is true
-        navbarp->setVisible( visible && (gSavedSettings.getU32("NavigationBarStyle") == 2));
+        mNavBarBarContainer->setVisible( visible && (gSavedSettings.getU32("NavigationBarStyle") == 2));
     }
 }
 
diff --git a/indra/newview/llvoicecallhandler.cpp b/indra/newview/llvoicecallhandler.cpp
index 92f3543517b5577e42b5ccbb3ab59b229c217935..04a143b62ce5e18f88941a2f8defcdf8107431f9 100644
--- a/indra/newview/llvoicecallhandler.cpp
+++ b/indra/newview/llvoicecallhandler.cpp
@@ -48,7 +48,7 @@ class LLVoiceCallAvatarHandler : public LLCommandHandler
 
         //Get the ID
         LLUUID id;
-        if (!id.set(params[0].asStringRef(), FALSE ))
+        if (!id.set(params[0].asString(), FALSE ))
         {
             return false;
         }
diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp
index 55769f567bb88f106be5d0b30c5b96597238767a..cf128f381a0d393d6877a2dbf748a87ead1ad676 100644
--- a/indra/newview/llvoicechannel.cpp
+++ b/indra/newview/llvoicechannel.cpp
@@ -80,9 +80,8 @@ LLVoiceChannel::~LLVoiceChannel()
     if (sCurrentVoiceChannel == this)
     {
         sCurrentVoiceChannel = NULL;
-        // Must check instance exists here, the singleton MAY have already been destroyed.
-        LLVoiceClient::removeObserver(this);
     }
+    LLVoiceClient::removeObserver(this);
 
     sVoiceChannelMap.erase(mSessionID);
 }
diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp
index fb7929a219922f493b83a8505a861b3cb819ffed..76df5c74d6fe83a74b70bbd2696d8cd2e972d548 100644
--- a/indra/newview/llvoiceclient.cpp
+++ b/indra/newview/llvoiceclient.cpp
@@ -170,7 +170,11 @@ void LLVoiceClient::init(LLPumpIO *pump)
 
 void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID)
 {
-    gAgent.addRegionChangedCallback(boost::bind(&LLVoiceClient::onRegionChanged, this));
+    if (mRegionChangedCallbackSlot.connected())
+    {
+        mRegionChangedCallbackSlot.disconnect();
+    }
+    mRegionChangedCallbackSlot = gAgent.addRegionChangedCallback(boost::bind(&LLVoiceClient::onRegionChanged, this));
     LLWebRTCVoiceClient::getInstance()->userAuthorized(user_id, agentID);
     LLVivoxVoiceClient::getInstance()->userAuthorized(user_id, agentID);
 }
@@ -607,8 +611,14 @@ bool LLVoiceClient::voiceEnabled()
 
 void LLVoiceClient::setVoiceEnabled(bool enabled)
 {
-    LLWebRTCVoiceClient::getInstance()->setVoiceEnabled(enabled);
-    LLVivoxVoiceClient::getInstance()->setVoiceEnabled(enabled);
+    if (LLWebRTCVoiceClient::instanceExists())
+    {
+        LLWebRTCVoiceClient::getInstance()->setVoiceEnabled(enabled);
+    }
+    if (LLVivoxVoiceClient::instanceExists())
+    {
+        LLVivoxVoiceClient::getInstance()->setVoiceEnabled(enabled);
+    }
 }
 
 void LLVoiceClient::updateMicMuteLogic()
diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h
index ebd43047b43a8bfd850e65c85537fe4ed0c82827..1e073209858a613c4857d584a8049ee2cc2aaa74 100644
--- a/indra/newview/llvoiceclient.h
+++ b/indra/newview/llvoiceclient.h
@@ -438,7 +438,7 @@ class LLVoiceClient: public LLParamSingleton<LLVoiceClient>
     bool getUserPTTState();
     void toggleUserPTTState(void);
     void inputUserControlState(bool down);  // interpret any sort of up-down mic-open control input according to ptt-toggle prefs
-    void setVoiceEnabled(bool enabled);
+    static void setVoiceEnabled(bool enabled);
 
     void setUsePTT(bool usePTT);
     void setPTTIsToggle(bool PTTIsToggle);
@@ -519,6 +519,7 @@ class LLVoiceClient: public LLParamSingleton<LLVoiceClient>
     LLPumpIO *m_servicePump;
 
     boost::signals2::connection  mSimulatorFeaturesReceivedSlot;
+    boost::signals2::connection  mRegionChangedCallbackSlot;
 
     LLCachedControl<bool> mVoiceEffectEnabled;
     LLCachedControl<std::string> mVoiceEffectDefault;
diff --git a/indra/newview/llvoicevisualizer.cpp b/indra/newview/llvoicevisualizer.cpp
index 8a7925674af944bfeaed7ee009138aeac090aed2..0981b6ac19f4d4d5c6b082bb4f4fcf451aac530b 100644
--- a/indra/newview/llvoicevisualizer.cpp
+++ b/indra/newview/llvoicevisualizer.cpp
@@ -338,11 +338,14 @@ void LLVoiceVisualizer::lipSyncOohAah( F32& ooh, F32& aah )
 //---------------------------------------------------
 void LLVoiceVisualizer::render()
 {
-    if ( ! mVoiceEnabled  || ALCinematicMode::isEnabled())
+    static LLCachedControl<bool> show_visualizer(gSavedSettings, "VoiceVisualizerEnabled", true);
+    if ( ! mVoiceEnabled  || !show_visualizer || ALCinematicMode::isEnabled())
     {
         return;
     }
 
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
+
     if ( mSoundSymbol.mActive )
     {
         mPreviousTime = mCurrentTime;
diff --git a/indra/newview/llvoicevisualizer.h b/indra/newview/llvoicevisualizer.h
index 03ab6cf95a2edab66ed09fe9e6e0b30d306d7c8a..4e7548293d7993737b1a43ee9feea09e40ff6b0f 100644
--- a/indra/newview/llvoicevisualizer.h
+++ b/indra/newview/llvoicevisualizer.h
@@ -100,6 +100,8 @@ class LLVoiceVisualizer final : public LLHUDEffect
         void setMaxGesticulationAmplitude();
         void setMinGesticulationAmplitude();
 
+        static bool getLipSyncEnabled() { return sLipSyncEnabled; }
+
     //---------------------------------------------------
     // private members
     //---------------------------------------------------
diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp
index 5bad4006b4d7161a40da39335a0a4e4ce5393b42..c72eec189672685a77e8586e9827edbbef959c17 100644
--- a/indra/newview/llvoicevivox.cpp
+++ b/indra/newview/llvoicevivox.cpp
@@ -2090,23 +2090,48 @@ bool LLVivoxVoiceClient::waitForChannel()
                     llcoro::suspend();
                     break;
                 }
-                sessionStatePtr_t joinSession = mNextAudioSession;
-                mNextAudioSession.reset();
-                mIsProcessingChannels = true;
-                if (!runSession(joinSession)) //suspends
+
+                try
                 {
-                    mIsProcessingChannels = false;
-                    LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL;
-                    break;
+                    sessionStatePtr_t joinSession = mNextAudioSession;
+                    mNextAudioSession.reset();
+                    mIsProcessingChannels = true;
+                    if (!runSession(joinSession)) //suspends
+                    {
+                        mIsProcessingChannels = false;
+                        LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL;
+                        break;
+                    }
+                    else
+                    {
+                        mIsProcessingChannels = false;
+                        LL_DEBUGS("Voice")
+                            << "runSession returned true to inner loop"
+                            << " RelogRequested=" << mRelogRequested
+                            << " VoiceEnabled=" << mVoiceEnabled
+                            << LL_ENDL;
+                    }
                 }
-                else
+                catch (const LLCoros::Stop&)
+                {
+                    LL_DEBUGS("LLVivoxVoiceClient") << "Received a shutdown exception" << LL_ENDL;
+                }
+                catch (const LLContinueError&)
                 {
-                    mIsProcessingChannels = false;
-                    LL_DEBUGS("Voice")
-                        << "runSession returned true to inner loop"
-                        << " RelogRequested=" << mRelogRequested
-                        << " VoiceEnabled=" << mVoiceEnabled
+                    LOG_UNHANDLED_EXCEPTION("LLVivoxVoiceClient");
+                }
+                catch (...)
+                {
+                    // Ideally for Windows need to log SEH exception instead or to set SEH
+                    // handlers but bugsplat shows local variables for windows, which should
+                    // be enough
+                    LL_WARNS("Voice") << "voiceControlStateMachine crashed in state VOICE_CHANNEL_STATE_PROCESS_CHANNEL"
+                        << " mRelogRequested " << mRelogRequested
+                        << " mVoiceEnabled " << mVoiceEnabled
+                        << " mIsProcessingChannels " << mIsProcessingChannels
+                        << " mProcessChannels " << mProcessChannels
                         << LL_ENDL;
+                    throw;
                 }
             }
 
@@ -5105,8 +5130,7 @@ bool LLVivoxVoiceClient::isVoiceWorking() const
     //Added stateSessionTerminated state to avoid problems with call in parcels with disabled voice (EXT-4758)
     // Condition with joining spatial num was added to take into account possible problems with connection to voice
     // server(EXT-4313). See bug descriptions and comments for MAX_NORMAL_JOINING_SPATIAL_NUM for more info.
-    return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && mIsProcessingChannels;
-//  return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && (stateLoggedIn <= mState) && (mState <= stateSessionTerminated);
+    return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && mIsLoggedIn;
 }
 
 // Returns true if the indicated participant in the current audio session is really an SL avatar.
@@ -6731,12 +6755,9 @@ void LLVivoxVoiceClient::expireVoiceFonts()
         }
     }
 
-    static const std::string voice_morphing_url = LLTrans::getString("voice_morphing_url");
-    static const std::string premium_voice_morphing_url = LLTrans::getString("premium_voice_morphing_url");
-
     LLSD args;
-    args["URL"] = voice_morphing_url;
-    args["PREMIUM_URL"] = premium_voice_morphing_url;
+    args["URL"] = LLTrans::getString("voice_morphing_url");
+    args["PREMIUM_URL"] = LLTrans::getString("premium_voice_morphing_url");
 
     // Give a notification if any voice fonts have expired.
     if (have_expired)
diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp
index 4528b57061698ebb773931797949a2552af5f3db..b2e5de537194ea4c2fd014c3015c84840ca593c2 100644
--- a/indra/newview/llvoicewebrtc.cpp
+++ b/indra/newview/llvoicewebrtc.cpp
@@ -87,6 +87,8 @@ namespace {
 
     const F32 SPEAKING_AUDIO_LEVEL = 0.30;
 
+    const uint32_t PEER_GAIN_CONVERSION_FACTOR = 220;
+
     static const std::string REPORTED_VOICE_SERVER_TYPE = "Secondlife WebRTC Gateway";
 
     // Don't send positional updates more frequently than this:
@@ -256,6 +258,8 @@ void LLWebRTCVoiceClient::cleanupSingleton()
     }
     cleanUp();
     sessionState::clearSessions();
+
+    mStatusObservers.clear();
 }
 
 //---------------------------------------------------
@@ -401,8 +405,9 @@ void LLWebRTCVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::ESt
     LL_DEBUGS("Voice") << "( " << LLVoiceClientStatusObserver::status2string(status) << " )"
                        << " mSession=" << mSession << LL_ENDL;
 
+    bool in_spatial_channel = inSpatialChannel();
     LL_DEBUGS("Voice") << " " << LLVoiceClientStatusObserver::status2string(status) << ", session channelInfo "
-                       << getAudioSessionChannelInfo() << ", proximal is " << inSpatialChannel() << LL_ENDL;
+                       << getAudioSessionChannelInfo() << ", proximal is " << in_spatial_channel << LL_ENDL;
 
     mIsProcessingChannels = status == LLVoiceClientStatusObserver::STATUS_JOINED;
 
@@ -410,7 +415,7 @@ void LLWebRTCVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::ESt
     for (status_observer_set_t::iterator it = mStatusObservers.begin(); it != mStatusObservers.end();)
     {
         LLVoiceClientStatusObserver *observer = *it;
-        observer->onChange(status, channelInfo, inSpatialChannel());
+        observer->onChange(status, channelInfo, in_spatial_channel);
         // In case onError() deleted an entry.
         it = mStatusObservers.upper_bound(observer);
     }
@@ -420,7 +425,7 @@ void LLWebRTCVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::ESt
         status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL &&
         status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED)
     {
-        bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking();
+        bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && mIsProcessingChannels;
 
         gAgent.setVoiceConnected(voice_status);
 
@@ -1335,7 +1340,10 @@ bool LLWebRTCVoiceClient::startAdHocSession(const LLSD& channelInfo, bool notify
 
 bool LLWebRTCVoiceClient::isVoiceWorking() const
 {
-    return mIsProcessingChannels;
+    // webrtc is working if the coroutine is active in the case of
+    // webrtc. WebRTC doesn't need to connect to a secondary process
+    // or a login server to become active.
+    return mIsCoroutineActive;
 }
 
 // Returns true if calling back the session URI after the session has closed is possible.
@@ -2440,7 +2448,7 @@ void LLVoiceWebRTCConnection::setSpeakerVolume(F32 volume)
 
 void LLVoiceWebRTCConnection::setUserVolume(const LLUUID& id, F32 volume)
 {
-    boost::json::object root = {{"ug", {id.asString(), (uint32_t) (volume * 200)}}};
+    boost::json::object root      = { { "ug", { { id.asString(), (uint32_t)(volume * PEER_GAIN_CONVERSION_FACTOR) } } } };
     std::string json_data = boost::json::serialize(root);
     if (mWebRTCDataInterface)
     {
@@ -2450,7 +2458,7 @@ void LLVoiceWebRTCConnection::setUserVolume(const LLUUID& id, F32 volume)
 
 void LLVoiceWebRTCConnection::setUserMute(const LLUUID& id, bool mute)
 {
-    boost::json::object root = {{"m", {id.asString(), mute}}};
+    boost::json::object root      = { { "m", { { id.asString(), mute } } } };
     std::string         json_data = boost::json::serialize(root);
     if (mWebRTCDataInterface)
     {
@@ -2983,7 +2991,9 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b
                     // we got a 'power' update.
                     if (participant_obj.contains("p") && participant_obj["p"].is_number())
                     {
-                        participant->mLevel = (F32)participant_obj["p"].as_int64();
+                        // server sends up power as an integer which is level * 128 to save
+                        // character count.
+                        participant->mLevel = (F32)participant_obj["p"].as_int64()/128.0f;
                     }
 
                     if (participant_obj.contains("v") && participant_obj["v"].is_bool())
@@ -2991,10 +3001,9 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b
                         participant->mIsSpeaking = participant_obj["v"].as_bool();
                     }
 
-                    if (participant_obj.contains("v") && participant_obj["m"].is_bool())
+                    if (participant_obj.contains("m") && participant_obj["m"].is_bool())
                     {
                         participant->mIsModeratorMuted = participant_obj["m"].as_bool();
-                        ;
                     }
                 }
             }
diff --git a/indra/newview/llwearablelist.cpp b/indra/newview/llwearablelist.cpp
index f3bd5bc23a7b43127cb162e6327cb4874f23dcf1..0c6ed678ca8495cf8064a14136f019ddad1f825c 100644
--- a/indra/newview/llwearablelist.cpp
+++ b/indra/newview/llwearablelist.cpp
@@ -36,6 +36,7 @@
 #include "llnotificationsutil.h"
 #include "llinventorymodel.h"
 #include "lltrans.h"
+#include "llappviewer.h"
 
 // Callback struct
 struct LLWearableArrivedData
@@ -81,9 +82,7 @@ void LLWearableList::getAsset(const LLAssetID& assetID, const std::string& weara
     LLViewerWearable* instance = get_if_there(mList, assetID, (LLViewerWearable*)NULL );
     if( instance )
     {
-#ifdef SHOW_DEBUG
         LL_DEBUGS("Avatar") << "wearable " << assetID << " found in LLWearableList" << LL_ENDL;
-#endif
         asset_arrived_callback( instance, userdata );
     }
     else
@@ -99,6 +98,22 @@ void LLWearableList::getAsset(const LLAssetID& assetID, const std::string& weara
 // static
 void LLWearableList::processGetAssetReply( const char* filename, const LLAssetID& uuid, void* userdata, S32 status, LLExtStat ext_status )
 {
+    if (!LLCoros::on_main_coro())
+    {
+        // if triggered from a coroutine, dispatch to main thread before accessing app state
+        std::string filename_in = filename;
+        LLUUID uuid_in = uuid;
+
+        LLAppViewer::instance()->postToMainCoro([=]()
+            {
+                processGetAssetReply(filename_in.c_str(), uuid_in, userdata, status, ext_status);
+            });
+
+        return;
+    }
+
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
+
     BOOL isNewWearable = FALSE;
     LLWearableArrivedData* data = (LLWearableArrivedData*) userdata;
 //  LLViewerWearable* wearable = NULL; // NULL indicates failure
@@ -203,10 +218,8 @@ void LLWearableList::processGetAssetReply( const char* filename, const LLAssetID
     if (wearable) // success
     {
         LLWearableList::instance().mList[ uuid ] = wearable;
-#ifdef SHOW_DEBUG
         LL_DEBUGS("Wearable") << "processGetAssetReply()" << LL_ENDL;
         LL_DEBUGS("Wearable") << wearable << LL_ENDL;
-#endif
     }
     else
     {
@@ -239,9 +252,7 @@ void LLWearableList::processGetAssetReply( const char* filename, const LLAssetID
 
 LLViewerWearable* LLWearableList::createCopy(const LLViewerWearable* old_wearable, const std::string& new_name)
 {
-#ifdef SHOW_DEBUG
     LL_DEBUGS() << "LLWearableList::createCopy()" << LL_ENDL;
-#endif
 
     LLViewerWearable *wearable = generateNewWearable();
     wearable->copyDataFrom(old_wearable);
@@ -260,9 +271,7 @@ LLViewerWearable* LLWearableList::createCopy(const LLViewerWearable* old_wearabl
 
 LLViewerWearable* LLWearableList::createNewWearable( LLWearableType::EType type, LLAvatarAppearance *avatarp )
 {
-#ifdef SHOW_DEBUG
     LL_DEBUGS() << "LLWearableList::createNewWearable()" << LL_ENDL;
-#endif
 
     LLViewerWearable *wearable = generateNewWearable();
     wearable->setType( type, avatarp );
diff --git a/indra/newview/skins/default/xui/en/floater_about.xml b/indra/newview/skins/default/xui/en/floater_about.xml
index 65f0de00863585fabf5089242c8382bb44accc63..c3a927398834d7ef534b0b1c49e5da43198779a8 100644
--- a/indra/newview/skins/default/xui/en/floater_about.xml
+++ b/indra/newview/skins/default/xui/en/floater_about.xml
@@ -46,7 +46,7 @@
        left="25"
        mouse_opaque="true"
        name="github_icon"
-       tool_tip="[APP_NAME] on Discord"
+       tool_tip="[APP_NAME] on GitHub"
        top="140"
        width="41">
         <icon.commit_callback
@@ -62,7 +62,7 @@
        left_pad="10"
        mouse_opaque="true"
        name="bsky_icon"
-       tool_tip="[APP_NAME] on Mastodon"
+       tool_tip="[APP_NAME] on Bluesky"
        top="140"
        width="42">
         <icon.commit_callback
diff --git a/indra/newview/skins/default/xui/fr/panel_outfits_inventory.xml b/indra/newview/skins/default/xui/fr/panel_outfits_inventory.xml
index 05c689dd3443d53e7aed0a82a7963ce660940dfe..4dfe724894c72cb17192bba827aeb8fc44b451ad 100644
--- a/indra/newview/skins/default/xui/fr/panel_outfits_inventory.xml
+++ b/indra/newview/skins/default/xui/fr/panel_outfits_inventory.xml
@@ -14,7 +14,10 @@
 	<panel name="bottom_panel">
 		<layout_stack name="bottom_panel_ls">
 			<layout_panel name="save_btn_lp">
-				<button label="Enregistrer sous" name="save_btn"/>
+				<button label="Enregistrer" name="save_btn"/>
+			</layout_panel>
+			<layout_panel name="save_as_btn_lp">
+				<button label="Enregistrer sous..." name="save_as_btn"/>
 			</layout_panel>
 			<layout_panel name="wear_btn_lp">
 				<button label="Porter" name="wear_btn"/>
diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp
index b9fffb4637abbb03b30decd832de544411106c1a..40e5eacf3762cd5c0e885965d599b3ab109d4b5a 100644
--- a/indra/viewer_components/login/lllogin.cpp
+++ b/indra/viewer_components/login/lllogin.cpp
@@ -61,6 +61,16 @@ class LLLogin::Impl
     LLEventPump& getEventPump() { return mPump; }
 
 private:
+    LLSD hidePasswd(const LLSD& data)
+    {
+        LLSD result(data);
+        if (result.has("params") && result["params"].has("passwd"))
+        {
+            result["params"]["passwd"] = "*******";
+        }
+        return result;
+    }
+
     LLSD getProgressEventLLSD(const std::string& state, const std::string& change,
                            const LLSD& data = LLSD())
     {
@@ -69,15 +79,16 @@ class LLLogin::Impl
         status_data["change"] = change;
         status_data["progress"] = 0.0f;
 
-        if(mAuthResponse.has("transfer_rate"))
+        if (mAuthResponse.has("transfer_rate"))
         {
             status_data["transfer_rate"] = mAuthResponse["transfer_rate"];
         }
 
-        if(data.isDefined())
+        if (data.isDefined())
         {
             status_data["data"] = data;
         }
+
         return status_data;
     }
 
@@ -114,17 +125,18 @@ class LLLogin::Impl
 
 void LLLogin::Impl::connect(const std::string& uri, const LLSD& login_params)
 {
-    LL_DEBUGS("LLLogin") << " connect with  uri '" << uri << "', login_params " << login_params << LL_ENDL;
+    LL_DEBUGS("LLLogin") << " connect with uri '" << uri << "', login_params " << login_params << LL_ENDL;
 
     // Launch a coroutine with our login_() method. Run the coroutine until
     // its first wait; at that point, return here.
     std::string coroname =
-        LLCoros::instance().launch("LLLogin::Impl::login_",
-                                   boost::bind(&Impl::loginCoro, this, uri, login_params));
-    LL_DEBUGS("LLLogin") << " connected with  uri '" << uri << "', login_params " << login_params << LL_ENDL;
+        LLCoros::instance().launch("LLLogin::Impl::login_", [=]() { loginCoro(uri, login_params); });
+
+    LL_DEBUGS("LLLogin") << " connected with uri '" << uri << "', login_params " << login_params << LL_ENDL;
 }
 
-namespace {
+namespace
+{
 // Instantiate this rendezvous point at namespace scope so it's already
 // present no matter how early the updater might post to it.
 // Use an LLEventMailDrop, which has future-like semantics: regardless of the
@@ -135,12 +147,8 @@ static LLEventMailDrop sSyncPoint("LoginSync");
 
 void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params)
 {
-    LLSD printable_params = login_params;
-    if (printable_params.has("params")
-        && printable_params["params"].has("passwd"))
-    {
-        printable_params["params"]["passwd"] = "*******";
-    }
+    LLSD printable_params = hidePasswd(login_params);
+
     try
     {
         LL_DEBUGS("LLLogin") << "Entering coroutine " << LLCoros::getName()
@@ -166,12 +174,7 @@ void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params)
             ++attempts;
             LLSD progress_data;
             progress_data["attempt"] = attempts;
-            progress_data["request"] = request;
-            if (progress_data["request"].has("params")
-                && progress_data["request"]["params"].has("passwd"))
-            {
-                progress_data["request"]["params"]["passwd"] = "*******";
-            }
+            progress_data["request"] = hidePasswd(request);
             sendProgressEvent("offline", "authenticating", progress_data);
 
             // We expect zero or more "Downloading" status events, followed by