From c1133cb932959207dbaf2da33832afb5a19b6063 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Tue, 6 Mar 2012 17:04:05 +0100 Subject: [PATCH 01/19] Use LLAPRPool apr_pool_t* should not be used anywhere, except where a comment explicitely says it's ok. This one apparently sneeked in in the meantime. --- indra/llcommon/llaprpool.cpp | 2 +- indra/llcommon/llaprpool.h | 2 +- indra/newview/llappviewerlinux_api_dbus.cpp | 23 +++++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/indra/llcommon/llaprpool.cpp b/indra/llcommon/llaprpool.cpp index 03ab406a2..3559ff430 100644 --- a/indra/llcommon/llaprpool.cpp +++ b/indra/llcommon/llaprpool.cpp @@ -1,5 +1,5 @@ /** - * @file LLAPRPool.cpp + * @file llaprpool.cpp * * Copyright (c) 2010, Aleric Inglewood. * diff --git a/indra/llcommon/llaprpool.h b/indra/llcommon/llaprpool.h index aefdaa08b..133f60316 100644 --- a/indra/llcommon/llaprpool.h +++ b/indra/llcommon/llaprpool.h @@ -1,5 +1,5 @@ /** - * @file LLAPRPool.h + * @file llaprpool.h * @brief Implementation of LLAPRPool. * * Copyright (c) 2010, Aleric Inglewood. diff --git a/indra/newview/llappviewerlinux_api_dbus.cpp b/indra/newview/llappviewerlinux_api_dbus.cpp index b475eeedb..dfb138c79 100644 --- a/indra/newview/llappviewerlinux_api_dbus.cpp +++ b/indra/newview/llappviewerlinux_api_dbus.cpp @@ -38,6 +38,7 @@ #endif #include "linden_common.h" +#include "llaprpool.h" extern "C" { #include @@ -55,8 +56,8 @@ extern "C" { #undef LL_DBUS_SYM static bool sSymsGrabbed = false; -static apr_pool_t *sSymDBUSDSOMemoryPool = NULL; -static apr_dso_handle_t *sSymDBUSDSOHandleG = NULL; +static LLAPRPool sSymDBUSDSOMemoryPool; // Used for sSymDBUSDSOHandleG (and what it is pointing at?) +static apr_dso_handle_t* sSymDBUSDSOHandleG = NULL; bool grab_dbus_syms(std::string dbus_dso_name) { @@ -74,12 +75,12 @@ bool grab_dbus_syms(std::string dbus_dso_name) #define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) do{rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll##DBUSSYM, sSymDBUSDSOHandle, #DBUSSYM); if (rv != APR_SUCCESS) {INFOMSG("Failed to grab symbol: %s", #DBUSSYM); if (REQUIRED) sym_error = true;} else DEBUGMSG("grabbed symbol: %s from %p", #DBUSSYM, (void*)ll##DBUSSYM);}while(0) //attempt to load the shared library - apr_pool_create(&sSymDBUSDSOMemoryPool, NULL); + sSymDBUSDSOMemoryPool.create(); #ifdef LL_STANDALONE void *dso_handle = dlopen(dbus_dso_name.c_str(), RTLD_NOW | RTLD_GLOBAL); rv = (!dso_handle)?APR_EDSOOPEN:apr_os_dso_handle_put(&sSymDBUSDSOHandle, - dso_handle, sSymDBUSDSOMemoryPool); + dso_handle, sSymDBUSDSOMemoryPool()); if ( APR_SUCCESS == rv ) #else @@ -113,6 +114,10 @@ bool grab_dbus_syms(std::string dbus_dso_name) #undef LL_DBUS_SYM sSymsGrabbed = rtn; + if (!sSymsGrabbed) + { + sSymDBUSDSOMemoryPool.destroy(); + } return rtn; } @@ -127,13 +132,9 @@ void ungrab_dbus_syms() apr_dso_unload(sSymDBUSDSOHandleG); sSymDBUSDSOHandleG = NULL; } - - if ( sSymDBUSDSOMemoryPool ) - { - apr_pool_destroy(sSymDBUSDSOMemoryPool); - sSymDBUSDSOMemoryPool = NULL; - } - + + sSymDBUSDSOMemoryPool.destroy(); + // NULL-out all of the symbols we'd grabbed #define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) do{ll##DBUSSYM = NULL;}while(0) #include "llappviewerlinux_api_dbus_syms_raw.inc" From 52b4008968f24c4348f85787d9c23b391dbf1af7 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Tue, 6 Mar 2012 23:10:43 +0100 Subject: [PATCH 02/19] Prepare synchronization with viewer-development. Synchronization will be complete after viewer-development gets my LLAPRPool and LLAPRFile patch that I'm currently working on. --- indra/llcommon/llapr.cpp | 6 ++---- indra/llcommon/llapr.h | 6 +++--- indra/llcommon/llaprpool.h | 4 ++-- indra/llcommon/llthread.cpp | 11 ++++++++++- indra/llcommon/llthread.h | 8 ++++++-- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index ee7717f12..d16b87cd7 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -88,7 +88,7 @@ bool ll_apr_warn_status(apr_status_t status) void ll_apr_assert_status(apr_status_t status) { - llassert(ll_apr_warn_status(status) == false); + llassert(!ll_apr_warn_status(status)); } //--------------------------------------------------------------------- @@ -146,7 +146,7 @@ apr_status_t LLAPRFile::open(std::string const& filename, apr_int32_t flags, acc apr_status_t status; { - apr_pool_t* apr_file_open_pool; + apr_pool_t* apr_file_open_pool; // The use of apr_pool_t is OK here. // This is a temporary variable for a pool that is passed directly to apr_file_open below. if (access_type == short_lived) { @@ -199,7 +199,6 @@ apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, BOO // File I/O S32 LLAPRFile::read(void *buf, S32 nbytes) { - //llassert_always(mFile); (ASC-TUDCC) -HgB if(!mFile) { llwarns << "apr mFile is removed by somebody else. Can not read." << llendl ; @@ -222,7 +221,6 @@ S32 LLAPRFile::read(void *buf, S32 nbytes) S32 LLAPRFile::write(const void *buf, S32 nbytes) { - // llassert_always(mFile); (ASC-TUDCC) -HgB if(!mFile) { llwarns << "apr mFile is removed by somebody else. Can not write." << llendl ; diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index e7c3c53f1..3574e1790 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -113,7 +113,7 @@ typedef LLAtomic32 LLAtomicU32; typedef LLAtomic32 LLAtomicS32; // File IO convenience functions. -// Returns NULL if the file fails to openm sets *sizep to file size of not NULL +// Returns NULL if the file fails to open, sets *sizep to file size if not NULL // abbreviated flags #define LL_APR_R (APR_READ) // "r" #define LL_APR_W (APR_CREATE|APR_TRUNCATE|APR_WRITE) // "w" @@ -131,7 +131,7 @@ typedef LLAtomic32 LLAtomicS32; // especially do not put some time-costly operations between open() and close(). // otherwise it might lock the APRFilePool. //there are two different apr_pools the APRFile can use: -// 1, a temperary pool passed to an APRFile function, which is used within this function and only once. +// 1, a temporary pool passed to an APRFile function, which is used within this function and only once. // 2, a global pool. // @@ -189,7 +189,7 @@ public: }; /** - * @brief Function which approprately logs error or remains quiet on + * @brief Function which appropriately logs error or remains quiet on * APR_SUCCESS. * @return Returns true if status is an error condition. */ diff --git a/indra/llcommon/llaprpool.h b/indra/llcommon/llaprpool.h index 133f60316..dc123e942 100644 --- a/indra/llcommon/llaprpool.h +++ b/indra/llcommon/llaprpool.h @@ -60,9 +60,9 @@ extern void ll_init_apr(); class LL_COMMON_API LLAPRPool { protected: - apr_pool_t* mPool; //!< Pointer to the underlaying pool. NULL if not initialized. + apr_pool_t* mPool; //!< Pointer to the underlaying pool. NULL if not initialized. LLAPRPool* mParent; //!< Pointer to the parent pool, if any. Only valid when mPool is non-zero. - apr_os_thread_t mOwner; //!< The thread that owns this memory pool. Only valid when mPool is non-zero. + apr_os_thread_t mOwner; //!< The thread that owns this memory pool. Only valid when mPool is non-zero. public: //! Construct an uninitialized (destructed) pool. diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 8ae458c1c..ef52dde46 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -399,6 +399,15 @@ LLMutexBase::LLMutexBase() : { } +bool LLMutexBase::isSelfLocked() const +{ +#if LL_DARWIN + return mLockingThread == LLThread::currentID(); +#else + return mLockingThread == local_thread_ID; +#endif +} + void LLMutexBase::lock() { #if LL_DARWIN @@ -431,7 +440,7 @@ void LLMutexBase::unlock() apr_thread_mutex_unlock(mAPRMutexp); } - + //---------------------------------------------------------------------------- //static diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index f0e4f39f5..c5a06f19c 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -73,7 +73,7 @@ class LL_COMMON_API LLThread { private: static U32 sIDIter; - + public: typedef enum e_thread_status { @@ -179,13 +179,17 @@ public: LLMutexBase() ; - void lock(); //blocks + void lock(); // blocks void unlock(); // Returns true if lock was obtained successfully. bool tryLock() { return !APR_STATUS_IS_EBUSY(apr_thread_mutex_trylock(mAPRMutexp)); } // non-blocking, but does do a lock/unlock so not free bool isLocked() { bool is_not_locked = tryLock(); if (is_not_locked) unlock(); return !is_not_locked; } + + // Returns true if locked by this thread. + bool isSelfLocked() const; + // get ID of locking thread U32 lockingThread() const { return mLockingThread; } From 8c721c4a6905ada3725ab6a1822e23be57460bd2 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Mon, 12 Mar 2012 04:37:16 +0100 Subject: [PATCH 03/19] Merge/collision fix. Belonged in previous commit really. --- indra/llcommon/llthread.h | 1 - 1 file changed, 1 deletion(-) diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 00d085e20..19c286756 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -193,7 +193,6 @@ public: bool isSelfLocked() const; // get ID of locking thread - bool isSelfLocked(); //return true if locked in a same thread U32 lockingThread() const { return mLockingThread; } protected: From fffbda1b909f1825b4d7712726413934a23e0f64 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Mon, 12 Mar 2012 04:37:55 +0100 Subject: [PATCH 04/19] Add ASSERT_SINGLE_THREAD --- indra/llcommon/llthread.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 19c286756..89b6b8d91 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -41,6 +41,9 @@ #ifdef SHOW_ASSERT extern LL_COMMON_API bool is_main_thread(void); +#define ASSERT_SINGLE_THREAD do { static apr_os_thread_t first_thread_id = apr_os_thread_current(); llassert(apr_os_thread_equal(first_thread_id, apr_os_thread_current())); } while(0) +#else +#define ASSERT_SINGLE_THREAD do { } while(0) #endif class LLThread; From b18bc0860083e6367b6562c169261a394cae1469 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Tue, 13 Mar 2012 16:32:10 +0100 Subject: [PATCH 05/19] Add AIAccessConst, AISTAccess, AISTAccessConst, ... Also added AIThreadSafeSingleThread, AIThreadSafeSingleThreadDC and AITHREADSAFESINGLETHREAD --- indra/llcommon/aithreadsafe.h | 211 ++++++++++++++++++++++++++++++++-- 1 file changed, 201 insertions(+), 10 deletions(-) diff --git a/indra/llcommon/aithreadsafe.h b/indra/llcommon/aithreadsafe.h index 0cff3b77a..e22f587e2 100644 --- a/indra/llcommon/aithreadsafe.h +++ b/indra/llcommon/aithreadsafe.h @@ -49,7 +49,10 @@ template struct AIReadAccessConst; template struct AIReadAccess; template struct AIWriteAccess; +template struct AIAccessConst; template struct AIAccess; +template struct AISTAccessConst; +template struct AISTAccess; #if LL_WINDOWS template class AIThreadSafeBits; @@ -398,6 +401,7 @@ class AIThreadSafeSimple : public AIThreadSafeBits { protected: // Only this one may access the object (through ptr()). + friend struct AIAccessConst; friend struct AIAccess; // Locking control. @@ -509,13 +513,13 @@ public: }; /** - * @brief Write lock object and provide read/write access. + * @brief Write lock object and provide read access. */ template -struct AIAccess +struct AIAccessConst { - //! Construct a AIAccess from a non-constant AIThreadSafeSimple. - AIAccess(AIThreadSafeSimple& wrapper) : mWrapper(wrapper) + //! Construct a AIAccessConst from a constant AIThreadSafeSimple. + AIAccessConst(AIThreadSafeSimple const& wrapper) : mWrapper(const_cast&>(wrapper)) #if AI_NEED_ACCESS_CC , mIsCopyConstructed(false) #endif @@ -524,12 +528,12 @@ struct AIAccess } //! Access the underlaying object for (read and) write access. - T* operator->() const { return this->mWrapper.ptr(); } + T const* operator->() const { return this->mWrapper.ptr(); } //! Access the underlaying object for (read and) write access. - T& operator*() const { return *this->mWrapper.ptr(); } + T const& operator*() const { return *this->mWrapper.ptr(); } - ~AIAccess() + ~AIAccessConst() { #if AI_NEED_ACCESS_CC if (mIsCopyConstructed) return; @@ -538,17 +542,204 @@ struct AIAccess } protected: - AIThreadSafeSimple& mWrapper; //!< Reference to the object that we provide access to. + AIThreadSafeSimple& mWrapper; //!< Reference to the object that we provide access to. #if AI_NEED_ACCESS_CC bool mIsCopyConstructed; public: - AIAccess(AIAccess const& orig) : mWrapper(orig.mWrapper), mIsCopyConstructed(true) { } + AIAccessConst(AIAccessConst const& orig) : mWrapper(orig.mWrapper), mIsCopyConstructed(true) { } #else private: // Disallow copy constructing directly. - AIAccess(AIAccess const&); + AIAccessConst(AIAccessConst const&); #endif }; +/** + * @brief Write lock object and provide read/write access. + */ +template +struct AIAccess : public AIAccessConst +{ + //! Construct a AIAccess from a non-constant AIThreadSafeSimple. + AIAccess(AIThreadSafeSimple& wrapper) : AIAccessConst(wrapper) { } + + //! Access the underlaying object for (read and) write access. + T* operator->() const { return this->mWrapper.ptr(); } + + //! Access the underlaying object for (read and) write access. + T& operator*() const { return *this->mWrapper.ptr(); } +}; + +/** + * @brief A wrapper class for objects that should only be accessed by a single thread. + * + * Use AITHREADSAFESINGLETHREAD to define instances of any type, and use AISTAccess + * to get access to the instance. + * + * For example, + * + * + * class Foo { public: Foo(int, int); }; + * + * AITHREADSAFESINGLETHREAD(Foo, foo, (2, 3)); + * + * AISTAccess foo_w(foo); + * // Use foo_w-> for read and write access. + */ +template +class AIThreadSafeSingleThread : public AIThreadSafeBits +{ +protected: + // Only these one may access the object (through ptr()). + friend struct AISTAccessConst; + friend struct AISTAccess; + + // For use by AIThreadSafeSingleThreadDC. + AIThreadSafeSingleThread(void) +#ifdef LL_DEBUG + : mAccessed(false) +#endif + { } + +#ifdef LL_DEBUG + mutable bool mAccessed; + mutable apr_os_thread_t mTheadID; + + void accessed(void) const + { + if (!mAccessed) + { + mAccessed = true; + mTheadID = apr_os_thread_current(); + } + else + { + llassert_always(apr_os_thread_equal(mTheadID, apr_os_thread_current())); + } + } +#endif + +public: + // Only for use by AITHREADSAFESINGLETHREAD, see below. + AIThreadSafeSingleThread(T* object) +#ifdef LL_DEBUG + : mAccessed(false) +#endif + { + llassert(object == AIThreadSafeBits::ptr()); + } +}; + +/** + * @brief A wrapper class for objects that should only be accessed by a single thread. + * + * This class is the same as an AIThreadSafeSingleThread wrapper, except that it can only + * be used for default constructed objects. + * + * For example, instead of + * + * + * Foo foo; + * + * + * One would use + * + * + * AIThreadSafeSingleThreadDC foo; + * + * + * The advantage over AITHREADSAFESINGLETHREAD is that this object can be allocated with + * new on the heap. For example: + * + * + * AIThreadSafeSingleThreadDC* ptr = new AIThreadSafeSingleThreadDC; + * + * + * which is not possible with AITHREADSAFESINGLETHREAD. + */ +template +class AIThreadSafeSingleThreadDC : public AIThreadSafeSingleThread +{ +public: + // Construct a wrapper around a default constructed object. + AIThreadSafeSingleThreadDC(void) { new (AIThreadSafeSingleThread::ptr()) T; } +}; + +/** + * @brief Instantiate a static, global or local object of a given type wrapped in AIThreadSafeSingleThread, using an arbitrary constructor. + * + * For example, instead of doing + * + * + * Foo foo(x, y); + * static Bar bar; + * + * + * One can instantiate a thread-safe instance with + * + * + * AITHREADSAFESINGLETHREAD(Foo, foo, (x, y)); + * static AITHREADSAFESINGLETHREAD(Bar, bar, ); + * + * + * Note: This macro does not allow to allocate such object on the heap. + * If that is needed, have a look at AIThreadSafeSingleThreadDC. + */ +#define AITHREADSAFESINGLETHREAD(type, var, paramlist) AIThreadSafeSingleThread var(new (var.memory()) type paramlist) + +/** + * @brief Access single threaded object for read access. + */ +template +struct AISTAccessConst +{ + //! Construct a AISTAccessConst from a constant AIThreadSafeSingleThread. + AISTAccessConst(AIThreadSafeSingleThread const& wrapper) : mWrapper(const_cast&>(wrapper)) + { +#if LL_DEBUG + wrapper.accessed(); +#endif + } + + //! Access the underlaying object for read access. + T const* operator->() const { return this->mWrapper.ptr(); } + + //! Access the underlaying object for read write access. + T const& operator*() const { return *this->mWrapper.ptr(); } + +protected: + AIThreadSafeSingleThread& mWrapper; //!< Reference to the object that we provide access to. + +#if AI_NEED_ACCESS_CC +public: + AISTAccessConst(AISTAccessConst const& orig) : mWrapper(orig.mWrapper) { } +#else +private: + // Disallow copy constructing directly. + AISTAccessConst(AISTAccessConst const&); +#endif +}; + +/** + * @brief Access single threaded object for read/write access. + */ +template +struct AISTAccess : public AISTAccessConst +{ + //! Construct a AISTAccess from a non-constant AIThreadSafeSingleThread. + AISTAccess(AIThreadSafeSingleThread& wrapper) : AISTAccessConst(wrapper) + { +#if LL_DEBUG + wrapper.accessed(); +#endif + } + + //! Access the underlaying object for (read and) write access. + T* operator->() const { return this->mWrapper.ptr(); } + + //! Access the underlaying object for (read and) write access. + T& operator*() const { return *this->mWrapper.ptr(); } +}; + #endif From 1023088c0d25f2c7aabf6771f64dd6e3b7ac2261 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Tue, 13 Mar 2012 16:34:45 +0100 Subject: [PATCH 06/19] Fix 64-bit compile warning. --- indra/llrender/llvertexbuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp index 794d5c794..5a5802585 100644 --- a/indra/llrender/llvertexbuffer.cpp +++ b/indra/llrender/llvertexbuffer.cpp @@ -1223,7 +1223,7 @@ void LLVertexBuffer::setupVertexArray() if (mTypeMask & (1 << i)) { glEnableVertexAttribArrayARB(i); - glVertexAttribPointerARB(i, attrib_size[i], attrib_type[i], attrib_normalized[i], sTypeSize[i], (void*) mOffsets[i]); + glVertexAttribPointerARB(i, attrib_size[i], attrib_type[i], attrib_normalized[i], sTypeSize[i], reinterpret_cast(mOffsets[i])); } else { From 8f6dbfc39975f9e523a1e202226f9e3c8d4e28ec Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Tue, 13 Mar 2012 18:14:09 +0100 Subject: [PATCH 07/19] Compile fix for standalone --- indra/newview/llappviewerlinux_api_dbus.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llappviewerlinux_api_dbus.cpp b/indra/newview/llappviewerlinux_api_dbus.cpp index dfb138c79..656a7444a 100644 --- a/indra/newview/llappviewerlinux_api_dbus.cpp +++ b/indra/newview/llappviewerlinux_api_dbus.cpp @@ -86,7 +86,7 @@ bool grab_dbus_syms(std::string dbus_dso_name) #else if ( APR_SUCCESS == (rv = apr_dso_load(&sSymDBUSDSOHandle, dbus_dso_name.c_str(), - sSymDBUSDSOMemoryPool) )) + sSymDBUSDSOMemoryPool()) )) #endif { INFOMSG("Found DSO: %s", dbus_dso_name.c_str()); From 058720824d31ab3af900add3df7087f3fb8c5bb9 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Wed, 14 Mar 2012 01:57:44 +0100 Subject: [PATCH 08/19] Fixup of LLAtomic and LLThreadSafeRefCount --- indra/llaudio/llaudiodecodemgr.cpp | 16 +++----- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/llapr.h | 21 ---------- indra/llcommon/llatomic.h | 61 ++++++++++++++++++++++++++++++ indra/llcommon/llcommon.cpp | 2 - indra/llcommon/llthread.cpp | 24 +----------- indra/llcommon/llthread.h | 27 +++---------- 7 files changed, 73 insertions(+), 79 deletions(-) create mode 100644 indra/llcommon/llatomic.h diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp index 3af703d26..5063cbe7a 100644 --- a/indra/llaudio/llaudiodecodemgr.cpp +++ b/indra/llaudio/llaudiodecodemgr.cpp @@ -168,19 +168,13 @@ long vfs_tell (void *datasource) return file->tell(); } -LLVorbisDecodeState::LLVorbisDecodeState(const LLUUID &uuid, const std::string &out_filename) -{ - mDone = FALSE; - mValid = FALSE; - mBytesRead = -1; - mUUID = uuid; - mInFilep = NULL; - mCurrentSection = 0; +LLVorbisDecodeState::LLVorbisDecodeState(const LLUUID &uuid, const std::string &out_filename) : + mValid(FALSE), mDone(FALSE), mBytesRead(-1), mUUID(uuid), #if !defined(USE_WAV_VFILE) - mOutFilename = out_filename; - mFileHandle = LLLFSThread::nullHandle(); + mOutFilename(out_filename), mFileHandle(LLLFSThread::nullHandle()), #endif - // No default value for mVF, it's an ogg structure? + mInFilep(NULL), mCurrentSection(0) +{ } LLVorbisDecodeState::~LLVorbisDecodeState() diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 2ff8856ec..29feeebc4 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -121,6 +121,7 @@ set(llcommon_HEADER_FILES llaprpool.h llassettype.h llassoclist.h + llatomic.h llavatarconstants.h llbase32.h llbase64.h diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index 3574e1790..61f3f97c0 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -91,27 +91,6 @@ protected: apr_thread_mutex_t* mMutex; }; -template class LLAtomic32 -{ -public: - LLAtomic32() {}; - LLAtomic32(Type x) {apr_atomic_set32(&mData, apr_uint32_t(x)); }; - ~LLAtomic32() {}; - - operator const Type() { apr_uint32_t data = apr_atomic_read32(&mData); return Type(data); } - Type operator =(const Type& x) { apr_atomic_set32(&mData, apr_uint32_t(x)); return Type(mData); } - void operator -=(Type x) { apr_atomic_sub32(&mData, apr_uint32_t(x)); } - void operator +=(Type x) { apr_atomic_add32(&mData, apr_uint32_t(x)); } - Type operator ++(int) { return apr_atomic_inc32(&mData); } // Type++ - Type operator --(int) { return apr_atomic_dec32(&mData); } // Type-- - -private: - apr_uint32_t mData; -}; - -typedef LLAtomic32 LLAtomicU32; -typedef LLAtomic32 LLAtomicS32; - // File IO convenience functions. // Returns NULL if the file fails to open, sets *sizep to file size if not NULL // abbreviated flags diff --git a/indra/llcommon/llatomic.h b/indra/llcommon/llatomic.h new file mode 100644 index 000000000..3cc5bcf38 --- /dev/null +++ b/indra/llcommon/llatomic.h @@ -0,0 +1,61 @@ +/** + * @file llatomic.h + * @brief Definition of LLAtomic + * + * $LicenseInfo:firstyear=2004&license=viewergpl$ + * + * Copyright (c) 2004-2009, Linden Research, Inc. + * Copyright (c) 2012, Aleric Inglewood + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLATOMIC_H +#define LL_LLATOMIC_H + +#include "apr_atomic.h" + +template class LLAtomic32 +{ +public: + LLAtomic32(void) { } + LLAtomic32(LLAtomic32 const& atom) { apr_uint32_t data = apr_atomic_read32(const_cast(&atom.mData)); apr_atomic_set32(&mData, data); } + LLAtomic32(Type x) { apr_atomic_set32(&mData, static_cast(x)); } + LLAtomic32& operator=(LLAtomic32 const& atom) { apr_uint32_t data = apr_atomic_read32(const_cast(&atom.mData)); apr_atomic_set32(&mData, data); return *this; } + + operator Type() const { apr_uint32_t data = apr_atomic_read32(const_cast(&mData)); return static_cast(data); } + void operator=(Type x) { apr_atomic_set32(&mData, static_cast(x)); } + void operator-=(Type x) { apr_atomic_sub32(&mData, static_cast(x)); } + void operator+=(Type x) { apr_atomic_add32(&mData, static_cast(x)); } + Type operator++(int) { return apr_atomic_inc32(&mData); } // Type++ + bool operator--() { return apr_atomic_dec32(&mData); } // Returns (--Type != 0) + +private: + apr_uint32_t mData; +}; + +typedef LLAtomic32 LLAtomicU32; +typedef LLAtomic32 LLAtomicS32; + +#endif diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp index 298dd4695..33b30ac72 100644 --- a/indra/llcommon/llcommon.cpp +++ b/indra/llcommon/llcommon.cpp @@ -39,7 +39,6 @@ void LLCommon::initClass() { LLMemory::initClass(); LLTimer::initClass(); - LLThreadSafeRefCount::initThreadSafeRefCount(); // LLWorkerThread::initClass(); // LLFrameCallbackManager::initClass(); } @@ -49,7 +48,6 @@ void LLCommon::cleanupClass() { // LLFrameCallbackManager::cleanupClass(); // LLWorkerThread::cleanupClass(); - LLThreadSafeRefCount::cleanupThreadSafeRefCount(); LLTimer::cleanupClass(); LLMemory::cleanupClass(); } diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 433e109d8..4088d4a44 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -178,7 +178,7 @@ void LLThread::shutdown() } mAPRThreadp = NULL; } - sCount--; + --sCount; delete mRunCondition; mRunCondition = 0; } @@ -444,28 +444,6 @@ void LLMutexBase::unlock() apr_thread_mutex_unlock(mAPRMutexp); } -//---------------------------------------------------------------------------- - -//static -LLMutex* LLThreadSafeRefCount::sMutex = 0; - -//static -void LLThreadSafeRefCount::initThreadSafeRefCount() -{ - if (!sMutex) - { - sMutex = new LLMutex; - } -} - -//static -void LLThreadSafeRefCount::cleanupThreadSafeRefCount() -{ - delete sMutex; - sMutex = NULL; -} - - //---------------------------------------------------------------------------- LLThreadSafeRefCount::LLThreadSafeRefCount() : diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 89b6b8d91..5724e0087 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -38,6 +38,7 @@ #include "llmemory.h" #include "apr_thread_cond.h" #include "llaprpool.h" +#include "llatomic.h" #ifdef SHOW_ASSERT extern LL_COMMON_API bool is_main_thread(void); @@ -400,13 +401,6 @@ void LLThread::unlockData() class LL_COMMON_API LLThreadSafeRefCount { -public: - static void initThreadSafeRefCount(); // creates sMutex - static void cleanupThreadSafeRefCount(); // destroys sMutex - -private: - static LLMutex* sMutex; - private: LLThreadSafeRefCount(const LLThreadSafeRefCount&); // not implemented LLThreadSafeRefCount&operator=(const LLThreadSafeRefCount&); // not implemented @@ -419,31 +413,20 @@ public: void ref() { - if (sMutex) sMutex->lock(); mRef++; - if (sMutex) sMutex->unlock(); } - S32 unref() + void unref() { - llassert(mRef >= 1); - if (sMutex) sMutex->lock(); - S32 res = --mRef; - if (sMutex) sMutex->unlock(); - if (0 == res) - { - delete this; - return 0; - } - return res; - } + if (!--mRef) delete this; + } S32 getNumRefs() const { return mRef; } private: - S32 mRef; + LLAtomicS32 mRef; }; //============================================================================ From d63c54cb78514db41fc275bf4e2dc67ea92be93a Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Thu, 15 Mar 2012 03:19:28 +0100 Subject: [PATCH 09/19] Allow AIThreadSafe*DC classes to be constructed with zero or one parameter. Also, allow AIThreadSafeSingleThreadDC objects to auto convert to it's underlaying type, to assign that type and to write it directly to an ostream. --- indra/llcommon/aithreadsafe.h | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/aithreadsafe.h b/indra/llcommon/aithreadsafe.h index e22f587e2..225be3681 100644 --- a/indra/llcommon/aithreadsafe.h +++ b/indra/llcommon/aithreadsafe.h @@ -256,6 +256,8 @@ class AIThreadSafeDC : public AIThreadSafe public: // Construct a wrapper around a default constructed object. AIThreadSafeDC(void) { new (AIThreadSafe::ptr()) T; } + // Allow an arbitrary parameter to be passed for construction. + template AIThreadSafeDC(T2 const& val) { new (AIThreadSafe::ptr()) T(val); } }; /** @@ -472,10 +474,12 @@ class AIThreadSafeSimpleDC : public AIThreadSafeSimple public: // Construct a wrapper around a default constructed object. AIThreadSafeSimpleDC(void) { new (AIThreadSafeSimple::ptr()) T; } + // Allow an arbitrary parameter to be passed for construction. + template AIThreadSafeSimpleDC(T2 const& val) { new (AIThreadSafeSimple::ptr()) T(val); } protected: // For use by AIThreadSafeSimpleDCRootPool - AIThreadSafeSimpleDC(LLAPRPool& parent) : AIThreadSafeSimple(parent) { new (AIThreadSafeSimple::ptr()) T; } + AIThreadSafeSimpleDC(LLAPRRootPool& parent) : AIThreadSafeSimple(parent) { new (AIThreadSafeSimple::ptr()) T; } }; // Helper class for AIThreadSafeSimpleDCRootPool to assure initialization of @@ -664,6 +668,17 @@ class AIThreadSafeSingleThreadDC : public AIThreadSafeSingleThread public: // Construct a wrapper around a default constructed object. AIThreadSafeSingleThreadDC(void) { new (AIThreadSafeSingleThread::ptr()) T; } + // Allow an arbitrary parameter to be passed for construction. + template AIThreadSafeSingleThreadDC(T2 const& val) { new (AIThreadSafeSingleThread::ptr()) T(val); } + + // Allow assigning with T. + AIThreadSafeSingleThreadDC& operator=(T const& val) { AIThreadSafeSingleThread::accessed(); *AIThreadSafeSingleThread::ptr() = val; return *this; } + // Allow writing to an ostream. + friend std::ostream& operator<<(std::ostream& os, AIThreadSafeSingleThreadDC const& wrapped_val) { wrapped_val.accessed(); return os << *wrapped_val.ptr(); } + + // Automatic conversion to T. + operator T&(void) { AIThreadSafeSingleThread::accessed(); return *AIThreadSafeSingleThread::ptr(); } + operator T const&(void) const { AIThreadSafeSingleThread::accessed(); return *AIThreadSafeSingleThread::ptr(); } }; /** From 4391614d6e0860a76a128175cb375cc87c03dde1 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Fri, 16 Mar 2012 00:10:07 +0100 Subject: [PATCH 10/19] AIThreadSafe* update. Updated/added documentation. Removed AIThreadSafeWindowsHack that annoyed me (fix your compiler). Don't use 'static' in anonymous namespace. Use the AIThreadSafe*DC variants for default constructed objects, as opposed to the AITHREADSAFE* macro's. --- indra/llcommon/aithreadsafe.h | 123 +++++++++++++----- indra/llimage/llimage.cpp | 2 +- indra/llimage/llimage.h | 2 +- indra/newview/llviewercontrol.cpp | 2 +- indra/newview/llviewercontrol.h | 2 +- indra/newview/statemachine/aifilepicker.cpp | 2 +- indra/newview/statemachine/aifilepicker.h | 2 +- indra/newview/statemachine/aistatemachine.cpp | 6 +- indra/newview/statemachine/aistatemachine.h | 2 +- 9 files changed, 104 insertions(+), 39 deletions(-) diff --git a/indra/llcommon/aithreadsafe.h b/indra/llcommon/aithreadsafe.h index 225be3681..a3008b68a 100644 --- a/indra/llcommon/aithreadsafe.h +++ b/indra/llcommon/aithreadsafe.h @@ -26,8 +26,65 @@ * * 31/03/2010 * Initial version, written by Aleric Inglewood @ SL + * + * 14/03/2012 + * Added AIThreadSafeSingleThread and friends. + * Added AIAccessConst (and derived AIAccess from it) to allow read + * access to a const AIThreadSafeSimple. */ +// This file defines wrapper template classes for arbitrary types T +// adding locking to the instance and shielding it from access +// without first being locked. +// +// Locking and getting access works by creating a temporary (local) +// access object that takes the wrapper class as argument. Creating +// the access object obtains the lock, while destructing it releases +// the lock. +// +// There are three types of wrapper classes: +// AIThreadSafe, AIThreadSafeSimple and AIThreadSafeSingleThread. +// +// AIThreadSafe is for use with the access classes: +// AIReadAccessConst, AIReadAccess and AIWriteAccess. +// +// AIThreadSafeSimple is for use with the access classes: +// AIAccessConst and AIAccess. +// +// AIThreadSafeSingleThread is for use with the access classes: +// AISTAccessConst and AISTAccess. +// +// AIReadAccessConst provides read access to a const AIThreadSafe. +// AIReadAccess provides read access to a non-const AIThreadSafe. +// AIWriteAccess provides read/write access to a non-const AIThreadSafe. +// +// AIAccessConst provides read access to a const AIThreadSafeSimple. +// AIAccess provides read/write access to a non-const AIThreadSafeSimple. +// +// AISTAccessConst provides read access to a const AIThreadSafeSingleThread. +// AISTAccess provides read/write access to a non-const AIThreadSafeSingleThread. +// +// Thus, AIThreadSafe is to protect objects with a read/write lock, +// AIThreadSafeSimple is to protect objects with a single mutex, +// and AIThreadSafeSingleThread doesn't do any locking but makes sure +// (in Debug mode) that the wrapped object is only accessed by one thread. +// +// Each wrapper class allows it's wrapped object to be constructed +// with arbitrary parameters by using operator new with placement; +// for example, to instantiate a class Foo with read/write locking: +// +// AIThreadSafe foo(new (foo.memory()) Foo(param1, param2, ...)); +// +// Each wrapper class has a derived class that end on 'DC' (which +// stand for Default Constructed): AIThreadSafeDC, AIThreadSafeSimpleDC +// and AIThreadSafeSingleThreadDC. The default constructors of those +// wrapper classes cause the wrapped instance to be default constructed +// as well. They also provide a general one-parameter constructor. +// For example: +// +// AIThreadSafeDC foo; // Default constructed Foo. +// AIThreadSafeDC foo(3.4); // Foo with one constructor parameter. +// #ifndef AITHREADSAFE_H #define AITHREADSAFE_H @@ -54,14 +111,6 @@ template struct AIAccess; template struct AISTAccessConst; template struct AISTAccess; -#if LL_WINDOWS -template class AIThreadSafeBits; -template -struct AIThreadSafeWindowsHack { - AIThreadSafeWindowsHack(AIThreadSafeBits& var, T* object); -}; -#endif - template class AIThreadSafeBits { @@ -84,9 +133,6 @@ public: void* memory() const { return const_cast(&mMemory[0]); } protected: -#if LL_WINDOWS - template friend struct AIThreadSafeWindowsHack; -#endif // Accessors. T const* ptr() const { return reinterpret_cast(mMemory); } T* ptr() { return reinterpret_cast(mMemory); } @@ -188,6 +234,14 @@ public: llassert(object == AIThreadSafeBits::ptr()); #endif } + +#if LL_DEBUG + // Can only be locked when there still exists an AIAccess object that + // references this object and will access it upon destruction. + // If the assertion fails, make sure that such AIAccess object is + // destructed before the deletion of this object. + ~AIThreadSafe() { llassert(!mRWLock.isLocked()); } +#endif }; /** @@ -197,48 +251,39 @@ public: * * * Foo foo(x, y); - * static Bar bar; + * static Bar bar(0.1, true); * * * One can instantiate a thread-safe instance with * * * AITHREADSAFE(Foo, foo, (x, y)); - * static AITHREADSAFE(Bar, bar, ); + * static AITHREADSAFE(Bar, bar, (0.1, true)); * * * Note: This macro does not allow to allocate such object on the heap. * If that is needed, have a look at AIThreadSafeDC. */ -#if LL_WINDOWS -template -AIThreadSafeWindowsHack::AIThreadSafeWindowsHack(AIThreadSafeBits& var, T* object) -{ - llassert(object == var.ptr()); -} -#define AITHREADSAFE(type, var, paramlist) \ - AIThreadSafe var(NULL); \ - AIThreadSafeWindowsHack dummy_##var(var, new (var.memory()) type paramlist) -#else #define AITHREADSAFE(type, var, paramlist) AIThreadSafe var(new (var.memory()) type paramlist) -#endif /** * @brief A wrapper class for objects that need to be accessed by more than one thread. * * This class is the same as an AIThreadSafe wrapper, except that it can only - * be used for default constructed objects. + * be used for default constructed objects, or constructed with one parameter. * * For example, instead of * * * Foo foo; + * Bar bar(3); * * * One would use * * * AIThreadSafeDC foo; + * AIThreadSafeDC bar(3); * * * The advantage over AITHREADSAFE is that this object can be allocated with @@ -395,6 +440,7 @@ struct AIWriteAccess : public AIReadAccess * * AIAccess foo_w(foo); * // Use foo_w-> for read and write access. + * * * See also AIThreadSafe */ @@ -417,6 +463,14 @@ protected: public: // Only for use by AITHREADSAFESIMPLE, see below. AIThreadSafeSimple(T* object) { llassert(object == AIThreadSafeBits::ptr()); } + +#if LL_DEBUG + // Can only be locked when there still exists an AIAccess object that + // references this object and will access it upon destruction. + // If the assertion fails, make sure that such AIAccess object is + // destructed before the deletion of this object. + ~AIThreadSafeSimple() { llassert(!mMutex.isLocked()); } +#endif }; /** @@ -426,14 +480,14 @@ public: * * * Foo foo(x, y); - * static Bar bar; + * static Bar bar(0.1, true); * * * One can instantiate a thread-safe instance with * * * AITHREADSAFESIMPLE(Foo, foo, (x, y)); - * static AITHREADSAFESIMPLE(Bar, bar, ); + * static AITHREADSAFESIMPLE(Bar, bar, (0.1, true)); * * * Note: This macro does not allow to allocate such object on the heap. @@ -445,18 +499,20 @@ public: * @brief A wrapper class for objects that need to be accessed by more than one thread. * * This class is the same as an AIThreadSafeSimple wrapper, except that it can only - * be used for default constructed objects. + * be used for default constructed objects, or constructed with one parameter. * * For example, instead of * * * Foo foo; + * Bar bar(0.1); * * * One would use * * * AIThreadSafeSimpleDC foo; + * AIThreadSafeSimpleDC bar(0.1); * * * The advantage over AITHREADSAFESIMPLE is that this object can be allocated with @@ -590,6 +646,7 @@ struct AIAccess : public AIAccessConst * * AISTAccess foo_w(foo); * // Use foo_w-> for read and write access. + * */ template class AIThreadSafeSingleThread : public AIThreadSafeBits @@ -639,18 +696,20 @@ public: * @brief A wrapper class for objects that should only be accessed by a single thread. * * This class is the same as an AIThreadSafeSingleThread wrapper, except that it can only - * be used for default constructed objects. + * be used for default constructed objects, or constructed with one parameter. * * For example, instead of * * * Foo foo; + * Bar bar(0.1); * * * One would use * * * AIThreadSafeSingleThreadDC foo; + * AIThreadSafeSingleThreadDC bar(0.1); * * * The advantage over AITHREADSAFESINGLETHREAD is that this object can be allocated with @@ -661,6 +720,12 @@ public: * * * which is not possible with AITHREADSAFESINGLETHREAD. + * + * This class is primarily intended to test if some (member) variable needs locking, + * during development (in debug mode), and is therefore more flexible in that it + * automatically converts to the underlaying type, can be assigned to and can be + * written to an ostream, as if it wasn't wrapped at all. This is to reduce the + * impact on the source code. */ template class AIThreadSafeSingleThreadDC : public AIThreadSafeSingleThread diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp index d6c2074f2..c3f4878ee 100644 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -269,7 +269,7 @@ U8* LLImageBase::allocateDataSize(S32 width, S32 height, S32 ncomponents, S32 si // LLImageRaw //--------------------------------------------------------------------------- -AITHREADSAFESIMPLE(S32, LLImageRaw::sGlobalRawMemory, ); +AIThreadSafeSimpleDC LLImageRaw::sGlobalRawMemory; S32 LLImageRaw::sRawImageCount = 0; S32 LLImageRaw::sRawImageCachedCount = 0; diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h index b58dd5931..3eaf74b04 100644 --- a/indra/llimage/llimage.h +++ b/indra/llimage/llimage.h @@ -248,7 +248,7 @@ protected: void setDataAndSize(U8 *data, S32 width, S32 height, S8 components) ; public: - static AIThreadSafeSimple sGlobalRawMemory; + static AIThreadSafeSimpleDC sGlobalRawMemory; static S32 sRawImageCount; static S32 sRawImageCachedCount; diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 1b190259b..905899b4f 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -87,7 +87,7 @@ BOOL gHackGodmode = FALSE; #endif -AITHREADSAFE(settings_map_type, gSettings,); +AIThreadSafeDC gSettings; LLControlGroup gSavedSettings("Global"); // saved at end of session LLControlGroup gSavedPerAccountSettings("PerAccount"); // saved at end of session LLControlGroup gColors("Colors"); // saved at end of session diff --git a/indra/newview/llviewercontrol.h b/indra/newview/llviewercontrol.h index df4313ef0..d8bdf3670 100644 --- a/indra/newview/llviewercontrol.h +++ b/indra/newview/llviewercontrol.h @@ -51,7 +51,7 @@ extern BOOL gHackGodmode; void settings_setup_listeners(); typedef std::map settings_map_type; -extern AIThreadSafe gSettings; +extern AIThreadSafeDC gSettings; // for the graphics settings void create_graphics_group(LLControlGroup& group); diff --git a/indra/newview/statemachine/aifilepicker.cpp b/indra/newview/statemachine/aifilepicker.cpp index 38cbf6996..452f6d6ba 100644 --- a/indra/newview/statemachine/aifilepicker.cpp +++ b/indra/newview/statemachine/aifilepicker.cpp @@ -65,7 +65,7 @@ AIFilePicker::AIFilePicker(void) : mPluginManager(NULL), mAutoKill(false), mCanc } // static -AITHREADSAFESIMPLE(AIFilePicker::context_map_type, AIFilePicker::sContextMap, ); +AIThreadSafeSimpleDC AIFilePicker::sContextMap; // static void AIFilePicker::store_folder(std::string const& context, std::string const& folder) diff --git a/indra/newview/statemachine/aifilepicker.h b/indra/newview/statemachine/aifilepicker.h index c86223124..81e23367e 100644 --- a/indra/newview/statemachine/aifilepicker.h +++ b/indra/newview/statemachine/aifilepicker.h @@ -185,7 +185,7 @@ public: private: LLPointer mPluginManager; //!< Pointer to the plugin manager. typedef std::map context_map_type; //!< Type of mContextMap. - static AIThreadSafeSimple sContextMap; //!< Map context (ie, "snapshot" or "image") to last used folder. + static AIThreadSafeSimpleDC sContextMap; //!< Map context (ie, "snapshot" or "image") to last used folder. std::string mContext; //!< Some key to indicate the context (remembers the folder per key). bool mAutoKill; //!< True if the default behavior is to delete itself after being finished. diff --git a/indra/newview/statemachine/aistatemachine.cpp b/indra/newview/statemachine/aistatemachine.cpp index 3be286418..68d3241e1 100644 --- a/indra/newview/statemachine/aistatemachine.cpp +++ b/indra/newview/statemachine/aistatemachine.cpp @@ -65,18 +65,18 @@ namespace { }; typedef std::vector active_statemachines_type; - static active_statemachines_type active_statemachines; + active_statemachines_type active_statemachines; typedef std::vector continued_statemachines_type; struct cscm_type { continued_statemachines_type continued_statemachines; bool calling_mainloop; }; - static AITHREADSAFE(cscm_type, continued_statemachines_and_calling_mainloop, ); + AIThreadSafeDC continued_statemachines_and_calling_mainloop; } // static -AITHREADSAFESIMPLE(U64, AIStateMachine::sMaxCount, ); +AIThreadSafeSimpleDC AIStateMachine::sMaxCount; void AIStateMachine::updateSettings(void) { diff --git a/indra/newview/statemachine/aistatemachine.h b/indra/newview/statemachine/aistatemachine.h index 67933d319..2b9d93bf9 100644 --- a/indra/newview/statemachine/aistatemachine.h +++ b/indra/newview/statemachine/aistatemachine.h @@ -224,7 +224,7 @@ class AIStateMachine { }; callback_type* mCallback; //!< Pointer to signal/connection, or NULL when not connected. - static AIThreadSafeSimple sMaxCount; //!< Number of cpu clocks below which we start a new state machine within the same frame. + static AIThreadSafeSimpleDC sMaxCount; //!< Number of cpu clocks below which we start a new state machine within the same frame. protected: //! State of the derived class. Only valid if mState == bs_run. Call set_state to change. From e9df867a21d83f1dc180d195a4a00798398499c2 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Sat, 24 Mar 2012 22:49:41 +0100 Subject: [PATCH 11/19] Like the other AIThreadSafe* objects, disallow AIThreadSafeSingleThread copy/assign --- indra/llcommon/aithreadsafe.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indra/llcommon/aithreadsafe.h b/indra/llcommon/aithreadsafe.h index a3008b68a..2c11908af 100644 --- a/indra/llcommon/aithreadsafe.h +++ b/indra/llcommon/aithreadsafe.h @@ -690,6 +690,11 @@ public: { llassert(object == AIThreadSafeBits::ptr()); } + +private: + // Disallow copying or assignments. + AIThreadSafeSingleThread(AIThreadSafeSingleThread const&); + void operator=(AIThreadSafeSingleThread const&); }; /** From 3c2f0b551ffe877f7a6ee026d28822c3503aec13 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Sat, 24 Mar 2012 23:58:19 +0100 Subject: [PATCH 12/19] Remove old and never used LLHTTPAssetStorage --- indra/llmessage/CMakeLists.txt | 2 - indra/llmessage/llhttpassetstorage.cpp | 1459 ------------------------ indra/llmessage/llhttpassetstorage.h | 159 --- 3 files changed, 1620 deletions(-) delete mode 100644 indra/llmessage/llhttpassetstorage.cpp delete mode 100644 indra/llmessage/llhttpassetstorage.h diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index a328d16e0..b09245ba3 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -34,7 +34,6 @@ set(llmessage_SOURCE_FILES lldispatcher.cpp llfiltersd2xmlrpc.cpp llhost.cpp - llhttpassetstorage.cpp llhttpclient.cpp llhttpclientadapter.cpp llhttpnode.cpp @@ -125,7 +124,6 @@ set(llmessage_HEADER_FILES llfiltersd2xmlrpc.h llfollowcamparams.h llhost.h - llhttpassetstorage.h llhttpclient.h llhttpclientinterface.h llhttpclientadapter.h diff --git a/indra/llmessage/llhttpassetstorage.cpp b/indra/llmessage/llhttpassetstorage.cpp deleted file mode 100644 index 612d76596..000000000 --- a/indra/llmessage/llhttpassetstorage.cpp +++ /dev/null @@ -1,1459 +0,0 @@ -/** - * @file llhttpassetstorage.cpp - * @brief Subclass capable of loading asset data to/from an external - * source. Currently, a web server accessed via curl - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llhttpassetstorage.h" - -#include - -#include "indra_constants.h" -#include "message.h" -#include "llproxy.h" -#include "llvfile.h" -#include "llvfs.h" - -#ifdef LL_STANDALONE -# include -#else -# include "zlib/zlib.h" -#endif - -const U32 MAX_RUNNING_REQUESTS = 1; -const F32 MAX_PROCESSING_TIME = 0.005f; -const S32 CURL_XFER_BUFFER_SIZE = 65536; -// Try for 30 minutes for now. -const F32 GET_URL_TO_FILE_TIMEOUT = 1800.0f; - -const S32 COMPRESSED_INPUT_BUFFER_SIZE = 4096; - -const S32 HTTP_OK = 200; -const S32 HTTP_PUT_OK = 201; -const S32 HTTP_NO_CONTENT = 204; -const S32 HTTP_MISSING = 404; -const S32 HTTP_SERVER_BAD_GATEWAY = 502; -const S32 HTTP_SERVER_TEMP_UNAVAILABLE = 503; - -///////////////////////////////////////////////////////////////////////////////// -// LLTempAssetData -// An asset not stored on central asset store, but on a simulator node somewhere. -///////////////////////////////////////////////////////////////////////////////// -struct LLTempAssetData -{ - LLUUID mAssetID; - LLUUID mAgentID; - std::string mHostName; -}; - -///////////////////////////////////////////////////////////////////////////////// -// LLHTTPAssetRequest -///////////////////////////////////////////////////////////////////////////////// - -class LLHTTPAssetRequest : public LLAssetRequest -{ -public: - LLHTTPAssetRequest(LLHTTPAssetStorage *asp, const LLUUID &uuid, - LLAssetType::EType type, LLAssetStorage::ERequestType rt, - const std::string& url, CURLM *curl_multi); - virtual ~LLHTTPAssetRequest(); - - void setupCurlHandle(); - void cleanupCurlHandle(); - - void prepareCompressedUpload(); - void finishCompressedUpload(); - size_t readCompressedData(void* data, size_t size); - - static size_t curlCompressedUploadCallback( - void *data, size_t size, size_t nmemb, void *user_data); - - virtual LLSD getTerseDetails() const; - virtual LLSD getFullDetails() const; - -public: - LLHTTPAssetStorage *mAssetStoragep; - - CURL *mCurlHandle; - CURLM *mCurlMultiHandle; - std::string mURLBuffer; - struct curl_slist *mHTTPHeaders; - LLVFile *mVFile; - LLUUID mTmpUUID; - LLAssetStorage::ERequestType mRequestType; - - bool mZInitialized; - z_stream mZStream; - char* mZInputBuffer; - bool mZInputExhausted; - - FILE *mFP; -}; - - -LLHTTPAssetRequest::LLHTTPAssetRequest(LLHTTPAssetStorage *asp, - const LLUUID &uuid, - LLAssetType::EType type, - LLAssetStorage::ERequestType rt, - const std::string& url, - CURLM *curl_multi) - : LLAssetRequest(uuid, type), - mZInitialized(false) -{ - memset(&mZStream, 0, sizeof(mZStream)); // we'll initialize this later, but for now zero the whole C-style struct to avoid debug/coverity noise - mAssetStoragep = asp; - mCurlHandle = NULL; - mCurlMultiHandle = curl_multi; - mVFile = NULL; - mRequestType = rt; - mHTTPHeaders = NULL; - mFP = NULL; - mZInputBuffer = NULL; - mZInputExhausted = false; - - mURLBuffer = url; -} - -LLHTTPAssetRequest::~LLHTTPAssetRequest() -{ - // Cleanup/cancel the request - if (mCurlHandle) - { - curl_multi_remove_handle(mCurlMultiHandle, mCurlHandle); - cleanupCurlHandle(); - } - if (mHTTPHeaders) - { - curl_slist_free_all(mHTTPHeaders); - } - delete mVFile; - finishCompressedUpload(); -} - -// virtual -LLSD LLHTTPAssetRequest::getTerseDetails() const -{ - LLSD sd = LLAssetRequest::getTerseDetails(); - - sd["url"] = mURLBuffer; - - return sd; -} - -// virtual -LLSD LLHTTPAssetRequest::getFullDetails() const -{ - LLSD sd = LLAssetRequest::getFullDetails(); - - if (mCurlHandle) - { - long curl_response = -1; - long curl_connect = -1; - double curl_total_time = -1.0f; - double curl_size_upload = -1.0f; - double curl_size_download = -1.0f; - double curl_content_length_upload = -1.0f; - double curl_content_length_download = -1.0f; - long curl_request_size = -1; - const char* curl_content_type = NULL; - - curl_easy_getinfo(mCurlHandle, CURLINFO_HTTP_CODE, &curl_response); - curl_easy_getinfo(mCurlHandle, CURLINFO_HTTP_CONNECTCODE, &curl_connect); - curl_easy_getinfo(mCurlHandle, CURLINFO_TOTAL_TIME, &curl_total_time); - curl_easy_getinfo(mCurlHandle, CURLINFO_SIZE_UPLOAD, &curl_size_upload); - curl_easy_getinfo(mCurlHandle, CURLINFO_SIZE_DOWNLOAD, &curl_size_download); - curl_easy_getinfo(mCurlHandle, CURLINFO_CONTENT_LENGTH_UPLOAD, &curl_content_length_upload); - curl_easy_getinfo(mCurlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &curl_content_length_download); - curl_easy_getinfo(mCurlHandle, CURLINFO_REQUEST_SIZE, &curl_request_size); - curl_easy_getinfo(mCurlHandle, CURLINFO_CONTENT_TYPE, &curl_content_type); - - sd["curl_response_code"] = (int) curl_response; - sd["curl_http_connect_code"] = (int) curl_connect; - sd["curl_total_time"] = curl_total_time; - sd["curl_size_upload"] = curl_size_upload; - sd["curl_size_download"] = curl_size_download; - sd["curl_content_length_upload"] = curl_content_length_upload; - sd["curl_content_length_download"] = curl_content_length_download; - sd["curl_request_size"] = (int) curl_request_size; - if (curl_content_type) - { - sd["curl_content_type"] = curl_content_type; - } - else - { - sd["curl_content_type"] = ""; - } - } - - sd["temp_id"] = mTmpUUID; - sd["request_type"] = LLAssetStorage::getRequestName(mRequestType); - sd["z_initialized"] = mZInitialized; - sd["z_input_exhausted"] = mZInputExhausted; - - S32 file_size = -1; - if (mFP) - { - struct stat file_stat; - int file_desc = fileno(mFP); - if ( fstat(file_desc, &file_stat) == 0) - { - file_size = file_stat.st_size; - } - } - sd["file_size"] = file_size; - - return sd; -} - - -void LLHTTPAssetRequest::setupCurlHandle() -{ - // *NOTE: Similar code exists in mapserver/llcurlutil.cpp JC - mCurlHandle = LLCurl::newEasyHandle(); - llassert_always(mCurlHandle != NULL) ; - - // Apply proxy settings if configured to do so - LLProxy::getInstance()->applyProxySettings(mCurlHandle); - - curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1); - curl_easy_setopt(mCurlHandle, CURLOPT_URL, mURLBuffer.c_str()); - curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this); - if (LLAssetStorage::RT_DOWNLOAD == mRequestType) - { - curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, ""); - // only do this on downloads, as uploads - // to some apache configs (like our test grids) - // mistakenly claim the response is gzip'd if the resource - // name ends in .gz, even though in a PUT, the response is - // just plain HTML saying "created" - } - /* Remove the Pragma: no-cache header that libcurl inserts by default; - we want the cached version, if possible. */ - if (mZInitialized) - { - curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, ""); - // disable use of proxy, which can't handle chunked transfers - } - mHTTPHeaders = curl_slist_append(mHTTPHeaders, "Pragma:"); - - // bug in curl causes DNS to be cached for too long a time, 0 sets it to never cache DNS results internally (to curl) - curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0); - - // resist the temptation to explicitly add the Transfer-Encoding: chunked - // header here - invokes a libCURL bug - curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mHTTPHeaders); - if (mAssetStoragep) - { - // Set the appropriate pending upload or download flag - mAssetStoragep->addRunningRequest(mRequestType, this); - } - else - { - llerrs << "LLHTTPAssetRequest::setupCurlHandle - No asset storage associated with this request!" << llendl; - } -} - -void LLHTTPAssetRequest::cleanupCurlHandle() -{ - LLCurl::deleteEasyHandle(mCurlHandle); - if (mAssetStoragep) - { - // Terminating a request. Thus upload or download is no longer pending. - mAssetStoragep->removeRunningRequest(mRequestType, this); - } - else - { - llerrs << "LLHTTPAssetRequest::~LLHTTPAssetRequest - No asset storage associated with this request!" << llendl; - } - mCurlHandle = NULL; -} - -void LLHTTPAssetRequest::prepareCompressedUpload() -{ - mZStream.next_in = Z_NULL; - mZStream.avail_in = 0; - mZStream.zalloc = Z_NULL; - mZStream.zfree = Z_NULL; - mZStream.opaque = Z_NULL; - - int r = deflateInit2(&mZStream, - 1, // compression level - Z_DEFLATED, // the only method defined - 15 + 16, // the default windowBits + gzip header flag - 8, // the default memLevel - Z_DEFAULT_STRATEGY); - - if (r != Z_OK) - { - llerrs << "LLHTTPAssetRequest::prepareCompressedUpload defalateInit2() failed" << llendl; - } - - mZInitialized = true; - mZInputBuffer = new char[COMPRESSED_INPUT_BUFFER_SIZE]; - mZInputExhausted = false; - - mVFile = new LLVFile(gAssetStorage->mVFS, - getUUID(), getType(), LLVFile::READ); -} - -void LLHTTPAssetRequest::finishCompressedUpload() -{ - if (mZInitialized) - { - llinfos << "LLHTTPAssetRequest::finishCompressedUpload: " - << "read " << mZStream.total_in << " byte asset file, " - << "uploaded " << mZStream.total_out << " byte compressed asset" - << llendl; - - deflateEnd(&mZStream); - delete[] mZInputBuffer; - } -} - -size_t LLHTTPAssetRequest::readCompressedData(void* data, size_t size) -{ - llassert(mZInitialized); - - mZStream.next_out = (Bytef*)data; - mZStream.avail_out = size; - - while (mZStream.avail_out > 0) - { - if (mZStream.avail_in == 0 && !mZInputExhausted) - { - S32 to_read = llmin(COMPRESSED_INPUT_BUFFER_SIZE, - (S32)(mVFile->getSize() - mVFile->tell())); - - if ( to_read > 0 ) - { - mVFile->read((U8*)mZInputBuffer, to_read); /*Flawfinder: ignore*/ - mZStream.next_in = (Bytef*)mZInputBuffer; - mZStream.avail_in = mVFile->getLastBytesRead(); - } - - mZInputExhausted = mZStream.avail_in == 0; - } - - int r = deflate(&mZStream, - mZInputExhausted ? Z_FINISH : Z_NO_FLUSH); - - if (r == Z_STREAM_END || r < 0 || mZInputExhausted) - { - if (r < 0) - { - llwarns << "LLHTTPAssetRequest::readCompressedData: deflate returned error code " - << (S32) r << llendl; - } - break; - } - } - - return size - mZStream.avail_out; -} - -//static -size_t LLHTTPAssetRequest::curlCompressedUploadCallback( - void *data, size_t size, size_t nmemb, void *user_data) -{ - size_t num_read = 0; - - if (gAssetStorage) - { - CURL *curl_handle = (CURL *)user_data; - LLHTTPAssetRequest *req = NULL; - curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req); - if (req) - { - num_read = req->readCompressedData(data, size * nmemb); - } - } - - return num_read; -} - -///////////////////////////////////////////////////////////////////////////////// -// LLHTTPAssetStorage -///////////////////////////////////////////////////////////////////////////////// - - -LLHTTPAssetStorage::LLHTTPAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, LLVFS *static_vfs, - const LLHost &upstream_host, - const std::string& web_host, - const std::string& local_web_host, - const std::string& host_name) - : LLAssetStorage(msg, xfer, vfs, static_vfs, upstream_host) -{ - _init(web_host, local_web_host, host_name); -} - -LLHTTPAssetStorage::LLHTTPAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, - LLVFS *static_vfs, - const std::string& web_host, - const std::string& local_web_host, - const std::string& host_name) - : LLAssetStorage(msg, xfer, vfs, static_vfs) -{ - _init(web_host, local_web_host, host_name); -} - -void LLHTTPAssetStorage::_init(const std::string& web_host, const std::string& local_web_host, const std::string& host_name) -{ - mBaseURL = web_host; - mLocalBaseURL = local_web_host; - mHostName = host_name; - - // curl_global_init moved to LLCurl::initClass() - - mCurlMultiHandle = LLCurl::newMultiHandle() ; - llassert_always(mCurlMultiHandle != NULL) ; -} - -LLHTTPAssetStorage::~LLHTTPAssetStorage() -{ - LLCurl::deleteMultiHandle(mCurlMultiHandle); - mCurlMultiHandle = NULL; - - // curl_global_cleanup moved to LLCurl::initClass() -} - -// storing data is simpler than getting it, so we just overload the whole method -void LLHTTPAssetStorage::storeAssetData( - const LLUUID& uuid, - LLAssetType::EType type, - LLAssetStorage::LLStoreAssetCallback callback, - void* user_data, - bool temp_file, - bool is_priority, - bool store_local, - const LLUUID& requesting_agent_id, - bool user_waiting, - F64 timeout) -{ - if (mVFS->getExists(uuid, type)) // VFS treats nonexistant and zero-length identically - { - LLAssetRequest *req = new LLAssetRequest(uuid, type); - req->mUpCallback = callback; - req->mUserData = user_data; - req->mRequestingAgentID = requesting_agent_id; - req->mIsUserWaiting = user_waiting; - req->mTimeout = timeout; - - // LLAssetStorage metric: Successful Request - S32 size = mVFS->getSize(uuid, type); - const char *message; - if( store_local ) - { - message = "Added to local upload queue"; - } - else - { - message = "Added to upload queue"; - } - reportMetric( uuid, type, LLStringUtil::null, requesting_agent_id, size, MR_OKAY, __FILE__, __LINE__, message ); - - // this will get picked up and transmitted in checkForTimeouts - if(store_local) - { - mPendingLocalUploads.push_back(req); - } - else if(is_priority) - { - mPendingUploads.push_front(req); - } - else - { - mPendingUploads.push_back(req); - } - } - else - { - llwarns << "AssetStorage: attempt to upload non-existent vfile " << uuid << ":" << LLAssetType::lookup(type) << llendl; - if (callback) - { - // LLAssetStorage metric: Zero size VFS - reportMetric( uuid, type, LLStringUtil::null, requesting_agent_id, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file didn't exist or was zero length (VFS - can't tell which)" ); - callback(uuid, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE, LL_EXSTAT_NONEXISTENT_FILE); - } - } -} - -// virtual -void LLHTTPAssetStorage::storeAssetData( - const std::string& filename, - const LLUUID& asset_id, - LLAssetType::EType asset_type, - LLStoreAssetCallback callback, - void* user_data, - bool temp_file, - bool is_priority, - bool user_waiting, - F64 timeout) -{ - llinfos << "LLAssetStorage::storeAssetData (legacy)" << asset_id << ":" << LLAssetType::lookup(asset_type) << llendl; - - LLLegacyAssetRequest *legacy = new LLLegacyAssetRequest; - - legacy->mUpCallback = callback; - legacy->mUserData = user_data; - - FILE *fp = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ - S32 size = 0; - if (fp) - { - fseek(fp, 0, SEEK_END); - size = ftell(fp); - fseek(fp, 0, SEEK_SET); - } - - if( size ) - { - LLVFile file(mVFS, asset_id, asset_type, LLVFile::WRITE); - - file.setMaxSize(size); - - const S32 buf_size = 65536; - U8 copy_buf[buf_size]; - while ((size = (S32)fread(copy_buf, 1, buf_size, fp))) - { - file.write(copy_buf, size); - } - fclose(fp); - - // if this upload fails, the caller needs to setup a new tempfile for us - if (temp_file) - { - LLFile::remove(filename); - } - - // LLAssetStorage metric: Success not needed; handled in the overloaded method here: - storeAssetData( - asset_id, - asset_type, - legacyStoreDataCallback, - (void**)legacy, - temp_file, - is_priority, - false, - LLUUID::null, - user_waiting, - timeout); - } - else // !size - { - if( fp ) - { - // LLAssetStorage metric: Zero size - reportMetric( asset_id, asset_type, filename, LLUUID::null, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file was zero length" ); - fclose( fp ); - } - else - { - // LLAssetStorage metric: Missing File - reportMetric( asset_id, asset_type, filename, LLUUID::null, 0, MR_FILE_NONEXIST, __FILE__, __LINE__, "The file didn't exist" ); - } - if (callback) - { - callback(LLUUID::null, user_data, LL_ERR_CANNOT_OPEN_FILE, LL_EXSTAT_BLOCKED_FILE); - } - delete legacy; - } -} - -// virtual -LLSD LLHTTPAssetStorage::getPendingDetails(LLAssetStorage::ERequestType rt, - LLAssetType::EType asset_type, - const std::string& detail_prefix) const -{ - LLSD sd = LLAssetStorage::getPendingDetails(rt, asset_type, detail_prefix); - const request_list_t* running = getRunningList(rt); - if (running) - { - // Loop through the pending requests sd, and add extra info about its running status. - S32 num_pending = sd["requests"].size(); - S32 i; - for (i = 0; i < num_pending; ++i) - { - LLSD& pending = sd["requests"][i]; - // See if this pending request is running. - const LLAssetRequest* req = findRequest(running, - LLAssetType::lookup(pending["type"].asString()), - pending["asset_id"]); - if (req) - { - // Keep the detail_url so we don't have to rebuild it. - LLURI detail_url = pending["detail"]; - pending = req->getTerseDetails(); - pending["detail"] = detail_url; - pending["is_running"] = true; - } - else - { - pending["is_running"] = false; - } - } - } - return sd; -} - -// virtual -LLSD LLHTTPAssetStorage::getPendingRequest(LLAssetStorage::ERequestType rt, - LLAssetType::EType asset_type, - const LLUUID& asset_id) const -{ - // Look for this asset in the running list first. - const request_list_t* running = getRunningList(rt); - if (running) - { - LLSD sd = LLAssetStorage::getPendingRequestImpl(running, asset_type, asset_id); - if (sd) - { - sd["is_running"] = true; - return sd; - } - } - LLSD sd = LLAssetStorage::getPendingRequest(rt, asset_type, asset_id); - if (sd) - { - sd["is_running"] = false; - } - return sd; -} - -// virtual -bool LLHTTPAssetStorage::deletePendingRequest(LLAssetStorage::ERequestType rt, - LLAssetType::EType asset_type, - const LLUUID& asset_id) -{ - // Try removing this from the running list first. - request_list_t* running = getRunningList(rt); - if (running) - { - LLAssetRequest* req = findRequest(running, asset_type, asset_id); - if (req) - { - // Remove this request from the running list to get it out of curl. - running->remove(req); - - // Find this request in the pending list, so we can move it to the end of the line. - request_list_t* pending = getRequestList(rt); - if (pending) - { - request_list_t::iterator result = std::find_if(pending->begin(), pending->end(), - std::bind2nd(ll_asset_request_equal(), req)); - if (pending->end() != result) - { - // This request was found in the pending list. Move it to the end! - LLAssetRequest* pending_req = *result; - pending->remove(pending_req); - - if (!pending_req->mIsUserWaiting) //A user is waiting on this request. Toss it. - { - pending->push_back(pending_req); - } - else - { - if (pending_req->mUpCallback) //Clean up here rather than _callUploadCallbacks because this request is already cleared the req. - { - pending_req->mUpCallback(pending_req->getUUID(), pending_req->mUserData, -1, LL_EXSTAT_REQUEST_DROPPED); - } - - } - - llinfos << "Asset " << getRequestName(rt) << " request for " - << asset_id << "." << LLAssetType::lookup(asset_type) - << " removed from curl and placed at the end of the pending queue." - << llendl; - } - else - { - llwarns << "Unable to find pending " << getRequestName(rt) << " request for " - << asset_id << "." << LLAssetType::lookup(asset_type) << llendl; - } - } - delete req; - - return true; - } - } - return LLAssetStorage::deletePendingRequest(rt, asset_type, asset_id); -} - -// internal requester, used by getAssetData in superclass -void LLHTTPAssetStorage::_queueDataRequest(const LLUUID& uuid, LLAssetType::EType type, - void (*callback)(LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32, LLExtStat), - void *user_data, BOOL duplicate, - BOOL is_priority) -{ - // stash the callback info so we can find it after we get the response message - LLAssetRequest *req = new LLAssetRequest(uuid, type); - req->mDownCallback = callback; - req->mUserData = user_data; - req->mIsPriority = is_priority; - - // this will get picked up and downloaded in checkForTimeouts - - // - // HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACK! Asset requests were taking too long and timing out. - // Since texture requests are the LEAST sensitive (on the simulator) to being delayed, add - // non-texture requests to the front, and add texture requests to the back. The theory is - // that we always want them first, even if they're out of order. - // - - if (req->getType() == LLAssetType::AT_TEXTURE) - { - mPendingDownloads.push_back(req); - } - else - { - mPendingDownloads.push_front(req); - } -} - -LLAssetRequest* LLHTTPAssetStorage::findNextRequest(LLAssetStorage::request_list_t& pending, - LLAssetStorage::request_list_t& running) -{ - // Early exit if the running list is full, or we don't have more pending than running. - if (running.size() >= MAX_RUNNING_REQUESTS - || pending.size() <= running.size()) return NULL; - - // Look for the first pending request that is not already running. - request_list_t::iterator running_begin = running.begin(); - request_list_t::iterator running_end = running.end(); - - request_list_t::iterator pending_iter = pending.begin(); - request_list_t::iterator pending_end = pending.end(); - // Loop over all pending requests until we miss finding it in the running list. - for (; pending_iter != pending.end(); ++pending_iter) - { - LLAssetRequest* req = *pending_iter; - // Look for this pending request in the running list. - if (running_end == std::find_if(running_begin, running_end, - std::bind2nd(ll_asset_request_equal(), req))) - { - // It isn't running! Return it. - return req; - } - } - return NULL; -} - -// overloaded to additionally move data to/from the webserver -void LLHTTPAssetStorage::checkForTimeouts() -{ - CURLMcode mcode; - LLAssetRequest *req; - while ( (req = findNextRequest(mPendingDownloads, mRunningDownloads)) ) - { - // Setup this curl download request - // We need to generate a new request here - // since the one in the list could go away - std::string tmp_url; - std::string uuid_str; - req->getUUID().toString(uuid_str); - std::string base_url = getBaseURL(req->getUUID(), req->getType()); - tmp_url = llformat("%s/%36s.%s", base_url.c_str() , uuid_str.c_str(), LLAssetType::lookup(req->getType())); - - LLHTTPAssetRequest *new_req = new LLHTTPAssetRequest(this, req->getUUID(), - req->getType(), RT_DOWNLOAD, tmp_url, mCurlMultiHandle); - new_req->mTmpUUID.generate(); - - // Sets pending download flag internally - new_req->setupCurlHandle(); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_FOLLOWLOCATION, TRUE); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEFUNCTION, &curlDownCallback); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEDATA, new_req->mCurlHandle); - - mcode = curl_multi_add_handle(mCurlMultiHandle, new_req->mCurlHandle); - if (mcode > CURLM_OK) - { - // Failure. Deleting the pending request will remove it from the running - // queue, and push it to the end of the pending queue. - new_req->cleanupCurlHandle(); - deletePendingRequest(RT_DOWNLOAD, new_req->getType(), new_req->getUUID()); - break; - } - else - { - llinfos << "Requesting " << new_req->mURLBuffer << llendl; - } - } - - while ( (req = findNextRequest(mPendingUploads, mRunningUploads)) ) - { - // setup this curl upload request - - bool do_compress = req->getType() == LLAssetType::AT_OBJECT; - - std::string tmp_url; - std::string uuid_str; - req->getUUID().toString(uuid_str); - tmp_url = mBaseURL + "/" + uuid_str + "." + LLAssetType::lookup(req->getType()); - if (do_compress) tmp_url += ".gz"; - - LLHTTPAssetRequest *new_req = new LLHTTPAssetRequest(this, req->getUUID(), - req->getType(), RT_UPLOAD, tmp_url, mCurlMultiHandle); - - if (req->mIsUserWaiting) //If a user is waiting on a realtime response, we want to perserve information across upload attempts. - { - new_req->mTime = req->mTime; - new_req->mTimeout = req->mTimeout; - new_req->mIsUserWaiting = req->mIsUserWaiting; - } - - if (do_compress) - { - new_req->prepareCompressedUpload(); - } - - // Sets pending upload flag internally - new_req->setupCurlHandle(); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_UPLOAD, 1); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEFUNCTION, &nullOutputCallback); - - if (do_compress) - { - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READFUNCTION, - &LLHTTPAssetRequest::curlCompressedUploadCallback); - } - else - { - LLVFile file(mVFS, req->getUUID(), req->getType()); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_INFILESIZE, file.getSize()); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READFUNCTION, - &curlUpCallback); - } - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READDATA, new_req->mCurlHandle); - - mcode = curl_multi_add_handle(mCurlMultiHandle, new_req->mCurlHandle); - if (mcode > CURLM_OK) - { - // Failure. Deleting the pending request will remove it from the running - // queue, and push it to the end of the pending queue. - new_req->cleanupCurlHandle(); - deletePendingRequest(RT_UPLOAD, new_req->getType(), new_req->getUUID()); - break; - } - else - { - // Get the uncompressed file size. - LLVFile file(mVFS,new_req->getUUID(),new_req->getType()); - S32 size = file.getSize(); - llinfos << "Requesting PUT " << new_req->mURLBuffer << ", asset size: " << size << " bytes" << llendl; - if (size == 0) - { - llwarns << "Rejecting zero size PUT request!" << llendl; - new_req->cleanupCurlHandle(); - deletePendingRequest(RT_UPLOAD, new_req->getType(), new_req->getUUID()); - } - } - // Pending upload will have been flagged by the request - } - - while ( (req = findNextRequest(mPendingLocalUploads, mRunningLocalUploads)) ) - { - // setup this curl upload request - LLVFile file(mVFS, req->getUUID(), req->getType()); - - std::string tmp_url; - std::string uuid_str; - req->getUUID().toString(uuid_str); - - // KLW - All temporary uploads are saved locally "http://localhost:12041/asset" - tmp_url = llformat("%s/%36s.%s", mLocalBaseURL.c_str(), uuid_str.c_str(), LLAssetType::lookup(req->getType())); - - LLHTTPAssetRequest *new_req = new LLHTTPAssetRequest(this, req->getUUID(), - req->getType(), RT_LOCALUPLOAD, tmp_url, mCurlMultiHandle); - new_req->mRequestingAgentID = req->mRequestingAgentID; - - // Sets pending upload flag internally - new_req->setupCurlHandle(); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_PUT, 1); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_INFILESIZE, file.getSize()); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEFUNCTION, &nullOutputCallback); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READFUNCTION, &curlUpCallback); - curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READDATA, new_req->mCurlHandle); - - mcode = curl_multi_add_handle(mCurlMultiHandle, new_req->mCurlHandle); - if (mcode > CURLM_OK) - { - // Failure. Deleting the pending request will remove it from the running - // queue, and push it to the end of the pending queue. - new_req->cleanupCurlHandle(); - deletePendingRequest(RT_LOCALUPLOAD, new_req->getType(), new_req->getUUID()); - break; - } - else - { - // Get the uncompressed file size. - S32 size = file.getSize(); - - llinfos << "TAT: LLHTTPAssetStorage::checkForTimeouts() : pending local!" - << " Requesting PUT " << new_req->mURLBuffer << ", asset size: " << size << " bytes" << llendl; - if (size == 0) - { - - llwarns << "Rejecting zero size PUT request!" << llendl; - new_req->cleanupCurlHandle(); - deletePendingRequest(RT_UPLOAD, new_req->getType(), new_req->getUUID()); - } - - } - // Pending upload will have been flagged by the request - } - S32 count = 0; - int queue_length; - do - { - mcode = curl_multi_perform(mCurlMultiHandle, &queue_length); - count++; - } while (mcode == CURLM_CALL_MULTI_PERFORM && (count < 5)); - - CURLMsg *curl_msg; - do - { - curl_msg = curl_multi_info_read(mCurlMultiHandle, &queue_length); - if (curl_msg && curl_msg->msg == CURLMSG_DONE) - { - long curl_result = 0; - S32 xfer_result = LL_ERR_NOERR; - - LLHTTPAssetRequest *req = NULL; - curl_easy_getinfo(curl_msg->easy_handle, CURLINFO_PRIVATE, &req); - - // TODO: Throw curl_result at all callbacks. - curl_easy_getinfo(curl_msg->easy_handle, CURLINFO_HTTP_CODE, &curl_result); - if (RT_UPLOAD == req->mRequestType || RT_LOCALUPLOAD == req->mRequestType) - { - if (curl_msg->data.result == CURLE_OK && - ( curl_result == HTTP_OK - || curl_result == HTTP_PUT_OK - || curl_result == HTTP_NO_CONTENT)) - { - llinfos << "Success uploading " << req->getUUID() << " to " << req->mURLBuffer << llendl; - if (RT_LOCALUPLOAD == req->mRequestType) - { - addTempAssetData(req->getUUID(), req->mRequestingAgentID, mHostName); - } - } - else if (curl_msg->data.result == CURLE_COULDNT_CONNECT || - curl_msg->data.result == CURLE_OPERATION_TIMEOUTED || - curl_result == HTTP_SERVER_BAD_GATEWAY || - curl_result == HTTP_SERVER_TEMP_UNAVAILABLE) - { - llwarns << "Re-requesting upload for " << req->getUUID() << ". Received upload error to " << req->mURLBuffer << - " with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl; - - ////HACK (probably) I am sick of this getting requeued and driving me mad. - //if (req->mIsUserWaiting) - //{ - // deletePendingRequest(RT_UPLOAD, req->getType(), req->getUUID()); - //} - } - else - { - llwarns << "Failure uploading " << req->getUUID() << " to " << req->mURLBuffer << - " with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl; - - xfer_result = LL_ERR_ASSET_REQUEST_FAILED; - } - - if (!(curl_msg->data.result == CURLE_COULDNT_CONNECT || - curl_msg->data.result == CURLE_OPERATION_TIMEOUTED || - curl_result == HTTP_SERVER_BAD_GATEWAY || - curl_result == HTTP_SERVER_TEMP_UNAVAILABLE)) - { - // shared upload finished callback - // in the base class, this is called from processUploadComplete - _callUploadCallbacks(req->getUUID(), req->getType(), (xfer_result == 0), LL_EXSTAT_CURL_RESULT | curl_result); - // Pending upload flag will get cleared when the request is deleted - } - } - else if (RT_DOWNLOAD == req->mRequestType) - { - if (curl_result == HTTP_OK && curl_msg->data.result == CURLE_OK) - { - if (req->mVFile && req->mVFile->getSize() > 0) - { - llinfos << "Success downloading " << req->mURLBuffer << ", size " << req->mVFile->getSize() << llendl; - - req->mVFile->rename(req->getUUID(), req->getType()); - } - else - { - // *TODO: if this actually indicates a bad asset on the server - // (not certain at this point), then delete it - llwarns << "Found " << req->mURLBuffer << " to be zero size" << llendl; - xfer_result = LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE; - } - } - else - { - // KLW - TAT See if an avatar owns this texture, and if so request re-upload. - llwarns << "Failure downloading " << req->mURLBuffer << - " with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl; - - xfer_result = (curl_result == HTTP_MISSING) ? LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE : LL_ERR_ASSET_REQUEST_FAILED; - - if (req->mVFile) - { - req->mVFile->remove(); - } - } - - // call the static callback for transfer completion - // this will cleanup all requests for this asset, including ours - downloadCompleteCallback( - xfer_result, - req->getUUID(), - req->getType(), - (void *)req, - LL_EXSTAT_CURL_RESULT | curl_result); - // Pending download flag will get cleared when the request is deleted - } - else - { - // nothing, just axe this request - // currently this can only mean an asset delete - } - - // Deleting clears the pending upload/download flag if it's set and the request is transferring - delete req; - req = NULL; - } - - } while (curl_msg && queue_length > 0); - - - // Cleanup - // We want to bump to the back of the line any running uploads that have timed out. - bumpTimedOutUploads(); - - LLAssetStorage::checkForTimeouts(); -} - -void LLHTTPAssetStorage::bumpTimedOutUploads() -{ - bool user_waiting=FALSE; - - F64 mt_secs = LLMessageSystem::getMessageTimeSeconds(); - - if (mPendingUploads.size()) - { - request_list_t::iterator it = mPendingUploads.begin(); - LLAssetRequest* req = *it; - user_waiting=req->mIsUserWaiting; - } - - // No point bumping currently running uploads if there are no others in line. - if (!(mPendingUploads.size() > mRunningUploads.size()) && !user_waiting) - { - return; - } - - // deletePendingRequest will modify the mRunningUploads list so we don't want to iterate over it. - request_list_t temp_running = mRunningUploads; - - request_list_t::iterator it = temp_running.begin(); - request_list_t::iterator end = temp_running.end(); - for ( ; it != end; ++it) - { - //request_list_t::iterator curiter = iter++; - LLAssetRequest* req = *it; - - if ( req->mTimeout < (mt_secs - req->mTime) ) - { - llwarns << "Asset upload request timed out for " - << req->getUUID() << "." - << LLAssetType::lookup(req->getType()) - << ", bumping to the back of the line!" << llendl; - - deletePendingRequest(RT_UPLOAD, req->getType(), req->getUUID()); - } - } -} - -// static -size_t LLHTTPAssetStorage::curlDownCallback(void *data, size_t size, size_t nmemb, void *user_data) -{ - if (!gAssetStorage) - { - llwarns << "Missing gAssetStorage, aborting curl download callback!" << llendl; - return 0; - } - S32 bytes = (S32)(size * nmemb); - CURL *curl_handle = (CURL *)user_data; - LLHTTPAssetRequest *req = NULL; - curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req); - - if (! req->mVFile) - { - req->mVFile = new LLVFile(gAssetStorage->mVFS, req->mTmpUUID, LLAssetType::AT_NONE, LLVFile::APPEND); - } - - double content_length = 0.0; - curl_easy_getinfo(curl_handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length); - - // sanitize content_length, reconcile w/ actual data - S32 file_length = llmax(0, (S32)llmin(content_length, 20000000.0), bytes + req->mVFile->getSize()); - - req->mVFile->setMaxSize(file_length); - req->mVFile->write((U8*)data, bytes); - - return nmemb; -} - -// static -size_t LLHTTPAssetStorage::curlUpCallback(void *data, size_t size, size_t nmemb, void *user_data) -{ - if (!gAssetStorage) - { - llwarns << "Missing gAssetStorage, aborting curl download callback!" << llendl; - return 0; - } - CURL *curl_handle = (CURL *)user_data; - LLHTTPAssetRequest *req = NULL; - curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req); - - if (! req->mVFile) - { - req->mVFile = new LLVFile(gAssetStorage->mVFS, req->getUUID(), req->getType(), LLVFile::READ); - } - - S32 bytes = llmin((S32)(size * nmemb), (S32)(req->mVFile->getSize() - req->mVFile->tell())); - - req->mVFile->read((U8*)data, bytes);/*Flawfinder: ignore*/ - - return req->mVFile->getLastBytesRead(); -} - -// static -size_t LLHTTPAssetStorage::nullOutputCallback(void *data, size_t size, size_t nmemb, void *user_data) -{ - // do nothing, this is here to soak up script output so it doesn't end up on stdout - - return nmemb; -} - - - -// blocking asset fetch which bypasses the VFS -// this is a very limited function for use by the simstate loader and other one-offs -S32 LLHTTPAssetStorage::getURLToFile(const LLUUID& uuid, LLAssetType::EType asset_type, const std::string &url, const std::string& filename, progress_callback callback, void *userdata) -{ - // *NOTE: There is no guarantee that the uuid and the asset_type match - // - not that it matters. - Doug - lldebugs << "LLHTTPAssetStorage::getURLToFile() - " << url << llendl; - - FILE *fp = LLFile::fopen(filename, "wb"); /*Flawfinder: ignore*/ - if (! fp) - { - llwarns << "Failed to open " << filename << " for writing" << llendl; - return LL_ERR_ASSET_REQUEST_FAILED; - } - - // make sure we use the normal curl setup, even though we don't really need a request object - LLHTTPAssetRequest req(this, uuid, asset_type, RT_DOWNLOAD, url, mCurlMultiHandle); - req.mFP = fp; - - req.setupCurlHandle(); - curl_easy_setopt(req.mCurlHandle, CURLOPT_FOLLOWLOCATION, TRUE); - curl_easy_setopt(req.mCurlHandle, CURLOPT_WRITEFUNCTION, &curlFileDownCallback); - curl_easy_setopt(req.mCurlHandle, CURLOPT_WRITEDATA, req.mCurlHandle); - - curl_multi_add_handle(mCurlMultiHandle, req.mCurlHandle); - llinfos << "Requesting as file " << req.mURLBuffer << llendl; - - // braindead curl loop - int queue_length; - CURLMsg *curl_msg; - LLTimer timeout; - timeout.setTimerExpirySec(GET_URL_TO_FILE_TIMEOUT); - bool success = false; - S32 xfer_result = 0; - do - { - curl_multi_perform(mCurlMultiHandle, &queue_length); - curl_msg = curl_multi_info_read(mCurlMultiHandle, &queue_length); - - if (callback) - { - callback(userdata); - } - - if ( curl_msg && (CURLMSG_DONE == curl_msg->msg) ) - { - success = true; - } - else if (timeout.hasExpired()) - { - llwarns << "Request for " << url << " has timed out." << llendl; - success = false; - xfer_result = LL_ERR_ASSET_REQUEST_FAILED; - break; - } - } while (!success); - - if (success) - { - long curl_result = 0; - curl_easy_getinfo(curl_msg->easy_handle, CURLINFO_HTTP_CODE, &curl_result); - - if (curl_result == HTTP_OK && curl_msg->data.result == CURLE_OK) - { - S32 size = ftell(req.mFP); - if (size > 0) - { - // everything seems to be in order - llinfos << "Success downloading " << req.mURLBuffer << " to file, size " << size << llendl; - } - else - { - llwarns << "Found " << req.mURLBuffer << " to be zero size" << llendl; - xfer_result = LL_ERR_ASSET_REQUEST_FAILED; - } - } - else - { - xfer_result = curl_result == HTTP_MISSING ? LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE : LL_ERR_ASSET_REQUEST_FAILED; - llinfos << "Failure downloading " << req.mURLBuffer << - " with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl; - } - } - - fclose(fp); - if (xfer_result) - { - LLFile::remove(filename); - } - return xfer_result; -} - - -// static -size_t LLHTTPAssetStorage::curlFileDownCallback(void *data, size_t size, size_t nmemb, void *user_data) -{ - CURL *curl_handle = (CURL *)user_data; - LLHTTPAssetRequest *req = NULL; - curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req); - - if (! req->mFP) - { - llwarns << "Missing mFP, aborting curl file download callback!" << llendl; - return 0; - } - - return fwrite(data, size, nmemb, req->mFP); -} - -LLAssetStorage::request_list_t* LLHTTPAssetStorage::getRunningList(LLAssetStorage::ERequestType rt) -{ - switch (rt) - { - case RT_DOWNLOAD: - return &mRunningDownloads; - case RT_UPLOAD: - return &mRunningUploads; - case RT_LOCALUPLOAD: - return &mRunningLocalUploads; - default: - return NULL; - } -} - -const LLAssetStorage::request_list_t* LLHTTPAssetStorage::getRunningList(LLAssetStorage::ERequestType rt) const -{ - switch (rt) - { - case RT_DOWNLOAD: - return &mRunningDownloads; - case RT_UPLOAD: - return &mRunningUploads; - case RT_LOCALUPLOAD: - return &mRunningLocalUploads; - default: - return NULL; - } -} - - -void LLHTTPAssetStorage::addRunningRequest(ERequestType rt, LLHTTPAssetRequest* request) -{ - request_list_t* requests = getRunningList(rt); - if (requests) - { - requests->push_back(request); - } - else - { - llerrs << "LLHTTPAssetStorage::addRunningRequest - Request is not an upload OR download, this is bad!" << llendl; - } -} - -void LLHTTPAssetStorage::removeRunningRequest(ERequestType rt, LLHTTPAssetRequest* request) -{ - request_list_t* requests = getRunningList(rt); - if (requests) - { - requests->remove(request); - } - else - { - llerrs << "LLHTTPAssetStorage::removeRunningRequest - Destroyed request is not an upload OR download, this is bad!" << llendl; - } -} - -// virtual -void LLHTTPAssetStorage::addTempAssetData(const LLUUID& asset_id, const LLUUID& agent_id, const std::string& host_name) -{ - if (agent_id.isNull() || asset_id.isNull()) - { - llwarns << "TAT: addTempAssetData bad id's asset_id: " << asset_id << " agent_id: " << agent_id << llendl; - return; - } - - LLTempAssetData temp_asset_data; - temp_asset_data.mAssetID = asset_id; - temp_asset_data.mAgentID = agent_id; - temp_asset_data.mHostName = host_name; - - mTempAssets[asset_id] = temp_asset_data; -} - -// virtual -BOOL LLHTTPAssetStorage::hasTempAssetData(const LLUUID& texture_id) const -{ - uuid_tempdata_map::const_iterator citer = mTempAssets.find(texture_id); - BOOL found = (citer != mTempAssets.end()); - return found; -} - -// virtual -std::string LLHTTPAssetStorage::getTempAssetHostName(const LLUUID& texture_id) const -{ - uuid_tempdata_map::const_iterator citer = mTempAssets.find(texture_id); - if (citer != mTempAssets.end()) - { - return citer->second.mHostName; - } - else - { - return std::string(); - } -} - -// virtual -LLUUID LLHTTPAssetStorage::getTempAssetAgentID(const LLUUID& texture_id) const -{ - uuid_tempdata_map::const_iterator citer = mTempAssets.find(texture_id); - if (citer != mTempAssets.end()) - { - return citer->second.mAgentID; - } - else - { - return LLUUID::null; - } -} - -// virtual -void LLHTTPAssetStorage::removeTempAssetData(const LLUUID& asset_id) -{ - mTempAssets.erase(asset_id); -} - -// virtual -void LLHTTPAssetStorage::removeTempAssetDataByAgentID(const LLUUID& agent_id) -{ - uuid_tempdata_map::iterator it = mTempAssets.begin(); - uuid_tempdata_map::iterator end = mTempAssets.end(); - - while (it != end) - { - const LLTempAssetData& asset_data = it->second; - if (asset_data.mAgentID == agent_id) - { - mTempAssets.erase(it++); - } - else - { - ++it; - } - } -} - -std::string LLHTTPAssetStorage::getBaseURL(const LLUUID& asset_id, LLAssetType::EType asset_type) -{ - if (LLAssetType::AT_TEXTURE == asset_type) - { - uuid_tempdata_map::const_iterator citer = mTempAssets.find(asset_id); - if (citer != mTempAssets.end()) - { - const std::string& host_name = citer->second.mHostName; - std::string url = llformat(LOCAL_ASSET_URL_FORMAT, host_name.c_str()); - return url; - } - } - - return mBaseURL; -} - -void LLHTTPAssetStorage::dumpTempAssetData(const LLUUID& avatar_id) const -{ - uuid_tempdata_map::const_iterator it = mTempAssets.begin(); - uuid_tempdata_map::const_iterator end = mTempAssets.end(); - S32 count = 0; - for ( ; it != end; ++it) - { - const LLTempAssetData& temp_asset_data = it->second; - if (avatar_id.isNull() - || avatar_id == temp_asset_data.mAgentID) - { - llinfos << "TAT: dump agent " << temp_asset_data.mAgentID - << " texture " << temp_asset_data.mAssetID - << " host " << temp_asset_data.mHostName - << llendl; - count++; - } - } - - if (avatar_id.isNull()) - { - llinfos << "TAT: dumped " << count << " entries for all avatars" << llendl; - } - else - { - llinfos << "TAT: dumped " << count << " entries for avatar " << avatar_id << llendl; - } -} - -void LLHTTPAssetStorage::clearTempAssetData() -{ - llinfos << "TAT: Clearing temp asset data map" << llendl; - mTempAssets.clear(); -} diff --git a/indra/llmessage/llhttpassetstorage.h b/indra/llmessage/llhttpassetstorage.h deleted file mode 100644 index f743ccf0a..000000000 --- a/indra/llmessage/llhttpassetstorage.h +++ /dev/null @@ -1,159 +0,0 @@ -/** - * @file llhttpassetstorage.h - * @brief Class for loading asset data to/from an external source over http. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLHTTPASSETSTORAGE_H -#define LLHTTPASSETSTORAGE_H - -#include "llassetstorage.h" -#include "curl/curl.h" - -class LLVFile; -class LLHTTPAssetRequest; -typedef void (*progress_callback)(void* userdata); - -struct LLTempAssetData; - -typedef std::map uuid_tempdata_map; - -class LLHTTPAssetStorage : public LLAssetStorage -{ -public: - LLHTTPAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, LLVFS *static_vfs, - const LLHost &upstream_host, - const std::string& web_host, - const std::string& local_web_host, - const std::string& host_name); - - LLHTTPAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, LLVFS *static_vfs, - const std::string& web_host, - const std::string& local_web_host, - const std::string& host_name); - - - virtual ~LLHTTPAssetStorage(); - - using LLAssetStorage::storeAssetData; // Unhiding virtuals... - - virtual void storeAssetData( - const LLUUID& uuid, - LLAssetType::EType atype, - LLStoreAssetCallback callback, - void* user_data, - bool temp_file = false, - bool is_priority = false, - bool store_local = false, - const LLUUID& requesting_agent_id = LLUUID::null, - bool user_waiting=FALSE, - F64 timeout=LL_ASSET_STORAGE_TIMEOUT); - - virtual void storeAssetData( - const std::string& filename, - const LLUUID& asset_id, - LLAssetType::EType atype, - LLStoreAssetCallback callback, - void* user_data, - bool temp_file, - bool is_priority, - bool user_waiting=FALSE, - F64 timeout=LL_ASSET_STORAGE_TIMEOUT); - - virtual LLSD getPendingDetails(ERequestType rt, - LLAssetType::EType asset_type, - const std::string& detail_prefix) const; - - virtual LLSD getPendingRequest(ERequestType rt, - LLAssetType::EType asset_type, - const LLUUID& asset_id) const; - - virtual bool deletePendingRequest(ERequestType rt, - LLAssetType::EType asset_type, - const LLUUID& asset_id); - - // Hack. One off curl download an URL to a file. Probably should be elsewhere. - // Only used by lldynamicstate. The API is broken, and should be replaced with - // a generic HTTP file fetch - Doug 9/25/06 - S32 getURLToFile(const LLUUID& uuid, LLAssetType::EType asset_type, const std::string &url, const std::string& filename, progress_callback callback, void *userdata); - - LLAssetRequest* findNextRequest(request_list_t& pending, request_list_t& running); - - void checkForTimeouts(); - - static size_t curlDownCallback(void *data, size_t size, size_t nmemb, void *user_data); - static size_t curlFileDownCallback(void *data, size_t size, size_t nmemb, void *user_data); - static size_t curlUpCallback(void *data, size_t size, size_t nmemb, void *user_data); - static size_t nullOutputCallback(void *data, size_t size, size_t nmemb, void *user_data); - - // Should only be used by the LLHTTPAssetRequest - void addRunningRequest(ERequestType rt, LLHTTPAssetRequest* request); - void removeRunningRequest(ERequestType rt, LLHTTPAssetRequest* request); - - request_list_t* getRunningList(ERequestType rt); - const request_list_t* getRunningList(ERequestType rt) const; - - // Temp assets are stored on sim nodes, they have agent ID and location data associated with them. - virtual void addTempAssetData(const LLUUID& asset_id, const LLUUID& agent_id, const std::string& host_name); - virtual BOOL hasTempAssetData(const LLUUID& texture_id) const; - virtual std::string getTempAssetHostName(const LLUUID& texture_id) const; - virtual LLUUID getTempAssetAgentID(const LLUUID& texture_id) const; - virtual void removeTempAssetData(const LLUUID& asset_id); - virtual void removeTempAssetDataByAgentID(const LLUUID& agent_id); - - // Pass LLUUID::null for all - virtual void dumpTempAssetData(const LLUUID& avatar_id) const; - virtual void clearTempAssetData(); - -protected: - void _queueDataRequest(const LLUUID& uuid, LLAssetType::EType type, - void (*callback)(LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32, LLExtStat), - void *user_data, BOOL duplicate, BOOL is_priority); - -private: - void _init(const std::string& web_host, const std::string& local_web_host, const std::string& host_name); - - // This will return the correct base URI for any http asset request - std::string getBaseURL(const LLUUID& asset_id, LLAssetType::EType asset_type); - - // Check for running uploads that have timed out - // Bump these to the back of the line to let other uploads complete. - void bumpTimedOutUploads(); - -protected: - std::string mBaseURL; - std::string mLocalBaseURL; - std::string mHostName; - - CURLM *mCurlMultiHandle; - - request_list_t mRunningDownloads; - request_list_t mRunningUploads; - request_list_t mRunningLocalUploads; - - uuid_tempdata_map mTempAssets; -}; - -#endif From e7a3a150a7403db43c6ad517595584031e72644a Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Thu, 3 May 2012 16:29:24 +0200 Subject: [PATCH 13/19] Add AIThreadSafeBits::wrapper_cast to cast wrapped object back to wrapper class. --- indra/llcommon/aithreadsafe.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/indra/llcommon/aithreadsafe.h b/indra/llcommon/aithreadsafe.h index 2c11908af..a8b1ac490 100644 --- a/indra/llcommon/aithreadsafe.h +++ b/indra/llcommon/aithreadsafe.h @@ -132,6 +132,14 @@ public: // Only for use by AITHREADSAFE, see below. void* memory() const { return const_cast(&mMemory[0]); } + // Cast a T* back to AIThreadSafeBits. This is the inverse of memory(). + template + static AIThreadSafeBits* wrapper_cast(T2* ptr) + { return reinterpret_cast*>(reinterpret_cast(ptr) - offsetof(AIThreadSafeBits, mMemory[0])); } + template + static AIThreadSafeBits const* wrapper_cast(T2 const* ptr) + { return reinterpret_cast const*>(reinterpret_cast(ptr) - offsetof(AIThreadSafeBits, mMemory[0])); } + protected: // Accessors. T const* ptr() const { return reinterpret_cast(mMemory); } From 5996ff1fa2763674389ad3c3dbf6e9c31f4c4407 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Thu, 3 May 2012 16:30:02 +0200 Subject: [PATCH 14/19] Fixed a comment. --- indra/llcommon/llqueuedthread.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index ab73670ae..2680072bc 100644 --- a/indra/llcommon/llqueuedthread.h +++ b/indra/llcommon/llqueuedthread.h @@ -44,7 +44,7 @@ #include "llsimplehash.h" //============================================================================ -// Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small +// Note: ~LLQueuedThread is O(N) N=# of queued requests, assumed to be small // It is assumed that LLQueuedThreads are rarely created/destroyed. class LL_COMMON_API LLQueuedThread : public LLThread From 0bee4a922f2bc84796d901f83766ecfe162f0f2a Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Thu, 3 May 2012 16:32:25 +0200 Subject: [PATCH 15/19] Make sure that sRunning <= number of threads with status RUNNING. --- indra/llcommon/llthread.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 4088d4a44..6f07b5482 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -104,6 +104,11 @@ void *APR_THREAD_FUNC LLThread::staticRun(apr_thread_t *apr_threadp, void *datap // the moment it happens... therefore make a copy here. char const* volatile name = threadp->mName.c_str(); + // Always make sure that sRunning <= number of threads with status RUNNING, + // so do this before changing mStatus (meaning that once we see that we + // are STOPPED, then sRunning is also up to date). + --sRunning; + // We're done with the run function, this thread is done executing now. threadp->mStatus = STOPPED; From 15fb0ac3aa3643a72989c9bed9e283a19631edaa Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Sun, 6 May 2012 19:48:12 +0200 Subject: [PATCH 16/19] Allow AIStateMachine::cont() to be called from another thread for an idle statemachine. --- indra/newview/statemachine/aistatemachine.cpp | 49 +++++++++++++++++-- indra/newview/statemachine/aistatemachine.h | 1 + 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/indra/newview/statemachine/aistatemachine.cpp b/indra/newview/statemachine/aistatemachine.cpp index 68d3241e1..badcdf5f5 100644 --- a/indra/newview/statemachine/aistatemachine.cpp +++ b/indra/newview/statemachine/aistatemachine.cpp @@ -157,14 +157,33 @@ void AIStateMachine::idle(void) mSleep = 0; } +// About thread safeness: +// +// The main thread initializes a statemachine and calls run, so a statemachine +// runs in the main thread. However, it is allowed that a state calls idle() +// and then allows one and only one other thread to call cont() upon some +// event (only once, of course, as idle() has to be called before cont() +// can be called again-- and another thread is not allowed to call idle()). +// Instead of cont(), the other thread may also call set_state(). + void AIStateMachine::cont(void) { DoutEntering(dc::statemachine, "AIStateMachine::cont() [" << (void*)this << "]"); llassert(mIdle); + // Atomic test mActive and change mIdle. + mIdleActive.lock(); mIdle = false; - if (mActive == as_idle) + bool not_active = mActive == as_idle; + mIdleActive.unlock(); + if (not_active) { AIWriteAccess cscm_w(continued_statemachines_and_calling_mainloop); + // We only get here when the statemachine was idle (set by the main thread), + // see first assertion. Hence, the main thread is not changing this, as the + // statemachine is not running. Thus, mActive can have changed when a THIRD + // thread called cont(), which is not allowed: if two threads can call cont() + // at any moment then the first assertion can't hold. + llassert_always(mActive == as_idle); cscm_w->continued_statemachines.push_back(this); if (!cscm_w->calling_mainloop) { @@ -173,6 +192,7 @@ void AIStateMachine::cont(void) gIdleCallbacks.addFunction(&AIStateMachine::mainloop); } mActive = as_queued; + llassert_always(!mIdle); // It should never happen that one thread calls cont() while another calls idle() concurrently. } } @@ -203,7 +223,7 @@ void AIStateMachine::finish(void) { DoutEntering(dc::statemachine, "AIStateMachine::finish() [" << (void*)this << "]"); llassert(mState == bs_run || mState == bs_abort); - // It is possible that mIdle is false when abort or finish was called from + // It is possible that mIdle is true when abort or finish was called from // outside multiplex_impl. However, that only may be done by the main thread. llassert(!mIdle || is_main_thread()); if (!mIdle) @@ -363,6 +383,9 @@ void AIStateMachine::mainloop(void*) if (!statemachine.mIdle) { U64 start = LLFastTimer::getCPUClockCount64(); + // This might call idle() and then pass the statemachine to another thread who then may call cont(). + // Hence, after this isn't not sure what mIdle is, and it can change from true to false at any moment, + // if it is true after this function returns. iter->statemachine().multiplex(start); U64 delta = LLFastTimer::getCPUClockCount64() - start; iter->add(delta); @@ -382,10 +405,23 @@ void AIStateMachine::mainloop(void*) while (iter != active_statemachines.end()) { AIStateMachine& statemachine(iter->statemachine()); - if (statemachine.mIdle) + // Atomic test mIdle and change mActive. + bool locked = statemachine.mIdleActive.tryLock(); + // If the lock failed, then another thread is in the middle of calling cont(), + // thus mIdle will end up false. So, there is no reason to block here; just + // treat mIdle as false already. + if (locked && statemachine.mIdle) { - Dout(dc::statemachine, "Erasing " << (void*)&statemachine << " from active_statemachines"); + // Without the lock, it would be possible that another thread called cont() right here, + // changing mIdle to false again but NOT adding the statemachine to continued_statemachines, + // thinking it is in active_statemachines (and it is), while immediately below it is + // erased from active_statemachines. statemachine.mActive = as_idle; + // Now, calling cont() is ok -- as that will cause the statemachine to be added to + // continued_statemachines, so it's fine in that case-- even necessary-- to remove it from + // active_statemachines regardless, and we can release the lock here. + statemachine.mIdleActive.unlock(); + Dout(dc::statemachine, "Erasing " << (void*)&statemachine << " from active_statemachines"); iter = active_statemachines.erase(iter); if (statemachine.mState == bs_killed) { @@ -395,6 +431,11 @@ void AIStateMachine::mainloop(void*) } else { + if (locked) + { + statemachine.mIdleActive.unlock(); + } + llassert(statemachine.mActive == as_active); // It should not be possible that another thread called cont() and changed this when we are we are not idle. llassert(statemachine.mState == bs_run || statemachine.mState == bs_initialize); ++iter; } diff --git a/indra/newview/statemachine/aistatemachine.h b/indra/newview/statemachine/aistatemachine.h index 2b9d93bf9..f20fcf048 100644 --- a/indra/newview/statemachine/aistatemachine.h +++ b/indra/newview/statemachine/aistatemachine.h @@ -206,6 +206,7 @@ class AIStateMachine { bool mAborted; //!< True after calling abort() and before calling run(). active_type mActive; //!< Whether statemachine is idle, queued to be added to the active list, or already on the active list. S64 mSleep; //!< Non-zero while the state machine is sleeping. + LLMutex mIdleActive; //!< Used for atomic operations on the pair mIdle / mActive. // Callback facilities. // From within an other state machine: From c02834b7e3a8a2c3f1e6abbc85c18b6879156abb Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Fri, 11 May 2012 04:15:07 +0200 Subject: [PATCH 17/19] Use AIThreadSafeDC and _wat convention to simplify AIEvent. --- indra/newview/statemachine/aievent.cpp | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/indra/newview/statemachine/aievent.cpp b/indra/newview/statemachine/aievent.cpp index ec2901932..8682f7670 100644 --- a/indra/newview/statemachine/aievent.cpp +++ b/indra/newview/statemachine/aievent.cpp @@ -64,22 +64,8 @@ inline void AIRegisteredStateMachines::trigger(void) } // A list (array) with all AIRegisteredStateMachines maps, one for each event type. -struct AIRegisteredStateMachinesList { - AIThreadSafeSimple mRegisteredStateMachinesList[AIEvent::number_of_events]; - AIRegisteredStateMachinesList(void); - AIThreadSafeSimple& operator[](AIEvent::AIEvents event) { return mRegisteredStateMachinesList[event]; } -}; - -AIRegisteredStateMachinesList::AIRegisteredStateMachinesList(void) -{ - for (int event = 0; event < AIEvent::number_of_events; ++event) - { - new (&mRegisteredStateMachinesList[event]) AIRegisteredStateMachines; - } -} - -// Instantiate the list with all AIRegisteredStateMachines maps. -static AIRegisteredStateMachinesList registered_statemachines_list; +static AIThreadSafeSimpleDC registered_statemachines_list[AIEvent::number_of_events]; +typedef AIAccess registered_statemachines_wat; //----------------------------------------------------------------------------- // External API starts here. @@ -91,7 +77,7 @@ static AIRegisteredStateMachinesList registered_statemachines_list; void AIEvent::Register(AIEvents event, AIStateMachine* statemachine, bool one_shot) { statemachine->idle(); - AIAccess registered_statemachines_w(registered_statemachines_list[event]); + registered_statemachines_wat registered_statemachines_w(registered_statemachines_list[event]); registered_statemachines_w->Register(statemachine, one_shot); } @@ -99,7 +85,7 @@ void AIEvent::Register(AIEvents event, AIStateMachine* statemachine, bool one_sh // static void AIEvent::Unregister(AIEvents event, AIStateMachine* statemachine) { - AIAccess registered_statemachines_w(registered_statemachines_list[event]); + registered_statemachines_wat registered_statemachines_w(registered_statemachines_list[event]); registered_statemachines_w->Unregister(statemachine); } @@ -107,7 +93,7 @@ void AIEvent::Unregister(AIEvents event, AIStateMachine* statemachine) // static void AIEvent::trigger(AIEvents event) { - AIAccess registered_statemachines_w(registered_statemachines_list[event]); + registered_statemachines_wat registered_statemachines_w(registered_statemachines_list[event]); registered_statemachines_w->trigger(); } From 782091ff025bc39bfd01a188d16b87ab7c84773a Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Mon, 4 Jun 2012 23:45:17 +0200 Subject: [PATCH 18/19] Print useful error when dlopen() return NULL on linux standalone. --- indra/llplugin/llplugininstance.cpp | 17 ++++++++++++++++- indra/llplugin/slplugin/slplugin.cpp | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/indra/llplugin/llplugininstance.cpp b/indra/llplugin/llplugininstance.cpp index 15e1d347a..793c8bb97 100644 --- a/indra/llplugin/llplugininstance.cpp +++ b/indra/llplugin/llplugininstance.cpp @@ -99,7 +99,22 @@ int LLPluginInstance::load(std::string &plugin_file) if(result != APR_SUCCESS) { char buf[1024]; - apr_dso_error(mDSOHandle, buf, sizeof(buf)); +#if LL_LINUX && defined(LL_STANDALONE) + if (!dso_handle) + { + char* error = dlerror(); + buf[0] = 0; + if (error) + { + strncpy(buf, dlerror(), sizeof(buf)); + } + buf[sizeof(buf) - 1] = 0; + } + else +#endif + { + apr_dso_error(mDSOHandle, buf, sizeof(buf)); + } #if LL_LINUX && defined(LL_STANDALONE) LL_WARNS("Plugin") << "plugin load " << plugin_file << " failed with error " << result << " , additional info string: " << buf << LL_ENDL; diff --git a/indra/llplugin/slplugin/slplugin.cpp b/indra/llplugin/slplugin/slplugin.cpp index 04662cf24..b841a30e1 100644 --- a/indra/llplugin/slplugin/slplugin.cpp +++ b/indra/llplugin/slplugin/slplugin.cpp @@ -191,7 +191,7 @@ int main(int argc, char **argv) #ifdef CWDEBUG Debug( libcw_do.margin().assign("SLPlugin ", 9) ); Debug(debug::init()); - // Uncomment this to automatically open a terminal with gdb. Requires SNOW-173. + // Uncomment this to automatically open a terminal with gdb. //Debug(attach_gdb()); #endif From 221e3908b9b5a673b73dd624e98e03d988b417a5 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Fri, 22 Jun 2012 06:21:21 +0200 Subject: [PATCH 19/19] There is no need to use a pure virtual destructor, and doing so crashes libcwd when configured with --enable-alloc. --- indra/llcommon/lloptioninterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/lloptioninterface.h b/indra/llcommon/lloptioninterface.h index 4faf95f5e..dd5829d79 100644 --- a/indra/llcommon/lloptioninterface.h +++ b/indra/llcommon/lloptioninterface.h @@ -39,7 +39,7 @@ class LLSD; class LLOptionInterface { public: - virtual ~LLOptionInterface() = 0; + virtual ~LLOptionInterface(); virtual LLSD getOption(const std::string& name) const = 0; };