diff options
| author | Nat Goodspeed <nat@lindenlab.com> | 2019-08-12 17:35:45 -0400 | 
|---|---|---|
| committer | Nat Goodspeed <nat@lindenlab.com> | 2019-08-12 17:35:45 -0400 | 
| commit | 4fce6dc4353dbf9ccd3c9c3aced89df72a4f3abd (patch) | |
| tree | 818dfbed3ede3f6700801d592e7e3c4db28788e2 /indra/llcommon | |
| parent | 5a72d34b7676031da35f93c91eda3eab01085aff (diff) | |
DRTVWR-493: Permit LLParamSingleton::initSingleton() circularity.
This was forbidden, but AndreyK points out cases in which LLParamSingleton::
initSingleton() should in fact be allowed to circle back to its own instance()
method. Use a recursive_mutex instead of plain mutex to permit that; remove
LL_ERRS preventing it.
Add LLParamSingleton::instance() method that calls
LLParamSingleton::getInstance(). Inheriting LLSingleton::instance() called
LLSingleton::getInstance() -- not at all what we want.
Add LLParamSingleton unit tests.
Diffstat (limited to 'indra/llcommon')
| -rw-r--r-- | indra/llcommon/llsingleton.h | 30 | ||||
| -rw-r--r-- | indra/llcommon/tests/llsingleton_test.cpp | 129 | 
2 files changed, 144 insertions, 15 deletions
| diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 38d5af5309..03b7d5a349 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -491,9 +491,6 @@ typename LLSingleton<T>::SingletonData LLSingleton<T>::sData;   * * However, distinct initParamSingleton() calls can be used to engage   *   different constructors, as long as only one such call is executed at   *   runtime. - * * Circularity is not permitted. No LLSingleton referenced by an - *   LLParamSingleton's constructor or initSingleton() method may call this - *   LLParamSingleton's instance() or getInstance() methods.   * * Unlike LLSingleton, an LLParamSingleton cannot be "revived" by an   *   instance() or getInstance() call after deleteSingleton().   * @@ -508,7 +505,6 @@ private:  public:      using super::deleteSingleton; -    using super::instance;      using super::instanceExists;      using super::wasDeleted; @@ -519,7 +515,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<std::mutex> lk(mMutex); +        std::unique_lock<decltype(mMutex)> lk(mMutex);          // For organizational purposes this function shouldn't be called twice          if (super::sData.mInitState != super::UNINITIALIZED)          { @@ -538,7 +534,7 @@ public:      {          // In case racing threads call getInstance() at the same moment as          // initParamSingleton(), serialize the calls. -        std::unique_lock<std::mutex> lk(mMutex); +        std::unique_lock<decltype(mMutex)> lk(mMutex);          switch (super::sData.mInitState)          { @@ -550,15 +546,10 @@ public:          case super::CONSTRUCTING:              super::logerrs("Tried to access param singleton ",                             super::demangle(typeid(DERIVED_TYPE).name()).c_str(), -                " from singleton constructor!"); +                           " from singleton constructor!");              break;          case super::INITIALIZING: -            super::logerrs("Tried to access param singleton ", -                           super::demangle(typeid(DERIVED_TYPE).name()).c_str(), -                           " from initSingleton() method!"); -            break; -          case super::INITIALIZED:              return super::sData.mInstance; @@ -573,12 +564,23 @@ public:          return nullptr;      } +    // instance() is replicated here so it calls +    // LLParamSingleton::getInstance() rather than LLSingleton::getInstance() +    // -- avoid making getInstance() virtual +    static DERIVED_TYPE& instance() +    { +        return *getInstance(); +    } +  private: -    static std::mutex mMutex; +    // Use a recursive_mutex in case of constructor circularity. With a +    // non-recursive mutex, that would result in deadlock rather than the +    // logerrs() call coded above. +    static std::recursive_mutex mMutex;  };  template<typename T> -typename std::mutex LLParamSingleton<T>::mMutex; +typename std::recursive_mutex LLParamSingleton<T>::mMutex;  /**   * Initialization locked singleton, only derived class can decide when to initialize. diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index 56886bc73f..da7bc6355c 100644 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -29,7 +29,8 @@  #include "llsingleton.h"  #include "../test/lltut.h" - +#include "wrapllerrs.h" +#include "llsd.h"  // Capture execution sequence by appending to log string.  std::string sLog; @@ -198,4 +199,130 @@ namespace tut      TESTS(A, B, 4, 5, 6, 7)      TESTS(B, A, 8, 9, 10, 11) + +#define PARAMSINGLETON(cls)                                             \ +    class cls: public LLParamSingleton<cls>                             \ +    {                                                                   \ +        LLSINGLETON(cls, const LLSD::String& str): mDesc(str) {}        \ +        cls(LLSD::Integer i): mDesc(i) {}                               \ +                                                                        \ +    public:                                                             \ +        std::string desc() const { return mDesc.asString(); }           \ +                                                                        \ +    private:                                                            \ +        LLSD mDesc;                                                     \ +    } + +    // Declare two otherwise-identical LLParamSingleton classes so we can +    // validly initialize each using two different constructors. If we tried +    // to test that with a single LLParamSingleton class within the same test +    // program, we'd get 'trying to use deleted LLParamSingleton' errors. +    PARAMSINGLETON(PSing1); +    PARAMSINGLETON(PSing2); + +    template<> template<> +    void singleton_object_t::test<12>() +    { +        set_test_name("LLParamSingleton"); + +        WrapLLErrs catcherr; +        // query methods +        ensure("false positive on instanceExists()", ! PSing1::instanceExists()); +        ensure("false positive on wasDeleted()", ! PSing1::wasDeleted()); +        // try to reference before initializing +        std::string threw = catcherr.catch_llerrs([](){ +                (void)PSing1::instance(); +            }); +        ensure_contains("too-early instance() didn't throw", threw, "Uninitialized"); +        // getInstance() behaves the same as instance() +        threw = catcherr.catch_llerrs([](){ +                (void)PSing1::getInstance(); +            }); +        ensure_contains("too-early getInstance() didn't throw", threw, "Uninitialized"); +        // initialize using LLSD::String constructor +        PSing1::initParamSingleton("string"); +        ensure_equals(PSing1::instance().desc(), "string"); +        ensure("false negative on instanceExists()", PSing1::instanceExists()); +        // try to initialize again +        threw = catcherr.catch_llerrs([](){ +                PSing1::initParamSingleton("again"); +            }); +        ensure_contains("second ctor(string) didn't throw", threw, "twice"); +        // try to initialize using the other constructor -- should be +        // well-formed, but illegal at runtime +        threw = catcherr.catch_llerrs([](){ +                PSing1::initParamSingleton(17); +            }); +        ensure_contains("other ctor(int) didn't throw", threw, "twice"); +        PSing1::deleteSingleton(); +        ensure("false negative on wasDeleted()", PSing1::wasDeleted()); +        threw = catcherr.catch_llerrs([](){ +                (void)PSing1::instance(); +            }); +        ensure_contains("accessed deleted LLParamSingleton", threw, "deleted"); +    } + +    template<> template<> +    void singleton_object_t::test<13>() +    { +        set_test_name("LLParamSingleton alternate ctor"); + +        WrapLLErrs catcherr; +        // We don't have to restate all the tests for PSing1. Only test validly +        // using the other constructor. +        PSing2::initParamSingleton(17); +        ensure_equals(PSing2::instance().desc(), "17"); +        // can't do it twice +        std::string threw = catcherr.catch_llerrs([](){ +                PSing2::initParamSingleton(34); +            }); +        ensure_contains("second ctor(int) didn't throw", threw, "twice"); +        // can't use the other constructor either +        threw = catcherr.catch_llerrs([](){ +                PSing2::initParamSingleton("string"); +            }); +        ensure_contains("other ctor(string) didn't throw", threw, "twice"); +    } + +    class CircularPCtor: public LLParamSingleton<CircularPCtor> +    { +        LLSINGLETON(CircularPCtor) +        { +            // never mind indirection, just go straight for the circularity +            (void)instance(); +        } +    }; + +    template<> template<> +    void singleton_object_t::test<14>() +    { +        set_test_name("Circular LLParamSingleton constructor"); +        WrapLLErrs catcherr; +        std::string threw = catcherr.catch_llerrs([](){ +                CircularPCtor::initParamSingleton(); +            }); +        ensure_contains("constructor circularity didn't throw", threw, "constructor"); +    } + +    class CircularPInit: public LLParamSingleton<CircularPInit> +    { +        LLSINGLETON_EMPTY_CTOR(CircularPInit); +    public: +        virtual void initSingleton() +        { +            // never mind indirection, just go straight for the circularity +            (void)instance(); +        } +    }; + +    template<> template<> +    void singleton_object_t::test<15>() +    { +        set_test_name("Circular LLParamSingleton initSingleton()"); +        WrapLLErrs catcherr; +        std::string threw = catcherr.catch_llerrs([](){ +                CircularPInit::initParamSingleton(); +            }); +        ensure("initSingleton() circularity threw", threw.empty()); +    }  } | 
