diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 78d6ea3090a72d8444beba16fb0644f74c797da0..ad6d3a5049e2097a94a997f2d4a6b0b9ac8315d9 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -119,8 +119,8 @@ set(llcommon_SOURCE_FILES
     lluriparser.cpp
     lluuid.cpp
     llworkerthread.cpp
+    timing.cpp
     u64.cpp
-    threadpool.cpp
     workqueue.cpp
     StackWalker.cpp
     )
@@ -256,7 +256,6 @@ set(llcommon_HEADER_FILES
     lockstatic.h
     stdtypes.h
     stringize.h
-    threadpool.h
     threadsafeschedule.h
     timer.h
     tuple.h
diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h
index 6042c0906cb2617e786cba97e9964665e49a5ef2..10a8ecfedb70be545bd68ce0f8df830005a59b5c 100644
--- a/indra/llcommon/llsingleton.h
+++ b/indra/llcommon/llsingleton.h
@@ -847,28 +847,22 @@ template<class T>
 class LLSimpleton
 {
 public:
-    template <typename... ARGS>
-    static void createInstance(ARGS&&... args)
-    {
+    static T* sInstance;
+    
+    static void createInstance() 
+    { 
         llassert(sInstance == nullptr);
-        sInstance = new T(std::forward<ARGS>(args)...);
+        sInstance = new T(); 
     }
-
+    
     static inline T* getInstance() { return sInstance; }
     static inline T& instance() { return *getInstance(); }
     static inline bool instanceExists() { return sInstance != nullptr; }
 
-    static void deleteSingleton()
-    {
-        delete sInstance;
-        sInstance = nullptr;
+    static void deleteSingleton() { 
+        delete sInstance; 
+        sInstance = nullptr; 
     }
-
-private:
-    static T* sInstance;
 };
 
-template <class T>
-T* LLSimpleton<T>::sInstance{ nullptr };
-
 #endif
diff --git a/indra/llcommon/llthreadsafequeue.h b/indra/llcommon/llthreadsafequeue.h
index 5c934791fe796d64fcec31d2c49246ec29b03065..06e8d8f6094d28417cc1710b121b7250f8bee24d 100644
--- a/indra/llcommon/llthreadsafequeue.h
+++ b/indra/llcommon/llthreadsafequeue.h
@@ -85,8 +85,8 @@ class LLThreadSafeQueue
 	LLThreadSafeQueue(U32 capacity = 1024);
 	virtual ~LLThreadSafeQueue() {}
 
-	// Add an element to the queue (will block if the queue has reached
-	// capacity).
+	// Add an element to the queue (will block if the queue has
+	// reached capacity).
 	//
 	// This call will raise an interrupt error if the queue is closed while
 	// the caller is blocked.
@@ -95,11 +95,6 @@ class LLThreadSafeQueue
 	// legacy name
 	void pushFront(ElementT const & element) { return push(element); }
 
-	// Add an element to the queue (will block if the queue has reached
-	// capacity). Return false if the queue is closed before push is possible.
-	template <typename T>
-	bool pushIfOpen(T&& element);
-
 	// Try to add an element to the queue without blocking. Returns
 	// true only if the element was actually added.
 	template <typename T>
@@ -316,8 +311,8 @@ bool LLThreadSafeQueue<ElementT, QueueT>::push_(lock_t& lock, T&& element)
 
 
 template <typename ElementT, typename QueueT>
-template <typename T>
-bool LLThreadSafeQueue<ElementT, QueueT>::pushIfOpen(T&& element)
+template<typename T>
+void LLThreadSafeQueue<ElementT, QueueT>::push(T&& element)
 {
     lock_t lock1(mLock);
     while (true)
@@ -326,10 +321,12 @@ bool LLThreadSafeQueue<ElementT, QueueT>::pushIfOpen(T&& element)
         // drained or not: the moment either end calls close(), further push()
         // operations will fail.
         if (mClosed)
-            return false;
+        {
+            LLTHROW(LLThreadSafeQueueInterrupt());
+        }
 
         if (push_(lock1, std::forward<T>(element)))
-            return true;
+            return;
 
         // Storage Full. Wait for signal.
         mCapacityCond.wait(lock1);
@@ -337,17 +334,6 @@ bool LLThreadSafeQueue<ElementT, QueueT>::pushIfOpen(T&& element)
 }
 
 
-template <typename ElementT, typename QueueT>
-template<typename T>
-void LLThreadSafeQueue<ElementT, QueueT>::push(T&& element)
-{
-    if (! pushIfOpen(std::forward<T>(element)))
-    {
-        LLTHROW(LLThreadSafeQueueInterrupt());
-    }
-}
-
-
 template<typename ElementT, typename QueueT>
 template<typename T>
 bool LLThreadSafeQueue<ElementT, QueueT>::tryPush(T&& element)
diff --git a/indra/llcommon/tests/threadsafeschedule_test.cpp b/indra/llcommon/tests/threadsafeschedule_test.cpp
index c421cc7b1c3634ba4b81511a1097d136a50bb166..af67b9f49247fdea378d7ef1c7ed4a713814dced 100644
--- a/indra/llcommon/tests/threadsafeschedule_test.cpp
+++ b/indra/llcommon/tests/threadsafeschedule_test.cpp
@@ -46,11 +46,11 @@ namespace tut
         // the real time required for each push() call. Explicitly increment
         // the timestamp for each one -- but since we're passing explicit
         // timestamps, make the queue reorder them.
-        queue.push(Queue::TimeTuple(Queue::Clock::now() + 200ms, "ghi"));
+        queue.push(Queue::TimeTuple(Queue::Clock::now() + 20ms, "ghi"));
         // Given the various push() overloads, you have to match the type
         // exactly: conversions are ambiguous.
         queue.push("abc"s);
-        queue.push(Queue::Clock::now() + 100ms, "def");
+        queue.push(Queue::Clock::now() + 10ms, "def");
         queue.close();
         auto entry = queue.pop();
         ensure_equals("failed to pop first", std::get<0>(entry), "abc"s);
diff --git a/indra/llcommon/tests/workqueue_test.cpp b/indra/llcommon/tests/workqueue_test.cpp
index bea3ad911b063d734d557dcc53d4b8286b63e269..d5405400fd6f693e3869c1c42cb369b2724cb3be 100644
--- a/indra/llcommon/tests/workqueue_test.cpp
+++ b/indra/llcommon/tests/workqueue_test.cpp
@@ -20,10 +20,7 @@
 // external library headers
 // other Linden headers
 #include "../test/lltut.h"
-#include "../test/catch_and_store_what_in.h"
 #include "llcond.h"
-#include "llcoros.h"
-#include "lleventcoro.h"
 #include "llstring.h"
 #include "stringize.h"
 
@@ -141,8 +138,7 @@ namespace tut
             [](){ return 17; },
             // Note that a postTo() *callback* can safely bind a reference to
             // a variable on the invoking thread, because the callback is run
-            // on the invoking thread. (Of course the bound variable must
-            // survive until the callback is called.)
+            // on the invoking thread.
             [&result](int i){ result = i; });
         // this should post the callback to main
         qptr->runOne();
@@ -160,70 +156,4 @@ namespace tut
         main.runPending();
         ensure_equals("failed to run string callback", alpha, "abc");
     }
-
-    template<> template<>
-    void object::test<5>()
-    {
-        set_test_name("postTo with void return");
-        WorkQueue main("main");
-        auto qptr = WorkQueue::getInstance("queue");
-        std::string observe;
-        main.postTo(
-            qptr,
-            // The ONLY reason we can get away with binding a reference to
-            // 'observe' in our work callable is because we're directly
-            // calling qptr->runOne() on this same thread. It would be a
-            // mistake to do that if some other thread were servicing 'queue'.
-            [&observe](){ observe = "queue"; },
-            [&observe](){ observe.append(";main"); });
-        qptr->runOne();
-        main.runOne();
-        ensure_equals("failed to run both lambdas", observe, "queue;main");
-    }
-
-    template<> template<>
-    void object::test<6>()
-    {
-        set_test_name("waitForResult");
-        std::string stored;
-        // Try to call waitForResult() on this thread's main coroutine. It
-        // should throw because the main coroutine must service the queue.
-        auto what{ catch_what<WorkQueue::Error>(
-                [this, &stored](){ stored = queue.waitForResult(
-                        [](){ return "should throw"; }); }) };
-        ensure("lambda should not have run", stored.empty());
-        ensure_not("waitForResult() should have thrown", what.empty());
-        ensure(STRINGIZE("should mention waitForResult: " << what),
-               what.find("waitForResult") != std::string::npos);
-
-        // Call waitForResult() on a coroutine, with a string result.
-        LLCoros::instance().launch(
-            "waitForResult string",
-            [this, &stored]()
-            { stored = queue.waitForResult(
-                    [](){ return "string result"; }); });
-        llcoro::suspend();
-        // Nothing will have happened yet because, even if the coroutine did
-        // run immediately, all it did was to queue the inner lambda on
-        // 'queue'. Service it.
-        queue.runOne();
-        llcoro::suspend();
-        ensure_equals("bad waitForResult return", stored, "string result");
-
-        // Call waitForResult() on a coroutine, with a void callable.
-        stored.clear();
-        bool done = false;
-        LLCoros::instance().launch(
-            "waitForResult void",
-            [this, &stored, &done]()
-            {
-                queue.waitForResult([&stored](){ stored = "ran"; });
-                done = true;
-            });
-        llcoro::suspend();
-        queue.runOne();
-        llcoro::suspend();
-        ensure_equals("didn't run coroutine", stored, "ran");
-        ensure("void waitForResult() didn't return", done);
-    }
 } // namespace tut
diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp
deleted file mode 100644
index cf25cc838e1ce55327cbd81b110473d1e2a1b7ef..0000000000000000000000000000000000000000
--- a/indra/llcommon/threadpool.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * @file   threadpool.cpp
- * @author Nat Goodspeed
- * @date   2021-10-21
- * @brief  Implementation for threadpool.
- * 
- * $LicenseInfo:firstyear=2021&license=viewerlgpl$
- * Copyright (c) 2021, Linden Research, Inc.
- * $/LicenseInfo$
- */
-
-// Precompiled header
-#include "linden_common.h"
-// associated header
-#include "threadpool.h"
-// STL headers
-// std headers
-// external library headers
-// other Linden headers
-#include "llerror.h"
-#include "llevents.h"
-#include "stringize.h"
-
-LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity):
-    mQueue(name, capacity),
-    mName("ThreadPool:" + name)
-{
-    for (size_t i = 0; i < threads; ++i)
-    {
-        std::string tname{ STRINGIZE(mName << ':' << (i+1) << '/' << threads) };
-        mThreads.emplace_back(tname, [this, tname](){ run(tname); });
-    }
-    // Listen on "LLApp", and when the app is shutting down, close the queue
-    // and join the workers.
-    LLEventPumps::instance().obtain("LLApp").listen(
-        mName,
-        [this](const LLSD& stat)
-        {
-            std::string status(stat["status"]);
-            if (status != "running")
-            {
-                // viewer is starting shutdown -- proclaim the end is nigh!
-                LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL;
-                close();
-            }
-            return false;
-        });
-}
-
-LL::ThreadPool::~ThreadPool()
-{
-    close();
-}
-
-void LL::ThreadPool::close()
-{
-    if (! mQueue.isClosed())
-    {
-        LL_DEBUGS("ThreadPool") << mName << " closing queue and joining threads" << LL_ENDL;
-        mQueue.close();
-        for (auto& pair: mThreads)
-        {
-            LL_DEBUGS("ThreadPool") << mName << " waiting on thread " << pair.first << LL_ENDL;
-            pair.second.join();
-        }
-        LL_DEBUGS("ThreadPool") << mName << " shutdown complete" << LL_ENDL;
-    }
-}
-
-void LL::ThreadPool::run(const std::string& name)
-{
-    LL_DEBUGS("ThreadPool") << name << " starting" << LL_ENDL;
-    run();
-    LL_DEBUGS("ThreadPool") << name << " stopping" << LL_ENDL;
-}
-
-void LL::ThreadPool::run()
-{
-    mQueue.runUntilClose();
-}
diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h
deleted file mode 100644
index 1ca24aec58e9e7052fc5bdedc0ebdeaecd6e3de7..0000000000000000000000000000000000000000
--- a/indra/llcommon/threadpool.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * @file   threadpool.h
- * @author Nat Goodspeed
- * @date   2021-10-21
- * @brief  ThreadPool configures a WorkQueue along with a pool of threads to
- *         service it.
- * 
- * $LicenseInfo:firstyear=2021&license=viewerlgpl$
- * Copyright (c) 2021, Linden Research, Inc.
- * $/LicenseInfo$
- */
-
-#if ! defined(LL_THREADPOOL_H)
-#define LL_THREADPOOL_H
-
-#include "workqueue.h"
-#include <string>
-#include <thread>
-#include <utility>                  // std::pair
-#include <vector>
-
-namespace LL
-{
-
-    class ThreadPool
-    {
-    public:
-        /**
-         * Pass ThreadPool a string name. This can be used to look up the
-         * relevant WorkQueue.
-         */
-        ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024);
-        virtual ~ThreadPool();
-
-        /**
-         * ThreadPool listens for application shutdown messages on the "LLApp"
-         * LLEventPump. Call close() to shut down this ThreadPool early.
-         */
-        void close();
-
-        std::string getName() const { return mName; }
-        size_t getWidth() const { return mThreads.size(); }
-        /// obtain a non-const reference to the WorkQueue to post work to it
-        WorkQueue& getQueue() { return mQueue; }
-
-        /**
-         * Override run() if you need special processing. The default run()
-         * implementation simply calls WorkQueue::runUntilClose().
-         */
-        virtual void run();
-
-    private:
-        void run(const std::string& name);
-
-        WorkQueue mQueue;
-        std::string mName;
-        std::vector<std::pair<std::string, std::thread>> mThreads;
-    };
-
-} // namespace LL
-
-#endif /* ! defined(LL_THREADPOOL_H) */
diff --git a/indra/llcommon/timing.cpp b/indra/llcommon/timing.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c2dc695ef33304af0abd3df0c6fc78c0f2cb5a92
--- /dev/null
+++ b/indra/llcommon/timing.cpp
@@ -0,0 +1,25 @@
+/** 
+ * @file timing.cpp
+ * @brief This file will be deprecated in the future.
+ *
+ * $LicenseInfo:firstyear=2000&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp
index 633594ceea780b763f9e037a4ca34a18b84e2244..b32357e832ee997939a0a96982277ae42d96534e 100644
--- a/indra/llcommon/workqueue.cpp
+++ b/indra/llcommon/workqueue.cpp
@@ -26,9 +26,8 @@
 using Mutex = LLCoros::Mutex;
 using Lock  = LLCoros::LockType;
 
-LL::WorkQueue::WorkQueue(const std::string& name, size_t capacity):
-    super(makeName(name)),
-    mQueue(capacity)
+LL::WorkQueue::WorkQueue(const std::string& name):
+    super(makeName(name))
 {
     // TODO: register for "LLApp" events so we can implicitly close() on
     // viewer shutdown.
@@ -39,21 +38,6 @@ void LL::WorkQueue::close()
     mQueue.close();
 }
 
-size_t LL::WorkQueue::size()
-{
-    return mQueue.size();
-}
-
-bool LL::WorkQueue::isClosed()
-{
-    return mQueue.isClosed();
-}
-
-bool LL::WorkQueue::done()
-{
-    return mQueue.done();
-}
-
 void LL::WorkQueue::runUntilClose()
 {
     try
@@ -144,13 +128,3 @@ void LL::WorkQueue::error(const std::string& msg)
 {
     LL_ERRS("WorkQueue") << msg << LL_ENDL;
 }
-
-void LL::WorkQueue::checkCoroutine(const std::string& method)
-{
-    // By convention, the default coroutine on each thread has an empty name
-    // string. See also LLCoros::logname().
-    if (LLCoros::getName().empty())
-    {
-        LLTHROW(Error("Do not call " + method + " from a thread's default coroutine"));
-    }
-}
diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h
index c25d78742544980203b357cdd9b19728fc0a5237..5ec790da799b1020a7ec82ec1ddf9e88d034aebd 100644
--- a/indra/llcommon/workqueue.h
+++ b/indra/llcommon/workqueue.h
@@ -12,14 +12,14 @@
 #if ! defined(LL_WORKQUEUE_H)
 #define LL_WORKQUEUE_H
 
-#include "llcoros.h"
-#include "llexception.h"
 #include "llinstancetracker.h"
 #include "threadsafeschedule.h"
 #include <chrono>
-#include <exception>                // std::current_exception
 #include <functional>               // std::function
+#include <queue>
 #include <string>
+#include <utility>                  // std::pair
+#include <vector>
 
 namespace LL
 {
@@ -45,16 +45,11 @@ namespace LL
         using TimedWork = Queue::TimeTuple;
         using Closed    = Queue::Closed;
 
-        struct Error: public LLException
-        {
-            Error(const std::string& what): LLException(what) {}
-        };
-
         /**
          * You may omit the WorkQueue name, in which case a unique name is
          * synthesized; for practical purposes that makes it anonymous.
          */
-        WorkQueue(const std::string& name = std::string(), size_t capacity=1024);
+        WorkQueue(const std::string& name = std::string());
 
         /**
          * Since the point of WorkQueue is to pass work to some other worker
@@ -64,36 +59,15 @@ namespace LL
          */
         void close();
 
-        /**
-         * WorkQueue supports multiple producers and multiple consumers. In
-         * the general case it's misleading to test size(), since any other
-         * thread might change it the nanosecond the lock is released. On that
-         * basis, some might argue against publishing a size() method at all.
-         *
-         * But there are two specific cases in which a test based on size()
-         * might be reasonable:
-         *
-         * * If you're the only producer, noticing that size() == 0 is
-         *   meaningful.
-         * * If you're the only consumer, noticing that size() > 0 is
-         *   meaningful.
-         */
-        size_t size();
-        /// producer end: are we prevented from pushing any additional items?
-        bool isClosed();
-        /// consumer end: are we done, is the queue entirely drained?
-        bool done();
-
         /*---------------------- fire and forget API -----------------------*/
 
         /// fire-and-forget, but at a particular (future?) time
         template <typename CALLABLE>
         void post(const TimePoint& time, CALLABLE&& callable)
         {
-            // Defer reifying an arbitrary CALLABLE until we hit this or
-            // postIfOpen(). All other methods should accept CALLABLEs of
-            // arbitrary type to avoid multiple levels of std::function
-            // indirection.
+            // Defer reifying an arbitrary CALLABLE until we hit this method.
+            // All other methods should accept CALLABLEs of arbitrary type to
+            // avoid multiple levels of std::function indirection.
             mQueue.push(TimedWork(time, std::move(callable)));
         }
 
@@ -108,47 +82,6 @@ namespace LL
             post(TimePoint::clock::now(), std::move(callable));
         }
 
-        /**
-         * post work for a particular time, unless the queue is closed before
-         * we can post
-         */
-        template <typename CALLABLE>
-        bool postIfOpen(const TimePoint& time, CALLABLE&& callable)
-        {
-            // Defer reifying an arbitrary CALLABLE until we hit this or
-            // post(). All other methods should accept CALLABLEs of arbitrary
-            // type to avoid multiple levels of std::function indirection.
-            return mQueue.pushIfOpen(TimedWork(time, std::move(callable)));
-        }
-
-        /**
-         * post work, unless the queue is closed before we can post
-         */
-        template <typename CALLABLE>
-        bool postIfOpen(CALLABLE&& callable)
-        {
-            return postIfOpen(TimePoint::clock::now(), std::move(callable));
-        }
-
-        /**
-         * Post work to be run at a specified time to another WorkQueue, which
-         * may or may not still exist and be open. Return true if we were able
-         * to post.
-         */
-        template <typename CALLABLE>
-        static bool postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable);
-
-        /**
-         * Post work to another WorkQueue, which may or may not still exist
-         * and be open. Return true if we were able to post.
-         */
-        template <typename CALLABLE>
-        static bool postMaybe(weak_t target, CALLABLE&& callable)
-        {
-            return postMaybe(target, TimePoint::clock::now(),
-                             std::forward<CALLABLE>(callable));
-        }
-
         /**
          * Launch a callable returning bool that will trigger repeatedly at
          * specified interval, until the callable returns false.
@@ -182,8 +115,63 @@ namespace LL
         // Studio compile errors that seem utterly unrelated to this source
         // code.
         template <typename CALLABLE, typename FOLLOWUP>
-        bool postTo(weak_t target,
-                    const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback);
+        bool postTo(WorkQueue::weak_t target,
+                    const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback)
+        {
+            // We're being asked to post to the WorkQueue at target.
+            // target is a weak_ptr: have to lock it to check it.
+            auto tptr = target.lock();
+            if (! tptr)
+                // can't post() if the target WorkQueue has been destroyed
+                return false;
+
+            // Here we believe target WorkQueue still exists. Post to it a
+            // lambda that packages our callable, our callback and a weak_ptr
+            // to this originating WorkQueue.
+            tptr->post(
+                time,
+                [reply = super::getWeak(),
+                 callable = std::move(callable),
+                 callback = std::move(callback)]
+                ()
+                {
+                    // Call the callable in any case -- but to minimize
+                    // copying the result, immediately bind it into a reply
+                    // lambda. The reply lambda also binds the original
+                    // callback, so that when we, the originating WorkQueue,
+                    // finally receive and process the reply lambda, we'll
+                    // call the bound callback with the bound result -- on the
+                    // same thread that originally called postTo().
+                    auto rlambda =
+                        [result = callable(),
+                         callback = std::move(callback)]
+                        ()
+                        { callback(std::move(result)); };
+                    // Check if this originating WorkQueue still exists.
+                    // Remember, the outer lambda is now running on a thread
+                    // servicing the target WorkQueue, and real time has
+                    // elapsed since postTo()'s tptr->post() call.
+                    // reply is a weak_ptr: have to lock it to check it.
+                    auto rptr = reply.lock();
+                    if (rptr)
+                    {
+                        // Only post reply lambda if the originating WorkQueue
+                        // still exists. If not -- who would we tell? Log it?
+                        try
+                        {
+                            rptr->post(std::move(rlambda));
+                        }
+                        catch (const Closed&)
+                        {
+                            // Originating WorkQueue might still exist, but
+                            // might be Closed. Same thing: just discard the
+                            // callback.
+                        }
+                    }
+                });
+            // looks like we were able to post()
+            return true;
+        }
 
         /**
          * Post work to another WorkQueue, requesting a specific callback to
@@ -193,36 +181,10 @@ namespace LL
          * inaccessible.
          */
         template <typename CALLABLE, typename FOLLOWUP>
-        bool postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback)
+        bool postTo(WorkQueue::weak_t target,
+                    CALLABLE&& callable, FOLLOWUP&& callback)
         {
-            return postTo(target, TimePoint::clock::now(),
-                          std::move(callable), std::move(callback));
-        }
-
-        /**
-         * Post work to another WorkQueue to be run at a specified time,
-         * blocking the calling coroutine until then, returning the result to
-         * caller on completion.
-         *
-         * In general, we assume that each thread's default coroutine is busy
-         * servicing its WorkQueue or whatever. To try to prevent mistakes, we
-         * forbid calling waitForResult() from a thread's default coroutine.
-         */
-        template <typename CALLABLE>
-        auto waitForResult(const TimePoint& time, CALLABLE&& callable);
-
-        /**
-         * Post work to another WorkQueue, blocking the calling coroutine
-         * until then, returning the result to caller on completion.
-         *
-         * In general, we assume that each thread's default coroutine is busy
-         * servicing its WorkQueue or whatever. To try to prevent mistakes, we
-         * forbid calling waitForResult() from a thread's default coroutine.
-         */
-        template <typename CALLABLE>
-        auto waitForResult(CALLABLE&& callable)
-        {
-            return waitForResult(TimePoint::clock::now(), std::move(callable));
+            return postTo(target, TimePoint::clock::now(), std::move(callable), std::move(callback));
         }
 
         /*--------------------------- worker API ---------------------------*/
@@ -270,23 +232,6 @@ namespace LL
         bool runUntil(const TimePoint& until);
 
     private:
-        template <typename CALLABLE, typename FOLLOWUP>
-        static auto makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback);
-        /// general case: arbitrary C++ return type
-        template <typename CALLABLE, typename FOLLOWUP, typename RETURNTYPE>
-        struct MakeReplyLambda;
-        /// specialize for CALLABLE returning void
-        template <typename CALLABLE, typename FOLLOWUP>
-        struct MakeReplyLambda<CALLABLE, FOLLOWUP, void>;
-
-        /// general case: arbitrary C++ return type
-        template <typename CALLABLE, typename RETURNTYPE>
-        struct WaitForResult;
-        /// specialize for CALLABLE returning void
-        template <typename CALLABLE>
-        struct WaitForResult<CALLABLE, void>;
-
-        static void checkCoroutine(const std::string& method);
         static void error(const std::string& msg);
         static std::string makeName(const std::string& name);
         void callWork(const Queue::DataTuple& work);
@@ -308,8 +253,8 @@ namespace LL
     {
     public:
         // bind the desired data
-        BackJack(weak_t target,
-                 const TimePoint& start,
+        BackJack(WorkQueue::weak_t target,
+                 const WorkQueue::TimePoint& start,
                  const std::chrono::duration<Rep, Period>& interval,
                  CALLABLE&& callable):
             mTarget(target),
@@ -356,8 +301,8 @@ namespace LL
         }
 
     private:
-        weak_t mTarget;
-        TimePoint mStart;
+        WorkQueue::weak_t mTarget;
+        WorkQueue::TimePoint mStart;
         std::chrono::duration<Rep, Period> mInterval;
         CALLABLE mCallable;
     };
@@ -385,187 +330,6 @@ namespace LL
                  getWeak(), TimePoint::clock::now(), interval, std::move(callable)));
     }
 
-    /// general case: arbitrary C++ return type
-    template <typename CALLABLE, typename FOLLOWUP, typename RETURNTYPE>
-    struct WorkQueue::MakeReplyLambda
-    {
-        auto operator()(CALLABLE&& callable, FOLLOWUP&& callback)
-        {
-            // Call the callable in any case -- but to minimize
-            // copying the result, immediately bind it into the reply
-            // lambda. The reply lambda also binds the original
-            // callback, so that when we, the originating WorkQueue,
-            // finally receive and process the reply lambda, we'll
-            // call the bound callback with the bound result -- on the
-            // same thread that originally called postTo().
-            return
-                [result = std::forward<CALLABLE>(callable)(),
-                 callback = std::move(callback)]
-                ()
-                { callback(std::move(result)); };
-        }
-    };
-
-    /// specialize for CALLABLE returning void
-    template <typename CALLABLE, typename FOLLOWUP>
-    struct WorkQueue::MakeReplyLambda<CALLABLE, FOLLOWUP, void>
-    {
-        auto operator()(CALLABLE&& callable, FOLLOWUP&& callback)
-        {
-            // Call the callable, which produces no result.
-            std::forward<CALLABLE>(callable)();
-            // Our completion callback is simply the caller's callback.
-            return std::move(callback);
-        }
-    };
-
-    template <typename CALLABLE, typename FOLLOWUP>
-    auto WorkQueue::makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback)
-    {
-        return MakeReplyLambda<CALLABLE, FOLLOWUP,
-                               decltype(std::forward<CALLABLE>(callable)())>()
-            (std::move(callable), std::move(callback));
-    }
-
-    template <typename CALLABLE, typename FOLLOWUP>
-    bool WorkQueue::postTo(weak_t target,
-                           const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback)
-    {
-        // We're being asked to post to the WorkQueue at target.
-        // target is a weak_ptr: have to lock it to check it.
-        auto tptr = target.lock();
-        if (! tptr)
-            // can't post() if the target WorkQueue has been destroyed
-            return false;
-
-        // Here we believe target WorkQueue still exists. Post to it a
-        // lambda that packages our callable, our callback and a weak_ptr
-        // to this originating WorkQueue.
-        tptr->post(
-            time,
-            [reply = super::getWeak(),
-             callable = std::move(callable),
-             callback = std::move(callback)]
-            ()
-            {
-                // Use postMaybe() below in case this originating WorkQueue
-                // has been closed or destroyed. Remember, the outer lambda is
-                // now running on a thread servicing the target WorkQueue, and
-                // real time has elapsed since postTo()'s tptr->post() call.
-                try
-                {
-                    // Make a reply lambda to repost to THIS WorkQueue.
-                    // Delegate to makeReplyLambda() so we can partially
-                    // specialize on void return.
-                    postMaybe(reply, makeReplyLambda(std::move(callable), std::move(callback)));
-                }
-                catch (...)
-                {
-                    // Either variant of makeReplyLambda() is responsible for
-                    // calling the caller's callable. If that throws, return
-                    // the exception to the originating thread.
-                    postMaybe(
-                        reply,
-                        // Bind the current exception to transport back to the
-                        // originating WorkQueue. Once there, rethrow it.
-                        [exc = std::current_exception()](){ std::rethrow_exception(exc); });
-                }
-            });
-
-        // looks like we were able to post()
-        return true;
-    }
-
-    template <typename CALLABLE>
-    bool WorkQueue::postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable)
-    {
-        // target is a weak_ptr: have to lock it to check it
-        auto tptr = target.lock();
-        if (tptr)
-        {
-            try
-            {
-                tptr->post(time, std::forward<CALLABLE>(callable));
-                // we were able to post()
-                return true;
-            }
-            catch (const Closed&)
-            {
-                // target WorkQueue still exists, but is Closed
-            }
-        }
-        // either target no longer exists, or its WorkQueue is Closed
-        return false;
-    }
-
-    /// general case: arbitrary C++ return type
-    template <typename CALLABLE, typename RETURNTYPE>
-    struct WorkQueue::WaitForResult
-    {
-        auto operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable)
-        {
-            LLCoros::Promise<RETURNTYPE> promise;
-            self->post(
-                time,
-                // We dare to bind a reference to Promise because it's
-                // specifically designed for cross-thread communication.
-                [&promise, callable = std::move(callable)]()
-                {
-                    try
-                    {
-                        // call the caller's callable and trigger promise with result
-                        promise.set_value(callable());
-                    }
-                    catch (...)
-                    {
-                        promise.set_exception(std::current_exception());
-                    }
-                });
-            auto future{ LLCoros::getFuture(promise) };
-            // now, on the calling thread, wait for that result
-            LLCoros::TempStatus st("waiting for WorkQueue::waitForResult()");
-            return future.get();
-        }
-    };
-
-    /// specialize for CALLABLE returning void
-    template <typename CALLABLE>
-    struct WorkQueue::WaitForResult<CALLABLE, void>
-    {
-        void operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable)
-        {
-            LLCoros::Promise<void> promise;
-            self->post(
-                time,
-                // &promise is designed for cross-thread access
-                [&promise, callable = std::move(callable)]()
-                {
-                    try
-                    {
-                        callable();
-                        promise.set_value();
-                    }
-                    catch (...)
-                    {
-                        promise.set_exception(std::current_exception());
-                    }
-                });
-            auto future{ LLCoros::getFuture(promise) };
-            // block until set_value()
-            LLCoros::TempStatus st("waiting for void WorkQueue::waitForResult()");
-            future.get();
-        }
-    };
-
-    template <typename CALLABLE>
-    auto WorkQueue::waitForResult(const TimePoint& time, CALLABLE&& callable)
-    {
-        checkCoroutine("waitForResult()");
-        // derive callable's return type so we can specialize for void
-        return WaitForResult<CALLABLE, decltype(std::forward<CALLABLE>(callable)())>()
-            (this, time, std::forward<CALLABLE>(callable));
-    }
-
 } // namespace LL
 
 #endif /* ! defined(LL_WORKQUEUE_H) */
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index 1b6920fe3b6cb1f9eca3cec1a0407a1db1d46ee8..cbc53928824f0daefd953039c2ce49de4d4db25b 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -172,19 +172,31 @@ BOOL is_little_endian()
 	return (*c == 0x78) ;
 }
 
+LLImageGLThread* LLImageGLThread::sInstance = nullptr;
+
 //static 
 void LLImageGL::initClass(LLWindow* window, S32 num_catagories, BOOL skip_analyze_alpha /* = false */)
 {
     LL_PROFILE_ZONE_SCOPED;
 	sSkipAnalyzeAlpha = skip_analyze_alpha;
-    LLImageGLThread::createInstance(window);
+    LLImageGLThread::sInstance = new LLImageGLThread(window);
+    LLImageGLThread::sInstance->start();
+}
+
+//static
+void LLImageGL::updateClass()
+{
+    LL_PROFILE_ZONE_SCOPED;
+    LLImageGLThread::sInstance->executeCallbacks();
 }
 
 //static 
 void LLImageGL::cleanupClass() 
 {
     LL_PROFILE_ZONE_SCOPED;
-    LLImageGLThread::deleteSingleton();
+    LLImageGLThread::sInstance->mFunctionQueue.close();
+    delete LLImageGLThread::sInstance;
+    LLImageGLThread::sInstance = nullptr;
 }
 
 //static
@@ -492,9 +504,6 @@ void LLImageGL::init(BOOL usemipmaps)
 #endif
 
 	mCategory = -1;
-
-	// Sometimes we have to post work for the main thread.
-	mMainQueue = LL::WorkQueue::getInstance("mainloop");
 }
 
 void LLImageGL::cleanup()
@@ -1527,7 +1536,8 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
     }
 
     //if we're on the image loading thread, be sure to delete old_texname and update mTexName on the main thread
-    if (! on_main_thread())
+    if (LLImageGLThread::sInstance != nullptr && 
+        LLThread::currentID() == LLImageGLThread::sInstance->getID())
     {
         {
             LL_PROFILE_ZONE_NAMED("cglt - sync");
@@ -1544,9 +1554,7 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
         }
 
         ref();
-        LL::WorkQueue::postMaybe(
-            mMainQueue,
-            [=]()
+        LLImageGLThread::sInstance->postCallback([=]()
             {
                 LL_PROFILE_ZONE_NAMED("cglt - delete callback");
                 if (old_texname != 0)
@@ -2251,24 +2259,73 @@ void LLImageGL::resetCurTexSizebar()
 */  
 
 LLImageGLThread::LLImageGLThread(LLWindow* window)
-    // We want exactly one thread, but a very large capacity: we never want
-    // anyone, especially inner-loop render code, to have to block on post()
-    // because we're full.
-    : ThreadPool("LLImageGL", 1, 1024*1024)
-    , mWindow(window)
+    : LLThread("LLImageGL"), mWindow(window)
 {
     mFinished = false;
 
     mContext = mWindow->createSharedContext();
 }
 
+// post a function to be executed on the LLImageGL background thread
+
+bool LLImageGLThread::post(const std::function<void()>& func)
+{
+    try
+    {
+        mFunctionQueue.post(func);
+    }
+    catch (LLThreadSafeQueueInterrupt e)
+    {
+        return false;
+    }
+
+    return true;
+}
+
+//post a callback to be executed on the main thread
+
+bool LLImageGLThread::postCallback(const std::function<void()>& callback)
+{
+    try
+    {
+        if (!mCallbackQueue.tryPost(callback))
+        {
+            mPendingCallbackQ.push(callback);
+        }
+    }
+    catch (LLThreadSafeQueueInterrupt e)
+    {
+        //thread is closing, drop request
+        return false;
+    }
+
+    return true;
+}
+
+void LLImageGLThread::executeCallbacks()
+{
+    LL_PROFILE_ZONE_SCOPED;
+    //executed from main thread
+    mCallbackQueue.runPending();
+
+    while (!mPendingCallbackQ.empty())
+    {
+        if (mCallbackQueue.tryPost(mPendingCallbackQ.front()))
+        {
+            mPendingCallbackQ.pop();
+        }
+        else
+        {
+            break;
+        }
+    }
+}
+
 void LLImageGLThread::run()
 {
-    // We must perform setup on this thread before actually servicing our
-    // WorkQueue, likewise cleanup afterwards.
     mWindow->makeContextCurrent(mContext);
     gGL.init();
-    ThreadPool::run();
+    mFunctionQueue.runUntilClose();
     gGL.shutdown();
     mWindow->destroySharedContext(mContext);
 }
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index 27496def1d62b184731a2cd9312ca78e71a9c197..8264e4a5f214ae0055b4c5ca304217aeffc824ce 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -37,7 +37,6 @@
 #include "llunits.h"
 #include "llthreadsafequeue.h"
 #include "llrender.h"
-#include "threadpool.h"
 #include "workqueue.h"
 
 class LLTextureAtlas ;
@@ -199,7 +198,6 @@ class LLImageGL : public LLRefCount
 	void freePickMask();
 
 	LLPointer<LLImageRaw> mSaveData; // used for destroyGL/restoreGL
-	LL::WorkQueue::weak_t mMainQueue;
 	U8* mPickMask;  //downsampled bitmap approximation of alpha channel.  NULL if no alpha channel
 	U16 mPickMaskWidth;
 	U16 mPickMaskHeight;
@@ -273,6 +271,7 @@ class LLImageGL : public LLRefCount
 
 public:
 	static void initClass(LLWindow* window, S32 num_catagories, BOOL skip_analyze_alpha = false); 
+    static void updateClass();
 	static void cleanupClass() ;
 
 private:
@@ -308,24 +307,34 @@ class LLImageGL : public LLRefCount
 
 };
 
-class LLImageGLThread : public LLSimpleton<LLImageGLThread>, LL::ThreadPool
+class LLImageGLThread : public LLThread
 {
 public:
     LLImageGLThread(LLWindow* window);
 
     // post a function to be executed on the LLImageGL background thread
-    template <typename CALLABLE>
-    bool post(CALLABLE&& func)
-    {
-        return getQueue().postIfOpen(std::forward<CALLABLE>(func));
-    }
+    bool post(const std::function<void()>& func);
+
+    //post a callback to be executed on the main thread
+    bool postCallback(const std::function<void()>& callback);
+
+    void executeCallbacks();
 
     void run() override;
 
-private:
+    // Work Queue for background thread
+    LL::WorkQueue mFunctionQueue;
+
+    // Work Queue for main thread (run from updateClass)
+    LL::WorkQueue mCallbackQueue;
+
     LLWindow* mWindow;
     void* mContext;
     LLAtomicBool mFinished;
+
+    std::queue<std::function<void()>> mPendingCallbackQ;
+
+    static LLImageGLThread* sInstance;
 };
 
 
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 55c1655d7beb2d0b2992b6461bcfc91893c08c97..f781ff4110824663e105dcc8c8088b4c89cd8366 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -308,10 +308,6 @@ if(LL_TESTS)
       ${BOOST_FIBER_LIBRARY} ${BOOST_CONTEXT_LIBRARY} ${BOOST_SYSTEM_LIBRARY}
       ${WINDOWS_LIBRARIES})
   if(NOT LINUX)
-    if(WINDOWS)
-      LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "imm32;${test_libs}")
-    else(WINDOWS)
-      LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "${test_libs}")
-    endif(WINDOWS)
+    LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "${test_libs}")
   endif(NOT LINUX)
 endif(LL_TESTS)
diff --git a/indra/llui/llviewereventrecorder.cpp b/indra/llui/llviewereventrecorder.cpp
index cb000aef74fd8166189431f35a18e0231c69e0bf..5a44ec947a0acd4a3e9c74c8b526b49f31e56ba0 100644
--- a/indra/llui/llviewereventrecorder.cpp
+++ b/indra/llui/llviewereventrecorder.cpp
@@ -28,6 +28,8 @@
 #include "llui.h"
 #include "llleap.h"
 
+LLViewerEventRecorder* LLSimpleton<LLViewerEventRecorder>::sInstance = nullptr;
+
 LLViewerEventRecorder::LLViewerEventRecorder() {
 
   clear(UNDEFINED);
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index 3f3dd43dafb06e544ad2cebcbbfe169f9334fc26..e52624d66aa35d660ba1681e24cf01d4b99ddaa4 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -55,8 +55,6 @@
 #include <shellapi.h>
 #include <fstream>
 #include <Imm.h>
-#include <future>
-#include <utility>                  // std::pair
 
 // Require DirectInput version 8
 #define DIRECTINPUT_VERSION 0x0800
@@ -176,19 +174,23 @@ DWORD	LLWindowWin32::sWinIMESentenceMode = IME_SMODE_AUTOMATIC;
 LLCoordWindow LLWindowWin32::sWinIMEWindowPosition(-1,-1);
 
 // The following class LLWinImm delegates Windows IMM APIs.
-// It was originally introduced to support US Windows XP, on which we needed
-// to dynamically load IMM32.DLL and use GetProcAddress to resolve its entry
-// points. Now that that's moot, we retain this wrapper only for hooks for
-// metrics.
+// We need this because some language versions of Windows,
+// e.g., US version of Windows XP, doesn't install IMM32.DLL
+// as a default, and we can't link against imm32.lib statically.
+// I believe DLL loading of this type is best suited to do
+// in a static initialization of a class.  What I'm not sure is
+// whether it follows the Linden Conding Standard... 
+// See http://wiki.secondlife.com/wiki/Coding_standards#Static_Members
 
 class LLWinImm
 {
 public:
-	static bool		isAvailable() { return true; }
+	static bool		isAvailable() { return sTheInstance.mHImmDll != NULL; }
 
 public:
 	// Wrappers for IMM API.
 	static BOOL		isIME(HKL hkl);															
+	static HWND		getDefaultIMEWnd(HWND hwnd);
 	static HIMC		getContext(HWND hwnd);													
 	static BOOL		releaseContext(HWND hwnd, HIMC himc);
 	static BOOL		getOpenStatus(HIMC himc);												
@@ -202,96 +204,236 @@ class LLWinImm
 	static BOOL		setCompositionFont(HIMC himc, LPLOGFONTW logfont);
 	static BOOL		setCandidateWindow(HIMC himc, LPCANDIDATEFORM candidate_form);
 	static BOOL		notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value);
+
+private:
+	LLWinImm();
+	~LLWinImm();
+
+private:
+	// Pointers to IMM API.
+	BOOL	 	(WINAPI *mImmIsIME)(HKL);
+	HWND		(WINAPI *mImmGetDefaultIMEWnd)(HWND);
+	HIMC		(WINAPI *mImmGetContext)(HWND);
+	BOOL		(WINAPI *mImmReleaseContext)(HWND, HIMC);
+	BOOL		(WINAPI *mImmGetOpenStatus)(HIMC);
+	BOOL		(WINAPI *mImmSetOpenStatus)(HIMC, BOOL);
+	BOOL		(WINAPI *mImmGetConversionStatus)(HIMC, LPDWORD, LPDWORD);
+	BOOL		(WINAPI *mImmSetConversionStatus)(HIMC, DWORD, DWORD);
+	BOOL		(WINAPI *mImmGetCompostitionWindow)(HIMC, LPCOMPOSITIONFORM);
+	BOOL		(WINAPI *mImmSetCompostitionWindow)(HIMC, LPCOMPOSITIONFORM);
+	LONG		(WINAPI *mImmGetCompositionString)(HIMC, DWORD, LPVOID, DWORD);
+	BOOL		(WINAPI *mImmSetCompositionString)(HIMC, DWORD, LPVOID, DWORD, LPVOID, DWORD);
+	BOOL		(WINAPI *mImmSetCompositionFont)(HIMC, LPLOGFONTW);
+	BOOL		(WINAPI *mImmSetCandidateWindow)(HIMC, LPCANDIDATEFORM);
+	BOOL		(WINAPI *mImmNotifyIME)(HIMC, DWORD, DWORD, DWORD);
+
+private:
+	HMODULE		mHImmDll;
+	static LLWinImm sTheInstance;
 };
 
+LLWinImm LLWinImm::sTheInstance;
+
+LLWinImm::LLWinImm() : mHImmDll(NULL)
+{
+	// Check system metrics 
+	if ( !GetSystemMetrics( SM_IMMENABLED ) )
+		return;
+
+	mHImmDll = LoadLibraryA("Imm32");
+	if (mHImmDll != NULL)
+	{
+		mImmIsIME               = (BOOL (WINAPI *)(HKL))                    GetProcAddress(mHImmDll, "ImmIsIME");
+		mImmGetDefaultIMEWnd	= (HWND (WINAPI *)(HWND))					GetProcAddress(mHImmDll, "ImmGetDefaultIMEWnd");
+		mImmGetContext          = (HIMC (WINAPI *)(HWND))                   GetProcAddress(mHImmDll, "ImmGetContext");
+		mImmReleaseContext      = (BOOL (WINAPI *)(HWND, HIMC))             GetProcAddress(mHImmDll, "ImmReleaseContext");
+		mImmGetOpenStatus       = (BOOL (WINAPI *)(HIMC))                   GetProcAddress(mHImmDll, "ImmGetOpenStatus");
+		mImmSetOpenStatus       = (BOOL (WINAPI *)(HIMC, BOOL))             GetProcAddress(mHImmDll, "ImmSetOpenStatus");
+		mImmGetConversionStatus = (BOOL (WINAPI *)(HIMC, LPDWORD, LPDWORD)) GetProcAddress(mHImmDll, "ImmGetConversionStatus");
+		mImmSetConversionStatus = (BOOL (WINAPI *)(HIMC, DWORD, DWORD))     GetProcAddress(mHImmDll, "ImmSetConversionStatus");
+		mImmGetCompostitionWindow = (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM))   GetProcAddress(mHImmDll, "ImmGetCompositionWindow");
+		mImmSetCompostitionWindow = (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM))   GetProcAddress(mHImmDll, "ImmSetCompositionWindow");
+		mImmGetCompositionString= (LONG (WINAPI *)(HIMC, DWORD, LPVOID, DWORD))					GetProcAddress(mHImmDll, "ImmGetCompositionStringW");
+		mImmSetCompositionString= (BOOL (WINAPI *)(HIMC, DWORD, LPVOID, DWORD, LPVOID, DWORD))	GetProcAddress(mHImmDll, "ImmSetCompositionStringW");
+		mImmSetCompositionFont  = (BOOL (WINAPI *)(HIMC, LPLOGFONTW))		GetProcAddress(mHImmDll, "ImmSetCompositionFontW");
+		mImmSetCandidateWindow  = (BOOL (WINAPI *)(HIMC, LPCANDIDATEFORM))  GetProcAddress(mHImmDll, "ImmSetCandidateWindow");
+		mImmNotifyIME			= (BOOL (WINAPI *)(HIMC, DWORD, DWORD, DWORD))	GetProcAddress(mHImmDll, "ImmNotifyIME");
+
+		if (mImmIsIME == NULL ||
+			mImmGetDefaultIMEWnd == NULL ||
+			mImmGetContext == NULL ||
+			mImmReleaseContext == NULL ||
+			mImmGetOpenStatus == NULL ||
+			mImmSetOpenStatus == NULL ||
+			mImmGetConversionStatus == NULL ||
+			mImmSetConversionStatus == NULL ||
+			mImmGetCompostitionWindow == NULL ||
+			mImmSetCompostitionWindow == NULL ||
+			mImmGetCompositionString == NULL ||
+			mImmSetCompositionString == NULL ||
+			mImmSetCompositionFont == NULL ||
+			mImmSetCandidateWindow == NULL ||
+			mImmNotifyIME == NULL)
+		{
+			// If any of the above API entires are not found, we can't use IMM API.  
+			// So, turn off the IMM support.  We should log some warning message in 
+			// the case, since it is very unusual; these APIs are available from 
+			// the beginning, and all versions of IMM32.DLL should have them all.  
+			// Unfortunately, this code may be executed before initialization of 
+			// the logging channel (LL_WARNS()), and we can't do it here...  Yes, this 
+			// is one of disadvantages to use static constraction to DLL loading. 
+			FreeLibrary(mHImmDll);
+			mHImmDll = NULL;
+
+			// If we unload the library, make sure all the function pointers are cleared
+			mImmIsIME = NULL;
+			mImmGetDefaultIMEWnd = NULL;
+			mImmGetContext = NULL;
+			mImmReleaseContext = NULL;
+			mImmGetOpenStatus = NULL;
+			mImmSetOpenStatus = NULL;
+			mImmGetConversionStatus = NULL;
+			mImmSetConversionStatus = NULL;
+			mImmGetCompostitionWindow = NULL;
+			mImmSetCompostitionWindow = NULL;
+			mImmGetCompositionString = NULL;
+			mImmSetCompositionString = NULL;
+			mImmSetCompositionFont = NULL;
+			mImmSetCandidateWindow = NULL;
+			mImmNotifyIME = NULL;
+		}
+	}
+}
+
+
 // static 
 BOOL	LLWinImm::isIME(HKL hkl)
 { 
-	return ImmIsIME(hkl);
+	if ( sTheInstance.mImmIsIME )
+		return sTheInstance.mImmIsIME(hkl); 
+	return FALSE;
 }
 
 // static 
 HIMC		LLWinImm::getContext(HWND hwnd)
 {
-	return ImmGetContext(hwnd);
+	if ( sTheInstance.mImmGetContext )
+		return sTheInstance.mImmGetContext(hwnd); 
+	return 0;
 }
 
 //static 
 BOOL		LLWinImm::releaseContext(HWND hwnd, HIMC himc)
 { 
-	return ImmReleaseContext(hwnd, himc);
+	if ( sTheInstance.mImmIsIME )
+		return sTheInstance.mImmReleaseContext(hwnd, himc); 
+	return FALSE;
 }
 
 // static 
 BOOL		LLWinImm::getOpenStatus(HIMC himc)
 { 
-	return ImmGetOpenStatus(himc);
+	if ( sTheInstance.mImmGetOpenStatus )
+		return sTheInstance.mImmGetOpenStatus(himc); 
+	return FALSE;
 }
 
 // static 
 BOOL		LLWinImm::setOpenStatus(HIMC himc, BOOL status)
 { 
-	return ImmSetOpenStatus(himc, status);
+	if ( sTheInstance.mImmSetOpenStatus )
+		return sTheInstance.mImmSetOpenStatus(himc, status); 
+	return FALSE;
 }
 
 // static 
 BOOL		LLWinImm::getConversionStatus(HIMC himc, LPDWORD conversion, LPDWORD sentence)	
 { 
-	return ImmGetConversionStatus(himc, conversion, sentence);
+	if ( sTheInstance.mImmGetConversionStatus )
+		return sTheInstance.mImmGetConversionStatus(himc, conversion, sentence); 
+	return FALSE;
 }
 
 // static 
 BOOL		LLWinImm::setConversionStatus(HIMC himc, DWORD conversion, DWORD sentence)		
 { 
-	return ImmSetConversionStatus(himc, conversion, sentence);
+	if ( sTheInstance.mImmSetConversionStatus )
+		return sTheInstance.mImmSetConversionStatus(himc, conversion, sentence); 
+	return FALSE;
 }
 
 // static 
 BOOL		LLWinImm::getCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form)					
 { 
-	return ImmGetCompositionWindow(himc, form);
+	if ( sTheInstance.mImmGetCompostitionWindow )
+		return sTheInstance.mImmGetCompostitionWindow(himc, form);	
+	return FALSE;
 }
 
 // static 
 BOOL		LLWinImm::setCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form)					
 { 
-	return ImmSetCompositionWindow(himc, form);
+	if ( sTheInstance.mImmSetCompostitionWindow )
+		return sTheInstance.mImmSetCompostitionWindow(himc, form);	
+	return FALSE;
 }
 
 
 // static 
 LONG		LLWinImm::getCompositionString(HIMC himc, DWORD index, LPVOID data, DWORD length)					
 { 
-	return ImmGetCompositionString(himc, index, data, length);
+	if ( sTheInstance.mImmGetCompositionString )
+		return sTheInstance.mImmGetCompositionString(himc, index, data, length);	
+	return FALSE;
 }
 
 
 // static 
 BOOL		LLWinImm::setCompositionString(HIMC himc, DWORD index, LPVOID pComp, DWORD compLength, LPVOID pRead, DWORD readLength)					
 { 
-	return ImmSetCompositionString(himc, index, pComp, compLength, pRead, readLength);
+	if ( sTheInstance.mImmSetCompositionString )
+		return sTheInstance.mImmSetCompositionString(himc, index, pComp, compLength, pRead, readLength);	
+	return FALSE;
 }
 
 // static 
 BOOL		LLWinImm::setCompositionFont(HIMC himc, LPLOGFONTW pFont)					
 { 
-	return ImmSetCompositionFont(himc, pFont);
+	if ( sTheInstance.mImmSetCompositionFont )
+		return sTheInstance.mImmSetCompositionFont(himc, pFont);	
+	return FALSE;
 }
 
 // static 
 BOOL		LLWinImm::setCandidateWindow(HIMC himc, LPCANDIDATEFORM form)					
 { 
-	return ImmSetCandidateWindow(himc, form);
+	if ( sTheInstance.mImmSetCandidateWindow )
+		return sTheInstance.mImmSetCandidateWindow(himc, form);	
+	return FALSE;
 }
 
 // static 
 BOOL		LLWinImm::notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value)					
 { 
-	return ImmNotifyIME(himc, action, index, value);
+	if ( sTheInstance.mImmNotifyIME )
+		return sTheInstance.mImmNotifyIME(himc, action, index, value);	
+	return FALSE;
 }
 
 
 
+
+// ----------------------------------------------------------------------------------------
+LLWinImm::~LLWinImm()
+{
+	if (mHImmDll != NULL)
+	{
+		FreeLibrary(mHImmDll);
+		mHImmDll = NULL;
+	}
+}
+
+
 class LLMonitorInfo
 {
 public:
@@ -326,32 +468,6 @@ class LLMonitorInfo
 static LLMonitorInfo sMonitorInfo;
 
 
-// Thread that owns the Window Handle
-// This whole struct is private to LLWindowWin32, which needs to mess with its
-// members, which is why it's a struct rather than a class. In effect, we make
-// the containing class a friend.
-struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
-{
-    static const int MAX_QUEUE_SIZE = 2048;
-
-    LLThreadSafeQueue<MSG> mMessageQueue;
-
-    LLWindowWin32Thread();
-
-    void run() override;
-
-    template <typename CALLABLE>
-    void post(CALLABLE&& func)
-    {
-        getQueue().post(std::forward<CALLABLE>(func));
-    }
-
-    // call PeekMessage and pull enqueue messages for later processing
-    void gatherInput();
-    HWND mWindowHandle = NULL;
-    HDC mhDC = 0;
-};
-
 
 LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
 							 const std::string& title, const std::string& name, S32 x, S32 y, S32 width,
@@ -363,7 +479,8 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
 	: LLWindow(callbacks, fullscreen, flags)
 {
     sMainThreadId = LLThread::currentID();
-    mWindowThread = new LLWindowWin32Thread();
+    mWindowThread = new LLWindowWin32Thread(this);
+    mWindowThread->start();
 	//MAINT-516 -- force a load of opengl32.dll just in case windows went sideways 
 	LoadLibrary(L"opengl32.dll");
 
@@ -434,6 +551,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
 
 	// Make an instance of our window then define the window class
 	mhInstance = GetModuleHandle(NULL);
+	mWndProc = NULL;
 
     // Init Direct Input - needed for joystick / Spacemouse
 
@@ -857,13 +975,17 @@ void LLWindowWin32::close()
                 // Something killed the window while we were busy destroying gl or handle somehow got broken
                 LL_WARNS("Window") << "Failed to destroy Window, invalid handle!" << LL_ENDL;
             }
+            mWindowHandle = NULL;
 
+            mWindowThread->mFinished = true;
         });
-    // Even though the above lambda might not yet have run, we've already
-    // bound mWindowHandle into it by value, which should suffice for the
-    // operations we're asking. That's the last time WE should touch it.
-    mWindowHandle = NULL;
-    mWindowThread->close();
+
+    while (!mWindowThread->isStopped())
+    {
+        //nudge window thread
+        PostMessage(mWindowHandle, WM_USER + 0x0017, 0xB0B0, 0x1337);
+        std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    }
 }
 
 BOOL LLWindowWin32::isValid()
@@ -1156,7 +1278,51 @@ BOOL LLWindowWin32::switchContext(BOOL fullscreen, const LLCoordScreen& size, BO
         << " Fullscreen: " << mFullscreen
         << LL_ENDL;
 
-	recreateWindow(window_rect, dw_ex_style, dw_style);
+    auto oldHandle = mWindowHandle;
+
+    //zero out mWindowHandle and mhDC before destroying window so window thread falls back to peekmessage
+    mWindowHandle = 0;
+    mhDC = 0;
+
+    if (oldHandle && !destroy_window_handler(oldHandle))
+    {
+        LL_WARNS("Window") << "Failed to properly close window before recreating it!" << LL_ENDL;
+    }
+
+    mWindowHandle = NULL;
+    mhDC = 0;
+
+    mWindowThread->post(
+        [this, window_rect, dw_ex_style, dw_style]()
+        {
+            mWindowHandle = CreateWindowEx(dw_ex_style,
+                mWindowClassName,
+                mWindowTitle,
+                WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style,
+                window_rect.left,								// x pos
+                window_rect.top,								// y pos
+                window_rect.right - window_rect.left,			// width
+                window_rect.bottom - window_rect.top,			// height
+                NULL,
+                NULL,
+                mhInstance,
+                NULL);
+
+            if (mWindowHandle)
+            {
+                mhDC = GetDC(mWindowHandle);
+            }
+        }
+    );
+
+    // HACK wait for above handle to become populated
+    // TODO: use a future
+    int count = 1024;
+    while (!mhDC && count > 0)
+    {
+        Sleep(10);
+        --count;
+    }
 
 	if (mWindowHandle)
 	{
@@ -1484,7 +1650,48 @@ const	S32   max_format  = (S32)num_formats - 1;
 			mhDC = 0;											// Zero The Device Context
 		}
 
-		recreateWindow(window_rect, dw_ex_style, dw_style);
+        auto oldHandle = mWindowHandle;
+        mWindowHandle = NULL;
+        mhDC = 0;
+
+        // Destroy The Window
+        if (oldHandle && !destroy_window_handler(oldHandle))
+        {
+            LL_WARNS("Window") << "Failed to properly close window!" << LL_ENDL;
+        }		
+
+        mWindowThread->post(
+            [this, window_rect, dw_ex_style, dw_style]()
+            {
+                mWindowHandle = CreateWindowEx(dw_ex_style,
+                    mWindowClassName,
+                    mWindowTitle,
+                    WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style,
+                    window_rect.left,								// x pos
+                    window_rect.top,								// y pos
+                    window_rect.right - window_rect.left,			// width
+                    window_rect.bottom - window_rect.top,			// height
+                    NULL,
+                    NULL,
+                    mhInstance,
+                    NULL);
+
+                if (mWindowHandle)
+                {
+                    mhDC = GetDC(mWindowHandle);
+                }
+            }
+        );
+
+        // HACK wait for above handle to become populated
+        // TODO: use a future
+        int count = 1024;
+        while (!mhDC && count > 0)
+        {
+            PostMessage(oldHandle, WM_USER + 8, 0x1717, 0x3b3b);
+            Sleep(10);
+            --count;
+        }
 
 		if (mWindowHandle)
 		{
@@ -1621,64 +1828,6 @@ const	S32   max_format  = (S32)num_formats - 1;
 	return TRUE;
 }
 
-void LLWindowWin32::recreateWindow(RECT window_rect, DWORD dw_ex_style, DWORD dw_style)
-{
-    auto oldHandle = mWindowHandle;
-
-    // zero out mWindowHandle and mhDC before destroying window so window
-    // thread falls back to peekmessage
-    mWindowHandle = 0;
-    mhDC = 0;
-
-    if (oldHandle && !destroy_window_handler(oldHandle))
-    {
-        LL_WARNS("Window") << "Failed to properly close window before recreating it!" << LL_ENDL;
-    }
-
-    std::promise<std::pair<HWND, HDC>> promise;
-    mWindowThread->post(
-        [this, window_rect, dw_ex_style, dw_style, &promise]()
-        {
-            auto handle = CreateWindowEx(dw_ex_style,
-                mWindowClassName,
-                mWindowTitle,
-                WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style,
-                window_rect.left,								// x pos
-                window_rect.top,								// y pos
-                window_rect.right - window_rect.left,			// width
-                window_rect.bottom - window_rect.top,			// height
-                NULL,
-                NULL,
-                mhInstance,
-                NULL);
-
-            if (! handle)
-            {
-                // Failed to create window: clear the variables. This
-                // assignment is valid because we're running on mWindowThread.
-                mWindowThread->mWindowHandle = NULL;
-                mWindowThread->mhDC = 0;
-            }
-            else
-            {
-                // Update mWindowThread's own mWindowHandle and mhDC.
-                mWindowThread->mWindowHandle = handle;
-                mWindowThread->mhDC = GetDC(handle);
-            }
-                
-            // It's important to wake up the future either way.
-            promise.set_value(std::make_pair(mWindowThread->mWindowHandle, mWindowThread->mhDC));
-        }
-    );
-
-    auto future = promise.get_future();
-    // This blocks until mWindowThread processes CreateWindowEx() and calls
-    // promise.set_value().
-    auto pair = future.get();
-    mWindowHandle = pair.first;
-    mhDC = pair.second;
-}
-
 void* LLWindowWin32::createSharedContext()
 {
     S32 attribs[] =
@@ -2032,14 +2181,12 @@ void LLWindowWin32::gatherInput()
     }
 
 
-    if (mWindowThread->getQueue().size())
+    if (mWindowThread->mFunctionQueue.size() > 0)
     {
         LL_PROFILE_ZONE_NAMED("gi - PostMessage");
         if (mWindowHandle)
-        {
-            // post a nonsense user message to wake up the Window Thread in
-            // case any functions are pending and no windows events came
-            // through this frame
+        { // post a nonsense user message to wake up the Window Thread in case any functions are pending
+            // and no windows events came through this frame
             PostMessage(mWindowHandle, WM_USER + 0x0017, 0xB0B0, 0x1337);
         }
     }
@@ -2129,6 +2276,17 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
 
     if (NULL != window_imp)
     {
+        // Has user provided their own window callback?
+        if (NULL != window_imp->mWndProc)
+        {
+            LL_PROFILE_ZONE_NAMED("mwp - WndProc");
+            if (!window_imp->mWndProc(h_wnd, u_msg, w_param, l_param))
+            {
+                // user has handled window message
+                return 0;
+            }
+        }
+
         // Juggle to make sure we can get negative positions for when
         // mouse is outside window.
         LLCoordWindow window_coord((S32)(S16)LOWORD(l_param), (S32)(S16)HIWORD(l_param));
@@ -4409,32 +4567,35 @@ std::vector<std::string> LLWindowWin32::getDynamicFallbackFontList()
 
 #endif // LL_WINDOWS
 
-inline LLWindowWin32::LLWindowWin32Thread::LLWindowWin32Thread()
-    : ThreadPool("Window Thread", 1, MAX_QUEUE_SIZE)
+inline LLWindowWin32Thread::LLWindowWin32Thread(LLWindowWin32* window)
+    : LLThread("Window Thread"), 
+    mWindow(window),
+    mFunctionQueue(MAX_QUEUE_SIZE)
 {
+
 }
 
-void LLWindowWin32::LLWindowWin32Thread::run()
+inline void LLWindowWin32Thread::run()
 {
-    sWindowThreadId = std::this_thread::get_id();
-    while (! getQueue().done())
+    sWindowThreadId = getID();
+    while (!mFinished)
     {
         LL_PROFILE_ZONE_SCOPED;
 
 
-        if (mWindowHandle != 0)
+        if (mWindow && mWindow->mWindowHandle != 0)
         {
             MSG msg;
             BOOL status;
-            if (mhDC == 0)
+            if (mWindow->mhDC == 0)
             {
                 LL_PROFILE_ZONE_NAMED("w32t - PeekMessage");
-                status = PeekMessage(&msg, mWindowHandle, 0, 0, PM_REMOVE);
+                status = PeekMessage(&msg, mWindow->mWindowHandle, 0, 0, PM_REMOVE);
             }
             else
             {
                 LL_PROFILE_ZONE_NAMED("w32t - GetMessage");
-                status = GetMessage(&msg, mWindowHandle, 0, 0);
+                status = GetMessage(&msg, mWindow->mWindowHandle, 0, 0);
             }
             if (status > 0)
             {
@@ -4448,7 +4609,11 @@ void LLWindowWin32::LLWindowWin32Thread::run()
         {
             LL_PROFILE_ZONE_NAMED("w32t - Function Queue");
             //process any pending functions
-            getQueue().runPending();
+            std::function<void()> curFunc;
+            while (mFunctionQueue.tryPopBack(curFunc))
+            {
+                curFunc();
+            }
         }
         
 #if 0
@@ -4460,6 +4625,11 @@ void LLWindowWin32::LLWindowWin32Thread::run()
     }
 }
 
+void LLWindowWin32Thread::post(const std::function<void()>& func)
+{
+    mFunctionQueue.pushFront(func);
+}
+
 void LLWindowWin32::post(const std::function<void()>& func)
 {
     mFunctionQueue.pushFront(func);
diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h
index 59660611774125b7d16eeb01250786d55bdfcda7..d082080807106e2fd7598c15f6099a002a252da9 100644
--- a/indra/llwindow/llwindowwin32.h
+++ b/indra/llwindow/llwindowwin32.h
@@ -36,12 +36,44 @@
 #include "llthread.h"
 #include "llthreadsafequeue.h"
 #include "llmutex.h"
-#include "workqueue.h"
 
 // Hack for async host by name
 #define LL_WM_HOST_RESOLVED      (WM_APP + 1)
 typedef void (*LLW32MsgCallback)(const MSG &msg);
 
+class LLWindowWin32;
+
+// Thread that owns the Window Handle
+class LLWindowWin32Thread : public LLThread
+{
+public:
+    class Message
+    {
+    public:
+        LRESULT mMsg;
+    };
+
+    static const int MAX_QUEUE_SIZE = 2048;
+
+    LLThreadSafeQueue<MSG> mMessageQueue;
+    LLThreadSafeQueue<std::function<void()>> mFunctionQueue;
+
+    bool mFinished = false;
+
+    LLWindowWin32Thread(LLWindowWin32* window);
+
+    void run() override;
+
+    void post(const std::function<void()>& func);
+
+private:
+
+    // call PeekMessage and pull enqueue messages for later processing
+    void gatherInput();
+    LLWindowWin32* mWindow = nullptr;
+
+};
+
 class LLWindowWin32 : public LLWindow
 {
 public:
@@ -186,6 +218,7 @@ class LLWindowWin32 : public LLWindow
 	HGLRC		mhRC = 0;			// OpenGL rendering context
 	HDC			mhDC = 0;			// Windows Device context handle
 	HINSTANCE	mhInstance;		// handle to application instance
+	WNDPROC		mWndProc;		// user-installable window proc
 	RECT		mOldMouseClip;  // Screen rect to which the mouse cursor was globally constrained before we changed it in clipMouse()
 	WPARAM		mLastSizeWParam;
 	F32			mOverrideAspectRatio;
@@ -237,15 +270,14 @@ class LLWindowWin32 : public LLWindow
 
 	BOOL			mMouseVanish;
 
-	struct LLWindowWin32Thread;
-	LLWindowWin32Thread* mWindowThread = nullptr;
-	LLThreadSafeQueue<std::function<void()>> mFunctionQueue;
-	LLThreadSafeQueue<std::function<void()>> mMouseQueue;
-	void post(const std::function<void()>& func);
-	void postMouseButtonEvent(const std::function<void()>& func);
-	void recreateWindow(RECT window_rect, DWORD dw_ex_style, DWORD dw_style);
+    LLWindowWin32Thread* mWindowThread = nullptr;
+    LLThreadSafeQueue<std::function<void()>> mFunctionQueue;
+    LLThreadSafeQueue<std::function<void()>> mMouseQueue;
+    void post(const std::function<void()>& func);
+    void postMouseButtonEvent(const std::function<void()>& func);
 
 	friend class LLWindowManager;
+    friend class LLWindowWin32Thread;
 };
 
 class LLSplashScreenWin32 : public LLSplashScreen
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 6d090be33a9b59598f31d4e050b5e6bb6215a151..631089f6ceb323aee639224ff2306afa9054476f 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -393,6 +393,7 @@ set(viewer_SOURCE_FILES
     llloginhandler.cpp
     lllogininstance.cpp
     llmachineid.cpp
+    llmainlooprepeater.cpp
     llmanip.cpp
     llmaniprotate.cpp
     llmanipscale.cpp
@@ -1031,6 +1032,7 @@ set(viewer_HEADER_FILES
     llloginhandler.h
     lllogininstance.h
     llmachineid.h
+    llmainlooprepeater.h
     llmanip.h
     llmaniprotate.h
     llmanipscale.h
@@ -1602,7 +1604,6 @@ if (WINDOWS)
         ${WINDOWS_LIBRARIES}
         comdlg32
         dxguid
-        imm32
         kernel32
         odbc32
         odbccp32
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 607e531e62f07c0571be8f9229e08aa6dc82b094..f15b5d09817c3173bfe2606c6eea92b2dbbb3d3c 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -3858,17 +3858,6 @@
         <key>Value</key>
         <integer>1</integer>
     </map>
-    <key>MainWorkTime</key>
-    <map>
-        <key>Comment</key>
-        <string>Max time per frame devoted to mainloop work queue (in milliseconds)</string>
-        <key>Persist</key>
-        <integer>1</integer>
-        <key>Type</key>
-        <string>F32</string>
-        <key>Value</key>
-        <real>0.1</real>
-    </map>
     <key>QueueInventoryFetchTimeout</key>
     <map>
         <key>Comment</key>
@@ -12674,20 +12663,6 @@
       <key>Value</key>
       <integer>50</integer>
     </map>
-    <key>ThreadPoolSizes</key>
-    <map>
-      <key>Comment</key>
-      <string>Map of size overrides for specific thread pools.</string>
-      <key>Persist</key>
-      <integer>1</integer>
-      <key>Type</key>
-      <string>LLSD</string>
-      <key>Value</key>
-      <map>
-        <key>General</key>
-        <integer>4</integer>
-      </map>
-    </map>
     <key>ThrottleBandwidthKBPS</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 93e5c2e34160c7c8ff78192a3a34a38debbd4a2a..89756d08819cd296dfd505a32e09d87f64c507cb 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -233,12 +233,11 @@
 #include "llavatariconctrl.h"
 #include "llgroupiconctrl.h"
 #include "llviewerassetstats.h"
-#include "workqueue.h"
-using namespace LL;
 
 // Include for security api initialization
 #include "llsecapi.h"
 #include "llmachineid.h"
+#include "llmainlooprepeater.h"
 #include "llcleanup.h"
 
 #include "llcoproceduremanager.h"
@@ -367,10 +366,6 @@ BOOL gLogoutInProgress = FALSE;
 
 BOOL gSimulateMemLeak = FALSE;
 
-// We don't want anyone, especially threads working on the graphics pipeline,
-// to have to block due to this WorkQueue being full.
-WorkQueue gMainloopWork("mainloop", 1024*1024);
-
 ////////////////////////////////////////////////////////////
 // Internal globals... that should be removed.
 static std::string gArgs;
@@ -386,6 +381,42 @@ static std::string gLaunchFileOnQuit;
 // Used on Win32 for other apps to identify our window (eg, win_setup)
 const char* const VIEWER_WINDOW_CLASSNAME = "Second Life";
 
+//-- LLDeferredTaskList ------------------------------------------------------
+
+/**
+ * A list of deferred tasks.
+ *
+ * We sometimes need to defer execution of some code until the viewer gets idle,
+ * e.g. removing an inventory item from within notifyObservers() may not work out.
+ *
+ * Tasks added to this list will be executed in the next LLAppViewer::idle() iteration.
+ * All tasks are executed only once.
+ */
+class LLDeferredTaskList: public LLSingleton<LLDeferredTaskList>
+{
+	LLSINGLETON_EMPTY_CTOR(LLDeferredTaskList);
+	LOG_CLASS(LLDeferredTaskList);
+
+	friend class LLAppViewer;
+	typedef boost::signals2::signal<void()> signal_t;
+
+	void addTask(const signal_t::slot_type& cb)
+	{
+		mSignal.connect(cb);
+	}
+
+	void run()
+	{
+		if (!mSignal.empty())
+		{
+			mSignal();
+			mSignal.disconnect_all_slots();
+		}
+	}
+
+	signal_t mSignal;
+};
+
 //----------------------------------------------------------------------------
 
 // List of entries from strings.xml to always replace
@@ -943,6 +974,9 @@ bool LLAppViewer::init()
 	}
 	LL_INFOS("InitInfo") << "Cache initialization is done." << LL_ENDL ;
 
+	// Initialize the repeater service.
+	LLMainLoopRepeater::instance().start();
+
     // Initialize event recorder
     LLViewerEventRecorder::createInstance();
 
@@ -2158,6 +2192,8 @@ bool LLAppViewer::cleanup()
 	SUBSYSTEM_CLEANUP(LLProxy);
     LLCore::LLHttp::cleanup();
 
+	LLMainLoopRepeater::instance().stop();
+
 	ll_close_fail_log();
 
 	LLError::LLCallStacks::cleanup();
@@ -4452,7 +4488,7 @@ bool LLAppViewer::initCache()
 
 void LLAppViewer::addOnIdleCallback(const boost::function<void()>& cb)
 {
-	gMainloopWork.post(cb);
+	LLDeferredTaskList::instance().addTask(cb);
 }
 
 void LLAppViewer::loadKeyBindings()
@@ -4850,6 +4886,7 @@ void LLAppViewer::idle()
 	LLNotificationsUI::LLToast::updateClass();
 	LLSmoothInterpolation::updateInterpolants();
 	LLMortician::updateClass();
+    LLImageGL::updateClass();
 	LLFilePickerThread::clearDead();  //calls LLFilePickerThread::notify()
 	LLDirPickerThread::clearDead();
 	F32 dt_raw = idle_timer.getElapsedTimeAndResetF32();
@@ -5226,19 +5263,8 @@ void LLAppViewer::idle()
 		}
 	}
 
-	// Service the WorkQueue we use for replies from worker threads.
-	// Use function statics for the timeslice setting so we only have to fetch
-	// and convert MainWorkTime once.
-	static F32 MainWorkTimeRaw = gSavedSettings.getF32("MainWorkTime");
-	static F32Milliseconds MainWorkTimeMs(MainWorkTimeRaw);
-	// MainWorkTime is specified in fractional milliseconds, but std::chrono
-	// uses integer representations. What if we want less than a microsecond?
-	// Use nanoseconds. We're very sure we will never need to specify a
-	// MainWorkTime that would be larger than we could express in
-	// std::chrono::nanoseconds.
-	static std::chrono::nanoseconds MainWorkTimeNanoSec{
-		std::chrono::nanoseconds::rep(MainWorkTimeMs.value() * 1000000)};
-	gMainloopWork.runFor(MainWorkTimeNanoSec);
+	// Execute deferred tasks.
+	LLDeferredTaskList::instance().run();
 
 	// Handle shutdown process, for example,
 	// wait for floaters to close, send quit message,
diff --git a/indra/newview/llenvironment.cpp b/indra/newview/llenvironment.cpp
index 1a66f10b8fb81ffd1a599ccb301161b3af07112f..dba24b3d02fcb788c6cd17a6b4ae4784c4e97ce7 100644
--- a/indra/newview/llenvironment.cpp
+++ b/indra/newview/llenvironment.cpp
@@ -824,6 +824,7 @@ std::string env_selection_to_string(LLEnvironment::EnvSelection_t sel)
 #undef RTNENUM
 }
 
+LLEnvironment* LLSimpleton<LLEnvironment>::sInstance = nullptr;
 //-------------------------------------------------------------------------
 LLEnvironment::LLEnvironment():
     mCloudScrollDelta(),
diff --git a/indra/newview/llmainlooprepeater.cpp b/indra/newview/llmainlooprepeater.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6736e9a950d500d7d71aa119997e78d01a1e8bc3
--- /dev/null
+++ b/indra/newview/llmainlooprepeater.cpp
@@ -0,0 +1,88 @@
+/** 
+ * @file llmachineid.cpp
+ * @brief retrieves unique machine ids
+ *
+ * $LicenseInfo:firstyear=2009&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+#include "llapr.h"
+#include "llevents.h"
+#include "llmainlooprepeater.h"
+
+
+
+// LLMainLoopRepeater
+//-----------------------------------------------------------------------------
+
+
+LLMainLoopRepeater::LLMainLoopRepeater(void):
+	mQueue(0)
+{
+	; // No op.
+}
+
+
+void LLMainLoopRepeater::start(void)
+{
+	if(mQueue != 0) return;
+
+	mQueue = new LLThreadSafeQueue<LLSD>(1024);
+	mMainLoopConnection = LLEventPumps::instance().
+		obtain("mainloop").listen(LLEventPump::inventName(), boost::bind(&LLMainLoopRepeater::onMainLoop, this, _1));
+	mRepeaterConnection = LLEventPumps::instance().
+		obtain("mainlooprepeater").listen(LLEventPump::inventName(), boost::bind(&LLMainLoopRepeater::onMessage, this, _1));
+}
+
+
+void LLMainLoopRepeater::stop(void)
+{
+	mMainLoopConnection.release();
+	mRepeaterConnection.release();
+
+	delete mQueue;
+	mQueue = 0;
+}
+
+
+bool LLMainLoopRepeater::onMainLoop(LLSD const &)
+{
+	LLSD message;
+	while(mQueue->tryPopBack(message)) {
+		std::string pump = message["pump"].asString();
+		if(pump.length() == 0 ) continue; // No pump.
+		LLEventPumps::instance().obtain(pump).post(message["payload"]);
+	}
+	return false;
+}
+
+
+bool LLMainLoopRepeater::onMessage(LLSD const & event)
+{
+	try {
+		mQueue->pushFront(event);
+	} catch(LLThreadSafeQueueError & e) {
+		LL_WARNS() << "could not repeat message (" << e.what() << ")" << 
+			event.asString() << LL_ENDL;
+	}
+	return false;
+}
diff --git a/indra/newview/llmainlooprepeater.h b/indra/newview/llmainlooprepeater.h
new file mode 100644
index 0000000000000000000000000000000000000000..2ec3a74e4ae3bc4a446182cc444c5d654cd044d6
--- /dev/null
+++ b/indra/newview/llmainlooprepeater.h
@@ -0,0 +1,64 @@
+/** 
+ * @file llmainlooprepeater.h
+ * @brief a service for repeating messages on the main loop.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLMAINLOOPREPEATER_H
+#define LL_LLMAINLOOPREPEATER_H
+
+
+#include "llsd.h"
+#include "llthreadsafequeue.h"
+
+
+//
+// A service which creates the pump 'mainlooprepeater' to which any thread can
+// post a message that will be re-posted on the main loop.
+//
+// The posted message should contain two map elements: pump and payload.  The
+// pump value is a string naming the pump to which the message should be
+// re-posted.  The payload value is what will be posted to the designated pump.
+//
+class LLMainLoopRepeater:
+	public LLSingleton<LLMainLoopRepeater>
+{
+	LLSINGLETON(LLMainLoopRepeater);
+public:
+	// Start the repeater service.
+	void start(void);
+	
+	// Stop the repeater service.
+	void stop(void);
+	
+private:
+	LLTempBoundListener mMainLoopConnection;
+	LLTempBoundListener mRepeaterConnection;
+	LLThreadSafeQueue<LLSD> * mQueue;
+	
+	bool onMainLoop(LLSD const &);
+	bool onMessage(LLSD const & event);
+};
+
+
+#endif
diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp
index bc00c518e9017c90ae9ad81d3662f180af05e77e..53247031b4ad1f0dd1388cdd1fe3cd0a5dc10f96 100644
--- a/indra/newview/llselectmgr.cpp
+++ b/indra/newview/llselectmgr.cpp
@@ -97,6 +97,8 @@
 #include "llglheaders.h"
 #include "llinventoryobserver.h"
 
+LLSelectMgr* LLSimpleton<LLSelectMgr>::sInstance = nullptr;
+
 LLViewerObject* getSelectedParentObject(LLViewerObject *object) ;
 //
 // Consts
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 9a4149948c4f128d498e4a1f185cf16a8481c56f..57c50748043e9ee437347300983a6626064eabdf 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -205,9 +205,6 @@
 
 #include "llstacktrace.h"
 
-#include "threadpool.h"
-
-
 #if LL_WINDOWS
 #include "lldxhardware.h"
 #endif
@@ -304,20 +301,6 @@ void callback_cache_name(const LLUUID& id, const std::string& full_name, bool is
 // local classes
 //
 
-void launchThreadPool()
-{
-    LLSD poolSizes{ gSavedSettings.getLLSD("ThreadPoolSizes") };
-    LLSD sizeSpec{ poolSizes["General"] };
-    LLSD::Integer size{ sizeSpec.isInteger()? sizeSpec.asInteger() : 3 };
-    LL_DEBUGS("ThreadPool") << "Instantiating General pool with "
-                            << size << " threads" << LL_ENDL;
-    // Use a function-static ThreadPool: static duration, but instantiated
-    // only on demand.
-    // We don't want anyone, especially the main thread, to have to block
-    // due to this ThreadPool being full.
-    static LL::ThreadPool pool("General", size, 1024*1024);
-}
-
 void update_texture_fetch()
 {
 	LLAppViewer::getTextureCache()->update(1); // unpauses the texture cache thread
@@ -1506,9 +1489,6 @@ bool idle_startup()
 		gAgentCamera.resetCamera();
 		display_startup();
 
-		// start up the ThreadPool we'll use for textures et al.
-		launchThreadPool();
-
 		// Initialize global class data needed for surfaces (i.e. textures)
 		LL_DEBUGS("AppInit") << "Initializing sky..." << LL_ENDL;
 		// Initialize all of the viewer object classes for the first time (doing things like texture fetches.
diff --git a/indra/newview/llviewercamera.cpp b/indra/newview/llviewercamera.cpp
index 5d8e80cc412b0f7af2a8bc21125e52cd1081bd99..5ebce115f637212c944bfd05c91aa4df01c2462e 100644
--- a/indra/newview/llviewercamera.cpp
+++ b/indra/newview/llviewercamera.cpp
@@ -54,6 +54,8 @@
 // System includes
 #include <iomanip> // for setprecision
 
+LLViewerCamera* LLSimpleton<LLViewerCamera>::sInstance = nullptr;
+
 LLTrace::CountStatHandle<> LLViewerCamera::sVelocityStat("camera_velocity");
 LLTrace::CountStatHandle<> LLViewerCamera::sAngularVelocityStat("camera_angular_velocity");
 
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index 498e4ef8bc76e76ca26f0b620be84e0e479b81aa..fbc5830a5ce40f37d38f143f398a917cda7cd549 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -679,9 +679,6 @@ void LLViewerTexture::init(bool firstinit)
 	
 	mVolumeList[LLRender::LIGHT_TEX].clear();
 	mVolumeList[LLRender::SCULPT_TEX].clear();
-
-	mMainQueue	= LL::WorkQueue::getInstance("mainloop");
-	mImageQueue = LL::WorkQueue::getInstance("LLImageGL");
 }
 
 //virtual 
@@ -1625,26 +1622,17 @@ void LLViewerFetchedTexture::scheduleCreateTexture()
     {
         mNeedsCreateTexture = TRUE;
 #if LL_WINDOWS //flip to 0 to revert to single-threaded OpenGL texture uploads
-        auto mainq = mMainQueue.lock();
-        if (mainq)
-        {
-            mainq->postTo(
-                mImageQueue,
-                // work to be done on LLImageGL worker thread
-                [this]()
-                {
-                    //actually create the texture on a background thread
-                    createTexture();
-                },
-                // callback to be run on main thread
-                [this]()
-                {
-                    //finalize on main thread
-                    postCreateTexture();
-                    unref();
-                });
-        }
-        else
+        if (!LLImageGLThread::sInstance->post([this]()
+            {
+                //actually create the texture on a background thread
+                createTexture();
+                LLImageGLThread::sInstance->postCallback([this]()
+                    {
+                        //finalize on main thread
+                        postCreateTexture();
+                        unref();
+                    });
+            }))
 #endif
         {
             gTextureList.mCreateTextureList.insert(this);
diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h
index 4cd4c7cd390c042ab143bfc3953b656f84b2fe93..f9f1bfef44c0d5968de3fedfff440cd3c02a309f 100644
--- a/indra/newview/llviewertexture.h
+++ b/indra/newview/llviewertexture.h
@@ -35,7 +35,6 @@
 #include "llrender.h"
 #include "llmetricperformancetester.h"
 #include "httpcommon.h"
-#include "workqueue.h"
 
 #include <map>
 #include <list>
@@ -214,9 +213,6 @@ class LLViewerTexture : public LLGLTexture
 	//do not use LLPointer here.
 	LLViewerMediaTexture* mParcelMedia ;
 
-	LL::WorkQueue::weak_t mMainQueue;
-	LL::WorkQueue::weak_t mImageQueue;
-
 	static F32 sTexelPixelRatio;
 public:
 	static const U32 sCurrentFileVersion;	
diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp
index 5f62908009eca619c5918ce2e0afe9d253721a5f..0a8457eb2cd3aea571ad3ef6a20576ce9038bbe3 100644
--- a/indra/newview/llworld.cpp
+++ b/indra/newview/llworld.cpp
@@ -62,6 +62,8 @@
 #include <cstring>
 
 
+LLWorld* LLSimpleton<LLWorld>::sInstance = nullptr;
+
 //
 // Globals
 //