From 087bd265534b8e3086ae1af441a9cf0eb7c684df Mon Sep 17 00:00:00 2001
From: Brad Kittenbrink <brad@lindenlab.com>
Date: Thu, 4 Jun 2009 16:24:21 +0000
Subject: [PATCH] Merge of QAR-1383 event-system-7 into trunk.

svn merge -r 121797:121853 svn+ssh://svn.lindenlab.com/svn/linden/branches/merge-event-system-7
---
 indra/llcommon/CMakeLists.txt                 |   9 +
 indra/llcommon/lldependencies.cpp             |  86 ++
 indra/llcommon/lldependencies.h               | 779 +++++++++++++++++
 indra/llcommon/llerror.cpp                    |  10 +-
 indra/llcommon/llerrorcontrol.h               |  32 +-
 indra/llcommon/llevent.cpp                    |   2 +
 indra/llcommon/llevent.h                      |   5 +
 indra/llcommon/llevents.cpp                   | 501 +++++++++++
 indra/llcommon/llevents.h                     | 822 ++++++++++++++++++
 indra/llcommon/lllazy.cpp                     |  23 +
 indra/llcommon/lllazy.h                       | 382 ++++++++
 indra/llcommon/stringize.h                    |  75 ++
 indra/llcommon/tests/lllazy_test.cpp          | 227 +++++
 indra/llmessage/CMakeLists.txt                |   6 +-
 indra/llmessage/llsdmessage.cpp               | 150 ++++
 indra/llmessage/llsdmessage.h                 | 146 ++++
 indra/llmessage/tests/llsdmessage_test.cpp    | 113 +++
 indra/llui/llmenugl.cpp                       |   2 +
 indra/llui/llmenugl.h                         |   2 +-
 indra/llui/llnotifications.cpp                |  23 +-
 indra/llui/llnotifications.h                  |  87 +-
 indra/llui/llpanel.h                          |   3 +-
 indra/llui/llui.h                             |   4 +-
 indra/llui/llview.cpp                         |   2 +
 indra/llui/llview.h                           |  12 +-
 indra/llxml/CMakeLists.txt                    |   1 -
 indra/llxml/llcontrol.h                       |   4 +-
 indra/newview/CMakeLists.txt                  |   5 +
 indra/newview/llagent.cpp                     | 209 +++--
 indra/newview/llagent.h                       |   4 +-
 indra/newview/llagentlanguage.h               |   2 +-
 indra/newview/llappviewer.cpp                 |  13 +-
 indra/newview/llcapabilitylistener.cpp        | 183 ++++
 indra/newview/llcapabilitylistener.h          | 113 +++
 indra/newview/llcapabilityprovider.h          |  39 +
 indra/newview/llfloatergroups.cpp             |   2 +
 indra/newview/llfloatergroups.h               |   4 +-
 indra/newview/llinventorybridge.cpp           |   2 +
 indra/newview/llnetmap.cpp                    |   2 +
 indra/newview/llnetmap.h                      |  10 +-
 indra/newview/llviewercontrol.h               |   8 +-
 indra/newview/llviewerinventory.cpp           |  63 +-
 indra/newview/llviewermedia.cpp               |   2 +-
 indra/newview/llviewermenu.cpp                |   3 +-
 indra/newview/llviewermenufile.cpp            |   2 +
 indra/newview/llviewerregion.cpp              |  33 +-
 indra/newview/llviewerregion.h                |  26 +-
 indra/newview/llviewerthrottle.cpp            |   2 +
 .../tests/llcapabilitylistener_test.cpp       | 274 ++++++
 indra/test/CMakeLists.txt                     |   6 +-
 indra/test/llevents_tut.cpp                   | 793 +++++++++++++++++
 install.xml                                   |  16 +-
 52 files changed, 5071 insertions(+), 253 deletions(-)
 create mode 100644 indra/llcommon/lldependencies.cpp
 create mode 100644 indra/llcommon/lldependencies.h
 create mode 100644 indra/llcommon/llevents.cpp
 create mode 100644 indra/llcommon/llevents.h
 create mode 100644 indra/llcommon/lllazy.cpp
 create mode 100644 indra/llcommon/lllazy.h
 create mode 100644 indra/llcommon/stringize.h
 create mode 100644 indra/llcommon/tests/lllazy_test.cpp
 create mode 100644 indra/llmessage/llsdmessage.cpp
 create mode 100644 indra/llmessage/llsdmessage.h
 create mode 100644 indra/llmessage/tests/llsdmessage_test.cpp
 create mode 100644 indra/newview/llcapabilitylistener.cpp
 create mode 100644 indra/newview/llcapabilitylistener.h
 create mode 100644 indra/newview/llcapabilityprovider.h
 create mode 100644 indra/newview/tests/llcapabilitylistener_test.cpp
 create mode 100644 indra/test/llevents_tut.cpp

diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index d6a9e107071..51bd4354dfe 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -3,6 +3,7 @@
 project(llcommon)
 
 include(00-Common)
+include(LLAddBuildTest)
 include(LLCommon)
 
 include_directories(
@@ -22,9 +23,11 @@ set(llcommon_SOURCE_FILES
     llcriticaldamp.cpp
     llcursortypes.cpp
     lldate.cpp
+    lldependencies.cpp
     llerror.cpp
     llerrorthread.cpp
     llevent.cpp
+    llevents.cpp
     llfasttimer.cpp
     llfile.cpp
     llfindlocale.cpp
@@ -95,6 +98,7 @@ set(llcommon_HEADER_FILES
     lldarrayptr.h
     lldate.h
     lldefs.h
+    lldependencies.h
     lldepthstack.h
     lldlinked.h
     lldqueueptr.h
@@ -105,6 +109,7 @@ set(llcommon_HEADER_FILES
     llerrorlegacy.h
     llerrorthread.h
     llevent.h
+    llevents.h
     lleventemitter.h
     llextendedstatus.h
     llfasttimer.h
@@ -118,6 +123,7 @@ set(llcommon_HEADER_FILES
     llhttpstatuscodes.h
     llindexedqueue.h
     llkeythrottle.h
+    lllazy.h
     lllinkedqueue.h
     llliveappconfig.h
     lllivefile.h
@@ -176,6 +182,7 @@ set(llcommon_HEADER_FILES
     stdenums.h
     stdtypes.h
     string_table.h
+    stringize.h
     timer.h
     timing.h
     u64.h
@@ -194,3 +201,5 @@ target_link_libraries(
     ${EXPAT_LIBRARIES}
     ${ZLIB_LIBRARIES}
     )
+
+ADD_BUILD_TEST(lllazy llcommon)
diff --git a/indra/llcommon/lldependencies.cpp b/indra/llcommon/lldependencies.cpp
new file mode 100644
index 00000000000..ffb5cfbdaa3
--- /dev/null
+++ b/indra/llcommon/lldependencies.cpp
@@ -0,0 +1,86 @@
+/**
+ * @file   lldependencies.cpp
+ * @author Nat Goodspeed
+ * @date   2008-09-17
+ * @brief  Implementation for lldependencies.
+ * 
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ * Copyright (c) 2008, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "lldependencies.h"
+// STL headers
+#include <map>
+#include <sstream>
+// std headers
+// external library headers
+#include <boost/graph/graph_traits.hpp>  // for boost::graph_traits
+#include <boost/graph/adjacency_list.hpp>
+#include <boost/graph/topological_sort.hpp>
+#include <boost/graph/exception.hpp>
+// other Linden headers
+
+LLDependenciesBase::VertexList LLDependenciesBase::topo_sort(int vertices, const EdgeList& edges) const
+{
+    // Construct a Boost Graph Library graph according to the constraints
+    // we've collected. It seems as though we ought to be able to capture
+    // the uniqueness of vertex keys using a setS of vertices with a
+    // string property -- but I don't yet understand adjacency_list well
+    // enough to get there. All the examples I've seen so far use integers
+    // for vertices.
+    // Define the Graph type. Use a vector for vertices so we can use the
+    // default topological_sort vertex lookup by int index. Use a set for
+    // edges because the same dependency may be stated twice: Node "a" may
+    // specify that it must precede "b", while "b" may also state that it
+    // must follow "a".
+    typedef boost::adjacency_list<boost::setS, boost::vecS, boost::directedS,
+                                  boost::no_property> Graph;
+    // Instantiate the graph. Without vertex properties, we need say no
+    // more about vertices than the total number.
+    Graph g(edges.begin(), edges.end(), vertices);
+    // topo sort
+    typedef boost::graph_traits<Graph>::vertex_descriptor VertexDesc;
+    typedef std::vector<VertexDesc> SortedList;
+    SortedList sorted;
+    // note that it throws not_a_dag if it finds a cycle
+    try
+    {
+        boost::topological_sort(g, std::back_inserter(sorted));
+    }
+    catch (const boost::not_a_dag& e)
+    {
+        // translate to the exception we define
+        std::ostringstream out;
+        out << "LLDependencies cycle: " << e.what() << '\n';
+        // Omit independent nodes: display only those that might contribute to
+        // the cycle.
+        describe(out, false);
+        throw Cycle(out.str());
+    }
+    // A peculiarity of boost::topological_sort() is that it emits results in
+    // REVERSE topological order: to get the result you want, you must
+    // traverse the SortedList using reverse iterators.
+    return VertexList(sorted.rbegin(), sorted.rend());
+}
+
+std::ostream& LLDependenciesBase::describe(std::ostream& out, bool full) const
+{
+    // Should never encounter this base-class implementation; may mean that
+    // the KEY type doesn't have a suitable ostream operator<<().
+    out << "<no description available>";
+    return out;
+}
+
+std::string LLDependenciesBase::describe(bool full) const
+{
+    // Just use the ostream-based describe() on a std::ostringstream. The
+    // implementation is here mostly so that we can avoid #include <sstream>
+    // in the header file.
+    std::ostringstream out;
+    describe(out, full);
+    return out.str();
+}
diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h
new file mode 100644
index 00000000000..bd4bd7c96ab
--- /dev/null
+++ b/indra/llcommon/lldependencies.h
@@ -0,0 +1,779 @@
+/**
+ * @file   lldependencies.h
+ * @author Nat Goodspeed
+ * @date   2008-09-17
+ * @brief  LLDependencies: a generic mechanism for expressing "b must follow a,
+ *         but precede c"
+ *
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ * Copyright (c) 2008, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLDEPENDENCIES_H)
+#define LL_LLDEPENDENCIES_H
+
+#include <string>
+#include <vector>
+#include <set>
+#include <map>
+#include <stdexcept>
+#include <iosfwd>
+#include <boost/iterator/transform_iterator.hpp>
+#include <boost/iterator/indirect_iterator.hpp>
+#include <boost/range/iterator_range.hpp>
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+
+/*****************************************************************************
+*   Utilities
+*****************************************************************************/
+/**
+ * generic range transformer: given a range acceptable to Boost.Range (e.g. a
+ * standard container, an iterator pair, ...) and a unary function to apply to
+ * each element of the range, make a corresponding range that lazily applies
+ * that function to each element on dereferencing.
+ */
+template<typename FUNCTION, typename RANGE>
+inline
+boost::iterator_range<boost::transform_iterator<FUNCTION,
+                                                typename boost::range_const_iterator<RANGE>::type> >
+make_transform_range(const RANGE& range, FUNCTION function)
+{
+    // shorthand for the iterator type embedded in our return type
+    typedef boost::transform_iterator<FUNCTION, typename boost::range_const_iterator<RANGE>::type>
+        transform_iterator;
+    return boost::make_iterator_range(transform_iterator(boost::begin(range), function),
+                                      transform_iterator(boost::end(range),   function));
+}
+
+/// non-const version of make_transform_range()
+template<typename FUNCTION, typename RANGE>
+inline
+boost::iterator_range<boost::transform_iterator<FUNCTION,
+                                                typename boost::range_iterator<RANGE>::type> >
+make_transform_range(RANGE& range, FUNCTION function)
+{
+    // shorthand for the iterator type embedded in our return type
+    typedef boost::transform_iterator<FUNCTION, typename boost::range_iterator<RANGE>::type>
+        transform_iterator;
+    return boost::make_iterator_range(transform_iterator(boost::begin(range), function),
+                                      transform_iterator(boost::end(range),   function));
+}
+
+/**
+ * From any range compatible with Boost.Range, instantiate any class capable
+ * of accepting an iterator pair.
+ */
+template<class TYPE>
+struct instance_from_range: public TYPE
+{
+    template<typename RANGE>
+    instance_from_range(RANGE range):
+        TYPE(boost::begin(range), boost::end(range))
+    {}
+};
+
+/*****************************************************************************
+*   LLDependencies
+*****************************************************************************/
+/**
+ * LLDependencies components that should not be reinstantiated for each KEY,
+ * NODE specialization
+ */
+class LLDependenciesBase
+{
+public:
+    virtual ~LLDependenciesBase() {}
+
+    /**
+     * Exception thrown by sort() if there's a cycle
+     */
+    struct Cycle: public std::runtime_error
+    {
+        Cycle(const std::string& what): std::runtime_error(what) {}
+    };
+
+    /**
+     * Provide a short description of this LLDependencies instance on the
+     * specified output stream, assuming that its KEY type has an operator<<()
+     * that works with std::ostream.
+     *
+     * Pass @a full as @c false to omit any keys without dependency constraints.
+     */
+    virtual std::ostream& describe(std::ostream& out, bool full=true) const;
+
+    /// describe() to a string
+    virtual std::string describe(bool full=true) const;
+
+protected:
+    typedef std::vector< std::pair<int, int> > EdgeList;
+    typedef std::vector<int> VertexList;
+    VertexList topo_sort(int vertices, const EdgeList& edges) const;
+
+    /**
+     * refpair is specifically intended to capture a pair of references. This
+     * is better than std::pair<T1&, T2&> because some implementations of
+     * std::pair's ctor accept const references to the two types. If the
+     * types are themselves references, this results in an illegal reference-
+     * to-reference.
+     */
+    template<typename T1, typename T2>
+    struct refpair
+    {
+        refpair(T1 value1, T2 value2):
+            first(value1),
+            second(value2)
+        {}
+        T1 first;
+        T2 second;
+    };
+};
+
+/// describe() helper: for most types, report the type as usual
+template<typename T>
+inline
+std::ostream& LLDependencies_describe(std::ostream& out, const T& key)
+{
+    out << key;
+    return out;
+}
+
+/// specialize LLDependencies_describe() for std::string
+template<>
+inline
+std::ostream& LLDependencies_describe(std::ostream& out, const std::string& key)
+{
+    out << '"' << key << '"';
+    return out;
+}
+
+/**
+ * It's reasonable to use LLDependencies in a keys-only way, more or less like
+ * std::set. For that case, the default NODE type is an empty struct.
+ */
+struct LLDependenciesEmpty
+{
+    LLDependenciesEmpty() {}
+    /**
+     * Give it a constructor accepting void* so caller can pass placeholder
+     * values such as NULL or 0 rather than having to write
+     * LLDependenciesEmpty().
+     */
+    LLDependenciesEmpty(void*) {}    
+};
+
+/**
+ * This class manages abstract dependencies between node types of your
+ * choosing. As with a std::map, nodes are copied when add()ed, so the node
+ * type should be relatively lightweight; to manipulate dependencies between
+ * expensive objects, use a pointer type.
+ *
+ * For a given node, you may state the keys of nodes that must precede it
+ * and/or nodes that must follow it. The sort() method will produce an order
+ * that should work, or throw an exception if the constraints are impossible.
+ * We cache results to minimize the cost of repeated sort() calls.
+ */
+template<typename KEY = std::string,
+         typename NODE = LLDependenciesEmpty>
+class LLDependencies: public LLDependenciesBase
+{
+    typedef LLDependencies<KEY, NODE> self_type;
+
+    /**
+     * Internally, we bundle the client's NODE with its before/after keys.
+     */
+    struct DepNode
+    {
+        typedef std::set<KEY> dep_set;
+        DepNode(const NODE& node_, const dep_set& after_, const dep_set& before_):
+            node(node_),
+            after(after_),
+            before(before_)
+        {}
+        NODE node;
+        dep_set after, before;    
+    };
+    typedef std::map<KEY, DepNode> DepNodeMap;
+    typedef typename DepNodeMap::value_type DepNodeMapEntry;
+
+    /// We have various ways to get the dependencies for a given DepNode.
+    /// Rather than having to restate each one for 'after' and 'before'
+    /// separately, pass a dep_selector so we can apply each to either.
+    typedef boost::function<const typename DepNode::dep_set&(const DepNode&)> dep_selector;
+
+public:
+    LLDependencies() {}
+
+    typedef KEY key_type;
+    typedef NODE node_type;
+
+    /// param type used to express lists of other node keys -- note that such
+    /// lists can be initialized with boost::assign::list_of()
+    typedef std::vector<KEY> KeyList;
+
+    /**
+     * Add a new node. State its dependencies on other nodes (which may not
+     * yet have been added) by listing the keys of nodes this new one must
+     * follow, and separately the keys of nodes this new one must precede.
+     *
+     * The node you pass is @em copied into an internal data structure. If you
+     * want to modify the node value after add()ing it, capture the returned
+     * NODE& reference.
+     *
+     * @note
+     * Actual dependency analysis is deferred to the sort() method, so 
+     * you can add an arbitrary number of nodes without incurring analysis
+     * overhead for each. The flip side of this is that add()ing nodes that
+     * define a cycle leaves this object in a state in which sort() will
+     * always throw the Cycle exception.
+     *
+     * Two distinct use cases are anticipated:
+     * * The data used to load this object are completely known at compile
+     * time (e.g. LLEventPump listener names). A Cycle exception represents a
+     * bug which can be corrected by the coder. The program need neither catch
+     * Cycle nor attempt to salvage the state of this object.
+     * * The data are loaded at runtime, therefore the universe of
+     * dependencies cannot be known at compile time. The client code should
+     * catch Cycle.
+     * ** If a Cycle exception indicates fatally-flawed input data, this
+     * object can simply be discarded, possibly with the entire program run.
+     * ** If it is essential to restore this object to a working state, the
+     * simplest workaround is to remove() nodes in LIFO order.
+     * *** It may be useful to add functionality to this class to track the
+     * add() chronology, providing a pop() method to remove the most recently
+     * added node.
+     * *** It may further be useful to add a restore() method which would
+     * pop() until sort() no longer throws Cycle. This method would be
+     * expensive -- but it's not clear that client code could resolve the
+     * problem more cheaply.
+     */
+    NODE& add(const KEY& key, const NODE& node = NODE(),
+              const KeyList& after = KeyList(),
+              const KeyList& before = KeyList())
+    {
+        // Get the passed-in lists as sets for equality comparison
+        typename DepNode::dep_set
+            after_set(after.begin(), after.end()),
+            before_set(before.begin(), before.end());
+        // Try to insert the new node; if it already exists, find the old
+        // node instead.
+        std::pair<typename DepNodeMap::iterator, bool> inserted =
+            mNodes.insert(typename DepNodeMap::value_type(key,
+                                                          DepNode(node, after_set, before_set)));
+        if (! inserted.second)      // bool indicating success of insert()
+        {
+            // We already have a node by this name. Have its dependencies
+            // changed? If the existing node's dependencies are identical, the
+            // result will be unchanged, so we can leave the cache intact.
+            // Regardless of inserted.second, inserted.first is the iterator
+            // to the newly-inserted (or existing) map entry. Of course, that
+            // entry's second is the DepNode of interest.
+            if (inserted.first->second.after  != after_set ||
+                inserted.first->second.before != before_set)
+            {
+                // Dependencies have changed: clear the cached result.
+                mCache.clear();
+                // save the new dependencies
+                inserted.first->second.after  = after_set;
+                inserted.first->second.before = before_set;
+            }
+        }
+        else                        // this node is new
+        {
+            // This will change results.
+            mCache.clear();
+        }
+        return inserted.first->second.node;
+    }
+
+    /// the value of an iterator, showing both KEY and its NODE
+    typedef refpair<const KEY&, NODE&> value_type;
+    /// the value of a const_iterator
+    typedef refpair<const KEY&, const NODE&> const_value_type;
+
+private:
+    // Extract functors
+    static value_type value_extract(DepNodeMapEntry& entry)
+    {
+        return value_type(entry.first, entry.second.node);
+    }
+
+    static const_value_type const_value_extract(const DepNodeMapEntry& entry)
+    {
+        return const_value_type(entry.first, entry.second.node);
+    }
+
+    // All the iterator access methods return iterator ranges just to cut down
+    // on the friggin' boilerplate!!
+
+    /// generic mNodes range method
+    template<typename ITERATOR, typename FUNCTION>
+    boost::iterator_range<ITERATOR> generic_range(FUNCTION function)
+    {
+        return make_transform_range(mNodes, function);
+    }
+
+    /// generic mNodes const range method
+    template<typename ITERATOR, typename FUNCTION>
+    boost::iterator_range<ITERATOR> generic_range(FUNCTION function) const
+    {
+        return make_transform_range(mNodes, function);
+    }
+
+public:
+    /// iterator over value_type entries
+    typedef boost::transform_iterator<boost::function<value_type(DepNodeMapEntry&)>,
+                                      typename DepNodeMap::iterator> iterator;
+    /// range over value_type entries
+    typedef boost::iterator_range<iterator> range;
+
+    /// iterate over value_type <i>in @c KEY order</i> rather than dependency order
+    range get_range()
+    {
+        return generic_range<iterator>(value_extract);
+    }
+
+    /// iterator over const_value_type entries
+    typedef boost::transform_iterator<boost::function<const_value_type(const DepNodeMapEntry&)>,
+                                      typename DepNodeMap::const_iterator> const_iterator;
+    /// range over const_value_type entries
+    typedef boost::iterator_range<const_iterator> const_range;
+
+    /// iterate over const_value_type <i>in @c KEY order</i> rather than dependency order
+    const_range get_range() const
+    {
+        return generic_range<const_iterator>(const_value_extract);
+    }
+
+    /// iterator over stored NODEs
+    typedef boost::transform_iterator<boost::function<NODE&(DepNodeMapEntry&)>,
+                                      typename DepNodeMap::iterator> node_iterator;
+    /// range over stored NODEs
+    typedef boost::iterator_range<node_iterator> node_range;
+
+    /// iterate over NODE <i>in @c KEY order</i> rather than dependency order
+    node_range get_node_range()
+    {
+        // First take a DepNodeMapEntry and extract a reference to its
+        // DepNode, then from that extract a reference to its NODE.
+        return generic_range<node_iterator>(
+            boost::bind<NODE&>(&DepNode::node,
+                               boost::bind<DepNode&>(&DepNodeMapEntry::second, _1)));
+    }
+
+    /// const iterator over stored NODEs
+    typedef boost::transform_iterator<boost::function<const NODE&(const DepNodeMapEntry&)>,
+                                      typename DepNodeMap::const_iterator> const_node_iterator;
+    /// const range over stored NODEs
+    typedef boost::iterator_range<const_node_iterator> const_node_range;
+
+    /// iterate over const NODE <i>in @c KEY order</i> rather than dependency order
+    const_node_range get_node_range() const
+    {
+        // First take a DepNodeMapEntry and extract a reference to its
+        // DepNode, then from that extract a reference to its NODE.
+        return generic_range<const_node_iterator>(
+            boost::bind<const NODE&>(&DepNode::node,
+                                     boost::bind<const DepNode&>(&DepNodeMapEntry::second, _1)));
+    }
+
+    /// const iterator over stored KEYs
+    typedef boost::transform_iterator<boost::function<const KEY&(const DepNodeMapEntry&)>,
+                                      typename DepNodeMap::const_iterator> const_key_iterator;
+    /// const range over stored KEYs
+    typedef boost::iterator_range<const_key_iterator> const_key_range;
+    // We don't provide a non-const iterator over KEYs because they should be
+    // immutable, and in fact our underlying std::map won't give us non-const
+    // references.
+
+    /// iterate over const KEY <i>in @c KEY order</i> rather than dependency order
+    const_key_range get_key_range() const
+    {
+        // From a DepNodeMapEntry, extract a reference to its KEY.
+        return generic_range<const_key_iterator>(
+            boost::bind<const KEY&>(&DepNodeMapEntry::first, _1));
+    }
+
+    /**
+     * Find an existing NODE, or return NULL. We decided to avoid providing a
+     * method analogous to std::map::find(), for a couple of reasons:
+     *
+     * * For a find-by-key, getting back an iterator to the (key, value) pair
+     * is less than useful, since you already have the key in hand.
+     * * For a failed request, comparing to end() is problematic. First, we
+     * provide range accessors, so it's more code to get end(). Second, we
+     * provide a number of different ranges -- quick, to which one's end()
+     * should we compare the iterator returned by find()?
+     *
+     * The returned pointer is solely to allow expressing the not-found
+     * condition. LLDependencies still owns the found NODE.
+     */
+    const NODE* get(const KEY& key) const
+    {
+        typename DepNodeMap::const_iterator found = mNodes.find(key);
+        if (found != mNodes.end())
+        {
+            return &found->second.node;
+        }
+        return NULL;
+    }
+
+    /**
+     * non-const get()
+     */
+    NODE* get(const KEY& key)
+    {
+        // Use const implementation, then cast away const-ness of return
+        return const_cast<NODE*>(const_cast<const self_type*>(this)->get(key));
+    }
+
+    /**
+     * Remove a node with specified key. This operation is the major reason
+     * we rebuild the graph on the fly instead of storing it.
+     */
+    bool remove(const KEY& key)
+    {
+        typename DepNodeMap::iterator found = mNodes.find(key);
+        if (found != mNodes.end())
+        {
+            mNodes.erase(found);
+            return true;
+        }
+        return false;
+    }
+
+private:
+    /// cached list of iterators
+    typedef std::vector<iterator> iterator_list;
+    typedef typename iterator_list::iterator iterator_list_iterator;
+
+public:
+    /**
+     * The return type of the sort() method needs some explanation. Provide a
+     * public typedef to facilitate storing the result.
+     *
+     * * We will prepare mCache by looking up DepNodeMap iterators.
+     * * We want to return a range containing iterators that will walk mCache.
+     * * If we simply stored DepNodeMap iterators and returned
+     * (mCache.begin(), mCache.end()), dereferencing each iterator would
+     * obtain a DepNodeMap iterator.
+     * * We want the caller to loop over @c value_type: pair<KEY, NODE>.
+     * * This requires two transformations:
+     * ** mCache must contain @c LLDependencies::iterator so that
+     * dereferencing each entry will obtain an @c LLDependencies::value_type
+     * rather than a DepNodeMapEntry.
+     * ** We must wrap mCache's iterators in boost::indirect_iterator so that
+     * dereferencing one of our returned iterators will also dereference the
+     * iterator contained in mCache.
+     */
+    typedef boost::iterator_range<boost::indirect_iterator<iterator_list_iterator> > sorted_range;
+    /// for convenience in looping over a sorted_range
+    typedef typename sorted_range::iterator sorted_iterator;
+
+    /**
+     * Once we've loaded in the dependencies of interest, arrange them into an
+     * order that works -- or throw Cycle exception.
+     *
+     * Return an iterator range over (key, node) pairs that traverses them in
+     * the desired order.
+     */
+    sorted_range sort() const
+    {
+        // Changes to mNodes cause us to clear our cache, so empty mCache
+        // means it's invalid and should be recomputed. However, if mNodes is
+        // also empty, then an empty mCache represents a valid order, so don't
+        // bother sorting.
+        if (mCache.empty() && ! mNodes.empty())
+        {
+            // Construct a map of node keys to distinct vertex numbers -- even for
+            // nodes mentioned only in before/after constraints, that haven't yet
+            // been explicitly added. Rely on std::map rejecting a second attempt
+            // to insert the same key. Use the map's size() as the vertex number
+            // to get a distinct value for each successful insertion.
+            typedef std::map<KEY, int> VertexMap;
+            VertexMap vmap;
+            // Nest each of these loops because !@#$%? MSVC warns us that its
+            // former broken behavior has finally been fixed -- and our builds
+            // treat warnings as errors.
+            {
+                for (typename DepNodeMap::const_iterator nmi = mNodes.begin(), nmend = mNodes.end();
+                     nmi != nmend; ++nmi)
+                {
+                    vmap.insert(typename VertexMap::value_type(nmi->first, vmap.size()));
+                    for (typename DepNode::dep_set::const_iterator ai = nmi->second.after.begin(),
+                                                                   aend = nmi->second.after.end();
+                         ai != aend; ++ai)
+                    {
+                        vmap.insert(typename VertexMap::value_type(*ai, vmap.size()));
+                    }
+                    for (typename DepNode::dep_set::const_iterator bi = nmi->second.before.begin(),
+                                                                   bend = nmi->second.before.end();
+                         bi != bend; ++bi)
+                    {
+                        vmap.insert(typename VertexMap::value_type(*bi, vmap.size()));
+                    }
+                }
+            }
+            // Define the edges. For this we must traverse mNodes again, mapping
+            // all the known key dependencies to integer pairs.
+            EdgeList edges;
+            {
+                for (typename DepNodeMap::const_iterator nmi = mNodes.begin(), nmend = mNodes.end();
+                     nmi != nmend; ++nmi)
+                {
+                    int thisnode = vmap[nmi->first];
+                    // after dependencies: build edges from the named node to this one
+                    for (typename DepNode::dep_set::const_iterator ai = nmi->second.after.begin(),
+                                                                   aend = nmi->second.after.end();
+                         ai != aend; ++ai)
+                    {
+                        edges.push_back(EdgeList::value_type(vmap[*ai], thisnode));
+                    }
+                    // before dependencies: build edges from this node to the
+                    // named one
+                    for (typename DepNode::dep_set::const_iterator bi = nmi->second.before.begin(),
+                                                                   bend = nmi->second.before.end();
+                         bi != bend; ++bi)
+                    {
+                        edges.push_back(EdgeList::value_type(thisnode, vmap[*bi]));
+                    }
+                }
+            }
+            // Hide the gory details of our topological sort, since they shouldn't
+            // get reinstantiated for each distinct NODE type.
+            VertexList sorted(topo_sort(vmap.size(), edges));
+            // Build the reverse of vmap to look up the key for each vertex
+            // descriptor. vmap contains exactly one entry for each distinct key,
+            // and we're certain that the associated int values are distinct
+            // indexes. The fact that they're not in order is irrelevant.
+            KeyList vkeys(vmap.size());
+            for (typename VertexMap::const_iterator vmi = vmap.begin(), vmend = vmap.end();
+                 vmi != vmend; ++vmi)
+            {
+                vkeys[vmi->second] = vmi->first;
+            }
+            // Walk the sorted output list, building the result into mCache so
+            // we'll have it next time someone asks.
+            mCache.clear();
+            for (VertexList::const_iterator svi = sorted.begin(), svend = sorted.end();
+                 svi != svend; ++svi)
+            {
+                // We're certain that vkeys[*svi] exists. However, there might not
+                // yet be a corresponding entry in mNodes.
+                self_type* non_const_this(const_cast<self_type*>(this));
+                typename DepNodeMap::iterator found = non_const_this->mNodes.find(vkeys[*svi]);
+                if (found != non_const_this->mNodes.end())
+                {
+                    // Make an iterator of appropriate type.
+                    mCache.push_back(iterator(found, value_extract));
+                }
+            }
+        }
+        // Whether or not we've just recomputed mCache, it should now contain
+        // the results we want. Return a range of indirect_iterators over it
+        // so that dereferencing a returned iterator will dereference the
+        // iterator stored in mCache and directly reference the (key, node)
+        // pair.
+        boost::indirect_iterator<iterator_list_iterator>
+            begin(mCache.begin()),
+            end(mCache.end());
+        return sorted_range(begin, end);
+    }
+
+    /// Override base-class describe() with actual implementation
+    virtual std::ostream& describe(std::ostream& out, bool full=true) const
+    {
+        typename DepNodeMap::const_iterator dmi(mNodes.begin()), dmend(mNodes.end());
+        if (dmi != dmend)
+        {
+            std::string sep;
+            describe(out, sep, *dmi, full);
+            while (++dmi != dmend)
+            {
+                describe(out, sep, *dmi, full);
+            }
+        }
+        return out;
+    }
+
+    /// describe() helper: report a DepNodeEntry
+    static std::ostream& describe(std::ostream& out, std::string& sep,
+                                  const DepNodeMapEntry& entry, bool full)
+    {
+        // If we were asked for a full report, describe every node regardless
+        // of whether it has dependencies. If we were asked to suppress
+        // independent nodes, describe this one if either after or before is
+        // non-empty.
+        if (full || (! entry.second.after.empty()) || (! entry.second.before.empty()))
+        {
+            out << sep;
+            sep = "\n";
+            if (! entry.second.after.empty())
+            {
+                out << "after ";
+                describe(out, entry.second.after);
+                out << " -> ";
+            }
+            LLDependencies_describe(out, entry.first);
+            if (! entry.second.before.empty())
+            {
+                out << " -> before ";
+                describe(out, entry.second.before);
+            }
+        }
+        return out;
+    }
+
+    /// describe() helper: report a dep_set
+    static std::ostream& describe(std::ostream& out, const typename DepNode::dep_set& keys)
+    {
+        out << '(';
+        typename DepNode::dep_set::const_iterator ki(keys.begin()), kend(keys.end());
+        if (ki != kend)
+        {
+            LLDependencies_describe(out, *ki);
+            while (++ki != kend)
+            {
+                out << ", ";
+                LLDependencies_describe(out, *ki);
+            }
+        }
+        out << ')';
+        return out;
+    }
+
+    /// Iterator over the before/after KEYs on which a given NODE depends
+    typedef typename DepNode::dep_set::const_iterator dep_iterator;
+    /// range over the before/after KEYs on which a given NODE depends
+    typedef boost::iterator_range<dep_iterator> dep_range;
+
+    /// dependencies access from key
+    dep_range get_dep_range_from_key(const KEY& key, const dep_selector& selector) const
+    {
+        typename DepNodeMap::const_iterator found = mNodes.find(key);
+        if (found != mNodes.end())
+        {
+            return dep_range(selector(found->second));
+        }
+        // We want to return an empty range. On some platforms a default-
+        // constructed range (e.g. dep_range()) does NOT suffice! The client
+        // is likely to try to iterate from boost::begin(range) to
+        // boost::end(range); yet these iterators might not be valid. Instead
+        // return a range over a valid, empty container.
+        static const typename DepNode::dep_set empty_deps;
+        return dep_range(empty_deps.begin(), empty_deps.end());
+    }
+
+    /// dependencies access from any one of our key-order iterators
+    template<typename ITERATOR>
+    dep_range get_dep_range_from_xform(const ITERATOR& iterator, const dep_selector& selector) const
+    {
+        return dep_range(selector(iterator.base()->second));
+    }
+
+    /// dependencies access from sorted_iterator
+    dep_range get_dep_range_from_sorted(const sorted_iterator& sortiter,
+                                        const dep_selector& selector) const
+    {
+        // sorted_iterator is a boost::indirect_iterator wrapping an mCache
+        // iterator, which we can obtain by sortiter.base(). Deferencing that
+        // gets us an mCache entry, an 'iterator' -- one of our traversal
+        // iterators -- on which we can use get_dep_range_from_xform().
+        return get_dep_range_from_xform(*sortiter.base(), selector);
+    }
+
+    /**
+     * Get a range over the after KEYs stored for the passed KEY or iterator,
+     * in <i>arbitrary order.</i> If you pass a nonexistent KEY, returns empty
+     * range -- same as a KEY with no after KEYs. Detect existence of a KEY
+     * using get() instead.
+     */
+    template<typename KEY_OR_ITER>
+    dep_range get_after_range(const KEY_OR_ITER& key) const;
+
+    /**
+     * Get a range over the before KEYs stored for the passed KEY or iterator,
+     * in <i>arbitrary order.</i> If you pass a nonexistent KEY, returns empty
+     * range -- same as a KEY with no before KEYs. Detect existence of a KEY
+     * using get() instead.
+     */
+    template<typename KEY_OR_ITER>
+    dep_range get_before_range(const KEY_OR_ITER& key) const;
+
+private:
+    DepNodeMap mNodes;
+    mutable iterator_list mCache;
+};
+
+/**
+ * Functor to get a dep_range from a KEY or iterator -- generic case. If the
+ * passed value isn't one of our iterator specializations, assume it's
+ * convertible to the KEY type.
+ */
+template<typename KEY_ITER>
+struct LLDependencies_dep_range_from
+{
+    template<typename KEY, typename NODE, typename SELECTOR>
+    typename LLDependencies<KEY, NODE>::dep_range
+    operator()(const LLDependencies<KEY, NODE>& deps,
+               const KEY_ITER& key,
+               const SELECTOR& selector)
+    {
+        return deps.get_dep_range_from_key(key, selector);
+    }
+};
+
+/// Specialize LLDependencies_dep_range_from for our key-order iterators
+template<typename FUNCTION, typename ITERATOR>
+struct LLDependencies_dep_range_from< boost::transform_iterator<FUNCTION, ITERATOR> >
+{
+    template<typename KEY, typename NODE, typename SELECTOR>
+    typename LLDependencies<KEY, NODE>::dep_range
+    operator()(const LLDependencies<KEY, NODE>& deps,
+               const boost::transform_iterator<FUNCTION, ITERATOR>& iter,
+               const SELECTOR& selector)
+    {
+        return deps.get_dep_range_from_xform(iter, selector);
+    }
+};
+
+/// Specialize LLDependencies_dep_range_from for sorted_iterator
+template<typename BASEITER>
+struct LLDependencies_dep_range_from< boost::indirect_iterator<BASEITER> >
+{
+    template<typename KEY, typename NODE, typename SELECTOR>
+    typename LLDependencies<KEY, NODE>::dep_range
+    operator()(const LLDependencies<KEY, NODE>& deps,
+               const boost::indirect_iterator<BASEITER>& iter,
+               const SELECTOR& selector)
+    {
+        return deps.get_dep_range_from_sorted(iter, selector);
+    }
+};
+
+/// generic get_after_range() implementation
+template<typename KEY, typename NODE>
+template<typename KEY_OR_ITER>
+typename LLDependencies<KEY, NODE>::dep_range
+LLDependencies<KEY, NODE>::get_after_range(const KEY_OR_ITER& key_iter) const
+{
+    return LLDependencies_dep_range_from<KEY_OR_ITER>()(
+        *this,
+        key_iter,
+        boost::bind<const typename DepNode::dep_set&>(&DepNode::after, _1));
+}
+
+/// generic get_before_range() implementation
+template<typename KEY, typename NODE>
+template<typename KEY_OR_ITER>
+typename LLDependencies<KEY, NODE>::dep_range
+LLDependencies<KEY, NODE>::get_before_range(const KEY_OR_ITER& key_iter) const
+{
+    return LLDependencies_dep_range_from<KEY_OR_ITER>()(
+        *this,
+        key_iter,
+        boost::bind<const typename DepNode::dep_set&>(&DepNode::before, _1));
+}
+
+#endif /* ! defined(LL_LLDEPENDENCIES_H) */
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index b135dafb3c1..8102eddb185 100644
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -433,7 +433,7 @@ namespace LLError
 		Settings()
 			:	printLocation(false),
 				defaultLevel(LLError::LEVEL_DEBUG),
-				crashFunction(NULL),
+				crashFunction(),
 				timeFunction(NULL),
 				fileRecorder(NULL),
 				fixedBufferRecorder(NULL),
@@ -601,12 +601,18 @@ namespace LLError
 		s.printLocation = print;
 	}
 
-	void setFatalFunction(FatalFunction f)
+	void setFatalFunction(const FatalFunction& f)
 	{
 		Settings& s = Settings::get();
 		s.crashFunction = f;
 	}
 
+    FatalFunction getFatalFunction()
+    {
+        Settings& s = Settings::get();
+        return s.crashFunction;
+    }
+
 	void setTimeFunction(TimeFunction f)
 	{
 		Settings& s = Settings::get();
diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h
index a55d706d2e9..c9424f8a5ea 100644
--- a/indra/llcommon/llerrorcontrol.h
+++ b/indra/llcommon/llerrorcontrol.h
@@ -35,7 +35,7 @@
 #define LL_LLERRORCONTROL_H
 
 #include "llerror.h"
-
+#include "boost/function.hpp"
 #include <string>
 
 class LLFixedBuffer;
@@ -83,16 +83,38 @@ namespace LLError
 		Control functions.
 	*/
 
-	typedef void(*FatalFunction)(const std::string& message);
+	typedef boost::function<void(const std::string&)> FatalFunction;
 	void crashAndLoop(const std::string& message);
-		// Default fatal funtion: access null pointer and loops forever
+		// Default fatal function: access null pointer and loops forever
 
-	void setFatalFunction(FatalFunction);
+	void setFatalFunction(const FatalFunction&);
 		// The fatal function will be called when an message of LEVEL_ERROR
 		// is logged.  Note: supressing a LEVEL_ERROR message from being logged
 		// (by, for example, setting a class level to LEVEL_NONE), will keep
 		// the that message from causing the fatal funciton to be invoked.
-		
+
+    FatalFunction getFatalFunction();
+        // Retrieve the previously-set FatalFunction
+
+    /// temporarily override the FatalFunction for the duration of a
+    /// particular scope, e.g. for unit tests
+    class OverrideFatalFunction
+    {
+    public:
+        OverrideFatalFunction(const FatalFunction& func):
+            mPrev(getFatalFunction())
+        {
+            setFatalFunction(func);
+        }
+        ~OverrideFatalFunction()
+        {
+            setFatalFunction(mPrev);
+        }
+
+    private:
+        FatalFunction mPrev;
+    };
+
 	typedef std::string (*TimeFunction)();
 	std::string utcTime();
 	
diff --git a/indra/llcommon/llevent.cpp b/indra/llcommon/llevent.cpp
index 24be6e8b347..f669d0e13f6 100644
--- a/indra/llcommon/llevent.cpp
+++ b/indra/llcommon/llevent.cpp
@@ -34,6 +34,8 @@
 
 #include "llevent.h"
 
+using namespace LLOldEvents;
+
 /************************************************
     Events
 ************************************************/
diff --git a/indra/llcommon/llevent.h b/indra/llcommon/llevent.h
index 60887a060ae..a74ddbd091b 100644
--- a/indra/llcommon/llevent.h
+++ b/indra/llcommon/llevent.h
@@ -38,6 +38,9 @@
 #include "llmemory.h"
 #include "llthread.h"
 
+namespace LLOldEvents
+{
+
 class LLEventListener;
 class LLEvent;
 class LLEventDispatcher;
@@ -194,4 +197,6 @@ class LLValueChangedEvent : public LLEvent
 	LLSD mValue;
 };
 
+} // LLOldEvents
+
 #endif // LL_EVENT_H
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
new file mode 100644
index 00000000000..eb380ba7c8b
--- /dev/null
+++ b/indra/llcommon/llevents.cpp
@@ -0,0 +1,501 @@
+/**
+ * @file   llevents.cpp
+ * @author Nat Goodspeed
+ * @date   2008-09-12
+ * @brief  Implementation for llevents.
+ * 
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ * Copyright (c) 2008, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+
+#if LL_WINDOWS
+#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want!
+#endif
+
+// associated header
+#include "llevents.h"
+// STL headers
+#include <set>
+#include <sstream>
+#include <algorithm>
+// std headers
+#include <typeinfo>
+#include <cassert>
+#include <cmath>
+#include <cctype>
+// external library headers
+#include <boost/range/iterator_range.hpp>
+#if LL_WINDOWS
+#pragma warning (push)
+#pragma warning (disable : 4701) // compiler thinks might use uninitialized var, but no
+#endif
+#include <boost/lexical_cast.hpp>
+#if LL_WINDOWS
+#pragma warning (pop)
+#endif
+// other Linden headers
+
+/*****************************************************************************
+*   queue_names: specify LLEventPump names that should be instantiated as
+*   LLEventQueue
+*****************************************************************************/
+/**
+ * At present, we recognize particular requested LLEventPump names as needing
+ * LLEventQueues. Later on we'll migrate this information to an external
+ * configuration file.
+ */
+const char* queue_names[] =
+{
+    "placeholder - replace with first real name string"
+};
+
+/*****************************************************************************
+*   If there's a "mainloop" pump, listen on that to flush all LLEventQueues
+*****************************************************************************/
+struct RegisterFlush
+{
+    RegisterFlush():
+        pumps(LLEventPumps::instance()),
+        mainloop(pumps.obtain("mainloop")),
+        name("flushLLEventQueues")
+    {
+        mainloop.listen(name, boost::bind(&RegisterFlush::flush, this, _1));
+    }
+    bool flush(const LLSD&)
+    {
+        pumps.flush();
+        return false;
+    }
+    ~RegisterFlush()
+    {
+        mainloop.stopListening(name);
+    }
+    LLEventPumps& pumps;
+    LLEventPump& mainloop;
+    const std::string name;
+};
+static RegisterFlush registerFlush;
+
+/*****************************************************************************
+*   LLEventPumps
+*****************************************************************************/
+LLEventPumps::LLEventPumps():
+    // Until we migrate this information to an external config file,
+    // initialize mQueueNames from the static queue_names array.
+    mQueueNames(boost::begin(queue_names), boost::end(queue_names))
+{
+}
+
+LLEventPump& LLEventPumps::obtain(const std::string& name)
+{
+    PumpMap::iterator found = mPumpMap.find(name);
+    if (found != mPumpMap.end())
+    {
+        // Here we already have an LLEventPump instance with the requested
+        // name.
+        return *found->second;
+    }
+    // Here we must instantiate an LLEventPump subclass. 
+    LLEventPump* newInstance;
+    // Should this name be an LLEventQueue?
+    PumpNames::const_iterator nfound = mQueueNames.find(name);
+    if (nfound != mQueueNames.end())
+        newInstance = new LLEventQueue(name);
+    else
+        newInstance = new LLEventStream(name);
+    // LLEventPump's constructor implicitly registers each new instance in
+    // mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll
+    // delete it later.
+    mOurPumps.insert(newInstance);
+    return *newInstance;
+}
+
+void LLEventPumps::flush()
+{
+    // Flush every known LLEventPump instance. Leave it up to each instance to
+    // decide what to do with the flush() call.
+    for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi)
+    {
+        pmi->second->flush();
+    }
+}
+
+std::string LLEventPumps::registerNew(const LLEventPump& pump, const std::string& name, bool tweak)
+{
+    std::pair<PumpMap::iterator, bool> inserted =
+        mPumpMap.insert(PumpMap::value_type(name, const_cast<LLEventPump*>(&pump)));
+    // If the insert worked, then the name is unique; return that.
+    if (inserted.second)
+        return name;
+    // Here the new entry was NOT inserted, and therefore name isn't unique.
+    // Unless we're permitted to tweak it, that's Bad.
+    if (! tweak)
+    {
+        throw LLEventPump::DupPumpName(std::string("Duplicate LLEventPump name '") + name + "'");
+    }
+    // The passed name isn't unique, but we're permitted to tweak it. Find the
+    // first decimal-integer suffix not already taken. The insert() attempt
+    // above will have set inserted.first to the iterator of the existing
+    // entry by that name. Starting there, walk forward until we reach an
+    // entry that doesn't start with 'name'. For each entry consisting of name
+    // + integer suffix, capture the integer suffix in a set. Use a set
+    // because we're going to encounter string suffixes in the order: name1,
+    // name10, name11, name2, ... Walking those possibilities in that order
+    // isn't convenient to detect the first available "hole."
+    std::set<int> suffixes;
+    PumpMap::iterator pmi(inserted.first), pmend(mPumpMap.end());
+    // We already know inserted.first references the existing entry with
+    // 'name' as the key; skip that one and start with the next.
+    while (++pmi != pmend)
+    {
+        if (pmi->first.substr(0, name.length()) != name)
+        {
+            // Found the first entry beyond the entries starting with 'name':
+            // stop looping.
+            break;
+        }
+        // Here we're looking at an entry that starts with 'name'. Is the rest
+        // of it an integer?
+        // Dubious (?) assumption: in the local character set, decimal digits
+        // are in increasing order such that '9' is the last of them. This
+        // test deals with 'name' values such as 'a', where there might be a
+        // very large number of entries starting with 'a' whose suffixes
+        // aren't integers. A secondary assumption is that digit characters
+        // precede most common name characters (true in ASCII, false in
+        // EBCDIC). The test below is correct either way, but it's worth more
+        // if the assumption holds.
+        if (pmi->first[name.length()] > '9')
+            break;
+        // It should be cheaper to detect that we're not looking at a digit
+        // character -- and therefore the suffix can't possibly be an integer
+        // -- than to attempt the lexical_cast and catch the exception.
+        if (! std::isdigit(pmi->first[name.length()]))
+            continue;
+        // Okay, the first character of the suffix is a digit, it's worth at
+        // least attempting to convert to int.
+        try
+        {
+            suffixes.insert(boost::lexical_cast<int>(pmi->first.substr(name.length())));
+        }
+        catch (const boost::bad_lexical_cast&)
+        {
+            // If the rest of pmi->first isn't an int, just ignore it.
+        }
+    }
+    // Here we've accumulated in 'suffixes' all existing int suffixes of the
+    // entries starting with 'name'. Find the first unused one.
+    int suffix = 1;
+    for ( ; suffixes.find(suffix) != suffixes.end(); ++suffix)
+        ;
+    // Here 'suffix' is not in 'suffixes'. Construct a new name based on that
+    // suffix, insert it and return it.
+    std::ostringstream out;
+    out << name << suffix;
+    return registerNew(pump, out.str(), tweak);
+}
+
+void LLEventPumps::unregister(const LLEventPump& pump)
+{
+    // Remove this instance from mPumpMap
+    PumpMap::iterator found = mPumpMap.find(pump.getName());
+    if (found != mPumpMap.end())
+    {
+        mPumpMap.erase(found);
+    }
+    // If this instance is one we created, also remove it from mOurPumps so we
+    // won't try again to delete it later!
+    PumpSet::iterator psfound = mOurPumps.find(const_cast<LLEventPump*>(&pump));
+    if (psfound != mOurPumps.end())
+    {
+        mOurPumps.erase(psfound);
+    }
+}
+
+LLEventPumps::~LLEventPumps()
+{
+    // On destruction, delete every LLEventPump we instantiated (via
+    // obtain()). CAREFUL: deleting an LLEventPump calls its destructor, which
+    // calls unregister(), which removes that LLEventPump instance from
+    // mOurPumps. So an iterator loop over mOurPumps to delete contained
+    // LLEventPump instances is dangerous! Instead, delete them one at a time
+    // until mOurPumps is empty.
+    while (! mOurPumps.empty())
+    {
+        delete *mOurPumps.begin();
+    }
+}
+
+/*****************************************************************************
+*   LLEventPump
+*****************************************************************************/
+#if LL_WINDOWS
+#pragma warning (push)
+#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
+#endif
+
+LLEventPump::LLEventPump(const std::string& name, bool tweak):
+    // Register every new instance with LLEventPumps
+    mName(LLEventPumps::instance().registerNew(*this, name, tweak)),
+    mEnabled(true)
+{}
+
+#if LL_WINDOWS
+#pragma warning (pop)
+#endif
+
+LLEventPump::~LLEventPump()
+{
+    // Unregister this doomed instance from LLEventPumps
+    LLEventPumps::instance().unregister(*this);
+}
+
+// static data member
+const LLEventPump::NameList LLEventPump::empty;
+
+LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventListener& listener,
+                                         const NameList& after,
+                                         const NameList& before)
+{
+    // Check for duplicate name before connecting listener to mSignal
+    ConnectionMap::const_iterator found = mConnections.find(name);
+    // In some cases the user might disconnect a connection explicitly -- or
+    // might use LLEventTrackable to disconnect implicitly. Either way, we can
+    // end up retaining in mConnections a zombie connection object that's
+    // already been disconnected. Such a connection object can't be
+    // reconnected -- nor, in the case of LLEventTrackable, would we want to
+    // try, since disconnection happens with the destruction of the listener
+    // object. That means it's safe to overwrite a disconnected connection
+    // object with the new one we're attempting. The case we want to prevent
+    // is only when the existing connection object is still connected.
+    if (found != mConnections.end() && found->second.connected())
+    {
+        throw DupListenerName(std::string("Attempt to register duplicate listener name '") + name +
+                              "' on " + typeid(*this).name() + " '" + getName() + "'");
+    }
+    // Okay, name is unique, try to reconcile its dependencies. Specify a new
+    // "node" value that we never use for an mSignal placement; we'll fix it
+    // later.
+    DependencyMap::node_type& newNode = mDeps.add(name, -1.0, after, before);
+    // What if this listener has been added, removed and re-added? In that
+    // case newNode already has a non-negative value because we never remove a
+    // listener from mDeps. But keep processing uniformly anyway in case the
+    // listener was added back with different dependencies. Then mDeps.sort()
+    // would put it in a different position, and the old newNode placement
+    // value would be wrong, so we'd have to reassign it anyway. Trust that
+    // re-adding a listener with the same dependencies is the trivial case for
+    // mDeps.sort(): it can just replay its cache.
+    DependencyMap::sorted_range sorted_range;
+    try
+    {
+        // Can we pick an order that works including this new entry?
+        sorted_range = mDeps.sort();
+    }
+    catch (const DependencyMap::Cycle& e)
+    {
+        // No: the new node's after/before dependencies have made mDeps
+        // unsortable. If we leave the new node in mDeps, it will continue
+        // to screw up all future attempts to sort()! Pull it out.
+        mDeps.remove(name);
+        throw Cycle(std::string("New listener '") + name + "' on " + typeid(*this).name() +
+                    " '" + getName() + "' would cause cycle: " + e.what());
+    }
+    // Walk the list to verify that we haven't changed the order.
+    float previous = 0.0, myprev = 0.0;
+    DependencyMap::sorted_iterator mydmi = sorted_range.end(); // need this visible after loop
+    for (DependencyMap::sorted_iterator dmi = sorted_range.begin();
+         dmi != sorted_range.end(); ++dmi)
+    {
+        // Since we've added the new entry with an invalid placement,
+        // recognize it and skip it.
+        if (dmi->first == name)
+        {
+            // Remember the iterator belonging to our new node, and which
+            // placement value was 'previous' at that point.
+            mydmi = dmi;
+            myprev = previous;
+            continue;
+        }
+        // If the new node has rearranged the existing nodes, we'll find
+        // that their placement values are no longer in increasing order.
+        if (dmi->second < previous)
+        {
+            // This is another scenario in which we'd better back out the
+            // newly-added node from mDeps -- but don't do it yet, we want to
+            // traverse the existing mDeps to report on it!
+            // Describe the change to the order of our listeners. Copy
+            // everything but the newest listener to a vector we can sort to
+            // obtain the old order.
+            typedef std::vector< std::pair<float, std::string> > SortNameList;
+            SortNameList sortnames;
+            for (DependencyMap::sorted_iterator cdmi(sorted_range.begin()), cdmend(sorted_range.end());
+                 cdmi != cdmend; ++cdmi)
+            {
+                if (cdmi->first != name)
+                {
+                    sortnames.push_back(SortNameList::value_type(cdmi->second, cdmi->first));
+                }
+            }
+            std::sort(sortnames.begin(), sortnames.end());
+            std::ostringstream out;
+            out << "New listener '" << name << "' on " << typeid(*this).name() << " '" << getName()
+                << "' would move previous listener '" << dmi->first << "'\nwas: ";
+            SortNameList::const_iterator sni(sortnames.begin()), snend(sortnames.end());
+            if (sni != snend)
+            {
+                out << sni->second;
+                while (++sni != snend)
+                {
+                    out << ", " << sni->second;
+                }
+            }
+            out << "\nnow: ";
+            DependencyMap::sorted_iterator ddmi(sorted_range.begin()), ddmend(sorted_range.end());
+            if (ddmi != ddmend)
+            {
+                out << ddmi->first;
+                while (++ddmi != ddmend)
+                {
+                    out << ", " << ddmi->first;
+                }
+            }
+            // NOW remove the offending listener node.
+            mDeps.remove(name);
+            // Having constructed a description of the order change, inform caller.
+            throw OrderChange(out.str());
+        }
+        // This node becomes the previous one.
+        previous = dmi->second;
+    }
+    // We just got done with a successful mDeps.add(name, ...) call. We'd
+    // better have found 'name' somewhere in that sorted list!
+    assert(mydmi != sorted_range.end());
+    // Four cases:
+    // 0. name is the only entry: placement 1.0
+    // 1. name is the first of several entries: placement (next placement)/2
+    // 2. name is between two other entries: placement (myprev + (next placement))/2
+    // 3. name is the last entry: placement ceil(myprev) + 1.0
+    // Since we've cleverly arranged for myprev to be 0.0 if name is the
+    // first entry, this folds down to two cases. Case 1 is subsumed by
+    // case 2, and case 0 is subsumed by case 3. So we need only handle
+    // cases 2 and 3, which means we need only detect whether name is the
+    // last entry. Increment mydmi to see if there's anything beyond.
+    if (++mydmi != sorted_range.end())
+    {
+        // The new node isn't last. Place it between the previous node and
+        // the successor.
+        newNode = (myprev + mydmi->second)/2.0;
+    }
+    else
+    {
+        // The new node is last. Bump myprev up to the next integer, add
+        // 1.0 and use that.
+        newNode = std::ceil(myprev) + 1.0;
+    }
+    // Now that newNode has a value that places it appropriately in mSignal,
+    // connect it.
+    LLBoundListener bound = mSignal.connect(newNode, listener);
+    mConnections[name] = bound;
+    return bound;
+}
+
+LLBoundListener LLEventPump::getListener(const std::string& name) const
+{
+    ConnectionMap::const_iterator found = mConnections.find(name);
+    if (found != mConnections.end())
+    {
+        return found->second;
+    }
+    // not found, return dummy LLBoundListener
+    return LLBoundListener();
+}
+
+void LLEventPump::stopListening(const std::string& name)
+{
+    ConnectionMap::iterator found = mConnections.find(name);
+    if (found != mConnections.end())
+    {
+        found->second.disconnect();
+        mConnections.erase(found);
+    }
+    // We intentionally do NOT remove this name from mDeps. It may happen that
+    // the same listener with the same name and dependencies will jump on and
+    // off this LLEventPump repeatedly. Keeping a cache of dependencies will
+    // avoid a new dependency sort in such cases.
+}
+
+/*****************************************************************************
+*   LLEventStream
+*****************************************************************************/
+bool LLEventStream::post(const LLSD& event)
+{
+    if (! mEnabled)
+        return false;
+    // Let caller know if any one listener handled the event. This is mostly
+    // useful when using LLEventStream as a listener for an upstream
+    // LLEventPump.
+    return mSignal(event);
+}
+
+/*****************************************************************************
+*   LLEventQueue
+*****************************************************************************/
+bool LLEventQueue::post(const LLSD& event)
+{
+    if (mEnabled)
+    {
+        // Defer sending this event by queueing it until flush()
+        mEventQueue.push_back(event);
+    }
+    // Unconditionally return false. We won't know until flush() whether a
+    // listener claims to have handled the event -- meanwhile, don't block
+    // other listeners.
+    return false;
+}
+
+void LLEventQueue::flush()
+{
+    // Consider the case when a given listener on this LLEventQueue posts yet
+    // another event on the same queue. If we loop over mEventQueue directly,
+    // we'll end up processing all those events during the same flush() call
+    // -- rather like an EventStream. Instead, copy mEventQueue and clear it,
+    // so that any new events posted to this LLEventQueue during flush() will
+    // be processed in the *next* flush() call.
+    EventQueue queue(mEventQueue);
+    mEventQueue.clear();
+    for ( ; ! queue.empty(); queue.pop_front())
+    {
+        mSignal(queue.front());
+    }
+}
+
+/*****************************************************************************
+*   LLListenerOrPumpName
+*****************************************************************************/
+LLListenerOrPumpName::LLListenerOrPumpName(const std::string& pumpname):
+    // Look up the specified pumpname, and bind its post() method as our listener
+    mListener(boost::bind(&LLEventPump::post,
+                          boost::ref(LLEventPumps::instance().obtain(pumpname)),
+                          _1))
+{
+}
+
+LLListenerOrPumpName::LLListenerOrPumpName(const char* pumpname):
+    // Look up the specified pumpname, and bind its post() method as our listener
+    mListener(boost::bind(&LLEventPump::post,
+                          boost::ref(LLEventPumps::instance().obtain(pumpname)),
+                          _1))
+{
+}
+
+bool LLListenerOrPumpName::operator()(const LLSD& event) const
+{
+    if (! mListener)
+    {
+        throw Empty("attempting to call uninitialized");
+    }
+    return (*mListener)(event);
+}
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h
new file mode 100644
index 00000000000..f70d532e4c5
--- /dev/null
+++ b/indra/llcommon/llevents.h
@@ -0,0 +1,822 @@
+/**
+ * @file   llevents.h
+ * @author Kent Quirk, Nat Goodspeed
+ * @date   2008-09-11
+ * @brief  This is an implementation of the event system described at
+ *         https://wiki.lindenlab.com/wiki/Viewer:Messaging/Event_System,
+ *         originally introduced in llnotifications.h. It has nothing
+ *         whatsoever to do with the older system in llevent.h.
+ * 
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ * Copyright (c) 2008, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLEVENTS_H)
+#define LL_LLEVENTS_H
+
+#include <string>
+#include <map>
+#include <set>
+#include <vector>
+#include <list>
+#include <deque>
+#include <stdexcept>
+#include <boost/signals2.hpp>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/utility.hpp>        // noncopyable
+#include <boost/optional/optional.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <boost/visit_each.hpp>
+#include <boost/ref.hpp>            // reference_wrapper
+#include <boost/type_traits/is_pointer.hpp>
+#include <boost/utility/addressof.hpp>
+#include <boost/preprocessor/repetition/enum_params.hpp>
+#include <boost/preprocessor/iteration/local.hpp>
+#include <boost/function.hpp>
+#include <boost/static_assert.hpp>
+#include "llsd.h"
+#include "llmemory.h"
+#include "lldependencies.h"
+
+// override this to allow binding free functions with more parameters
+#ifndef LLEVENTS_LISTENER_ARITY
+#define LLEVENTS_LISTENER_ARITY 10
+#endif
+
+// hack for testing
+#ifndef testable
+#define testable private
+#endif
+
+/*****************************************************************************
+*   Signal and handler declarations
+*   Using a single handler signature means that we can have a common handler
+*   type, rather than needing a distinct one for each different handler.
+*****************************************************************************/
+
+/**
+ * A boost::signals Combiner that stops the first time a handler returns true
+ * We need this because we want to have our handlers return bool, so that
+ * we have the option to cause a handler to stop further processing. The
+ * default handler fails when the signal returns a value but has no slots.
+ */
+struct LLStopWhenHandled
+{
+    typedef bool result_type;
+
+    template<typename InputIterator>
+    result_type operator()(InputIterator first, InputIterator last) const
+    {
+        for (InputIterator si = first; si != last; ++si)
+		{
+            if (*si)
+			{
+                return true;
+			}
+		}
+        return false;
+    }
+};
+
+/**
+ * We want to have a standard signature for all signals; this way,
+ * we can easily document a protocol for communicating across
+ * dlls and into scripting languages someday.
+ *
+ * We want to return a bool to indicate whether the signal has been
+ * handled and should NOT be passed on to other listeners.
+ * Return true to stop further handling of the signal, and false
+ * to continue.
+ *
+ * We take an LLSD because this way the contents of the signal
+ * are independent of the API used to communicate it.
+ * It is const ref because then there's low cost to pass it;
+ * if you only need to inspect it, it's very cheap.
+ *
+ * @internal
+ * The @c float template parameter indicates that we will internally use @c
+ * float to indicate relative listener order on a given LLStandardSignal.
+ * Don't worry, the @c float values are strictly internal! They are not part
+ * of the interface, for the excellent reason that requiring the caller to
+ * specify a numeric key to establish order means that the caller must know
+ * the universe of possible values. We use LLDependencies for that instead.
+ */
+typedef boost::signals2::signal<bool(const LLSD&), LLStopWhenHandled, float>  LLStandardSignal;
+/// Methods that forward listeners (e.g. constructed with
+/// <tt>boost::bind()</tt>) should accept (const LLEventListener&)
+typedef LLStandardSignal::slot_type LLEventListener;
+/// Result of registering a listener, supports <tt>connected()</tt>,
+/// <tt>disconnect()</tt> and <tt>blocked()</tt>
+typedef boost::signals2::connection LLBoundListener;
+
+/**
+ * A common idiom for event-based code is to accept either a callable --
+ * directly called on completion -- or the string name of an LLEventPump on
+ * which to post the completion event. Specifying a parameter as <tt>const
+ * LLListenerOrPumpName&</tt> allows either.
+ *
+ * Calling a validly-constructed LLListenerOrPumpName, passing the LLSD
+ * 'event' object, either calls the callable or posts the event to the named
+ * LLEventPump.
+ *
+ * A default-constructed LLListenerOrPumpName is 'empty'. (This is useful as
+ * the default value of an optional method parameter.) Calling it throws
+ * LLListenerOrPumpName::Empty. Test for this condition beforehand using
+ * either <tt>if (param)</tt> or <tt>if (! param)</tt>.
+ */
+class LLListenerOrPumpName
+{
+public:
+    /// passing string name of LLEventPump
+    LLListenerOrPumpName(const std::string& pumpname);
+    /// passing string literal (overload so compiler isn't forced to infer
+    /// double conversion)
+    LLListenerOrPumpName(const char* pumpname);
+    /// passing listener -- the "anything else" catch-all case. The type of an
+    /// object constructed by boost::bind() isn't intended to be written out.
+    /// Normally we'd just accept 'const LLEventListener&', but that would
+    /// require double implicit conversion: boost::bind() object to
+    /// LLEventListener, LLEventListener to LLListenerOrPumpName. So use a
+    /// template to forward anything.
+    template<typename T>
+    LLListenerOrPumpName(const T& listener): mListener(listener) {}
+
+    /// for omitted method parameter: uninitialized mListener
+    LLListenerOrPumpName() {}
+
+    /// test for validity
+    operator bool() const { return bool(mListener); }
+    bool operator! () const { return ! mListener; }
+
+    /// explicit accessor
+    const LLEventListener& getListener() const { return *mListener; }
+
+    /// implicit conversion to LLEventListener
+    operator LLEventListener() const { return *mListener; }
+
+    /// allow calling directly
+    bool operator()(const LLSD& event) const;
+
+    /// exception if you try to call when empty
+    struct Empty: public std::runtime_error
+    {
+        Empty(const std::string& what):
+            std::runtime_error(std::string("LLListenerOrPumpName::Empty: ") + what) {}
+    };
+
+private:
+    boost::optional<LLEventListener> mListener;
+};
+
+/*****************************************************************************
+*   LLEventPumps
+*****************************************************************************/
+class LLEventPump;
+
+/**
+ * LLEventPumps is a Singleton manager through which one typically accesses
+ * this subsystem.
+ */
+class LLEventPumps: public LLSingleton<LLEventPumps>
+{
+    friend class LLSingleton<LLEventPumps>;
+public:
+    /**
+     * Find or create an LLEventPump instance with a specific name. We return
+     * a reference so there's no question about ownership. obtain() @em finds
+     * an instance without conferring @em ownership.
+     */
+    LLEventPump& obtain(const std::string& name);
+    /**
+     * Flush all known LLEventPump instances
+     */
+    void flush();
+
+private:
+    friend class LLEventPump;
+    /**
+     * Register a new LLEventPump instance (internal)
+     */
+    std::string registerNew(const LLEventPump&, const std::string& name, bool tweak);
+    /**
+     * Unregister a doomed LLEventPump instance (internal)
+     */
+    void unregister(const LLEventPump&);
+
+private:
+    LLEventPumps();
+    ~LLEventPumps();
+
+testable:
+    // Map of all known LLEventPump instances, whether or not we instantiated
+    // them. We store a plain old LLEventPump* because this map doesn't claim
+    // ownership of the instances. Though the common usage pattern is to
+    // request an instance using obtain(), it's fair to instantiate an
+    // LLEventPump subclass statically, as a class member, on the stack or on
+    // the heap. In such cases, the instantiating party is responsible for its
+    // lifespan.
+    typedef std::map<std::string, LLEventPump*> PumpMap;
+    PumpMap mPumpMap;
+    // Set of all LLEventPumps we instantiated. Membership in this set means
+    // we claim ownership, and will delete them when this LLEventPumps is
+    // destroyed.
+    typedef std::set<LLEventPump*> PumpSet;
+    PumpSet mOurPumps;
+    // LLEventPump names that should be instantiated as LLEventQueue rather
+    // than as LLEventStream
+    typedef std::set<std::string> PumpNames;
+    PumpNames mQueueNames;
+};
+
+/*****************************************************************************
+*   details
+*****************************************************************************/
+namespace LLEventDetail
+{
+    /// Any callable capable of connecting an LLEventListener to an
+    /// LLStandardSignal to produce an LLBoundListener can be mapped to this
+    /// signature.
+    typedef boost::function<LLBoundListener(const LLEventListener&)> ConnectFunc;
+
+    /**
+     * Utility template function to use Visitor appropriately
+     *
+     * @param listener Callable to connect, typically a boost::bind()
+     * expression. This will be visited by Visitor using boost::visit_each().
+     * @param connect_func Callable that will connect() @a listener to an
+     * LLStandardSignal, returning LLBoundListener.
+     */
+    template <typename LISTENER>
+    LLBoundListener visit_and_connect(const LISTENER& listener,
+                                      const ConnectFunc& connect_func);
+} // namespace LLEventDetail
+
+/*****************************************************************************
+*   LLEventPump
+*****************************************************************************/
+/**
+ * LLEventPump is the base class interface through which we access the
+ * concrete subclasses LLEventStream and LLEventQueue.
+ */
+class LLEventPump: boost::noncopyable
+{
+public:
+    /**
+     * Exception thrown by LLEventPump(). You are trying to instantiate an
+     * LLEventPump (subclass) using the same name as some other instance, and
+     * you didn't pass <tt>tweak=true</tt> to permit it to generate a unique
+     * variant.
+     */
+    struct DupPumpName: public std::runtime_error
+    {
+        DupPumpName(const std::string& what):
+            std::runtime_error(std::string("DupPumpName: ") + what) {}
+    };
+
+    /**
+     * Instantiate an LLEventPump (subclass) with the string name by which it
+     * can be found using LLEventPumps::obtain().
+     *
+     * If you pass (or default) @a tweak to @c false, then a duplicate name
+     * will throw DupPumpName. This won't happen if LLEventPumps::obtain()
+     * instantiates the LLEventPump, because obtain() uses find-or-create
+     * logic. It can only happen if you instantiate an LLEventPump in your own
+     * code -- and a collision with the name of some other LLEventPump is
+     * likely to cause much more subtle problems!
+     *
+     * When you hand-instantiate an LLEventPump, consider passing @a tweak as
+     * @c true. This directs LLEventPump() to append a suffix to the passed @a
+     * name to make it unique. You can retrieve the adjusted name by calling
+     * getName() on your new instance.
+     */
+    LLEventPump(const std::string& name, bool tweak=false);
+    virtual ~LLEventPump();
+
+    /// group exceptions thrown by listen(). We use exceptions because these
+    /// particular errors are likely to be coding errors, found and fixed by
+    /// the developer even before preliminary checkin.
+    struct ListenError: public std::runtime_error
+    {
+        ListenError(const std::string& what): std::runtime_error(what) {}
+    };
+    /**
+     * exception thrown by listen(). You are attempting to register a
+     * listener on this LLEventPump using the same listener name as an
+     * already-registered listener.
+     */
+    struct DupListenerName: public ListenError
+    {
+        DupListenerName(const std::string& what):
+            ListenError(std::string("DupListenerName: ") + what)
+        {}
+    };
+    /**
+     * exception thrown by listen(). The order dependencies specified for your
+     * listener are incompatible with existing listeners.
+     *
+     * Consider listener "a" which specifies before "b" and "b" which
+     * specifies before "c". You are now attempting to register "c" before
+     * "a". There is no order that can satisfy all constraints.
+     */
+    struct Cycle: public ListenError
+    {
+        Cycle(const std::string& what): ListenError(std::string("Cycle: ") + what) {}
+    };
+    /**
+     * exception thrown by listen(). This one means that your new listener
+     * would force a change to the order of previously-registered listeners,
+     * and we don't have a good way to implement that.
+     *
+     * Consider listeners "some", "other" and "third". "some" and "other" are
+     * registered earlier without specifying relative order, so "other"
+     * happens to be first. Now you attempt to register "third" after "some"
+     * and before "other". Whoops, that would require swapping "some" and
+     * "other", which we can't do. Instead we throw this exception.
+     *
+     * It may not be possible to change the registration order so we already
+     * know "third"s order requirement by the time we register the second of
+     * "some" and "other". A solution would be to specify that "some" must
+     * come before "other", or equivalently that "other" must come after
+     * "some".
+     */
+    struct OrderChange: public ListenError
+    {
+        OrderChange(const std::string& what): ListenError(std::string("OrderChange: ") + what) {}
+    };
+
+    /// used by listen()
+    typedef std::vector<std::string> NameList;
+    /// convenience placeholder for when you explicitly want to pass an empty
+    /// NameList
+    const static NameList empty;
+
+    /// Get this LLEventPump's name
+    std::string getName() const { return mName; }
+
+    /**
+     * Register a new listener with a unique name. Specify an optional list
+     * of other listener names after which this one must be called, likewise
+     * an optional list of other listener names before which this one must be
+     * called. The other listeners mentioned need not yet be registered
+     * themselves. listen() can throw any ListenError; see ListenError
+     * subclasses.
+     *
+     * If (as is typical) you pass a <tt>boost::bind()</tt> expression,
+     * listen() will inspect the components of that expression. If a bound
+     * object matches any of several cases, the connection will automatically
+     * be disconnected when that object is destroyed.
+     *
+     * * You bind a <tt>boost::weak_ptr</tt>.
+     * * Binding a <tt>boost::shared_ptr</tt> that way would ensure that the
+     *   referenced object would @em never be destroyed, since the @c
+     *   shared_ptr stored in the LLEventPump would remain an outstanding
+     *   reference. Use the weaken() function to convert your @c shared_ptr to
+     *   @c weak_ptr. Because this is easy to forget, binding a @c shared_ptr
+     *   will produce a compile error (@c BOOST_STATIC_ASSERT failure).
+     * * You bind a simple pointer or reference to an object derived from
+     *   <tt>boost::enable_shared_from_this</tt>. (UNDER CONSTRUCTION)
+     * * You bind a simple pointer or reference to an object derived from
+     *   LLEventTrackable. Unlike the cases described above, though, this is
+     *   vulnerable to a couple of cross-thread race conditions, as described
+     *   in the LLEventTrackable documentation.
+     */
+    template <typename LISTENER>
+    LLBoundListener listen(const std::string& name, const LISTENER& listener,
+                           const NameList& after=NameList(),
+                           const NameList& before=NameList())
+    {
+        // Examine listener, using our listen_impl() method to make the
+        // actual connection.
+        // This is why listen() is a template. Conversion from boost::bind()
+        // to LLEventListener performs type erasure, so it's important to look
+        // at the boost::bind object itself before that happens.
+        return LLEventDetail::visit_and_connect(listener,
+                                                boost::bind(&LLEventPump::listen_impl,
+                                                            this,
+                                                            name,
+                                                            _1,
+                                                            after,
+                                                            before));
+    }
+
+    /// Get the LLBoundListener associated with the passed name (dummy
+    /// LLBoundListener if not found)
+    virtual LLBoundListener getListener(const std::string& name) const;
+    /**
+     * Instantiate one of these to block an existing connection:
+     * @code
+     * { // in some local scope
+     *     LLEventPump::Blocker block(someLLBoundListener);
+     *     // code that needs the connection blocked
+     * } // unblock the connection again
+     * @endcode
+     */
+    typedef boost::signals2::shared_connection_block Blocker;
+    /// Unregister a listener by name. Prefer this to
+    /// <tt>getListener(name).disconnect()</tt> because stopListening() also
+    /// forgets this name.
+    virtual void stopListening(const std::string& name);
+    /// Post an event to all listeners. The @c bool return is only meaningful
+    /// if the underlying leaf class is LLEventStream -- beware of relying on
+    /// it too much! Truthfully, we return @c bool mostly to permit chaining
+    /// one LLEventPump as a listener on another.
+    virtual bool post(const LLSD&) = 0;
+    /// Enable/disable: while disabled, silently ignore all post() calls
+    virtual void enable(bool enabled=true) { mEnabled = enabled; }
+    /// query
+    virtual bool enabled() const { return mEnabled; }
+
+private:
+    friend class LLEventPumps;
+    /// flush queued events
+    virtual void flush() {}
+
+private:
+    virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&,
+                                        const NameList& after,
+                                        const NameList& before);
+    std::string mName;
+
+protected:
+    /// implement the dispatching
+    LLStandardSignal mSignal;
+    /// valve open?
+    bool mEnabled;
+    /// Map of named listeners. This tracks the listeners that actually exist
+    /// at this moment. When we stopListening(), we discard the entry from
+    /// this map.
+    typedef std::map<std::string, boost::signals2::connection> ConnectionMap;
+    ConnectionMap mConnections;
+    typedef LLDependencies<std::string, float> DependencyMap;
+    /// Dependencies between listeners. For each listener, track the float
+    /// used to establish its place in mSignal's order. This caches all the
+    /// listeners that have ever registered; stopListening() does not discard
+    /// the entry from this map. This is to avoid a new dependency sort if the
+    /// same listener with the same dependencies keeps hopping on and off this
+    /// LLEventPump.
+    DependencyMap mDeps;
+};
+
+/*****************************************************************************
+*   LLEventStream
+*****************************************************************************/
+/**
+ * LLEventStream is a thin wrapper around LLStandardSignal. Posting an
+ * event immediately calls all registered listeners.
+ */
+class LLEventStream: public LLEventPump
+{
+public:
+    LLEventStream(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {}
+    virtual ~LLEventStream() {}
+
+    /// Post an event to all listeners
+    virtual bool post(const LLSD& event);
+};
+
+/*****************************************************************************
+*   LLEventQueue
+*****************************************************************************/
+/**
+ * LLEventQueue isa LLEventPump whose post() method defers calling registered
+ * listeners until flush() is called.
+ */
+class LLEventQueue: public LLEventPump
+{
+public:
+    LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {}
+    virtual ~LLEventQueue() {}
+
+    /// Post an event to all listeners
+    virtual bool post(const LLSD& event);
+
+private:
+    /// flush queued events
+    virtual void flush();
+
+private:
+    typedef std::deque<LLSD> EventQueue;
+    EventQueue mEventQueue;
+};
+
+/*****************************************************************************
+*   LLEventTrackable and underpinnings
+*****************************************************************************/
+/**
+ * LLEventTrackable wraps boost::signals2::trackable, which resembles
+ * boost::trackable. Derive your listener class from LLEventTrackable instead,
+ * and use something like
+ * <tt>LLEventPump::listen(boost::bind(&YourTrackableSubclass::method,
+ * instance, _1))</tt>. This will implicitly disconnect when the object
+ * referenced by @c instance is destroyed.
+ *
+ * @note
+ * LLEventTrackable doesn't address a couple of cases:
+ * * Object destroyed during call
+ *   - You enter a slot call in thread A.
+ *   - Thread B destroys the object, which of course disconnects it from any
+ *     future slot calls.
+ *   - Thread A's call uses 'this', which now refers to a defunct object.
+ *     Undefined behavior results.
+ * * Call during destruction
+ *   - @c MySubclass is derived from LLEventTrackable.
+ *   - @c MySubclass registers one of its own methods using
+ *     <tt>LLEventPump::listen()</tt>.
+ *   - The @c MySubclass object begins destruction. <tt>~MySubclass()</tt>
+ *     runs, destroying state specific to the subclass. (For instance, a
+ *     <tt>Foo*</tt> data member is <tt>delete</tt>d but not zeroed.)
+ *   - The listening method will not be disconnected until
+ *     <tt>~LLEventTrackable()</tt> runs.
+ *   - Before we get there, another thread posts data to the @c LLEventPump
+ *     instance, calling the @c MySubclass method.
+ *   - The method in question relies on valid @c MySubclass state. (For
+ *     instance, it attempts to dereference the <tt>Foo*</tt> pointer that was
+ *     <tt>delete</tt>d but not zeroed.)
+ *   - Undefined behavior results.
+ * If you suspect you may encounter any such scenario, you're better off
+ * managing the lifespan of your object with <tt>boost::shared_ptr</tt>.
+ * Passing <tt>LLEventPump::listen()</tt> a <tt>boost::bind()</tt> expression
+ * involving a <tt>boost::weak_ptr<Foo></tt> is recognized specially, engaging
+ * thread-safe Boost.Signals2 machinery.
+ */
+typedef boost::signals2::trackable LLEventTrackable;
+
+/**
+ * We originally provided a suite of overloaded
+ * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call
+ * LLEventPump::listen(...) and then pass the returned LLBoundListener to
+ * LLEventTrackable::track(). This was workable but error-prone: the coder
+ * must remember to call listenTo() rather than the more straightforward
+ * listen() method.
+ *
+ * Now we publish only the single canonical listen() method, so there's a
+ * uniform mechanism. Having a single way to do this is good, in that there's
+ * no question in the coder's mind which of several alternatives to choose.
+ *
+ * To support automatic connection management, we use boost::visit_each
+ * (http://www.boost.org/doc/libs/1_37_0/doc/html/boost/visit_each.html) to
+ * inspect each argument of a boost::bind expression. (Although the visit_each
+ * mechanism was first introduced with the original Boost.Signals library, it
+ * was only later documented.)
+ *
+ * Cases:
+ * * At least one of the function's arguments is a boost::weak_ptr<T>. Pass
+ *   the corresponding shared_ptr to slot_type::track(). Ideally that would be
+ *   the object whose method we want to call, but in fact we do the same for
+ *   any weak_ptr we might find among the bound arguments. If we're passing
+ *   our bound method a weak_ptr to some object, wouldn't the destruction of
+ *   that object invalidate the call? So we disconnect automatically when any
+ *   such object is destroyed. This is the mechanism preferred by boost::
+ *   signals2.
+ * * One of the functions's arguments is a boost::shared_ptr<T>. This produces
+ *   a compile error: the bound copy of the shared_ptr stored in the
+ *   boost_bind object stored in the signal object would make the referenced
+ *   T object immortal. We provide a weaken() function. Pass
+ *   weaken(your_shared_ptr) instead. (We can inspect, but not modify, the
+ *   boost::bind object. Otherwise we'd replace the shared_ptr with weak_ptr
+ *   implicitly and just proceed.)
+ * * One of the function's arguments is a plain pointer/reference to an object
+ *   derived from boost::enable_shared_from_this. We assume that this object
+ *   is managed using boost::shared_ptr, so we implicitly extract a shared_ptr
+ *   and track that. (UNDER CONSTRUCTION)
+ * * One of the function's arguments is derived from LLEventTrackable. Pass
+ *   the LLBoundListener to its LLEventTrackable::track(). This is vulnerable
+ *   to a couple different race conditions, as described in LLEventTrackable
+ *   documentation. (NOTE: Now that LLEventTrackable is a typedef for
+ *   boost::signals2::trackable, the Signals2 library handles this itself, so
+ *   our visitor needs no special logic for this case.)
+ * * Any other argument type is irrelevant to automatic connection management.
+ */
+
+namespace LLEventDetail
+{
+    template <typename F>
+    const F& unwrap(const F& f) { return f; }
+
+    template <typename F>
+    const F& unwrap(const boost::reference_wrapper<F>& f) { return f.get(); }
+
+    // Most of the following is lifted from the Boost.Signals use of
+    // visit_each.
+    template<bool Cond> struct truth {};
+
+    /**
+     * boost::visit_each() Visitor, used on a template argument <tt>const F&
+     * f</tt> as follows (see visit_and_connect()):
+     * @code
+     * LLEventListener listener(f);
+     * Visitor visitor(listener); // bind listener so it can track() shared_ptrs
+     * using boost::visit_each;   // allow unqualified visit_each() call for ADL
+     * visit_each(visitor, unwrap(f));
+     * @endcode
+     */
+    class Visitor
+    {
+    public:
+        /**
+         * Visitor binds a reference to LLEventListener so we can track() any
+         * shared_ptrs we find in the argument list.
+         */
+        Visitor(LLEventListener& listener):
+            mListener(listener)
+        {
+        }
+
+        /**
+         * boost::visit_each() calls this method for each component of a
+         * boost::bind() expression.
+         */
+        template <typename T>
+        void operator()(const T& t) const
+        {
+            decode(t, 0);
+        }
+
+    private:
+        // decode() decides between a reference wrapper and anything else
+        // boost::ref() variant
+        template<typename T>
+        void decode(const boost::reference_wrapper<T>& t, int) const
+        {
+//          add_if_trackable(t.get_pointer());
+        }
+
+        // decode() anything else
+        template<typename T>
+        void decode(const T& t, long) const
+        {
+            typedef truth<(boost::is_pointer<T>::value)> is_a_pointer;
+            maybe_get_pointer(t, is_a_pointer());
+        }
+
+        // maybe_get_pointer() decides between a pointer and a non-pointer
+        // plain pointer variant
+        template<typename T>
+        void maybe_get_pointer(const T& t, truth<true>) const
+        {
+//          add_if_trackable(t);
+        }
+
+        // shared_ptr variant
+        template<typename T>
+        void maybe_get_pointer(const boost::shared_ptr<T>& t, truth<false>) const
+        {
+            // If we have a shared_ptr to this object, it doesn't matter
+            // whether the object is derived from LLEventTrackable, so no
+            // further analysis of T is needed.
+//          mListener.track(t);
+
+            // Make this case illegal. Passing a bound shared_ptr to
+            // slot_type::track() is useless, since the bound shared_ptr will
+            // keep the object alive anyway! Force the coder to cast to weak_ptr.
+
+            // Trivial as it is, make the BOOST_STATIC_ASSERT() condition
+            // dependent on template param so the macro is only evaluated if
+            // this method is in fact instantiated, as described here:
+            // http://www.boost.org/doc/libs/1_34_1/doc/html/boost_staticassert.html
+
+            // ATTENTION: Don't bind a shared_ptr<anything> using
+            // LLEventPump::listen(boost::bind()). Doing so captures a copy of
+            // the shared_ptr, making the referenced object effectively
+            // immortal. Use the weaken() function, e.g.:
+            // somepump.listen(boost::bind(...weaken(my_shared_ptr)...));
+            // This lets us automatically disconnect when the referenced
+            // object is destroyed.
+            BOOST_STATIC_ASSERT(sizeof(T) == 0);
+        }
+
+        // weak_ptr variant
+        template<typename T>
+        void maybe_get_pointer(const boost::weak_ptr<T>& t, truth<false>) const
+        {
+            // If we have a weak_ptr to this object, it doesn't matter
+            // whether the object is derived from LLEventTrackable, so no
+            // further analysis of T is needed.
+            mListener.track(t);
+//          std::cout << "Found weak_ptr<" << typeid(T).name() << ">!\n";
+        }
+
+#if 0
+        // reference to anything derived from boost::enable_shared_from_this
+        template <typename T>
+        inline void maybe_get_pointer(const boost::enable_shared_from_this<T>& ct,
+                                      truth<false>) const
+        {
+            // Use the slot_type::track(shared_ptr) mechanism. Cast away
+            // const-ness because (in our code base anyway) it's unusual
+            // to find shared_ptr<const T>.
+            boost::enable_shared_from_this<T>&
+                t(const_cast<boost::enable_shared_from_this<T>&>(ct));
+            std::cout << "Capturing shared_from_this()" << std::endl;
+            boost::shared_ptr<T> sp(t.shared_from_this());
+/*==========================================================================*|
+            std::cout << "Capturing weak_ptr" << std::endl;
+            boost::weak_ptr<T> wp(sp);
+|*==========================================================================*/
+            std::cout << "Tracking shared__ptr" << std::endl;
+            mListener.track(sp);
+        }
+#endif
+
+        // non-pointer variant
+        template<typename T>
+        void maybe_get_pointer(const T& t, truth<false>) const
+        {
+            // Take the address of this object, because the object itself may be
+            // trackable
+//          add_if_trackable(boost::addressof(t));
+        }
+
+/*==========================================================================*|
+        // add_if_trackable() adds LLEventTrackable objects to mTrackables
+        inline void add_if_trackable(const LLEventTrackable* t) const
+        {
+            if (t)
+            {
+            }
+        }
+
+        // pointer to anything not an LLEventTrackable subclass
+        inline void add_if_trackable(const void*) const
+        {
+        }
+
+        // pointer to free function
+        // The following construct uses the preprocessor to generate
+        // add_if_trackable() overloads accepting pointer-to-function taking
+        // 0, 1, ..., LLEVENTS_LISTENER_ARITY parameters of arbitrary type.
+#define BOOST_PP_LOCAL_MACRO(n)                                     \
+        template <typename R                                        \
+                  BOOST_PP_COMMA_IF(n)                              \
+                  BOOST_PP_ENUM_PARAMS(n, typename T)>              \
+        inline void                                                 \
+        add_if_trackable(R (*)(BOOST_PP_ENUM_PARAMS(n, T))) const   \
+        {                                                           \
+        }
+#define BOOST_PP_LOCAL_LIMITS (0, LLEVENTS_LISTENER_ARITY)
+#include BOOST_PP_LOCAL_ITERATE()
+#undef  BOOST_PP_LOCAL_MACRO
+#undef  BOOST_PP_LOCAL_LIMITS
+|*==========================================================================*/
+
+        /// Bind a reference to the LLEventListener to call its track() method.
+        LLEventListener& mListener;
+    };
+
+    /**
+     * Utility template function to use Visitor appropriately
+     *
+     * @param raw_listener Callable to connect, typically a boost::bind()
+     * expression. This will be visited by Visitor using boost::visit_each().
+     * @param connect_funct Callable that will connect() @a raw_listener to an
+     * LLStandardSignal, returning LLBoundListener.
+     */
+    template <typename LISTENER>
+    LLBoundListener visit_and_connect(const LISTENER& raw_listener,
+                                      const ConnectFunc& connect_func)
+    {
+        // Capture the listener
+        LLEventListener listener(raw_listener);
+        // Define our Visitor, binding the listener so we can call
+        // listener.track() if we discover any shared_ptr<Foo>.
+        LLEventDetail::Visitor visitor(listener);
+        // Allow unqualified visit_each() call for ADL
+        using boost::visit_each;
+        // Visit each component of a boost::bind() expression. Pass
+        // 'raw_listener', our template argument, rather than 'listener' from
+        // which type details have been erased. unwrap() comes from
+        // Boost.Signals, in case we were passed a boost::ref().
+        visit_each(visitor, LLEventDetail::unwrap(raw_listener));
+        // Make the connection using passed function. At present, wrapping
+        // this functionality into this function is a bit silly: we don't
+        // really need a visit_and_connect() function any more, just a visit()
+        // function. The definition of this function dates from when, after
+        // visit_each(), after establishing the connection, we had to
+        // postprocess the new connection with the visitor object. That's no
+        // longer necessary.
+        return connect_func(listener);
+    }
+} // namespace LLEventDetail
+
+// Somewhat to my surprise, passing boost::bind(...boost::weak_ptr<T>...) to
+// listen() fails in Boost code trying to instantiate LLEventListener (i.e.
+// LLStandardSignal::slot_type) because the boost::get_pointer() utility function isn't
+// specialized for boost::weak_ptr. This remedies that omission.
+namespace boost
+{
+    template <typename T>
+    T* get_pointer(const weak_ptr<T>& ptr) { return shared_ptr<T>(ptr).get(); }
+}
+
+/// Since we forbid use of listen(boost::bind(...shared_ptr<T>...)), provide an
+/// easy way to cast to the corresponding weak_ptr.
+template <typename T>
+boost::weak_ptr<T> weaken(const boost::shared_ptr<T>& ptr)
+{
+    return boost::weak_ptr<T>(ptr);
+}
+
+#endif /* ! defined(LL_LLEVENTS_H) */
diff --git a/indra/llcommon/lllazy.cpp b/indra/llcommon/lllazy.cpp
new file mode 100644
index 00000000000..215095bc273
--- /dev/null
+++ b/indra/llcommon/lllazy.cpp
@@ -0,0 +1,23 @@
+/**
+ * @file   lllazy.cpp
+ * @author Nat Goodspeed
+ * @date   2009-01-28
+ * @brief  Implementation for lllazy.
+ * 
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "lllazy.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+
+// lllazy.h is presently header-only. This file exists only because our CMake
+// test macro ADD_BUILD_TEST requires it.
+int dummy = 0;
diff --git a/indra/llcommon/lllazy.h b/indra/llcommon/lllazy.h
new file mode 100644
index 00000000000..2240954d98c
--- /dev/null
+++ b/indra/llcommon/lllazy.h
@@ -0,0 +1,382 @@
+/**
+ * @file   lllazy.h
+ * @author Nat Goodspeed
+ * @date   2009-01-22
+ * @brief  Lazy instantiation of specified type. Useful in conjunction with
+ *         Michael Feathers's "Extract and Override Getter" ("Working
+ *         Effectively with Legacy Code", p. 352).
+ *
+ * Quoting his synopsis of steps on p.355:
+ *
+ * 1. Identify the object you need a getter for.
+ * 2. Extract all of the logic needed to create the object into a getter.
+ * 3. Replace all uses of the object with calls to the getter, and initialize
+ *    the reference that holds the object to null in all constructors.
+ * 4. Add the first-time logic to the getter so that the object is constructed
+ *    and assigned to the reference whenever the reference is null.
+ * 5. Subclass the class and override the getter to provide an alternative
+ *    object for testing.
+ *
+ * It's the second half of bullet 3 (3b, as it were) that bothers me. I find
+ * it all too easy to imagine adding pointer initializers to all but one
+ * constructor... the one not exercised by my tests. That suggested using
+ * (e.g.) boost::scoped_ptr<MyObject> so you don't have to worry about
+ * destroying it either.
+ *
+ * However, introducing additional machinery allows us to encapsulate bullet 4
+ * as well.
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLLAZY_H)
+#define LL_LLLAZY_H
+
+#include <boost/function.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/lambda/construct.hpp>
+#include <stdexcept>
+
+/// LLLazyCommon simply factors out of LLLazy<T> things that don't depend on
+/// its template parameter.
+class LLLazyCommon
+{
+public:
+    /**
+     * This exception is thrown if you try to replace an LLLazy<T>'s factory
+     * (or T* instance) after it already has an instance in hand. Since T
+     * might well be stateful, we can't know the effect of silently discarding
+     * and replacing an existing instance, so we disallow it. This facility is
+     * intended for testing, and in a test scenario we can definitely control
+     * that.
+     */
+    struct InstanceChange: public std::runtime_error
+    {
+        InstanceChange(const std::string& what): std::runtime_error(what) {}
+    };
+
+protected:
+    /**
+     * InstanceChange might be appropriate in a couple of different LLLazy<T>
+     * methods. Factor out the common logic.
+     */
+    template <typename PTR>
+    static void ensureNoInstance(const PTR& ptr)
+    {
+        if (ptr)
+        {
+            // Too late: we've already instantiated the lazy object. We don't
+            // know whether it's stateful or not, so it's not safe to discard
+            // the existing instance in favor of a replacement.
+            throw InstanceChange("Too late to replace LLLazy instance");
+        }
+    }
+};
+
+/**
+ * LLLazy<T> is useful when you have an outer class Outer that you're trying
+ * to bring under unit test, that contains a data member difficult to
+ * instantiate in a test harness. Typically the data member's class Inner has
+ * many thorny dependencies. Feathers generally advocates "Extract and
+ * Override Factory Method" (p. 350). But in C++, you can't call a derived
+ * class override of a virtual method from the derived class constructor,
+ * which limits applicability of "Extract and Override Factory Method." For
+ * such cases Feathers presents "Extract and Override Getter" (p. 352).
+ *
+ * So we'll assume that your class Outer contains a member like this:
+ * @code
+ * Inner mInner;
+ * @endcode
+ *
+ * LLLazy<Inner> can be used to replace this member. You can directly declare:
+ * @code
+ * LLLazy<Inner> mInner;
+ * @endcode
+ * and change references to mInner accordingly.
+ *
+ * (Alternatively, you can add a base class of the form
+ * <tt>LLLazyBase<Inner></tt>. This is discussed further in the LLLazyBase<T>
+ * documentation.)
+ *
+ * LLLazy<T> binds a <tt>boost::scoped_ptr<T></tt> and a factory functor
+ * returning T*. You can either bind that functor explicitly or let it default
+ * to the expression <tt>new T()</tt>.
+ *
+ * As long as LLLazy<T> remains unreferenced, its T remains uninstantiated.
+ * The first time you use get(), <tt>operator*()</tt> or <tt>operator->()</tt>
+ * it will instantiate its T and thereafter behave like a pointer to it.
+ *
+ * Thus, any existing reference to <tt>mInner.member</tt> should be replaced
+ * with <tt>mInner->member</tt>. Any simple reference to @c mInner should be
+ * replaced by <tt>*mInner</tt>.
+ *
+ * (If the original declaration was a pointer initialized in Outer's
+ * constructor, e.g. <tt>Inner* mInner</tt>, so much the better. In that case
+ * you should be able to drop in <tt>LLLazy<Inner></tt> without much change.)
+ *
+ * The support for "Extract and Override Getter" lies in the fact that you can
+ * replace the factory functor -- or provide an explicit T*. Presumably this
+ * is most useful from a test subclass -- which suggests that your @c mInner
+ * member should be @c protected.
+ *
+ * Note that <tt>boost::lambda::new_ptr<T>()</tt> makes a dandy factory
+ * functor, for either the set() method or LLLazy<T>'s constructor. If your T
+ * requires constructor arguments, use an expression more like
+ * <tt>boost::lambda::bind(boost::lambda::new_ptr<T>(), arg1, arg2, ...)</tt>.
+ *
+ * Of course the point of replacing the functor is to substitute a class that,
+ * though referenced as Inner*, is not an Inner; presumably this is a testing
+ * subclass of Inner (e.g. TestInner). Thus your test subclass TestOuter for
+ * the containing class Outer will contain something like this:
+ * @code
+ * class TestOuter: public Outer
+ * {
+ * public:
+ *     TestOuter()
+ *     {
+ *         // mInner must be 'protected' rather than 'private'
+ *         mInner.set(boost::lambda::new_ptr<TestInner>());
+ *     }
+ *     ...
+ * };
+ * @endcode
+ */
+template <typename T>
+class LLLazy: public LLLazyCommon
+{
+public:
+    /// Any nullary functor returning T* will work as a Factory
+    typedef boost::function<T* ()> Factory;
+
+    /// The default LLLazy constructor uses <tt>new T()</tt> as its Factory
+    LLLazy():
+        mFactory(boost::lambda::new_ptr<T>())
+    {}
+
+    /// Bind an explicit Factory functor
+    LLLazy(const Factory& factory):
+        mFactory(factory)
+    {}
+
+    /// Reference T, instantiating it if this is the first access
+    const T& get() const
+    {
+        if (! mInstance)
+        {
+            // use the bound Factory functor
+            mInstance.reset(mFactory());
+        }
+        return *mInstance;
+    }
+
+    /// non-const get()
+    T& get()
+    {
+        return const_cast<T&>(const_cast<const LLLazy<T>*>(this)->get());
+    }
+
+    /// operator*() is equivalent to get()
+    const T& operator*() const { return get(); }
+    /// operator*() is equivalent to get()
+    T& operator*() { return get(); }
+
+    /**
+     * operator->() must return (something resembling) T*. It's tempting to
+     * return the underlying boost::scoped_ptr<T>, but that would require
+     * breaking out the lazy-instantiation logic from get() into a common
+     * private method. Assume the pointer used for operator->() access is very
+     * short-lived.
+     */
+    const T* operator->() const { return &get(); }
+    /// non-const operator->()
+    T* operator->() { return &get(); }
+
+    /// set(Factory). This will throw InstanceChange if mInstance has already
+    /// been set.
+    void set(const Factory& factory)
+    {
+        ensureNoInstance(mInstance);
+        mFactory = factory;
+    }
+
+    /// set(T*). This will throw InstanceChange if mInstance has already been
+    /// set.
+    void set(T* instance)
+    {
+        ensureNoInstance(mInstance);
+        mInstance.reset(instance);
+    }
+
+private:
+    Factory mFactory;
+    // Consider an LLLazy<T> member of a class we're accessing by const
+    // reference. We want to allow even const methods to touch the LLLazy<T>
+    // member. Hence the actual pointer must be mutable because such access
+    // might assign it.
+    mutable boost::scoped_ptr<T> mInstance;
+};
+
+#if (! defined(__GNUC__)) || (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ > 3)
+// Not gcc at all, or a gcc more recent than gcc 3.3
+#define GCC33 0
+#else
+#define GCC33 1
+#endif
+
+/**
+ * LLLazyBase<T> wraps LLLazy<T>, giving you an alternative way to replace
+ * <tt>Inner mInner;</tt>. Instead of coding <tt>LLLazy<Inner> mInner</tt>,
+ * you can add LLLazyBase<Inner> to your Outer class's bases, e.g.:
+ * @code
+ * class Outer: public LLLazyBase<Inner>
+ * {
+ *     ...
+ * };
+ * @endcode
+ *
+ * This gives you @c public get() and @c protected set() methods without
+ * having to make your LLLazy<Inner> member @c protected. The tradeoff is that
+ * you must access the wrapped LLLazy<Inner> using get() and set() rather than
+ * with <tt>operator*()</tt> or <tt>operator->()</tt>.
+ *
+ * This mechanism can be used for more than one member, but only if they're of
+ * different types. That is, you can replace:
+ * @code
+ * DifficultClass mDifficult;
+ * AwkwardType    mAwkward;
+ * @endcode
+ * with:
+ * @code
+ * class Outer: public LLLazyBase<DifficultClass>, public LLLazyBase<AwkwardType>
+ * {
+ *     ...
+ * };
+ * @endcode
+ * but for a situation like this:
+ * @code
+ * DifficultClass mMainDifficult, mAuxDifficult;
+ * @endcode
+ * you should directly embed LLLazy<DifficultClass> (q.v.).
+ *
+ * For multiple LLLazyBase bases, e.g. the <tt>LLLazyBase<DifficultClass>,
+ * LLLazyBase<AwkwardType></tt> example above, access the relevant get()/set()
+ * as (e.g.) <tt>LLLazyBase<DifficultClass>::get()</tt>. (This is why you
+ * can't have multiple LLLazyBase<T> of the same T.) For a bit of syntactic
+ * sugar, please see getLazy()/setLazy().
+ */
+template <typename T>
+class LLLazyBase
+{
+public:
+    /// invoke default LLLazy constructor
+    LLLazyBase() {}
+    /// make wrapped LLLazy bind an explicit Factory
+    LLLazyBase(const typename LLLazy<T>::Factory& factory):
+        mInstance(factory)
+    {}
+
+    /// access to LLLazy::get()
+    T& get() { return *mInstance; }
+    /// access to LLLazy::get()
+    const T& get() const { return *mInstance; }
+
+protected:
+    // see getLazy()/setLazy()
+    #if (! GCC33)
+    template <typename T2, class MYCLASS> friend T2& getLazy(MYCLASS* this_);
+    template <typename T2, class MYCLASS> friend const T2& getLazy(const MYCLASS* this_);
+    #else // gcc 3.3
+    template <typename T2, class MYCLASS> friend T2& getLazy(const MYCLASS* this_);
+    #endif // gcc 3.3
+    template <typename T2, class MYCLASS> friend void setLazy(MYCLASS* this_, T2* instance);
+    template <typename T2, class MYCLASS>
+    friend void setLazy(MYCLASS* this_, const typename LLLazy<T2>::Factory& factory);
+
+    /// access to LLLazy::set(Factory)
+    void set(const typename LLLazy<T>::Factory& factory)
+    {
+        mInstance.set(factory);
+    }
+
+    /// access to LLLazy::set(T*)
+    void set(T* instance)
+    {
+        mInstance.set(instance);
+    }
+
+private:
+    LLLazy<T> mInstance;
+};
+
+/**
+ * @name getLazy()/setLazy()
+ * Suppose you have something like the following:
+ * @code
+ * class Outer: public LLLazyBase<DifficultClass>, public LLLazyBase<AwkwardType>
+ * {
+ *     ...
+ * };
+ * @endcode
+ *
+ * Your methods can reference the @c DifficultClass instance using
+ * <tt>LLLazyBase<DifficultClass>::get()</tt>, which is admittedly a bit ugly.
+ * Alternatively, you can write <tt>getLazy<DifficultClass>(this)</tt>, which
+ * is somewhat more straightforward to read.
+ *
+ * Similarly,
+ * @code
+ * LLLazyBase<DifficultClass>::set(new TestDifficultClass());
+ * @endcode
+ * could instead be written:
+ * @code
+ * setLazy<DifficultClass>(this, new TestDifficultClass());
+ * @endcode
+ *
+ * @note
+ * I wanted to provide getLazy() and setLazy() without explicitly passing @c
+ * this. That would imply making them methods on a base class rather than free
+ * functions. But if <tt>LLLazyBase<T></tt> derives normally from (say) @c
+ * LLLazyGrandBase providing those methods, then unqualified getLazy() would
+ * be ambiguous: you'd have to write <tt>LLLazyBase<T>::getLazy<T>()</tt>,
+ * which is even uglier than <tt>LLLazyBase<T>::get()</tt>, and therefore
+ * pointless. You can make the compiler not care which @c LLLazyGrandBase
+ * instance you're talking about by making @c LLLazyGrandBase a @c virtual
+ * base class of @c LLLazyBase. But in that case,
+ * <tt>LLLazyGrandBase::getLazy<T>()</tt> can't access
+ * <tt>LLLazyBase<T>::get()</tt>!
+ *
+ * We want <tt>getLazy<T>()</tt> to access <tt>LLLazyBase<T>::get()</tt> as if
+ * in the lexical context of some subclass method. Ironically, free functions
+ * let us do that better than methods on a @c virtual base class -- but that
+ * implies passing @c this explicitly. So be it.
+ */
+//@{
+#if (! GCC33)
+template <typename T, class MYCLASS>
+T& getLazy(MYCLASS* this_) { return this_->LLLazyBase<T>::get(); }
+template <typename T, class MYCLASS>
+const T& getLazy(const MYCLASS* this_) { return this_->LLLazyBase<T>::get(); }
+#else // gcc 3.3
+// For const-correctness, we really should have two getLazy() variants: one
+// accepting const MYCLASS* and returning const T&, the other accepting
+// non-const MYCLASS* and returning non-const T&. This works fine on the Mac
+// (gcc 4.0.1) and Windows (MSVC 8.0), but fails on our Linux 32-bit Debian
+// Sarge stations (gcc 3.3.5). Since I really don't know how to beat that aging
+// compiler over the head to make it do the right thing, I'm going to have to
+// move forward with the wrong thing: a single getLazy() function that accepts
+// const MYCLASS* and returns non-const T&.
+template <typename T, class MYCLASS>
+T& getLazy(const MYCLASS* this_) { return const_cast<MYCLASS*>(this_)->LLLazyBase<T>::get(); }
+#endif // gcc 3.3
+template <typename T, class MYCLASS>
+void setLazy(MYCLASS* this_, T* instance) { this_->LLLazyBase<T>::set(instance); }
+template <typename T, class MYCLASS>
+void setLazy(MYCLASS* this_, const typename LLLazy<T>::Factory& factory)
+{
+    this_->LLLazyBase<T>::set(factory);
+}
+//@}
+
+#endif /* ! defined(LL_LLLAZY_H) */
diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h
new file mode 100644
index 00000000000..1b2958020fa
--- /dev/null
+++ b/indra/llcommon/stringize.h
@@ -0,0 +1,75 @@
+/**
+ * @file   stringize.h
+ * @author Nat Goodspeed
+ * @date   2008-12-17
+ * @brief  stringize(item) template function and STRINGIZE(expression) macro
+ * 
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ * Copyright (c) 2008, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_STRINGIZE_H)
+#define LL_STRINGIZE_H
+
+#include <sstream>
+
+/**
+ * stringize(item) encapsulates an idiom we use constantly, using
+ * operator<<(std::ostringstream&, TYPE) followed by std::ostringstream::str()
+ * to render a string expressing some item.
+ */
+template <typename T>
+std::string stringize(const T& item)
+{
+    std::ostringstream out;
+    out << item;
+    return out.str();
+}
+
+/**
+ * STRINGIZE(item1 << item2 << item3 ...) effectively expands to the
+ * following:
+ * @code
+ * std::ostringstream out;
+ * out << item1 << item2 << item3 ... ;
+ * return out.str();
+ * @endcode
+ */
+#define STRINGIZE(EXPRESSION) (static_cast<std::ostringstream&>(Stringize() << EXPRESSION).str())
+
+/**
+ * Helper class for STRINGIZE() macro. Ideally the body of
+ * STRINGIZE(EXPRESSION) would look something like this:
+ * @code
+ * (std::ostringstream() << EXPRESSION).str()
+ * @endcode
+ * That doesn't work because each of the relevant operator<<() functions
+ * accepts a non-const std::ostream&, to which you can't pass a temp instance
+ * of std::ostringstream. Stringize plays the necessary const tricks to make
+ * the whole thing work.
+ */
+class Stringize
+{
+public:
+    /**
+     * This is the essence of Stringize. The leftmost << operator (the one
+     * coded in the STRINGIZE() macro) engages this operator<<() const method
+     * on the temp Stringize instance. Every other << operator (ones embedded
+     * in EXPRESSION) simply sees the std::ostream& returned by the first one.
+     *
+     * Finally, the STRINGIZE() macro downcasts that std::ostream& to
+     * std::ostringstream&.
+     */
+    template <typename T>
+    std::ostream& operator<<(const T& item) const
+    {
+        mOut << item;
+        return mOut;
+    }
+
+private:
+    mutable std::ostringstream mOut;
+};
+
+#endif /* ! defined(LL_STRINGIZE_H) */
diff --git a/indra/llcommon/tests/lllazy_test.cpp b/indra/llcommon/tests/lllazy_test.cpp
new file mode 100644
index 00000000000..db581d650f4
--- /dev/null
+++ b/indra/llcommon/tests/lllazy_test.cpp
@@ -0,0 +1,227 @@
+/**
+ * @file   lllazy_test.cpp
+ * @author Nat Goodspeed
+ * @date   2009-01-28
+ * @brief  Tests of lllazy.h.
+ * 
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "lllazy.h"
+// STL headers
+#include <iostream>
+// std headers
+// external library headers
+#include <boost/lambda/construct.hpp>
+#include <boost/lambda/bind.hpp>
+// other Linden headers
+#include "../test/lltut.h"
+
+namespace bll = boost::lambda;
+
+/*****************************************************************************
+*   Test classes
+*****************************************************************************/
+
+// Let's say that because of its many external dependencies, YuckyFoo is very
+// hard to instantiate in a test harness.
+class YuckyFoo
+{
+public:
+    virtual ~YuckyFoo() {}
+    virtual std::string whoami() const { return "YuckyFoo"; }
+};
+
+// Let's further suppose that YuckyBar is another hard-to-instantiate class.
+class YuckyBar
+{
+public:
+    YuckyBar(const std::string& which):
+        mWhich(which)
+    {}
+    virtual ~YuckyBar() {}
+
+    virtual std::string identity() const { return std::string("YuckyBar(") + mWhich + ")"; }
+
+private:
+    const std::string mWhich;
+};
+
+// Pretend that this class would be tough to test because, up until we started
+// trying to test it, it contained instances of both YuckyFoo and YuckyBar.
+// Now we've refactored so it contains LLLazy<YuckyFoo> and LLLazy<YuckyBar>.
+// More than that, it contains them by virtue of deriving from
+// LLLazyBase<YuckyFoo> and LLLazyBase<YuckyBar>.
+// We postulate two different LLLazyBases because, with only one, you need not
+// specify *which* get()/set() method you're talking about. That's a simpler
+// case.
+class NeedsTesting: public LLLazyBase<YuckyFoo>, public LLLazyBase<YuckyBar>
+{
+public:
+    NeedsTesting():
+        // mYuckyBar("RealYuckyBar")
+        LLLazyBase<YuckyBar>(bll::bind(bll::new_ptr<YuckyBar>(), "RealYuckyBar"))
+    {}
+    virtual ~NeedsTesting() {}
+
+    virtual std::string describe() const
+    {
+        return std::string("NeedsTesting(") + getLazy<YuckyFoo>(this).whoami() + ", " +
+            getLazy<YuckyBar>(this).identity() + ")";
+    }
+
+private:
+    // These instance members were moved to LLLazyBases:
+    // YuckyFoo mYuckyFoo;
+    // YuckyBar mYuckyBar;
+};
+
+// Fake up a test YuckyFoo class
+class TestFoo: public YuckyFoo
+{
+public:
+    virtual std::string whoami() const { return "TestFoo"; }
+};
+
+// and a test YuckyBar
+class TestBar: public YuckyBar
+{
+public:
+    TestBar(const std::string& which): YuckyBar(which) {}
+    virtual std::string identity() const
+    {
+        return std::string("TestBar(") + YuckyBar::identity() + ")";
+    }
+};
+
+// So here's a test subclass of NeedsTesting that uses TestFoo and TestBar
+// instead of YuckyFoo and YuckyBar.
+class TestNeedsTesting: public NeedsTesting
+{
+public:
+    TestNeedsTesting()
+    {
+        // Exercise setLazy(T*)
+        setLazy<YuckyFoo>(this, new TestFoo());
+        // Exercise setLazy(Factory)
+        setLazy<YuckyBar>(this, bll::bind(bll::new_ptr<TestBar>(), "TestYuckyBar"));
+    }
+
+    virtual std::string describe() const
+    {
+        return std::string("TestNeedsTesting(") + NeedsTesting::describe() + ")";
+    }
+
+    void toolate()
+    {
+        setLazy<YuckyFoo>(this, new TestFoo());
+    }
+};
+
+// This class tests having an explicit LLLazy<T> instance as a named member,
+// rather than deriving from LLLazyBase<T>.
+class LazyMember
+{
+public:
+    YuckyFoo& getYuckyFoo() { return *mYuckyFoo; }
+    std::string whoisit() const { return mYuckyFoo->whoami(); }
+
+protected:
+    LLLazy<YuckyFoo> mYuckyFoo;
+};
+
+// This is a test subclass of the above, dynamically replacing the
+// LLLazy<YuckyFoo> member.
+class TestLazyMember: public LazyMember
+{
+public:
+    // use factory setter
+    TestLazyMember()
+    {
+        mYuckyFoo.set(bll::new_ptr<TestFoo>());
+    }
+
+    // use instance setter
+    TestLazyMember(YuckyFoo* instance)
+    {
+        mYuckyFoo.set(instance);
+    }
+};
+
+/*****************************************************************************
+*   TUT
+*****************************************************************************/
+namespace tut
+{
+    struct lllazy_data
+    {
+    };
+    typedef test_group<lllazy_data> lllazy_group;
+    typedef lllazy_group::object lllazy_object;
+    lllazy_group lllazygrp("lllazy");
+
+    template<> template<>
+    void lllazy_object::test<1>()
+    {
+        // Instantiate an official one, just because we can
+        NeedsTesting nt;
+        // and a test one
+        TestNeedsTesting tnt;
+//      std::cout << nt.describe() << '\n';
+        ensure_equals(nt.describe(), "NeedsTesting(YuckyFoo, YuckyBar(RealYuckyBar))");
+//      std::cout << tnt.describe() << '\n';
+        ensure_equals(tnt.describe(),
+                      "TestNeedsTesting(NeedsTesting(TestFoo, TestBar(YuckyBar(TestYuckyBar))))");
+    }
+
+    template<> template<>
+    void lllazy_object::test<2>()
+    {
+        TestNeedsTesting tnt;
+        std::string threw;
+        try
+        {
+            tnt.toolate();
+        }
+        catch (const LLLazyCommon::InstanceChange& e)
+        {
+            threw = e.what();
+        }
+        ensure_contains("InstanceChange exception", threw, "replace LLLazy instance");
+    }
+
+    template<> template<>
+    void lllazy_object::test<3>()
+    {
+        {
+            LazyMember lm;
+            // operator*() on-demand instantiation
+            ensure_equals(lm.getYuckyFoo().whoami(), "YuckyFoo");
+        }
+        {
+            LazyMember lm;
+            // operator->() on-demand instantiation
+            ensure_equals(lm.whoisit(), "YuckyFoo");
+        }
+    }
+
+    template<> template<>
+    void lllazy_object::test<4>()
+    {
+        {
+            // factory setter
+            TestLazyMember tlm;
+            ensure_equals(tlm.whoisit(), "TestFoo");
+        }
+        {
+            // instance setter
+            TestLazyMember tlm(new TestFoo());
+            ensure_equals(tlm.whoisit(), "TestFoo");
+        }
+    }
+} // namespace tut
diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt
index 0f3e159802d..c0f7a4d3354 100644
--- a/indra/llmessage/CMakeLists.txt
+++ b/indra/llmessage/CMakeLists.txt
@@ -3,6 +3,7 @@
 project(llmessage)
 
 include(00-Common)
+include(LLAddBuildTest)
 include(LLCommon)
 include(LLMath)
 include(LLMessage)
@@ -63,6 +64,7 @@ set(llmessage_SOURCE_FILES
     llregionpresenceverifier.cpp
     llsdappservices.cpp
     llsdhttpserver.cpp
+    llsdmessage.cpp
     llsdmessagebuilder.cpp
     llsdmessagereader.cpp
     llsdrpcclient.cpp
@@ -156,6 +158,7 @@ set(llmessage_HEADER_FILES
     llregionpresenceverifier.h
     llsdappservices.h
     llsdhttpserver.h
+    llsdmessage.h
     llsdmessagebuilder.h
     llsdmessagereader.h
     llsdrpcclient.h
@@ -217,5 +220,6 @@ IF (NOT LINUX AND VIEWER)
     #ADD_BUILD_TEST(llhttpclientadapter llmessage)
     ADD_BUILD_TEST(lltrustedmessageservice llmessage)
     ADD_BUILD_TEST(lltemplatemessagedispatcher llmessage)
+    # Don't make llmessage depend on llsdmessage_test because ADD_COMM_BUILD_TEST depends on llmessage!
+    ADD_COMM_BUILD_TEST(llsdmessage "" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py")
 ENDIF (NOT LINUX AND VIEWER)
-
diff --git a/indra/llmessage/llsdmessage.cpp b/indra/llmessage/llsdmessage.cpp
new file mode 100644
index 00000000000..f663268466c
--- /dev/null
+++ b/indra/llmessage/llsdmessage.cpp
@@ -0,0 +1,150 @@
+/**
+ * @file   llsdmessage.cpp
+ * @author Nat Goodspeed
+ * @date   2008-10-31
+ * @brief  Implementation for llsdmessage.
+ * 
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ * Copyright (c) 2008, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if LL_WINDOWS
+#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want!
+#endif
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llsdmessage.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "llevents.h"
+#include "llsdserialize.h"
+#include "llhttpclient.h"
+#include "llmessageconfig.h"
+#include "llhost.h"
+#include "message.h"
+#include "llsdutil.h"
+
+// Declare a static LLSDMessage instance to ensure that we have a listener as
+// soon as someone tries to post on our canonical LLEventPump name.
+static LLSDMessage httpListener;
+
+LLSDMessage::LLSDMessage():
+    // Instantiating our own local LLEventPump with a string name the
+    // constructor is NOT allowed to tweak is a way of ensuring Singleton
+    // semantics: attempting to instantiate a second LLSDMessage object would
+    // throw LLEventPump::DupPumpName.
+    mEventPump("LLHTTPClient")
+{
+    mEventPump.listen("self", boost::bind(&LLSDMessage::httpListener, this, _1));
+}
+
+bool LLSDMessage::httpListener(const LLSD& request)
+{
+    // Extract what we want from the request object. We do it all up front
+    // partly to document what we expect.
+    LLSD::String url(request["url"]);
+    LLSD payload(request["payload"]);
+    LLSD::String reply(request["reply"]);
+    LLSD::String error(request["error"]);
+    LLSD::Real timeout(request["timeout"]);
+    // If the LLSD doesn't even have a "url" key, we doubt it was intended for
+    // this listener.
+    if (url.empty())
+    {
+        std::ostringstream out;
+        out << "request event without 'url' key to '" << mEventPump.getName() << "'";
+        throw ArgError(out.str());
+    }
+    // Establish default timeout. This test relies on LLSD::asReal() returning
+    // exactly 0.0 for an undef value.
+    if (! timeout)
+    {
+        timeout = HTTP_REQUEST_EXPIRY_SECS;
+    }
+    LLHTTPClient::post(url, payload,
+                       new LLSDMessage::EventResponder(LLEventPumps::instance(),
+                                                       url, "POST", reply, error),
+                       LLSD(),      // headers
+                       timeout);
+    return false;
+}
+
+void LLSDMessage::EventResponder::result(const LLSD& data)
+{
+    // If our caller passed an empty replyPump name, they're not
+    // listening: this is a fire-and-forget message. Don't bother posting
+    // to the pump whose name is "".
+    if (! mReplyPump.empty())
+    {
+        mPumps.obtain(mReplyPump).post(data);
+    }
+    else                            // default success handling
+    {
+        LL_INFOS("LLSDMessage::EventResponder")
+            << "'" << mMessage << "' to '" << mTarget << "' succeeded"
+            << LL_ENDL;
+    }
+}
+
+void LLSDMessage::EventResponder::error(U32 status, const std::string& reason, const LLSD& content)
+{
+    // If our caller passed an empty errorPump name, they're not
+    // listening: "default error handling is acceptable." Only post to an
+    // explicit pump name.
+    if (! mErrorPump.empty())
+    {
+        LLSD info;
+        info["target"]  = mTarget;
+        info["message"] = mMessage;
+        info["status"]  = LLSD::Integer(status);
+        info["reason"]  = reason;
+        info["content"] = content;
+        mPumps.obtain(mErrorPump).post(info);
+    }
+    else                        // default error handling
+    {
+        // convention seems to be to use llinfos, but that seems a bit casual?
+        LL_WARNS("LLSDMessage::EventResponder")
+            << "'" << mMessage << "' to '" << mTarget
+            << "' failed with code " << status << ": " << reason << '\n'
+            << ll_pretty_print_sd(content)
+            << LL_ENDL;
+    }
+}
+
+LLSDMessage::ResponderAdapter::ResponderAdapter(LLHTTPClient::ResponderPtr responder,
+                                                const std::string& name):
+    mResponder(responder),
+    mReplyPump(name + ".reply", true), // tweak name for uniqueness
+    mErrorPump(name + ".error", true)
+{
+    mReplyPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, true));
+    mErrorPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, false));
+}
+
+bool LLSDMessage::ResponderAdapter::listener(const LLSD& payload, bool success)
+{
+    if (success)
+    {
+        mResponder->result(payload);
+    }
+    else
+    {
+        mResponder->error(payload["status"].asInteger(), payload["reason"], payload["content"]);
+    }
+
+    /*---------------- MUST BE LAST STATEMENT BEFORE RETURN ----------------*/
+    delete this;
+    // Destruction of mResponder will usually implicitly free its referent as well
+    /*------------------------- NOTHING AFTER THIS -------------------------*/
+    return false;
+}
+
+void LLSDMessage::link()
+{
+}
diff --git a/indra/llmessage/llsdmessage.h b/indra/llmessage/llsdmessage.h
new file mode 100644
index 00000000000..8ae94512436
--- /dev/null
+++ b/indra/llmessage/llsdmessage.h
@@ -0,0 +1,146 @@
+/**
+ * @file   llsdmessage.h
+ * @author Nat Goodspeed
+ * @date   2008-10-30
+ * @brief  API intended to unify sending capability, UDP and TCP messages:
+ *         https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes
+ * 
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ * Copyright (c) 2008, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLSDMESSAGE_H)
+#define LL_LLSDMESSAGE_H
+
+#include "llerror.h"                // LOG_CLASS()
+#include "llevents.h"               // LLEventPumps
+#include "llhttpclient.h"
+#include <string>
+#include <stdexcept>
+
+class LLSD;
+
+/**
+ * Class managing the messaging API described in
+ * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes
+ */
+class LLSDMessage
+{
+    LOG_CLASS(LLSDMessage);
+
+public:
+    LLSDMessage();
+
+    /// Exception if you specify arguments badly
+    struct ArgError: public std::runtime_error
+    {
+        ArgError(const std::string& what):
+            std::runtime_error(std::string("ArgError: ") + what) {}
+    };
+
+    /**
+     * The response idiom used by LLSDMessage -- LLEventPump names on which to
+     * post reply or error -- is designed for the case in which your
+     * reply/error handlers are methods on the same class as the method
+     * sending the message. Any state available to the sending method that
+     * must be visible to the reply/error methods can conveniently be stored
+     * on that class itself, if it's not already.
+     *
+     * The LLHTTPClient::Responder idiom requires a separate instance of a
+     * separate class so that it can dispatch to the code of interest by
+     * calling canonical virtual methods. Interesting state must be copied
+     * into that new object. 
+     *
+     * With some trepidation, because existing response code is packaged in
+     * LLHTTPClient::Responder subclasses, we provide this adapter class
+     * <i>for transitional purposes only.</i> Instantiate a new heap
+     * ResponderAdapter with your new LLHTTPClient::ResponderPtr. Pass
+     * ResponderAdapter::getReplyName() and/or getErrorName() in your
+     * LLSDMessage (or LLViewerRegion::getCapAPI()) request event. The
+     * ResponderAdapter will call the appropriate Responder method, then
+     * @c delete itself.
+     */
+    class ResponderAdapter
+    {
+    public:
+        /**
+         * Bind the new LLHTTPClient::Responder subclass instance.
+         *
+         * Passing the constructor a name other than the default is only
+         * interesting if you suspect some usage will lead to an exception or
+         * log message.
+         */
+        ResponderAdapter(LLHTTPClient::ResponderPtr responder,
+                         const std::string& name="ResponderAdapter");
+
+        /// EventPump name on which LLSDMessage should post reply event
+        std::string getReplyName() const { return mReplyPump.getName(); }
+        /// EventPump name on which LLSDMessage should post error event
+        std::string getErrorName() const { return mErrorPump.getName(); }
+
+    private:
+        // We have two different LLEventStreams, though we route them both to
+        // the same listener, so that we can bind an extra flag identifying
+        // which case (reply or error) reached that listener.
+        bool listener(const LLSD&, bool success);
+
+        LLHTTPClient::ResponderPtr mResponder;
+        LLEventStream mReplyPump, mErrorPump;
+    };
+
+    /**
+     * Force our implementation file to be linked with caller. The .cpp file
+     * contains a static instance of this class, which must be linked into the
+     * executable to support the canonical listener. But since the primary
+     * interface to that static instance is via a named LLEventPump rather
+     * than by direct reference, the linker doesn't necessarily perceive the
+     * necessity to bring in the translation unit. Referencing this dummy
+     * method forces the issue.
+     */
+    static void link();
+
+private:
+    friend class LLCapabilityListener;
+    /// Responder used for internal purposes by LLSDMessage and
+    /// LLCapabilityListener. Others should use higher-level APIs.
+    class EventResponder: public LLHTTPClient::Responder
+    {
+    public:
+        /**
+         * LLHTTPClient::Responder that dispatches via named LLEventPump instances.
+         * We bind LLEventPumps, even though it's an LLSingleton, for testability.
+         * We bind the string names of the desired LLEventPump instances rather
+         * than actually obtain()ing them so we only obtain() the one we're going
+         * to use. If the caller doesn't bother to listen() on it, the other pump
+         * may never materialize at all.
+         * @a target and @a message are only to clarify error processing.
+         * For a capability message, @a target should be the region description,
+         * @a message should be the capability name.
+         * For a service with a visible URL, pass the URL as @a target and the HTTP verb
+         * (e.g. "POST") as @a message.
+         */
+        EventResponder(LLEventPumps& pumps,
+                       const std::string& target, const std::string& message,
+                       const std::string& replyPump, const std::string& errorPump):
+            mPumps(pumps),
+            mTarget(target),
+            mMessage(message),
+            mReplyPump(replyPump),
+            mErrorPump(errorPump)
+        {}
+    
+        virtual void result(const LLSD& data);
+        virtual void error(U32 status, const std::string& reason, const LLSD& content);
+    
+    private:
+        LLEventPumps& mPumps;
+        const std::string mTarget, mMessage, mReplyPump, mErrorPump;
+    };
+
+private:
+    bool httpListener(const LLSD&);
+    LLEventStream mEventPump;
+};
+
+#endif /* ! defined(LL_LLSDMESSAGE_H) */
diff --git a/indra/llmessage/tests/llsdmessage_test.cpp b/indra/llmessage/tests/llsdmessage_test.cpp
new file mode 100644
index 00000000000..2957d7cc4f1
--- /dev/null
+++ b/indra/llmessage/tests/llsdmessage_test.cpp
@@ -0,0 +1,113 @@
+/**
+ * @file   llsdmessage_tut.cpp
+ * @author Nat Goodspeed
+ * @date   2008-12-22
+ * @brief  Test of llsdmessage.h
+ * 
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ * Copyright (c) 2008, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if LL_WINDOWS
+#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want!
+#endif
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llsdmessage.h"
+// STL headers
+#include <iostream>
+// std headers
+#include <stdexcept>
+// external library headers
+// other Linden headers
+#include "../test/lltut.h"
+#include "llsdserialize.h"
+#include "llevents.h"
+#include "stringize.h"
+#include "llhost.h"
+#include "tests/networkio.h"
+#include "tests/commtest.h"
+
+/*****************************************************************************
+*   TUT
+*****************************************************************************/
+namespace tut
+{
+    struct llsdmessage_data: public commtest_data
+    {
+        LLEventPump& httpPump;
+
+        llsdmessage_data():
+            httpPump(pumps.obtain("LLHTTPClient"))
+        {
+            LLSDMessage::link();
+        }
+    };
+    typedef test_group<llsdmessage_data> llsdmessage_group;
+    typedef llsdmessage_group::object llsdmessage_object;
+    llsdmessage_group llsdmgr("llsdmessage");
+
+    template<> template<>
+    void llsdmessage_object::test<1>()
+    {
+        bool threw = false;
+        // This should fail...
+        try
+        {
+            LLSDMessage localListener;
+        }
+        catch (const LLEventPump::DupPumpName&)
+        {
+            threw = true;
+        }
+        ensure("second LLSDMessage should throw", threw);
+    }
+
+    template<> template<>
+    void llsdmessage_object::test<2>()
+    {
+        LLSD request, body;
+        body["data"] = "yes";
+        request["payload"] = body;
+        request["reply"] = replyPump.getName();
+        request["error"] = errorPump.getName();
+        bool threw = false;
+        try
+        {
+            httpPump.post(request);
+        }
+        catch (const LLSDMessage::ArgError&)
+        {
+            threw = true;
+        }
+        ensure("missing URL", threw);
+    }
+
+    template<> template<>
+    void llsdmessage_object::test<3>()
+    {
+        LLSD request, body;
+        body["data"] = "yes";
+        request["url"] = server + "got-message";
+        request["payload"] = body;
+        request["reply"] = replyPump.getName();
+        request["error"] = errorPump.getName();
+        httpPump.post(request);
+        ensure("got response", netio.pump());
+        ensure("success response", success);
+        ensure_equals(result.asString(), "success");
+
+        body["status"] = 499;
+        body["reason"] = "custom error message";
+        request["url"] = server + "fail";
+        request["payload"] = body;
+        httpPump.post(request);
+        ensure("got response", netio.pump());
+        ensure("failure response", ! success);
+        ensure_equals(result["status"].asInteger(), body["status"].asInteger());
+        ensure_equals(result["reason"].asString(),  body["reason"].asString());
+    }
+} // namespace tut
diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp
index 0762d7c12db..a8d06643ff1 100644
--- a/indra/llui/llmenugl.cpp
+++ b/indra/llui/llmenugl.cpp
@@ -67,6 +67,8 @@
 #include <set>
 #include <boost/tokenizer.hpp>
 
+using namespace LLOldEvents;
+
 // static
 LLMenuHolderGL *LLMenuGL::sMenuContainer = NULL;
 
diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h
index 030b81b58cb..e62402d6179 100644
--- a/indra/llui/llmenugl.h
+++ b/indra/llui/llmenugl.h
@@ -224,7 +224,7 @@ class LLMenuItemGL : public LLView
 // calls a user defined callback.
 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-class LLMenuItemCallGL : public LLMenuItemGL, public LLObservable
+class LLMenuItemCallGL : public LLMenuItemGL, public LLOldEvents::LLObservable
 {
 public:
 	// normal constructor
diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp
index 6b7dd0a3dee..fe1ea950703 100644
--- a/indra/llui/llnotifications.cpp
+++ b/indra/llui/llnotifications.cpp
@@ -714,7 +714,7 @@ std::string LLNotification::getLabel() const
 // =========================================================
 // LLNotificationChannel implementation
 // ---
-void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type& slot)
+LLBoundListener LLNotificationChannelBase::connectChangedImpl(const LLEventListener& slot)
 {
 	// when someone wants to connect to a channel, we first throw them
 	// all of the notifications that are already in the channel
@@ -722,23 +722,23 @@ void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type
 	// only about new notifications
 	for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
 	{
-		slot.get_slot_function()(LLSD().insert("sigtype", "load").insert("id", (*it)->id()));
+		slot(LLSD().insert("sigtype", "load").insert("id", (*it)->id()));
 	}
 	// and then connect the signal so that all future notifications will also be
 	// forwarded.
-	mChanged.connect(slot);
+	return mChanged.connect(slot);
 }
 
-void LLNotificationChannelBase::connectPassedFilter(const LLStandardSignal::slot_type& slot)
+LLBoundListener LLNotificationChannelBase::connectPassedFilterImpl(const LLEventListener& slot)
 {
 	// these two filters only fire for notifications added after the current one, because
 	// they don't participate in the hierarchy.
-	mPassedFilter.connect(slot);
+	return mPassedFilter.connect(slot);
 }
 
-void LLNotificationChannelBase::connectFailedFilter(const LLStandardSignal::slot_type& slot)
+LLBoundListener LLNotificationChannelBase::connectFailedFilterImpl(const LLEventListener& slot)
 {
-	mFailedFilter.connect(slot);
+	return mFailedFilter.connect(slot);
 }
 
 // external call, conforms to our standard signature
@@ -896,8 +896,7 @@ mParent(parent)
 	else
 	{
 		LLNotificationChannelPtr p = LLNotifications::instance().getChannel(parent);
-		LLStandardSignal::slot_type f = boost::bind(&LLNotificationChannelBase::updateItem, this, _1);
-		p->connectChanged(f);
+		p->connectChanged(boost::bind(&LLNotificationChannelBase::updateItem, this, _1));
 	}
 }
 
@@ -1093,11 +1092,11 @@ void LLNotifications::createDefaultChannels()
 
 	// connect action methods to these channels
 	LLNotifications::instance().getChannel("Expiration")->
-		connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1));
+        connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1));
 	LLNotifications::instance().getChannel("Unique")->
-		connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1));
+        connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1));
 	LLNotifications::instance().getChannel("Unique")->
-		connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1));
+        connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1));
 	LLNotifications::instance().getChannel("Ignore")->
 		connectFailedFilter(&handleIgnoredNotification);
 }
diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h
index bb379121cc2..d01296c89e8 100644
--- a/indra/llui/llnotifications.h
+++ b/indra/llui/llnotifications.h
@@ -91,49 +91,20 @@
 
 #include <boost/utility.hpp>
 #include <boost/shared_ptr.hpp>
-#include <boost/signal.hpp>
+#include <boost/enable_shared_from_this.hpp>
 #include <boost/type_traits.hpp>
 
 // we want to minimize external dependencies, but this one is important
 #include "llsd.h"
 
 // and we need this to manage the notification callbacks
+#include "llevents.h"
 #include "llfunctorregistry.h"
 #include "llui.h"
 
 class LLNotification;
 typedef boost::shared_ptr<LLNotification> LLNotificationPtr;
 
-/*****************************************************************************
-*   Signal and handler declarations
-*   Using a single handler signature means that we can have a common handler
-*   type, rather than needing a distinct one for each different handler.
-*****************************************************************************/
-
-/**
- * A boost::signals Combiner that stops the first time a handler returns true
- * We need this because we want to have our handlers return bool, so that
- * we have the option to cause a handler to stop further processing. The
- * default handler fails when the signal returns a value but has no slots.
- */
-struct LLStopWhenHandled
-{
-    typedef bool result_type;
-
-    template<typename InputIterator>
-    result_type operator()(InputIterator first, InputIterator last) const
-    {
-        for (InputIterator si = first; si != last; ++si)
-		{
-            if (*si)
-			{
-                return true;
-			}
-		}
-        return false;
-    }
-};
-
 	
 typedef enum e_notification_priority
 {
@@ -144,27 +115,11 @@ typedef enum e_notification_priority
 	NOTIFICATION_PRIORITY_CRITICAL
 } ENotificationPriority;
 
-/**
- * We want to have a standard signature for all signals; this way,
- * we can easily document a protocol for communicating across
- * dlls and into scripting languages someday.
- * we want to return a bool to indicate whether the signal has been
- * handled and should NOT be passed on to other listeners.
- * Return true to stop further handling of the signal, and false
- * to continue.
- * We take an LLSD because this way the contents of the signal
- * are independent of the API used to communicate it.
- * It is const ref because then there's low cost to pass it;
- * if you only need to inspect it, it's very cheap.
- */
-
 typedef boost::function<void (const LLSD&, const LLSD&)> LLNotificationResponder;
 
 typedef LLFunctorRegistry<LLNotificationResponder> LLNotificationFunctorRegistry;
 typedef LLFunctorRegistration<LLNotificationResponder> LLNotificationFunctorRegistration;
 
-typedef boost::signal<bool(const LLSD&), LLStopWhenHandled>  LLStandardSignal;
-
 // context data that can be looked up via a notification's payload by the display logic
 // derive from this class to implement specific contexts
 class LLNotificationContext : public LLInstanceTracker<LLNotificationContext, LLUUID>
@@ -699,7 +654,7 @@ typedef std::multimap<std::string, LLNotificationPtr> LLNotificationMap;
 // all of the built-in tests should attach to the "Visible" channel
 //
 class LLNotificationChannelBase :
-	public boost::signals::trackable
+	public LLEventTrackable
 {
 	LOG_CLASS(LLNotificationChannelBase);
 public:
@@ -709,15 +664,45 @@ class LLNotificationChannelBase :
 	virtual ~LLNotificationChannelBase() {}
 	// you can also connect to a Channel, so you can be notified of
 	// changes to this channel
-	virtual void connectChanged(const LLStandardSignal::slot_type& slot);
-	virtual void connectPassedFilter(const LLStandardSignal::slot_type& slot);
-	virtual void connectFailedFilter(const LLStandardSignal::slot_type& slot);
+	template <typename LISTENER>
+    LLBoundListener connectChanged(const LISTENER& slot)
+    {
+        // Examine slot to see if it binds an LLEventTrackable subclass, or a
+        // boost::shared_ptr to something, or a boost::weak_ptr to something.
+        // Call this->connectChangedImpl() to actually connect it.
+        return LLEventDetail::visit_and_connect(slot,
+                                  boost::bind(&LLNotificationChannelBase::connectChangedImpl,
+                                              this,
+                                              _1));
+    }
+    template <typename LISTENER>
+	LLBoundListener connectPassedFilter(const LISTENER& slot)
+    {
+        // see comments in connectChanged()
+        return LLEventDetail::visit_and_connect(slot,
+                                  boost::bind(&LLNotificationChannelBase::connectPassedFilterImpl,
+                                              this,
+                                              _1));
+    }
+    template <typename LISTENER>
+	LLBoundListener connectFailedFilter(const LISTENER& slot)
+    {
+        // see comments in connectChanged()
+        return LLEventDetail::visit_and_connect(slot,
+                                  boost::bind(&LLNotificationChannelBase::connectFailedFilterImpl,
+                                              this,
+                                              _1));
+    }
 
 	// use this when items change or to add a new one
 	bool updateItem(const LLSD& payload);
 	const LLNotificationFilter& getFilter() { return mFilter; }
 
 protected:
+    LLBoundListener connectChangedImpl(const LLEventListener& slot);
+    LLBoundListener connectPassedFilterImpl(const LLEventListener& slot);
+    LLBoundListener connectFailedFilterImpl(const LLEventListener& slot);
+
 	LLNotificationSet mItems;
 	LLStandardSignal mChanged;
 	LLStandardSignal mPassedFilter;
diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h
index 756d02ef7d9..a7c95790307 100644
--- a/indra/llui/llpanel.h
+++ b/indra/llui/llpanel.h
@@ -42,6 +42,7 @@
 #include "llviewborder.h"
 #include "lluistring.h"
 #include "v4color.h"
+#include "llevents.h"
 #include <list>
 #include <queue>
 
@@ -56,7 +57,7 @@ const BOOL BORDER_NO = FALSE;
  * With or without border,
  * Can contain LLUICtrls.
  */
-class LLPanel : public LLUICtrl, public boost::signals::trackable
+class LLPanel : public LLUICtrl, public LLEventTrackable
 {
 public:
 
diff --git a/indra/llui/llui.h b/indra/llui/llui.h
index ebcc7304b18..6d6ce7a97c5 100644
--- a/indra/llui/llui.h
+++ b/indra/llui/llui.h
@@ -43,7 +43,7 @@
 #include "llgl.h"			// *TODO: break this dependency
 #include <stack>
 //#include "llimagegl.h"
-#include <boost/signal.hpp>
+#include <boost/signals2.hpp>
 
 // LLUIFactory
 #include "llsd.h"
@@ -661,7 +661,7 @@ template <typename T> std::set<T*> LLInstanceTracker<T, T*>::sInstances;
 class LLCallbackRegistry
 {
 public:
-	typedef boost::signal<void()> callback_signal_t;
+	typedef boost::signals2::signal<void()> callback_signal_t;
 	
 	void registerCallback(const callback_signal_t::slot_type& slot)
 	{
diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp
index 2350ea60505..8ec681fcaf6 100644
--- a/indra/llui/llview.cpp
+++ b/indra/llui/llview.cpp
@@ -56,6 +56,8 @@
 #include "lltexteditor.h"
 #include "lltextbox.h"
 
+using namespace LLOldEvents;
+
 //HACK: this allows you to instantiate LLView from xml with "<view/>" which we don't want
 static LLRegisterWidget<LLView> r("view");
 
diff --git a/indra/llui/llview.h b/indra/llui/llview.h
index e0e0f6ba47a..721fe99f4a6 100644
--- a/indra/llui/llview.h
+++ b/indra/llui/llview.h
@@ -441,11 +441,11 @@ class LLView : public LLMouseHandler, public LLMortician
 	void localRectToScreen( const LLRect& local, LLRect* screen ) const;
 	
 	// Listener dispatching functions (Dispatcher deletes pointers to listeners on deregistration or destruction)
-	LLSimpleListener* getListenerByName(const std::string& callback_name);
-	void registerEventListener(std::string name, LLSimpleListener* function);
+    LLOldEvents::LLSimpleListener* getListenerByName(const std::string& callback_name);
+	void registerEventListener(std::string name, LLOldEvents::LLSimpleListener* function);
 	void deregisterEventListener(std::string name);
-	std::string findEventListener(LLSimpleListener *listener) const;
-	void addListenerToControl(LLEventDispatcher *observer, const std::string& name, LLSD filter, LLSD userdata);
+	std::string findEventListener(LLOldEvents::LLSimpleListener *listener) const;
+	void addListenerToControl(LLOldEvents::LLEventDispatcher *observer, const std::string& name, LLSD filter, LLSD userdata);
 
 	void addBoolControl(const std::string& name, bool initial_value);
 	LLControlVariable *getControl(const std::string& name);
@@ -651,7 +651,7 @@ class LLView : public LLMouseHandler, public LLMortician
 
 	static LLWindow* sWindow;	// All root views must know about their window.
 
-	typedef std::map<std::string, LLPointer<LLSimpleListener> > dispatch_list_t;
+	typedef std::map<std::string, LLPointer<LLOldEvents::LLSimpleListener> > dispatch_list_t;
 	dispatch_list_t mDispatchList;
 
 	std::string		mControlName;
@@ -659,7 +659,7 @@ class LLView : public LLMouseHandler, public LLMortician
 	typedef std::map<std::string, LLView*> dummy_widget_map_t;
 	mutable dummy_widget_map_t mDummyWidgets;
 
-	boost::signals::connection mControlConnection;
+	boost::signals2::connection mControlConnection;
 
 	ECursorType mHoverCursor;
 	
diff --git a/indra/llxml/CMakeLists.txt b/indra/llxml/CMakeLists.txt
index dc7787beea9..c5fb44e7215 100644
--- a/indra/llxml/CMakeLists.txt
+++ b/indra/llxml/CMakeLists.txt
@@ -39,6 +39,5 @@ add_library (llxml ${llxml_SOURCE_FILES})
 # Sort by high-level to low-level
 target_link_libraries( llxml
     llmath
-    ${BOOST_SIGNALS_LIBRARY}
     ${EXPAT_LIBRARIES}
     )
diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h
index ba0a1c7cbff..a39a56aacd7 100644
--- a/indra/llxml/llcontrol.h
+++ b/indra/llxml/llcontrol.h
@@ -53,7 +53,7 @@
 #endif
 
 #include <boost/bind.hpp>
-#include <boost/signal.hpp>
+#include <boost/signals2.hpp>
 
 #if LL_WINDOWS
 # if (_MSC_VER >= 1300 && _MSC_VER < 1400)
@@ -89,7 +89,7 @@ typedef enum e_control_type
 class LLControlVariable : public LLRefCount
 {
 	friend class LLControlGroup;
-	typedef boost::signal<void(const LLSD&)> signal_t;
+	typedef boost::signals2::signal<void(const LLSD&)> signal_t;
 
 private:
 	std::string		mName;
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 7b1c3003f0d..57f1716db5d 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -78,6 +78,7 @@ set(viewer_SOURCE_FILES
     llbox.cpp
     llcallbacklist.cpp
     llcallingcard.cpp
+    llcapabilitylistener.cpp
     llcaphttpsender.cpp
     llchatbar.cpp
     llclassifiedinfo.cpp
@@ -481,6 +482,8 @@ set(viewer_HEADER_FILES
     llbox.h
     llcallbacklist.h
     llcallingcard.h
+    llcapabilitylistener.h
+    llcapabilityprovider.h
     llcaphttpsender.h
     llchatbar.h
     llclassifiedinfo.h
@@ -1391,3 +1394,5 @@ if (INSTALL)
 endif (INSTALL)
 
 ADD_VIEWER_BUILD_TEST(llagentaccess viewer)
+ADD_VIEWER_COMM_BUILD_TEST(llcapabilitylistener viewer 
+  ${CMAKE_CURRENT_SOURCE_DIR}/../llmessage/tests/test_llsdmessage_peer.py)
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index 98526c8a570..07503404dce 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -134,6 +134,8 @@
 #include "llappviewer.h"
 #include "llviewerjoystick.h"
 #include "llfollowcam.h"
+#include "stringize.h"
+#include "llcapabilitylistener.h"
 
 using namespace LLVOAvatarDefines;
 
@@ -914,7 +916,7 @@ LLViewerRegion *LLAgent::getRegion() const
 }
 
 
-const LLHost& LLAgent::getRegionHost() const
+LLHost LLAgent::getRegionHost() const
 {
 	if (mRegionp)
 	{
@@ -4674,109 +4676,128 @@ void LLAgent::lookAtLastChat()
 
 const F32 SIT_POINT_EXTENTS = 0.2f;
 
+LLSD ll_sdmap_from_vector3(const LLVector3& vec)
+{
+    LLSD ret;
+    ret["X"] = vec.mV[VX];
+    ret["Y"] = vec.mV[VY];
+    ret["Z"] = vec.mV[VZ];
+    return ret;
+}
+
+LLVector3 ll_vector3_from_sdmap(const LLSD& sd)
+{
+    LLVector3 ret;
+    ret.mV[VX] = F32(sd["X"].asReal());
+    ret.mV[VY] = F32(sd["Y"].asReal());
+    ret.mV[VZ] = F32(sd["Z"].asReal());
+    return ret;
+}
+
 void LLAgent::setStartPosition( U32 location_id )
 {
-  LLViewerObject          *object;
+    LLViewerObject          *object;
 
-  if ( !(gAgentID == LLUUID::null) )
-  {
+    if (gAgentID == LLUUID::null)
+    {
+        return;
+    }
     // we've got an ID for an agent viewerobject
     object = gObjectList.findObject(gAgentID);
-    if (object)
+    if (! object)
     {
-      // we've got the viewer object
-      // Sometimes the agent can be velocity interpolated off of
-      // this simulator.  Clamp it to the region the agent is
-      // in, a little bit in on each side.
-      const F32 INSET = 0.5f; //meters
-      const F32 REGION_WIDTH = LLWorld::getInstance()->getRegionWidthInMeters();
-
-      LLVector3 agent_pos = getPositionAgent();
-      LLVector3 agent_look_at = mFrameAgent.getAtAxis();
-
-      if (mAvatarObject.notNull())
-      {
-	// the z height is at the agent's feet
-	agent_pos.mV[VZ] -= 0.5f * mAvatarObject->mBodySize.mV[VZ];
-      }
-
-      agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET );
-      agent_pos.mV[VY] = llclamp( agent_pos.mV[VY], INSET, REGION_WIDTH - INSET );
-
-      // Don't let them go below ground, or too high.
-      agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ],
-				  mRegionp->getLandHeightRegion( agent_pos ),
-				  LLWorld::getInstance()->getRegionMaxHeight() );
-      // Send the CapReq
-
-      LLSD body;
-
-      std::string url = gAgent.getRegion()->getCapability("HomeLocation");
-      std::ostringstream strBuffer;
-      if( url.empty() )
-      {
-	LLMessageSystem* msg = gMessageSystem;
-	msg->newMessageFast(_PREHASH_SetStartLocationRequest);
-	msg->nextBlockFast( _PREHASH_AgentData);
-	msg->addUUIDFast(_PREHASH_AgentID, getID());
-	msg->addUUIDFast(_PREHASH_SessionID, getSessionID());
-	msg->nextBlockFast( _PREHASH_StartLocationData);
-	// corrected by sim
-	msg->addStringFast(_PREHASH_SimName, "");
-	msg->addU32Fast(_PREHASH_LocationID, location_id);
-	msg->addVector3Fast(_PREHASH_LocationPos, agent_pos);
-	msg->addVector3Fast(_PREHASH_LocationLookAt,mFrameAgent.getAtAxis());
-	
-	// Reliable only helps when setting home location.  Last
-	// location is sent on quit, and we don't have time to ack
-	// the packets.
-	msg->sendReliable(mRegionp->getHost());
-
-	const U32 HOME_INDEX = 1;
-	if( HOME_INDEX == location_id )
-	  {
-	    setHomePosRegion( mRegionp->getHandle(), getPositionAgent() );
-	  }
-      }
-      else
-      {
-	strBuffer << location_id;
-	body["HomeLocation"]["LocationId"] = strBuffer.str();
-
-	strBuffer.str("");
-	strBuffer << agent_pos.mV[VX];
-	body["HomeLocation"]["LocationPos"]["X"] = strBuffer.str();
-
-	strBuffer.str("");
-	strBuffer << agent_pos.mV[VY];
-	body["HomeLocation"]["LocationPos"]["Y"] = strBuffer.str();
-
-	strBuffer.str("");
-	strBuffer << agent_pos.mV[VZ];
-	body["HomeLocation"]["LocationPos"]["Z"] = strBuffer.str();
-
-	strBuffer.str("");
-	strBuffer << agent_look_at.mV[VX];
-	body["HomeLocation"]["LocationLookAt"]["X"] = strBuffer.str();
-
-	strBuffer.str("");
-	strBuffer << agent_look_at.mV[VY];
-	body["HomeLocation"]["LocationLookAt"]["Y"] = strBuffer.str();
-
-	strBuffer.str("");
-	strBuffer << agent_look_at.mV[VZ];
-	body["HomeLocation"]["LocationLookAt"]["Z"] = strBuffer.str();
-
-	LLHTTPClient::post( url, body, new LLHomeLocationResponder() );
-      }
+        llinfos << "setStartPosition - Can't find agent viewerobject id " << gAgentID << llendl;
+        return;
+    }
+    // we've got the viewer object
+    // Sometimes the agent can be velocity interpolated off of
+    // this simulator.  Clamp it to the region the agent is
+    // in, a little bit in on each side.
+    const F32 INSET = 0.5f; //meters
+    const F32 REGION_WIDTH = LLWorld::getInstance()->getRegionWidthInMeters();
+
+    LLVector3 agent_pos = getPositionAgent();
+
+    if (mAvatarObject.notNull())
+    {
+        // the z height is at the agent's feet
+        agent_pos.mV[VZ] -= 0.5f * mAvatarObject->mBodySize.mV[VZ];
     }
-    else
+
+    agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET );
+    agent_pos.mV[VY] = llclamp( agent_pos.mV[VY], INSET, REGION_WIDTH - INSET );
+
+    // Don't let them go below ground, or too high.
+    agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ],
+                                mRegionp->getLandHeightRegion( agent_pos ),
+                                LLWorld::getInstance()->getRegionMaxHeight() );
+    // Send the CapReq
+    LLSD request;
+    LLSD body;
+    LLSD homeLocation;
+
+    homeLocation["LocationId"] = LLSD::Integer(location_id);
+    homeLocation["LocationPos"] = ll_sdmap_from_vector3(agent_pos);
+    homeLocation["LocationLookAt"] = ll_sdmap_from_vector3(mFrameAgent.getAtAxis());
+
+    body["HomeLocation"] = homeLocation;
+
+    // This awkward idiom warrants explanation.
+    // For starters, LLSDMessage::ResponderAdapter is ONLY for testing the new
+    // LLSDMessage functionality with a pre-existing LLHTTPClient::Responder.
+    // In new code, define your reply/error methods on the same class as the
+    // sending method, bind them to local LLEventPump objects and pass those
+    // LLEventPump names in the request LLSD object.
+    // When testing old code, the new LLHomeLocationResponder object
+    // is referenced by an LLHTTPClient::ResponderPtr, so when the
+    // ResponderAdapter is deleted, the LLHomeLocationResponder will be too.
+    // We must trust that the underlying LLHTTPClient code will eventually
+    // fire either the reply callback or the error callback; either will cause
+    // the ResponderAdapter to delete itself.
+    LLSDMessage::ResponderAdapter*
+        adapter(new LLSDMessage::ResponderAdapter(new LLHomeLocationResponder()));
+
+    request["message"] = "HomeLocation";
+    request["payload"] = body;
+    request["reply"]   = adapter->getReplyName();
+    request["error"]   = adapter->getErrorName();
+
+    gAgent.getRegion()->getCapAPI().post(request);
+
+    const U32 HOME_INDEX = 1;
+    if( HOME_INDEX == location_id )
     {
-      llinfos << "setStartPosition - Can't find agent viewerobject id " << gAgentID << llendl;
+        setHomePosRegion( mRegionp->getHandle(), getPositionAgent() );
     }
-  }
 }
 
+struct HomeLocationMapper: public LLCapabilityListener::CapabilityMapper
+{
+    // No reply message expected
+    HomeLocationMapper(): LLCapabilityListener::CapabilityMapper("HomeLocation") {}
+    virtual void buildMessage(LLMessageSystem* msg,
+                              const LLUUID& agentID,
+                              const LLUUID& sessionID,
+                              const std::string& capabilityName,
+                              const LLSD& payload) const
+    {
+        msg->newMessageFast(_PREHASH_SetStartLocationRequest);
+        msg->nextBlockFast( _PREHASH_AgentData);
+        msg->addUUIDFast(_PREHASH_AgentID, agentID);
+        msg->addUUIDFast(_PREHASH_SessionID, sessionID);
+        msg->nextBlockFast( _PREHASH_StartLocationData);
+        // corrected by sim
+        msg->addStringFast(_PREHASH_SimName, "");
+        msg->addU32Fast(_PREHASH_LocationID, payload["HomeLocation"]["LocationId"].asInteger());
+        msg->addVector3Fast(_PREHASH_LocationPos,
+                            ll_vector3_from_sdmap(payload["HomeLocation"]["LocationPos"]));
+        msg->addVector3Fast(_PREHASH_LocationLookAt,
+                            ll_vector3_from_sdmap(payload["HomeLocation"]["LocationLookAt"]));
+    }
+};
+// Need an instance of this class so it will self-register
+static HomeLocationMapper homeLocationMapper;
+
 void LLAgent::requestStopMotion( LLMotion* motion )
 {
 	// Notify all avatars that a motion has stopped.
@@ -5400,7 +5421,7 @@ void update_group_floaters(const LLUUID& group_id)
 		gIMMgr->refresh();
 	}
 
-	gAgent.fireEvent(new LLEvent(&gAgent, "new group"), "");
+	gAgent.fireEvent(new LLOldEvents::LLEvent(&gAgent, "new group"), "");
 }
 
 // static
diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h
index 226c78e6311..27fcb07fd59 100644
--- a/indra/newview/llagent.h
+++ b/indra/newview/llagent.h
@@ -119,7 +119,7 @@ inline bool operator==(const LLGroupData &a, const LLGroupData &b)
 
 //
 
-class LLAgent : public LLObservable
+class LLAgent : public LLOldEvents::LLObservable
 {
 	LOG_CLASS(LLAgent);
 	
@@ -176,7 +176,7 @@ class LLAgent : public LLObservable
 	// Set the home data
 	void			setRegion(LLViewerRegion *regionp);
 	LLViewerRegion	*getRegion() const;
-	const LLHost&	getRegionHost() const;
+	LLHost			getRegionHost() const;
 	std::string		getSLURL() const;
 	
 	void			updateAgentPosition(const F32 dt, const F32 yaw, const S32 mouse_x, const S32 mouse_y);		// call once per frame to update position, angles radians
diff --git a/indra/newview/llagentlanguage.h b/indra/newview/llagentlanguage.h
index 6bc7250c6e3..596c5842321 100644
--- a/indra/newview/llagentlanguage.h
+++ b/indra/newview/llagentlanguage.h
@@ -36,7 +36,7 @@
 #include "llmemory.h"	// LLSingleton<>
 #include "llevent.h"
 
-class LLAgentLanguage: public LLSingleton<LLAgentLanguage>, public LLSimpleListener
+class LLAgentLanguage: public LLSingleton<LLAgentLanguage>, public LLOldEvents::LLSimpleListener
 {
  public:
 	LLAgentLanguage();
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 0c34d018a23..b66b514597b 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -91,6 +91,7 @@
 #include "lltexturecache.h"
 #include "lltexturefetch.h"
 #include "llimageworker.h"
+#include "llevents.h"
 
 // The files below handle dependencies from cleanup.
 #include "llkeyframemotion.h"
@@ -841,7 +842,14 @@ bool LLAppViewer::mainLoop()
 	LLTimer debugTime;
 	LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
 	joystick->setNeedsReset(true);
- 	
+
+    LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop"));
+    // As we do not (yet) send data on the mainloop LLEventPump that varies
+    // with each frame, no need to instantiate a new LLSD event object each
+    // time. Obviously, if that changes, just instantiate the LLSD at the
+    // point of posting.
+    LLSD newFrame;
+
 	// Handle messages
 	while (!LLApp::isExiting())
 	{
@@ -882,6 +890,9 @@ bool LLAppViewer::mainLoop()
 				LLFloaterMemLeak::getInstance()->idle() ;				
 			}			
 
+            // canonical per-frame event
+            mainloop.post(newFrame);
+
 			if (!LLApp::isExiting())
 			{
 				pingMainloopTimeout("Main:JoystickKeyboard");
diff --git a/indra/newview/llcapabilitylistener.cpp b/indra/newview/llcapabilitylistener.cpp
new file mode 100644
index 00000000000..3277da89303
--- /dev/null
+++ b/indra/newview/llcapabilitylistener.cpp
@@ -0,0 +1,183 @@
+/**
+ * @file   llcapabilitylistener.cpp
+ * @author Nat Goodspeed
+ * @date   2009-01-07
+ * @brief  Implementation for llcapabilitylistener.
+ * 
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "llviewerprecompiledheaders.h"
+// associated header
+#include "llcapabilitylistener.h"
+// STL headers
+#include <map>
+// std headers
+// external library headers
+#include <boost/bind.hpp>
+// other Linden headers
+#include "stringize.h"
+#include "llcapabilityprovider.h"
+
+class LLCapabilityListener::CapabilityMappers: public LLSingleton<LLCapabilityListener::CapabilityMappers>
+{
+public:
+    void registerMapper(const LLCapabilityListener::CapabilityMapper*);
+    void unregisterMapper(const LLCapabilityListener::CapabilityMapper*);
+    const LLCapabilityListener::CapabilityMapper* find(const std::string& cap) const;
+
+    struct DupCapMapper: public std::runtime_error
+    {
+        DupCapMapper(const std::string& what):
+            std::runtime_error(std::string("DupCapMapper: ") + what)
+        {}
+    };
+
+private:
+    friend class LLSingleton<LLCapabilityListener::CapabilityMappers>;
+    CapabilityMappers();
+
+    typedef std::map<std::string, const LLCapabilityListener::CapabilityMapper*> CapabilityMap;
+    CapabilityMap mMap;
+};
+
+LLCapabilityListener::LLCapabilityListener(const std::string& name,
+                                           LLMessageSystem* messageSystem,
+                                           const LLCapabilityProvider& provider,
+                                           const LLUUID& agentID,
+                                           const LLUUID& sessionID):
+    mEventPump(name),
+    mMessageSystem(messageSystem),
+    mProvider(provider),
+    mAgentID(agentID),
+    mSessionID(sessionID)
+{
+    mEventPump.listen("self", boost::bind(&LLCapabilityListener::capListener, this, _1));
+}
+
+bool LLCapabilityListener::capListener(const LLSD& request)
+{
+    // Extract what we want from the request object. We do it all up front
+    // partly to document what we expect.
+    LLSD::String cap(request["message"]);
+    LLSD payload(request["payload"]);
+    LLSD::String reply(request["reply"]);
+    LLSD::String error(request["error"]);
+    LLSD::Real timeout(request["timeout"]);
+    // If the LLSD doesn't even have a "message" key, we doubt it was intended
+    // for this listener.
+    if (cap.empty())
+    {
+        LL_ERRS("capListener") << "capability request event without 'message' key to '"
+                               << getCapAPI().getName()
+                               << "' on region\n" << mProvider.getDescription()
+                               << LL_ENDL;
+        return false;               // in case fatal-error function isn't
+    }
+    // Establish default timeout. This test relies on LLSD::asReal() returning
+    // exactly 0.0 for an undef value.
+    if (! timeout)
+    {
+        timeout = HTTP_REQUEST_EXPIRY_SECS;
+    }
+    // Look up the url for the requested capability name.
+    std::string url = mProvider.getCapability(cap);
+    if (! url.empty())
+    {
+        // This capability is supported by the region to which we're talking.
+        LLHTTPClient::post(url, payload,
+                           new LLSDMessage::EventResponder(LLEventPumps::instance(),
+                                                           mProvider.getDescription(),
+                                                           cap, reply, error),
+                           LLSD(),  // headers
+                           timeout);
+    }
+    else
+    {
+        // Capability not supported -- do we have a registered mapper?
+        const CapabilityMapper* mapper = CapabilityMappers::instance().find(cap);
+        if (! mapper)               // capability neither supported nor mapped
+        {
+            LL_ERRS("capListener") << "unsupported capability '" << cap << "' request to '"
+                                   << getCapAPI().getName() << "' on region\n"
+                                   << mProvider.getDescription()
+                                   << LL_ENDL;
+        }
+        else if (! mapper->getReplyName().empty()) // mapper expects reply support
+        {
+            LL_ERRS("capListener") << "Mapper for capability '" << cap
+                                   << "' requires unimplemented support for reply message '"
+                                   << mapper->getReplyName()
+                                   << "' on '" << getCapAPI().getName() << "' on region\n"
+                                   << mProvider.getDescription()
+                                   << LL_ENDL;
+        }
+        else
+        {
+            LL_INFOS("capListener") << "fallback invoked for capability '" << cap
+                                    << "' request to '" << getCapAPI().getName()
+                                    << "' on region\n" << mProvider.getDescription()
+                                    << LL_ENDL;
+            mapper->buildMessage(mMessageSystem, mAgentID, mSessionID, cap, payload);
+            mMessageSystem->sendReliable(mProvider.getHost());
+        }
+    }
+    return false;
+}
+
+LLCapabilityListener::CapabilityMapper::CapabilityMapper(const std::string& cap, const std::string& reply):
+    mCapName(cap),
+    mReplyName(reply)
+{
+    LLCapabilityListener::CapabilityMappers::instance().registerMapper(this);
+}
+
+LLCapabilityListener::CapabilityMapper::~CapabilityMapper()
+{
+    LLCapabilityListener::CapabilityMappers::instance().unregisterMapper(this);
+}
+
+LLSD LLCapabilityListener::CapabilityMapper::readResponse(LLMessageSystem* messageSystem) const
+{
+    return LLSD();
+}
+
+LLCapabilityListener::CapabilityMappers::CapabilityMappers() {}
+
+void LLCapabilityListener::CapabilityMappers::registerMapper(const LLCapabilityListener::CapabilityMapper* mapper)
+{
+    // Try to insert a new map entry by which we can look up the passed mapper
+    // instance.
+    std::pair<CapabilityMap::iterator, bool> inserted =
+        mMap.insert(CapabilityMap::value_type(mapper->getCapName(), mapper));
+    // If we already have a mapper for that name, insert() merely located the
+    // existing iterator and returned false. It is a coding error to try to
+    // register more than one mapper for the same capability name.
+    if (! inserted.second)
+    {
+        throw DupCapMapper(std::string("Duplicate capability name ") + mapper->getCapName());
+    }
+}
+
+void LLCapabilityListener::CapabilityMappers::unregisterMapper(const LLCapabilityListener::CapabilityMapper* mapper)
+{
+    CapabilityMap::iterator found = mMap.find(mapper->getCapName());
+    if (found != mMap.end())
+    {
+        mMap.erase(found);
+    }
+}
+
+const LLCapabilityListener::CapabilityMapper*
+LLCapabilityListener::CapabilityMappers::find(const std::string& cap) const
+{
+    CapabilityMap::const_iterator found = mMap.find(cap);
+    if (found != mMap.end())
+    {
+        return found->second;
+    }
+    return NULL;
+}
diff --git a/indra/newview/llcapabilitylistener.h b/indra/newview/llcapabilitylistener.h
new file mode 100644
index 00000000000..061227e04d6
--- /dev/null
+++ b/indra/newview/llcapabilitylistener.h
@@ -0,0 +1,113 @@
+/**
+ * @file   llcapabilitylistener.h
+ * @author Nat Goodspeed
+ * @date   2009-01-07
+ * @brief  Provide an event-based API for capability requests
+ * 
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLCAPABILITYLISTENER_H)
+#define LL_LLCAPABILITYLISTENER_H
+
+#include "llevents.h"               // LLEventPump
+#include "llsdmessage.h"            // LLSDMessage::ArgError
+#include "llerror.h"                // LOG_CLASS()
+
+class LLCapabilityProvider;
+class LLSD;
+
+class LLCapabilityListener
+{
+    LOG_CLASS(LLCapabilityListener);
+public:
+    LLCapabilityListener(const std::string& name, LLMessageSystem* messageSystem,
+                         const LLCapabilityProvider& provider,
+                         const LLUUID& agentID, const LLUUID& sessionID);
+
+    /// Capability-request exception
+    typedef LLSDMessage::ArgError ArgError;
+    /// Get LLEventPump on which we listen for capability requests
+    /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities)
+    LLEventPump& getCapAPI() { return mEventPump; }
+
+    /**
+     * Base class for mapping an as-yet-undeployed capability name to a (pair
+     * of) LLMessageSystem message(s). To map a capability name to such
+     * messages, derive a subclass of CapabilityMapper and declare a static
+     * instance in a translation unit known to be loaded. The mapping is not
+     * region-specific. If an LLViewerRegion's capListener() receives a
+     * request for a supported capability, it will use the capability's URL.
+     * If not, it will look for an applicable CapabilityMapper subclass
+     * instance.
+     */
+    class CapabilityMapper
+    {
+    public:
+        /**
+         * Base-class constructor. Typically your subclass constructor will
+         * pass these parameters as literals.
+         * @param cap the capability name handled by this (subclass) instance
+         * @param reply the name of the response LLMessageSystem message. Omit
+         * if the LLMessageSystem message you intend to send doesn't prompt a
+         * reply message, or if you already handle that message in some other
+         * way.
+         */
+        CapabilityMapper(const std::string& cap, const std::string& reply = "");
+        virtual ~CapabilityMapper();
+        /// query the capability name
+        std::string getCapName() const { return mCapName; }
+        /// query the reply message name
+        std::string getReplyName() const { return mReplyName; }
+        /**
+         * Override this method to build the LLMessageSystem message we should
+         * send instead of the requested capability message. DO NOT send that
+         * message: that will be handled by the caller.
+         */
+        virtual void buildMessage(LLMessageSystem* messageSystem,
+                                  const LLUUID& agentID,
+                                  const LLUUID& sessionID,
+                                  const std::string& capabilityName,
+                                  const LLSD& payload) const = 0;
+        /**
+         * Override this method if you pass a non-empty @a reply
+         * LLMessageSystem message name to the constructor: that is, if you
+         * expect to receive an LLMessageSystem message in response to the
+         * message you constructed in buildMessage(). If you don't pass a @a
+         * reply message name, you need not override this method as it won't
+         * be called.
+         *
+         * Using LLMessageSystem message-reading operations, your
+         * readResponse() override should construct and return an LLSD object
+         * of the form you expect to receive from the real implementation of
+         * the capability you intend to invoke, when it finally goes live.
+         */
+        virtual LLSD readResponse(LLMessageSystem* messageSystem) const;
+
+    private:
+        const std::string mCapName;
+        const std::string mReplyName;
+    };
+
+private:
+    /// Bind the LLCapabilityProvider passed to our ctor
+    const LLCapabilityProvider& mProvider;
+
+    /// Post an event to this LLEventPump to invoke a capability message on
+    /// the bound LLCapabilityProvider's server
+    /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities)
+    LLEventStream mEventPump;
+
+    LLMessageSystem* mMessageSystem;
+    LLUUID mAgentID, mSessionID;
+
+    /// listener to process capability requests
+    bool capListener(const LLSD&);
+
+    /// helper class for capListener()
+    class CapabilityMappers;
+};
+
+#endif /* ! defined(LL_LLCAPABILITYLISTENER_H) */
diff --git a/indra/newview/llcapabilityprovider.h b/indra/newview/llcapabilityprovider.h
new file mode 100644
index 00000000000..0ddb2b6cb9d
--- /dev/null
+++ b/indra/newview/llcapabilityprovider.h
@@ -0,0 +1,39 @@
+/**
+ * @file   llcapabilityprovider.h
+ * @author Nat Goodspeed
+ * @date   2009-01-07
+ * @brief  Interface by which to reference (e.g.) LLViewerRegion to obtain a
+ *         capability.
+ * 
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLCAPABILITYPROVIDER_H)
+#define LL_LLCAPABILITYPROVIDER_H
+
+#include "llhost.h"
+#include <string>
+
+/// Interface for obtaining a capability URL, given a capability name
+class LLCapabilityProvider
+{
+public:
+    virtual ~LLCapabilityProvider() {}
+    /**
+     * Get a capability URL, given a capability name. Returns empty string if
+     * no such capability is defined on this provider.
+     */
+    virtual std::string getCapability(const std::string& name) const = 0;
+    /**
+     * Get host to which to send that capability request.
+     */
+    virtual LLHost getHost() const = 0;
+    /**
+     * Describe this LLCapabilityProvider for logging etc.
+     */
+    virtual std::string getDescription() const = 0;
+};
+
+#endif /* ! defined(LL_LLCAPABILITYPROVIDER_H) */
diff --git a/indra/newview/llfloatergroups.cpp b/indra/newview/llfloatergroups.cpp
index 8cd72974474..9e0b0d72308 100644
--- a/indra/newview/llfloatergroups.cpp
+++ b/indra/newview/llfloatergroups.cpp
@@ -57,6 +57,8 @@
 #include "llviewerwindow.h"
 #include "llimview.h"
 
+using namespace LLOldEvents;
+
 // static
 std::map<const LLUUID, LLFloaterGroupPicker*> LLFloaterGroupPicker::sInstances;
 
diff --git a/indra/newview/llfloatergroups.h b/indra/newview/llfloatergroups.h
index da1c4e23dd9..3eb3e5af523 100644
--- a/indra/newview/llfloatergroups.h
+++ b/indra/newview/llfloatergroups.h
@@ -84,14 +84,14 @@ class LLFloaterGroupPicker : public LLFloater, public LLUIFactory<LLFloaterGroup
 	static instance_map_t sInstances;
 };
 
-class LLPanelGroups : public LLPanel, public LLSimpleListener
+class LLPanelGroups : public LLPanel, public LLOldEvents::LLSimpleListener
 {
 public:
 	LLPanelGroups();
 	virtual ~LLPanelGroups();
 
 	//LLEventListener
-	/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
+	/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
 	
 	// clear the group list, and get a fresh set of info.
 	void reset();
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index e4459e38bd2..5d09d8748fd 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -87,6 +87,8 @@
 #include "llselectmgr.h"
 #include "llfloateropenobject.h"
 
+using namespace LLOldEvents;
+
 // Helpers
 // bug in busy count inc/dec right now, logic is complex... do we really need it?
 void inc_busy_count()
diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp
index 2eee54df40f..23d32fee81e 100644
--- a/indra/newview/llnetmap.cpp
+++ b/indra/newview/llnetmap.cpp
@@ -68,6 +68,8 @@
 
 #include "llglheaders.h"
 
+using namespace LLOldEvents;
+
 const F32 MAP_SCALE_MIN = 64;
 const F32 MAP_SCALE_MID = 172;
 const F32 MAP_SCALE_MAX = 512;
diff --git a/indra/newview/llnetmap.h b/indra/newview/llnetmap.h
index a5bf9f0fb5e..9fbd5097fdf 100644
--- a/indra/newview/llnetmap.h
+++ b/indra/newview/llnetmap.h
@@ -109,31 +109,31 @@ class LLNetMap : public LLPanel
 	class LLScaleMap : public LLMemberListener<LLNetMap>
 	{
 	public:
-		/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
+		/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
 	};
 
 	class LLStopTracking : public LLMemberListener<LLNetMap>
 	{
 	public:
-		/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
+		/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
 	};
 
 	class LLEnableTracking : public LLMemberListener<LLNetMap>
 	{
 	public:
-		/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
+		/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
 	};
 
 	class LLShowAgentProfile : public LLMemberListener<LLNetMap>
 	{
 	public:
-		/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
+		/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
 	};
 
 	class LLEnableProfile : public LLMemberListener<LLNetMap>
 	{
 	public:
-		/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
+		/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
 	};
 };
 
diff --git a/indra/newview/llviewercontrol.h b/indra/newview/llviewercontrol.h
index d0dc80cb9a1..c4003111d3a 100644
--- a/indra/newview/llviewercontrol.h
+++ b/indra/newview/llviewercontrol.h
@@ -84,7 +84,7 @@ class LLCachedControl
 {
     T mCachedValue;
     LLPointer<LLControlVariable> mControl;
-    boost::signals::connection mConnection;
+    boost::signals2::scoped_connection mConnection;
 
 public:
 	LLCachedControl(const std::string& name, 
@@ -109,17 +109,13 @@ class LLCachedControl
 		}
 
 		// Add a listener to the controls signal...
-		mControl->getSignal()->connect(
+		mConnection = mControl->getSignal()->connect(
 			boost::bind(&LLCachedControl<T>::handleValueChange, this, _1)
 			);
 	}
 
 	~LLCachedControl()
 	{
-		if(mConnection.connected())
-		{
-			mConnection.disconnect();
-		}
 	}
 
 	LLCachedControl& operator =(const T& newvalue)
diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp
index 7f1f0fe6d05..f47d0777b0e 100644
--- a/indra/newview/llviewerinventory.cpp
+++ b/indra/newview/llviewerinventory.cpp
@@ -764,56 +764,39 @@ void move_inventory_item(
 	gAgent.sendReliableMessage();
 }
 
-class LLCopyInventoryFromNotecardResponder : public LLHTTPClient::Responder
-{
-public:
-	//If we get back a normal response, handle it here
-	virtual void result(const LLSD& content)
-	{
-		// What do we do here?
-		llinfos << "CopyInventoryFromNotecard request successful." << llendl;
-	}
-
-	//If we get back an error (not found, etc...), handle it here
-	virtual void error(U32 status, const std::string& reason)
-	{
-		llinfos << "LLCopyInventoryFromNotecardResponder::error "
-			<< status << ": " << reason << llendl;
-	}
-};
-
 void copy_inventory_from_notecard(const LLUUID& object_id, const LLUUID& notecard_inv_id, const LLInventoryItem *src, U32 callback_id)
 {
-	LLSD body;
 	LLViewerRegion* viewer_region = NULL;
-	if(object_id.notNull())
-	{
-		LLViewerObject* vo = gObjectList.findObject(object_id);
-		if(vo)
-		{
-			viewer_region = vo->getRegion();
-		}
+    LLViewerObject* vo = NULL;
+	if (object_id.notNull() && (vo = gObjectList.findObject(object_id)) != NULL)
+    {
+        viewer_region = vo->getRegion();
 	}
 
 	// Fallback to the agents region if for some reason the 
 	// object isn't found in the viewer.
-	if(!viewer_region)
+	if (! viewer_region)
 	{
 		viewer_region = gAgent.getRegion();
 	}
 
-	if(viewer_region)
+	if (! viewer_region)
 	{
-		std::string url = viewer_region->getCapability("CopyInventoryFromNotecard");
-		if (!url.empty())
-		{
-			body["notecard-id"] = notecard_inv_id;
-			body["object-id"] = object_id;
-			body["item-id"] = src->getUUID();
-			body["folder-id"] = gInventory.findCategoryUUIDForType(src->getType());
-			body["callback-id"] = (LLSD::Integer)callback_id;
-
-			LLHTTPClient::post(url, body, new LLCopyInventoryFromNotecardResponder());
-		}
-	}
+        LL_WARNS("copy_inventory_from_notecard") << "Can't find region from object_id "
+                                                 << object_id << " or gAgent"
+                                                 << LL_ENDL;
+        return;
+    }
+
+    LLSD request, body;
+    body["notecard-id"] = notecard_inv_id;
+    body["object-id"] = object_id;
+    body["item-id"] = src->getUUID();
+    body["folder-id"] = gInventory.findCategoryUUIDForType(src->getType());
+    body["callback-id"] = (LLSD::Integer)callback_id;
+
+    request["message"] = "CopyInventoryFromNotecard";
+    request["payload"] = body;
+
+    viewer_region->getCapAPI().post(request);
 }
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index 70fc78d2774..82f83597323 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -46,7 +46,7 @@
 #include "lluuid.h"
 
 #include <boost/bind.hpp>	// for SkinFolder listener
-#include <boost/signal.hpp>
+#include <boost/signals2.hpp>
 
 
 // Implementation functions not exported into header file
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index fe91da05fac..ac36cf7bb68 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -1,4 +1,3 @@
-
 /** 
  * @file llviewermenu.cpp
  * @brief Builds menus out of items.
@@ -217,6 +216,8 @@
 #include "lltexlayer.h"
 
 using namespace LLVOAvatarDefines;
+using namespace LLOldEvents;
+
 void init_client_menu(LLMenuGL* menu);
 void init_server_menu(LLMenuGL* menu);
 
diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp
index 5240fd3211a..d1f50212db7 100644
--- a/indra/newview/llviewermenufile.cpp
+++ b/indra/newview/llviewermenufile.cpp
@@ -71,6 +71,8 @@
 // system libraries
 #include <boost/tokenizer.hpp>
 
+using namespace LLOldEvents;
+
 typedef LLMemberListener<LLView> view_listener_t;
 
 
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index 12d9c1a992a..d4ee7a7e504 100644
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -65,6 +65,11 @@
 #include "llvoclouds.h"
 #include "llworld.h"
 #include "llspatialpartition.h"
+#include "stringize.h"
+
+#ifdef LL_WINDOWS
+	#pragma warning(disable:4355)
+#endif
 
 // Viewer object cache version, change if object update
 // format changes. JC
@@ -172,7 +177,18 @@ LLViewerRegion::LLViewerRegion(const U64 &handle,
 	mCacheEntriesCount(0),
 	mCacheID(),
 	mEventPoll(NULL),
-	mReleaseNotesRequested(FALSE)
+	mReleaseNotesRequested(FALSE),
+    // I'd prefer to set the LLCapabilityListener name to match the region
+    // name -- it's disappointing that's not available at construction time.
+    // We could instead store an LLCapabilityListener*, making
+    // setRegionNameAndZone() replace the instance. Would that pose
+    // consistency problems? Can we even request a capability before calling
+    // setRegionNameAndZone()?
+    // For testability -- the new Michael Feathers paradigm --
+    // LLCapabilityListener binds all the globals it expects to need at
+    // construction time.
+    mCapabilityListener(host.getString(), gMessageSystem, *this,
+                        gAgent.getID(), gAgent.getSessionID())
 {
 	mWidth = region_width_meters;
 	mOriginGlobal = from_region_handle(handle); 
@@ -223,7 +239,6 @@ LLViewerRegion::LLViewerRegion(const U64 &handle,
 	mObjectPartition.push_back(new LLBridgePartition());	//PARTITION_BRIDGE
 	mObjectPartition.push_back(new LLHUDParticlePartition());//PARTITION_HUD_PARTICLE
 	mObjectPartition.push_back(NULL);						//PARTITION_NONE
-	
 }
 
 
@@ -774,6 +789,15 @@ std::ostream& operator<<(std::ostream &s, const LLViewerRegion &region)
 	s << "{ ";
 	s << region.mHost;
 	s << " mOriginGlobal = " << region.getOriginGlobal()<< "\n";
+    std::string name(region.getName()), zone(region.getZoning());
+    if (! name.empty())
+    {
+        s << " mName         = " << name << '\n';
+    }
+    if (! zone.empty())
+    {
+        s << " mZoning       = " << zone << '\n';
+    }
 	s << "}";
 	return s;
 }
@@ -1518,3 +1542,8 @@ void LLViewerRegion::showReleaseNotes()
 	LLWeb::loadURL(url);
 	mReleaseNotesRequested = FALSE;
 }
+
+std::string LLViewerRegion::getDescription() const
+{
+    return stringize(*this);
+}
diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h
index b0a98a99b8c..4d345c811aa 100644
--- a/indra/newview/llviewerregion.h
+++ b/indra/newview/llviewerregion.h
@@ -49,6 +49,8 @@
 #include "lldatapacker.h"
 #include "llvocache.h"
 #include "llweb.h"
+#include "llcapabilityprovider.h"
+#include "llcapabilitylistener.h"
 
 // Surface id's
 #define LAND  1
@@ -66,8 +68,9 @@ class LLSurface;
 class LLVOCache;
 class LLVOCacheEntry;
 class LLSpatialPartition;
+class LLEventPump;
 
-class LLViewerRegion 
+class LLViewerRegion: public LLCapabilityProvider // implements this interface
 {
 public:
 	//MUST MATCH THE ORDER OF DECLARATION IN CONSTRUCTOR
@@ -226,11 +229,19 @@ class LLViewerRegion
 	// Get/set named capability URLs for this region.
 	void setSeedCapability(const std::string& url);
 	void setCapability(const std::string& name, const std::string& url);
-	std::string getCapability(const std::string& name) const;
+	// implements LLCapabilityProvider
+    virtual std::string getCapability(const std::string& name) const;
 	static bool isSpecialCapabilityName(const std::string &name);
 	void logActiveCapabilities() const;
 
-	const LLHost	&getHost() const			{ return mHost; }
+    /// Capability-request exception
+    typedef LLCapabilityListener::ArgError ArgError;
+    /// Get LLEventPump on which we listen for capability requests
+    /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities)
+    LLEventPump& getCapAPI() { return mCapabilityListener.getCapAPI(); }
+
+    /// implements LLCapabilityProvider
+	virtual LLHost	getHost() const				{ return mHost; }
 	const U64 		&getHandle() const 			{ return mHandle; }
 
 	LLSurface		&getLand() const			{ return *mLandp; }
@@ -274,6 +285,8 @@ class LLViewerRegion
 	void calculateCameraDistance();
 
 	friend std::ostream& operator<<(std::ostream &s, const LLViewerRegion &region);
+    /// implements LLCapabilityProvider
+    virtual std::string getDescription() const;
 
 	// used by LCD to get details for debug screen
 	U32 getNetDetailsForLCD();
@@ -394,6 +407,11 @@ class LLViewerRegion
 	
 	LLEventPoll* mEventPoll;
 
+    /// Post an event to this LLCapabilityListener to invoke a capability message on
+    /// this LLViewerRegion's server
+    /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities)
+    LLCapabilityListener mCapabilityListener;
+
 private:
 	bool	mAlive;					// can become false if circuit disconnects
 
@@ -461,5 +479,3 @@ inline BOOL LLViewerRegion::getReleaseNotesRequested() const
 }
 
 #endif
-
-
diff --git a/indra/newview/llviewerthrottle.cpp b/indra/newview/llviewerthrottle.cpp
index bf779c427a4..73065c5c00d 100644
--- a/indra/newview/llviewerthrottle.cpp
+++ b/indra/newview/llviewerthrottle.cpp
@@ -40,6 +40,8 @@
 #include "llviewerstats.h"
 #include "lldatapacker.h"
 
+using namespace LLOldEvents;
+
 // consts
 
 // The viewer is allowed to set the under-the-hood bandwidth to 50%
diff --git a/indra/newview/tests/llcapabilitylistener_test.cpp b/indra/newview/tests/llcapabilitylistener_test.cpp
new file mode 100644
index 00000000000..3c5f6fad2d6
--- /dev/null
+++ b/indra/newview/tests/llcapabilitylistener_test.cpp
@@ -0,0 +1,274 @@
+/**
+ * @file   llcapabilitylistener_test.cpp
+ * @author Nat Goodspeed
+ * @date   2008-12-31
+ * @brief  Test for llcapabilitylistener.cpp.
+ * 
+ * $LicenseInfo:firstyear=2008&license=internal$
+ * Copyright (c) 2008, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "../llviewerprecompiledheaders.h"
+// Own header
+#include "../llcapabilitylistener.h"
+// STL headers
+#include <stdexcept>
+#include <map>
+#include <vector>
+// std headers
+// external library headers
+#include "boost/bind.hpp"
+// other Linden headers
+#include "../test/lltut.h"
+#include "../llcapabilityprovider.h"
+#include "lluuid.h"
+#include "llerrorcontrol.h"
+#include "tests/networkio.h"
+#include "tests/commtest.h"
+#include "stringize.h"
+
+#if defined(LL_WINDOWS)
+#pragma warning(disable: 4355)      // using 'this' in base-class ctor initializer expr
+#endif
+
+/*****************************************************************************
+*   TestCapabilityProvider
+*****************************************************************************/
+struct TestCapabilityProvider: public LLCapabilityProvider
+{
+    TestCapabilityProvider(const LLHost& host):
+        mHost(host)
+    {}
+
+    std::string getCapability(const std::string& cap) const
+    {
+        CapMap::const_iterator found = mCaps.find(cap);
+        if (found != mCaps.end())
+            return found->second;
+        // normal LLViewerRegion lookup failure mode
+        return "";
+    }
+    void setCapability(const std::string& cap, const std::string& url)
+    {
+        mCaps[cap] = url;
+    }
+    LLHost getHost() const { return mHost; }
+    std::string getDescription() const { return "TestCapabilityProvider"; }
+
+    LLHost mHost;
+    typedef std::map<std::string, std::string> CapMap;
+    CapMap mCaps;
+};
+
+/*****************************************************************************
+*   Dummy LLMessageSystem methods
+*****************************************************************************/
+/*==========================================================================*|
+// This doesn't work because we're already linking in llmessage.a, and we get
+// duplicate-symbol errors from the linker. Perhaps if I wanted to go through
+// the exercise of providing dummy versions of every single symbol defined in
+// message.o -- maybe some day.
+typedef std::vector< std::pair<std::string, std::string> > StringPairVector;
+StringPairVector call_history;
+
+S32 LLMessageSystem::sendReliable(const LLHost& host)
+{
+    call_history.push_back(StringPairVector::value_type("sendReliable", stringize(host)));
+    return 0;
+}
+|*==========================================================================*/
+
+/*****************************************************************************
+*   TUT
+*****************************************************************************/
+namespace tut
+{
+    struct llcapears_data: public commtest_data
+    {
+        TestCapabilityProvider provider;
+        LLCapabilityListener regionListener;
+        LLEventPump& regionPump;
+
+        llcapears_data():
+            provider(host),
+            regionListener("testCapabilityListener", NULL, provider, LLUUID(), LLUUID()),
+            regionPump(regionListener.getCapAPI())
+        {
+            provider.setCapability("good", server + "capability-test");
+            provider.setCapability("fail", server + "fail");
+        }
+    };
+    typedef test_group<llcapears_data> llcapears_group;
+    typedef llcapears_group::object llcapears_object;
+    llcapears_group llsdmgr("llcapabilitylistener");
+
+    struct CaptureError: public LLError::OverrideFatalFunction
+    {
+        CaptureError():
+            LLError::OverrideFatalFunction(boost::bind(&CaptureError::operator(), this, _1))
+        {
+            LLError::setPrintLocation(false);
+        }
+
+        struct FatalException: public std::runtime_error
+        {
+            FatalException(const std::string& what): std::runtime_error(what) {}
+        };
+
+        void operator()(const std::string& message)
+        {
+            error = message;
+            throw FatalException(message);
+        }
+
+        std::string error;
+    };
+
+    template<> template<>
+    void llcapears_object::test<1>()
+    {
+        LLSD request, body;
+        body["data"] = "yes";
+        request["payload"] = body;
+        request["reply"] = replyPump.getName();
+        request["error"] = errorPump.getName();
+        std::string threw;
+        try
+        {
+            CaptureError capture;
+            regionPump.post(request);
+        }
+        catch (const CaptureError::FatalException& e)
+        {
+            threw = e.what();
+        }
+        ensure_contains("missing capability name", threw, "without 'message' key");
+    }
+
+    template<> template<>
+    void llcapears_object::test<2>()
+    {
+        LLSD request, body;
+        body["data"] = "yes";
+        request["message"] = "good";
+        request["payload"] = body;
+        request["reply"] = replyPump.getName();
+        request["error"] = errorPump.getName();
+        regionPump.post(request);
+        ensure("got response", netio.pump());
+        ensure("success response", success);
+        ensure_equals(result.asString(), "success");
+
+        body["status"] = 499;
+        body["reason"] = "custom error message";
+        request["message"] = "fail";
+        request["payload"] = body;
+        regionPump.post(request);
+        ensure("got response", netio.pump());
+        ensure("failure response", ! success);
+        ensure_equals(result["status"].asInteger(), body["status"].asInteger());
+        ensure_equals(result["reason"].asString(),  body["reason"].asString());
+    }
+
+    template<> template<>
+    void llcapears_object::test<3>()
+    {
+        LLSD request, body;
+        body["data"] = "yes";
+        request["message"] = "unknown";
+        request["payload"] = body;
+        request["reply"] = replyPump.getName();
+        request["error"] = errorPump.getName();
+        std::string threw;
+        try
+        {
+            CaptureError capture;
+            regionPump.post(request);
+        }
+        catch (const CaptureError::FatalException& e)
+        {
+            threw = e.what();
+        }
+        ensure_contains("bad capability name", threw, "unsupported capability");
+    }
+
+    struct TestMapper: public LLCapabilityListener::CapabilityMapper
+    {
+        // Instantiator gets to specify whether mapper expects a reply.
+        // I'd really like to be able to test CapabilityMapper::buildMessage()
+        // functionality, too, but -- even though LLCapabilityListener accepts
+        // the LLMessageSystem* that it passes to CapabilityMapper --
+        // LLMessageSystem::sendReliable(const LLHost&) isn't virtual, so it's
+        // not helpful to pass a subclass instance. I suspect that making any
+        // LLMessageSystem methods virtual would provoke howls of outrage,
+        // given how heavily it's used. Nor can I just provide a local
+        // definition of LLMessageSystem::sendReliable(const LLHost&) because
+        // we're already linking in the rest of message.o via llmessage.a, and
+        // that produces duplicate-symbol link errors.
+        TestMapper(const std::string& replyMessage = std::string()):
+            LLCapabilityListener::CapabilityMapper("test", replyMessage)
+        {}
+        virtual void buildMessage(LLMessageSystem* msg,
+                                  const LLUUID& agentID,
+                                  const LLUUID& sessionID,
+                                  const std::string& capabilityName,
+                                  const LLSD& payload) const
+        {
+            msg->newMessageFast(_PREHASH_SetStartLocationRequest);
+            msg->nextBlockFast( _PREHASH_AgentData);
+            msg->addUUIDFast(_PREHASH_AgentID, agentID);
+            msg->addUUIDFast(_PREHASH_SessionID, sessionID);
+            msg->nextBlockFast( _PREHASH_StartLocationData);
+            // corrected by sim
+            msg->addStringFast(_PREHASH_SimName, "");
+            msg->addU32Fast(_PREHASH_LocationID, payload["HomeLocation"]["LocationId"].asInteger());
+/*==========================================================================*|
+            msg->addVector3Fast(_PREHASH_LocationPos,
+                                ll_vector3_from_sdmap(payload["HomeLocation"]["LocationPos"]));
+            msg->addVector3Fast(_PREHASH_LocationLookAt,
+                                ll_vector3_from_sdmap(payload["HomeLocation"]["LocationLookAt"]));
+|*==========================================================================*/
+        }
+    };
+
+    template<> template<>
+    void llcapears_object::test<4>()
+    {
+        TestMapper testMapper("WantReply");
+        LLSD request, body;
+        body["data"] = "yes";
+        request["message"] = "test";
+        request["payload"] = body;
+        request["reply"] = replyPump.getName();
+        request["error"] = errorPump.getName();
+        std::string threw;
+        try
+        {
+            CaptureError capture;
+            regionPump.post(request);
+        }
+        catch (const CaptureError::FatalException& e)
+        {
+            threw = e.what();
+        }
+        ensure_contains("capability mapper wants reply", threw, "unimplemented support for reply message");
+    }
+
+    template<> template<>
+    void llcapears_object::test<5>()
+    {
+        TestMapper testMapper;
+        std::string threw;
+        try
+        {
+            TestMapper testMapper2;
+        }
+        catch (const std::runtime_error& e)
+        {
+            threw = e.what();
+        }
+        ensure_contains("no dup cap mapper", threw, "DupCapMapper");
+    }
+}
diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt
index c8682c8ea74..49a0a8f3613 100644
--- a/indra/test/CMakeLists.txt
+++ b/indra/test/CMakeLists.txt
@@ -13,6 +13,7 @@ include(LLXML)
 include(LScript)
 include(Linking)
 include(Tut)
+include(Boost)
 
 include_directories(
     ${LLCOMMON_INCLUDE_DIRS}
@@ -34,7 +35,9 @@ set(test_SOURCE_FILES
     llblowfish_tut.cpp
     llbuffer_tut.cpp
     lldate_tut.cpp
+    lldependencies_tut.cpp
     llerror_tut.cpp
+    llevents_tut.cpp
     llhost_tut.cpp
     llhttpdate_tut.cpp
     llhttpclient_tut.cpp
@@ -71,6 +74,7 @@ set(test_SOURCE_FILES
     math.cpp
     message_tut.cpp
     reflection_tut.cpp
+    stringize_tut.cpp
     test.cpp
     v2math_tut.cpp
     v3color_tut.cpp
@@ -104,7 +108,7 @@ endif (NOT DARWIN)
 set_source_files_properties(${test_HEADER_FILES}
                             PROPERTIES HEADER_FILE_ONLY TRUE)
 
-list(APPEND test_SOURC_FILES ${test_HEADER_FILES})
+list(APPEND test_SOURCE_FILES ${test_HEADER_FILES})
 
 add_executable(test ${test_SOURCE_FILES})
 
diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp
new file mode 100644
index 00000000000..e401f89b22b
--- /dev/null
+++ b/indra/test/llevents_tut.cpp
@@ -0,0 +1,793 @@
+/**
+ * @file   llevents_tut.cpp
+ * @author Nat Goodspeed
+ * @date   2008-09-12
+ * @brief  Test of llevents.h
+ * 
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ * Copyright (c) 2008, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if LL_WINDOWS
+#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want!
+#endif
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+// UGLY HACK! We want to verify state internal to the classes without
+// providing public accessors.
+#define testable public
+#include "llevents.h"
+#undef testable
+// STL headers
+// std headers
+#include <iostream>
+#include <typeinfo>
+// external library headers
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/assign/list_of.hpp>
+// other Linden headers
+#include "lltut.h"
+#include "stringize.h"
+
+using boost::assign::list_of;
+
+/*****************************************************************************
+*   test listener class
+*****************************************************************************/
+class Listener;
+std::ostream& operator<<(std::ostream&, const Listener&);
+
+class Listener
+{
+public:
+    Listener(const std::string& name):
+        mName(name)
+    {
+//      std::cout << *this << ": ctor\n";
+    }
+    Listener(const Listener& that):
+        mName(that.mName),
+        mLastEvent(that.mLastEvent)
+    {
+//      std::cout << *this << ": copy\n";
+    }
+    virtual ~Listener()
+    {
+//      std::cout << *this << ": dtor\n";
+    }
+    std::string getName() const { return mName; }
+    bool call(const LLSD& event)
+    {
+//      std::cout << *this << "::call(" << event << ")\n";
+        mLastEvent = event;
+        return false;
+    }
+    bool callstop(const LLSD& event)
+    {
+//      std::cout << *this << "::callstop(" << event << ")\n";
+        mLastEvent = event;
+        return true;
+    }
+    LLSD getLastEvent() const
+    {
+//      std::cout << *this << "::getLastEvent() -> " << mLastEvent << "\n";
+        return mLastEvent;
+    }
+    void reset(const LLSD& to = LLSD())
+    {
+//      std::cout << *this << "::reset(" << to << ")\n";
+        mLastEvent = to;
+    }
+
+private:
+    std::string mName;
+    LLSD mLastEvent;
+};
+
+std::ostream& operator<<(std::ostream& out, const Listener& listener)
+{
+    out << "Listener(" << listener.getName() /* << "@" << &listener */ << ')';
+    return out;
+}
+
+struct Collect
+{
+    bool add(const std::string& bound, const LLSD& event)
+    {
+        result.push_back(bound);
+        return false;
+    }
+    void clear() { result.clear(); }
+    typedef std::vector<std::string> StringList;
+    StringList result;
+};
+
+std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings)
+{
+    out << '(';
+    Collect::StringList::const_iterator begin(strings.begin()), end(strings.end());
+    if (begin != end)
+    {
+        out << '"' << *begin << '"';
+        while (++begin != end)
+        {
+            out << ", \"" << *begin << '"';
+        }
+    }
+    out << ')';
+    return out;
+}
+
+template<typename T>
+T make(const T& value) { return value; }
+
+/*****************************************************************************
+*   tut test group
+*****************************************************************************/
+namespace tut
+{
+    struct events_data
+    {
+        events_data():
+            pumps(LLEventPumps::instance()),
+            listener0("first"),
+            listener1("second")
+        {}
+        LLEventPumps& pumps;
+        Listener listener0;
+        Listener listener1;
+
+        void check_listener(const std::string& desc, const Listener& listener, LLSD::Integer got)
+        {
+            ensure_equals(STRINGIZE(listener << ' ' << desc),
+                          listener.getLastEvent().asInteger(), got);
+        }
+    };
+    typedef test_group<events_data> events_group;
+    typedef events_group::object events_object;
+    tut::events_group evgr("events");
+
+    template<> template<>
+    void events_object::test<1>()
+    {
+        set_test_name("basic operations");
+        // Now there's a static constructor in llevents.cpp that registers on
+        // the "mainloop" pump to call LLEventPumps::flush().
+        // Actually -- having to modify this to track the statically-
+        // constructed pumps in other TUT modules in this giant monolithic test
+        // executable isn't such a hot idea.
+//      ensure_equals("initial pump", pumps.mPumpMap.size(), 1);
+        size_t initial_pumps(pumps.mPumpMap.size());
+        LLEventPump& per_frame(pumps.obtain("per-frame"));
+        ensure_equals("first explicit pump", pumps.mPumpMap.size(), initial_pumps+1);
+        // Verify that per_frame was instantiated as an LLEventStream.
+        ensure("LLEventStream leaf class", dynamic_cast<LLEventStream*>(&per_frame));
+        ensure("enabled", per_frame.enabled());
+        // Trivial test, but posting an event to an EventPump with no
+        // listeners should not blow up. The test is relevant because defining
+        // a boost::signal with a non-void return signature, using the default
+        // combiner, blows up if there are no listeners. This is because the
+        // default combiner is defined to return the value returned by the
+        // last listener, which is meaningless if there were no listeners.
+        per_frame.post(0);
+        // 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.
+        LLBoundListener connection = per_frame.listen(listener0.getName(),
+                                                      boost::bind(&Listener::call,
+                                                                  boost::ref(listener0),
+                                                                  _1));
+        ensure("connected", connection.connected());
+        ensure("not blocked", ! connection.blocked());
+        per_frame.post(1);
+        check_listener("received", listener0, 1);
+        { // block the connection
+            LLEventPump::Blocker block(connection);
+            ensure("blocked", connection.blocked());
+            per_frame.post(2);
+            check_listener("not updated", listener0, 1);
+        } // unblock
+        ensure("unblocked", ! connection.blocked());
+        per_frame.post(3);
+        check_listener("unblocked", listener0, 3);
+        LLBoundListener sameConnection = per_frame.getListener(listener0.getName());
+        ensure("still connected", sameConnection.connected());
+        ensure("still not blocked", ! sameConnection.blocked());
+        { // block it again
+            LLEventPump::Blocker block(sameConnection);
+            ensure("re-blocked", sameConnection.blocked());
+            per_frame.post(4);
+            check_listener("re-blocked", listener0, 3);
+        } // unblock
+        bool threw = false;
+        try
+        {
+            per_frame.listen(listener0.getName(), // note bug, dup name
+                             boost::bind(&Listener::call, boost::ref(listener1), _1));
+        }
+        catch (const LLEventPump::DupListenerName& e)
+        {
+            threw = true;
+            ensure_equals(e.what(),
+                          std::string("DupListenerName: "
+                                      "Attempt to register duplicate listener name '") +
+                          listener0.getName() +
+                          "' on " + typeid(per_frame).name() + " '" + per_frame.getName() + "'");
+        }
+        ensure("threw DupListenerName", threw);
+        // do it right this time
+        per_frame.listen(listener1.getName(),
+                         boost::bind(&Listener::call, boost::ref(listener1), _1));
+        per_frame.post(5);
+        check_listener("got", listener0, 5);
+        check_listener("got", listener1, 5);
+        per_frame.enable(false);
+        per_frame.post(6);
+        check_listener("didn't get", listener0, 5);
+        check_listener("didn't get", listener1, 5);
+        per_frame.enable();
+        per_frame.post(7);
+        check_listener("got", listener0, 7);
+        check_listener("got", listener1, 7);
+        per_frame.stopListening(listener0.getName());
+        ensure("disconnected 0", ! connection.connected());
+        ensure("disconnected 1", ! sameConnection.connected());
+        per_frame.post(8);
+        check_listener("disconnected", listener0, 7);
+        check_listener("still connected", listener1, 8);
+        per_frame.stopListening(listener1.getName());
+        per_frame.post(9);
+        check_listener("disconnected", listener1, 8);
+    }
+
+    template<> template<>
+    void events_object::test<2>()
+    {
+        set_test_name("callstop() returning true");
+        LLEventPump& per_frame(pumps.obtain("per-frame"));
+        listener0.reset(0);
+        listener1.reset(0);
+        LLBoundListener bound0 = per_frame.listen(listener0.getName(),
+                                                  boost::bind(&Listener::callstop,
+                                                              boost::ref(listener0),
+                                                              _1));
+        LLBoundListener bound1 = per_frame.listen(listener1.getName(),
+                                                  boost::bind(&Listener::call,
+                                                              boost::ref(listener1),
+                                                              _1),
+                                                  // after listener0
+                                                  make<LLEventPump::NameList>(list_of(listener0.getName())));
+        ensure("enabled", per_frame.enabled());
+        ensure("connected 0", bound0.connected());
+        ensure("unblocked 0", ! bound0.blocked());
+        ensure("connected 1", bound1.connected());
+        ensure("unblocked 1", ! bound1.blocked());
+        per_frame.post(1);
+        check_listener("got", listener0, 1);
+        // Because listener0.callstop() returns true, control never reaches listener1.call().
+        check_listener("got", listener1, 0);
+    }
+
+    bool chainEvents(Listener& someListener, const LLSD& event)
+    {
+        // Make this call so we can watch for side effects for test purposes.
+        someListener.call(event);
+        // This function represents a recursive event chain -- or some other
+        // scenario in which an event handler raises additional events.
+        int value = event.asInteger();
+        if (value)
+        {
+            LLEventPumps::instance().obtain("login").post(value - 1);
+        }
+        return false;
+    }
+
+    template<> template<>
+    void events_object::test<3>()
+    {
+        set_test_name("LLEventQueue delayed action");
+        // This access is NOT legal usage: we can do it only because we're
+        // hacking private for test purposes. Normally we'd either compile in
+        // a particular name, or (later) edit a config file.
+        pumps.mQueueNames.insert("login");
+        LLEventPump& login(pumps.obtain("login"));
+        // The "mainloop" pump is special: posting on that implicitly calls
+        // LLEventPumps::flush(), which in turn should flush our "login"
+        // LLEventQueue.
+        LLEventPump& mainloop(pumps.obtain("mainloop"));
+        ensure("LLEventQueue leaf class", dynamic_cast<LLEventQueue*>(&login));
+        login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1));
+        listener0.reset(0);
+        login.post(1);
+        check_listener("waiting for queued event", listener0, 0);
+        mainloop.post(LLSD());
+        check_listener("got queued event", listener0, 1);
+        login.stopListening(listener0.getName());
+        // Verify that when an event handler posts a new event on the same
+        // LLEventQueue, it doesn't get processed in the same flush() call --
+        // it waits until the next flush() call.
+        listener0.reset(17);
+        login.listen("chainEvents", boost::bind(chainEvents, boost::ref(listener0), _1));
+        login.post(1);
+        check_listener("chainEvents(1) not yet called", listener0, 17);
+        mainloop.post(LLSD());
+        check_listener("chainEvents(1) called", listener0, 1);
+        mainloop.post(LLSD());
+        check_listener("chainEvents(0) called", listener0, 0);
+        mainloop.post(LLSD());
+        check_listener("chainEvents(-1) not called", listener0, 0);
+        login.stopListening("chainEvents");
+    }
+
+    template<> template<>
+    void events_object::test<4>()
+    {
+        set_test_name("explicitly-instantiated LLEventStream");
+        // Explicitly instantiate an LLEventStream, and verify that it
+        // self-registers with LLEventPumps
+        size_t registered = pumps.mPumpMap.size();
+        size_t owned = pumps.mOurPumps.size();
+        LLEventPump* localInstance;
+        {
+            LLEventStream myEventStream("stream");
+            localInstance = &myEventStream;
+            LLEventPump& stream(pumps.obtain("stream"));
+            ensure("found named LLEventStream instance", &stream == localInstance);
+            ensure_equals("registered new instance", pumps.mPumpMap.size(), registered + 1);
+            ensure_equals("explicit instance not owned", pumps.mOurPumps.size(), owned);
+        } // destroy myEventStream -- should unregister
+        ensure_equals("destroyed instance unregistered", pumps.mPumpMap.size(), registered);
+        ensure_equals("destroyed instance not owned", pumps.mOurPumps.size(), owned);
+        LLEventPump& stream(pumps.obtain("stream"));
+        ensure("new LLEventStream instance", &stream != localInstance);
+        ensure_equals("obtain()ed instance registered", pumps.mPumpMap.size(), registered + 1);
+        ensure_equals("obtain()ed instance owned", pumps.mOurPumps.size(), owned + 1);
+    }
+
+    template<> template<>
+    void events_object::test<5>()
+    {
+        set_test_name("stopListening()");
+        LLEventPump& login(pumps.obtain("login"));
+        login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1));
+        login.stopListening(listener0.getName());
+        // should not throw because stopListening() should have removed name
+        login.listen(listener0.getName(),
+                     boost::bind(&Listener::callstop, boost::ref(listener0), _1));
+        LLBoundListener wrong = login.getListener("bogus");
+        ensure("bogus connection disconnected", ! wrong.connected());
+        ensure("bogus connection blocked", wrong.blocked());
+    }
+
+    template<> template<>
+    void events_object::test<6>()
+    {
+        set_test_name("chaining LLEventPump instances");
+        LLEventPump& upstream(pumps.obtain("upstream"));
+        // One potentially-useful construct is to chain LLEventPumps together.
+        // Among other things, this allows you to turn subsets of listeners on
+        // and off in groups.
+        LLEventPump& filter0(pumps.obtain("filter0"));
+        LLEventPump& filter1(pumps.obtain("filter1"));
+        upstream.listen(filter0.getName(),
+                        boost::bind(&LLEventPump::post, boost::ref(filter0), _1));
+        upstream.listen(filter1.getName(),
+                        boost::bind(&LLEventPump::post, boost::ref(filter1), _1));
+        filter0.listen(listener0.getName(),
+                       boost::bind(&Listener::call, boost::ref(listener0), _1));
+        filter1.listen(listener1.getName(),
+                       boost::bind(&Listener::call, boost::ref(listener1), _1));
+        listener0.reset(0);
+        listener1.reset(0);
+        upstream.post(1);
+        check_listener("got unfiltered", listener0, 1);
+        check_listener("got unfiltered", listener1, 1);
+        filter0.enable(false);
+        upstream.post(2);
+        check_listener("didn't get filtered", listener0, 1);
+        check_listener("got filtered", listener1, 2);
+    }
+
+    template<> template<>
+    void events_object::test<7>()
+    {
+        set_test_name("listener dependency order");
+        typedef LLEventPump::NameList NameList;
+        typedef Collect::StringList StringList;
+        LLEventPump& button(pumps.obtain("button"));
+        Collect collector;
+        button.listen("Mary",
+                      boost::bind(&Collect::add, boost::ref(collector), "Mary", _1),
+                      // state that "Mary" must come after "checked"
+                      make<NameList>(list_of("checked")));
+        button.listen("checked",
+                      boost::bind(&Collect::add, boost::ref(collector), "checked", _1),
+                      // "checked" must come after "spot"
+                      make<NameList>(list_of("spot")));
+        button.listen("spot",
+                      boost::bind(&Collect::add, boost::ref(collector), "spot", _1));
+        button.post(1);
+        ensure_equals(collector.result, make<StringList>(list_of("spot")("checked")("Mary")));
+        collector.clear();
+        button.stopListening("Mary");
+        button.listen("Mary",
+                      boost::bind(&Collect::add, boost::ref(collector), "Mary", _1),
+                      LLEventPump::empty, // no after dependencies
+                      // now "Mary" must come before "spot"
+                      make<NameList>(list_of("spot")));
+        button.post(2);
+        ensure_equals(collector.result, make<StringList>(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 (const LLEventPump::Cycle& e)
+        {
+            threw = e.what();
+//          std::cout << "Caught: " << e.what() << '\n';
+        }
+        // Obviously the specific wording of the exception text can
+        // change; go ahead and change the test to match.
+        // Establish that it contains:
+        // - the name and runtime type of the LLEventPump
+        ensure_contains("LLEventPump type", threw, typeid(button).name());
+        ensure_contains("LLEventPump name", threw, "'button'");
+        // - the name of the new listener that caused the problem
+        ensure_contains("new listener name", threw, "'spot'");
+        // - a synopsis of the problematic dependencies.
+        ensure_contains("cyclic dependencies", threw,
+                        "\"Mary\" -> before (\"spot\")");
+        ensure_contains("cyclic dependencies", threw,
+                        "after (\"spot\") -> \"checked\"");
+        ensure_contains("cyclic dependencies", threw,
+                        "after (\"Mary\", \"checked\") -> \"spot\"");
+        button.listen("yellow",
+                      boost::bind(&Collect::add, boost::ref(collector), "yellow", _1),
+                      make<NameList>(list_of("checked")));
+        button.listen("shoelaces",
+                      boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1),
+                      make<NameList>(list_of("checked")));
+        button.post(3);
+        ensure_equals(collector.result, make<StringList>(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 (const LLEventPump::OrderChange& e)
+        {
+            threw = e.what();
+//          std::cout << "Caught: " << e.what() << '\n';
+        }
+        // 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.
+        ensure_contains("LLEventPump type", threw, typeid(button).name());
+        ensure_contains("LLEventPump name", threw, "'button'");
+        ensure_contains("new listener name", threw, "'of'");
+        ensure_contains("prev listener name", threw, "'yellow'");
+        ensure_contains("old order", threw, "was: Mary, checked, yellow, shoelaces");
+        ensure_contains("new order", threw, "now: Mary, checked, shoelaces, of, yellow");
+        button.post(4);
+        ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces")));
+    }
+
+    template<> template<>
+    void events_object::test<8>()
+    {
+        set_test_name("tweaked and untweaked LLEventPump instance names");
+        {   // nested scope
+            // Hand-instantiate an LLEventStream...
+            LLEventStream bob("bob");
+            bool threw = false;
+            try
+            {
+                // then another with a duplicate name.
+                LLEventStream bob2("bob");
+            }
+            catch (const LLEventPump::DupPumpName& /*e*/)
+            {
+                threw = true;
+//              std::cout << "Caught: " << e.what() << '\n';
+            }
+            ensure("Caught DupPumpName", threw);
+        }   // delete first 'bob'
+        LLEventStream bob("bob");   // should work, previous one unregistered
+        LLEventStream bob1("bob", true); // allowed to tweak name
+        ensure_equals("tweaked LLEventStream name", bob1.getName(), "bob1");
+        std::vector< boost::shared_ptr<LLEventStream> > streams;
+        for (int i = 2; i <= 10; ++i)
+        {
+            streams.push_back(boost::shared_ptr<LLEventStream>(new LLEventStream("bob", true)));
+        }
+        ensure_equals("last tweaked LLEventStream name", streams.back()->getName(), "bob10");
+    }
+
+    // Define a function that accepts an LLListenerOrPumpName
+    void eventSource(const LLListenerOrPumpName& listener)
+    {
+        // Pretend that some time has elapsed. Call listener immediately.
+        listener(17);
+    }
+
+    template<> template<>
+    void events_object::test<9>()
+    {
+        set_test_name("LLListenerOrPumpName");
+        // Passing a boost::bind() expression to LLListenerOrPumpName
+        listener0.reset(0);
+        eventSource(boost::bind(&Listener::call, boost::ref(listener0), _1));
+        check_listener("got by listener", listener0, 17);
+        // Passing a string LLEventPump name to LLListenerOrPumpName
+        listener0.reset(0);
+        LLEventStream random("random");
+        random.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1));
+        eventSource("random");
+        check_listener("got by pump name", listener0, 17);
+        bool threw = false;
+        try
+        {
+            LLListenerOrPumpName empty;
+            empty(17);
+        }
+        catch (const LLListenerOrPumpName::Empty&)
+        {
+            threw = true;
+        }
+        ensure("threw Empty", threw);
+    }
+
+    class TempListener: public Listener
+    {
+    public:
+        TempListener(const std::string& name, bool& liveFlag):
+            Listener(name),
+            mLiveFlag(liveFlag)
+        {
+            mLiveFlag = true;
+        }
+
+        virtual ~TempListener()
+        {
+            mLiveFlag = false;
+        }
+
+    private:
+        bool& mLiveFlag;
+    };
+
+    template<> template<>
+    void events_object::test<10>()
+    {
+        set_test_name("listen(boost::bind(...TempListener...))");
+        // listen() can't do anything about a plain TempListener instance:
+        // it's not managed with shared_ptr, nor is it an LLEventTrackable subclass
+        bool live = false;
+        LLEventPump& heaptest(pumps.obtain("heaptest"));
+        LLBoundListener connection;
+        {
+            TempListener tempListener("temp", live);
+            ensure("TempListener constructed", live);
+            connection = heaptest.listen(tempListener.getName(),
+                                         boost::bind(&Listener::call,
+                                                     boost::ref(tempListener),
+                                                     _1));
+            heaptest.post(1);
+            check_listener("received", tempListener, 1);
+        } // presumably this will make newListener go away?
+        // verify that
+        ensure("TempListener destroyed", ! live);
+        // This is the case against which we can't defend. Don't even try to
+        // post to heaptest -- that would engage Undefined Behavior.
+        // Cautiously inspect connection...
+        ensure("misleadingly connected", connection.connected());
+        // then disconnect by hand.
+        heaptest.stopListening("temp");
+    }
+
+    template<> template<>
+    void events_object::test<11>()
+    {
+        set_test_name("listen(boost::bind(...weak_ptr...))");
+        // listen() detecting weak_ptr<TempListener> in boost::bind() object
+        bool live = false;
+        LLEventPump& heaptest(pumps.obtain("heaptest"));
+        LLBoundListener connection;
+        ensure("default state", ! connection.connected());
+        {
+            boost::shared_ptr<TempListener> newListener(new TempListener("heap", live));
+            newListener->reset();
+            ensure("TempListener constructed", live);
+            connection = heaptest.listen(newListener->getName(),
+                                         boost::bind(&Listener::call, weaken(newListener), _1));
+            ensure("new connection", connection.connected());
+            heaptest.post(1);
+            check_listener("received", *newListener, 1);
+        } // presumably this will make newListener go away?
+        // verify that
+        ensure("TempListener destroyed", ! live);
+        ensure("implicit disconnect", ! connection.connected());
+        // now just make sure we don't blow up trying to access a freed object!
+        heaptest.post(2);
+    }
+
+    template<> template<>
+    void events_object::test<12>()
+    {
+        set_test_name("listen(boost::bind(...shared_ptr...))");
+/*==========================================================================*|
+        // DISABLED because I've made this case produce a compile error.
+        // Following the error leads the disappointed dev to a comment
+        // instructing her to use the weaken() function to bind a weak_ptr<T>
+        // instead of binding a shared_ptr<T>, and explaining why. I know of
+        // no way to use TUT to code a repeatable test in which the expected
+        // outcome is a compile error. The interested reader is invited to
+        // uncomment this block and build to see for herself.
+
+        // listen() detecting shared_ptr<TempListener> in boost::bind() object
+        bool live = false;
+        LLEventPump& heaptest(pumps.obtain("heaptest"));
+        LLBoundListener connection;
+        std::string listenerName("heap");
+        ensure("default state", ! connection.connected());
+        {
+            boost::shared_ptr<TempListener> newListener(new TempListener(listenerName, live));
+            ensure_equals("use_count", newListener.use_count(), 1);
+            newListener->reset();
+            ensure("TempListener constructed", live);
+            connection = heaptest.listen(newListener->getName(),
+                                         boost::bind(&Listener::call, newListener, _1));
+            ensure("new connection", connection.connected());
+            ensure_equals("use_count", newListener.use_count(), 2);
+            heaptest.post(1);
+            check_listener("received", *newListener, 1);
+        } // this should make newListener go away...
+        // Unfortunately, the fact that we've bound a shared_ptr by value into
+        // our LLEventPump means that copy will keep the referenced object alive.
+        ensure("TempListener still alive", live);
+        ensure("still connected", connection.connected());
+        // disconnecting explicitly should delete the TempListener...
+        heaptest.stopListening(listenerName);
+#if 0   // however, in my experience, it does not. I don't know why not.
+        // Ah: on 2009-02-19, Frank Mori Hess, author of the Boost.Signals2
+        // library, stated on the boost-users mailing list:
+        // http://www.nabble.com/Re%3A--signals2--review--The-review-of-the-signals2-library-(formerly-thread_safe_signals)-begins-today%2C-Nov-1st-p22102367.html
+        // "It will get destroyed eventually. The signal cleans up its slot
+        // list little by little during connect/invoke. It doesn't immediately
+        // remove disconnected slots from the slot list since other threads
+        // might be using the same slot list concurrently. It might be
+        // possible to make it immediately reset the shared_ptr owning the
+        // slot though, leaving an empty shared_ptr in the slot list, since
+        // that wouldn't invalidate any iterators."
+        ensure("TempListener destroyed", ! live);
+        ensure("implicit disconnect", ! connection.connected());
+#endif  // 0
+        // now just make sure we don't blow up trying to access a freed object!
+        heaptest.post(2);
+|*==========================================================================*/
+    }
+
+    class TempTrackableListener: public TempListener, public LLEventTrackable
+    {
+    public:
+        TempTrackableListener(const std::string& name, bool& liveFlag):
+            TempListener(name, liveFlag)
+        {}
+    };
+
+    template<> template<>
+    void events_object::test<13>()
+    {
+        set_test_name("listen(boost::bind(...TempTrackableListener ref...))");
+        bool live = false;
+        LLEventPump& heaptest(pumps.obtain("heaptest"));
+        LLBoundListener connection;
+        {
+            TempTrackableListener tempListener("temp", live);
+            ensure("TempTrackableListener constructed", live);
+            connection = heaptest.listen(tempListener.getName(),
+                                         boost::bind(&TempTrackableListener::call,
+                                                     boost::ref(tempListener), _1));
+            heaptest.post(1);
+            check_listener("received", tempListener, 1);
+        } // presumably this will make tempListener go away?
+        // verify that
+        ensure("TempTrackableListener destroyed", ! live);
+        ensure("implicit disconnect", ! connection.connected());
+        // now just make sure we don't blow up trying to access a freed object!
+        heaptest.post(2);
+    }
+
+    template<> template<>
+    void events_object::test<14>()
+    {
+        set_test_name("listen(boost::bind(...TempTrackableListener pointer...))");
+        bool live = false;
+        LLEventPump& heaptest(pumps.obtain("heaptest"));
+        LLBoundListener connection;
+        {
+            TempTrackableListener* newListener(new TempTrackableListener("temp", live));
+            ensure("TempTrackableListener constructed", live);
+            connection = heaptest.listen(newListener->getName(),
+                                         boost::bind(&TempTrackableListener::call,
+                                                     newListener, _1));
+            heaptest.post(1);
+            check_listener("received", *newListener, 1);
+            // explicitly destroy newListener
+            delete newListener;
+        }
+        // verify that
+        ensure("TempTrackableListener destroyed", ! live);
+        ensure("implicit disconnect", ! connection.connected());
+        // now just make sure we don't blow up trying to access a freed object!
+        heaptest.post(2);
+    }
+
+    class TempSharedListener: public TempListener,
+                              public boost::enable_shared_from_this<TempSharedListener>
+    {
+    public:
+        TempSharedListener(const std::string& name, bool& liveFlag):
+            TempListener(name, liveFlag)
+        {}
+    };
+
+    template<> template<>
+    void events_object::test<15>()
+    {
+        set_test_name("listen(boost::bind(...TempSharedListener ref...))");
+#if 0
+        bool live = false;
+        LLEventPump& heaptest(pumps.obtain("heaptest"));
+        LLBoundListener connection;
+        {
+            // We MUST have at least one shared_ptr to an
+            // enable_shared_from_this subclass object before
+            // shared_from_this() can work.
+            boost::shared_ptr<TempSharedListener>
+                tempListener(new TempSharedListener("temp", live));
+            ensure("TempSharedListener constructed", live);
+            // However, we're not passing either the shared_ptr or its
+            // corresponding weak_ptr -- instead, we're passing a reference to
+            // the TempSharedListener.
+/*==========================================================================*|
+            std::cout << "Capturing const ref" << std::endl;
+            const boost::enable_shared_from_this<TempSharedListener>& cref(*tempListener);
+            std::cout << "Capturing const ptr" << std::endl;
+            const boost::enable_shared_from_this<TempSharedListener>* cp(&cref);
+            std::cout << "Capturing non-const ptr" << std::endl;
+            boost::enable_shared_from_this<TempSharedListener>* p(const_cast<boost::enable_shared_from_this<TempSharedListener>*>(cp));
+            std::cout << "Capturing shared_from_this()" << std::endl;
+            boost::shared_ptr<TempSharedListener> sp(p->shared_from_this());
+            std::cout << "Capturing weak_ptr" << std::endl;
+            boost::weak_ptr<TempSharedListener> wp(weaken(sp));
+            std::cout << "Binding weak_ptr" << std::endl;
+|*==========================================================================*/
+            connection = heaptest.listen(tempListener->getName(),
+                                         boost::bind(&TempSharedListener::call, *tempListener, _1));
+            heaptest.post(1);
+            check_listener("received", *tempListener, 1);
+        } // presumably this will make tempListener go away?
+        // verify that
+        ensure("TempSharedListener destroyed", ! live);
+        ensure("implicit disconnect", ! connection.connected());
+        // now just make sure we don't blow up trying to access a freed object!
+        heaptest.post(2);
+#endif // 0
+    }
+} // namespace tut
diff --git a/install.xml b/install.xml
index 33875aee864..a0d53dbcc03 100644
--- a/install.xml
+++ b/install.xml
@@ -207,30 +207,30 @@
           <key>darwin</key>
           <map>
             <key>md5sum</key>
-            <string>279834a12a0ed4531fd602594eac8509</string>
+            <string>081ef195a856c708cc473c4421b4b290</string>
             <key>url</key>
-            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.32.0-darwin-20080812.tar.bz2</uri>
+            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-darwin-20090223.tar.bz2</uri>
           </map>
           <key>linux</key>
           <map>
             <key>md5sum</key>
-            <string>b9a943052e5525da5417d6f471d70bc5</string>
+            <string>b516a8576ecad0f957db7fc2cd972aac</string>
             <key>url</key>
-            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.32.0-linux-20080812.tar.bz2</uri>
+            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux-20090223a.tar.bz2</uri>
           </map>
           <key>linux64</key>
           <map>
             <key>md5sum</key>
-            <string>b97ae0855e77cc25b37ca63df093bb9b</string>
+            <string>6db62bb7f141b3a1b3107e1f9aad0eb0</string>
             <key>url</key>
-            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.0-linux64-20080909.tar.bz2</uri>
+            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux64-20090223a.tar.bz2</uri>
           </map>
           <key>windows</key>
           <map>
             <key>md5sum</key>
-            <string>d2b2ad9b46b9981c2a6be7c912bd17b4</string>
+            <string>3b56fe9e8d2975c612639d0a5370ffe7</string>
             <key>url</key>
-            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-windows-20080723.tar.bz2</uri>
+            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-windows-20090225.tar.bz2</uri>
           </map>
         </map>
       </map>
-- 
GitLab