diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h
index 9be02f7e7d40af147d5139c6da31e56d5cd43134..596a2388a165e5d312722312325f2faa5f34386e 100644
--- a/indra/llcommon/llsingleton.h
+++ b/indra/llcommon/llsingleton.h
@@ -491,9 +491,6 @@ typename LLSingleton<T>::SingletonData LLSingleton<T>::sData;
  * * However, distinct initParamSingleton() calls can be used to engage
  *   different constructors, as long as only one such call is executed at
  *   runtime.
- * * Circularity is not permitted. No LLSingleton referenced by an
- *   LLParamSingleton's constructor or initSingleton() method may call this
- *   LLParamSingleton's instance() or getInstance() methods.
  * * Unlike LLSingleton, an LLParamSingleton cannot be "revived" by an
  *   instance() or getInstance() call after deleteSingleton().
  *
@@ -508,7 +505,6 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
 
 public:
     using super::deleteSingleton;
-    using super::instance;
     using super::instanceExists;
     using super::wasDeleted;
 
@@ -519,7 +515,7 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
         // In case racing threads both call initParamSingleton() at the same
         // time, serialize them. One should initialize; the other should see
         // mInitState already set.
-        std::unique_lock<std::mutex> lk(mMutex);
+        std::unique_lock<decltype(mMutex)> lk(mMutex);
         // For organizational purposes this function shouldn't be called twice
         if (super::sData.mInitState != super::UNINITIALIZED)
         {
@@ -539,7 +535,7 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
     {
         // In case racing threads call getInstance() at the same moment as
         // initParamSingleton(), serialize the calls.
-        std::unique_lock<std::mutex> lk(mMutex);
+        std::unique_lock<decltype(mMutex)> lk(mMutex);
 
         switch (super::sData.mInitState)
         {
@@ -551,15 +547,10 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
         case super::CONSTRUCTING:
             super::logerrs("Tried to access param singleton ",
                            super::demangle(typeid(DERIVED_TYPE).name()).c_str(),
-                " from singleton constructor!");
+                           " from singleton constructor!");
             break;
 
         case super::INITIALIZING:
-            super::logerrs("Tried to access param singleton ",
-                           super::demangle(typeid(DERIVED_TYPE).name()).c_str(),
-                           " from initSingleton() method!");
-            break;
-
         case super::INITIALIZED:
             return super::sData.mInstance;
 
@@ -574,12 +565,23 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
         return nullptr;
     }
 
+    // instance() is replicated here so it calls
+    // LLParamSingleton::getInstance() rather than LLSingleton::getInstance()
+    // -- avoid making getInstance() virtual
+    static DERIVED_TYPE& instance()
+    {
+        return *getInstance();
+    }
+
 private:
-    static std::mutex mMutex;
+    // Use a recursive_mutex in case of constructor circularity. With a
+    // non-recursive mutex, that would result in deadlock rather than the
+    // logerrs() call coded above.
+    static std::recursive_mutex mMutex;
 };
 
 template<typename T>
-typename std::mutex LLParamSingleton<T>::mMutex;
+typename std::recursive_mutex LLParamSingleton<T>::mMutex;
 
 /**
  * Initialization locked singleton, only derived class can decide when to initialize.
diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp
index a459d17fb8b58b2692bf731910eb204116ab8495..fa02d2bb1a0013e23baf4978c5842ec8e3eac73f 100644
--- a/indra/llcommon/tests/lleventcoro_test.cpp
+++ b/indra/llcommon/tests/lleventcoro_test.cpp
@@ -506,16 +506,10 @@ namespace tut
             replyName = waiter.getName0();
             errorName = waiter.getName1();
             WrapLLErrs capture;
-            try
-            {
-                result = waiter.suspendWithLog();
-                debug("no exception");
-            }
-            catch (const WrapLLErrs::FatalException& e)
-            {
-                debug(STRINGIZE("exception " << e.what()));
-                threw = e.what();
-            }
+            threw = capture.catch_llerrs([&waiter, &debug](){
+                    result = waiter.suspendWithLog();
+                    debug("no exception");
+                });
         }
         END
     }
@@ -762,18 +756,13 @@ namespace tut
         {
             LLCoroEventPumps waiter;
             WrapLLErrs capture;
-            try
-            {
-                result = waiter.postAndSuspendWithLog(
-                    LLSDMap("value", 31)("fail", LLSD()),
-                    immediateAPI.getPump(), "reply", "error");
-                debug("no exception");
-            }
-            catch (const WrapLLErrs::FatalException& e)
-            {
-                debug(STRINGIZE("exception " << e.what()));
-                threw = e.what();
-            }
+            threw = capture.catch_llerrs(
+                [&waiter, &debug](){
+                    result = waiter.postAndSuspendWithLog(
+                        LLSDMap("value", 31)("fail", LLSD()),
+                        immediateAPI.getPump(), "reply", "error");
+                    debug("no exception");
+                });
         }
         END
     }
diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp
index 5a4df81bf1ecbc9e74a1bc9f54723cff38c04dfd..a181d5c941e09a5855eafe0d2bb8f76096bc7734 100644
--- a/indra/llcommon/tests/lleventdispatcher_test.cpp
+++ b/indra/llcommon/tests/lleventdispatcher_test.cpp
@@ -22,6 +22,7 @@
 #include "llsdutil.h"
 #include "stringize.h"
 #include "tests/wrapllerrs.h"
+#include "../test/catch_and_store_what_in.h"
 
 #include <map>
 #include <string>
@@ -630,16 +631,9 @@ namespace tut
 
         void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
         {
-            std::string threw;
-            try
-            {
-                work(func, args);
-            }
-            catch (const std::runtime_error& e)
-            {
-                cout << "*** " << e.what() << '\n';
-                threw = e.what();
-            }
+            std::string threw = catch_what<std::runtime_error>([this, &func, &args](){
+                    work(func, args);
+                });
             ensure_has(threw, exc_frag);
         }
 
@@ -717,15 +711,9 @@ namespace tut
         LLSD attempts(LLSDArray(17)(LLSDMap("pi", 3.14)("two", 2)));
         foreach(LLSD ae, inArray(attempts))
         {
-            std::string threw;
-            try
-            {
-                work.add("freena_err", "freena", freena, ae);
-            }
-            catch (const std::exception& e)
-            {
-                threw = e.what();
-            }
+            std::string threw = catch_what<std::exception>([this, &ae](){
+                    work.add("freena_err", "freena", freena, ae);
+                });
             ensure_has(threw, "must be an array");
         }
     }
@@ -734,15 +722,9 @@ namespace tut
     void object::test<2>()
     {
         set_test_name("map-style registration with badly-formed defaults");
-        std::string threw;
-        try
-        {
-            work.add("freena_err", "freena", freena, LLSDArray("a")("b"), 17);
-        }
-        catch (const std::exception& e)
-        {
-            threw = e.what();
-        }
+        std::string threw = catch_what<std::exception>([this](){
+                work.add("freena_err", "freena", freena, LLSDArray("a")("b"), 17);
+            });
         ensure_has(threw, "must be a map or an array");
     }
 
@@ -750,17 +732,11 @@ namespace tut
     void object::test<3>()
     {
         set_test_name("map-style registration with too many array defaults");
-        std::string threw;
-        try
-        {
-            work.add("freena_err", "freena", freena,
-                     LLSDArray("a")("b"),
-                     LLSDArray(17)(0.9)("gack"));
-        }
-        catch (const std::exception& e)
-        {
-            threw = e.what();
-        }
+        std::string threw = catch_what<std::exception>([this](){
+                work.add("freena_err", "freena", freena,
+                         LLSDArray("a")("b"),
+                         LLSDArray(17)(0.9)("gack"));
+            });
         ensure_has(threw, "shorter than");
     }
 
@@ -768,17 +744,11 @@ namespace tut
     void object::test<4>()
     {
         set_test_name("map-style registration with too many map defaults");
-        std::string threw;
-        try
-        {
-            work.add("freena_err", "freena", freena,
-                     LLSDArray("a")("b"),
-                     LLSDMap("b", 17)("foo", 3.14)("bar", "sinister"));
-        }
-        catch (const std::exception& e)
-        {
-            threw = e.what();
-        }
+        std::string threw = catch_what<std::exception>([this](){
+                work.add("freena_err", "freena", freena,
+                         LLSDArray("a")("b"),
+                         LLSDMap("b", 17)("foo", 3.14)("bar", "sinister"));
+            });
         ensure_has(threw, "nonexistent params");
         ensure_has(threw, "foo");
         ensure_has(threw, "bar");
@@ -1039,16 +1009,9 @@ namespace tut
         // We don't have a comparable helper function for the one-arg
         // operator() method, and it's not worth building one just for this
         // case. Write it out.
-        std::string threw;
-        try
-        {
-            work(LLSDMap("op", "freek"));
-        }
-        catch (const std::runtime_error& e)
-        {
-            cout << "*** " << e.what() << "\n";
-            threw = e.what();
-        }
+        std::string threw = catch_what<std::runtime_error>([this](){
+                work(LLSDMap("op", "freek"));
+            });
         ensure_has(threw, "bad");
         ensure_has(threw, "op");
         ensure_has(threw, "freek");
diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp
index eb98b12ef5260395b35e1c4fc429ce00cea847ec..1875013794fe1abd6c5703d6b322341211856c65 100644
--- a/indra/llcommon/tests/lleventfilter_test.cpp
+++ b/indra/llcommon/tests/lleventfilter_test.cpp
@@ -350,15 +350,9 @@ namespace tut
         // Now let the timer expire.
         filter.forceTimeout();
         // Notice the timeout.
-        std::string threw;
-        try
-        {
-            mainloop.post(17);
-        }
-        catch (const WrapLLErrs::FatalException& e)
-        {
-            threw = e.what();
-        }
+        std::string threw = capture.catch_llerrs([this](){
+                mainloop.post(17);
+            });
         ensure_contains("errorAfter() timeout exception", threw, "timeout");
         // Timing out cancels the timer. Verify that.
         listener0.reset(0);
diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp
index c7d4b8a06bdd879d364cf14395f70f38db20df24..d94fc0c56d0b2052bee78d39a47910f48acd0557 100644
--- a/indra/llcommon/tests/llinstancetracker_test.cpp
+++ b/indra/llcommon/tests/llinstancetracker_test.cpp
@@ -198,14 +198,9 @@ namespace tut
         {
             WrapLLErrs wrapper;
             Keyed::instance_iter i(Keyed::beginInstances());
-            try
-            {
-                delete keyed;
-            }
-            catch (const WrapLLErrs::FatalException& e)
-            {
-                what = e.what();
-            }
+            what = wrapper.catch_llerrs([&keyed](){
+                    delete keyed;
+                });
         }
         ensure(! what.empty());
     }
@@ -219,14 +214,9 @@ namespace tut
         {
             WrapLLErrs wrapper;
             Keyed::key_iter i(Keyed::beginKeys());
-            try
-            {
-                delete keyed;
-            }
-            catch (const WrapLLErrs::FatalException& e)
-            {
-                what = e.what();
-            }
+            what = wrapper.catch_llerrs([&keyed](){
+                    delete keyed;
+                });
         }
         ensure(! what.empty());
     }
@@ -240,14 +230,9 @@ namespace tut
         {
             WrapLLErrs wrapper;
             Unkeyed::instance_iter i(Unkeyed::beginInstances());
-            try
-            {
-                delete unkeyed;
-            }
-            catch (const WrapLLErrs::FatalException& e)
-            {
-                what = e.what();
-            }
+            what = wrapper.catch_llerrs([&unkeyed](){
+                    delete unkeyed;
+                });
         }
         ensure(! what.empty());
     }
diff --git a/indra/llcommon/tests/lllazy_test.cpp b/indra/llcommon/tests/lllazy_test.cpp
index 32a717f4fccffea7227d1801504dc331221f84dd..542306ee22a36805a70232c55fd4328aba79ac24 100644
--- a/indra/llcommon/tests/lllazy_test.cpp
+++ b/indra/llcommon/tests/lllazy_test.cpp
@@ -38,6 +38,7 @@
 #include <boost/lambda/bind.hpp>
 // other Linden headers
 #include "../test/lltut.h"
+#include "../test/catch_and_store_what_in.h"
 
 namespace bll = boost::lambda;
 
@@ -200,15 +201,9 @@ namespace tut
     void lllazy_object::test<2>()
     {
         TestNeedsTesting tnt;
-        std::string threw;
-        try
-        {
-            tnt.toolate();
-        }
-        catch (const LLLazyCommon::InstanceChange& e)
-        {
-            threw = e.what();
-        }
+        std::string threw = catch_what<LLLazyCommon::InstanceChange>([&tnt](){
+                tnt.toolate();
+            });
         ensure_contains("InstanceChange exception", threw, "replace LLLazy instance");
     }
 
diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp
index 45648536c4db95fac6b382abc77d5e60a8fadf8e..bf0a74d10da2d9d1453f94fbb9b39788bf2b0258 100644
--- a/indra/llcommon/tests/llleap_test.cpp
+++ b/indra/llcommon/tests/llleap_test.cpp
@@ -23,7 +23,7 @@
 #include "../test/lltut.h"
 #include "../test/namedtempfile.h"
 #include "../test/catch_and_store_what_in.h"
-#include "wrapllerrs.h"
+#include "wrapllerrs.h"             // CaptureLog
 #include "llevents.h"
 #include "llprocess.h"
 #include "llstring.h"
@@ -290,12 +290,9 @@ namespace tut
     void object::test<6>()
     {
         set_test_name("empty plugin vector");
-        std::string threw;
-        try
-        {
-            LLLeap::create("empty", StringVec());
-        }
-        CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error)
+        std::string threw = catch_what<LLLeap::Error>([](){
+                LLLeap::create("empty", StringVec());
+            });
         ensure_contains("LLLeap::Error", threw, "no plugin");
         // try the suppress-exception variant
         ensure("bad launch returned non-NULL", ! LLLeap::create("empty", StringVec(), false));
@@ -308,12 +305,9 @@ namespace tut
         // Synthesize bogus executable name
         std::string BADPYTHON(PYTHON.substr(0, PYTHON.length()-1) + "x");
         CaptureLog log;
-        std::string threw;
-        try
-        {
-            LLLeap::create("bad exe", BADPYTHON);
-        }
-        CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error)
+        std::string threw = catch_what<LLLeap::Error>([&BADPYTHON](){
+                LLLeap::create("bad exe", BADPYTHON);
+            });
         ensure_contains("LLLeap::create() didn't throw", threw, "failed");
         log.messageWith("failed");
         log.messageWith(BADPYTHON);
diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index 5c87cdabd914600873d8d0b062735b4d899a7804..222d8320843de36a2d2db59b86d74f51a2ff689c 100644
--- a/indra/llcommon/tests/llprocess_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -25,8 +25,6 @@
 #include <boost/function.hpp>
 #include <boost/algorithm/string/find_iterator.hpp>
 #include <boost/algorithm/string/finder.hpp>
-//#include <boost/lambda/lambda.hpp>
-//#include <boost/lambda/bind.hpp>
 // other Linden headers
 #include "../test/lltut.h"
 #include "../test/namedtempfile.h"
@@ -35,7 +33,7 @@
 #include "llsdutil.h"
 #include "llevents.h"
 #include "llstring.h"
-#include "wrapllerrs.h"
+#include "wrapllerrs.h"             // CaptureLog
 
 #if defined(LL_WINDOWS)
 #define sleep(secs) _sleep((secs) * 1000)
@@ -45,8 +43,7 @@
 #include <sys/wait.h>
 #endif
 
-//namespace lambda = boost::lambda;
- std::string apr_strerror_helper(apr_status_t rv)
+std::string apr_strerror_helper(apr_status_t rv)
 {
     char errbuf[256];
     apr_strerror(rv, errbuf, sizeof(errbuf));
@@ -960,12 +957,9 @@ namespace tut
 #define CATCH_IN(THREW, EXCEPTION, CODE)                                \
     do                                                                  \
     {                                                                   \
-        (THREW).clear();                                                \
-        try                                                             \
-        {                                                               \
-            CODE;                                                       \
-        }                                                               \
-        CATCH_AND_STORE_WHAT_IN(THREW, EXCEPTION)                       \
+        (THREW) = catch_what<EXCEPTION>([&](){                          \
+                CODE;                                                   \
+            });                                                         \
         ensure("failed to throw " #EXCEPTION ": " #CODE, ! (THREW).empty()); \
     } while (0)
 
diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp
index 56886bc73fd9185932f76c82d0d72232cc5c4e5c..da7bc6355c9cb3aaf8d3cb130b26020c40173a43 100644
--- a/indra/llcommon/tests/llsingleton_test.cpp
+++ b/indra/llcommon/tests/llsingleton_test.cpp
@@ -29,7 +29,8 @@
 
 #include "llsingleton.h"
 #include "../test/lltut.h"
-
+#include "wrapllerrs.h"
+#include "llsd.h"
 
 // Capture execution sequence by appending to log string.
 std::string sLog;
@@ -198,4 +199,130 @@ namespace tut
 
     TESTS(A, B, 4, 5, 6, 7)
     TESTS(B, A, 8, 9, 10, 11)
+
+#define PARAMSINGLETON(cls)                                             \
+    class cls: public LLParamSingleton<cls>                             \
+    {                                                                   \
+        LLSINGLETON(cls, const LLSD::String& str): mDesc(str) {}        \
+        cls(LLSD::Integer i): mDesc(i) {}                               \
+                                                                        \
+    public:                                                             \
+        std::string desc() const { return mDesc.asString(); }           \
+                                                                        \
+    private:                                                            \
+        LLSD mDesc;                                                     \
+    }
+
+    // Declare two otherwise-identical LLParamSingleton classes so we can
+    // validly initialize each using two different constructors. If we tried
+    // to test that with a single LLParamSingleton class within the same test
+    // program, we'd get 'trying to use deleted LLParamSingleton' errors.
+    PARAMSINGLETON(PSing1);
+    PARAMSINGLETON(PSing2);
+
+    template<> template<>
+    void singleton_object_t::test<12>()
+    {
+        set_test_name("LLParamSingleton");
+
+        WrapLLErrs catcherr;
+        // query methods
+        ensure("false positive on instanceExists()", ! PSing1::instanceExists());
+        ensure("false positive on wasDeleted()", ! PSing1::wasDeleted());
+        // try to reference before initializing
+        std::string threw = catcherr.catch_llerrs([](){
+                (void)PSing1::instance();
+            });
+        ensure_contains("too-early instance() didn't throw", threw, "Uninitialized");
+        // getInstance() behaves the same as instance()
+        threw = catcherr.catch_llerrs([](){
+                (void)PSing1::getInstance();
+            });
+        ensure_contains("too-early getInstance() didn't throw", threw, "Uninitialized");
+        // initialize using LLSD::String constructor
+        PSing1::initParamSingleton("string");
+        ensure_equals(PSing1::instance().desc(), "string");
+        ensure("false negative on instanceExists()", PSing1::instanceExists());
+        // try to initialize again
+        threw = catcherr.catch_llerrs([](){
+                PSing1::initParamSingleton("again");
+            });
+        ensure_contains("second ctor(string) didn't throw", threw, "twice");
+        // try to initialize using the other constructor -- should be
+        // well-formed, but illegal at runtime
+        threw = catcherr.catch_llerrs([](){
+                PSing1::initParamSingleton(17);
+            });
+        ensure_contains("other ctor(int) didn't throw", threw, "twice");
+        PSing1::deleteSingleton();
+        ensure("false negative on wasDeleted()", PSing1::wasDeleted());
+        threw = catcherr.catch_llerrs([](){
+                (void)PSing1::instance();
+            });
+        ensure_contains("accessed deleted LLParamSingleton", threw, "deleted");
+    }
+
+    template<> template<>
+    void singleton_object_t::test<13>()
+    {
+        set_test_name("LLParamSingleton alternate ctor");
+
+        WrapLLErrs catcherr;
+        // We don't have to restate all the tests for PSing1. Only test validly
+        // using the other constructor.
+        PSing2::initParamSingleton(17);
+        ensure_equals(PSing2::instance().desc(), "17");
+        // can't do it twice
+        std::string threw = catcherr.catch_llerrs([](){
+                PSing2::initParamSingleton(34);
+            });
+        ensure_contains("second ctor(int) didn't throw", threw, "twice");
+        // can't use the other constructor either
+        threw = catcherr.catch_llerrs([](){
+                PSing2::initParamSingleton("string");
+            });
+        ensure_contains("other ctor(string) didn't throw", threw, "twice");
+    }
+
+    class CircularPCtor: public LLParamSingleton<CircularPCtor>
+    {
+        LLSINGLETON(CircularPCtor)
+        {
+            // never mind indirection, just go straight for the circularity
+            (void)instance();
+        }
+    };
+
+    template<> template<>
+    void singleton_object_t::test<14>()
+    {
+        set_test_name("Circular LLParamSingleton constructor");
+        WrapLLErrs catcherr;
+        std::string threw = catcherr.catch_llerrs([](){
+                CircularPCtor::initParamSingleton();
+            });
+        ensure_contains("constructor circularity didn't throw", threw, "constructor");
+    }
+
+    class CircularPInit: public LLParamSingleton<CircularPInit>
+    {
+        LLSINGLETON_EMPTY_CTOR(CircularPInit);
+    public:
+        virtual void initSingleton()
+        {
+            // never mind indirection, just go straight for the circularity
+            (void)instance();
+        }
+    };
+
+    template<> template<>
+    void singleton_object_t::test<15>()
+    {
+        set_test_name("Circular LLParamSingleton initSingleton()");
+        WrapLLErrs catcherr;
+        std::string threw = catcherr.catch_llerrs([](){
+                CircularPInit::initParamSingleton();
+            });
+        ensure("initSingleton() circularity threw", threw.empty());
+    }
 }
diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h
index 08fbf19b1cdccec7323769b515246737368de2d0..b07d5afbd8fa7a7582356f670b6f9bee27896dd6 100644
--- a/indra/llcommon/tests/wrapllerrs.h
+++ b/indra/llcommon/tests/wrapllerrs.h
@@ -37,6 +37,7 @@
 #include "llerrorcontrol.h"
 #include "llexception.h"
 #include "stringize.h"
+#include "../test/catch_and_store_what_in.h"
 #include <boost/bind.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
@@ -81,6 +82,31 @@ struct WrapLLErrs
         LLTHROW(FatalException(message));
     }
 
+    /// Convenience wrapper for catch_what<FatalException>()
+    //
+    // The implementation makes it clear that this function need not be a
+    // member; it could easily be a free function. It is a member because it
+    // makes no sense to attempt to catch FatalException unless there is a
+    // WrapLLErrs instance in scope. Without a live WrapLLErrs instance, any
+    // LL_ERRS() reached by code within 'func' would terminate the test
+    // program instead of throwing FatalException.
+    //
+    // We were tempted to introduce a free function, likewise accepting
+    // arbitrary 'func', that would instantiate WrapLLErrs and then call
+    // catch_llerrs() on that instance. We decided against it, for this
+    // reason: on extending a test function containing a single call to that
+    // free function, a maintainer would most likely make additional calls to
+    // that free function, instead of switching to an explicit WrapLLErrs
+    // declaration with several calls to its catch_llerrs() member function.
+    // Even a construct such as WrapLLErrs().catch_llerrs(...) would make the
+    // object declaration more visible; it's not unreasonable to expect a
+    // maintainer to extend that by naming and reusing the WrapLLErrs instance.
+    template <typename FUNC>
+    std::string catch_llerrs(FUNC func)
+    {
+        return catch_what<FatalException>(func);
+    }
+
     std::string error;
     LLError::SettingsStoragePtr mPriorErrorSettings;
     LLError::FatalFunction mPriorFatal;
diff --git a/indra/llmessage/tests/llareslistener_test.cpp b/indra/llmessage/tests/llareslistener_test.cpp
index c04696c86b721f5734214fc9ad369c3a9b44847c..254185cbd0981c9081cbd56e5966170b8370a610 100644
--- a/indra/llmessage/tests/llareslistener_test.cpp
+++ b/indra/llmessage/tests/llareslistener_test.cpp
@@ -138,15 +138,9 @@ namespace tut
         WrapLLErrs capture;
         LLSD request;
         request["op"] = "foo";
-        std::string threw;
-        try
-        {
-            LLEventPumps::instance().obtain("LLAres").post(request);
-        }
-        catch (const WrapLLErrs::FatalException& e)
-        {
-            threw = e.what();
-        }
+        std::string threw = capture.catch_llerrs([&request](){
+                LLEventPumps::instance().obtain("LLAres").post(request);
+            });
         ensure_contains("LLAresListener bad op", threw, "bad");
     }
 
@@ -157,15 +151,9 @@ namespace tut
         WrapLLErrs capture;
         LLSD request;
         request["op"] = "rewriteURI";
-        std::string threw;
-        try
-        {
-            LLEventPumps::instance().obtain("LLAres").post(request);
-        }
-        catch (const WrapLLErrs::FatalException& e)
-        {
-            threw = e.what();
-        }
+        std::string threw = capture.catch_llerrs([&request](){
+                LLEventPumps::instance().obtain("LLAres").post(request);
+            });
         ensure_contains("LLAresListener bad req", threw, "missing");
         ensure_contains("LLAresListener bad req", threw, "reply");
         ensure_contains("LLAresListener bad req", threw, "uri");
@@ -179,15 +167,9 @@ namespace tut
         LLSD request;
         request["op"] = "rewriteURI";
         request["reply"] = "nonexistent";
-        std::string threw;
-        try
-        {
-            LLEventPumps::instance().obtain("LLAres").post(request);
-        }
-        catch (const WrapLLErrs::FatalException& e)
-        {
-            threw = e.what();
-        }
+        std::string threw = capture.catch_llerrs([&request](){
+                LLEventPumps::instance().obtain("LLAres").post(request);
+            });
         ensure_contains("LLAresListener bad req", threw, "missing");
         ensure_contains("LLAresListener bad req", threw, "uri");
         ensure_does_not_contain("LLAresListener bad req", threw, "reply");
@@ -201,15 +183,9 @@ namespace tut
         LLSD request;
         request["op"] = "rewriteURI";
         request["uri"] = "foo.bar.com";
-        std::string threw;
-        try
-        {
-            LLEventPumps::instance().obtain("LLAres").post(request);
-        }
-        catch (const WrapLLErrs::FatalException& e)
-        {
-            threw = e.what();
-        }
+        std::string threw = capture.catch_llerrs([&request](){
+                LLEventPumps::instance().obtain("LLAres").post(request);
+            });
         ensure_contains("LLAresListener bad req", threw, "missing");
         ensure_contains("LLAresListener bad req", threw, "reply");
         ensure_does_not_contain("LLAresListener bad req", threw, "uri");
diff --git a/indra/newview/tests/llxmlrpclistener_test.cpp b/indra/newview/tests/llxmlrpclistener_test.cpp
index 6e9756e7d5305f9b8fd3e0a4b9242ac8d4420ee6..dbaae7280c6f71080ab59ff2cc65c8fed41a94bb 100644
--- a/indra/newview/tests/llxmlrpclistener_test.cpp
+++ b/indra/newview/tests/llxmlrpclistener_test.cpp
@@ -88,15 +88,9 @@ namespace tut
         WrapLLErrs capture;
         LLSD request;
         request["uri"] = uri;
-        std::string threw;
-        try
-        {
-            pumps.obtain("LLXMLRPCTransaction").post(request);
-        }
-        catch (const WrapLLErrs::FatalException& e)
-        {
-            threw = e.what();
-        }
+        std::string threw = capture.catch_llerrs([&pumps, &request](){
+                pumps.obtain("LLXMLRPCTransaction").post(request);
+            });
         ensure_contains("threw exception", threw, "missing params");
         ensure_contains("identified missing", threw, "method");
         ensure_contains("identified missing", threw, "reply");
@@ -113,15 +107,9 @@ namespace tut
         request["reply"] = "reply";
         LLSD& params(request["params"]);
         params["who"]["specifically"] = "world"; // LLXMLRPCListener only handles scalar params
-        std::string threw;
-        try
-        {
-            pumps.obtain("LLXMLRPCTransaction").post(request);
-        }
-        catch (const WrapLLErrs::FatalException& e)
-        {
-            threw = e.what();
-        }
+        std::string threw = capture.catch_llerrs([&pumps, &request](){
+                pumps.obtain("LLXMLRPCTransaction").post(request);
+            });
         ensure_contains("threw exception", threw, "unknown type");
     }
 
diff --git a/indra/test/catch_and_store_what_in.h b/indra/test/catch_and_store_what_in.h
index 59f8cc008585769dfe250e4c4ddde6059129cd43..5beba06024368dd878bd207484055c7600c3e7d3 100644
--- a/indra/test/catch_and_store_what_in.h
+++ b/indra/test/catch_and_store_what_in.h
@@ -2,7 +2,7 @@
  * @file   catch_and_store_what_in.h
  * @author Nat Goodspeed
  * @date   2012-02-15
- * @brief  CATCH_AND_STORE_WHAT_IN() macro
+ * @brief  catch_what() template function, CATCH_AND_STORE_WHAT_IN() macro
  * 
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Copyright (c) 2012, Linden Research, Inc.
@@ -12,6 +12,30 @@
 #if ! defined(LL_CATCH_AND_STORE_WHAT_IN_H)
 #define LL_CATCH_AND_STORE_WHAT_IN_H
 
+/**
+ * In the brave new world of lambdas, we can use a nicer C++ idiom for testing
+ * exceptions than CATCH_AND_STORE_WHAT_IN() below, e.g.:
+ *
+ * @code
+ * std::string threw = catch_what<std::runtime_error>(
+ *     [](){ throw std::runtime_error("badness"); });
+ * ensure_equals(threw, "badness");
+ * @endcode
+ */
+template <typename EXCEPTION, typename FUNC>
+std::string catch_what(FUNC func)
+{
+    try
+    {
+        func();
+        return {};
+    }
+    catch (const EXCEPTION& err)
+    {
+        return err.what();
+    }
+}
+
 /**
  * Idiom useful for test programs: catch an expected exception, store its
  * what() string in a specified std::string variable. From there the caller
diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp
index 16edab628229e6afad61886f43947fa2af17a167..3abae3e43ef4802be2b52b9d719f79e9e95fbd11 100644
--- a/indra/test/llevents_tut.cpp
+++ b/indra/test/llevents_tut.cpp
@@ -134,17 +134,15 @@ void events_object::test<1>()
 		per_frame.post(4);
 		check_listener("re-blocked", listener0, 3);
 	} // unblock
-	std::string threw;
-	try
-	{
-		// NOTE: boost::bind() saves its arguments by VALUE! If you pass
-		// an object instance rather than a pointer, you'll end up binding
-		// to an internal copy of that instance! Use boost::ref() to
-		// capture a reference instead.
-		per_frame.listen(listener0.getName(), // note bug, dup name
-						 boost::bind(&Listener::call, boost::ref(listener1), _1));
-	}
-	CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupListenerName)
+	std::string threw = catch_what<LLEventPump::DupListenerName>(
+		[&per_frame, this](){
+			// NOTE: boost::bind() saves its arguments by VALUE! If you pass
+			// an object instance rather than a pointer, you'll end up binding
+			// to an internal copy of that instance! Use boost::ref() to
+			// capture a reference instead.
+			per_frame.listen(listener0.getName(), // note bug, dup name
+							 boost::bind(&Listener::call, boost::ref(listener1), _1));
+		});
 	ensure_equals(threw,
 				  std::string("DupListenerName: "
 							  "Attempt to register duplicate listener name '") +
@@ -341,15 +339,13 @@ void events_object::test<7>()
 	ensure_equals(collector.result, make<StringVec>(list_of("Mary")("spot")("checked")));
 	collector.clear();
 	button.stopListening("spot");
-	std::string threw;
-	try
-	{
-		button.listen("spot",
-					  boost::bind(&Collect::add, boost::ref(collector), "spot", _1),
-					  // after "Mary" and "checked" -- whoops!
-			 		  make<NameList>(list_of("Mary")("checked")));
-	}
-	CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::Cycle)
+	std::string threw = catch_what<LLEventPump::Cycle>(
+		[&button, &collector](){
+			button.listen("spot",
+						  boost::bind(&Collect::add, boost::ref(collector), "spot", _1),
+						  // after "Mary" and "checked" -- whoops!
+						  make<NameList>(list_of("Mary")("checked")));
+		});
 	// Obviously the specific wording of the exception text can
 	// change; go ahead and change the test to match.
 	// Establish that it contains:
@@ -374,15 +370,13 @@ void events_object::test<7>()
 	button.post(3);
 	ensure_equals(collector.result, make<StringVec>(list_of("Mary")("checked")("yellow")("shoelaces")));
 	collector.clear();
-	threw.clear();
-	try
-	{
-		button.listen("of",
-					  boost::bind(&Collect::add, boost::ref(collector), "of", _1),
-					  make<NameList>(list_of("shoelaces")),
-					  make<NameList>(list_of("yellow")));
-	}
-	CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::OrderChange)
+	threw = catch_what<LLEventPump::OrderChange>(
+		[&button, &collector](){
+			button.listen("of",
+						  boost::bind(&Collect::add, boost::ref(collector), "of", _1),
+						  make<NameList>(list_of("shoelaces")),
+						  make<NameList>(list_of("yellow")));
+		});
 	// Same remarks about the specific wording of the exception. Just
 	// ensure that it contains enough information to clarify the
 	// problem and what must be done to resolve it.
@@ -404,13 +398,11 @@ void events_object::test<8>()
 	{ 	// nested scope
 		// Hand-instantiate an LLEventStream...
 		LLEventStream bob("bob");
-		std::string threw;
-		try
-		{
-			// then another with a duplicate name.
-			LLEventStream bob2("bob");
-		}
-		CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupPumpName)
+		std::string threw = catch_what<LLEventPump::DupPumpName>(
+			[](){
+				// then another with a duplicate name.
+				LLEventStream bob2("bob");
+			});
 		ensure("Caught DupPumpName", !threw.empty());
 	} 	// delete first 'bob'
 	LLEventStream bob("bob"); 		// should work, previous one unregistered
@@ -445,13 +437,11 @@ void events_object::test<9>()
 	listener0.listenTo(random);
 	eventSource("random");
 	check_listener("got by pump name", listener0, 17);
-	std::string threw;
-	try
-	{
-		LLListenerOrPumpName empty;
-		empty(17);
-	}
-	CATCH_AND_STORE_WHAT_IN(threw, LLListenerOrPumpName::Empty)
+	std::string threw = catch_what<LLListenerOrPumpName::Empty>(
+		[](){
+			LLListenerOrPumpName empty;
+			empty(17);
+		});
 
 	ensure("threw Empty", !threw.empty());
 }
diff --git a/indra/test/test.cpp b/indra/test/test.cpp
index 861ec1d942fac8120aac7435dbf6fdbe5106ca7b..d4cd4b951e72e031698ed9cab8c27fa51520bf11 100644
--- a/indra/test/test.cpp
+++ b/indra/test/test.cpp
@@ -37,7 +37,6 @@
 #include "linden_common.h"
 #include "llerrorcontrol.h"
 #include "lltut.h"
-#include "tests/wrapllerrs.h"             // RecorderProxy
 #include "stringize.h"
 #include "namedtempfile.h"
 #include "lltrace.h"