diff options
| author | AndreyL ProductEngine <alihatskiy@productengine.com> | 2019-11-12 00:39:07 +0200 | 
|---|---|---|
| committer | AndreyL ProductEngine <alihatskiy@productengine.com> | 2019-11-12 00:39:07 +0200 | 
| commit | 84f3b2865fe3ec2a3dfb0a96ed0389381cebeed5 (patch) | |
| tree | 1490249690bb3c7c0509e8ad73e2b7f7748d9dba /indra/llcommon | |
| parent | 6ec37fac60c0288e576b72b91b16d74b37dbc32d (diff) | |
| parent | 78bdf57ad6610b34389226bf941ba736ca0c2225 (diff) | |
Merged in lindenlab/viewer-release
Diffstat (limited to 'indra/llcommon')
| -rw-r--r-- | indra/llcommon/llsingleton.cpp | 71 | ||||
| -rw-r--r-- | indra/llcommon/llsingleton.h | 409 | ||||
| -rw-r--r-- | indra/llcommon/tests/lleventcoro_test.cpp | 33 | ||||
| -rw-r--r-- | indra/llcommon/tests/lleventdispatcher_test.cpp | 83 | ||||
| -rw-r--r-- | indra/llcommon/tests/lleventfilter_test.cpp | 12 | ||||
| -rw-r--r-- | indra/llcommon/tests/llinstancetracker_test.cpp | 33 | ||||
| -rw-r--r-- | indra/llcommon/tests/lllazy_test.cpp | 13 | ||||
| -rw-r--r-- | indra/llcommon/tests/llleap_test.cpp | 20 | ||||
| -rw-r--r-- | indra/llcommon/tests/llprocess_test.cpp | 16 | ||||
| -rw-r--r-- | indra/llcommon/tests/llsingleton_test.cpp | 133 | ||||
| -rw-r--r-- | indra/llcommon/tests/wrapllerrs.h | 26 | 
11 files changed, 610 insertions, 239 deletions
| diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index 9fbd78a000..c45c144570 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -134,12 +134,6 @@ LLSingletonBase::list_t& LLSingletonBase::get_initializing()      return LLSingletonBase::MasterList::instance().get_initializing_();  } -//static -LLSingletonBase::list_t& LLSingletonBase::get_initializing_from(MasterList* master) -{ -    return master->get_initializing_(); -} -  LLSingletonBase::~LLSingletonBase() {}  void LLSingletonBase::push_initializing(const char* name) @@ -156,7 +150,7 @@ void LLSingletonBase::pop_initializing()      if (list.empty())      {          logerrs("Underflow in stack of currently-initializing LLSingletons at ", -                demangle(typeid(*this).name()).c_str(), "::getInstance()"); +                classname(this).c_str(), "::getInstance()");      }      // Now we know list.back() exists: capture it @@ -178,14 +172,39 @@ void LLSingletonBase::pop_initializing()      if (back != this)      {          logerrs("Push/pop mismatch in stack of currently-initializing LLSingletons: ", -                demangle(typeid(*this).name()).c_str(), "::getInstance() trying to pop ", -                demangle(typeid(*back).name()).c_str()); +                classname(this).c_str(), "::getInstance() trying to pop ", +                classname(back).c_str());      }      // log AFTER popping so logging singletons don't cry circularity      log_initializing("Popping", typeid(*back).name());  } +void LLSingletonBase::reset_initializing(list_t::size_type size) +{ +    // called for cleanup in case the LLSingleton subclass constructor throws +    // an exception + +    // The tricky thing about this, the reason we have a separate method +    // instead of just calling pop_initializing(), is (hopefully remote) +    // possibility that the exception happened *before* the +    // 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()); + +    while (list.size() > size) +    { +        list.pop_back(); +    } + +    // as in pop_initializing() +    if (list.empty()) +    { +        MasterList::instance().cleanup_initializing_(); +    } +} +  //static  void LLSingletonBase::log_initializing(const char* verb, const char* name)  { @@ -197,7 +216,7 @@ void LLSingletonBase::log_initializing(const char* verb, const char* name)               ri != rend; ++ri)          {              LLSingletonBase* sb(*ri); -            LL_CONT << ' ' << demangle(typeid(*sb).name()); +            LL_CONT << ' ' << classname(sb);          }          LL_ENDL;      } @@ -231,7 +250,7 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt                  // 'found' is an iterator; *found is an LLSingletonBase*; **found                  // is the actual LLSingletonBase instance.                  LLSingletonBase* foundp(*found); -                out << demangle(typeid(*foundp).name()) << " -> "; +                out << classname(foundp) << " -> ";              }              // We promise to capture dependencies from both the constructor              // and the initSingleton() method, so an LLSingleton's instance @@ -245,7 +264,7 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt              if (initState == CONSTRUCTING)              {                  logerrs("LLSingleton circularity in Constructor: ", out.str().c_str(), -                    demangle(typeid(*this).name()).c_str(), ""); +                    classname(this).c_str(), "");              }              else if (it_next == initializing.end())              { @@ -256,14 +275,14 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt                  // Example: LLNotifications singleton initializes default channels.                  // Channels register themselves with singleton once done.                  logdebugs("LLSingleton circularity: ", out.str().c_str(), -                    demangle(typeid(*this).name()).c_str(), ""); +                    classname(this).c_str(), "");              }              else              {                  // Actual circularity with other singleton (or single singleton is used extensively).                  // Dependency can be unclear.                  logwarns("LLSingleton circularity: ", out.str().c_str(), -                    demangle(typeid(*this).name()).c_str(), ""); +                    classname(this).c_str(), "");              }          }          else @@ -276,8 +295,8 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt              if (current->mDepends.insert(this).second)              {                  // only log the FIRST time we hit this dependency! -                logdebugs(demangle(typeid(*current).name()).c_str(), -                          " depends on ", demangle(typeid(*this).name()).c_str()); +                logdebugs(classname(current).c_str(), +                          " depends on ", classname(this).c_str());              }          }      } @@ -336,19 +355,19 @@ void LLSingletonBase::cleanupAll()              sp->mCleaned = true;              logdebugs("calling ", -                      demangle(typeid(*sp).name()).c_str(), "::cleanupSingleton()"); +                      classname(sp).c_str(), "::cleanupSingleton()");              try              {                  sp->cleanupSingleton();              }              catch (const std::exception& e)              { -                logwarns("Exception in ", demangle(typeid(*sp).name()).c_str(), +                logwarns("Exception in ", classname(sp).c_str(),                           "::cleanupSingleton(): ", e.what());              }              catch (...)              { -                logwarns("Unknown exception in ", demangle(typeid(*sp).name()).c_str(), +                logwarns("Unknown exception in ", classname(sp).c_str(),                           "::cleanupSingleton()");              }          } @@ -363,7 +382,7 @@ void LLSingletonBase::deleteAll()      {          // Capture the class name first: in case of exception, don't count on          // being able to extract it later. -        const std::string name = demangle(typeid(*sp).name()); +        const std::string name = classname(sp);          try          {              // Call static method through instance function pointer. @@ -440,7 +459,17 @@ void LLSingletonBase::logerrs(const char* p1, const char* p2, const char* p3, co      log(LLError::LEVEL_ERROR, p1, p2, p3, p4);      // The other important side effect of LL_ERRS() is      // https://www.youtube.com/watch?v=OMG7paGJqhQ (emphasis on OMG) -    LLError::crashAndLoop(std::string()); +    std::ostringstream out; +    out << p1 << p2 << p3 << p4; +    auto crash = LLError::getFatalFunction(); +    if (crash) +    { +        crash(out.str()); +    } +    else +    { +        LLError::crashAndLoop(out.str()); +    }  }  std::string LLSingletonBase::demangle(const char* mangled) diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 859e271e26..0da6d548ab 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -31,6 +31,18 @@  #include <vector>  #include <typeinfo> +#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 <mutex> + +#if LL_WINDOWS +#pragma warning (pop) +#endif +  class LLSingletonBase: private boost::noncopyable  {  public: @@ -43,7 +55,6 @@ private:      // This, on the other hand, is a stack whose top indicates the LLSingleton      // currently being initialized.      static list_t& get_initializing(); -    static list_t& get_initializing_from(MasterList*);      // Produce a vector<LLSingletonBase*> of master list, in dependency order.      typedef std::vector<LLSingletonBase*> vec_t;      static vec_t dep_sort(); @@ -57,10 +68,11 @@ protected:      typedef enum e_init_state      {          UNINITIALIZED = 0,          // must be default-initialized state -        CONSTRUCTING, -        INITIALIZING, -        INITIALIZED, -        DELETED +        CONSTRUCTING,               // within DERIVED_TYPE constructor +        CONSTRUCTED,                // finished DERIVED_TYPE constructor +        INITIALIZING,               // within DERIVED_TYPE::initSingleton() +        INITIALIZED,                // normal case +        DELETED                     // deleteSingleton() or deleteAll() called      } EInitState;      // Define tag<T> to pass to our template constructor. You can't explicitly @@ -100,6 +112,9 @@ protected:      // That being the case, we control exactly when it happens -- and we can      // pop the stack immediately thereafter.      void pop_initializing(); +    // 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); @@ -115,6 +130,10 @@ protected:      static void logwarns(const char* p1, const char* p2="",                           const char* p3="", const char* p4="");      static std::string demangle(const char* mangled); +    template <typename T> +    static std::string classname()       { return demangle(typeid(T).name()); } +    template <typename T> +    static std::string classname(T* ptr) { return demangle(typeid(*ptr).name()); }      // Default methods in case subclass doesn't declare them.      virtual void initSingleton() {} @@ -178,7 +197,15 @@ struct LLSingleton_manage_master      void remove(LLSingletonBase* sb) { sb->remove_master(); }      void push_initializing(LLSingletonBase* sb) { sb->push_initializing(typeid(T).name()); }      void pop_initializing (LLSingletonBase* sb) { sb->pop_initializing(); } -    LLSingletonBase::list_t& get_initializing(T*) { return LLSingletonBase::get_initializing(); } +    // used for init stack cleanup in case an LLSingleton subclass constructor +    // throws an exception +    void reset_initializing(LLSingletonBase::list_t::size_type size) +    { +        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(); }  };  // But for the specific case of LLSingletonBase::MasterList, don't. @@ -189,9 +216,14 @@ struct LLSingleton_manage_master<LLSingletonBase::MasterList>      void remove(LLSingletonBase*) {}      void push_initializing(LLSingletonBase*) {}      void pop_initializing (LLSingletonBase*) {} -    LLSingletonBase::list_t& get_initializing(LLSingletonBase::MasterList* instance) +    // since we never pushed, no need to clean up +    void reset_initializing(LLSingletonBase::list_t::size_type size) {} +    LLSingletonBase::list_t& get_initializing()      { -        return LLSingletonBase::get_initializing_from(instance); +        // 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;      }  }; @@ -201,10 +233,19 @@ LLSingletonBase::LLSingletonBase(tag<DERIVED_TYPE>):      mCleaned(false),      mDeleteSingleton(NULL)  { -    // Make this the currently-initializing LLSingleton. +    // This is the earliest possible point at which we can push this new +    // instance onto the init stack. LLSingleton::constructSingleton() can't +    // do it before calling the constructor, because it doesn't have an +    // instance pointer until the constructor returns. Fortunately this +    // constructor is guaranteed to be called before any subclass constructor. +    // Make this new instance the currently-initializing LLSingleton.      LLSingleton_manage_master<DERIVED_TYPE>().push_initializing(this);  } +// forward declare for friend directive within LLSingleton +template <typename DERIVED_TYPE> +class LLParamSingleton; +  /**   * LLSingleton implements the getInstance() method part of the Singleton   * pattern. It can't make the derived class constructors protected, though, so @@ -270,9 +311,94 @@ template <typename DERIVED_TYPE>  class LLSingleton : public LLSingletonBase  {  private: -    static DERIVED_TYPE* constructSingleton() +    // Allow LLParamSingleton subclass -- but NOT DERIVED_TYPE itself -- to +    // access our private members. +    friend class LLParamSingleton<DERIVED_TYPE>; + +    // 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. +    template <typename... Args> +    static void constructSingleton(Args&&... args) +    { +        auto prev_size = LLSingleton_manage_master<DERIVED_TYPE>().get_initializing().size(); +        // getInstance() calls are from within constructor +        sData.mInitState = CONSTRUCTING; +        try +        { +            sData.mInstance = new DERIVED_TYPE(std::forward<Args>(args)...); +            // we have called constructor, have not yet called initSingleton() +            sData.mInitState = CONSTRUCTED; +        } +        catch (const std::exception& err) +        { +            // LLSingletonBase might -- or might not -- have pushed the new +            // instance onto the init stack before the exception. Reset the +            // init stack to its previous size BEFORE logging so log-machinery +            // LLSingletons don't record a dependency on DERIVED_TYPE! +            LLSingleton_manage_master<DERIVED_TYPE>().reset_initializing(prev_size); +            logwarns("Error constructing ", classname<DERIVED_TYPE>().c_str(), +                     ": ", err.what()); +            // 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; +            // propagate the exception +            throw; +        } +    } + +    static void finishInitializing() +    { +        // getInstance() calls are from within initSingleton() +        sData.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; + +            // pop this off stack of initializing singletons +            pop_initializing(); +        } +        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(); +            logwarns("Error in ", classname<DERIVED_TYPE>().c_str(), +                     "::initSingleton(): ", err.what()); +            // and get rid of the instance entirely +            deleteSingleton(); +            // propagate the exception +            throw; +        } +    } + +    static void pop_initializing()      { -        return new DERIVED_TYPE(); +        // route through LLSingleton_manage_master so we Do The Right Thing +        // (namely, nothing) for MasterList +        LLSingleton_manage_master<DERIVED_TYPE>().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 +        // 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. +        sData.mInstance->capture_dependency( +            LLSingleton_manage_master<DERIVED_TYPE>().get_initializing(), +            sData.mInitState);      }      // We know of no way to instruct the compiler that every subclass @@ -285,34 +411,17 @@ private:      // subclass body.      virtual void you_must_use_LLSINGLETON_macro() = 0; -    // stores pointer to singleton instance -    struct SingletonLifetimeManager +    // 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      { -        SingletonLifetimeManager() -        { -            construct(); -        } - -        static void construct() -        { -            sData.mInitState = CONSTRUCTING; -            sData.mInstance = constructSingleton(); -            sData.mInitState = INITIALIZING; -        } - -        ~SingletonLifetimeManager() +        SingletonInitializer()          { -            // The dependencies between LLSingletons, and the arbitrary order -            // of static-object destruction, mean that we DO NOT WANT this -            // destructor to delete this LLSingleton. This destructor will run -            // without regard to any other LLSingleton whose cleanup might -            // depend on its existence. If you want to clean up LLSingletons, -            // call LLSingletonBase::deleteAll() sometime before static-object -            // destruction begins. That method will properly honor cross- -            // LLSingleton dependencies. Otherwise we simply leak LLSingleton -            // instances at shutdown. Since the whole process is terminating -            // anyway, that's not necessarily a bad thing; it depends on what -            // resources your LLSingleton instances are managing. +            constructSingleton();          }      }; @@ -363,64 +472,59 @@ public:      static void deleteSingleton()      {          delete sData.mInstance; -        sData.mInstance = NULL; -        sData.mInitState = DELETED; +        // SingletonData state handled by destructor, above      }      static DERIVED_TYPE* getInstance()      { -        static SingletonLifetimeManager sLifeTimeMgr; +        // call constructSingleton() only the first time we get here +        static SingletonInitializer sInitializer;          switch (sData.mInitState)          {          case UNINITIALIZED:              // should never be uninitialized at this point              logerrs("Uninitialized singleton ", -                    demangle(typeid(DERIVED_TYPE).name()).c_str()); +                    classname<DERIVED_TYPE>().c_str());              return NULL;          case CONSTRUCTING: +            // here if DERIVED_TYPE's constructor (directly or indirectly) +            // calls DERIVED_TYPE::getInstance()              logerrs("Tried to access singleton ", -                    demangle(typeid(DERIVED_TYPE).name()).c_str(), +                    classname<DERIVED_TYPE>().c_str(),                      " from singleton constructor!");              return NULL; -        case INITIALIZING: -            // go ahead and flag ourselves as initialized so we can be -            // reentrant during initialization -            sData.mInitState = INITIALIZED;  -            // 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(); -            // pop this off stack of initializing singletons -            LLSingleton_manage_master<DERIVED_TYPE>().pop_initializing(sData.mInstance); +        case CONSTRUCTED: +            // first time through: set to CONSTRUCTED by +            // constructSingleton(), called by sInitializer's constructor; +            // still have to call initSingleton() +            finishInitializing();              break; +        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;          case DELETED: +            // called after deleteSingleton()              logwarns("Trying to access deleted singleton ", -                     demangle(typeid(DERIVED_TYPE).name()).c_str(), +                     classname<DERIVED_TYPE>().c_str(),                       " -- creating new instance"); -            SingletonLifetimeManager::construct(); -            // same as first time construction -            sData.mInitState = INITIALIZED;  -            sData.mInstance->initSingleton();  -            // pop this off stack of initializing singletons -            LLSingleton_manage_master<DERIVED_TYPE>().pop_initializing(sData.mInstance); +            // This recovery sequence is NOT thread-safe! We would need a +            // recursive_mutex a la LLParamSingleton. +            constructSingleton(); +            finishInitializing();              break;          } -        // 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 this call -        // came from another LLSingleton, rather than from vanilla application -        // code, record the dependency. -        sData.mInstance->capture_dependency( -            LLSingleton_manage_master<DERIVED_TYPE>().get_initializing(sData.mInstance), -            sData.mInitState); +        // record the dependency, if any: check if we got here from another +        // LLSingleton's constructor or initSingleton() method +        capture_dependency();          return sData.mInstance;      } @@ -460,6 +564,173 @@ private:  template<typename T>  typename LLSingleton<T>::SingletonData LLSingleton<T>::sData; + +/** + * LLParamSingleton<T> is like LLSingleton<T>, except in the following ways: + * + * * It is NOT instantiated on demand (instance() or getInstance()). You must + *   first call initParamSingleton(constructor args...). + * * Before initParamSingleton(), calling instance() or getInstance() dies with + *   LL_ERRS. + * * initParamSingleton() may be called only once. A second call dies with + *   LL_ERRS. + * * However, distinct initParamSingleton() calls can be used to engage + *   different constructors, as long as only one such call is executed at + *   runtime. + * * Unlike LLSingleton, an LLParamSingleton cannot be "revived" by an + *   instance() or getInstance() call after deleteSingleton(). + * + * Importantly, though, each LLParamSingleton subclass does participate in the + * dependency-ordered LLSingletonBase::deleteAll() processing. + */ +template <typename DERIVED_TYPE> +class LLParamSingleton : public LLSingleton<DERIVED_TYPE> +{ +private: +    typedef LLSingleton<DERIVED_TYPE> 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; + +public: +    using super::deleteSingleton; +    using super::instanceExists; +    using super::wasDeleted; + +    // Passes arguments to DERIVED_TYPE's constructor and sets appropriate states +    template <typename... Args> +    static void initParamSingleton(Args&&... args) +    { +        // 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<mutex_t> lk(getMutex()); +        // For organizational purposes this function shouldn't be called twice +        if (super::sData.mInitState != super::UNINITIALIZED) +        { +            super::logerrs("Tried to initialize singleton ", +                           super::template classname<DERIVED_TYPE>().c_str(), +                           " twice!"); +        } +        else +        { +            super::constructSingleton(std::forward<Args>(args)...); +            super::finishInitializing(); +        } +    } + +    static DERIVED_TYPE* getInstance() +    { +        // In case racing threads call getInstance() at the same moment as +        // initParamSingleton(), serialize the calls. +        std::unique_lock<mutex_t> lk(getMutex()); + +        switch (super::sData.mInitState) +        { +        case super::UNINITIALIZED: +            super::logerrs("Uninitialized param singleton ", +                           super::template classname<DERIVED_TYPE>().c_str()); +            break; + +        case super::CONSTRUCTING: +            super::logerrs("Tried to access param singleton ", +                           super::template classname<DERIVED_TYPE>().c_str(), +                           " 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<DERIVED_TYPE>().c_str(), +                           "::initSingleton() not yet called"); +            break; + +        case super::INITIALIZING: +            // As with LLSingleton, explicitly permit circular calls from +            // within initSingleton() +        case super::INITIALIZED: +            // for any valid call, capture dependencies +            super::capture_dependency(); +            return super::sData.mInstance; + +        case super::DELETED: +            super::logerrs("Trying to access deleted param singleton ", +                           super::template classname<DERIVED_TYPE>().c_str()); +            break; +        } + +        // should never actually get here; this is to pacify the compiler, +        // which assumes control might return from logerrs() +        return nullptr; +    } + +    // instance() is replicated here so it calls +    // LLParamSingleton::getInstance() rather than LLSingleton::getInstance() +    // -- avoid making getInstance() virtual +    static DERIVED_TYPE& instance() +    { +        return *getInstance(); +    } + +private: +    // 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<mutex_t*> -- but a default-constructed +    // std::atomic<T> 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; +    } +}; + +/** + * Initialization locked singleton, only derived class can decide when to initialize. + * Starts locked. + * For cases when singleton has a dependency onto something or. + * + * LLLockedSingleton is like an LLParamSingleton with a nullary constructor. + * It cannot be instantiated on demand (instance() or getInstance() call) -- + * it must be instantiated by calling construct(). However, it does + * participate in dependency-ordered LLSingletonBase::deleteAll() processing. + */ +template <typename DT> +class LLLockedSingleton : public LLParamSingleton<DT> +{ +    typedef LLParamSingleton<DT> super; + +public: +    using super::deleteSingleton; +    using super::getInstance; +    using super::instance; +    using super::instanceExists; +    using super::wasDeleted; + +    static void construct() +    { +        super::initParamSingleton(); +    } +}; +  /**   * Use LLSINGLETON(Foo); at the start of an LLSingleton<Foo> subclass body   * when you want to declare an out-of-line constructor: @@ -484,13 +755,13 @@ typename LLSingleton<T>::SingletonData LLSingleton<T>::sData;   * file, use 'inline' (unless it's a template class) to avoid duplicate-symbol   * errors at link time.   */ -#define LLSINGLETON(DERIVED_CLASS)                                      \ +#define LLSINGLETON(DERIVED_CLASS, ...)                                 \  private:                                                                \      /* implement LLSingleton pure virtual method whose sole purpose */  \      /* is to remind people to use this macro */                         \      virtual void you_must_use_LLSINGLETON_macro() {}                    \      friend class LLSingleton<DERIVED_CLASS>;                            \ -    DERIVED_CLASS() +    DERIVED_CLASS(__VA_ARGS__)  /**   * Use LLSINGLETON_EMPTY_CTOR(Foo); at the start of an LLSingleton<Foo> diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index a459d17fb8..fa02d2bb1a 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -506,16 +506,10 @@ namespace tut              replyName = waiter.getName0();              errorName = waiter.getName1();              WrapLLErrs capture; -            try -            { -                result = waiter.suspendWithLog(); -                debug("no exception"); -            } -            catch (const WrapLLErrs::FatalException& e) -            { -                debug(STRINGIZE("exception " << e.what())); -                threw = e.what(); -            } +            threw = capture.catch_llerrs([&waiter, &debug](){ +                    result = waiter.suspendWithLog(); +                    debug("no exception"); +                });          }          END      } @@ -762,18 +756,13 @@ namespace tut          {              LLCoroEventPumps waiter;              WrapLLErrs capture; -            try -            { -                result = waiter.postAndSuspendWithLog( -                    LLSDMap("value", 31)("fail", LLSD()), -                    immediateAPI.getPump(), "reply", "error"); -                debug("no exception"); -            } -            catch (const WrapLLErrs::FatalException& e) -            { -                debug(STRINGIZE("exception " << e.what())); -                threw = e.what(); -            } +            threw = capture.catch_llerrs( +                [&waiter, &debug](){ +                    result = waiter.postAndSuspendWithLog( +                        LLSDMap("value", 31)("fail", LLSD()), +                        immediateAPI.getPump(), "reply", "error"); +                    debug("no exception"); +                });          }          END      } diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 5a4df81bf1..a181d5c941 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -22,6 +22,7 @@  #include "llsdutil.h"  #include "stringize.h"  #include "tests/wrapllerrs.h" +#include "../test/catch_and_store_what_in.h"  #include <map>  #include <string> @@ -630,16 +631,9 @@ namespace tut          void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)          { -            std::string threw; -            try -            { -                work(func, args); -            } -            catch (const std::runtime_error& e) -            { -                cout << "*** " << e.what() << '\n'; -                threw = e.what(); -            } +            std::string threw = catch_what<std::runtime_error>([this, &func, &args](){ +                    work(func, args); +                });              ensure_has(threw, exc_frag);          } @@ -717,15 +711,9 @@ namespace tut          LLSD attempts(LLSDArray(17)(LLSDMap("pi", 3.14)("two", 2)));          foreach(LLSD ae, inArray(attempts))          { -            std::string threw; -            try -            { -                work.add("freena_err", "freena", freena, ae); -            } -            catch (const std::exception& e) -            { -                threw = e.what(); -            } +            std::string threw = catch_what<std::exception>([this, &ae](){ +                    work.add("freena_err", "freena", freena, ae); +                });              ensure_has(threw, "must be an array");          }      } @@ -734,15 +722,9 @@ namespace tut      void object::test<2>()      {          set_test_name("map-style registration with badly-formed defaults"); -        std::string threw; -        try -        { -            work.add("freena_err", "freena", freena, LLSDArray("a")("b"), 17); -        } -        catch (const std::exception& e) -        { -            threw = e.what(); -        } +        std::string threw = catch_what<std::exception>([this](){ +                work.add("freena_err", "freena", freena, LLSDArray("a")("b"), 17); +            });          ensure_has(threw, "must be a map or an array");      } @@ -750,17 +732,11 @@ namespace tut      void object::test<3>()      {          set_test_name("map-style registration with too many array defaults"); -        std::string threw; -        try -        { -            work.add("freena_err", "freena", freena, -                     LLSDArray("a")("b"), -                     LLSDArray(17)(0.9)("gack")); -        } -        catch (const std::exception& e) -        { -            threw = e.what(); -        } +        std::string threw = catch_what<std::exception>([this](){ +                work.add("freena_err", "freena", freena, +                         LLSDArray("a")("b"), +                         LLSDArray(17)(0.9)("gack")); +            });          ensure_has(threw, "shorter than");      } @@ -768,17 +744,11 @@ namespace tut      void object::test<4>()      {          set_test_name("map-style registration with too many map defaults"); -        std::string threw; -        try -        { -            work.add("freena_err", "freena", freena, -                     LLSDArray("a")("b"), -                     LLSDMap("b", 17)("foo", 3.14)("bar", "sinister")); -        } -        catch (const std::exception& e) -        { -            threw = e.what(); -        } +        std::string threw = catch_what<std::exception>([this](){ +                work.add("freena_err", "freena", freena, +                         LLSDArray("a")("b"), +                         LLSDMap("b", 17)("foo", 3.14)("bar", "sinister")); +            });          ensure_has(threw, "nonexistent params");          ensure_has(threw, "foo");          ensure_has(threw, "bar"); @@ -1039,16 +1009,9 @@ namespace tut          // We don't have a comparable helper function for the one-arg          // operator() method, and it's not worth building one just for this          // case. Write it out. -        std::string threw; -        try -        { -            work(LLSDMap("op", "freek")); -        } -        catch (const std::runtime_error& e) -        { -            cout << "*** " << e.what() << "\n"; -            threw = e.what(); -        } +        std::string threw = catch_what<std::runtime_error>([this](){ +                work(LLSDMap("op", "freek")); +            });          ensure_has(threw, "bad");          ensure_has(threw, "op");          ensure_has(threw, "freek"); diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp index eb98b12ef5..1875013794 100644 --- a/indra/llcommon/tests/lleventfilter_test.cpp +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -350,15 +350,9 @@ namespace tut          // Now let the timer expire.          filter.forceTimeout();          // Notice the timeout. -        std::string threw; -        try -        { -            mainloop.post(17); -        } -        catch (const WrapLLErrs::FatalException& e) -        { -            threw = e.what(); -        } +        std::string threw = capture.catch_llerrs([this](){ +                mainloop.post(17); +            });          ensure_contains("errorAfter() timeout exception", threw, "timeout");          // Timing out cancels the timer. Verify that.          listener0.reset(0); diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index c7d4b8a06b..d94fc0c56d 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -198,14 +198,9 @@ namespace tut          {              WrapLLErrs wrapper;              Keyed::instance_iter i(Keyed::beginInstances()); -            try -            { -                delete keyed; -            } -            catch (const WrapLLErrs::FatalException& e) -            { -                what = e.what(); -            } +            what = wrapper.catch_llerrs([&keyed](){ +                    delete keyed; +                });          }          ensure(! what.empty());      } @@ -219,14 +214,9 @@ namespace tut          {              WrapLLErrs wrapper;              Keyed::key_iter i(Keyed::beginKeys()); -            try -            { -                delete keyed; -            } -            catch (const WrapLLErrs::FatalException& e) -            { -                what = e.what(); -            } +            what = wrapper.catch_llerrs([&keyed](){ +                    delete keyed; +                });          }          ensure(! what.empty());      } @@ -240,14 +230,9 @@ namespace tut          {              WrapLLErrs wrapper;              Unkeyed::instance_iter i(Unkeyed::beginInstances()); -            try -            { -                delete unkeyed; -            } -            catch (const WrapLLErrs::FatalException& e) -            { -                what = e.what(); -            } +            what = wrapper.catch_llerrs([&unkeyed](){ +                    delete unkeyed; +                });          }          ensure(! what.empty());      } diff --git a/indra/llcommon/tests/lllazy_test.cpp b/indra/llcommon/tests/lllazy_test.cpp index 32a717f4fc..542306ee22 100644 --- a/indra/llcommon/tests/lllazy_test.cpp +++ b/indra/llcommon/tests/lllazy_test.cpp @@ -38,6 +38,7 @@  #include <boost/lambda/bind.hpp>  // other Linden headers  #include "../test/lltut.h" +#include "../test/catch_and_store_what_in.h"  namespace bll = boost::lambda; @@ -200,15 +201,9 @@ namespace tut      void lllazy_object::test<2>()      {          TestNeedsTesting tnt; -        std::string threw; -        try -        { -            tnt.toolate(); -        } -        catch (const LLLazyCommon::InstanceChange& e) -        { -            threw = e.what(); -        } +        std::string threw = catch_what<LLLazyCommon::InstanceChange>([&tnt](){ +                tnt.toolate(); +            });          ensure_contains("InstanceChange exception", threw, "replace LLLazy instance");      } diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 45648536c4..bf0a74d10d 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -23,7 +23,7 @@  #include "../test/lltut.h"  #include "../test/namedtempfile.h"  #include "../test/catch_and_store_what_in.h" -#include "wrapllerrs.h" +#include "wrapllerrs.h"             // CaptureLog  #include "llevents.h"  #include "llprocess.h"  #include "llstring.h" @@ -290,12 +290,9 @@ namespace tut      void object::test<6>()      {          set_test_name("empty plugin vector"); -        std::string threw; -        try -        { -            LLLeap::create("empty", StringVec()); -        } -        CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error) +        std::string threw = catch_what<LLLeap::Error>([](){ +                LLLeap::create("empty", StringVec()); +            });          ensure_contains("LLLeap::Error", threw, "no plugin");          // try the suppress-exception variant          ensure("bad launch returned non-NULL", ! LLLeap::create("empty", StringVec(), false)); @@ -308,12 +305,9 @@ namespace tut          // Synthesize bogus executable name          std::string BADPYTHON(PYTHON.substr(0, PYTHON.length()-1) + "x");          CaptureLog log; -        std::string threw; -        try -        { -            LLLeap::create("bad exe", BADPYTHON); -        } -        CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error) +        std::string threw = catch_what<LLLeap::Error>([&BADPYTHON](){ +                LLLeap::create("bad exe", BADPYTHON); +            });          ensure_contains("LLLeap::create() didn't throw", threw, "failed");          log.messageWith("failed");          log.messageWith(BADPYTHON); diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 5c87cdabd9..222d832084 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -25,8 +25,6 @@  #include <boost/function.hpp>  #include <boost/algorithm/string/find_iterator.hpp>  #include <boost/algorithm/string/finder.hpp> -//#include <boost/lambda/lambda.hpp> -//#include <boost/lambda/bind.hpp>  // other Linden headers  #include "../test/lltut.h"  #include "../test/namedtempfile.h" @@ -35,7 +33,7 @@  #include "llsdutil.h"  #include "llevents.h"  #include "llstring.h" -#include "wrapllerrs.h" +#include "wrapllerrs.h"             // CaptureLog  #if defined(LL_WINDOWS)  #define sleep(secs) _sleep((secs) * 1000) @@ -45,8 +43,7 @@  #include <sys/wait.h>  #endif -//namespace lambda = boost::lambda; - std::string apr_strerror_helper(apr_status_t rv) +std::string apr_strerror_helper(apr_status_t rv)  {      char errbuf[256];      apr_strerror(rv, errbuf, sizeof(errbuf)); @@ -960,12 +957,9 @@ namespace tut  #define CATCH_IN(THREW, EXCEPTION, CODE)                                \      do                                                                  \      {                                                                   \ -        (THREW).clear();                                                \ -        try                                                             \ -        {                                                               \ -            CODE;                                                       \ -        }                                                               \ -        CATCH_AND_STORE_WHAT_IN(THREW, EXCEPTION)                       \ +        (THREW) = catch_what<EXCEPTION>([&](){                          \ +                CODE;                                                   \ +            });                                                         \          ensure("failed to throw " #EXCEPTION ": " #CODE, ! (THREW).empty()); \      } while (0) diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index 56886bc73f..75ddff9d7d 100644 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -29,7 +29,8 @@  #include "llsingleton.h"  #include "../test/lltut.h" - +#include "wrapllerrs.h" +#include "llsd.h"  // Capture execution sequence by appending to log string.  std::string sLog; @@ -198,4 +199,134 @@ namespace tut      TESTS(A, B, 4, 5, 6, 7)      TESTS(B, A, 8, 9, 10, 11) + +#define PARAMSINGLETON(cls)                                             \ +    class cls: public LLParamSingleton<cls>                             \ +    {                                                                   \ +        LLSINGLETON(cls, const LLSD::String& str): mDesc(str) {}        \ +        cls(LLSD::Integer i): mDesc(i) {}                               \ +                                                                        \ +    public:                                                             \ +        std::string desc() const { return mDesc.asString(); }           \ +                                                                        \ +    private:                                                            \ +        LLSD mDesc;                                                     \ +    } + +    // Declare two otherwise-identical LLParamSingleton classes so we can +    // validly initialize each using two different constructors. If we tried +    // to test that with a single LLParamSingleton class within the same test +    // program, we'd get 'trying to use deleted LLParamSingleton' errors. +    PARAMSINGLETON(PSing1); +    PARAMSINGLETON(PSing2); + +    template<> template<> +    void singleton_object_t::test<12>() +    { +        set_test_name("LLParamSingleton"); + +        WrapLLErrs catcherr; +        // query methods +        ensure("false positive on instanceExists()", ! PSing1::instanceExists()); +        ensure("false positive on wasDeleted()", ! PSing1::wasDeleted()); +        // try to reference before initializing +        std::string threw = catcherr.catch_llerrs([](){ +                (void)PSing1::instance(); +            }); +        ensure_contains("too-early instance() didn't throw", threw, "Uninitialized"); +        // getInstance() behaves the same as instance() +        threw = catcherr.catch_llerrs([](){ +                (void)PSing1::getInstance(); +            }); +        ensure_contains("too-early getInstance() didn't throw", threw, "Uninitialized"); +        // initialize using LLSD::String constructor +        PSing1::initParamSingleton("string"); +        ensure_equals(PSing1::instance().desc(), "string"); +        ensure("false negative on instanceExists()", PSing1::instanceExists()); +        // try to initialize again +        threw = catcherr.catch_llerrs([](){ +                PSing1::initParamSingleton("again"); +            }); +        ensure_contains("second ctor(string) didn't throw", threw, "twice"); +        // try to initialize using the other constructor -- should be +        // well-formed, but illegal at runtime +        threw = catcherr.catch_llerrs([](){ +                PSing1::initParamSingleton(17); +            }); +        ensure_contains("other ctor(int) didn't throw", threw, "twice"); +        PSing1::deleteSingleton(); +        ensure("false negative on wasDeleted()", PSing1::wasDeleted()); +        threw = catcherr.catch_llerrs([](){ +                (void)PSing1::instance(); +            }); +        ensure_contains("accessed deleted LLParamSingleton", threw, "deleted"); +    } + +    template<> template<> +    void singleton_object_t::test<13>() +    { +        set_test_name("LLParamSingleton alternate ctor"); + +        WrapLLErrs catcherr; +        // We don't have to restate all the tests for PSing1. Only test validly +        // using the other constructor. +        PSing2::initParamSingleton(17); +        ensure_equals(PSing2::instance().desc(), "17"); +        // can't do it twice +        std::string threw = catcherr.catch_llerrs([](){ +                PSing2::initParamSingleton(34); +            }); +        ensure_contains("second ctor(int) didn't throw", threw, "twice"); +        // can't use the other constructor either +        threw = catcherr.catch_llerrs([](){ +                PSing2::initParamSingleton("string"); +            }); +        ensure_contains("other ctor(string) didn't throw", threw, "twice"); +    } + +    class CircularPCtor: public LLParamSingleton<CircularPCtor> +    { +        LLSINGLETON(CircularPCtor) +        { +            // never mind indirection, just go straight for the circularity +            (void)instance(); +        } +    }; + +    template<> template<> +    void singleton_object_t::test<14>() +    { +        set_test_name("Circular LLParamSingleton constructor"); +        WrapLLErrs catcherr; +        std::string threw = catcherr.catch_llerrs([](){ +                CircularPCtor::initParamSingleton(); +            }); +        ensure_contains("constructor circularity didn't throw", threw, "constructor"); +    } + +    class CircularPInit: public LLParamSingleton<CircularPInit> +    { +        LLSINGLETON_EMPTY_CTOR(CircularPInit); +    public: +        virtual void initSingleton() +        { +            // never mind indirection, just go straight for the circularity +            CircularPInit *pt = getInstance(); +            if (!pt) +            { +                throw; +            } +        } +    }; + +    template<> template<> +    void singleton_object_t::test<15>() +    { +        set_test_name("Circular LLParamSingleton initSingleton()"); +        WrapLLErrs catcherr; +        std::string threw = catcherr.catch_llerrs([](){ +                CircularPInit::initParamSingleton(); +            }); +        ensure("initSingleton() circularity threw", threw.empty()); +    }  } diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 08fbf19b1c..b07d5afbd8 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -37,6 +37,7 @@  #include "llerrorcontrol.h"  #include "llexception.h"  #include "stringize.h" +#include "../test/catch_and_store_what_in.h"  #include <boost/bind.hpp>  #include <boost/noncopyable.hpp>  #include <boost/shared_ptr.hpp> @@ -81,6 +82,31 @@ struct WrapLLErrs          LLTHROW(FatalException(message));      } +    /// Convenience wrapper for catch_what<FatalException>() +    // +    // The implementation makes it clear that this function need not be a +    // member; it could easily be a free function. It is a member because it +    // makes no sense to attempt to catch FatalException unless there is a +    // WrapLLErrs instance in scope. Without a live WrapLLErrs instance, any +    // LL_ERRS() reached by code within 'func' would terminate the test +    // program instead of throwing FatalException. +    // +    // We were tempted to introduce a free function, likewise accepting +    // arbitrary 'func', that would instantiate WrapLLErrs and then call +    // catch_llerrs() on that instance. We decided against it, for this +    // reason: on extending a test function containing a single call to that +    // free function, a maintainer would most likely make additional calls to +    // that free function, instead of switching to an explicit WrapLLErrs +    // declaration with several calls to its catch_llerrs() member function. +    // Even a construct such as WrapLLErrs().catch_llerrs(...) would make the +    // object declaration more visible; it's not unreasonable to expect a +    // maintainer to extend that by naming and reusing the WrapLLErrs instance. +    template <typename FUNC> +    std::string catch_llerrs(FUNC func) +    { +        return catch_what<FatalException>(func); +    } +      std::string error;      LLError::SettingsStoragePtr mPriorErrorSettings;      LLError::FatalFunction mPriorFatal; | 
