diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 8d1c6b63e65bf8faac83228943a4c2f636118fca..57faafc042f1245f23643b8f13c205e00f24755c 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -312,7 +312,7 @@ jobs:
       AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
       AZURE_TENANT_ID:     ${{ secrets.AZURE_TENANT_ID }}
     needs: build
-    runs-on: windows
+    runs-on: windows-large
     steps:
       - name: Sign and package Windows viewer
         if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID
@@ -412,7 +412,7 @@ jobs:
       - uses: actions/download-artifact@v4
         with:
           pattern: "*-metadata"
-      
+
       - name: Rename metadata
         run: |
           cp Windows-metadata/autobuild-package.xml Windows-autobuild-package.xml
@@ -441,7 +441,7 @@ jobs:
           append_body: true
           fail_on_unmatched_files: true
           files: |
-            macOS-installer/*.dmg 
+            macOS-installer/*.dmg
             Windows-installer/*.exe
             *-autobuild-package.xml
             *-viewer_version.txt
diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake
index 16643da37f8a2d8bdfdf0a1768eeea85e6fbefb9..e30e5eb4cb1c1477cba0561bab5dc39b87cd373b 100644
--- a/indra/cmake/00-Common.cmake
+++ b/indra/cmake/00-Common.cmake
@@ -165,9 +165,10 @@ if (WINDOWS)
   string(REPLACE "/Ob2" "/Ob3" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})
   string(REPLACE "/Ob2" "/Ob3" CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE})
 
-  # configure win32 API for 10 and above compatibility
-  set(WINVER "0x0A00" CACHE STRING "Win32 API Target version (see http://msdn.microsoft.com/en-us/library/aa383745%28v=VS.85%29.aspx)")
-  add_compile_definitions(WINVER=${WINVER} _WIN32_WINNT=${WINVER})
+  # workaround for github runner image breakage:
+  # https://github.com/actions/runner-images/issues/10004#issuecomment-2153445161
+  # can be removed after the above issue is resolved and deployed across GHA
+  add_compile_definitions(_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
 endif (WINDOWS)
 
 if (LINUX)
diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp
index 323b9e85cc9c0b98c49f068ce81fe80cc65d9f7f..9ec86af60dbc59a69e136ecb911f51f85df82fc3 100644
--- a/indra/llcommon/llleap.cpp
+++ b/indra/llcommon/llleap.cpp
@@ -18,8 +18,6 @@
 #include <algorithm>
 // std headers
 // external library headers
-#include <boost/bind.hpp>
-#include <boost/tokenizer.hpp>
 // other Linden headers
 #include "llerror.h"
 #include "llstring.h"
@@ -56,7 +54,9 @@ class LLLeapImpl: public LLLeap
         // Pass it a callback to our connect() method, so it can send events
         // from a particular LLEventPump to the plugin without having to know
         // this class or method name.
-        mListener(new LLLeapListener(boost::bind(&LLLeapImpl::connect, this, _1, _2)))
+        mListener(new LLLeapListener(
+                      [this](LLEventPump& pump, const std::string& listener)
+                      { return connect(pump, listener); }))
     {
         // Rule out unpopulated Params block
         if (! cparams.executable.isProvided())
@@ -85,7 +85,7 @@ class LLLeapImpl: public LLLeap
         }
 
         // Listen for child "termination" right away to catch launch errors.
-        mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::bad_launch, this, _1));
+        mDonePump.listen("LLLeap", [this](const LLSD& data){ return bad_launch(data); });
 
         // Okay, launch child.
         // Get a modifiable copy of params block to set files and postend.
@@ -105,7 +105,7 @@ class LLLeapImpl: public LLLeap
 
         // Okay, launch apparently worked. Change our mDonePump listener.
         mDonePump.stopListening("LLLeap");
-        mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::done, this, _1));
+        mDonePump.listen("LLLeap", [this](const LLSD& data){ return done(data); });
 
         // Child might pump large volumes of data through either stdout or
         // stderr. Don't bother copying all that data into notification event.
@@ -120,13 +120,9 @@ class LLLeapImpl: public LLLeap
 
         // Listening on stdout is stateful. In general, we're either waiting
         // for the length prefix or waiting for the specified length of data.
-        // We address that with two different listener methods -- one of which
-        // is blocked at any given time.
+        mReadPrefix = true;
         mStdoutConnection = childout.getPump()
-            .listen("prefix", boost::bind(&LLLeapImpl::rstdout, this, _1));
-        mStdoutDataConnection = childout.getPump()
-            .listen("data",   boost::bind(&LLLeapImpl::rstdoutData, this, _1));
-        mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
+            .listen("LLLeap", [this](const LLSD& data){ return rstdout(data); });
 
         // Log anything sent up through stderr. When a typical program
         // encounters an error, it writes its error message to stderr and
@@ -134,7 +130,7 @@ class LLLeapImpl: public LLLeap
         // interpreter behaves that way. More generally, though, a plugin
         // author can log whatever s/he wants to the viewer log using stderr.
         mStderrConnection = childerr.getPump()
-            .listen("LLLeap", boost::bind(&LLLeapImpl::rstderr, this, _1));
+            .listen("LLLeap", [this](const LLSD& data){ return rstderr(data); });
 
         // For our lifespan, intercept any LL_ERRS so we can notify plugin
         mRecorder = LLError::addGenericRecorder(
@@ -247,120 +243,120 @@ class LLLeapImpl: public LLLeap
         return false;
     }
 
-    // Initial state of stateful listening on child stdout: wait for a length
-    // prefix, followed by ':'.
-    bool rstdout(const LLSD& data)
+    // Stateful listening on child stdout:
+    // wait for a length prefix, followed by ':'.
+    bool rstdout(const LLSD&)
     {
         LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT));
-        // It's possible we got notified of a couple digit characters without
-        // seeing the ':' -- unlikely, but still. Until we see ':', keep
-        // waiting.
-        if (childout.contains(':'))
+        while (childout.size())
         {
-            std::istream& childstream(childout.get_istream());
-            // Saw ':', read length prefix and store in mExpect.
-            size_t expect;
-            childstream >> expect;
-            int colon(childstream.get());
-            if (colon != ':')
+            /*----------------- waiting for length prefix ------------------*/
+            if (mReadPrefix)
             {
-                // Protocol failure. Clear out the rest of the pending data in
-                // childout (well, up to a max length) to log what was wrong.
-                LLProcess::ReadPipe::size_type
-                    readlen((std::min)(childout.size(), LLProcess::ReadPipe::size_type(80)));
-                bad_protocol(STRINGIZE(expect << char(colon) << childout.read(readlen)));
+                // It's possible we got notified of a couple digit characters without
+                // seeing the ':' -- unlikely, but still. Until we see ':', keep
+                // waiting.
+                if (! childout.contains(':'))
+                {
+                    if (childout.contains('\n'))
+                    {
+                        // Since this is the initial listening state, this is where we'd
+                        // arrive if the child isn't following protocol at all -- say
+                        // because the user specified 'ls' or some darn thing.
+                        bad_protocol(childout.getline());
+                    }
+                    // Either way, stop looping.
+                    break;
+                }
+
+                // Saw ':', read length prefix and store in mExpect.
+                std::istream& childstream(childout.get_istream());
+                size_t expect;
+                childstream >> expect;
+                int colon(childstream.get());
+                if (colon != ':')
+                {
+                    // Protocol failure. Clear out the rest of the pending data in
+                    // childout (well, up to a max length) to log what was wrong.
+                    LLProcess::ReadPipe::size_type
+                        readlen((std::min)(childout.size(),
+                                           LLProcess::ReadPipe::size_type(80)));
+                    bad_protocol(stringize(expect, char(colon), childout.read(readlen)));
+                    break;
+                }
+                else
+                {
+                    // Saw length prefix, saw colon, life is good. Now wait for
+                    // that length of data to arrive.
+                    mExpect = expect;
+                    LL_DEBUGS("LLLeap") << "got length, waiting for "
+                                        << mExpect << " bytes of data" << LL_ENDL;
+                    // Transition to "read data" mode and loop back to check
+                    // if we've already received all the advertised data.
+                    mReadPrefix = false;
+                    continue;
+                }
             }
+            /*----------------- saw prefix, wait for data ------------------*/
             else
             {
-                // Saw length prefix, saw colon, life is good. Now wait for
-                // that length of data to arrive.
-                mExpect = expect;
-                LL_DEBUGS("LLLeap") << "got length, waiting for "
-                                    << mExpect << " bytes of data" << LL_ENDL;
-                // Block calls to this method; resetting mBlocker unblocks
-                // calls to the other method.
-                mBlocker.reset(new LLEventPump::Blocker(mStdoutConnection));
-                // Go check if we've already received all the advertised data.
-                if (childout.size())
+                // Until we've accumulated the promised length of data, keep waiting.
+                if (childout.size() < mExpect)
                 {
-                    LLSD updata(data);
-                    updata["len"] = LLSD::Integer(childout.size());
-                    rstdoutData(updata);
+                    break;
                 }
-            }
-        }
-        else if (childout.contains('\n'))
-        {
-            // Since this is the initial listening state, this is where we'd
-            // arrive if the child isn't following protocol at all -- say
-            // because the user specified 'ls' or some darn thing.
-            bad_protocol(childout.getline());
-        }
-        return false;
-    }
 
-    // State in which we listen on stdout for the specified length of data to
-    // arrive.
-    bool rstdoutData(const LLSD& data)
-    {
-        LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT));
-        // Until we've accumulated the promised length of data, keep waiting.
-        if (childout.size() >= mExpect)
-        {
-            // Ready to rock and roll.
-            LL_DEBUGS("LLLeap") << "needed " << mExpect << " bytes, got "
-                                << childout.size() << ", parsing LLSD" << LL_ENDL;
-            LLSD data;
+                // We have the data we were told to expect! Ready to rock and roll.
+                LL_DEBUGS("LLLeap") << "needed " << mExpect << " bytes, got "
+                                    << childout.size() << ", parsing LLSD" << LL_ENDL;
+                LLSD data;
 #if 1
-            // specifically require notation LLSD from child
-            LLPointer<LLSDParser> parser(new LLSDNotationParser());
-            S32 parse_status(parser->parse(childout.get_istream(), data, mExpect));
-            if (parse_status == LLSDParser::PARSE_FAILURE)
+                // specifically require notation LLSD from child
+                LLPointer<LLSDParser> parser(new LLSDNotationParser());
+                S32 parse_status(parser->parse(childout.get_istream(), data, mExpect));
+                if (parse_status == LLSDParser::PARSE_FAILURE)
 #else
-            // SL-18330: accept any valid LLSD serialization format from child
-            // Unfortunately this runs into trouble we have not yet debugged.
-            bool parse_status(LLSDSerialize::deserialize(data, childout.get_istream(), mExpect));
-            if (! parse_status)
+                // SL-18330: accept any valid LLSD serialization format from child
+                // Unfortunately this runs into trouble we have not yet debugged.
+                bool parse_status(LLSDSerialize::deserialize(data, childout.get_istream(), mExpect));
+                if (! parse_status)
 #endif
-            {
-                bad_protocol("unparseable LLSD data");
-            }
-            else if (! (data.isMap() && data["pump"].isString() && data.has("data")))
-            {
-                // we got an LLSD object, but it lacks required keys
-                bad_protocol("missing 'pump' or 'data'");
-            }
-            else
-            {
-                try
                 {
-                    // The LLSD object we got from our stream contains the
-                    // keys we need.
-                    LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
+                    bad_protocol("unparseable LLSD data");
+                    break;
                 }
-                catch (const std::exception& err)
+                else if (! (data.isMap() && data["pump"].isString() && data.has("data")))
                 {
-                    // No plugin should be allowed to crash the viewer by
-                    // driving an exception -- intentionally or not.
-                    LOG_UNHANDLED_EXCEPTION(stringize("handling request ", data));
-                    // Whether or not the plugin added a "reply" key to the
-                    // request, send a reply. We happen to know who originated
-                    // this request, and the reply LLEventPump of interest.
-                    // Not our problem if the plugin ignores the reply event.
-                    data["reply"] = mReplyPump.getName();
-                    sendReply(llsd::map("error",
-                                        stringize(LLError::Log::classname(err), ": ", err.what())),
-                              data);
+                    // we got an LLSD object, but it lacks required keys
+                    bad_protocol("missing 'pump' or 'data'");
+                    break;
                 }
-                // Block calls to this method; resetting mBlocker unblocks
-                // calls to the other method.
-                mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
-                // Go check for any more pending events in the buffer.
-                if (childout.size())
+                else
                 {
-                    LLSD updata(data);
-                    data["len"] = LLSD::Integer(childout.size());
-                    rstdout(updata);
+                    try
+                    {
+                        // The LLSD object we got from our stream contains the
+                        // keys we need.
+                        LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
+                    }
+                    catch (const std::exception& err)
+                    {
+                        // No plugin should be allowed to crash the viewer by
+                        // driving an exception -- intentionally or not.
+                        LOG_UNHANDLED_EXCEPTION(stringize("handling request ", data));
+                        // Whether or not the plugin added a "reply" key to the
+                        // request, send a reply. We happen to know who originated
+                        // this request, and the reply LLEventPump of interest.
+                        // Not our problem if the plugin ignores the reply event.
+                        data["reply"] = mReplyPump.getName();
+                        sendReply(llsd::map("error",
+                                            stringize(LLError::Log::classname(err), ": ", err.what())),
+                                  data);
+                    }
+                    // Transition to "read prefix" mode and go check for any
+                    // more pending events in the buffer.
+                    mReadPrefix = true;
+                    continue;
                 }
             }
         }
@@ -445,7 +441,8 @@ class LLLeapImpl: public LLLeap
         // child's stdin, suitably enriched with the pump name on which it was
         // received.
         return pump.listen(listener,
-                           boost::bind(&LLLeapImpl::wstdin, this, pump.getName(), _1));
+                           [this, name=pump.getName()](const LLSD& data)
+                           { return wstdin(name, data); });
     }
 
     std::string mDesc;
@@ -453,11 +450,11 @@ class LLLeapImpl: public LLLeap
     LLEventStream mReplyPump;
     LLProcessPtr mChild;
     LLTempBoundListener
-        mStdinConnection, mStdoutConnection, mStdoutDataConnection, mStderrConnection;
-    std::unique_ptr<LLEventPump::Blocker> mBlocker;
+        mStdinConnection, mStdoutConnection, mStderrConnection;
     LLProcess::ReadPipe::size_type mExpect;
     LLError::RecorderPtr mRecorder;
     std::unique_ptr<LLLeapListener> mListener;
+    bool mReadPrefix;
 };
 
 // These must follow the declaration of LLLeapImpl, so they may as well be last.
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index 2fe040f4247d45250f5b0545fbb60f54d8c43f12..5978ab132406efb8375646acb228820d51bdcf4b 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-7.1.8
+7.1.9