From b2913b7cf1d6fb3716bdea42695f2cac1db5a8b8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 2 Dec 2019 14:39:24 -0500 Subject: [PATCH 01/48] DRTVWR-494: Defend LLInstanceTracker against multi-thread usage. The previous implementation went to some effort to crash if anyone attempted to create or destroy an LLInstanceTracker subclass instance during traversal. That restriction is manageable within a single thread, but becomes unworkable if it's possible that a given subclass might be used on more than one thread. Remove LLInstanceTracker::instance_iter, beginInstances(), endInstances(), also key_iter, beginKeys() and endKeys(). Instead, introduce key_snapshot() and instance_snapshot(), the only means of iterating over LLInstanceTracker instances. (These are intended to resemble functions, but in fact the current implementation simply presents the classes.) Iterating over a captured snapshot defends against container modifications during traversal. The term 'snapshot' reminds the coder that a new instance created during traversal will not be considered. To defend against instance deletion during traversal, a snapshot stores std::weak_ptrs which it lazily dereferences, skipping on the fly any that have expired. Dereferencing instance_snapshot::iterator gets you a reference rather than a pointer. Because some use cases want to delete all existing instances, add an instance_snapshot::deleteAll() method that extracts the pointer. Those cases used to require explicitly copying instance pointers into a separate container; instance_snapshot() now takes care of that. It remains the caller's responsibility to ensure that all instances of that LLInstanceTracker subclass were allocated on the heap. Replace unkeyed static LLInstanceTracker::getInstance(T*) -- which returned nullptr if that instance had been destroyed -- with new getWeak() method returning std::weak_ptr. Caller must detect expiration of that weak_ptr. Adjust tests accordingly. Use of std::weak_ptr to detect expired instances requires engaging std::shared_ptr in the constructor. We now store shared_ptrs in the static containers (std::map for keyed, std::set for unkeyed). Make LLInstanceTrackerBase a template parameterized on the type of the static data it manages. For that reason, hoist static data class declarations out of the class definitions to an LLInstanceTrackerStuff namespace. Remove the static atomic sIterationNestDepth and its methods incrementDepth(), decrementDepth() and getDepth(), since they were used only to forbid creation and destruction during traversal. Add a std::mutex to static data. Introduce an internal LockStatic class that locks the mutex while providing a pointer to static data, making that the only way to access the static data. The LLINSTANCETRACKER_DTOR_NOEXCEPT macro goes away because we no longer expect ~LLInstanceTracker() to throw an exception in test programs. That affects LLTrace::StatBase as well as LLInstanceTracker itself. Adapt consumers to the new LLInstanceTracker API. --- indra/llcommon/lleventtimer.cpp | 3 +- indra/llcommon/llfasttimer.cpp | 31 +- indra/llcommon/llinstancetracker.cpp | 17 +- indra/llcommon/llinstancetracker.h | 670 ++++++++++-------- indra/llcommon/llleaplistener.cpp | 8 +- indra/llcommon/llthreadlocalstorage.cpp | 12 +- indra/llcommon/lltrace.h | 2 +- indra/llcommon/lltracethreadrecorder.cpp | 12 +- .../llcommon/tests/llinstancetracker_test.cpp | 107 ++- indra/llcommon/tests/llleap_test.cpp | 28 +- indra/llrender/llgl.cpp | 6 +- indra/llui/llconsole.cpp | 4 +- indra/llui/lllayoutstack.cpp | 6 +- indra/llui/llnotificationslistener.cpp | 8 +- indra/llwindow/llwindow.cpp | 16 +- indra/llxml/llcontrol.h | 2 - indra/newview/llappviewer.cpp | 26 +- indra/newview/llchathistory.cpp | 4 +- indra/newview/llimprocessing.cpp | 4 +- indra/newview/llscenemonitor.cpp | 44 +- indra/newview/lltoast.cpp | 23 +- indra/newview/llviewercontrollistener.cpp | 12 +- 22 files changed, 530 insertions(+), 515 deletions(-) diff --git a/indra/llcommon/lleventtimer.cpp b/indra/llcommon/lleventtimer.cpp index 0d96e03da4..3986dee3ac 100644 --- a/indra/llcommon/lleventtimer.cpp +++ b/indra/llcommon/lleventtimer.cpp @@ -58,9 +58,8 @@ LLEventTimer::~LLEventTimer() void LLEventTimer::updateClass() { std::list completed_timers; - for (instance_iter iter = beginInstances(); iter != endInstances(); ) + for (auto& timer : instance_snapshot()) { - LLEventTimer& timer = *iter++; F32 et = timer.mEventTimer.getElapsedTimeF32(); if (timer.mEventTimer.getStarted() && et > timer.mPeriod) { timer.mEventTimer.reset(); diff --git a/indra/llcommon/llfasttimer.cpp b/indra/llcommon/llfasttimer.cpp index 3d28cd15b0..08ea668964 100644 --- a/indra/llcommon/llfasttimer.cpp +++ b/indra/llcommon/llfasttimer.cpp @@ -193,27 +193,26 @@ TimeBlockTreeNode& BlockTimerStatHandle::getTreeNode() const void BlockTimer::bootstrapTimerTree() { - for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(), end_it = BlockTimerStatHandle::instance_tracker_t::endInstances(); - it != end_it; - ++it) + for (auto& base : BlockTimerStatHandle::instance_snapshot()) { - BlockTimerStatHandle& timer = static_cast(*it); + // because of indirect derivation from LLInstanceTracker, have to downcast + BlockTimerStatHandle& timer = static_cast(base); if (&timer == &BlockTimer::getRootTimeBlock()) continue; // bootstrap tree construction by attaching to last timer to be on stack // when this timer was called if (timer.getParent() == &BlockTimer::getRootTimeBlock()) -{ + { TimeBlockAccumulator& accumulator = timer.getCurrentAccumulator(); if (accumulator.mLastCaller) - { + { timer.setParent(accumulator.mLastCaller); accumulator.mParent = accumulator.mLastCaller; - } + } // no need to push up tree on first use, flag can be set spuriously accumulator.mMoveUpTree = false; - } + } } } @@ -306,12 +305,10 @@ void BlockTimer::processTimes() updateTimes(); // reset for next frame - for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(), - end_it = BlockTimerStatHandle::instance_tracker_t::endInstances(); - it != end_it; - ++it) + for (auto& base : BlockTimerStatHandle::instance_snapshot()) { - BlockTimerStatHandle& timer = static_cast(*it); + // because of indirect derivation from LLInstanceTracker, have to downcast + BlockTimerStatHandle& timer = static_cast(base); TimeBlockAccumulator& accumulator = timer.getCurrentAccumulator(); accumulator.mLastCaller = NULL; @@ -362,12 +359,10 @@ void BlockTimer::logStats() LLSD sd; { - for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(), - end_it = BlockTimerStatHandle::instance_tracker_t::endInstances(); - it != end_it; - ++it) + for (auto& base : BlockTimerStatHandle::instance_snapshot()) { - BlockTimerStatHandle& timer = static_cast(*it); + // because of indirect derivation from LLInstanceTracker, have to downcast + BlockTimerStatHandle& timer = static_cast(base); LLTrace::PeriodicRecording& frame_recording = LLTrace::get_frame_recording(); sd[timer.getName()]["Time"] = (LLSD::Real) (frame_recording.getLastRecording().getSum(timer).value()); sd[timer.getName()]["Calls"] = (LLSD::Integer) (frame_recording.getLastRecording().getSum(timer.callCount())); diff --git a/indra/llcommon/llinstancetracker.cpp b/indra/llcommon/llinstancetracker.cpp index 3f990f4869..accb4286e8 100644 --- a/indra/llcommon/llinstancetracker.cpp +++ b/indra/llcommon/llinstancetracker.cpp @@ -34,18 +34,5 @@ // external library headers // other Linden headers -void LLInstanceTrackerBase::StaticBase::incrementDepth() -{ - ++sIterationNestDepth; -} - -void LLInstanceTrackerBase::StaticBase::decrementDepth() -{ - llassert(sIterationNestDepth); - --sIterationNestDepth; -} - -U32 LLInstanceTrackerBase::StaticBase::getDepth() -{ - return sIterationNestDepth; -} +// This .cpp file is required by our CMake test macro. It contributes no code +// to the viewer. diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 363d0bcbd5..76b201ad8c 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -28,354 +28,456 @@ #ifndef LL_LLINSTANCETRACKER_H #define LL_LLINSTANCETRACKER_H -#include #include +#include +#include #include +#include +#include +#include -#include "llstringtable.h" #include #include +#include -// As of 2017-05-06, as far as nat knows, only clang supports __has_feature(). -// Unfortunately VS2013's preprocessor shortcut logic doesn't prevent it from -// producing (fatal) warnings for defined(__clang__) && __has_feature(...). -// Have to work around that. -#if ! defined(__clang__) -#define __has_feature(x) 0 -#endif // __clang__ - -#if defined(LL_TEST_llinstancetracker) && __has_feature(cxx_noexcept) -// ~LLInstanceTracker() performs llassert_always() validation. That's fine in -// production code, since the llassert_always() is implemented as an LL_ERRS -// message, which will crash-with-message. In our integration test executable, -// though, this llassert_always() throws an exception instead so we can test -// error conditions and continue running the test. However -- as of C++11, -// destructors are implicitly noexcept(true). Unless we mark -// ~LLInstanceTracker() noexcept(false), the test executable crashes even on -// the ATTEMPT to throw. -#define LLINSTANCETRACKER_DTOR_NOEXCEPT noexcept(false) -#else -// If we're building for production, or in fact building *any other* test, or -// we're using a compiler that doesn't support __has_feature(), or we're not -// compiling with a C++ version that supports noexcept -- don't specify it. -#define LLINSTANCETRACKER_DTOR_NOEXCEPT -#endif - +/***************************************************************************** +* LLInstanceTrackerBase +*****************************************************************************/ /** * Base class manages "class-static" data that must actually have singleton * semantics: one instance per process, rather than one instance per module as * sometimes happens with data simply declared static. */ +namespace LLInstanceTrackerStuff +{ + struct StaticBase + { + // We need to be able to lock static data while manipulating it. + typedef std::mutex mutex_t; + mutex_t mMutex; + }; +} // namespace LLInstanceTrackerStuff + +template class LL_COMMON_API LLInstanceTrackerBase { protected: - /// It's not essential to derive your STATICDATA (for use with - /// getStatic()) from StaticBase; it's just that both known - /// implementations do. - struct StaticBase - { - StaticBase(): - sIterationNestDepth(0) - {} + typedef Static StaticData; - void incrementDepth(); - void decrementDepth(); - U32 getDepth(); - private: -#ifdef LL_WINDOWS - std::atomic_uint32_t sIterationNestDepth; -#else - std::atomic_uint sIterationNestDepth; -#endif - }; + // Instantiate this class to obtain a pointer to the canonical static + // instance of class Static while holding a lock on that instance. Use of + // Static::mMutex presumes either that Static is derived from StaticBase, + // or that Static declares some other suitable mMutex. + class LockStatic + { + typedef std::unique_lock lock_t; + public: + LockStatic(): + mData(getStatic()), + mLock(mData->mMutex) + {} + Static* get() const { return mData; } + operator Static*() const { return get(); } + Static* operator->() const { return get(); } + // sometimes we must explicitly unlock... + void unlock() + { + // but once we do, access is no longer permitted + mData = nullptr; + mLock.unlock(); + } + protected: + Static* mData; + lock_t mLock; + private: + Static* getStatic() + { + static Static sData; + return &sData; + } + }; }; -LL_COMMON_API void assert_main_thread(); - +/***************************************************************************** +* LLInstanceTracker with key +*****************************************************************************/ enum EInstanceTrackerAllowKeyCollisions { - LLInstanceTrackerErrorOnCollision, - LLInstanceTrackerReplaceOnCollision + LLInstanceTrackerErrorOnCollision, + LLInstanceTrackerReplaceOnCollision }; +namespace LLInstanceTrackerStuff +{ + template + struct StaticMap: public StaticBase + { + typedef std::map InstanceMap; + InstanceMap mMap; + }; +} // LLInstanceTrackerStuff + /// This mix-in class adds support for tracking all instances of the specified class parameter T /// The (optional) key associates a value of type KEY with a given instance of T, for quick lookup /// If KEY is not provided, then instances are stored in a simple set /// @NOTE: see explicit specialization below for default KEY==void case -/// @NOTE: this class is not thread-safe unless used as read-only -template -class LLInstanceTracker : public LLInstanceTrackerBase +template +class LLInstanceTracker : + public LLInstanceTrackerBase>> { - typedef LLInstanceTracker self_t; - typedef typename std::multimap InstanceMap; - struct StaticData: public StaticBase - { - InstanceMap sMap; - }; - static StaticData& getStatic() { static StaticData sData; return sData;} - static InstanceMap& getMap_() { return getStatic().sMap; } + typedef LLInstanceTrackerBase>> super; + using typename super::StaticData; + using typename super::LockStatic; + typedef typename StaticData::InstanceMap InstanceMap; public: - class instance_iter : public boost::iterator_facade - { - public: - typedef boost::iterator_facade super_t; - - instance_iter(const typename InstanceMap::iterator& it) - : mIterator(it) - { - getStatic().incrementDepth(); - } + // snapshot of std::pair> pairs + class snapshot + { + // It's very important that what we store in this snapshot are + // weak_ptrs, NOT shared_ptrs. That's how we discover whether any + // instance has been deleted during the lifespan of a snapshot. + typedef std::vector>> VectorType; + // Dereferencing our iterator produces a std::shared_ptr for each + // instance that still exists. Since we store weak_ptrs, that involves + // two chained transformations: + // - a transform_iterator to lock the weak_ptr and return a shared_ptr + // - a filter_iterator to skip any shared_ptr that has become invalid. + // It is very important that we filter lazily, that is, during + // traversal. Any one of our stored weak_ptrs might expire during + // traversal. + typedef std::pair> strong_pair; + // Note for future reference: nat has not yet had any luck (up to + // Boost 1.67) trying to use boost::transform_iterator with a hand- + // coded functor, only with actual functions. In my experience, an + // internal boost::result_of() operation fails, even with an explicit + // result_type typedef. But this works. + static strong_pair strengthen(typename VectorType::value_type& pair) + { + return { pair.first, pair.second.lock() }; + } + static bool dead_skipper(const strong_pair& pair) + { + return bool(pair.second); + } - ~instance_iter() - { - getStatic().decrementDepth(); - } + public: + snapshot(): + // populate our vector with a snapshot of (locked!) InstanceMap + // note, this assigns pair to pair + mData(mLock->mMap.begin(), mLock->mMap.end()) + { + // release the lock once we've populated mData + mLock.unlock(); + } + // You can't make a transform_iterator (or anything else) that + // literally stores a C++ function (decltype(strengthen)) -- but you + // can make a transform_iterator based on a _function pointer._ + typedef boost::transform_iterator strong_iterator; + typedef boost::filter_iterator iterator; - private: - friend class boost::iterator_core_access; + iterator begin() { return make_iterator(mData.begin()); } + iterator end() { return make_iterator(mData.end()); } - void increment() { mIterator++; } - bool equal(instance_iter const& other) const - { - return mIterator == other.mIterator; - } + private: + iterator make_iterator(typename VectorType::iterator iter) + { + // transform_iterator only needs the base iterator and the transform. + // filter_iterator wants the predicate and both ends of the range. + return iterator(dead_skipper, + strong_iterator(iter, strengthen), + strong_iterator(mData.end(), strengthen)); + } - T& dereference() const - { - return *(mIterator->second); - } + LockStatic mLock; // lock static data during construction + VectorType mData; + }; - typename InstanceMap::iterator mIterator; - }; + // iterate over this for references to each instance + class instance_snapshot: public snapshot + { + private: + static T& instance_getter(typename snapshot::iterator::reference pair) + { + return *pair.second; + } + public: + typedef boost::transform_iterator iterator; + iterator begin() { return iterator(snapshot::begin(), instance_getter); } + iterator end() { return iterator(snapshot::end(), instance_getter); } - class key_iter : public boost::iterator_facade - { - public: - typedef boost::iterator_facade super_t; + void deleteAll() + { + for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it) + { + delete it->second.get(); + } + } + }; - key_iter(typename InstanceMap::iterator it) - : mIterator(it) - { - getStatic().incrementDepth(); - } + // iterate over this for each key + class key_snapshot: public snapshot + { + private: + static KEY key_getter(typename snapshot::iterator::reference pair) + { + return pair.first; + } + public: + typedef boost::transform_iterator iterator; + iterator begin() { return iterator(snapshot::begin(), key_getter); } + iterator end() { return iterator(snapshot::end(), key_getter); } + }; - key_iter(const key_iter& other) - : mIterator(other.mIterator) - { - getStatic().incrementDepth(); - } + static T* getInstance(const KEY& k) + { + LockStatic lock; + const InstanceMap& map(lock->mMap); + typename InstanceMap::const_iterator found = map.find(k); + return (found == map.end()) ? NULL : found->second.get(); + } - ~key_iter() - { - getStatic().decrementDepth(); - } - - - private: - friend class boost::iterator_core_access; - - void increment() { mIterator++; } - bool equal(key_iter const& other) const - { - return mIterator == other.mIterator; - } - - KEY& dereference() const - { - return const_cast(mIterator->first); - } - - typename InstanceMap::iterator mIterator; - }; - - static T* getInstance(const KEY& k) - { - const InstanceMap& map(getMap_()); - typename InstanceMap::const_iterator found = map.find(k); - return (found == map.end()) ? NULL : found->second; - } - - static instance_iter beginInstances() - { - return instance_iter(getMap_().begin()); - } - - static instance_iter endInstances() - { - return instance_iter(getMap_().end()); - } - - static S32 instanceCount() - { - return getMap_().size(); - } - - static key_iter beginKeys() - { - return key_iter(getMap_().begin()); - } - static key_iter endKeys() - { - return key_iter(getMap_().end()); - } + static S32 instanceCount() + { + return LockStatic()->mMap.size(); + } protected: - LLInstanceTracker(const KEY& key) - { - // make sure static data outlives all instances - getStatic(); - add_(key); - } - virtual ~LLInstanceTracker() LLINSTANCETRACKER_DTOR_NOEXCEPT - { - // it's unsafe to delete instances of this type while all instances are being iterated over. - llassert_always(getStatic().getDepth() == 0); - remove_(); - } - virtual void setKey(KEY key) { remove_(); add_(key); } - virtual const KEY& getKey() const { return mInstanceKey; } + LLInstanceTracker(const KEY& key) + { + // We do not intend to manage the lifespan of this object with + // shared_ptr, so give it a no-op deleter. We store shared_ptrs in our + // InstanceMap specifically so snapshot can store weak_ptrs so we can + // detect deletions during traversals. + std::shared_ptr ptr(static_cast(this), [](T*){}); + LockStatic lock; + add_(lock, key, ptr); + } +public: + virtual ~LLInstanceTracker() + { + LockStatic lock; + remove_(lock); + } +protected: + virtual void setKey(KEY key) + { + LockStatic lock; + // Even though the shared_ptr we store in our map has a no-op deleter + // for T itself, letting the use count decrement to 0 will still + // delete the use-count object. Capture the shared_ptr we just removed + // and re-add it to the map with the new key. + auto ptr = remove_(lock); + add_(lock, key, ptr); + } +public: + virtual const KEY& getKey() const { return mInstanceKey; } private: - LLInstanceTracker( const LLInstanceTracker& ); - const LLInstanceTracker& operator=( const LLInstanceTracker& ); + LLInstanceTracker( const LLInstanceTracker& ) = delete; + LLInstanceTracker& operator=( const LLInstanceTracker& ) = delete; - void add_(const KEY& key) - { - mInstanceKey = key; - InstanceMap& map = getMap_(); - typename InstanceMap::iterator insertion_point_it = map.lower_bound(key); - if (insertion_point_it != map.end() - && insertion_point_it->first == key) - { // found existing entry with that key - switch(KEY_COLLISION_BEHAVIOR) - { - case LLInstanceTrackerErrorOnCollision: - { - // use assert here instead of LL_ERRS(), otherwise the error will be ignored since this call is made during global object initialization - llassert_always_msg(false, "Instance with this same key already exists!"); - break; - } - case LLInstanceTrackerReplaceOnCollision: - { - // replace pointer, but leave key (should have compared equal anyway) - insertion_point_it->second = static_cast(this); - break; - } - default: - break; - } - } - else - { // new key - map.insert(insertion_point_it, std::make_pair(key, static_cast(this))); - } - } - void remove_() - { - InstanceMap& map = getMap_(); - typename InstanceMap::iterator iter = map.find(mInstanceKey); - if (iter != map.end()) - { - map.erase(iter); - } - } + // for logging + template + static K report(K key) { return key; } + static std::string report(const std::string& key) { return "'" + key + "'"; } + static std::string report(const char* key) { return report(std::string(key)); } + + // caller must instantiate LockStatic + void add_(LockStatic& lock, const KEY& key, const std::shared_ptr& ptr) + { + mInstanceKey = key; + InstanceMap& map = lock->mMap; + switch(KEY_COLLISION_BEHAVIOR) + { + case LLInstanceTrackerErrorOnCollision: + { + // map stores shared_ptr to self + auto pair = map.emplace(key, ptr); + if (! pair.second) + { + LL_ERRS("LLInstanceTracker") << "Instance with key " << report(key) + << " already exists!" << LL_ENDL; + } + break; + } + case LLInstanceTrackerReplaceOnCollision: + map[key] = ptr; + break; + default: + break; + } + } + std::shared_ptr remove_(LockStatic& lock) + { + InstanceMap& map = lock->mMap; + typename InstanceMap::iterator iter = map.find(mInstanceKey); + if (iter != map.end()) + { + auto ret = iter->second; + map.erase(iter); + return ret; + } + return {}; + } private: - KEY mInstanceKey; + KEY mInstanceKey; }; +/***************************************************************************** +* LLInstanceTracker without key +*****************************************************************************/ +namespace LLInstanceTrackerStuff +{ + template + struct StaticSet: public StaticBase + { + typedef std::set InstanceSet; + InstanceSet mSet; + }; +} // LLInstanceTrackerStuff + +// TODO: +// - For the case of omitted KEY template parameter, consider storing +// std::map> instead of std::set>. +// That might let us share more of the implementation between KEY and +// non-KEY LLInstanceTracker subclasses. +// - Even if not that, consider trying to unify the snapshot implementations. +// The trouble is that the 'iterator' published by each (and by their +// subclasses) must reflect the specific type of the callables that +// distinguish them. (Maybe make instance_snapshot() and key_snapshot() +// factory functions that pass lambdas to a factory function for the generic +// template class?) + /// explicit specialization for default case where KEY is void /// use a simple std::set template -class LLInstanceTracker : public LLInstanceTrackerBase +class LLInstanceTracker : + public LLInstanceTrackerBase>> { - typedef LLInstanceTracker self_t; - typedef typename std::set InstanceSet; - struct StaticData: public StaticBase - { - InstanceSet sSet; - }; - static StaticData& getStatic() { static StaticData sData; return sData; } - static InstanceSet& getSet_() { return getStatic().sSet; } + typedef LLInstanceTrackerBase>> super; + using typename super::StaticData; + using typename super::LockStatic; + typedef typename StaticData::InstanceSet InstanceSet; public: + /** + * Storing a dumb T* somewhere external is a bad idea, since + * LLInstanceTracker subclasses are explicitly destroyed rather than + * managed by smart pointers. It's legal to declare stack instances of an + * LLInstanceTracker subclass. But it's reasonable to store a + * std::weak_ptr, which will become invalid when the T instance is + * destroyed. + */ + std::weak_ptr getWeak() + { + return mSelf; + } + + static S32 instanceCount() { return LockStatic()->mSet.size(); } - /** - * Does a particular instance still exist? Of course, if you already have - * a T* in hand, you need not call getInstance() to @em locate the - * instance -- unlike the case where getInstance() accepts some kind of - * key. Nonetheless this method is still useful to @em validate a - * particular T*, since each instance's destructor removes itself from the - * underlying set. - */ - static T* getInstance(T* k) - { - const InstanceSet& set(getSet_()); - typename InstanceSet::const_iterator found = set.find(k); - return (found == set.end())? NULL : *found; - } - static S32 instanceCount() { return getSet_().size(); } + // snapshot of std::shared_ptr pointers + class snapshot + { + // It's very important that what we store in this snapshot are + // weak_ptrs, NOT shared_ptrs. That's how we discover whether any + // instance has been deleted during the lifespan of a snapshot. + typedef std::vector> VectorType; + // Dereferencing our iterator produces a std::shared_ptr for each + // instance that still exists. Since we store weak_ptrs, that involves + // two chained transformations: + // - a transform_iterator to lock the weak_ptr and return a shared_ptr + // - a filter_iterator to skip any shared_ptr that has become invalid. + typedef std::shared_ptr strong_ptr; + static strong_ptr strengthen(typename VectorType::value_type& ptr) + { + return ptr.lock(); + } + static bool dead_skipper(const strong_ptr& ptr) + { + return bool(ptr); + } - class instance_iter : public boost::iterator_facade - { - public: - instance_iter(const typename InstanceSet::iterator& it) - : mIterator(it) - { - getStatic().incrementDepth(); - } + public: + snapshot(): + // populate our vector with a snapshot of (locked!) InstanceSet + // note, this assigns stored shared_ptrs to weak_ptrs for snapshot + mData(mLock->mSet.begin(), mLock->mSet.end()) + { + // release the lock once we've populated mData + mLock.unlock(); + } - instance_iter(const instance_iter& other) - : mIterator(other.mIterator) - { - getStatic().incrementDepth(); - } + typedef boost::transform_iterator strong_iterator; + typedef boost::filter_iterator iterator; - ~instance_iter() - { - getStatic().decrementDepth(); - } + iterator begin() { return make_iterator(mData.begin()); } + iterator end() { return make_iterator(mData.end()); } - private: - friend class boost::iterator_core_access; + private: + iterator make_iterator(typename VectorType::iterator iter) + { + // transform_iterator only needs the base iterator and the transform. + // filter_iterator wants the predicate and both ends of the range. + return iterator(dead_skipper, + strong_iterator(iter, strengthen), + strong_iterator(mData.end(), strengthen)); + } - void increment() { mIterator++; } - bool equal(instance_iter const& other) const - { - return mIterator == other.mIterator; - } + LockStatic mLock; // lock static data during construction + VectorType mData; + }; - T& dereference() const - { - return **mIterator; - } + // iterate over this for references to each instance + struct instance_snapshot: public snapshot + { + typedef boost::indirect_iterator iterator; + iterator begin() { return iterator(snapshot::begin()); } + iterator end() { return iterator(snapshot::end()); } - typename InstanceSet::iterator mIterator; - }; - - static instance_iter beginInstances() { return instance_iter(getSet_().begin()); } - static instance_iter endInstances() { return instance_iter(getSet_().end()); } + void deleteAll() + { + for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it) + { + delete it->get(); + } + } + }; protected: - LLInstanceTracker() - { - // make sure static data outlives all instances - getStatic(); - getSet_().insert(static_cast(this)); - } - virtual ~LLInstanceTracker() LLINSTANCETRACKER_DTOR_NOEXCEPT - { - // it's unsafe to delete instances of this type while all instances are being iterated over. - llassert_always(getStatic().getDepth() == 0); - getSet_().erase(static_cast(this)); - } + LLInstanceTracker() + { + // Since we do not intend for this shared_ptr to manage lifespan, give + // it a no-op deleter. + std::shared_ptr ptr(static_cast(this), [](T*){}); + // save corresponding weak_ptr for future reference + mSelf = ptr; + // Also store it in our class-static set to track this instance. + LockStatic()->mSet.emplace(ptr); + } +public: + virtual ~LLInstanceTracker() + { + // convert weak_ptr to shared_ptr because that's what we store in our + // InstanceSet + LockStatic()->mSet.erase(mSelf.lock()); + } +protected: + LLInstanceTracker(const LLInstanceTracker& other): + LLInstanceTracker() + {} - LLInstanceTracker(const LLInstanceTracker& other) - { - getSet_().insert(static_cast(this)); - } +private: + // Storing a weak_ptr to self is a bit like deriving from + // std::enable_shared_from_this(), except more explicit. + std::weak_ptr mSelf; }; #endif diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index fa5730f112..f50bacb1e8 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -228,13 +228,11 @@ void LLLeapListener::getAPIs(const LLSD& request) const { Response reply(LLSD(), request); - for (LLEventAPI::instance_iter eai(LLEventAPI::beginInstances()), - eaend(LLEventAPI::endInstances()); - eai != eaend; ++eai) + for (auto& ea : LLEventAPI::instance_snapshot()) { LLSD info; - info["desc"] = eai->getDesc(); - reply[eai->getName()] = info; + info["desc"] = ea.getDesc(); + reply[ea.getName()] = info; } } diff --git a/indra/llcommon/llthreadlocalstorage.cpp b/indra/llcommon/llthreadlocalstorage.cpp index 8cef05caac..d8a063e8d5 100644 --- a/indra/llcommon/llthreadlocalstorage.cpp +++ b/indra/llcommon/llthreadlocalstorage.cpp @@ -93,11 +93,9 @@ void LLThreadLocalPointerBase::initAllThreadLocalStorage() { if (!sInitialized) { - for (LLInstanceTracker::instance_iter it = beginInstances(), end_it = endInstances(); - it != end_it; - ++it) + for (auto& base : instance_snapshot()) { - (*it).initStorage(); + base.initStorage(); } sInitialized = true; } @@ -108,11 +106,9 @@ void LLThreadLocalPointerBase::destroyAllThreadLocalStorage() { if (sInitialized) { - //for (LLInstanceTracker::instance_iter it = beginInstances(), end_it = endInstances(); - // it != end_it; - // ++it) + //for (auto& base : instance_snapshot()) //{ - // (*it).destroyStorage(); + // base.destroyStorage(); //} sInitialized = false; } diff --git a/indra/llcommon/lltrace.h b/indra/llcommon/lltrace.h index 79ff55b739..0d0cd6f581 100644 --- a/indra/llcommon/lltrace.h +++ b/indra/llcommon/lltrace.h @@ -57,7 +57,7 @@ class StatBase { public: StatBase(const char* name, const char* description); - virtual ~StatBase() LLINSTANCETRACKER_DTOR_NOEXCEPT {} + virtual ~StatBase() {} virtual const char* getUnitLabel() const; const std::string& getName() const { return mName; } diff --git a/indra/llcommon/lltracethreadrecorder.cpp b/indra/llcommon/lltracethreadrecorder.cpp index 181fc2f058..025dc57044 100644 --- a/indra/llcommon/lltracethreadrecorder.cpp +++ b/indra/llcommon/lltracethreadrecorder.cpp @@ -28,6 +28,7 @@ #include "lltracethreadrecorder.h" #include "llfasttimer.h" #include "lltrace.h" +#include "llstl.h" namespace LLTrace { @@ -64,16 +65,15 @@ void ThreadRecorder::init() activate(&mThreadRecordingBuffers); // initialize time block parent pointers - for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(), end_it = BlockTimerStatHandle::instance_tracker_t::endInstances(); - it != end_it; - ++it) + for (auto& base : BlockTimerStatHandle::instance_snapshot()) { - BlockTimerStatHandle& time_block = static_cast(*it); - TimeBlockTreeNode& tree_node = mTimeBlockTreeNodes[it->getIndex()]; + // because of indirect derivation from LLInstanceTracker, have to downcast + BlockTimerStatHandle& time_block = static_cast(base); + TimeBlockTreeNode& tree_node = mTimeBlockTreeNodes[time_block.getIndex()]; tree_node.mBlock = &time_block; tree_node.mParent = &root_time_block; - it->getCurrentAccumulator().mParent = &root_time_block; + time_block.getCurrentAccumulator().mParent = &root_time_block; } mRootTimer = new BlockTimer(root_time_block); diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index d94fc0c56d..9b89159625 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -41,7 +41,6 @@ #include // other Linden headers #include "../test/lltut.h" -#include "wrapllerrs.h" struct Badness: public std::runtime_error { @@ -112,24 +111,22 @@ namespace tut void object::test<2>() { ensure_equals(Unkeyed::instanceCount(), 0); - Unkeyed* dangling = NULL; + std::weak_ptr dangling; { Unkeyed one; ensure_equals(Unkeyed::instanceCount(), 1); - Unkeyed* found = Unkeyed::getInstance(&one); - ensure_equals(found, &one); + std::weak_ptr found = one.getWeak(); + ensure(! found.expired()); { boost::scoped_ptr two(new Unkeyed); ensure_equals(Unkeyed::instanceCount(), 2); - Unkeyed* found = Unkeyed::getInstance(two.get()); - ensure_equals(found, two.get()); } ensure_equals(Unkeyed::instanceCount(), 1); - // store an unwise pointer to a temp Unkeyed instance - dangling = &one; + // store a weak pointer to a temp Unkeyed instance + dangling = found; } // make that instance vanish // check the now-invalid pointer to the destroyed instance - ensure("getInstance(T*) failed to track destruction", ! Unkeyed::getInstance(dangling)); + ensure("weak_ptr failed to track destruction", dangling.expired()); ensure_equals(Unkeyed::instanceCount(), 0); } @@ -142,7 +139,8 @@ namespace tut // reimplement LLInstanceTracker using, say, a hash map instead of a // std::map. We DO insist that every key appear exactly once. typedef std::vector StringVector; - StringVector keys(Keyed::beginKeys(), Keyed::endKeys()); + auto snap = Keyed::key_snapshot(); + StringVector keys(snap.begin(), snap.end()); std::sort(keys.begin(), keys.end()); StringVector::const_iterator ki(keys.begin()); ensure_equals(*ki++, "one"); @@ -153,17 +151,15 @@ namespace tut ensure("didn't reach end", ki == keys.end()); // Use a somewhat different approach to order independence with - // beginInstances(): explicitly capture the instances we know in a + // instance_snapshot(): explicitly capture the instances we know in a // set, and delete them as we iterate through. typedef std::set InstanceSet; InstanceSet instances; instances.insert(&one); instances.insert(&two); instances.insert(&three); - for (Keyed::instance_iter ii(Keyed::beginInstances()), iend(Keyed::endInstances()); - ii != iend; ++ii) + for (auto& ref : Keyed::instance_snapshot()) { - Keyed& ref = *ii; ensure_equals("spurious instance", instances.erase(&ref), 1); } ensure_equals("unreported instance", instances.size(), 0); @@ -180,11 +176,10 @@ namespace tut instances.insert(&two); instances.insert(&three); - for (Unkeyed::instance_iter ii(Unkeyed::beginInstances()), iend(Unkeyed::endInstances()); ii != iend; ++ii) - { - Unkeyed& ref = *ii; - ensure_equals("spurious instance", instances.erase(&ref), 1); - } + for (auto& ref : Unkeyed::instance_snapshot()) + { + ensure_equals("spurious instance", instances.erase(&ref), 1); + } ensure_equals("unreported instance", instances.size(), 0); } @@ -192,49 +187,49 @@ namespace tut template<> template<> void object::test<5>() { - set_test_name("delete Keyed with outstanding instance_iter"); - std::string what; - Keyed* keyed = new Keyed("delete Keyed with outstanding instance_iter"); - { - WrapLLErrs wrapper; - Keyed::instance_iter i(Keyed::beginInstances()); - what = wrapper.catch_llerrs([&keyed](){ - delete keyed; - }); - } - ensure(! what.empty()); + std::string desc("delete Keyed with outstanding instance_snapshot"); + set_test_name(desc); + Keyed* keyed = new Keyed(desc); + // capture a snapshot but do not yet traverse it + auto snapshot = Keyed::instance_snapshot(); + // delete the one instance + delete keyed; + // traversing the snapshot should reflect the deletion + // avoid ensure_equals() because it requires the ability to stream the + // two values to std::ostream + ensure(snapshot.begin() == snapshot.end()); } template<> template<> void object::test<6>() { - set_test_name("delete Keyed with outstanding key_iter"); - std::string what; - Keyed* keyed = new Keyed("delete Keyed with outstanding key_it"); - { - WrapLLErrs wrapper; - Keyed::key_iter i(Keyed::beginKeys()); - what = wrapper.catch_llerrs([&keyed](){ - delete keyed; - }); - } - ensure(! what.empty()); + std::string desc("delete Keyed with outstanding key_snapshot"); + set_test_name(desc); + Keyed* keyed = new Keyed(desc); + // capture a snapshot but do not yet traverse it + auto snapshot = Keyed::key_snapshot(); + // delete the one instance + delete keyed; + // traversing the snapshot should reflect the deletion + // avoid ensure_equals() because it requires the ability to stream the + // two values to std::ostream + ensure(snapshot.begin() == snapshot.end()); } template<> template<> void object::test<7>() { - set_test_name("delete Unkeyed with outstanding instance_iter"); + set_test_name("delete Unkeyed with outstanding instance_snapshot"); std::string what; Unkeyed* unkeyed = new Unkeyed; - { - WrapLLErrs wrapper; - Unkeyed::instance_iter i(Unkeyed::beginInstances()); - what = wrapper.catch_llerrs([&unkeyed](){ - delete unkeyed; - }); - } - ensure(! what.empty()); + // capture a snapshot but do not yet traverse it + auto snapshot = Unkeyed::instance_snapshot(); + // delete the one instance + delete unkeyed; + // traversing the snapshot should reflect the deletion + // avoid ensure_equals() because it requires the ability to stream the + // two values to std::ostream + ensure(snapshot.begin() == snapshot.end()); } template<> template<> @@ -246,11 +241,9 @@ namespace tut // We can't use the iterator-range InstanceSet constructor because // beginInstances() returns an iterator that dereferences to an // Unkeyed&, not an Unkeyed*. - for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()), - ukend(Unkeyed::endInstances()); - uki != ukend; ++uki) + for (auto& ref : Unkeyed::instance_snapshot()) { - existing.insert(&*uki); + existing.insert(&ref); } try { @@ -273,11 +266,9 @@ namespace tut // instances was also present in the original set. If that's not true, // it's because our new Unkeyed ended up in the updated set despite // its constructor exception. - for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()), - ukend(Unkeyed::endInstances()); - uki != ukend; ++uki) + for (auto& ref : Unkeyed::instance_snapshot()) { - ensure("failed to remove instance", existing.find(&*uki) != existing.end()); + ensure("failed to remove instance", existing.find(&ref) != existing.end()); } } } // namespace tut diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index bf0a74d10d..9d71e327d8 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -49,24 +49,28 @@ const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte #endif -void waitfor(const std::vector& instances, int timeout=60) +// capture std::weak_ptrs to LLLeap instances so we can tell when they expire +typedef std::vector> LLLeapVector; + +void waitfor(const LLLeapVector& instances, int timeout=60) { int i; for (i = 0; i < timeout; ++i) { // Every iteration, test whether any of the passed LLLeap instances // still exist (are still running). - std::vector::const_iterator vli(instances.begin()), vlend(instances.end()); - for ( ; vli != vlend; ++vli) + bool found = false; + for (auto& ptr : instances) { - // getInstance() returns NULL if it's terminated/gone, non-NULL if - // it's still running - if (LLLeap::getInstance(*vli)) + if (! ptr.expired()) + { + found = true; break; + } } // If we made it through all of 'instances' without finding one that's // still running, we're done. - if (vli == vlend) + if (! found) { /*==========================================================================*| std::cout << instances.size() << " LLLeap instances terminated in " @@ -86,8 +90,8 @@ void waitfor(const std::vector& instances, int timeout=60) void waitfor(LLLeap* instance, int timeout=60) { - std::vector instances; - instances.push_back(instance); + LLLeapVector instances; + instances.push_back(instance->getWeak()); waitfor(instances, timeout); } @@ -218,11 +222,11 @@ namespace tut NamedTempFile script("py", "import time\n" "time.sleep(1)\n"); - std::vector instances; + LLLeapVector instances; instances.push_back(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + sv(list_of(PYTHON)(script.getName())))->getWeak()); instances.push_back(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + sv(list_of(PYTHON)(script.getName())))->getWeak()); // In this case we're simply establishing that two LLLeap instances // can coexist without throwing exceptions or bombing in any other // way. Wait for them to terminate. diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index c0f0cec80b..a24de45fde 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -2376,9 +2376,8 @@ void LLGLNamePool::release(GLuint name) //static void LLGLNamePool::upkeepPools() { - for (tracker_t::instance_iter iter = beginInstances(); iter != endInstances(); ++iter) + for (auto& pool : instance_snapshot()) { - LLGLNamePool & pool = *iter; pool.upkeep(); } } @@ -2386,9 +2385,8 @@ void LLGLNamePool::upkeepPools() //static void LLGLNamePool::cleanupPools() { - for (tracker_t::instance_iter iter = beginInstances(); iter != endInstances(); ++iter) + for (auto& pool : instance_snapshot()) { - LLGLNamePool & pool = *iter; pool.cleanup(); } } diff --git a/indra/llui/llconsole.cpp b/indra/llui/llconsole.cpp index 5f50e46233..7817d99aef 100644 --- a/indra/llui/llconsole.cpp +++ b/indra/llui/llconsole.cpp @@ -369,9 +369,9 @@ LLConsole::Paragraph::Paragraph (LLWString str, const LLColor4 &color, F32 add_t // static void LLConsole::updateClass() { - for (instance_iter it = beginInstances(); it != endInstances(); ++it) + for (auto& con : instance_snapshot()) { - it->update(); + con.update(); } } diff --git a/indra/llui/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp index 4a464b3507..4aae1e374b 100644 --- a/indra/llui/lllayoutstack.cpp +++ b/indra/llui/lllayoutstack.cpp @@ -636,10 +636,10 @@ void LLLayoutStack::createResizeBar(LLLayoutPanel* panelp) //static void LLLayoutStack::updateClass() { - for (instance_iter it = beginInstances(); it != endInstances(); ++it) + for (auto& layout : instance_snapshot()) { - it->updateLayout(); - it->mAnimatedThisFrame = false; + layout.updateLayout(); + layout.mAnimatedThisFrame = false; } } diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp index be26416cbb..e73ba1fbe9 100644 --- a/indra/llui/llnotificationslistener.cpp +++ b/indra/llui/llnotificationslistener.cpp @@ -127,18 +127,16 @@ void LLNotificationsListener::listChannels(const LLSD& params) const { LLReqID reqID(params); LLSD response(reqID.makeResponse()); - for (LLNotificationChannel::instance_iter cmi(LLNotificationChannel::beginInstances()), - cmend(LLNotificationChannel::endInstances()); - cmi != cmend; ++cmi) + for (auto& cm : LLNotificationChannel::instance_snapshot()) { LLSD channelInfo, parents; - BOOST_FOREACH(const std::string& parent, cmi->getParents()) + for (const std::string& parent : cm.getParents()) { parents.append(parent); } channelInfo["parents"] = parents; channelInfo["parent"] = parents.size()? parents[0] : ""; - response[cmi->getName()] = channelInfo; + response[cm.getName()] = channelInfo; } LLEventPumps::instance().obtain(params["reply"]).post(response); } diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp index 1b24250618..40e297bac1 100644 --- a/indra/llwindow/llwindow.cpp +++ b/indra/llwindow/llwindow.cpp @@ -457,9 +457,9 @@ LLCoordCommon LL_COORD_TYPE_WINDOW::convertToCommon() const { const LLCoordWindow& self = LLCoordWindow::getTypedCoords(*this); - LLWindow* windowp = &(*LLWindow::beginInstances()); + auto windowit = LLWindow::instance_snapshot().begin(); LLCoordGL out; - windowp->convertCoords(self, &out); + windowit->convertCoords(self, &out); return out.convert(); } @@ -467,18 +467,18 @@ void LL_COORD_TYPE_WINDOW::convertFromCommon(const LLCoordCommon& from) { LLCoordWindow& self = LLCoordWindow::getTypedCoords(*this); - LLWindow* windowp = &(*LLWindow::beginInstances()); + auto windowit = LLWindow::instance_snapshot().begin(); LLCoordGL from_gl(from); - windowp->convertCoords(from_gl, &self); + windowit->convertCoords(from_gl, &self); } LLCoordCommon LL_COORD_TYPE_SCREEN::convertToCommon() const { const LLCoordScreen& self = LLCoordScreen::getTypedCoords(*this); - LLWindow* windowp = &(*LLWindow::beginInstances()); + auto windowit = LLWindow::instance_snapshot().begin(); LLCoordGL out; - windowp->convertCoords(self, &out); + windowit->convertCoords(self, &out); return out.convert(); } @@ -486,7 +486,7 @@ void LL_COORD_TYPE_SCREEN::convertFromCommon(const LLCoordCommon& from) { LLCoordScreen& self = LLCoordScreen::getTypedCoords(*this); - LLWindow* windowp = &(*LLWindow::beginInstances()); + auto windowit = LLWindow::instance_snapshot().begin(); LLCoordGL from_gl(from); - windowp->convertCoords(from_gl, &self); + windowit->convertCoords(from_gl, &self); } diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h index de0d366492..39e1d3e615 100644 --- a/indra/llxml/llcontrol.h +++ b/indra/llxml/llcontrol.h @@ -200,8 +200,6 @@ public: LLControlGroup(const std::string& name); ~LLControlGroup(); void cleanup(); - - typedef LLInstanceTracker::instance_iter instance_iter; LLControlVariablePtr getControl(const std::string& name); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index ff897ef962..76305e6bdb 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1679,24 +1679,9 @@ bool LLAppViewer::cleanup() gDirUtilp->deleteFilesInDir(logdir, "*-*-*-*-*.dmp"); } - { - // Kill off LLLeap objects. We can find them all because LLLeap is derived - // from LLInstanceTracker. But collect instances first: LLInstanceTracker - // specifically forbids adding/deleting instances while iterating. - std::vector leaps; - leaps.reserve(LLLeap::instanceCount()); - for (LLLeap::instance_iter li(LLLeap::beginInstances()), lend(LLLeap::endInstances()); - li != lend; ++li) - { - leaps.push_back(&*li); - } - // Okay, now trash them all. We don't have to NULL or erase the entry - // in 'leaps' because the whole vector is going away momentarily. - BOOST_FOREACH(LLLeap* leap, leaps) - { - delete leap; - } - } // destroy 'leaps' + // Kill off LLLeap objects. We can find them all because LLLeap is derived + // from LLInstanceTracker. + LLLeap::instance_snapshot().deleteAll(); //flag all elements as needing to be destroyed immediately // to ensure shutdown order @@ -2840,12 +2825,11 @@ bool LLAppViewer::initConfiguration() // Let anyone else who cares know that we've populated our settings // variables. - for (LLControlGroup::key_iter ki(LLControlGroup::beginKeys()), kend(LLControlGroup::endKeys()); - ki != kend; ++ki) + for (const auto& key : LLControlGroup::key_snapshot()) { // For each named instance of LLControlGroup, send an event saying // we've initialized an LLControlGroup instance by that name. - LLEventPumps::instance().obtain("LLControlGroup").post(LLSDMap("init", *ki)); + LLEventPumps::instance().obtain("LLControlGroup").post(LLSDMap("init", key)); } return true; // Config was successful. diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index 1099d4bc09..4131af828e 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -1344,10 +1344,8 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL // We don't want multiple friendship offers to appear, this code checks if there are previous offers // by iterating though all panels. // Note: it might be better to simply add a "pending offer" flag somewhere - for (LLToastNotifyPanel::instance_iter ti(LLToastNotifyPanel::beginInstances()) - , tend(LLToastNotifyPanel::endInstances()); ti != tend; ++ti) + for (auto& panel : LLToastNotifyPanel::instance_snapshot()) { - LLToastNotifyPanel& panel = *ti; LLIMToastNotifyPanel * imtoastp = dynamic_cast(&panel); const std::string& notification_name = panel.getNotificationName(); if (notification_name == "OfferFriendship" diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp index 3606a439a6..83f920c70d 100644 --- a/indra/newview/llimprocessing.cpp +++ b/indra/newview/llimprocessing.cpp @@ -1387,10 +1387,8 @@ void LLIMProcessing::processNewMessage(LLUUID from_id, payload["sender"] = sender.getIPandPort(); bool add_notification = true; - for (LLToastNotifyPanel::instance_iter ti(LLToastNotifyPanel::beginInstances()) - , tend(LLToastNotifyPanel::endInstances()); ti != tend; ++ti) + for (auto& panel : LLToastNotifyPanel::instance_snapshot()) { - LLToastNotifyPanel& panel = *ti; const std::string& notification_name = panel.getNotificationName(); if (notification_name == "OfferFriendship" && panel.isControlPanelEnabled()) { diff --git a/indra/newview/llscenemonitor.cpp b/indra/newview/llscenemonitor.cpp index 5ab0013055..2c0c38dc75 100644 --- a/indra/newview/llscenemonitor.cpp +++ b/indra/newview/llscenemonitor.cpp @@ -559,16 +559,14 @@ void LLSceneMonitor::dumpToFile(std::string file_name) typedef StatType trace_count; - for (trace_count::instance_iter it = trace_count::beginInstances(), end_it = trace_count::endInstances(); - it != end_it; - ++it) + for (auto& it : trace_count::instance_snapshot()) { std::ostringstream row; row << std::setprecision(10); - row << it->getName(); + row << it.getName(); - const char* unit_label = it->getUnitLabel(); + const char* unit_label = it.getUnitLabel(); if(unit_label[0]) { row << "(" << unit_label << ")"; @@ -579,8 +577,8 @@ void LLSceneMonitor::dumpToFile(std::string file_name) for (S32 frame = 1; frame <= frame_count; frame++) { Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame); - samples += recording.getSampleCount(*it); - row << ", " << recording.getSum(*it); + samples += recording.getSampleCount(it); + row << ", " << recording.getSum(it); } row << '\n'; @@ -593,15 +591,13 @@ void LLSceneMonitor::dumpToFile(std::string file_name) typedef StatType trace_event; - for (trace_event::instance_iter it = trace_event::beginInstances(), end_it = trace_event::endInstances(); - it != end_it; - ++it) + for (auto& it : trace_event::instance_snapshot()) { std::ostringstream row; row << std::setprecision(10); - row << it->getName(); + row << it.getName(); - const char* unit_label = it->getUnitLabel(); + const char* unit_label = it.getUnitLabel(); if(unit_label[0]) { row << "(" << unit_label << ")"; @@ -612,8 +608,8 @@ void LLSceneMonitor::dumpToFile(std::string file_name) for (S32 frame = 1; frame <= frame_count; frame++) { Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame); - samples += recording.getSampleCount(*it); - F64 mean = recording.getMean(*it); + samples += recording.getSampleCount(it); + F64 mean = recording.getMean(it); if (llisnan(mean)) { row << ", n/a"; @@ -634,15 +630,13 @@ void LLSceneMonitor::dumpToFile(std::string file_name) typedef StatType trace_sample; - for (trace_sample::instance_iter it = trace_sample::beginInstances(), end_it = trace_sample::endInstances(); - it != end_it; - ++it) + for (auto& it : trace_sample::instance_snapshot()) { std::ostringstream row; row << std::setprecision(10); - row << it->getName(); + row << it.getName(); - const char* unit_label = it->getUnitLabel(); + const char* unit_label = it.getUnitLabel(); if(unit_label[0]) { row << "(" << unit_label << ")"; @@ -653,8 +647,8 @@ void LLSceneMonitor::dumpToFile(std::string file_name) for (S32 frame = 1; frame <= frame_count; frame++) { Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame); - samples += recording.getSampleCount(*it); - F64 mean = recording.getMean(*it); + samples += recording.getSampleCount(it); + F64 mean = recording.getMean(it); if (llisnan(mean)) { row << ", n/a"; @@ -674,15 +668,13 @@ void LLSceneMonitor::dumpToFile(std::string file_name) } typedef StatType trace_mem; - for (trace_mem::instance_iter it = trace_mem::beginInstances(), end_it = trace_mem::endInstances(); - it != end_it; - ++it) + for (auto& it : trace_mem::instance_snapshot()) { - os << it->getName() << "(KiB)"; + os << it.getName() << "(KiB)"; for (S32 frame = 1; frame <= frame_count; frame++) { - os << ", " << scene_load_recording.getPrevRecording(frame_count - frame).getMax(*it).valueInUnits(); + os << ", " << scene_load_recording.getPrevRecording(frame_count - frame).getMax(it).valueInUnits(); } os << '\n'; diff --git a/indra/newview/lltoast.cpp b/indra/newview/lltoast.cpp index 870e0d94f0..bf56a10d4d 100644 --- a/indra/newview/lltoast.cpp +++ b/indra/newview/lltoast.cpp @@ -612,11 +612,8 @@ S32 LLToast::notifyParent(const LLSD& info) //static void LLToast::updateClass() { - for (LLInstanceTracker::instance_iter iter = LLInstanceTracker::beginInstances(); - iter != LLInstanceTracker::endInstances(); ) + for (auto& toast : LLInstanceTracker::instance_snapshot()) { - LLToast& toast = *iter++; - toast.updateHoveredState(); } } @@ -624,22 +621,6 @@ void LLToast::updateClass() // static void LLToast::cleanupToasts() { - LLToast * toastp = NULL; - - while (LLInstanceTracker::instanceCount() > 0) - { - { // Need to scope iter to allow deletion - LLInstanceTracker::instance_iter iter = LLInstanceTracker::beginInstances(); - toastp = &(*iter); - } - - //LL_INFOS() << "Cleaning up toast id " << toastp->getNotificationID() << LL_ENDL; - - // LLToast destructor will remove it from the LLInstanceTracker. - if (!toastp) - break; // Don't get stuck in the loop if a null pointer somehow got on the list - - delete toastp; - } + LLInstanceTracker::instance_snapshot().deleteAll(); } diff --git a/indra/newview/llviewercontrollistener.cpp b/indra/newview/llviewercontrollistener.cpp index d2484b2b23..3443bb644a 100644 --- a/indra/newview/llviewercontrollistener.cpp +++ b/indra/newview/llviewercontrollistener.cpp @@ -50,11 +50,9 @@ LLViewerControlListener::LLViewerControlListener() std::ostringstream groupnames; groupnames << "[\"group\"] is one of "; const char* delim = ""; - for (LLControlGroup::key_iter cgki(LLControlGroup::beginKeys()), - cgkend(LLControlGroup::endKeys()); - cgki != cgkend; ++cgki) + for (const auto& key : LLControlGroup::key_snapshot()) { - groupnames << delim << '"' << *cgki << '"'; + groupnames << delim << '"' << key << '"'; delim = ", "; } groupnames << '\n'; @@ -181,11 +179,9 @@ void LLViewerControlListener::groups(LLSD const & request) { // No Info, we're not looking up either a group or a control name. Response response(LLSD(), request); - for (LLControlGroup::key_iter cgki(LLControlGroup::beginKeys()), - cgkend(LLControlGroup::endKeys()); - cgki != cgkend; ++cgki) + for (const auto& key : LLControlGroup::key_snapshot()) { - response["groups"].append(*cgki); + response["groups"].append(key); } } From b22c41c8c547163c432378b75028fe04f6526b58 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 2 Dec 2019 15:40:02 -0500 Subject: [PATCH 02/48] DRTVWR-494: Quiet VS warnings about its own header. --- indra/llcommon/llinstancetracker.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 76b201ad8c..3c8a5e3fb6 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -32,10 +32,20 @@ #include #include #include -#include #include #include +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable:4265) +#endif +// 'std::_Pad' : class has virtual functions, but destructor is not virtual +#include + +#if LL_WINDOWS +#pragma warning (pop) +#endif + #include #include #include From a9cf349de4fea24d0cc0ad659bde7327ec623005 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 23 Nov 2019 22:18:45 -0500 Subject: [PATCH 03/48] DRTVWR-494: Improve thread safety of LLSingleton machinery. Remove warnings about LLSingleton not being thread-safe because, at this point, we have devoted considerable effort to trying to make it thread-safe. Add LLSingleton::Locker, a nested class which both provides a function- static mutex and a scoped lock that uses it. Instantiating Locker, which has a nullary constructor, replaces the somewhat cumbersome idiom of declaring a std::unique_lock lk(getMutex); This eliminates (or rather, absorbs) the typedefs and getMutex() method from LLParamSingleton. Replace explicit std::unique_lock declarations in LLParamSingleton methods with Locker declarations. Remove LLSingleton::SingletonInitializer nested struct. Instead of getInstance() relying on function-static initialization to protect (only) constructSingleton() calls, explicitly use a Locker instance to cover its whole scope, and make the UNINITIALIZED case call constructSingleton(). Rearrange cases so that after constructSingleton(), control falls through to the CONSTRUCTED case and the finishInitializing() call. Use a Locker instance in other public-facing methods too: instanceExists(), wasDeleted(), ~LLSingleton(). Make destructor protected so it can only be called via deleteSingleton() (but must be accessible to subclasses for overrides). Remove LLSingletonBase::get_master() and get_initializing(), which permitted directly manipulating the master list and the initializing stack without any locking mechanism. Replace with get_initializing_size(). Similarly, replace LLSingleton_manage_master::get_initializing() with get_initializing_size(). Use in constructSingleton() in place of get_initializing().size(). Remove LLSingletonBase::capture_dependency()'s list_t parameter, which accepted the list returned by get_initializing(). Encapsulate that retrieval within the scope of the new lock in capture_dependency(). Add LLSingleton_manage_master::capture_dependency(LLSingletonBase*, EInitState) to forward (or not) a call to LLSingletonBase::capture_dependency(). Nullary LLSingleton::capture_dependency() calls new LLSingleton_manage_master method. Equip LLSingletonBase::MasterList with a mutex of its own, separate from the one donated by the LLSingleton machinery, to serialize use of MasterList data members. Introduce MasterList::Lock nested class to lock the MasterList mutex while providing a reference to the MasterList instance. Introduce subclasses LockedMaster, which provides a reference to the actual mMaster master list while holding the MasterList lock; and LockedInitializing, which does the same for the initializing list. Make mMaster and get_initializing_() private so that consuming code can *only* access those lists via LockedInitializing and LockedMaster. Make MasterList::cleanup_initializing_() private, with a LockedInitializing public forwarding method. This avoids another call to MasterList::instance(), and also mandates that the lock is currently held during every call. Similarly, move LLSingletonBase::log_initializing() to a LockedInitializing log() method. (transplanted from dca0f16266c7bddedb51ae7d7dca468ba87060d5) --- indra/llcommon/llsingleton.cpp | 151 +++++++++++++++++++++++------- indra/llcommon/llsingleton.h | 164 ++++++++++++++++----------------- 2 files changed, 197 insertions(+), 118 deletions(-) diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index c45c144570..812fd31719 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -31,6 +31,7 @@ #include "llerrorcontrol.h" // LLError::is_available() #include "lldependencies.h" #include "llcoro_get_id.h" +#include "llexception.h" #include #include #include @@ -57,17 +58,59 @@ bool oktolog(); class LLSingletonBase::MasterList: public LLSingleton { +private: LLSINGLETON_EMPTY_CTOR(MasterList); -public: - // No need to make this private with accessors; nobody outside this source - // file can see it. + // Independently of the LLSingleton locks governing construction, + // destruction and other state changes of the MasterList instance itself, + // we must also defend each of the data structures owned by the + // MasterList. + // This must be a recursive_mutex because, while the lock is held for + // manipulating some data in the master list, we must also check whether + // it's safe to log -- which involves querying a different LLSingleton -- + // which requires accessing the master list. + typedef std::recursive_mutex mutex_t; + typedef std::unique_lock lock_t; + mutex_t mMutex; + +public: + // Instantiate this to both obtain a reference to MasterList::instance() + // and lock its mutex for the lifespan of this Lock instance. + class Lock + { + public: + Lock(): + mMasterList(MasterList::instance()), + mLock(mMasterList.mMutex) + {} + Lock(const Lock&) = delete; + Lock& operator=(const Lock&) = delete; + MasterList& get() const { return mMasterList; } + operator MasterList&() const { return get(); } + + protected: + MasterList& mMasterList; + MasterList::lock_t mLock; + }; + +private: // This is the master list of all instantiated LLSingletons (save the // MasterList itself) in arbitrary order. You MUST call dep_sort() before // traversing this list. - LLSingletonBase::list_t mMaster; + list_t mMaster; +public: + // Instantiate this to obtain a reference to MasterList::mMaster and to + // hold the MasterList lock for the lifespan of this LockedMaster + // instance. + struct LockedMaster: public Lock + { + list_t& get() const { return mMasterList.mMaster; } + operator list_t&() const { return get(); } + }; + +private: // We need to maintain a stack of LLSingletons currently being // initialized, either in the constructor or in initSingleton(). However, // managing that as a stack depends on having a DISTINCT 'initializing' @@ -83,10 +126,44 @@ public: // Instead, use a map of llcoro::id to select the appropriate // coro-specific 'initializing' stack. llcoro::get_id() is carefully // implemented to avoid requiring LLCoros. - typedef boost::unordered_map InitializingMap; + typedef boost::unordered_map InitializingMap; InitializingMap mInitializing; - // non-static method, cf. LLSingletonBase::get_initializing() +public: + // Instantiate this to obtain a reference to the coroutine-specific + // initializing list and to hold the MasterList lock for the lifespan of + // this LockedInitializing instance. + struct LockedInitializing: public Lock + { + public: + LockedInitializing(): + // only do the lookup once, cache the result + // note that the lock is already locked during this lookup + mList(&mMasterList.get_initializing_()) + {} + list_t& get() const + { + if (! mList) + { + LLTHROW(std::runtime_error("Trying to use LockedInitializing " + "after cleanup_initializing()")); + } + return *mList; + } + operator list_t&() const { return get(); } + void log(const char* verb, const char* name); + void cleanup_initializing() + { + mMasterList.cleanup_initializing_(); + mList = nullptr; + } + + private: + // Store pointer since cleanup_initializing() must clear it. + list_t* mList; + }; + +private: list_t& get_initializing_() { // map::operator[] has find-or-create semantics, exactly what we need @@ -104,16 +181,12 @@ public: } }; -//static -LLSingletonBase::list_t& LLSingletonBase::get_master() -{ - return LLSingletonBase::MasterList::instance().mMaster; -} - void LLSingletonBase::add_master() { // As each new LLSingleton is constructed, add to the master list. - get_master().push_back(this); + // This temporary LockedMaster should suffice to hold the MasterList lock + // during the push_back() call. + MasterList::LockedMaster().get().push_back(this); } void LLSingletonBase::remove_master() @@ -125,27 +198,32 @@ void LLSingletonBase::remove_master() // master list, and remove this item IF FOUND. We have few enough // LLSingletons, and they are so rarely destroyed (once per run), that the // cost of a linear search should not be an issue. - get_master().remove(this); + // This temporary LockedMaster should suffice to hold the MasterList lock + // during the remove() call. + MasterList::LockedMaster().get().remove(this); } //static -LLSingletonBase::list_t& LLSingletonBase::get_initializing() +LLSingletonBase::list_t::size_type LLSingletonBase::get_initializing_size() { - return LLSingletonBase::MasterList::instance().get_initializing_(); + return MasterList::LockedInitializing().get().size(); } LLSingletonBase::~LLSingletonBase() {} void LLSingletonBase::push_initializing(const char* name) { + MasterList::LockedInitializing locked_list; // log BEFORE pushing so logging singletons don't cry circularity - log_initializing("Pushing", name); - get_initializing().push_back(this); + locked_list.log("Pushing", name); + locked_list.get().push_back(this); } void LLSingletonBase::pop_initializing() { - list_t& list(get_initializing()); + // Lock the MasterList for the duration of this call + MasterList::LockedInitializing locked_list; + list_t& list(locked_list.get()); if (list.empty()) { @@ -165,7 +243,7 @@ void LLSingletonBase::pop_initializing() // entirely. if (list.empty()) { - MasterList::instance().cleanup_initializing_(); + locked_list.cleanup_initializing(); } // Now validate the newly-popped LLSingleton. @@ -177,7 +255,7 @@ void LLSingletonBase::pop_initializing() } // log AFTER popping so logging singletons don't cry circularity - log_initializing("Popping", typeid(*back).name()); + locked_list.log("Popping", typeid(*back).name()); } void LLSingletonBase::reset_initializing(list_t::size_type size) @@ -191,7 +269,8 @@ void LLSingletonBase::reset_initializing(list_t::size_type size) // push_initializing() call in LLSingletonBase's constructor. So only // remove the stack top if in fact we've pushed something more than the // previous size. - list_t& list(get_initializing()); + MasterList::LockedInitializing locked_list; + list_t& list(locked_list.get()); while (list.size() > size) { @@ -201,29 +280,32 @@ void LLSingletonBase::reset_initializing(list_t::size_type size) // as in pop_initializing() if (list.empty()) { - MasterList::instance().cleanup_initializing_(); + locked_list.cleanup_initializing(); } } -//static -void LLSingletonBase::log_initializing(const char* verb, const char* name) +void LLSingletonBase::MasterList::LockedInitializing::log(const char* verb, const char* name) { if (oktolog()) { LL_DEBUGS("LLSingleton") << verb << ' ' << demangle(name) << ';'; - list_t& list(get_initializing()); - for (list_t::const_reverse_iterator ri(list.rbegin()), rend(list.rend()); - ri != rend; ++ri) + if (mList) { - LLSingletonBase* sb(*ri); - LL_CONT << ' ' << classname(sb); + for (list_t::const_reverse_iterator ri(mList->rbegin()), rend(mList->rend()); + ri != rend; ++ri) + { + LLSingletonBase* sb(*ri); + LL_CONT << ' ' << classname(sb); + } } LL_ENDL; } } -void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initState) +void LLSingletonBase::capture_dependency(EInitState initState) { + MasterList::LockedInitializing locked_list; + list_t& initializing(locked_list.get()); // Did this getInstance() call come from another LLSingleton, or from // vanilla application code? Note that although this is a nontrivial // method, the vast majority of its calls arrive here with initializing @@ -313,8 +395,9 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort() // deleteAll(). typedef LLDependencies SingletonDeps; SingletonDeps sdeps; - list_t& master(get_master()); - BOOST_FOREACH(LLSingletonBase* sp, master) + // Lock while traversing the master list + MasterList::LockedMaster master; + BOOST_FOREACH(LLSingletonBase* sp, master.get()) { // Build the SingletonDeps structure by adding, for each // LLSingletonBase* sp in the master list, sp itself. It has no @@ -326,7 +409,7 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort() SingletonDeps::KeyList(sp->mDepends.begin(), sp->mDepends.end())); } vec_t ret; - ret.reserve(master.size()); + ret.reserve(master.get().size()); // We should be able to effect this with a transform_iterator that // extracts just the first (key) element from each sorted_iterator, then // uses vec_t's range constructor... but frankly this is more diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 0da6d548ab..8dec8bfb3b 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -51,10 +51,9 @@ public: private: // All existing LLSingleton instances are tracked in this master list. typedef std::list list_t; - static list_t& get_master(); - // This, on the other hand, is a stack whose top indicates the LLSingleton - // currently being initialized. - static list_t& get_initializing(); + // Size of stack whose top indicates the LLSingleton currently being + // initialized. + static list_t::size_type get_initializing_size(); // Produce a vector of master list, in dependency order. typedef std::vector vec_t; static vec_t dep_sort(); @@ -115,13 +114,10 @@ protected: // Remove 'this' from the init stack in case of exception in the // LLSingleton subclass constructor. static void reset_initializing(list_t::size_type size); -private: - // logging - static void log_initializing(const char* verb, const char* name); protected: // If a given call to B::getInstance() happens during either A::A() or // A::initSingleton(), record that A directly depends on B. - void capture_dependency(list_t& initializing, EInitState); + void capture_dependency(EInitState); // delegate LL_ERRS() logging to llsingleton.cpp static void logerrs(const char* p1, const char* p2="", @@ -203,9 +199,16 @@ struct LLSingleton_manage_master { LLSingletonBase::reset_initializing(size); } - // For any LLSingleton subclass except the MasterList, obtain the init - // stack from the MasterList singleton instance. - LLSingletonBase::list_t& get_initializing() { return LLSingletonBase::get_initializing(); } + // For any LLSingleton subclass except the MasterList, obtain the size of + // the init stack from the MasterList singleton instance. + LLSingletonBase::list_t::size_type get_initializing_size() + { + return LLSingletonBase::get_initializing_size(); + } + void capture_dependency(LLSingletonBase* sb, LLSingletonBase::EInitState state) + { + sb->capture_dependency(state); + } }; // But for the specific case of LLSingletonBase::MasterList, don't. @@ -218,13 +221,8 @@ struct LLSingleton_manage_master void pop_initializing (LLSingletonBase*) {} // since we never pushed, no need to clean up void reset_initializing(LLSingletonBase::list_t::size_type size) {} - LLSingletonBase::list_t& get_initializing() - { - // The MasterList shouldn't depend on any other LLSingletons. We'd - // get into trouble if we tried to recursively engage that machinery. - static LLSingletonBase::list_t sDummyList; - return sDummyList; - } + LLSingletonBase::list_t::size_type get_initializing_size() { return 0; } + void capture_dependency(LLSingletonBase*, LLSingletonBase::EInitState) {} }; // Now we can implement LLSingletonBase's template constructor. @@ -304,8 +302,6 @@ class LLParamSingleton; * remaining LLSingleton instances will be destroyed in dependency order. (Or * call MySubclass::deleteSingleton() to specifically destroy the canonical * MySubclass instance.) - * - * As currently written, LLSingleton is not thread-safe. */ template class LLSingleton : public LLSingletonBase @@ -315,6 +311,47 @@ private: // access our private members. friend class LLParamSingleton; + // Scoped lock on the mutex associated with this LLSingleton + class Locker + { + public: + Locker(): mLock(getMutex()) {} + + private: + // Use a recursive_mutex in case of constructor circularity. With a + // non-recursive mutex, that would result in deadlock. + typedef std::recursive_mutex mutex_t; + + // LLSingleton must have a distinct instance of sMutex for every + // distinct T. It's tempting to consider hoisting Locker up into + // LLSingletonBase. Don't do it. + // + // sMutex must be a function-local static rather than a static member. One + // of the essential features of LLSingleton and friends is that they must + // support getInstance() even when the containing module's static + // variables have not yet been runtime-initialized. A mutex requires + // construction. A static class member might not yet have been + // constructed. + // + // We could store a dumb mutex_t*, notice when it's NULL and allocate a + // heap mutex -- but that's vulnerable to race conditions. And we can't + // defend the dumb pointer with another mutex. + // + // We could store a std::atomic -- but a default-constructed + // std::atomic does not contain a valid T, even a default-constructed + // T! Which means std::atomic, too, requires runtime initialization. + // + // But a function-local static is guaranteed to be initialized exactly + // once, the first time control reaches that declaration. + static mutex_t& getMutex() + { + static mutex_t sMutex; + return sMutex; + } + + std::unique_lock mLock; + }; + // LLSingleton only supports a nullary constructor. However, the specific // purpose for its subclass LLParamSingleton is to support Singletons // requiring constructor arguments. constructSingleton() supports both use @@ -322,7 +359,7 @@ private: template static void constructSingleton(Args&&... args) { - auto prev_size = LLSingleton_manage_master().get_initializing().size(); + auto prev_size = LLSingleton_manage_master().get_initializing_size(); // getInstance() calls are from within constructor sData.mInitState = CONSTRUCTING; try @@ -386,9 +423,6 @@ private: LLSingleton_manage_master().pop_initializing(sData.mInstance); } - // Without this 'using' declaration, the static method we're declaring - // here would hide the base-class method we want it to call. - using LLSingletonBase::capture_dependency; static void capture_dependency() { // By this point, if DERIVED_TYPE was pushed onto the initializing @@ -396,9 +430,8 @@ private: // an LLSingleton that directly depends on DERIVED_TYPE. If // getInstance() was called by another LLSingleton, rather than from // vanilla application code, record the dependency. - sData.mInstance->capture_dependency( - LLSingleton_manage_master().get_initializing(), - sData.mInitState); + LLSingleton_manage_master().capture_dependency( + sData.mInstance, sData.mInitState); } // We know of no way to instruct the compiler that every subclass @@ -411,20 +444,6 @@ private: // subclass body. virtual void you_must_use_LLSINGLETON_macro() = 0; - // The purpose of this struct is to engage the C++11 guarantee that static - // variables declared in function scope are initialized exactly once, even - // if multiple threads concurrently reach the same declaration. - // https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables - // Since getInstance() declares a static instance of SingletonInitializer, - // only the first call to getInstance() calls constructSingleton(). - struct SingletonInitializer - { - SingletonInitializer() - { - constructSingleton(); - } - }; - protected: // Pass DERIVED_TYPE explicitly to LLSingletonBase's constructor because, // until our subclass constructor completes, *this isn't yet a @@ -439,15 +458,20 @@ protected: LLSingleton_manage_master().add(this); } -public: +protected: virtual ~LLSingleton() { + // In case racing threads call getInstance() at the same moment as + // this destructor, serialize the calls. + Locker lk; + // remove this instance from the master list LLSingleton_manage_master().remove(this); sData.mInstance = NULL; sData.mInitState = DELETED; } +public: /** * @brief Immediately delete the singleton. * @@ -477,17 +501,12 @@ public: static DERIVED_TYPE* getInstance() { - // call constructSingleton() only the first time we get here - static SingletonInitializer sInitializer; + // In case racing threads call getInstance() at the same moment, + // serialize the calls. + Locker lk; switch (sData.mInitState) { - case UNINITIALIZED: - // should never be uninitialized at this point - logerrs("Uninitialized singleton ", - classname().c_str()); - return NULL; - case CONSTRUCTING: // here if DERIVED_TYPE's constructor (directly or indirectly) // calls DERIVED_TYPE::getInstance() @@ -496,9 +515,11 @@ public: " from singleton constructor!"); return NULL; + case UNINITIALIZED: + constructSingleton(); + // fall through... + case CONSTRUCTED: - // first time through: set to CONSTRUCTED by - // constructSingleton(), called by sInitializer's constructor; // still have to call initSingleton() finishInitializing(); break; @@ -515,8 +536,6 @@ public: logwarns("Trying to access deleted singleton ", classname().c_str(), " -- creating new instance"); - // This recovery sequence is NOT thread-safe! We would need a - // recursive_mutex a la LLParamSingleton. constructSingleton(); finishInitializing(); break; @@ -539,6 +558,8 @@ public: // Use this to avoid accessing singletons before they can safely be constructed. static bool instanceExists() { + // defend any access to sData from racing threads + Locker lk; return sData.mInitState == INITIALIZED; } @@ -547,6 +568,8 @@ public: // cleaned up. static bool wasDeleted() { + // defend any access to sData from racing threads + Locker lk; return sData.mInitState == DELETED; } @@ -588,10 +611,7 @@ class LLParamSingleton : public LLSingleton { private: typedef LLSingleton super; - // Use a recursive_mutex in case of constructor circularity. With a - // non-recursive mutex, that would result in deadlock rather than the - // logerrs() call in getInstance(). - typedef std::recursive_mutex mutex_t; + using typename super::Locker; public: using super::deleteSingleton; @@ -605,7 +625,7 @@ public: // In case racing threads both call initParamSingleton() at the same // time, serialize them. One should initialize; the other should see // mInitState already set. - std::unique_lock lk(getMutex()); + Locker lk; // For organizational purposes this function shouldn't be called twice if (super::sData.mInitState != super::UNINITIALIZED) { @@ -624,7 +644,7 @@ public: { // In case racing threads call getInstance() at the same moment as // initParamSingleton(), serialize the calls. - std::unique_lock lk(getMutex()); + Locker lk; switch (super::sData.mInitState) { @@ -677,30 +697,6 @@ public: { return *getInstance(); } - -private: - // sMutex must be a function-local static rather than a static member. One - // of the essential features of LLSingleton and friends is that they must - // support getInstance() even when the containing module's static - // variables have not yet been runtime-initialized. A mutex requires - // construction. A static class member might not yet have been - // constructed. - // - // We could store a dumb mutex_t*, notice when it's NULL and allocate a - // heap mutex -- but that's vulnerable to race conditions. And we can't - // defend the dumb pointer with another mutex. - // - // We could store a std::atomic -- but a default-constructed - // std::atomic does not contain a valid T, even a default-constructed - // T! Which means std::atomic, too, requires runtime initialization. - // - // But a function-local static is guaranteed to be initialized exactly - // once, the first time control reaches that declaration. - static mutex_t& getMutex() - { - static mutex_t sMutex; - return sMutex; - } }; /** From 4a861a146671782636cdf648171e3c18f78fe08f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Dec 2019 11:33:55 -0500 Subject: [PATCH 04/48] DRTVWR-494: Add on_main_thread(), sibling to assert_main_thread(). --- indra/llcommon/llthread.cpp | 28 ++++++++++++++++++++++++---- indra/llcommon/llthread.h | 3 +-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index a4171729db..f875e4e0dc 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -97,14 +97,34 @@ U32 LL_THREAD_LOCAL sThreadID = 0; U32 LLThread::sIDIter = 0; +namespace +{ + + U32 main_thread() + { + // Using a function-static variable to identify the main thread + // requires that control reach here from the main thread before it + // reaches here from any other thread. We simply trust that whichever + // thread gets here first is the main thread. + static U32 s_thread_id = LLThread::currentID(); + return s_thread_id; + } + +} // anonymous namespace + +LL_COMMON_API bool on_main_thread() +{ + return (LLThread::currentID() == main_thread()); +} LL_COMMON_API void assert_main_thread() { - static U32 s_thread_id = LLThread::currentID(); - if (LLThread::currentID() != s_thread_id) + auto curr = LLThread::currentID(); + auto main = main_thread(); + if (curr != main) { - LL_WARNS() << "Illegal execution from thread id " << (S32) LLThread::currentID() - << " outside main thread " << (S32) s_thread_id << LL_ENDL; + LL_WARNS() << "Illegal execution from thread id " << curr + << " outside main thread " << main << LL_ENDL; } } diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 863c9051f3..37f6e66bbb 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -34,8 +34,6 @@ #include "llrefcount.h" #include -LL_COMMON_API void assert_main_thread(); - namespace LLTrace { class ThreadRecorder; @@ -168,5 +166,6 @@ public: //============================================================================ extern LL_COMMON_API void assert_main_thread(); +extern LL_COMMON_API bool on_main_thread(); #endif // LL_LLTHREAD_H From dd73274c8a8b80490f7ce39d732d71b3cddbc68f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Dec 2019 11:36:04 -0500 Subject: [PATCH 05/48] DRTVWR-494: Streamline LLEventTimer::updateClass(). No need to capture a separate list of completed LLEventTimer instances to delete after the primary loop, since at this point we're looping over a snapshot and can directly delete each completed timer. --- indra/llcommon/lleventtimer.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/indra/llcommon/lleventtimer.cpp b/indra/llcommon/lleventtimer.cpp index 3986dee3ac..f575a7b6bf 100644 --- a/indra/llcommon/lleventtimer.cpp +++ b/indra/llcommon/lleventtimer.cpp @@ -57,7 +57,6 @@ LLEventTimer::~LLEventTimer() //static void LLEventTimer::updateClass() { - std::list completed_timers; for (auto& timer : instance_snapshot()) { F32 et = timer.mEventTimer.getElapsedTimeF32(); @@ -65,20 +64,10 @@ void LLEventTimer::updateClass() timer.mEventTimer.reset(); if ( timer.tick() ) { - completed_timers.push_back( &timer ); + delete &timer; } } } - - if ( completed_timers.size() > 0 ) - { - for (std::list::iterator completed_iter = completed_timers.begin(); - completed_iter != completed_timers.end(); - completed_iter++ ) - { - delete *completed_iter; - } - } } From ce7a04f30e77e8e00c1bbfdc7ce8c33c1731f910 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Dec 2019 11:45:14 -0500 Subject: [PATCH 06/48] DRTVWR-494: Encapsulate redundant VS boilerplate around . --- indra/llcommon/llapr.h | 12 +----------- indra/llcommon/llinstancetracker.h | 11 +---------- indra/llcommon/llmutex.h | 11 +---------- indra/llcommon/llsingleton.h | 13 +------------ indra/llcommon/llthreadsafequeue.h | 12 +----------- indra/llcommon/mutex.h | 22 ++++++++++++++++++++++ 6 files changed, 27 insertions(+), 54 deletions(-) create mode 100644 indra/llcommon/mutex.h diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index da50dda103..3c07976f42 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -41,17 +41,7 @@ #include "llstring.h" -#if LL_WINDOWS -#pragma warning (push) -#pragma warning (disable:4265) -#endif -// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual - -#include - -#if LL_WINDOWS -#pragma warning (pop) -#endif +#include "mutex.h" struct apr_dso_handle_t; /** diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 3c8a5e3fb6..272ad8086e 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -35,16 +35,7 @@ #include #include -#if LL_WINDOWS -#pragma warning (push) -#pragma warning (disable:4265) -#endif -// 'std::_Pad' : class has virtual functions, but destructor is not virtual -#include - -#if LL_WINDOWS -#pragma warning (pop) -#endif +#include "mutex.h" #include #include diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h index f841d7f950..1a93c048b6 100644 --- a/indra/llcommon/llmutex.h +++ b/indra/llcommon/llmutex.h @@ -30,18 +30,9 @@ #include "stdtypes.h" #include -#if LL_WINDOWS -#pragma warning (push) -#pragma warning (disable:4265) -#endif -// 'std::_Pad' : class has virtual functions, but destructor is not virtual -#include +#include "mutex.h" #include -#if LL_WINDOWS -#pragma warning (pop) -#endif - //============================================================================ #define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO) diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 8dec8bfb3b..4efffde43a 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -30,18 +30,7 @@ #include #include #include - -#if LL_WINDOWS -#pragma warning (push) -#pragma warning (disable:4265) -#endif -// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual - -#include - -#if LL_WINDOWS -#pragma warning (pop) -#endif +#include "mutex.h" class LLSingletonBase: private boost::noncopyable { diff --git a/indra/llcommon/llthreadsafequeue.h b/indra/llcommon/llthreadsafequeue.h index b0bddac8e5..2cee7a3141 100644 --- a/indra/llcommon/llthreadsafequeue.h +++ b/indra/llcommon/llthreadsafequeue.h @@ -30,19 +30,9 @@ #include "llexception.h" #include #include - -#if LL_WINDOWS -#pragma warning (push) -#pragma warning (disable:4265) -#endif -// 'std::_Pad' : class has virtual functions, but destructor is not virtual -#include +#include "mutex.h" #include -#if LL_WINDOWS -#pragma warning (pop) -#endif - // // A general queue exception. // diff --git a/indra/llcommon/mutex.h b/indra/llcommon/mutex.h new file mode 100644 index 0000000000..90d0942270 --- /dev/null +++ b/indra/llcommon/mutex.h @@ -0,0 +1,22 @@ +/** + * @file mutex.h + * @author Nat Goodspeed + * @date 2019-12-03 + * @brief Wrap in odious boilerplate + * + * $LicenseInfo:firstyear=2019&license=viewerlgpl$ + * Copyright (c) 2019, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable:4265) +#endif +// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual + +#include + +#if LL_WINDOWS +#pragma warning (pop) +#endif From 291220c46e1cfbb83bcd93ac482080b84bd2f530 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Dec 2019 12:49:18 -0500 Subject: [PATCH 07/48] DRTVWR-494: Streamline LLSingleton state machine. The CONSTRUCTED state was only briefly set between constructSingleton() and finishInitializing(). But as no consumer code is executed between setting CONSTRUCTED and setting INITIALIZING, it was impossible to reach the switch statement in either getInstance() method in state CONSTRUCTED. So there was no point in state CONSTRUCTED. Remove it. With CONSTRUCTED gone, we only ever call finishInitializing() right after constructSingleton(). Merge finishInitializing() into constructSingleton(). --- indra/llcommon/llsingleton.h | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 4efffde43a..ebae601029 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -57,7 +57,6 @@ protected: { UNINITIALIZED = 0, // must be default-initialized state CONSTRUCTING, // within DERIVED_TYPE constructor - CONSTRUCTED, // finished DERIVED_TYPE constructor INITIALIZING, // within DERIVED_TYPE::initSingleton() INITIALIZED, // normal case DELETED // deleteSingleton() or deleteAll() called @@ -355,7 +354,6 @@ private: { sData.mInstance = new DERIVED_TYPE(std::forward(args)...); // we have called constructor, have not yet called initSingleton() - sData.mInitState = CONSTRUCTED; } catch (const std::exception& err) { @@ -373,10 +371,7 @@ private: // propagate the exception throw; } - } - static void finishInitializing() - { // getInstance() calls are from within initSingleton() sData.mInitState = INITIALIZING; try @@ -506,11 +501,6 @@ public: case UNINITIALIZED: constructSingleton(); - // fall through... - - case CONSTRUCTED: - // still have to call initSingleton() - finishInitializing(); break; case INITIALIZING: @@ -526,7 +516,6 @@ public: classname().c_str(), " -- creating new instance"); constructSingleton(); - finishInitializing(); break; } @@ -625,7 +614,6 @@ public: else { super::constructSingleton(std::forward(args)...); - super::finishInitializing(); } } @@ -648,18 +636,6 @@ public: " from singleton constructor!"); break; - case super::CONSTRUCTED: - // Should never happen!? The CONSTRUCTED state is specifically to - // navigate through LLSingleton::SingletonInitializer getting - // constructed (once) before LLSingleton::getInstance()'s switch - // on mInitState. But our initParamSingleton() method calls - // constructSingleton() and then calls finishInitializing(), which - // immediately sets INITIALIZING. Why are we here? - super::logerrs("Param singleton ", - super::template classname().c_str(), - "::initSingleton() not yet called"); - break; - case super::INITIALIZING: // As with LLSingleton, explicitly permit circular calls from // within initSingleton() From 2054057b54e27ae91a6542b4e9a3646c5a9f8240 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Dec 2019 20:44:26 -0500 Subject: [PATCH 08/48] DRTVWR-494: Extract LockStatic as a standalone template class. The pattern of requiring a lock to permit *any* access to a static instance of something seems generally useful. Break out lockstatic.h; recast LLInstanceTracker to use it. Moving LockStatic to an external template class instead of a nested class in LLInstanceTrackerBase leaves LLInstanceTrackerBase pretty empty. Get rid of it. And *that* means we can move the definition of the StaticData used by each LLInstanceTracker specialization into the class itself, rather than having to define it beforehand in namespace LLInstanceTrackerStuff. --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/llinstancetracker.h | 98 ++++++------------------------ indra/llcommon/lockstatic.h | 56 +++++++++++++++++ 3 files changed, 75 insertions(+), 80 deletions(-) create mode 100644 indra/llcommon/lockstatic.h diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index af41b9e460..98e1c00ce3 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -247,6 +247,7 @@ set(llcommon_HEADER_FILES llwin32headers.h llwin32headerslean.h llworkerthread.h + lockstatic.h stdtypes.h stringize.h timer.h diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 272ad8086e..cfb40c25f0 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -41,64 +41,20 @@ #include #include +#include "lockstatic.h" + /***************************************************************************** -* LLInstanceTrackerBase +* StaticBase *****************************************************************************/ -/** - * Base class manages "class-static" data that must actually have singleton - * semantics: one instance per process, rather than one instance per module as - * sometimes happens with data simply declared static. - */ namespace LLInstanceTrackerStuff { struct StaticBase { // We need to be able to lock static data while manipulating it. - typedef std::mutex mutex_t; - mutex_t mMutex; + std::mutex mMutex; }; } // namespace LLInstanceTrackerStuff -template -class LL_COMMON_API LLInstanceTrackerBase -{ -protected: - typedef Static StaticData; - - // Instantiate this class to obtain a pointer to the canonical static - // instance of class Static while holding a lock on that instance. Use of - // Static::mMutex presumes either that Static is derived from StaticBase, - // or that Static declares some other suitable mMutex. - class LockStatic - { - typedef std::unique_lock lock_t; - public: - LockStatic(): - mData(getStatic()), - mLock(mData->mMutex) - {} - Static* get() const { return mData; } - operator Static*() const { return get(); } - Static* operator->() const { return get(); } - // sometimes we must explicitly unlock... - void unlock() - { - // but once we do, access is no longer permitted - mData = nullptr; - mLock.unlock(); - } - protected: - Static* mData; - lock_t mLock; - private: - Static* getStatic() - { - static Static sData; - return &sData; - } - }; -}; - /***************************************************************************** * LLInstanceTracker with key *****************************************************************************/ @@ -108,29 +64,20 @@ enum EInstanceTrackerAllowKeyCollisions LLInstanceTrackerReplaceOnCollision }; -namespace LLInstanceTrackerStuff -{ - template - struct StaticMap: public StaticBase - { - typedef std::map InstanceMap; - InstanceMap mMap; - }; -} // LLInstanceTrackerStuff - /// This mix-in class adds support for tracking all instances of the specified class parameter T /// The (optional) key associates a value of type KEY with a given instance of T, for quick lookup /// If KEY is not provided, then instances are stored in a simple set /// @NOTE: see explicit specialization below for default KEY==void case template -class LLInstanceTracker : - public LLInstanceTrackerBase>> +class LLInstanceTracker { - typedef LLInstanceTrackerBase>> super; - using typename super::StaticData; - using typename super::LockStatic; - typedef typename StaticData::InstanceMap InstanceMap; + typedef std::map> InstanceMap; + struct StaticData: public LLInstanceTrackerStuff::StaticBase + { + InstanceMap mMap; + }; + typedef llthread::LockStatic LockStatic; public: // snapshot of std::pair> pairs @@ -334,16 +281,6 @@ private: /***************************************************************************** * LLInstanceTracker without key *****************************************************************************/ -namespace LLInstanceTrackerStuff -{ - template - struct StaticSet: public StaticBase - { - typedef std::set InstanceSet; - InstanceSet mSet; - }; -} // LLInstanceTrackerStuff - // TODO: // - For the case of omitted KEY template parameter, consider storing // std::map> instead of std::set>. @@ -359,13 +296,14 @@ namespace LLInstanceTrackerStuff /// explicit specialization for default case where KEY is void /// use a simple std::set template -class LLInstanceTracker : - public LLInstanceTrackerBase>> +class LLInstanceTracker { - typedef LLInstanceTrackerBase>> super; - using typename super::StaticData; - using typename super::LockStatic; - typedef typename StaticData::InstanceSet InstanceSet; + typedef std::set> InstanceSet; + struct StaticData: public LLInstanceTrackerStuff::StaticBase + { + InstanceSet mSet; + }; + typedef llthread::LockStatic LockStatic; public: /** diff --git a/indra/llcommon/lockstatic.h b/indra/llcommon/lockstatic.h new file mode 100644 index 0000000000..5f08742cae --- /dev/null +++ b/indra/llcommon/lockstatic.h @@ -0,0 +1,56 @@ +/** + * @file lockstatic.h + * @author Nat Goodspeed + * @date 2019-12-03 + * @brief LockStatic class provides mutex-guarded access to the specified + * static data. + * + * $LicenseInfo:firstyear=2019&license=viewerlgpl$ + * Copyright (c) 2019, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LOCKSTATIC_H) +#define LL_LOCKSTATIC_H + +#include "mutex.h" // std::unique_lock + +namespace llthread +{ + +// Instantiate this template to obtain a pointer to the canonical static +// instance of Static while holding a lock on that instance. Use of +// Static::mMutex presumes that Static declares some suitable mMutex. +template +class LockStatic +{ + typedef std::unique_lock lock_t; +public: + LockStatic(): + mData(getStatic()), + mLock(mData->mMutex) + {} + Static* get() const { return mData; } + operator Static*() const { return get(); } + Static* operator->() const { return get(); } + // sometimes we must explicitly unlock... + void unlock() + { + // but once we do, access is no longer permitted + mData = nullptr; + mLock.unlock(); + } +protected: + Static* mData; + lock_t mLock; +private: + Static* getStatic() + { + static Static sData; + return &sData; + } +}; + +} // llthread namespace + +#endif /* ! defined(LL_LOCKSTATIC_H) */ From 01761461077a21c195bc79e5556b8d37cd8b91bc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Dec 2019 15:42:56 -0500 Subject: [PATCH 09/48] DRTVWR-494: Add llmake_heap(); update to variadic llmake(). --- indra/llcommon/llmake.h | 52 ++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/indra/llcommon/llmake.h b/indra/llcommon/llmake.h index 08744f90fb..02463d97ea 100644 --- a/indra/llcommon/llmake.h +++ b/indra/llcommon/llmake.h @@ -12,10 +12,8 @@ * * also relevant: * - * Template argument deduction for class templates - * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0091r3.html - * was apparently adopted in June 2016? Unclear when compilers will - * portably support this, but there is hope. + * Template argument deduction for class templates (C++17) + * https://en.cppreference.com/w/cpp/language/class_template_argument_deduction * * $LicenseInfo:firstyear=2015&license=viewerlgpl$ * Copyright (c) 2015, Linden Research, Inc. @@ -25,37 +23,43 @@ #if ! defined(LL_LLMAKE_H) #define LL_LLMAKE_H -/*==========================================================================*| -// When we allow ourselves to compile with C++11 features enabled, this form -// should generically handle an arbitrary number of arguments. - +/** + * Usage: llmake(args...) + * + * Deduces the types T... of 'args' and returns an instance of + * SomeTemplate(args...). + */ template class CLASS_TEMPLATE, typename... ARGS> CLASS_TEMPLATE llmake(ARGS && ... args) { return CLASS_TEMPLATE(std::forward(args)...); } -|*==========================================================================*/ -// As of 2015-12-18, this is what we'll use instead. Add explicit overloads -// for different numbers of template parameters as use cases arise. +/// dumb pointer template just in case that's what's wanted +template +using dumb_pointer = T*; /** - * Usage: llmake(arg) + * Same as llmake(), but returns a pointer to a new heap instance of + * SomeTemplate(args...) using the pointer of your choice. * - * Deduces the type T of 'arg' and returns an instance of SomeTemplate - * initialized with 'arg'. Assumes a constructor accepting T (by value, - * reference or whatever). + * @code + * auto* dumb = llmake_heap(args...); + * auto shared = llmake_heap(args...); + * auto unique = llmake_heap(args...); + * @endcode */ -template class CLASS_TEMPLATE, typename ARG1> -CLASS_TEMPLATE llmake(const ARG1& arg1) +// POINTER_TEMPLATE is characterized as template rather than as +// template because (e.g.) std::unique_ptr has multiple template +// arguments. Even though we only engage one, std::unique_ptr doesn't match a +// template template parameter that itself takes only one template parameter. +template class CLASS_TEMPLATE, + template class POINTER_TEMPLATE=dumb_pointer, + typename... ARGS> +POINTER_TEMPLATE> llmake_heap(ARGS&&... args) { - return CLASS_TEMPLATE(arg1); -} - -template class CLASS_TEMPLATE, typename ARG1, typename ARG2> -CLASS_TEMPLATE llmake(const ARG1& arg1, const ARG2& arg2) -{ - return CLASS_TEMPLATE(arg1, arg2); + return POINTER_TEMPLATE>( + new CLASS_TEMPLATE(std::forward(args)...)); } #endif /* ! defined(LL_LLMAKE_H) */ From fbfaa3db8ecc7014c8b8b70c411ab67f3a0da73f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Dec 2019 15:44:32 -0500 Subject: [PATCH 10/48] DRTVWR-494: Move explanatory comments from LLSingleton to LockStatic. --- indra/llcommon/lockstatic.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/indra/llcommon/lockstatic.h b/indra/llcommon/lockstatic.h index 5f08742cae..96c53c6473 100644 --- a/indra/llcommon/lockstatic.h +++ b/indra/llcommon/lockstatic.h @@ -46,6 +46,23 @@ protected: private: Static* getStatic() { + // Static::mMutex must be function-local static rather than class- + // static. Some of our consumers must function properly (therefore + // lock properly) even when the containing module's static variables + // have not yet been runtime-initialized. A mutex requires + // construction. A static class member might not yet have been + // constructed. + // + // We could store a dumb mutex_t*, notice when it's NULL and allocate a + // heap mutex -- but that's vulnerable to race conditions. And we can't + // defend the dumb pointer with another mutex. + // + // We could store a std::atomic -- but a default-constructed + // std::atomic does not contain a valid T, even a default-constructed + // T! Which means std::atomic, too, requires runtime initialization. + // + // But a function-local static is guaranteed to be initialized exactly + // once: the first time control reaches that declaration. static Static sData; return &sData; } From 17df3fbb8f3ab80b6f17d37983c4e20a40c9cae1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 5 Dec 2019 11:53:44 -0500 Subject: [PATCH 11/48] DRTVWR-494: Fix up merge glitch. --- indra/llcommon/llthreadsafequeue.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/llthreadsafequeue.h b/indra/llcommon/llthreadsafequeue.h index 4da7291f2a..3f49dbccc2 100644 --- a/indra/llcommon/llthreadsafequeue.h +++ b/indra/llcommon/llthreadsafequeue.h @@ -31,7 +31,7 @@ #include #include #include "mutex.h" -#include +#include // // A general queue exception. From 622bbc7e39faecd7a59439b8e692b1e4cf153b56 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 5 Dec 2019 11:54:52 -0500 Subject: [PATCH 12/48] DRTVWR-476: Adjust LLCoros to new LLInstanceTracker API. --- indra/llcommon/llcoros.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index 72462f3582..550b4a27c1 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -212,12 +212,11 @@ void LLCoros::printActiveCoroutines(const std::string& when) { LL_INFOS("LLCoros") << "-------------- List of active coroutines ------------"; F64 time = LLTimer::getTotalSeconds(); - for (auto it(CoroData::beginInstances()), end(CoroData::endInstances()); - it != end; ++it) + for (auto& cd : CoroData::instance_snapshot()) { - F64 life_time = time - it->mCreationTime; + F64 life_time = time - cd.mCreationTime; LL_CONT << LL_NEWLINE - << it->mName << ' ' << it->mStatus << " life: " << life_time; + << cd.mName << ' ' << cd.mStatus << " life: " << life_time; } LL_CONT << LL_ENDL; LL_INFOS("LLCoros") << "-----------------------------------------------------" << LL_ENDL; From c897947bda0e98cbe354be7ffce505759ff2af81 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Dec 2019 15:04:11 -0500 Subject: [PATCH 13/48] DRTVWR-494: Move LL_ERRS out of llinstancetracker.h header file. Add a namespaced free function in .cpp file to report LL_ERRS as needed. Per code review, use a more indicative namespace name. --- indra/llcommon/llinstancetracker.cpp | 11 +++++++---- indra/llcommon/llinstancetracker.h | 17 ++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/indra/llcommon/llinstancetracker.cpp b/indra/llcommon/llinstancetracker.cpp index accb4286e8..e7193b70b5 100644 --- a/indra/llcommon/llinstancetracker.cpp +++ b/indra/llcommon/llinstancetracker.cpp @@ -27,12 +27,15 @@ #include "linden_common.h" // associated header #include "llinstancetracker.h" -#include "llapr.h" - +#include "llerror.h" // STL headers // std headers // external library headers // other Linden headers -// This .cpp file is required by our CMake test macro. It contributes no code -// to the viewer. +void LLInstanceTrackerPrivate::logerrs(const char* cls, const std::string& arg1, + const std::string& arg2, const std::string& arg3) +{ + LL_ERRS("LLInstanceTracker") << LLError::Log::demangle(cls) + << arg1 << arg2 << arg3 << LL_ENDL; +} diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index cfb40c25f0..196bc5c0dd 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -42,18 +42,21 @@ #include #include "lockstatic.h" +#include "stringize.h" /***************************************************************************** * StaticBase *****************************************************************************/ -namespace LLInstanceTrackerStuff +namespace LLInstanceTrackerPrivate { struct StaticBase { // We need to be able to lock static data while manipulating it. std::mutex mMutex; }; -} // namespace LLInstanceTrackerStuff + + void logerrs(const char* cls, const std::string&, const std::string&, const std::string&); +} // namespace LLInstanceTrackerPrivate /***************************************************************************** * LLInstanceTracker with key @@ -73,7 +76,7 @@ template> InstanceMap; - struct StaticData: public LLInstanceTrackerStuff::StaticBase + struct StaticData: public LLInstanceTrackerPrivate::StaticBase { InstanceMap mMap; }; @@ -232,7 +235,7 @@ private: // for logging template - static K report(K key) { return key; } + static std::string report(K key) { return stringize(key); } static std::string report(const std::string& key) { return "'" + key + "'"; } static std::string report(const char* key) { return report(std::string(key)); } @@ -249,8 +252,8 @@ private: auto pair = map.emplace(key, ptr); if (! pair.second) { - LL_ERRS("LLInstanceTracker") << "Instance with key " << report(key) - << " already exists!" << LL_ENDL; + LLInstanceTrackerPrivate::logerrs(typeid(*this).name(), " instance with key ", + report(key), " already exists!"); } break; } @@ -299,7 +302,7 @@ template class LLInstanceTracker { typedef std::set> InstanceSet; - struct StaticData: public LLInstanceTrackerStuff::StaticBase + struct StaticData: public LLInstanceTrackerPrivate::StaticBase { InstanceSet mSet; }; From 65e00abe6f0bc091ae1ea2f41daa03eb556dc7dd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Dec 2019 16:02:28 -0500 Subject: [PATCH 14/48] DRTVWR-494: Put streaming operator<<() for kdu_dims in kdu_core. It seems the lookup now requires that the operator<<() function be defined in the same namespace as the argument. --- indra/llkdu/llimagej2ckdu.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index 4048b9a43d..dac5349f57 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -44,16 +44,19 @@ using namespace kdu_core; #include #include -// stream kdu_dims to std::ostream // Turns out this must NOT be in the anonymous namespace! -// It must also precede #include "stringize.h". +namespace kdu_core +{ +// stream kdu_dims to std::ostream inline std::ostream& operator<<(std::ostream& out, const kdu_dims& dims) { return out << "(" << dims.pos.x << "," << dims.pos.y << ")," "[" << dims.size.x << "x" << dims.size.y << "]"; } +} // namespace kdu_core +// operator<<(std::ostream&, const kdu_dims&) must precede #include "stringize.h" #include "stringize.h" namespace { From 55d7ac5e81fc8f7a056aa7d1aa382cb522eb77cf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Dec 2019 16:31:49 -0500 Subject: [PATCH 15/48] DRTVWR-494: Use std::thread::id for LLThread::currentID(). LLThread::currentID() used to return a U32, a distinct unsigned value incremented by explicitly constructing LLThread or by calling LLThread:: registerThreadID() early in a thread launched by other means. The latter imposed an unobvious requirement on new code based on std::thread. Using std::thread::id instead delegates to the compiler/library the problem of distinguishing threads launched by any means. Change lots of explicit U32 declarations. Introduce LLThread::id_t typedef to avoid having to run around fixing uses again if we later revisit this decision. LLMutex, which stores an LLThread::id_t, wants a distinguished value meaning NO_THREAD, and had an enum with that name. But as std::thread::id promises that the default-constructed value is distinct from every valid value, NO_THREAD becomes unnecessary and goes away. Because LLMutex now stores LLThread::id_t instead of U32, make llmutex.h #include "llthread.h" instead of the other way around. This makes LLMutex an incomplete type within llthread.h, so move LLThread::lockData() and unlockData() to the .cpp file. Similarly, remove llrefcount.h's #include "llmutex.h" to break circularity; instead forward-declare LLMutex. It turns out that a number of source files assumed that #include "llthread.h" would get the definition for LLMutex. Sprinkle #include "llmutex.h" as needed. In the SAFE_SSL code in llcorehttp/httpcommon.cpp, there's an ssl_thread_id() callback that returns an unsigned long to the SSL library. When LLThread:: currentID() was U32, we could simply return that. But std::thread::id is very deliberately opaque, and can't be reinterpret_cast to unsigned long. Fortunately it can be hashed because std::hash is specialized with that type. --- indra/llcommon/llmutex.cpp | 13 +++++----- indra/llcommon/llmutex.h | 14 ++++------- indra/llcommon/llrefcount.h | 3 ++- indra/llcommon/llthread.cpp | 36 +++++++++++++++------------- indra/llcommon/llthread.h | 24 +++++-------------- indra/llcommon/lluuid.cpp | 3 ++- indra/llcommon/llworkerthread.h | 1 + indra/llcorehttp/httpcommon.cpp | 4 +++- indra/llmessage/llbuffer.cpp | 1 + indra/llmessage/llbufferstream.cpp | 1 + indra/llmessage/llproxy.h | 1 + indra/llplugin/llpluginmessagepipe.h | 1 + indra/llvfs/llvfs.h | 1 + 13 files changed, 49 insertions(+), 54 deletions(-) diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp index 75f43a4704..4d73c04d07 100644 --- a/indra/llcommon/llmutex.cpp +++ b/indra/llcommon/llmutex.cpp @@ -32,8 +32,7 @@ //============================================================================ LLMutex::LLMutex() : - mCount(0), - mLockingThread(NO_THREAD) + mCount(0) { } @@ -55,7 +54,7 @@ void LLMutex::lock() #if MUTEX_DEBUG // Have to have the lock before we can access the debug info - U32 id = LLThread::currentID(); + auto id = LLThread::currentID(); if (mIsLocked[id] != FALSE) LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; mIsLocked[id] = TRUE; @@ -74,13 +73,13 @@ void LLMutex::unlock() #if MUTEX_DEBUG // Access the debug info while we have the lock - U32 id = LLThread::currentID(); + auto id = LLThread::currentID(); if (mIsLocked[id] != TRUE) LL_ERRS() << "Not locked in Thread: " << id << LL_ENDL; mIsLocked[id] = FALSE; #endif - mLockingThread = NO_THREAD; + mLockingThread = LLThread::id_t(); mMutex.unlock(); } @@ -102,7 +101,7 @@ bool LLMutex::isSelfLocked() return mLockingThread == LLThread::currentID(); } -U32 LLMutex::lockingThread() const +LLThread::id_t LLMutex::lockingThread() const { return mLockingThread; } @@ -122,7 +121,7 @@ bool LLMutex::trylock() #if MUTEX_DEBUG // Have to have the lock before we can access the debug info - U32 id = LLThread::currentID(); + auto id = LLThread::currentID(); if (mIsLocked[id] != FALSE) LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; mIsLocked[id] = TRUE; diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h index 1a93c048b6..838d7d34c0 100644 --- a/indra/llcommon/llmutex.h +++ b/indra/llcommon/llmutex.h @@ -28,6 +28,7 @@ #define LL_LLMUTEX_H #include "stdtypes.h" +#include "llthread.h" #include #include "mutex.h" @@ -44,11 +45,6 @@ class LL_COMMON_API LLMutex { public: - typedef enum - { - NO_THREAD = 0xFFFFFFFF - } e_locking_thread; - LLMutex(); virtual ~LLMutex(); @@ -57,15 +53,15 @@ public: void unlock(); // undefined behavior when called on mutex not being held bool isLocked(); // non-blocking, but does do a lock/unlock so not free bool isSelfLocked(); //return true if locked in a same thread - U32 lockingThread() const; //get ID of locking thread - + LLThread::id_t lockingThread() const; //get ID of locking thread + protected: std::mutex mMutex; mutable U32 mCount; - mutable U32 mLockingThread; + mutable LLThread::id_t mLockingThread; #if MUTEX_DEBUG - std::map mIsLocked; + std::map mIsLocked; #endif }; diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h index fb0411d27b..7e4af6ea66 100644 --- a/indra/llcommon/llrefcount.h +++ b/indra/llcommon/llrefcount.h @@ -28,9 +28,10 @@ #include #include -#include "llmutex.h" #include "llatomic.h" +class LLMutex; + //---------------------------------------------------------------------------- // RefCount objects should generally only be accessed by way of LLPointer<>'s // see llthread.h for LLThreadSafeRefCount diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index f875e4e0dc..0b9dec969c 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -92,21 +92,16 @@ void set_thread_name( DWORD dwThreadID, const char* threadName) // } // //---------------------------------------------------------------------------- - -U32 LL_THREAD_LOCAL sThreadID = 0; - -U32 LLThread::sIDIter = 0; - namespace { - U32 main_thread() + LLThread::id_t main_thread() { // Using a function-static variable to identify the main thread // requires that control reach here from the main thread before it // reaches here from any other thread. We simply trust that whichever // thread gets here first is the main thread. - static U32 s_thread_id = LLThread::currentID(); + static LLThread::id_t s_thread_id = LLThread::currentID(); return s_thread_id; } @@ -128,10 +123,8 @@ LL_COMMON_API void assert_main_thread() } } -void LLThread::registerThreadID() -{ - sThreadID = ++sIDIter; -} +// this function has become moot +void LLThread::registerThreadID() {} // // Handed to the APR thread creation function @@ -142,11 +135,12 @@ void LLThread::threadRun() set_thread_name(-1, mName.c_str()); #endif + // this is the first point at which we're actually running in the new thread + mID = currentID(); + // for now, hard code all LLThreads to report to single master thread recorder, which is known to be running on main thread mRecorder = new LLTrace::ThreadRecorder(*LLTrace::get_master_thread_recorder()); - sThreadID = mID; - // Run the user supplied function do { @@ -188,8 +182,6 @@ LLThread::LLThread(const std::string& name, apr_pool_t *poolp) : mStatus(STOPPED), mRecorder(NULL) { - - mID = ++sIDIter; mRunCondition = new LLCondition(); mDataLock = new LLMutex(); mLocalAPRFilePoolp = NULL ; @@ -367,9 +359,9 @@ void LLThread::setQuitting() } // static -U32 LLThread::currentID() +LLThread::id_t LLThread::currentID() { - return sThreadID; + return std::this_thread::get_id(); } // static @@ -396,6 +388,16 @@ void LLThread::wakeLocked() } } +void LLThread::lockData() +{ + mDataLock->lock(); +} + +void LLThread::unlockData() +{ + mDataLock->unlock(); +} + //============================================================================ //---------------------------------------------------------------------------- diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 37f6e66bbb..5cd0731f6c 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -30,7 +30,6 @@ #include "llapp.h" #include "llapr.h" #include "boost/intrusive_ptr.hpp" -#include "llmutex.h" #include "llrefcount.h" #include @@ -43,7 +42,6 @@ class LL_COMMON_API LLThread { private: friend class LLMutex; - static U32 sIDIter; public: typedef enum e_thread_status @@ -53,6 +51,7 @@ public: QUITTING= 2, // Someone wants this thread to quit CRASHED = -1 // An uncaught exception was thrown by the thread } EThreadStatus; + typedef std::thread::id id_t; LLThread(const std::string& name, apr_pool_t *poolp = NULL); virtual ~LLThread(); // Warning! You almost NEVER want to destroy a thread unless it's in the STOPPED state. @@ -62,7 +61,7 @@ public: bool isStopped() const { return (STOPPED == mStatus) || (CRASHED == mStatus); } bool isCrashed() const { return (CRASHED == mStatus); } - static U32 currentID(); // Return ID of current thread + static id_t currentID(); // Return ID of current thread static void yield(); // Static because it can be called by the main thread, which doesn't have an LLThread data structure. public: @@ -86,7 +85,7 @@ public: LLVolatileAPRPool* getLocalAPRFilePool() { return mLocalAPRFilePoolp ; } - U32 getID() const { return mID; } + id_t getID() const { return mID; } // Called by threads *not* created via LLThread to register some // internal state used by LLMutex. You must call this once early @@ -107,7 +106,7 @@ protected: std::thread *mThreadp; EThreadStatus mStatus; - U32 mID; + id_t mID; LLTrace::ThreadRecorder* mRecorder; //a local apr_pool for APRFile operations in this thread. If it exists, LLAPRFile::sAPRFilePoolp should not be used. @@ -124,8 +123,8 @@ protected: virtual bool runCondition(void); // Lock/Unlock Run Condition -- use around modification of any variable used in runCondition() - inline void lockData(); - inline void unlockData(); + void lockData(); + void unlockData(); // This is the predicate that decides whether the thread should sleep. // It should only be called with mDataLock locked, since the virtual runCondition() function may need to access @@ -140,17 +139,6 @@ protected: }; -void LLThread::lockData() -{ - mDataLock->lock(); -} - -void LLThread::unlockData() -{ - mDataLock->unlock(); -} - - //============================================================================ // Simple responder for self destructing callbacks diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index 8f33d789eb..b05630c6b5 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -43,6 +43,7 @@ #include "llstring.h" #include "lltimer.h" #include "llthread.h" +#include "llmutex.h" const LLUUID LLUUID::null; const LLTransactionID LLTransactionID::tnull; @@ -738,7 +739,7 @@ void LLUUID::getCurrentTime(uuid_time_t *timestamp) getSystemTime(&time_last); uuids_this_tick = uuids_per_tick; init = TRUE; - mMutex = new LLMutex(); + mMutex = new LLMutex(); } uuid_time_t time_now = {0,0}; diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h index b1a6f61360..0387e75c65 100644 --- a/indra/llcommon/llworkerthread.h +++ b/indra/llcommon/llworkerthread.h @@ -34,6 +34,7 @@ #include "llqueuedthread.h" #include "llatomic.h" +#include "llmutex.h" #define USE_FRAME_CALLBACK_MANAGER 0 diff --git a/indra/llcorehttp/httpcommon.cpp b/indra/llcorehttp/httpcommon.cpp index 7c93c54cdf..e37a38b05f 100644 --- a/indra/llcorehttp/httpcommon.cpp +++ b/indra/llcorehttp/httpcommon.cpp @@ -40,6 +40,7 @@ #include #if SAFE_SSL #include +#include // std::hash #endif @@ -369,7 +370,8 @@ void ssl_locking_callback(int mode, int type, const char *file, int line) //static unsigned long ssl_thread_id(void) { - return LLThread::currentID(); + // std::thread::id is very deliberately opaque, but we can hash it + return std::hash()(LLThread::currentID()); } #endif diff --git a/indra/llmessage/llbuffer.cpp b/indra/llmessage/llbuffer.cpp index 1a0eceba0f..cfe38605ad 100644 --- a/indra/llmessage/llbuffer.cpp +++ b/indra/llmessage/llbuffer.cpp @@ -32,6 +32,7 @@ #include "llmath.h" #include "llstl.h" #include "llthread.h" +#include "llmutex.h" #include #define ASSERT_LLBUFFERARRAY_MUTEX_LOCKED() llassert(!mMutexp || mMutexp->isSelfLocked()) diff --git a/indra/llmessage/llbufferstream.cpp b/indra/llmessage/llbufferstream.cpp index ff1c9993cc..39508c1c52 100644 --- a/indra/llmessage/llbufferstream.cpp +++ b/indra/llmessage/llbufferstream.cpp @@ -31,6 +31,7 @@ #include "llbuffer.h" #include "llthread.h" +#include "llmutex.h" static const S32 DEFAULT_OUTPUT_SEGMENT_SIZE = 1024 * 4; diff --git a/indra/llmessage/llproxy.h b/indra/llmessage/llproxy.h index 87891901ad..a1ffa9e5d5 100644 --- a/indra/llmessage/llproxy.h +++ b/indra/llmessage/llproxy.h @@ -32,6 +32,7 @@ #include "llmemory.h" #include "llsingleton.h" #include "llthread.h" +#include "llmutex.h" #include #include diff --git a/indra/llplugin/llpluginmessagepipe.h b/indra/llplugin/llpluginmessagepipe.h index c3498beac0..9d5835eb82 100644 --- a/indra/llplugin/llpluginmessagepipe.h +++ b/indra/llplugin/llpluginmessagepipe.h @@ -31,6 +31,7 @@ #include "lliosocket.h" #include "llthread.h" +#include "llmutex.h" class LLPluginMessagePipe; diff --git a/indra/llvfs/llvfs.h b/indra/llvfs/llvfs.h index dca5ff4ad5..42feafe20b 100644 --- a/indra/llvfs/llvfs.h +++ b/indra/llvfs/llvfs.h @@ -31,6 +31,7 @@ #include "lluuid.h" #include "llassettype.h" #include "llthread.h" +#include "llmutex.h" enum EVFSValid { From 739916fee0e784df94e144753be6ce581b75b1c4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 9 Dec 2019 11:33:08 -0500 Subject: [PATCH 16/48] DRTVWR-494: VS 2013 can't yet handle variadic llmake(). --- indra/llcommon/llmake.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/indra/llcommon/llmake.h b/indra/llcommon/llmake.h index 02463d97ea..f901ee2bf1 100644 --- a/indra/llcommon/llmake.h +++ b/indra/llcommon/llmake.h @@ -23,6 +23,9 @@ #if ! defined(LL_LLMAKE_H) #define LL_LLMAKE_H +// If we're using a compiler newer than VS 2013, use variadic llmake(). +#if (! defined(_MSC_VER)) || (_MSC_VER > 1800) + /** * Usage: llmake(args...) * @@ -35,6 +38,22 @@ CLASS_TEMPLATE llmake(ARGS && ... args) return CLASS_TEMPLATE(std::forward(args)...); } +#else // older implementation for VS 2013 + +template class CLASS_TEMPLATE, typename ARG1> +CLASS_TEMPLATE llmake(const ARG1& arg1) +{ + return CLASS_TEMPLATE(arg1); +} + +template class CLASS_TEMPLATE, typename ARG1, typename ARG2> +CLASS_TEMPLATE llmake(const ARG1& arg1, const ARG2& arg2) +{ + return CLASS_TEMPLATE(arg1, arg2); +} + +#endif // VS 2013 workaround + /// dumb pointer template just in case that's what's wanted template using dumb_pointer = T*; From acb1796dea69767b6e0cd069339523e83f33ff87 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 9 Dec 2019 11:37:36 -0500 Subject: [PATCH 17/48] DRTVWR-494: Add LLMainThreadTask to perform work on the main thread. If already running on the main thread, LLMaintThreadTask simply runs the work inline. Otherwise it queues it for the main thread using LLEventTimer, using std::future to retrieve the result. --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/llmainthreadtask.cpp | 22 ++++++ indra/llcommon/llmainthreadtask.h | 102 ++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 indra/llcommon/llmainthreadtask.cpp create mode 100644 indra/llcommon/llmainthreadtask.h diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 98e1c00ce3..55c44446b4 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -189,6 +189,7 @@ set(llcommon_HEADER_FILES lllistenerwrapper.h llliveappconfig.h lllivefile.h + llmainthreadtask.h llmd5.h llmemory.h llmemorystream.h diff --git a/indra/llcommon/llmainthreadtask.cpp b/indra/llcommon/llmainthreadtask.cpp new file mode 100644 index 0000000000..e0d70cacd8 --- /dev/null +++ b/indra/llcommon/llmainthreadtask.cpp @@ -0,0 +1,22 @@ +/** + * @file llmainthreadtask.cpp + * @author Nat Goodspeed + * @date 2019-12-05 + * @brief Implementation for llmainthreadtask. + * + * $LicenseInfo:firstyear=2019&license=viewerlgpl$ + * Copyright (c) 2019, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llmainthreadtask.h" +// STL headers +// std headers +// external library headers +// other Linden headers + +// This file is required by our CMake integration-test machinery. It +// contributes no code to the viewer executable. diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h new file mode 100644 index 0000000000..526374981a --- /dev/null +++ b/indra/llcommon/llmainthreadtask.h @@ -0,0 +1,102 @@ +/** + * @file llmainthreadtask.h + * @author Nat Goodspeed + * @date 2019-12-04 + * @brief LLMainThreadTask dispatches work to the main thread. When invoked on + * the main thread, it performs the work inline. + * + * $LicenseInfo:firstyear=2019&license=viewerlgpl$ + * Copyright (c) 2019, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLMAINTHREADTASK_H) +#define LL_LLMAINTHREADTASK_H + +#include "lleventtimer.h" +#include "llthread.h" +#include "lockstatic.h" +#include "llmake.h" +#include +#include // std::result_of +#include + +class LLMainThreadTask +{ +private: + // Don't instantiate this class -- use dispatch() instead. + LLMainThreadTask() {} + // If our caller doesn't explicitly pass a LockStatic, make a + // fake one. + struct Static + { + boost::signals2::dummy_mutex mMutex; + }; + typedef llthread::LockStatic LockStatic; + +public: + /// dispatch() is the only way to invoke this functionality. + /// If you call it with a LockStatic, dispatch() unlocks it + /// before blocking for the result. + template + static auto dispatch(llthread::LockStatic& lk, CALLABLE&& callable) + -> decltype(callable()) + { + if (on_main_thread()) + { + // we're already running on the main thread, perfect + return callable(); + } + else + { + // It's essential to construct LLEventTimer subclass instances on + // the heap because, on completion, LLEventTimer deletes them. + // Once we enable C++17, we can use Class Template Argument + // Deduction. Until then, use llmake_heap(). + auto* task = llmake_heap(std::forward(callable)); + // The moment we construct a new LLEventTimer subclass object, its + // tick() method might get called. However, its tick() method + // might depend on something locked by the passed LockStatic. + // Unlock it so tick() can proceed. + lk.unlock(); + auto future = task->mTask.get_future(); + // Now simply block on the future. + return future.get(); + } + } + + /// You can call dispatch() without a LockStatic. + template + static auto dispatch(CALLABLE&& callable) -> decltype(callable()) + { + LockStatic lk; + return dispatch(lk, std::forward(callable)); + } + +private: + template + struct Task: public LLEventTimer + { + Task(CALLABLE&& callable): + // no wait time: call tick() next chance we get + LLEventTimer(0), + mTask(std::forward(callable)) + {} + BOOL tick() override + { + // run the task on the main thread, will populate the future + // obtained by get_future() + mTask(); + // tell LLEventTimer we're done (one shot) + return TRUE; + } + // Given arbitrary CALLABLE, which might be a lambda, how are we + // supposed to obtain its signature for std::packaged_task? It seems + // redundant to have to add an argument list to engage result_of, then + // add the argument list again to complete the signature. At least we + // only support a nullary CALLABLE. + std::packaged_task::type()> mTask; + }; +}; + +#endif /* ! defined(LL_LLMAINTHREADTASK_H) */ From 2f1aacbac563eec80c241da256a1d3d8884ec834 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 9 Dec 2019 14:33:56 -0500 Subject: [PATCH 18/48] DRTVWR-476: Make Sync::bump() atomic, add set() method. Using Sync with multiple threads is trickier than with coroutines. In particular, Sync::bump() was racy (get() and set() as two different operations), and threads were proceeding when they should have waited. Fortunately LLCond, on which Sync is based, already supports atomic update operations. Use that for bump(). But to nail things down even more specifically, add set(n) to complement yield_until(n). Using those methods, there should be no ambiguity about which call in one thread synchronizes with which call in the other thread. --- indra/test/sync.h | 51 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/indra/test/sync.h b/indra/test/sync.h index cafbc034b4..0f0b6cece8 100644 --- a/indra/test/sync.h +++ b/indra/test/sync.h @@ -41,18 +41,49 @@ public: mTimeout(timeout) {} - /// Bump mCond by n steps -- ideally, do this every time a participating - /// coroutine wakes up from any suspension. The choice to bump() after - /// resumption rather than just before suspending is worth calling out: - /// this practice relies on the fact that condition_variable::notify_all() - /// merely marks a suspended coroutine ready to run, rather than - /// immediately resuming it. This way, though, even if a coroutine exits - /// before reaching its next suspend point, the other coroutine isn't - /// left waiting forever. + /** + * Bump mCond by n steps -- ideally, do this every time a participating + * coroutine wakes up from any suspension. The choice to bump() after + * resumption rather than just before suspending is worth calling out: + * this practice relies on the fact that condition_variable::notify_all() + * merely marks a suspended coroutine ready to run, rather than + * immediately resuming it. This way, though, even if a coroutine exits + * before reaching its next suspend point, the other coroutine isn't + * left waiting forever. + */ void bump(int n=1) { - LL_DEBUGS() << llcoro::logname() << " bump(" << n << ") -> " << (mCond.get() + n) << LL_ENDL; - mCond.set_all(mCond.get() + n); + // Calling mCond.set_all(mCond.get() + n) would be great for + // coroutines -- but not so good between kernel threads -- it would be + // racy. Make the increment atomic by calling update_all(), which runs + // the passed lambda within a mutex lock. + int updated; + mCond.update_all( + [&n, &updated](int& data) + { + data += n; + // Capture the new value for possible logging purposes. + updated = data; + }); + // In the multi-threaded case, this log message could be a bit + // misleading, as it will be emitted after waiting threads have + // already awakened. But emitting the log message within the lock + // would seem to hold the lock longer than we really ought. + LL_DEBUGS() << llcoro::logname() << " bump(" << n << ") -> " << updated << LL_ENDL; + } + + /** + * Set mCond to a specific n. Use of bump() and yield() is nicely + * maintainable, since you can insert or delete matching operations in a + * test function and have the rest of the Sync operations continue to + * line up as before. But sometimes you need to get very specific, which + * is where set() and yield_until() come in handy: less maintainable, + * more precise. + */ + void set(int n) + { + LL_DEBUGS() << llcoro::logname() << " set(" << n << ")" << LL_ENDL; + mCond.set_all(n); } /// suspend until "somebody else" has bumped mCond by n steps From 3abbd0b19607eebac452aad0703fe6cd0a2051b2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Dec 2019 10:56:24 -0500 Subject: [PATCH 19/48] DRTVWR-476: Make llcoro::logname() distinguish different threads. Actually, introduce static LLCoros::logname() and make the namespaced free function an alias for that. Because CoroData is a subclass of LLInstanceTracker with a key, every instance requires a distinct key. That conflicts with our "getName() returns empty string for default coroutine on thread" convention. Introduce a new CoroData constructor, specifically for the default coroutine on each thread, that initializes the getName() name to empty string while providing a distinct "mainN" key. Make get_CoroData() use that new constructor for its thread_local instance, passing an atomic incremented each time we initialize one for a new thread. Then LLCoros::logname() returns either the getName() name or the key. --- indra/llcommon/llcoros.cpp | 28 ++++++++++++++++++++++++---- indra/llcommon/llcoros.h | 15 +++++++++------ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index 550b4a27c1..8f1b1f0512 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -34,6 +34,7 @@ #include "llcoros.h" // STL headers // std headers +#include // external library headers #include #include @@ -73,10 +74,9 @@ LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) // canonical values. if (! current) { - // It's tempting to provide a distinct name for each thread's "main - // coroutine." But as getName() has always returned the empty string - // to mean "not in a coroutine," empty string should suffice here. - static thread_local CoroData sMain(""); + static std::atomic which_thread(0); + // Use alternate CoroData constructor. + static thread_local CoroData sMain(which_thread++); // We need not reset() the local_ptr to this instance; we'll simply // find it again every time we discover that current is null. current = &sMain; @@ -198,6 +198,13 @@ std::string LLCoros::getName() return get_CoroData("getName()").mName; } +//static +std::string LLCoros::logname() +{ + LLCoros::CoroData& data(get_CoroData("logname()")); + return data.mName.empty()? data.getKey() : data.mName; +} + void LLCoros::setStackSize(S32 stacksize) { LL_DEBUGS("LLCoros") << "Setting coroutine stack size to " << stacksize << LL_ENDL; @@ -353,3 +360,16 @@ LLCoros::CoroData::CoroData(const std::string& name): mCreationTime(LLTimer::getTotalSeconds()) { } + +LLCoros::CoroData::CoroData(int n): + // This constructor is used for the thread_local instance belonging to the + // default coroutine on each thread. We must give each one a different + // LLInstanceTracker key because LLInstanceTracker's map spans all + // threads, but we want the default coroutine on each thread to have the + // empty string as its visible name because some consumers test for that. + LLInstanceTracker("main" + stringize(n)), + mName(), + mConsuming(false), + mCreationTime(LLTimer::getTotalSeconds()) +{ +} diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index 2e4cd8ccad..95859198d4 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -142,6 +142,13 @@ public: */ static std::string getName(); + /** + * This variation returns a name suitable for log messages: the explicit + * name for an explicitly-launched coroutine, or "mainN" for the default + * coroutine on a thread. + */ + static std::string logname(); + /** * For delayed initialization. To be clear, this will only affect * coroutines launched @em after this point. The underlying facility @@ -272,6 +279,7 @@ private: struct CoroData: public LLInstanceTracker { CoroData(const std::string& name); + CoroData(int n); // tweaked name of the current coroutine const std::string mName; @@ -292,12 +300,7 @@ namespace llcoro { inline -std::string logname() -{ - static std::string main("main"); - std::string name(LLCoros::getName()); - return name.empty()? main : name; -} +std::string logname() { return LLCoros::logname(); } } // llcoro From 31e5979660af806687dbffed1e2d8bc54cad3e15 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Dec 2019 11:09:02 -0500 Subject: [PATCH 20/48] DRTVWR-476: LLTHROW() requires LLException or subclass. --- indra/llcommon/llsingleton.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index dd04eb00d7..d1717d1601 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -135,8 +135,8 @@ public: { if (! mList) { - LLTHROW(std::runtime_error("Trying to use LockedInitializing " - "after cleanup_initializing()")); + LLTHROW(LLException("Trying to use LockedInitializing " + "after cleanup_initializing()")); } return *mList; } From 07d87ec7637b91ac17e4bb12f77031afdfcd3f7b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Dec 2019 11:11:20 -0500 Subject: [PATCH 21/48] DRTVWR-476: Add unit tests for LLMainThreadTask. Now that we have the Sync class to help construct unit tests that move forward in a deterministic stepwise order, we can build suitable unit tests for LLMainThreadTask. --- indra/llcommon/CMakeLists.txt | 1 + .../llcommon/tests/llmainthreadtask_test.cpp | 139 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 indra/llcommon/tests/llmainthreadtask_test.cpp diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index d17ee4c70a..eeb315ead6 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -340,6 +340,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llmainthreadtask "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}") diff --git a/indra/llcommon/tests/llmainthreadtask_test.cpp b/indra/llcommon/tests/llmainthreadtask_test.cpp new file mode 100644 index 0000000000..e54c7eda18 --- /dev/null +++ b/indra/llcommon/tests/llmainthreadtask_test.cpp @@ -0,0 +1,139 @@ +/** + * @file llmainthreadtask_test.cpp + * @author Nat Goodspeed + * @date 2019-12-05 + * @brief Test for llmainthreadtask. + * + * $LicenseInfo:firstyear=2019&license=viewerlgpl$ + * Copyright (c) 2019, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llmainthreadtask.h" +// STL headers +// std headers +#include +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "../test/sync.h" +#include "llthread.h" // on_main_thread() +#include "lleventtimer.h" +#include "lockstatic.h" + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llmainthreadtask_data + { + // 2-second timeout + Sync mSync{F32Milliseconds(2000.0f)}; + + llmainthreadtask_data() + { + // we're not testing the result; this is just to cache the + // initial thread as the main thread. + on_main_thread(); + } + }; + typedef test_group llmainthreadtask_group; + typedef llmainthreadtask_group::object object; + llmainthreadtask_group llmainthreadtaskgrp("llmainthreadtask"); + + template<> template<> + void object::test<1>() + { + set_test_name("inline"); + bool ran = false; + bool result = LLMainThreadTask::dispatch( + [&ran]()->bool{ + ran = true; + return true; + }); + ensure("didn't run lambda", ran); + ensure("didn't return result", result); + } + + struct StaticData + { + std::mutex mMutex; // LockStatic looks for mMutex + bool ran{false}; + }; + typedef llthread::LockStatic LockStatic; + + template<> template<> + void object::test<2>() + { + set_test_name("cross-thread"); + std::atomic_bool result(false); + // wrapping our thread lambda in a packaged_task will catch any + // exceptions it might throw and deliver them via future + std::packaged_task thread_work( + [this, &result](){ + // lock static data first + LockStatic lk; + // unblock test<2>()'s yield_until(1) + mSync.set(1); + // dispatch work to main thread -- should block here + bool on_main( + LLMainThreadTask::dispatch( + lk, // unlock this before blocking! + []()->bool{ + // have to lock static mutex to set static data + LockStatic()->ran = true; + // indicate whether task was run on the main thread + return on_main_thread(); + })); + // wait for test<2>() to unblock us again + mSync.yield_until(3); + result = on_main; + }); + auto thread_result = thread_work.get_future(); + std::thread thread; + try + { + // run thread_work + thread = std::thread(std::move(thread_work)); + // wait for thread to set(1) + mSync.yield_until(1); + // try to acquire the lock, should block because thread has it + LockStatic lk; + // wake up when dispatch() unlocks the static mutex + ensure("shouldn't have run yet", !lk->ran); + ensure("shouldn't have returned yet", !result); + // unlock so the task can acquire the lock + lk.unlock(); + // run the task -- should unblock thread, which will immediately block + // on mSync + LLEventTimer::updateClass(); + // 'lk', having unlocked, can no longer be used to access; relock with + // a new LockStatic instance + ensure("should now have run", LockStatic()->ran); + ensure("returned too early", !result); + // okay, let thread perform the assignment + mSync.set(3); + } + catch (...) + { + // A test failure exception anywhere in the try block can cause + // the test program to terminate without explanation when + // ~thread() finds that 'thread' is still joinable. We could + // either join() or detach() it -- but since it might be blocked + // waiting for something from the main thread that now can never + // happen, it's safer to detach it. + thread.detach(); + throw; + } + // 'thread' should be all done now + thread.join(); + // deliver any exception thrown by thread_work + thread_result.get(); + ensure("ran changed", LockStatic()->ran); + ensure("didn't run on main thread", result); + } +} // namespace tut From f9910a19a0cbe9c83dfaec4ccec592485186db05 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Dec 2019 11:51:38 -0500 Subject: [PATCH 22/48] DRTVWR-494: Document LLMainThreadTask class. --- indra/llcommon/llmainthreadtask.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h index 526374981a..2e0583d104 100644 --- a/indra/llcommon/llmainthreadtask.h +++ b/indra/llcommon/llmainthreadtask.h @@ -21,6 +21,34 @@ #include // std::result_of #include +/** + * LLMainThreadTask provides a way to perform some task specifically on the + * main thread, waiting for it to complete. A task consists of a C++ nullary + * invocable (i.e. any callable that requires no arguments) with arbitrary + * return type. + * + * Instead of instantiating LLMainThreadTask, pass your invocable to its + * static dispatch() method. dispatch() returns the result of calling your + * task. (Or, if your task throws an exception, dispatch() throws that + * exception. See std::packaged_task.) + * + * When you call dispatch() on the main thread (as determined by + * on_main_thread() in llthread.h), it simply calls your task and returns the + * result. + * + * When you call dispatch() on a secondary thread, it instantiates an + * LLEventTimer subclass scheduled immediately. Next time the main loop calls + * LLEventTimer::updateClass(), your task will be run, and LLMainThreadTask + * will fulfill a future with its result. Meanwhile the requesting thread + * blocks on that future. As soon as it is set, the requesting thread wakes up + * with the task result. + * + * Under some circumstances it's necessary for the calling thread to hold a + * lock until the task has been scheduled -- yet important to release the lock + * while waiting for the result. If you pass a LockStatic to dispatch(), + * a secondary thread will unlock it before blocking on the future. (The main + * thread simply holds the lock for the duration of the task.) + */ class LLMainThreadTask { private: From e5fd84304d4db0de9355f9bb9b2375f28cd8b46e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 11 Dec 2019 14:55:48 -0500 Subject: [PATCH 23/48] DRTVWR-476: Update boost, colladadom, googlemock Update boost to codeticket version 533684. Update colladadom to codeticket version 533705. Update googlemock to codeticket version 533706. --- autobuild.xml | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 510ab732b5..a92e9b516f 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -166,9 +166,9 @@ archive hash - 5f210778a1fd7bcbc286ea1eec09f5d9 + 4d2bc845faac0e92a1e57568ef1f2aa0 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44384/392137/boost-1.67-darwin64-531381.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47847/424736/boost-1.67-darwin64-533684.tar.bz2 name darwin64 @@ -202,9 +202,9 @@ archive hash - 59add8c74a7955917c88a25ebc84b568 + 590c49fdc253ad59249663d3b0b3adf7 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44393/392045/boost-1.67-windows-531381.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47852/424591/boost-1.67-windows-533684.tar.bz2 name windows @@ -214,9 +214,9 @@ archive hash - 9a6d1801c88e18f42a2228c21043800f + a53f7a651ae8c8b5ab71f53bbd6a59b8 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44389/392031/boost-1.67-windows64-531381.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47848/424582/boost-1.67-windows64-533684.tar.bz2 name windows64 @@ -308,9 +308,9 @@ archive hash - 1a731435cb559831ab418d5f60e346cd + 5aad6e709fa930e762c4826adc765991 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44416/392216/colladadom-2.3.531391-darwin64-531391.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47874/424841/colladadom-2.3.533705-darwin64-533705.tar.bz2 name darwin64 @@ -344,9 +344,9 @@ archive hash - 1076600dc11b5c36dddcd80c2b291530 + 2aa5ae3604b78e809eadd1eb90b4425c url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44417/392222/colladadom-2.3.531391-windows-531391.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47880/424864/colladadom-2.3.533705-windows-533705.tar.bz2 name windows @@ -356,16 +356,16 @@ archive hash - 5977db3bd9b5c02b4337096de92fb598 + 952284bdb45d76a87914e32ae43f30c8 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44414/392209/colladadom-2.3.531391-windows64-531391.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47875/424857/colladadom-2.3.533705-windows64-533705.tar.bz2 name windows64 version - 2.3.531391 + 2.3.533705 curl @@ -1272,9 +1272,9 @@ archive hash - eaba8da53b483e72ea8237cde78e26da + d9199dac32c05ff324cbde896d367c9b url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44415/392202/googlemock-1.7.0.531390-darwin64-531390.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47876/424848/googlemock-1.7.0.533706-darwin64-533706.tar.bz2 name darwin64 @@ -1308,9 +1308,9 @@ archive hash - 6013840ad4d6c8a2e68fde2403e0800b + cbd0d82ec309e79a43a4c9871f56dca9 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44412/392188/googlemock-1.7.0.531390-windows-531390.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47882/424878/googlemock-1.7.0.533706-windows-533706.tar.bz2 name windows @@ -1320,16 +1320,16 @@ archive hash - 7e57a4f153e0f2cbcea097652b37602c + 1470ce12427a5b585038200d14a56ef6 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44409/392173/googlemock-1.7.0.531390-windows64-531390.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47881/424871/googlemock-1.7.0.533706-windows64-533706.tar.bz2 name windows64 version - 1.7.0.531390 + 1.7.0.533706 gstreamer From 35d61cb1d6dc27b7e3fbd88fd411d62fca02a25f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 11 Dec 2019 15:41:01 -0500 Subject: [PATCH 24/48] DRTVWR-476: Adapt LLInstanceTracker::snapshot for VS limitations. --- indra/llcommon/llinstancetracker.h | 38 ++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 196bc5c0dd..402333cca7 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -143,7 +143,24 @@ public: strong_iterator(mData.end(), strengthen)); } - LockStatic mLock; // lock static data during construction + // lock static data during construction +#if ! LL_WINDOWS + LockStatic mLock; +#else // LL_WINDOWS + // We want to be able to use (e.g.) our instance_snapshot subclass as: + // for (auto& inst : T::instance_snapshot()) ... + // But when this snapshot base class directly contains LockStatic, as + // above, Visual Studio 2017 requires us to code instead: + // for (auto& inst : std::move(T::instance_snapshot())) ... + // nat thinks this should be unnecessary, as an anonymous class + // instance is already a temporary. It shouldn't need to be cast to + // rvalue reference (the role of std::move()). clang evidently agrees, + // as the short form works fine with Xcode on Mac. + // To support the succinct usage, instead of directly storing + // LockStatic, store std::shared_ptr, which is copyable. + std::shared_ptr mLockp{std::make_shared()}; + LockStatic& mLock{*mLockp}; +#endif // LL_WINDOWS VectorType mData; }; @@ -373,7 +390,24 @@ public: strong_iterator(mData.end(), strengthen)); } - LockStatic mLock; // lock static data during construction + // lock static data during construction +#if ! LL_WINDOWS + LockStatic mLock; +#else // LL_WINDOWS + // We want to be able to use our instance_snapshot subclass as: + // for (auto& inst : T::instance_snapshot()) ... + // But when this snapshot base class directly contains LockStatic, as + // above, Visual Studio 2017 requires us to code instead: + // for (auto& inst : std::move(T::instance_snapshot())) ... + // nat thinks this should be unnecessary, as an anonymous class + // instance is already a temporary. It shouldn't need to be cast to + // rvalue reference (the role of std::move()). clang evidently agrees, + // as the short form works fine with Xcode on Mac. + // To support the succinct usage, instead of directly storing + // LockStatic, store std::shared_ptr, which is copyable. + std::shared_ptr mLockp{std::make_shared()}; + LockStatic& mLock{*mLockp}; +#endif // LL_WINDOWS VectorType mData; }; From 7bdb6675d01be3e08aa00904308a168a20b7e843 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 11 Dec 2019 16:36:08 -0500 Subject: [PATCH 25/48] DRTVWR-476: On Windows, use prebuilt Boost.Stacktrace. --- indra/llcommon/llexception.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indra/llcommon/llexception.cpp b/indra/llcommon/llexception.cpp index c0483e8927..5ce8958687 100644 --- a/indra/llcommon/llexception.cpp +++ b/indra/llcommon/llexception.cpp @@ -24,6 +24,11 @@ // `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if // _Unwind_Backtrace is available without `_GNU_SOURCE`." #define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED +#if LL_WINDOWS +// On Windows, header-only implementation causes macro collisions -- use +// prebuilt library +#define BOOST_STACKTRACE_LINK +#endif // LL_WINDOWS #include // other Linden headers #include "llerror.h" From e8d174a1a6074b8f5f857cf6a6d6eb3c573e0c1d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Dec 2019 07:39:23 -0500 Subject: [PATCH 26/48] DRTVWR-494: Fix VS LLError::Log::demangle() vulnerability. The Windows implementation of demangle() assumed that a "mangled" class name produced by typeid(class).name() always starts with the prefix "class ", checked for that and removed it. If the mangled name didn't start with that prefix, it would emit a debug message and return the full name. When the class in question is actually a struct, the prefix is "struct " instead. But when demangle() was being called before logging had been fully initialized, the debug message remarking that it didn't start with "class " crashed. Look for either "class " or "struct " prefix. Remove whichever is found and return the rest of the name. If neither is found, only log if logging is available. --- indra/llcommon/llerror.cpp | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 335a0995fe..83d380fafd 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -302,28 +302,35 @@ namespace LLError { #ifdef __GNUC__ // GCC: type_info::name() returns a mangled class name,st demangle - // passing nullptr, 0 forces allocation of a unique buffer we can free - // fixing MAINT-8724 on OSX 10.14 + // passing nullptr, 0 forces allocation of a unique buffer we can free + // fixing MAINT-8724 on OSX 10.14 int status = -1; char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status); - std::string result(name ? name : mangled); - free(name); - return result; + std::string result(name ? name : mangled); + free(name); + return result; + #elif LL_WINDOWS - // DevStudio: type_info::name() includes the text "class " at the start - - static const std::string class_prefix = "class "; + // Visual Studio: type_info::name() includes the text "class " at the start std::string name = mangled; - if (0 != name.compare(0, class_prefix.length(), class_prefix)) + for (const auto& prefix : std::vector{ "class ", "struct " }) { - LL_DEBUGS() << "Did not see '" << class_prefix << "' prefix on '" - << name << "'" << LL_ENDL; - return name; + if (0 == name.compare(0, prefix.length(), prefix)) + { + return name.substr(prefix.length()); + } } + // huh, that's odd, we should see one or the other prefix -- but don't + // try to log unless logging is already initialized + if (is_available()) + { + // in Python, " or ".join(vector) -- but in C++, a PITB + LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '" + << name << "'" << LL_ENDL; + } + return name; - return name.substr(class_prefix.length()); - -#else +#else // neither GCC nor Visual Studio return mangled; #endif } From a3ae17b7a0870fb7ec8838a63a6eb4eae9ae1827 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Dec 2019 10:35:47 -0500 Subject: [PATCH 27/48] DRTVWR-494: Remove LLMainThreadTask::dispatch(LockStatic&, ...) Monty's code review reveals that conflating dispatch() with [un]lock functionality is inconsistent and unnecessary. --- indra/llcommon/llmainthreadtask.h | 35 ++----------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h index 2e0583d104..d509b687c0 100644 --- a/indra/llcommon/llmainthreadtask.h +++ b/indra/llcommon/llmainthreadtask.h @@ -15,11 +15,9 @@ #include "lleventtimer.h" #include "llthread.h" -#include "lockstatic.h" #include "llmake.h" #include #include // std::result_of -#include /** * LLMainThreadTask provides a way to perform some task specifically on the @@ -42,33 +40,17 @@ * will fulfill a future with its result. Meanwhile the requesting thread * blocks on that future. As soon as it is set, the requesting thread wakes up * with the task result. - * - * Under some circumstances it's necessary for the calling thread to hold a - * lock until the task has been scheduled -- yet important to release the lock - * while waiting for the result. If you pass a LockStatic to dispatch(), - * a secondary thread will unlock it before blocking on the future. (The main - * thread simply holds the lock for the duration of the task.) */ class LLMainThreadTask { private: // Don't instantiate this class -- use dispatch() instead. LLMainThreadTask() {} - // If our caller doesn't explicitly pass a LockStatic, make a - // fake one. - struct Static - { - boost::signals2::dummy_mutex mMutex; - }; - typedef llthread::LockStatic LockStatic; public: /// dispatch() is the only way to invoke this functionality. - /// If you call it with a LockStatic, dispatch() unlocks it - /// before blocking for the result. - template - static auto dispatch(llthread::LockStatic& lk, CALLABLE&& callable) - -> decltype(callable()) + template + static auto dispatch(CALLABLE&& callable) -> decltype(callable()) { if (on_main_thread()) { @@ -82,25 +64,12 @@ public: // Once we enable C++17, we can use Class Template Argument // Deduction. Until then, use llmake_heap(). auto* task = llmake_heap(std::forward(callable)); - // The moment we construct a new LLEventTimer subclass object, its - // tick() method might get called. However, its tick() method - // might depend on something locked by the passed LockStatic. - // Unlock it so tick() can proceed. - lk.unlock(); auto future = task->mTask.get_future(); // Now simply block on the future. return future.get(); } } - /// You can call dispatch() without a LockStatic. - template - static auto dispatch(CALLABLE&& callable) -> decltype(callable()) - { - LockStatic lk; - return dispatch(lk, std::forward(callable)); - } - private: template struct Task: public LLEventTimer From 073c628d4e7e3999ff4c094a77963d46bd7f2d3c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Dec 2019 16:14:38 -0500 Subject: [PATCH 28/48] DRTVWR-494: Dispatch all LLSingleton construction to the main thread. Given the viewer's mutually-dependent LLSingletons, given that different threads might simultaneously request different LLSingletons from such a chain of circular dependencies, the key to avoiding deadlock is to serialize all LLSingleton construction on one thread: the main thread. Add comments to LLSingleton::getInstance() explaining the problem and the solution. Recast LLSingleton's static SingletonData to use LockStatic. Instead of using Locker, and simply trusting that every reference to sData is within the dynamic scope of a Locker instance, LockStatic enforces that: you can only access SingletonData members via LockStatic. Reorganize the switch in getInstance() to group the CONSTRUCTING error, the INITIALIZING/INITIALIZED success case, and the DELETED/UNINITIALIZED construction case. When [re]constructing an instance, on the main thread, retain the lock and call constructSingleton() (and capture_dependency()) directly. On a secondary thread, unlock LockStatic and use LLMainThreadTask::dispatch() to call getInstance() on the main thread. Since we might end up enqueuing multiple such tasks, it's important to let getInstance() notice when the instance has already been constructed and simply return the existing pointer. Add loginfos() method, sibling to logerrs(), logwarns() and logdebugs(). Produce loginfos() messages when dispatching to the main thread, when actually running on the main thread and when resuming the suspended requesting thread. Make deleteSingleton() manage all associated state, instead of delegating some of that work to ~LLSingleton(). Now, within LockStatic, extract the instance pointer and set state to DELETED; that lets subsequent code, which retains the only remaining pointer to the instance, remove the master-list entry, call the subclass cleanupSingleton() and destructor without needing to hold the lock. In fact, entirely remove ~LLSingleton(). Import LLSingletonBase::cleanup_() method to wrap the call to subclass cleanupSingleton() in try/catch. Remove cleanupAll() calls from llsingleton_test.cpp, and reorder the success cases to reflect the fact that T::cleanupSingleton() is called immediately before ~T() for each distinct LLSingleton subclass T. When getInstance() on a secondary thread dispatches to the main thread, it necessarily unlocks its LockStatic lock. But an LLSingleton dependency chain strongly depends on the function stack on which getInstance() is invoked -- the task dispatched to the main thread doesn't know the dependencies tracked on the requesting thread stack. So, once the main thread delivers the instance pointer, the requesting thread captures its own dependencies for that instance. Back in the requesting thread, obtaining the current EInitState to pass to capture_dependencies() would have required relocking LockStatic. Instead, I've convinced myself that (a) capture_dependencies() only wanted to know EInitState to produce an error for CONSTRUCTING, and (b) in CONSTRUCTING state, we never get as far as capture_dependencies() because getInstance() produces an error first. Eliminate the EInitState parameter from all capture_dependencies() methods. Remove the LLSingletonBase::capture_dependency() stanza that tested EInitState. Make the capture_dependencies() variants that accepted LockStatic instead accept LLSingletonBase*. That lets getInstance(), in the LLMainThreadTask case, pass the newly-returned instance pointer. For symmetry, make pop_initializing() accept LLSingletonBase* as well, instead of accepting LockStatic and extracting mInstance. --- indra/llcommon/llsingleton.cpp | 36 ++- indra/llcommon/llsingleton.h | 324 +++++++++++++--------- indra/llcommon/tests/llsingleton_test.cpp | 14 +- 3 files changed, 213 insertions(+), 161 deletions(-) diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index 812fd31719..bf594f122c 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -302,7 +302,7 @@ void LLSingletonBase::MasterList::LockedInitializing::log(const char* verb, cons } } -void LLSingletonBase::capture_dependency(EInitState initState) +void LLSingletonBase::capture_dependency() { MasterList::LockedInitializing locked_list; list_t& initializing(locked_list.get()); @@ -334,21 +334,8 @@ void LLSingletonBase::capture_dependency(EInitState initState) LLSingletonBase* foundp(*found); out << classname(foundp) << " -> "; } - // We promise to capture dependencies from both the constructor - // and the initSingleton() method, so an LLSingleton's instance - // pointer is on the initializing list during both. Now that we've - // detected circularity, though, we must distinguish the two. If - // the recursive call is from the constructor, we CAN'T honor it: - // otherwise we'd be returning a pointer to a partially- - // constructed object! But from initSingleton() is okay: that - // method exists specifically to support circularity. // Decide which log helper to call. - if (initState == CONSTRUCTING) - { - logerrs("LLSingleton circularity in Constructor: ", out.str().c_str(), - classname(this).c_str(), ""); - } - else if (it_next == initializing.end()) + if (it_next == initializing.end()) { // Points to self after construction, but during initialization. // Singletons can initialize other classes that depend onto them, @@ -457,6 +444,19 @@ void LLSingletonBase::cleanupAll() } } +void LLSingletonBase::cleanup_() +{ + logdebugs("calling ", classname(this).c_str(), "::cleanupSingleton()"); + try + { + cleanupSingleton(); + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION(classname(this) + "::cleanupSingleton()"); + } +} + //static void LLSingletonBase::deleteAll() { @@ -536,6 +536,12 @@ void LLSingletonBase::logwarns(const char* p1, const char* p2, const char* p3, c log(LLError::LEVEL_WARN, p1, p2, p3, p4); } +//static +void LLSingletonBase::loginfos(const char* p1, const char* p2, const char* p3, const char* p4) +{ + log(LLError::LEVEL_INFO, p1, p2, p3, p4); +} + //static void LLSingletonBase::logerrs(const char* p1, const char* p2, const char* p3, const char* p4) { diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index ebae601029..4f3b8ceb38 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -31,6 +31,9 @@ #include #include #include "mutex.h" +#include "lockstatic.h" +#include "llthread.h" // on_main_thread() +#include "llmainthreadtask.h" class LLSingletonBase: private boost::noncopyable { @@ -105,7 +108,7 @@ protected: protected: // If a given call to B::getInstance() happens during either A::A() or // A::initSingleton(), record that A directly depends on B. - void capture_dependency(EInitState); + void capture_dependency(); // delegate LL_ERRS() logging to llsingleton.cpp static void logerrs(const char* p1, const char* p2="", @@ -113,6 +116,9 @@ protected: // delegate LL_WARNS() logging to llsingleton.cpp static void logwarns(const char* p1, const char* p2="", const char* p3="", const char* p4=""); + // delegate LL_INFOS() logging to llsingleton.cpp + static void loginfos(const char* p1, const char* p2="", + const char* p3="", const char* p4=""); static std::string demangle(const char* mangled); template static std::string classname() { return demangle(typeid(T).name()); } @@ -123,6 +129,9 @@ protected: virtual void initSingleton() {} virtual void cleanupSingleton() {} + // internal wrapper around calls to cleanupSingleton() + void cleanup_(); + // deleteSingleton() isn't -- and shouldn't be -- a virtual method. It's a // class static. However, given only Foo*, deleteAll() does need to be // able to reach Foo::deleteSingleton(). Make LLSingleton (which declares @@ -193,9 +202,9 @@ struct LLSingleton_manage_master { return LLSingletonBase::get_initializing_size(); } - void capture_dependency(LLSingletonBase* sb, LLSingletonBase::EInitState state) + void capture_dependency(LLSingletonBase* sb) { - sb->capture_dependency(state); + sb->capture_dependency(); } }; @@ -210,14 +219,14 @@ struct LLSingleton_manage_master // since we never pushed, no need to clean up void reset_initializing(LLSingletonBase::list_t::size_type size) {} LLSingletonBase::list_t::size_type get_initializing_size() { return 0; } - void capture_dependency(LLSingletonBase*, LLSingletonBase::EInitState) {} + void capture_dependency(LLSingletonBase*) {} }; // Now we can implement LLSingletonBase's template constructor. template LLSingletonBase::LLSingletonBase(tag): mCleaned(false), - mDeleteSingleton(NULL) + mDeleteSingleton(nullptr) { // This is the earliest possible point at which we can push this new // instance onto the init stack. LLSingleton::constructSingleton() can't @@ -295,65 +304,40 @@ template class LLSingleton : public LLSingletonBase { private: - // Allow LLParamSingleton subclass -- but NOT DERIVED_TYPE itself -- to - // access our private members. - friend class LLParamSingleton; - - // Scoped lock on the mutex associated with this LLSingleton - class Locker + // LLSingleton must have a distinct instance of + // SingletonData for every distinct DERIVED_TYPE. It's tempting to + // consider hoisting SingletonData up into LLSingletonBase. Don't do it. + struct SingletonData { - public: - Locker(): mLock(getMutex()) {} - - private: // Use a recursive_mutex in case of constructor circularity. With a // non-recursive mutex, that would result in deadlock. typedef std::recursive_mutex mutex_t; + mutex_t mMutex; // LockStatic looks for mMutex - // LLSingleton must have a distinct instance of sMutex for every - // distinct T. It's tempting to consider hoisting Locker up into - // LLSingletonBase. Don't do it. - // - // sMutex must be a function-local static rather than a static member. One - // of the essential features of LLSingleton and friends is that they must - // support getInstance() even when the containing module's static - // variables have not yet been runtime-initialized. A mutex requires - // construction. A static class member might not yet have been - // constructed. - // - // We could store a dumb mutex_t*, notice when it's NULL and allocate a - // heap mutex -- but that's vulnerable to race conditions. And we can't - // defend the dumb pointer with another mutex. - // - // We could store a std::atomic -- but a default-constructed - // std::atomic does not contain a valid T, even a default-constructed - // T! Which means std::atomic, too, requires runtime initialization. - // - // But a function-local static is guaranteed to be initialized exactly - // once, the first time control reaches that declaration. - static mutex_t& getMutex() - { - static mutex_t sMutex; - return sMutex; - } - - std::unique_lock mLock; + EInitState mInitState{UNINITIALIZED}; + DERIVED_TYPE* mInstance{nullptr}; }; + typedef llthread::LockStatic LockStatic; + + // Allow LLParamSingleton subclass -- but NOT DERIVED_TYPE itself -- to + // access our private members. + friend class LLParamSingleton; // LLSingleton only supports a nullary constructor. However, the specific // purpose for its subclass LLParamSingleton is to support Singletons // requiring constructor arguments. constructSingleton() supports both use // cases. + // Accepting LockStatic& requires that the caller has already locked our + // static data before calling. template - static void constructSingleton(Args&&... args) + static void constructSingleton(LockStatic& lk, Args&&... args) { auto prev_size = LLSingleton_manage_master().get_initializing_size(); - // getInstance() calls are from within constructor - sData.mInitState = CONSTRUCTING; + // Any getInstance() calls after this point are from within constructor + lk->mInitState = CONSTRUCTING; try { - sData.mInstance = new DERIVED_TYPE(std::forward(args)...); - // we have called constructor, have not yet called initSingleton() + lk->mInstance = new DERIVED_TYPE(std::forward(args)...); } catch (const std::exception& err) { @@ -367,55 +351,56 @@ private: // There isn't a separate EInitState value meaning "we attempted // to construct this LLSingleton subclass but could not," so use // DELETED. That seems slightly more appropriate than UNINITIALIZED. - sData.mInitState = DELETED; + lk->mInitState = DELETED; // propagate the exception throw; } - // getInstance() calls are from within initSingleton() - sData.mInitState = INITIALIZING; + // Any getInstance() calls after this point are from within initSingleton() + lk->mInitState = INITIALIZING; try { // initialize singleton after constructing it so that it can // reference other singletons which in turn depend on it, thus // breaking cyclic dependencies - sData.mInstance->initSingleton(); - sData.mInitState = INITIALIZED; + lk->mInstance->initSingleton(); + lk->mInitState = INITIALIZED; // pop this off stack of initializing singletons - pop_initializing(); + pop_initializing(lk->mInstance); } catch (const std::exception& err) { // pop this off stack of initializing singletons here, too -- // BEFORE logging, so log-machinery LLSingletons don't record a // dependency on DERIVED_TYPE! - pop_initializing(); + pop_initializing(lk->mInstance); logwarns("Error in ", classname().c_str(), "::initSingleton(): ", err.what()); - // and get rid of the instance entirely + // Get rid of the instance entirely. This call depends on our + // recursive_mutex. We could have a deleteSingleton(LockStatic&) + // overload and pass lk, but we don't strictly need it. deleteSingleton(); // propagate the exception throw; } } - static void pop_initializing() + static void pop_initializing(LLSingletonBase* sb) { // route through LLSingleton_manage_master so we Do The Right Thing // (namely, nothing) for MasterList - LLSingleton_manage_master().pop_initializing(sData.mInstance); + LLSingleton_manage_master().pop_initializing(sb); } - static void capture_dependency() + static void capture_dependency(LLSingletonBase* sb) { // By this point, if DERIVED_TYPE was pushed onto the initializing // stack, it has been popped off. So the top of that stack, if any, is // an LLSingleton that directly depends on DERIVED_TYPE. If // getInstance() was called by another LLSingleton, rather than from // vanilla application code, record the dependency. - LLSingleton_manage_master().capture_dependency( - sData.mInstance, sData.mInitState); + LLSingleton_manage_master().capture_dependency(sb); } // We know of no way to instruct the compiler that every subclass @@ -442,19 +427,6 @@ protected: LLSingleton_manage_master().add(this); } -protected: - virtual ~LLSingleton() - { - // In case racing threads call getInstance() at the same moment as - // this destructor, serialize the calls. - Locker lk; - - // remove this instance from the master list - LLSingleton_manage_master().remove(this); - sData.mInstance = NULL; - sData.mInitState = DELETED; - } - public: /** * @brief Immediately delete the singleton. @@ -479,54 +451,149 @@ public: */ static void deleteSingleton() { - delete sData.mInstance; - // SingletonData state handled by destructor, above + DERIVED_TYPE* lameduck; + { + LockStatic lk; + // Capture the instance and clear SingletonData. This sequence + // guards against the chance that the destructor throws, somebody + // catches it and there's a subsequent call to getInstance(). + lameduck = lk->mInstance; + lk->mInstance = nullptr; + lk->mInitState = DELETED; + // At this point we can safely unlock SingletonData during the + // remaining cleanup. If another thread calls deleteSingleton() (or + // getInstance(), or whatever) it won't find our instance, now + // referenced only as 'lameduck'. + } + // of course, only cleanup and delete if there's something there + if (lameduck) + { + // remove this instance from the master list BEFORE attempting + // cleanup so possible destructor exception won't leave the master + // list confused + LLSingleton_manage_master().remove(lameduck); + lameduck->cleanup_(); + delete lameduck; + } } static DERIVED_TYPE* getInstance() { - // In case racing threads call getInstance() at the same moment, - // serialize the calls. - Locker lk; + // We know the viewer has LLSingleton dependency circularities. If you + // feel strongly motivated to eliminate them, cheers and good luck. + // (At that point we could consider a much simpler locking mechanism.) - switch (sData.mInitState) - { - case CONSTRUCTING: - // here if DERIVED_TYPE's constructor (directly or indirectly) - // calls DERIVED_TYPE::getInstance() - logerrs("Tried to access singleton ", - classname().c_str(), - " from singleton constructor!"); - return NULL; + // If A and B depend on each other, and thread T1 requests A at the + // same moment thread T2 requests B, you could get a sequence like this: + // - T1 locks A + // - T2 locks B + // - T1, having constructed A, calls A::initSingleton(), which calls + // B::getInstance() and blocks on B's lock + // - T2, having constructed B, calls B::initSingleton(), which calls + // A::getInstance() and blocks on A's lock + // In other words, classic deadlock. - case UNINITIALIZED: - constructSingleton(); - break; + // Avoid that by constructing and initializing every LLSingleton on + // the main thread. In that scenario: + // - T1 locks A + // - T2 locks B + // - T1 discovers A is UNINITIALIZED, so it queues a task for the main + // thread, unlocks A and blocks on the std::future. + // - T2 discovers B is UNINITIALIZED, so it queues a task for the main + // thread, unlocks B and blocks on the std::future. + // - The main thread executes T1's request for A. It locks A and + // starts to construct it. + // - A::initSingleton() calls B::getInstance(). Fine: nobody's holding + // B's lock. + // - The main thread locks B, constructs B, calls B::initSingleton(), + // which calls A::getInstance(), which returns A. + // - B::getInstance() returns B to A::initSingleton(), unlocking B. + // - A::getInstance() returns A to the task wrapper, unlocking A. + // - The task wrapper passes A to T1 via the future. T1 resumes. + // - The main thread executes T2's request for B. Oh look, B already + // exists. The task wrapper passes B to T2 via the future. T2 + // resumes. + // This still works even if one of T1 or T2 *is* the main thread. + // This still works even if thread T3 requests B at the same moment as + // T2. Finding B still UNINITIALIZED, T3 also queues a task for the + // main thread, unlocks B and blocks on a (distinct) std::future. By + // the time the main thread executes T3's request for B, B already + // exists, and is simply delivered via the future. - case INITIALIZING: - // here if DERIVED_TYPE::initSingleton() (directly or indirectly) - // calls DERIVED_TYPE::getInstance(): go ahead and allow it - case INITIALIZED: - // normal subsequent calls - break; + { // nested scope for 'lk' + // In case racing threads call getInstance() at the same moment, + // serialize the calls. + LockStatic lk; - case DELETED: - // called after deleteSingleton() - logwarns("Trying to access deleted singleton ", - classname().c_str(), - " -- creating new instance"); - constructSingleton(); - break; - } + switch (lk->mInitState) + { + case CONSTRUCTING: + // here if DERIVED_TYPE's constructor (directly or indirectly) + // calls DERIVED_TYPE::getInstance() + logerrs("Tried to access singleton ", + classname().c_str(), + " from singleton constructor!"); + return nullptr; - // record the dependency, if any: check if we got here from another - // LLSingleton's constructor or initSingleton() method - capture_dependency(); - return sData.mInstance; + case INITIALIZING: + // here if DERIVED_TYPE::initSingleton() (directly or indirectly) + // calls DERIVED_TYPE::getInstance(): go ahead and allow it + case INITIALIZED: + // normal subsequent calls + // record the dependency, if any: check if we got here from another + // LLSingleton's constructor or initSingleton() method + capture_dependency(lk->mInstance); + return lk->mInstance; + + case DELETED: + // called after deleteSingleton() + logwarns("Trying to access deleted singleton ", + classname().c_str(), + " -- creating new instance"); + // fall through + case UNINITIALIZED: + break; + } + + // Here we need to construct a new instance. + if (on_main_thread()) + { + // On the main thread, directly construct the instance while + // holding the lock. + constructSingleton(lk); + capture_dependency(lk->mInstance); + return lk->mInstance; + } + } // unlock 'lk' + + // Here we need to construct a new instance, but we're on a secondary + // thread. Per the comment block above, dispatch to the main thread. + loginfos(classname().c_str(), + "::getInstance() dispatching to main thread"); + auto instance = LLMainThreadTask::dispatch( + [](){ + // VERY IMPORTANT to call getInstance() on the main thread, + // rather than going straight to constructSingleton()! + // During the time window before mInitState is INITIALIZED, + // multiple requests might be queued. It's essential that, as + // the main thread processes them, only the FIRST such request + // actually constructs the instance -- every subsequent one + // simply returns the existing instance. + loginfos(classname().c_str(), + "::getInstance() on main thread"); + return getInstance(); + }); + // record the dependency chain tracked on THIS thread, not the main + // thread (consider a getInstance() overload with a tag param that + // suppresses dep tracking when dispatched to the main thread) + capture_dependency(instance); + loginfos(classname().c_str(), + "::getInstance() returning on invoking thread"); + return instance; } // Reference version of getInstance() - // Preferred over getInstance() as it disallows checking for NULL + // Preferred over getInstance() as it disallows checking for nullptr static DERIVED_TYPE& instance() { return *getInstance(); @@ -537,8 +604,8 @@ public: static bool instanceExists() { // defend any access to sData from racing threads - Locker lk; - return sData.mInitState == INITIALIZED; + LockStatic lk; + return lk->mInitState == INITIALIZED; } // Has this singleton been deleted? This can be useful during shutdown @@ -547,24 +614,11 @@ public: static bool wasDeleted() { // defend any access to sData from racing threads - Locker lk; - return sData.mInitState == DELETED; + LockStatic lk; + return lk->mInitState == DELETED; } - -private: - struct SingletonData - { - // explicitly has a default constructor so that member variables are zero initialized in BSS - // and only changed by singleton logic, not constructor running during startup - EInitState mInitState; - DERIVED_TYPE* mInstance; - }; - static SingletonData sData; }; -template -typename LLSingleton::SingletonData LLSingleton::sData; - /** * LLParamSingleton is like LLSingleton, except in the following ways: @@ -589,7 +643,7 @@ class LLParamSingleton : public LLSingleton { private: typedef LLSingleton super; - using typename super::Locker; + using typename super::LockStatic; public: using super::deleteSingleton; @@ -603,9 +657,9 @@ public: // In case racing threads both call initParamSingleton() at the same // time, serialize them. One should initialize; the other should see // mInitState already set. - Locker lk; + LockStatic lk; // For organizational purposes this function shouldn't be called twice - if (super::sData.mInitState != super::UNINITIALIZED) + if (lk->mInitState != super::UNINITIALIZED) { super::logerrs("Tried to initialize singleton ", super::template classname().c_str(), @@ -613,7 +667,7 @@ public: } else { - super::constructSingleton(std::forward(args)...); + super::constructSingleton(lk, std::forward(args)...); } } @@ -621,9 +675,9 @@ public: { // In case racing threads call getInstance() at the same moment as // initParamSingleton(), serialize the calls. - Locker lk; + LockStatic lk; - switch (super::sData.mInitState) + switch (lk->mInitState) { case super::UNINITIALIZED: super::logerrs("Uninitialized param singleton ", @@ -641,8 +695,8 @@ public: // within initSingleton() case super::INITIALIZED: // for any valid call, capture dependencies - super::capture_dependency(); - return super::sData.mInstance; + super::capture_dependency(lk->mInstance); + return lk->mInstance; case super::DELETED: super::logerrs("Trying to access deleted param singleton ", diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index 75ddff9d7d..15ffe68e67 100644 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -143,8 +143,6 @@ namespace tut \ (void)CLS::instance(); \ ensure_equals(sLog, #CLS "i" #CLS); \ - LLSingletonBase::cleanupAll(); \ - ensure_equals(sLog, #CLS "i" #CLS "x" #CLS); \ LLSingletonBase::deleteAll(); \ ensure_equals(sLog, #CLS "i" #CLS "x" #CLS "~" #CLS); \ } \ @@ -159,10 +157,8 @@ namespace tut \ (void)CLS::instance(); \ ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS); \ - LLSingletonBase::cleanupAll(); \ - ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER); \ LLSingletonBase::deleteAll(); \ - ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \ } \ \ template<> template<> \ @@ -175,10 +171,8 @@ namespace tut \ (void)CLS::instance(); \ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \ - LLSingletonBase::cleanupAll(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \ LLSingletonBase::deleteAll(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \ } \ \ template<> template<> \ @@ -191,10 +185,8 @@ namespace tut \ (void)CLS::instance(); \ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \ - LLSingletonBase::cleanupAll(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \ LLSingletonBase::deleteAll(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \ } TESTS(A, B, 4, 5, 6, 7) From fb22aab59f1a967de3e9e0940791400187f8234d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Dec 2019 16:19:23 -0500 Subject: [PATCH 29/48] DRTVWR-494: LLParamSingleton::initParamSingleton() now returns T*. So does LLLockedSingleton::construct(). --- indra/llcommon/llsingleton.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 4f3b8ceb38..39d0e9b013 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -650,9 +650,15 @@ public: using super::instanceExists; using super::wasDeleted; - // Passes arguments to DERIVED_TYPE's constructor and sets appropriate states + // Passes arguments to DERIVED_TYPE's constructor and sets appropriate + // states. We'd rather return a reference than a pointer, but the test for + // redundant calls makes that awkward. The compiler, unaware that + // logerrs() won't return, requires that that alternative return + // *something*. But what? It can't be a dummy static instance because + // there should be only one instance of any LLSingleton subclass! Easier + // to allow that case to return nullptr. template - static void initParamSingleton(Args&&... args) + static DERIVED_TYPE* initParamSingleton(Args&&... args) { // In case racing threads both call initParamSingleton() at the same // time, serialize them. One should initialize; the other should see @@ -664,10 +670,12 @@ public: super::logerrs("Tried to initialize singleton ", super::template classname().c_str(), " twice!"); + return nullptr; } else { super::constructSingleton(lk, std::forward(args)...); + return lk->mInstance; } } @@ -740,9 +748,9 @@ public: using super::instanceExists; using super::wasDeleted; - static void construct() + static DT* construct() { - super::initParamSingleton(); + return super::initParamSingleton(); } }; From d0235180e44e3171c6f06559e5aae4b2bc02c4bd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Dec 2019 17:17:06 -0500 Subject: [PATCH 30/48] DRTVWR-494: LLParamSingleton::initParamSingleton() on main thread. When calling LLParamSingleton::initParamSingleton() on a secondary thread, use LLMainThreadTask::dispatch() to construct the instance on the main thread -- as with LLSingleton::getInstance(). --- indra/llcommon/llsingleton.h | 47 +++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 39d0e9b013..314ee2caa8 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -59,6 +59,7 @@ protected: typedef enum e_init_state { UNINITIALIZED = 0, // must be default-initialized state + QUEUED, // construction queued, not yet executing CONSTRUCTING, // within DERIVED_TYPE constructor INITIALIZING, // within DERIVED_TYPE::initSingleton() INITIALIZED, // normal case @@ -552,6 +553,11 @@ public: " -- creating new instance"); // fall through case UNINITIALIZED: + case QUEUED: + // QUEUED means some secondary thread has already requested an + // instance, but for present purposes that's semantically + // identical to UNINITIALIZED: either way, we must ourselves + // request an instance. break; } @@ -564,10 +570,13 @@ public: capture_dependency(lk->mInstance); return lk->mInstance; } + + // Here we need to construct a new instance, but we're on a secondary + // thread. + lk->mInitState = QUEUED; } // unlock 'lk' - // Here we need to construct a new instance, but we're on a secondary - // thread. Per the comment block above, dispatch to the main thread. + // Per the comment block above, dispatch to the main thread. loginfos(classname().c_str(), "::getInstance() dispatching to main thread"); auto instance = LLMainThreadTask::dispatch( @@ -588,7 +597,7 @@ public: // suppresses dep tracking when dispatched to the main thread) capture_dependency(instance); loginfos(classname().c_str(), - "::getInstance() returning on invoking thread"); + "::getInstance() returning on requesting thread"); return instance; } @@ -672,11 +681,40 @@ public: " twice!"); return nullptr; } - else + else if (on_main_thread()) { + // on the main thread, simply construct instance while holding lock super::constructSingleton(lk, std::forward(args)...); return lk->mInstance; } + else + { + // on secondary thread, dispatch to main thread -- + // set state so we catch any other calls before the main thread + // picks up the task + lk->mInitState = super::QUEUED; + // very important to unlock here so main thread can actually process + lk.unlock(); + super::loginfos(super::template classname().c_str(), + "::initParamSingleton() dispatching to main thread"); + // Normally it would be the height of folly to reference-bind + // 'args' into a lambda to be executed on some other thread! By + // the time that thread executed the lambda, the references would + // all be dangling, and Bad Things would result. But + // LLMainThreadTask::dispatch() promises to block until the passed + // task has completed. So in this case we know the references will + // remain valid until the lambda has run, so we dare to bind + // references. + auto instance = LLMainThreadTask::dispatch( + [&](){ + super::loginfos(super::template classname().c_str(), + "::initParamSingleton() on main thread"); + return initParamSingleton(std::forward(args)...); + }); + super::loginfos(super::template classname().c_str(), + "::initParamSingleton() returning on requesting thread"); + return instance; + } } static DERIVED_TYPE* getInstance() @@ -688,6 +726,7 @@ public: switch (lk->mInitState) { case super::UNINITIALIZED: + case super::QUEUED: super::logerrs("Uninitialized param singleton ", super::template classname().c_str()); break; From f6a075a701cf9d3a86d09acf92637534c1433071 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Dec 2019 17:23:54 -0500 Subject: [PATCH 31/48] DRTVWR-494: LLParamSingleton::initParamSingleton() returns reference. --- indra/llcommon/llsingleton.h | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 314ee2caa8..0c11e54910 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -654,20 +654,10 @@ private: typedef LLSingleton super; using typename super::LockStatic; -public: - using super::deleteSingleton; - using super::instanceExists; - using super::wasDeleted; - // Passes arguments to DERIVED_TYPE's constructor and sets appropriate - // states. We'd rather return a reference than a pointer, but the test for - // redundant calls makes that awkward. The compiler, unaware that - // logerrs() won't return, requires that that alternative return - // *something*. But what? It can't be a dummy static instance because - // there should be only one instance of any LLSingleton subclass! Easier - // to allow that case to return nullptr. + // states, returning a pointer to the new instance. template - static DERIVED_TYPE* initParamSingleton(Args&&... args) + static DERIVED_TYPE* initParamSingleton_(Args&&... args) { // In case racing threads both call initParamSingleton() at the same // time, serialize them. One should initialize; the other should see @@ -709,7 +699,7 @@ public: [&](){ super::loginfos(super::template classname().c_str(), "::initParamSingleton() on main thread"); - return initParamSingleton(std::forward(args)...); + return initParamSingleton_(std::forward(args)...); }); super::loginfos(super::template classname().c_str(), "::initParamSingleton() returning on requesting thread"); @@ -717,6 +707,19 @@ public: } } +public: + using super::deleteSingleton; + using super::instanceExists; + using super::wasDeleted; + + /// initParamSingleton() constructs the instance, returning a reference. + /// Pass whatever arguments are required to construct DERIVED_TYPE. + template + static DERIVED_TYPE& initParamSingleton(Args&&... args) + { + return *initParamSingleton_(std::forward(args)...); + } + static DERIVED_TYPE* getInstance() { // In case racing threads call getInstance() at the same moment as From 7e8dc5284027a4dff9e2f6e2a82ce4d1fe66814b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Dec 2019 08:44:51 -0500 Subject: [PATCH 32/48] DRTVWR-494: Fix Windows macro collisions in windows_volume_catcher by tweaking #include order. --- indra/media_plugins/cef/windows_volume_catcher.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indra/media_plugins/cef/windows_volume_catcher.cpp b/indra/media_plugins/cef/windows_volume_catcher.cpp index 6953ad3ab8..7a36123a11 100644 --- a/indra/media_plugins/cef/windows_volume_catcher.cpp +++ b/indra/media_plugins/cef/windows_volume_catcher.cpp @@ -27,8 +27,9 @@ */ #include "volume_catcher.h" -#include #include "llsingleton.h" +#include +#include class VolumeCatcherImpl : public LLSingleton { LLSINGLETON(VolumeCatcherImpl); From f4df26308fb05b7ca7fb7eeef758308d990ba4cf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Dec 2019 13:47:11 -0500 Subject: [PATCH 33/48] DRTVWR-476: Update viewer-manager to codeticket version 533794. --- autobuild.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index a92e9b516f..0487e9e489 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -3150,9 +3150,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 9b341f9a6f36eb595ef2ea88425add21 + 1ba3fb47e32eda07b9308babb278ee69 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47032/414972/viewer_manager-2.0.533067-darwin64-533067.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48017/426433/viewer_manager-2.0.533794-darwin64-533794.tar.bz2 name darwin64 @@ -3174,9 +3174,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - ef076d8d018c55e25124b7a577bae132 + 65bd60bf3fe8a9d6f8efc230dcf3bd6b url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47034/414989/viewer_manager-2.0.533067-windows-533067.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48018/426440/viewer_manager-2.0.533794-windows-533794.tar.bz2 name windows @@ -3187,7 +3187,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors source_type hg version - 2.0.533067 + 2.0.533794 vlc-bin From 66e533be815660886cf38a60f81bf71418242f86 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Dec 2019 14:12:42 -0500 Subject: [PATCH 34/48] DRTVWR-476: Fix merge glitch. --- indra/llcommon/llsingleton.cpp | 51 ---------------------------------- 1 file changed, 51 deletions(-) diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index 0d741f0ffd..1029c4894b 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -120,40 +120,6 @@ private: // This local_ptr isn't static because it's a member of an LLSingleton. LLCoros::local_ptr mInitializing; -public: - // Instantiate this to obtain a reference to the coroutine-specific - // initializing list and to hold the MasterList lock for the lifespan of - // this LockedInitializing instance. - struct LockedInitializing: public Lock - { - public: - LockedInitializing(): - // only do the lookup once, cache the result - // note that the lock is already locked during this lookup - mList(&mMasterList.get_initializing_()) - {} - list_t& get() const - { - if (! mList) - { - LLTHROW(std::runtime_error("Trying to use LockedInitializing " - "after cleanup_initializing()")); - } - return *mList; - } - operator list_t&() const { return get(); } - void log(const char* verb, const char* name); - void cleanup_initializing() - { - mMasterList.cleanup_initializing_(); - mList = nullptr; - } - - private: - // Store pointer since cleanup_initializing() must clear it. - list_t* mList; - }; - public: // Instantiate this to obtain a reference to the coroutine-specific // initializing list and to hold the MasterList lock for the lifespan of @@ -441,23 +407,6 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort() return ret; } -void LLSingletonBase::cleanup_() -{ - logdebugs("calling ", classname(this).c_str(), "::cleanupSingleton()"); - try - { - cleanupSingleton(); - } - catch (const std::exception& e) - { - logwarns("Exception in ", classname(this).c_str(), "::cleanupSingleton(): ", e.what()); - } - catch (...) - { - logwarns("Unknown exception in ", classname(this).c_str(), "::cleanupSingleton()"); - } -} - void LLSingletonBase::cleanup_() { logdebugs("calling ", classname(this).c_str(), "::cleanupSingleton()"); From ac6335f75099f02caa0d7626a70591a8c5af7150 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Dec 2019 14:20:19 -0500 Subject: [PATCH 35/48] DRTVWR-476: Update LLMainThreadTask tests for simpler API. --- indra/llcommon/tests/llmainthreadtask_test.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/indra/llcommon/tests/llmainthreadtask_test.cpp b/indra/llcommon/tests/llmainthreadtask_test.cpp index e54c7eda18..8178aa629a 100644 --- a/indra/llcommon/tests/llmainthreadtask_test.cpp +++ b/indra/llcommon/tests/llmainthreadtask_test.cpp @@ -75,14 +75,11 @@ namespace tut // exceptions it might throw and deliver them via future std::packaged_task thread_work( [this, &result](){ - // lock static data first - LockStatic lk; // unblock test<2>()'s yield_until(1) mSync.set(1); // dispatch work to main thread -- should block here bool on_main( LLMainThreadTask::dispatch( - lk, // unlock this before blocking! []()->bool{ // have to lock static mutex to set static data LockStatic()->ran = true; From 49d978bec286936f2f9f5abea7b243f44404613e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Dec 2019 15:24:56 -0500 Subject: [PATCH 36/48] DRTVWR-476: Log calls to LLParamSingleton::initParamSingleton(). --- indra/llcommon/llsingleton.cpp | 12 ++++++------ indra/llcommon/llsingleton.h | 8 +++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index 1029c4894b..57b30a96ce 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -43,8 +43,6 @@ namespace { void log(LLError::ELevel level, const char* p1, const char* p2, const char* p3, const char* p4); -void logdebugs(const char* p1="", const char* p2="", const char* p3="", const char* p4=""); - bool oktolog(); } // anonymous namespace @@ -487,10 +485,6 @@ void log(LLError::ELevel level, } } -void logdebugs(const char* p1, const char* p2, const char* p3, const char* p4) -{ - log(LLError::LEVEL_DEBUG, p1, p2, p3, p4); -} } // anonymous namespace //static @@ -505,6 +499,12 @@ void LLSingletonBase::loginfos(const char* p1, const char* p2, const char* p3, c log(LLError::LEVEL_INFO, p1, p2, p3, p4); } +//static +void LLSingletonBase::logdebugs(const char* p1, const char* p2, const char* p3, const char* p4) +{ + log(LLError::LEVEL_DEBUG, p1, p2, p3, p4); +} + //static void LLSingletonBase::logerrs(const char* p1, const char* p2, const char* p3, const char* p4) { diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index c5ba0ca5a1..04c2ea210f 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -110,15 +110,15 @@ protected: // A::initSingleton(), record that A directly depends on B. void capture_dependency(); - // delegate LL_ERRS() logging to llsingleton.cpp + // delegate logging calls to llsingleton.cpp static void logerrs(const char* p1, const char* p2="", const char* p3="", const char* p4=""); - // delegate LL_WARNS() logging to llsingleton.cpp static void logwarns(const char* p1, const char* p2="", const char* p3="", const char* p4=""); - // delegate LL_INFOS() logging to llsingleton.cpp static void loginfos(const char* p1, const char* p2="", const char* p3="", const char* p4=""); + static void logdebugs(const char* p1, const char* p2="", + const char* p3="", const char* p4=""); static std::string demangle(const char* mangled); template static std::string classname() { return demangle(typeid(T).name()); } @@ -698,6 +698,8 @@ private: else if (on_main_thread()) { // on the main thread, simply construct instance while holding lock + super::logdebugs(super::template classname().c_str(), + "::initParamSingleton()"); super::constructSingleton(lk, std::forward(args)...); return lk->mInstance; } From 57d20b86bbadefff46a8f3f02eb06db2ff107f4c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 16 Dec 2019 15:12:52 -0500 Subject: [PATCH 37/48] DRTVWR-476: Update boost, colladadom, googlemock Update boost to codeticket version 533856. Update colladadom to codeticket version 533872. Update googlemock to codeticket version 533873. --- autobuild.xml | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 0487e9e489..43f730c564 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -166,9 +166,9 @@ archive hash - 4d2bc845faac0e92a1e57568ef1f2aa0 + 1e7a892d237096c0c252dc26bb0bcf1d url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47847/424736/boost-1.67-darwin64-533684.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48114/427588/boost-1.67-darwin64-533856.tar.bz2 name darwin64 @@ -202,9 +202,9 @@ archive hash - 590c49fdc253ad59249663d3b0b3adf7 + 3fdcf88309a340c5e0b6db7be5182c1a url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47852/424591/boost-1.67-windows-533684.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48105/427355/boost-1.67-windows-533856.tar.bz2 name windows @@ -214,9 +214,9 @@ archive hash - a53f7a651ae8c8b5ab71f53bbd6a59b8 + ba2bd6b732d32b6feff6c329cff67041 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47848/424582/boost-1.67-windows64-533684.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48116/427536/boost-1.67-windows64-533856.tar.bz2 name windows64 @@ -308,9 +308,9 @@ archive hash - 5aad6e709fa930e762c4826adc765991 + ca6bb7108b8cf4bac4c4acac515b9b5f url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47874/424841/colladadom-2.3.533705-darwin64-533705.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48135/427607/colladadom-2.3.533872-darwin64-533872.tar.bz2 name darwin64 @@ -344,9 +344,9 @@ archive hash - 2aa5ae3604b78e809eadd1eb90b4425c + 2a0161276047081ac27e2b5c260e61d8 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47880/424864/colladadom-2.3.533705-windows-533705.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48138/427646/colladadom-2.3.533872-windows-533872.tar.bz2 name windows @@ -356,16 +356,16 @@ archive hash - 952284bdb45d76a87914e32ae43f30c8 + b0d938c122f1e3a2ab5fdbb33bae165c url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47875/424857/colladadom-2.3.533705-windows64-533705.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48134/427621/colladadom-2.3.533872-windows64-533872.tar.bz2 name windows64 version - 2.3.533705 + 2.3.533872 curl @@ -1272,9 +1272,9 @@ archive hash - d9199dac32c05ff324cbde896d367c9b + cfa0065ee16facb7e4372bd9be834124 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47876/424848/googlemock-1.7.0.533706-darwin64-533706.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48136/427614/googlemock-1.7.0.533873-darwin64-533873.tar.bz2 name darwin64 @@ -1308,9 +1308,9 @@ archive hash - cbd0d82ec309e79a43a4c9871f56dca9 + f36799013f5f5dcc07526b55902f2188 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47882/424878/googlemock-1.7.0.533706-windows-533706.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48144/427663/googlemock-1.7.0.533873-windows-533873.tar.bz2 name windows @@ -1320,16 +1320,16 @@ archive hash - 1470ce12427a5b585038200d14a56ef6 + 9722f105a90eddb6142aef6c7b42eb18 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47881/424871/googlemock-1.7.0.533706-windows64-533706.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48141/427655/googlemock-1.7.0.533873-windows64-533873.tar.bz2 name windows64 version - 1.7.0.533706 + 1.7.0.533873 gstreamer From b2d0fb4160ed4f7505c0a079001f4a7bb9caaacc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 17 Dec 2019 15:42:34 -0500 Subject: [PATCH 38/48] DRTVWR-494: Move most LLSingleton cleanup back to destructor instead of deleteSingleton(). Specifically, clear static SingletonData and remove the instance from the MasterList in the destructor. Empirically, some consumers are manually deleting LLSingleton instances, instead of calling deleteSingleton(). If deleteSingleton() handles cleanup rather than the destructor, we're left with dangling pointers in the Master List. We don't also call cleanupSingleton() from the destructor because only deleteSingleton() promises to call cleanupSingleton(). Hopefully whoever is directly deleting an LLSingleton subclass instance isn't relying on cleanupSingleton(). --- indra/llcommon/llsingleton.cpp | 6 ++--- indra/llcommon/llsingleton.h | 44 ++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index bf594f122c..4c76206d8d 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -384,7 +384,7 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort() SingletonDeps sdeps; // Lock while traversing the master list MasterList::LockedMaster master; - BOOST_FOREACH(LLSingletonBase* sp, master.get()) + for (LLSingletonBase* sp : master.get()) { // Build the SingletonDeps structure by adding, for each // LLSingletonBase* sp in the master list, sp itself. It has no @@ -401,14 +401,14 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort() // extracts just the first (key) element from each sorted_iterator, then // uses vec_t's range constructor... but frankly this is more // straightforward, as long as we remember the above reserve() call! - BOOST_FOREACH(SingletonDeps::sorted_iterator::value_type pair, sdeps.sort()) + for (const SingletonDeps::sorted_iterator::value_type& pair : sdeps.sort()) { ret.push_back(pair.first); } // The master list is not itself pushed onto the master list. Add it as // the very last entry -- it is the LLSingleton on which ALL others // depend! -- so our caller will process it. - ret.push_back(MasterList::getInstance()); + ret.push_back(&master.Lock::get()); return ret; } diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 0c11e54910..5ee40a658a 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -428,6 +428,21 @@ protected: LLSingleton_manage_master().add(this); } +protected: + virtual ~LLSingleton() + { + // This phase of cleanup is performed in the destructor rather than in + // deleteSingleton() to defend against manual deletion. When we moved + // cleanup to deleteSingleton(), we hit crashes due to dangling + // pointers in the MasterList. + LockStatic lk; + lk->mInstance = nullptr; + lk->mInitState = DELETED; + + // Remove this instance from the master list. + LLSingleton_manage_master().remove(this); + } + public: /** * @brief Immediately delete the singleton. @@ -452,29 +467,16 @@ public: */ static void deleteSingleton() { - DERIVED_TYPE* lameduck; - { - LockStatic lk; - // Capture the instance and clear SingletonData. This sequence - // guards against the chance that the destructor throws, somebody - // catches it and there's a subsequent call to getInstance(). - lameduck = lk->mInstance; - lk->mInstance = nullptr; - lk->mInitState = DELETED; - // At this point we can safely unlock SingletonData during the - // remaining cleanup. If another thread calls deleteSingleton() (or - // getInstance(), or whatever) it won't find our instance, now - // referenced only as 'lameduck'. - } + // Hold the lock while we call cleanupSingleton() and the destructor. + // Our destructor also instantiates LockStatic, requiring a recursive + // mutex. + LockStatic lk; // of course, only cleanup and delete if there's something there - if (lameduck) + if (lk->mInstance) { - // remove this instance from the master list BEFORE attempting - // cleanup so possible destructor exception won't leave the master - // list confused - LLSingleton_manage_master().remove(lameduck); - lameduck->cleanup_(); - delete lameduck; + lk->mInstance->cleanup_(); + delete lk->mInstance; + // destructor clears mInstance (and mInitState) } } From 682eaf6a13ca921470d23e4bb460f89b40773971 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 17 Dec 2019 15:49:58 -0500 Subject: [PATCH 39/48] DRTVWR-476: Remove LLSingleton::Locker, leftover from merge. --- indra/llcommon/llsingleton.h | 41 ------------------------------------ 1 file changed, 41 deletions(-) diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 04d7a27d3e..1557582c06 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -314,47 +314,6 @@ private: // access our private members. friend class LLParamSingleton; - // Scoped lock on the mutex associated with this LLSingleton - class Locker - { - public: - Locker(): mLock(getMutex()) {} - - private: - // Use a recursive_mutex in case of constructor circularity. With a - // non-recursive mutex, that would result in deadlock. - typedef std::recursive_mutex mutex_t; - - // LLSingleton must have a distinct instance of sMutex for every - // distinct T. It's tempting to consider hoisting Locker up into - // LLSingletonBase. Don't do it. - // - // sMutex must be a function-local static rather than a static member. One - // of the essential features of LLSingleton and friends is that they must - // support getInstance() even when the containing module's static - // variables have not yet been runtime-initialized. A mutex requires - // construction. A static class member might not yet have been - // constructed. - // - // We could store a dumb mutex_t*, notice when it's NULL and allocate a - // heap mutex -- but that's vulnerable to race conditions. And we can't - // defend the dumb pointer with another mutex. - // - // We could store a std::atomic -- but a default-constructed - // std::atomic does not contain a valid T, even a default-constructed - // T! Which means std::atomic, too, requires runtime initialization. - // - // But a function-local static is guaranteed to be initialized exactly - // once, the first time control reaches that declaration. - static mutex_t& getMutex() - { - static mutex_t sMutex; - return sMutex; - } - - std::unique_lock mLock; - }; - // LLSingleton only supports a nullary constructor. However, the specific // purpose for its subclass LLParamSingleton is to support Singletons // requiring constructor arguments. constructSingleton() supports both use From 632782e7ef513d014c1a636cddd5104c1a0fa460 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 18 Dec 2019 12:25:45 -0500 Subject: [PATCH 40/48] DRTVWR-494: Get initialized LLMutexes for very early log calls. Use function-static LLMutex instances instead of module-static instances, since some log calls are evidently issued before we get around to initializing llerror.cpp module-static variables. --- indra/llcommon/llerror.cpp | 39 +++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 83d380fafd..4bf4827119 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -1155,8 +1155,25 @@ namespace } namespace { - LLMutex gLogMutex; - LLMutex gCallStacksLogMutex; + // We need a couple different mutexes, but we want to use the same mechanism + // for both. Make getMutex() a template function with different instances + // for different MutexDiscriminator values. + enum MutexDiscriminator + { + LOG_MUTEX, + STACKS_MUTEX + }; + // Some logging calls happen very early in processing -- so early that our + // module-static variables aren't yet initialized. getMutex() wraps a + // function-static LLMutex so that early calls can still have a valid + // LLMutex instance. + template + LLMutex* getMutex() + { + // guaranteed to be initialized the first time control reaches here + static LLMutex sMutex; + return &sMutex; + } bool checkLevelMap(const LevelMap& map, const std::string& key, LLError::ELevel& level) @@ -1204,7 +1221,7 @@ namespace LLError bool Log::shouldLog(CallSite& site) { - LLMutexTrylock lock(&gLogMutex, 5); + LLMutexTrylock lock(getMutex(), 5); if (!lock.isLocked()) { return false; @@ -1255,7 +1272,7 @@ namespace LLError std::ostringstream* Log::out() { - LLMutexTrylock lock(&gLogMutex,5); + LLMutexTrylock lock(getMutex(),5); // If we hit a logging request very late during shutdown processing, // when either of the relevant LLSingletons has already been deleted, // DO NOT resurrect them. @@ -1275,7 +1292,7 @@ namespace LLError void Log::flush(std::ostringstream* out, char* message) { - LLMutexTrylock lock(&gLogMutex,5); + LLMutexTrylock lock(getMutex(),5); if (!lock.isLocked()) { return; @@ -1315,7 +1332,7 @@ namespace LLError void Log::flush(std::ostringstream* out, const CallSite& site) { - LLMutexTrylock lock(&gLogMutex,5); + LLMutexTrylock lock(getMutex(),5); if (!lock.isLocked()) { return; @@ -1514,7 +1531,7 @@ namespace LLError //static void LLCallStacks::push(const char* function, const int line) { - LLMutexTrylock lock(&gCallStacksLogMutex, 5); + LLMutexTrylock lock(getMutex(), 5); if (!lock.isLocked()) { return; @@ -1549,7 +1566,7 @@ namespace LLError //static void LLCallStacks::end(std::ostringstream* _out) { - LLMutexTrylock lock(&gCallStacksLogMutex, 5); + LLMutexTrylock lock(getMutex(), 5); if (!lock.isLocked()) { return; @@ -1565,13 +1582,13 @@ namespace LLError clear() ; } - LLError::Log::flush(_out, sBuffer[sIndex++]) ; + LLError::Log::flush(_out, sBuffer[sIndex++]) ; } //static void LLCallStacks::print() { - LLMutexTrylock lock(&gCallStacksLogMutex, 5); + LLMutexTrylock lock(getMutex(), 5); if (!lock.isLocked()) { return; @@ -1609,7 +1626,7 @@ namespace LLError bool debugLoggingEnabled(const std::string& tag) { - LLMutexTrylock lock(&gLogMutex, 5); + LLMutexTrylock lock(getMutex(), 5); if (!lock.isLocked()) { return false; From a851a0c1305e8120c6aff6018ffca417fc225f6d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 18 Dec 2019 12:27:05 -0500 Subject: [PATCH 41/48] DRTVWR-494: Avoid keeping iterator to destroyed temporary container. --- indra/llwindow/llwindow.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp index 40e297bac1..d77997a928 100644 --- a/indra/llwindow/llwindow.cpp +++ b/indra/llwindow/llwindow.cpp @@ -457,9 +457,8 @@ LLCoordCommon LL_COORD_TYPE_WINDOW::convertToCommon() const { const LLCoordWindow& self = LLCoordWindow::getTypedCoords(*this); - auto windowit = LLWindow::instance_snapshot().begin(); LLCoordGL out; - windowit->convertCoords(self, &out); + LLWindow::instance_snapshot().begin()->convertCoords(self, &out); return out.convert(); } @@ -467,18 +466,16 @@ void LL_COORD_TYPE_WINDOW::convertFromCommon(const LLCoordCommon& from) { LLCoordWindow& self = LLCoordWindow::getTypedCoords(*this); - auto windowit = LLWindow::instance_snapshot().begin(); LLCoordGL from_gl(from); - windowit->convertCoords(from_gl, &self); + LLWindow::instance_snapshot().begin()->convertCoords(from_gl, &self); } LLCoordCommon LL_COORD_TYPE_SCREEN::convertToCommon() const { const LLCoordScreen& self = LLCoordScreen::getTypedCoords(*this); - auto windowit = LLWindow::instance_snapshot().begin(); LLCoordGL out; - windowit->convertCoords(self, &out); + LLWindow::instance_snapshot().begin()->convertCoords(self, &out); return out.convert(); } @@ -486,7 +483,6 @@ void LL_COORD_TYPE_SCREEN::convertFromCommon(const LLCoordCommon& from) { LLCoordScreen& self = LLCoordScreen::getTypedCoords(*this); - auto windowit = LLWindow::instance_snapshot().begin(); LLCoordGL from_gl(from); - windowit->convertCoords(from_gl, &self); + LLWindow::instance_snapshot().begin()->convertCoords(from_gl, &self); } From bd008a170898e69beeb8bc0481b4bfd94de86c55 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 18 Dec 2019 13:03:00 -0500 Subject: [PATCH 42/48] DRTVWR-476: Re-enable LLInstanceTracker tests disabled months ago. --- indra/llcommon/tests/llinstancetracker_test.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index fb6eb5d3b3..9b89159625 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -183,7 +183,7 @@ namespace tut ensure_equals("unreported instance", instances.size(), 0); } - /* + template<> template<> void object::test<5>() { @@ -199,7 +199,7 @@ namespace tut // two values to std::ostream ensure(snapshot.begin() == snapshot.end()); } - + template<> template<> void object::test<6>() { @@ -231,7 +231,7 @@ namespace tut // two values to std::ostream ensure(snapshot.begin() == snapshot.end()); } - + template<> template<> void object::test<8>() { @@ -270,5 +270,5 @@ namespace tut { ensure("failed to remove instance", existing.find(&ref) != existing.end()); } - }*/ + } } // namespace tut From 82bdbd5e41a11a6f0b9e4350df3c0543a309bafb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 18 Dec 2019 13:18:37 -0500 Subject: [PATCH 43/48] DRTVWR-476: Reinstate operator<<(std::ostream, LLError::LLStacktrace) which was accidentally deleted by a merge. --- indra/llcommon/llerror.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 2e0488d9ce..b5d617f15a 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -1679,6 +1679,11 @@ namespace LLError { freeStackBuffer(); } + + std::ostream& operator<<(std::ostream& out, const LLStacktrace&) + { + return out << boost::stacktrace::stacktrace(); + } } bool debugLoggingEnabled(const std::string& tag) From 1bd2a91d00086915bb2e0d09328dbb6bd638f2fb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 18 Dec 2019 15:29:23 -0500 Subject: [PATCH 44/48] DRTVWR-476: LLChannelManager depends on LLUI. Tell LLSingleton. --- indra/newview/llchannelmanager.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/indra/newview/llchannelmanager.cpp b/indra/newview/llchannelmanager.cpp index 0b7b9cbbc7..9e7a8ba95c 100644 --- a/indra/newview/llchannelmanager.cpp +++ b/indra/newview/llchannelmanager.cpp @@ -48,11 +48,18 @@ LLChannelManager::LLChannelManager() LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLChannelManager::onLoginCompleted, this)); mChannelList.clear(); mStartUpChannel = NULL; - + if(!gViewerWindow) { LL_ERRS() << "LLChannelManager::LLChannelManager() - viwer window is not initialized yet" << LL_ENDL; } + + // We don't actually need this instance right now, but our + // cleanupSingleton() method deletes LLScreenChannels, which need to + // unregister from LLUI. Calling LLUI::instance() here establishes the + // dependency so LLSingletonBase::deleteAll() calls our deleteSingleton() + // before LLUI::deleteSingleton(). + LLUI::instance(); } //-------------------------------------------------------------------------- From be662c65ca2fffa035d0be8e2ac6b34c2388ddd5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 18 Dec 2019 15:54:00 -0500 Subject: [PATCH 45/48] DRTVWR-476: make printActiveCoroutines() output slightly clearer. For the main coroutine on each thread, show the 'main0' (or whatever) name instead of the empty-string name. --- indra/llcommon/llcoros.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index adb86c4e0b..262929006d 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -223,7 +223,7 @@ void LLCoros::printActiveCoroutines(const std::string& when) { F64 life_time = time - cd.mCreationTime; LL_CONT << LL_NEWLINE - << cd.mName << ' ' << cd.mStatus << " life: " << life_time; + << cd.getKey() << ' ' << cd.mStatus << " life: " << life_time; } LL_CONT << LL_ENDL; LL_INFOS("LLCoros") << "-----------------------------------------------------" << LL_ENDL; From e782e12309d2cf7cafdcbca42e60abd896e35e66 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 19 Dec 2019 10:17:30 -0500 Subject: [PATCH 46/48] DRTVWR-476: Introduce LLThreadSafeQueue::close(). Also isClosed() and explicit operator bool() to detect closed state. close() causes every subsequent pushFront() to throw LLThreadSafeQueueInterrupt. Once the queue is drained, it causes popBack() to throw likewise. --- indra/llcommon/llthreadsafequeue.h | 78 +++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 8 deletions(-) diff --git a/indra/llcommon/llthreadsafequeue.h b/indra/llcommon/llthreadsafequeue.h index 808867132a..6edffb4187 100644 --- a/indra/llcommon/llthreadsafequeue.h +++ b/indra/llcommon/llthreadsafequeue.h @@ -78,7 +78,7 @@ public: // Add an element to the front of queue (will block if the queue has // reached capacity). // - // This call will raise an interrupt error if the queue is deleted while + // This call will raise an interrupt error if the queue is closed while // the caller is blocked. void pushFront(ElementT const & element); @@ -89,7 +89,7 @@ public: // Pop the element at the end of the queue (will block if the queue is // empty). // - // This call will raise an interrupt error if the queue is deleted while + // This call will raise an interrupt error if the queue is closed while // the caller is blocked. ElementT popBack(void); @@ -100,11 +100,27 @@ public: // Returns the size of the queue. size_t size(); + // closes the queue: + // - every subsequent pushFront() call will throw LLThreadSafeQueueInterrupt + // - every subsequent tryPushFront() call will return false + // - popBack() calls will return normally until the queue is drained, then + // every subsequent popBack() will throw LLThreadSafeQueueInterrupt + // - tryPopBack() calls will return normally until the queue is drained, + // then every subsequent tryPopBack() call will return false + void close(); + + // detect closed state + bool isClosed(); + // inverse of isClosed() + explicit operator bool(); + private: std::deque< ElementT > mStorage; U32 mCapacity; + bool mClosed; boost::fibers::mutex mLock; + typedef std::unique_lock lock_t; boost::fibers::condition_variable mCapacityCond; boost::fibers::condition_variable mEmptyCond; }; @@ -114,7 +130,8 @@ private: template LLThreadSafeQueue::LLThreadSafeQueue(U32 capacity) : -mCapacity(capacity) + mCapacity(capacity), + mClosed(false) { } @@ -122,12 +139,18 @@ mCapacity(capacity) template void LLThreadSafeQueue::pushFront(ElementT const & element) { - std::unique_lock lock1(mLock); + lock_t lock1(mLock); while (true) { + if (mClosed) + { + LLTHROW(LLThreadSafeQueueInterrupt()); + } + if (mStorage.size() < mCapacity) { mStorage.push_front(element); + lock1.unlock(); mEmptyCond.notify_one(); return; } @@ -141,14 +164,18 @@ void LLThreadSafeQueue::pushFront(ElementT const & element) template bool LLThreadSafeQueue::tryPushFront(ElementT const & element) { - std::unique_lock lock1(mLock, std::defer_lock); + lock_t lock1(mLock, std::defer_lock); if (!lock1.try_lock()) return false; + if (mClosed) + return false; + if (mStorage.size() >= mCapacity) return false; mStorage.push_front(element); + lock1.unlock(); mEmptyCond.notify_one(); return true; } @@ -157,17 +184,23 @@ bool LLThreadSafeQueue::tryPushFront(ElementT const & element) template ElementT LLThreadSafeQueue::popBack(void) { - std::unique_lock lock1(mLock); + lock_t lock1(mLock); while (true) { if (!mStorage.empty()) { ElementT value = mStorage.back(); mStorage.pop_back(); + lock1.unlock(); mCapacityCond.notify_one(); return value; } + if (mClosed) + { + LLTHROW(LLThreadSafeQueueInterrupt()); + } + // Storage empty. Wait for signal. mEmptyCond.wait(lock1); } @@ -177,15 +210,18 @@ ElementT LLThreadSafeQueue::popBack(void) template bool LLThreadSafeQueue::tryPopBack(ElementT & element) { - std::unique_lock lock1(mLock, std::defer_lock); + lock_t lock1(mLock, std::defer_lock); if (!lock1.try_lock()) return false; + // no need to check mClosed: tryPopBack() behavior when the queue is + // closed is implemented by simple inability to push any new elements if (mStorage.empty()) return false; element = mStorage.back(); mStorage.pop_back(); + lock1.unlock(); mCapacityCond.notify_one(); return true; } @@ -194,8 +230,34 @@ bool LLThreadSafeQueue::tryPopBack(ElementT & element) template size_t LLThreadSafeQueue::size(void) { - std::lock_guard lock(mLock); + lock_t lock(mLock); return mStorage.size(); } +template +void LLThreadSafeQueue::close() +{ + lock_t lock(mLock); + mClosed = true; + lock.unlock(); + // wake up any blocked popBack() calls + mEmptyCond.notify_all(); + // wake up any blocked pushFront() calls + mCapacityCond.notify_all(); +} + +template +bool LLThreadSafeQueue::isClosed() +{ + lock_t lock(mLock); + return mClosed; +} + +template +LLThreadSafeQueue::operator bool() +{ + lock_t lock(mLock); + return ! mClosed; +} + #endif From b967454da1731276f3bcfd09f68183fabdb95004 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 19 Dec 2019 11:50:52 -0500 Subject: [PATCH 47/48] DRTVWR-476: Use LLThreadSafeQueue::close() to shut down coprocs. The tactic of pushing an empty QueuedCoproc::ptr_t to signal coprocedure close only works for LLCoprocedurePools with a single coprocedure (e.g. "Upload" and "AIS"). Only one coprocedureInvokerCoro() coroutine will pop that empty pointer and shut down properly -- the rest will continue waiting indefinitely. Rather than pushing some number of empty pointers, hopefully enough to notify all consumer coroutines, close() the queue. That will notify as many consumers as there may be. That means catching LLThreadSafeQueueInterrupt from popBack(), instead of detecting empty pointer. Also, if a queued coprocedure throws an exception, coprocedureInvokerCoro() logs it as before -- but instead of rethrowing it, the coroutine now loops back to wait for more work. Otherwise, the number of coroutines servicing the queue dwindles. --- indra/llmessage/llcoproceduremanager.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/indra/llmessage/llcoproceduremanager.cpp b/indra/llmessage/llcoproceduremanager.cpp index c1e53ea278..d252c0e4b0 100644 --- a/indra/llmessage/llcoproceduremanager.cpp +++ b/indra/llmessage/llcoproceduremanager.cpp @@ -291,7 +291,7 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size): << LL_ENDL; // This should ensure that all waiting coprocedures in this // pool will wake up and terminate. - pendingCoprocs->pushFront({}); + pendingCoprocs->close(); } return false; }); @@ -323,7 +323,7 @@ LLUUID LLCoprocedurePool::enqueueCoprocedure(const std::string &name, LLCoproced LL_INFOS("CoProcMgr") << "Coprocedure(" << name << ") enqueuing with id=" << id.asString() << " in pool \"" << mPoolName << "\" at " << mPending << LL_ENDL; auto pushed = mPendingCoprocs->tryPushFront(boost::make_shared(name, id, proc)); // We don't really have a lot of good options if tryPushFront() failed, - // perhaps because the consuming coroutine is gummed up or something. This + // perhaps because the consuming coroutines are gummed up or something. This // method is probably called from code called by mainloop. If we toss an // llcoro::suspend() call here, we'll circle back for another mainloop // iteration, possibly resulting in being re-entered here. Let's avoid that. @@ -341,13 +341,14 @@ void LLCoprocedurePool::coprocedureInvokerCoro( QueuedCoproc::ptr_t coproc; for (;;) { + try { LLCoros::TempStatus st("waiting for work"); coproc = pendingCoprocs->popBack(); } - if (! coproc) + catch (const LLThreadSafeQueueError&) { - // close() pushes an empty pointer to signal done + // queue is closed break; } @@ -369,7 +370,7 @@ void LLCoprocedurePool::coprocedureInvokerCoro( << ") in pool '" << mPoolName << "'")); // must NOT omit this or we deplete the pool mActiveCoprocs.erase(itActive); - throw; + continue; } LL_DEBUGS("CoProcMgr") << "Finished coprocedure(" << coproc->mName << ")" << " in pool \"" << mPoolName << "\"" << LL_ENDL; @@ -380,5 +381,5 @@ void LLCoprocedurePool::coprocedureInvokerCoro( void LLCoprocedurePool::close() { - mPendingCoprocs->pushFront({}); + mPendingCoprocs->close(); } From 68c74a80d87c08cc201196c39d0c43f4d1c8078b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 19 Dec 2019 12:02:23 -0500 Subject: [PATCH 48/48] DRTVWR-476: Eliminate LLWearableType LLSingleton circularity. LLWearableType::initSingleton() calls LLWearableDictionary::initParamSingleton(). LLWearableDictionary's constructor constructs specific WearableEntry instances, each of which wants to translate its name string to a user-facing label using LLWearableType::mTrans. WearableEntry's constructor was calling LLWearableType::getInstance(). Under circumstances we don't fully understand (recursive mutex misbehavior?), that could hang. Instead, pass the canonical LLWearableType instance to LLWearableDictionary's constructor, and from there into WearableEntry's constructor. --- indra/llappearance/llwearabletype.cpp | 49 ++++++++++++++------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/indra/llappearance/llwearabletype.cpp b/indra/llappearance/llwearabletype.cpp index 6b7dc41ffd..e45df00e9f 100644 --- a/indra/llappearance/llwearabletype.cpp +++ b/indra/llappearance/llwearabletype.cpp @@ -32,7 +32,8 @@ struct WearableEntry : public LLDictionaryEntry { - WearableEntry(const std::string &name, + WearableEntry(LLWearableType& wtype, + const std::string &name, const std::string& default_new_name, LLAssetType::EType assetType, LLInventoryType::EIconName iconName, @@ -41,7 +42,7 @@ struct WearableEntry : public LLDictionaryEntry LLDictionaryEntry(name), mAssetType(assetType), mDefaultNewName(default_new_name), - mLabel(LLWearableType::getInstance()->mTrans->getString(name)), + mLabel(wtype.mTrans->getString(name)), mIconName(iconName), mDisableCameraSwitch(disable_camera_switch), mAllowMultiwear(allow_multiwear) @@ -59,32 +60,32 @@ struct WearableEntry : public LLDictionaryEntry class LLWearableDictionary : public LLParamSingleton, public LLDictionary { - LLSINGLETON(LLWearableDictionary); + LLSINGLETON(LLWearableDictionary, LLWearableType&); }; -LLWearableDictionary::LLWearableDictionary() +LLWearableDictionary::LLWearableDictionary(LLWearableType& wtype) { - addEntry(LLWearableType::WT_SHAPE, new WearableEntry("shape", "New Shape", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SHAPE, FALSE, FALSE)); - addEntry(LLWearableType::WT_SKIN, new WearableEntry("skin", "New Skin", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SKIN, FALSE, FALSE)); - addEntry(LLWearableType::WT_HAIR, new WearableEntry("hair", "New Hair", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_HAIR, FALSE, FALSE)); - addEntry(LLWearableType::WT_EYES, new WearableEntry("eyes", "New Eyes", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_EYES, FALSE, FALSE)); - addEntry(LLWearableType::WT_SHIRT, new WearableEntry("shirt", "New Shirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHIRT, FALSE, TRUE)); - addEntry(LLWearableType::WT_PANTS, new WearableEntry("pants", "New Pants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PANTS, FALSE, TRUE)); - addEntry(LLWearableType::WT_SHOES, new WearableEntry("shoes", "New Shoes", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHOES, FALSE, TRUE)); - addEntry(LLWearableType::WT_SOCKS, new WearableEntry("socks", "New Socks", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SOCKS, FALSE, TRUE)); - addEntry(LLWearableType::WT_JACKET, new WearableEntry("jacket", "New Jacket", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_JACKET, FALSE, TRUE)); - addEntry(LLWearableType::WT_GLOVES, new WearableEntry("gloves", "New Gloves", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_GLOVES, FALSE, TRUE)); - addEntry(LLWearableType::WT_UNDERSHIRT, new WearableEntry("undershirt", "New Undershirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERSHIRT, FALSE, TRUE)); - addEntry(LLWearableType::WT_UNDERPANTS, new WearableEntry("underpants", "New Underpants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERPANTS, FALSE, TRUE)); - addEntry(LLWearableType::WT_SKIRT, new WearableEntry("skirt", "New Skirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SKIRT, FALSE, TRUE)); - addEntry(LLWearableType::WT_ALPHA, new WearableEntry("alpha", "New Alpha", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_ALPHA, FALSE, TRUE)); - addEntry(LLWearableType::WT_TATTOO, new WearableEntry("tattoo", "New Tattoo", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_TATTOO, FALSE, TRUE)); - addEntry(LLWearableType::WT_UNIVERSAL, new WearableEntry("universal", "New Universal", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNIVERSAL, FALSE, TRUE)); + addEntry(LLWearableType::WT_SHAPE, new WearableEntry(wtype, "shape", "New Shape", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SHAPE, FALSE, FALSE)); + addEntry(LLWearableType::WT_SKIN, new WearableEntry(wtype, "skin", "New Skin", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SKIN, FALSE, FALSE)); + addEntry(LLWearableType::WT_HAIR, new WearableEntry(wtype, "hair", "New Hair", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_HAIR, FALSE, FALSE)); + addEntry(LLWearableType::WT_EYES, new WearableEntry(wtype, "eyes", "New Eyes", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_EYES, FALSE, FALSE)); + addEntry(LLWearableType::WT_SHIRT, new WearableEntry(wtype, "shirt", "New Shirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHIRT, FALSE, TRUE)); + addEntry(LLWearableType::WT_PANTS, new WearableEntry(wtype, "pants", "New Pants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PANTS, FALSE, TRUE)); + addEntry(LLWearableType::WT_SHOES, new WearableEntry(wtype, "shoes", "New Shoes", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHOES, FALSE, TRUE)); + addEntry(LLWearableType::WT_SOCKS, new WearableEntry(wtype, "socks", "New Socks", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SOCKS, FALSE, TRUE)); + addEntry(LLWearableType::WT_JACKET, new WearableEntry(wtype, "jacket", "New Jacket", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_JACKET, FALSE, TRUE)); + addEntry(LLWearableType::WT_GLOVES, new WearableEntry(wtype, "gloves", "New Gloves", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_GLOVES, FALSE, TRUE)); + addEntry(LLWearableType::WT_UNDERSHIRT, new WearableEntry(wtype, "undershirt", "New Undershirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERSHIRT, FALSE, TRUE)); + addEntry(LLWearableType::WT_UNDERPANTS, new WearableEntry(wtype, "underpants", "New Underpants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERPANTS, FALSE, TRUE)); + addEntry(LLWearableType::WT_SKIRT, new WearableEntry(wtype, "skirt", "New Skirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SKIRT, FALSE, TRUE)); + addEntry(LLWearableType::WT_ALPHA, new WearableEntry(wtype, "alpha", "New Alpha", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_ALPHA, FALSE, TRUE)); + addEntry(LLWearableType::WT_TATTOO, new WearableEntry(wtype, "tattoo", "New Tattoo", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_TATTOO, FALSE, TRUE)); + addEntry(LLWearableType::WT_UNIVERSAL, new WearableEntry(wtype, "universal", "New Universal", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNIVERSAL, FALSE, TRUE)); - addEntry(LLWearableType::WT_PHYSICS, new WearableEntry("physics", "New Physics", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PHYSICS, TRUE, TRUE)); + addEntry(LLWearableType::WT_PHYSICS, new WearableEntry(wtype, "physics", "New Physics", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PHYSICS, TRUE, TRUE)); - addEntry(LLWearableType::WT_INVALID, new WearableEntry("invalid", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_UNKNOWN, FALSE, FALSE)); - addEntry(LLWearableType::WT_NONE, new WearableEntry("none", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_NONE, FALSE, FALSE)); + addEntry(LLWearableType::WT_INVALID, new WearableEntry(wtype, "invalid", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_UNKNOWN, FALSE, FALSE)); + addEntry(LLWearableType::WT_NONE, new WearableEntry(wtype, "none", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_NONE, FALSE, FALSE)); } @@ -103,7 +104,7 @@ LLWearableType::~LLWearableType() void LLWearableType::initSingleton() { // To make sure all wrapping functions will crash without initing LLWearableType; - LLWearableDictionary::initParamSingleton(); + LLWearableDictionary::initParamSingleton(*this); // Todo: consider merging LLWearableType and LLWearableDictionary }