diff --git a/doc/contributions.txt b/doc/contributions.txt
index 849c95de4..b5b84a45d 100644
--- a/doc/contributions.txt
+++ b/doc/contributions.txt
@@ -111,6 +111,7 @@ Aleric Inglewood
IMP-663
IMP-664
IMP-670
+ IMP-701
Alissa Sabre
VWR-81
VWR-83
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index cc6bffe3f..7f352269b 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -80,6 +80,7 @@ set(llcommon_HEADER_FILES
CMakeLists.txt
aiaprpool.h
+ aithreadsafe.h
bitpack.h
ctype_workaround.h
doublelinkedlist.h
diff --git a/indra/llcommon/aithreadsafe.h b/indra/llcommon/aithreadsafe.h
new file mode 100644
index 000000000..70cd2a3db
--- /dev/null
+++ b/indra/llcommon/aithreadsafe.h
@@ -0,0 +1,482 @@
+/**
+ * @file aithreadsafe.h
+ * @brief Implementation of AIThreadSafe, AIReadAccessConst, AIReadAccess and AIWriteAccess.
+ *
+ * Copyright (c) 2010, Aleric Inglewood.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * 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.
+ *
+ * CHANGELOG
+ * and additional copyright holders.
+ *
+ * 31/03/2010
+ * Initial version, written by Aleric Inglewood @ SL
+ */
+
+#ifndef AITHREADSAFE_H
+#define AITHREADSAFE_H
+
+#include
+
+#include "llthread.h"
+#include "llerror.h"
+
+template struct AIReadAccessConst;
+template struct AIReadAccess;
+template struct AIWriteAccess;
+template struct AIAccess;
+
+template
+class AIThreadSafeBits
+{
+private:
+ // AIThreadSafe is a wrapper around an instance of T.
+ // Because T might not have a default constructor, it is constructed
+ // 'in place', with placement new, in the memory reserved here.
+ //
+ // Make sure that the memory that T will be placed in is properly
+ // aligned by using an array of long's.
+ long mMemory[(sizeof(T) + sizeof(long) - 1) / sizeof(long)];
+
+public:
+ // The wrapped objects are constructed in-place with placement new *outside*
+ // of this object (by AITHREADSAFE macro(s) or derived classes).
+ // However, we are responsible for the destruction of the wrapped object.
+ ~AIThreadSafeBits() { ptr()->~T(); }
+
+ // Only for use by AITHREADSAFE, see below.
+ void* memory() const { return const_cast(&mMemory[0]); }
+
+protected:
+ // Accessors.
+ T const* ptr() const { return reinterpret_cast(mMemory); }
+ T* ptr() { return reinterpret_cast(mMemory); }
+};
+
+/**
+ * @brief A wrapper class for objects that need to be accessed by more than one thread, allowing concurrent readers.
+ *
+ * Use AITHREADSAFE to define instances of any type, and use AIReadAccessConst,
+ * AIReadAccess and AIWriteAccess to get access to the instance.
+ *
+ * For example,
+ *
+ *
+ * class Foo { public: Foo(int, int); };
+ *
+ * AITHREADSAFE(Foo, foo, (2, 3));
+ *
+ * AIReadAccess foo_r(foo);
+ * // Use foo_r-> for read access.
+ *
+ * AIWriteAccess foo_w(foo);
+ * // Use foo_w-> for write access.
+ *
+ *
+ * If foo is constant, you have to use AIReadAccessConst.
+ *
+ * It is possible to pass access objects to a function that
+ * downgrades the access, for example:
+ *
+ *
+ * void readfunc(AIReadAccess const& access);
+ *
+ * AIWriteAccess foo_w(foo);
+ * readfunc(foo_w); // readfunc will perform read access to foo_w.
+ *
+ *
+ * If AIReadAccess is non-const, you can upgrade the access by creating
+ * an AIWriteAccess object from it. For example:
+ *
+ *
+ * AIWriteAccess foo_w(foo_r);
+ *
+ *
+ * This API is Robust(tm). If you try anything that could result in problems,
+ * it simply won't compile. The only mistake you can still easily make is
+ * to obtain write access to an object when it is not needed, or to unlock
+ * an object in between accesses while the state of the object should be
+ * preserved. For example:
+ *
+ *
+ * // This resets foo to point to the first file and then returns that.
+ * std::string filename = AIWriteAccess(foo)->get_first_filename();
+ *
+ * // WRONG! The state between calling get_first_filename and get_next_filename should be preserved!
+ *
+ * AIWriteAccess foo_w(foo); // Wrong. The code below only needs read-access.
+ * while (!filename.empty())
+ * {
+ * something(filename);
+ * filename = foo_w->next_filename();
+ * }
+ *
+ *
+ * Correct would be
+ *
+ *
+ * AIReadAccess foo_r(foo);
+ * std::string filename = AIWriteAccess(foo_r)->get_first_filename();
+ * while (!filename.empty())
+ * {
+ * something(filename);
+ * filename = foo_r->next_filename();
+ * }
+ *
+ *
+ */
+template
+class AIThreadSafe : public AIThreadSafeBits
+{
+protected:
+ // Only these may access the object (through ptr()).
+ friend struct AIReadAccessConst;
+ friend struct AIReadAccess;
+ friend struct AIWriteAccess;
+
+ // Locking control.
+ AIRWLock mRWLock;
+
+ // For use by AIThreadSafeDC
+ AIThreadSafe(void) { }
+ AIThreadSafe(AIAPRPool& parent) : mRWLock(parent) { }
+
+public:
+ // Only for use by AITHREADSAFE, see below.
+ AIThreadSafe(T* object) { llassert(object == AIThreadSafeBits::ptr()); }
+};
+
+/**
+ * @brief Instantiate an static, global or local object of a given type wrapped in AIThreadSafe, using an arbitrary constructor.
+ *
+ * For example, instead of doing
+ *
+ *
+ * Foo foo(x, y);
+ * static Bar bar;
+ *
+ *
+ * One can instantiate a thread-safe instance with
+ *
+ *
+ * AITHREADSAFE(Foo, foo, (x, y));
+ * static AITHREADSAFE(Bar, bar, );
+ *
+ *
+ * Note: This macro does not allow to allocate such object on the heap.
+ * If that is needed, have a look at AIThreadSafeDC.
+ */
+#define AITHREADSAFE(type, var, paramlist) AIThreadSafe var(new (var.memory()) type paramlist)
+
+/**
+ * @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.
+ *
+ * For example, instead of
+ *
+ *
+ * Foo foo;
+ *
+ *
+ * One would use
+ *
+ *
+ * AIThreadSafeDC foo;
+ *
+ *
+ * The advantage over AITHREADSAFE is that this object can be allocated with
+ * new on the heap. For example:
+ *
+ *
+ * AIThreadSafeDC* ptr = new AIThreadSafeDC;
+ *
+ *
+ * which is not possible with AITHREADSAFE.
+ */
+template
+class AIThreadSafeDC : public AIThreadSafe
+{
+public:
+ // Construct a wrapper around a default constructed object.
+ AIThreadSafeDC(void) { new (AIThreadSafe::ptr()) T; }
+};
+
+/**
+ * @brief Read lock object and provide read access.
+ */
+template
+struct AIReadAccessConst
+{
+ //! Internal enum for the lock-type of the AI*Access object.
+ enum state_type
+ {
+ readlocked, //!< A AIReadAccessConst or AIReadAccess.
+ read2writelocked, //!< A AIWriteAccess constructed from a AIReadAccess.
+ writelocked, //!< A AIWriteAccess constructed from a AIThreadSafe.
+ write2writelocked //!< A AIWriteAccess constructed from (the AIReadAccess base class of) a AIWriteAccess.
+ };
+
+ //! Construct a AIReadAccessConst from a constant AIThreadSafe.
+ AIReadAccessConst(AIThreadSafe const& wrapper)
+ : mWrapper(const_cast&>(wrapper)),
+ mState(readlocked)
+ {
+ mWrapper.mRWLock.rdlock();
+ }
+
+ //! Destruct the AI*Access object.
+ // These should never be dynamically allocated, so there is no need to make this virtual.
+ ~AIReadAccessConst()
+ {
+ if (mState == readlocked)
+ mWrapper.mRWLock.rdunlock();
+ else if (mState == writelocked)
+ mWrapper.mRWLock.wrunlock();
+ else if (mState == read2writelocked)
+ mWrapper.mRWLock.wr2rdlock();
+ }
+
+ //! Access the underlaying object for read access.
+ T const* operator->() const { return mWrapper.ptr(); }
+
+ //! Access the underlaying object for read access.
+ T const& operator*() const { return *mWrapper.ptr(); }
+
+protected:
+ //! Constructor used by AIReadAccess.
+ AIReadAccessConst(AIThreadSafe& wrapper, state_type state)
+ : mWrapper(wrapper), mState(state) { }
+
+ AIThreadSafe& mWrapper; //!< Reference to the object that we provide access to.
+ state_type const mState; //!< The lock state that mWrapper is in.
+
+private:
+ // Disallow copy constructing directly.
+ AIReadAccessConst(AIReadAccessConst const&);
+};
+
+/**
+ * @brief Read lock object and provide read access, with possible promotion to write access.
+ */
+template
+struct AIReadAccess : public AIReadAccessConst
+{
+ typedef typename AIReadAccessConst::state_type state_type;
+ using AIReadAccessConst::readlocked;
+
+ //! Construct a AIReadAccess from a non-constant AIThreadSafe.
+ AIReadAccess(AIThreadSafe& wrapper) : AIReadAccessConst(wrapper, readlocked) { this->mWrapper.mRWLock.rdlock(); }
+
+protected:
+ //! Constructor used by AIWriteAccess.
+ AIReadAccess(AIThreadSafe& wrapper, state_type state) : AIReadAccessConst(wrapper, state) { }
+
+ friend class AIWriteAccess;
+};
+
+/**
+ * @brief Write lock object and provide read/write access.
+ */
+template
+struct AIWriteAccess : public AIReadAccess
+{
+ using AIReadAccessConst::readlocked;
+ using AIReadAccessConst::read2writelocked;
+ using AIReadAccessConst::writelocked;
+ using AIReadAccessConst::write2writelocked;
+
+ //! Construct a AIWriteAccess from a non-constant AIThreadSafe.
+ AIWriteAccess(AIThreadSafe& wrapper) : AIReadAccess(wrapper, writelocked) { this->mWrapper.mRWLock.wrlock();}
+
+ //! Promote read access to write access.
+ explicit AIWriteAccess(AIReadAccess& access)
+ : AIReadAccess(access.mWrapper, (access.mState == readlocked) ? read2writelocked : write2writelocked)
+ {
+ if (this->mState == read2writelocked)
+ {
+ this->mWrapper.mRWLock.rd2wrlock();
+ }
+ }
+
+ //! 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 need to be accessed by more than one thread.
+ *
+ * Use AITHREADSAFESIMPLE to define instances of any type, and use AIAccess
+ * to get access to the instance.
+ *
+ * For example,
+ *
+ *
+ * class Foo { public: Foo(int, int); };
+ *
+ * AITHREADSAFESIMPLE(Foo, foo, (2, 3));
+ *
+ * AIAccess foo_w(foo);
+ * // Use foo_w-> for read and write access.
+ *
+ * See also AIThreadSafe
+ */
+template
+class AIThreadSafeSimple : public AIThreadSafeBits
+{
+protected:
+ // Only this one may access the object (through ptr()).
+ friend struct AIAccess;
+
+ // Locking control.
+ LLMutex mMutex;
+
+ // For use by AIThreadSafeSimpleDC
+ AIThreadSafeSimple(void) { }
+ AIThreadSafeSimple(AIAPRPool& parent) : mMutex(parent) { }
+
+public:
+ // Only for use by AITHREADSAFESIMPLE, see below.
+ AIThreadSafeSimple(T* object) { llassert(object == AIThreadSafeBits::ptr()); }
+};
+
+/**
+ * @brief Instantiate an static, global or local object of a given type wrapped in AIThreadSafeSimple, using an arbitrary constructor.
+ *
+ * For example, instead of doing
+ *
+ *
+ * Foo foo(x, y);
+ * static Bar bar;
+ *
+ *
+ * One can instantiate a thread-safe instance with
+ *
+ *
+ * AITHREADSAFESIMPLE(Foo, foo, (x, y));
+ * static AITHREADSAFESIMPLE(Bar, bar, );
+ *
+ *
+ * Note: This macro does not allow to allocate such object on the heap.
+ * If that is needed, have a look at AIThreadSafeSimpleDC.
+ */
+#define AITHREADSAFESIMPLE(type, var, paramlist) AIThreadSafeSimple var(new (var.memory()) type paramlist)
+
+/**
+ * @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.
+ *
+ * For example, instead of
+ *
+ *
+ * Foo foo;
+ *
+ *
+ * One would use
+ *
+ *
+ * AIThreadSafeSimpleDC foo;
+ *
+ *
+ * The advantage over AITHREADSAFESIMPLE is that this object can be allocated with
+ * new on the heap. For example:
+ *
+ *
+ * AIThreadSafeSimpleDC* ptr = new AIThreadSafeSimpleDC;
+ *
+ *
+ * which is not possible with AITHREADSAFESIMPLE.
+ */
+template
+class AIThreadSafeSimpleDC : public AIThreadSafeSimple
+{
+public:
+ // Construct a wrapper around a default constructed object.
+ AIThreadSafeSimpleDC(void) { new (AIThreadSafeSimple::ptr()) T; }
+
+protected:
+ // For use by AIThreadSafeSimpleDCRootPool
+ AIThreadSafeSimpleDC(AIAPRPool& parent) : AIThreadSafeSimple(parent) { new (AIThreadSafeSimple::ptr()) T; }
+};
+
+// Helper class for AIThreadSafeSimpleDCRootPool to assure initialization of
+// the root pool before constructing AIThreadSafeSimpleDC.
+class AIThreadSafeSimpleDCRootPool_pbase
+{
+protected:
+ AIAPRRootPool mRootPool;
+
+private:
+ template friend class AIThreadSafeSimpleDCRootPool;
+ AIThreadSafeSimpleDCRootPool_pbase(void) { }
+};
+
+/**
+ * @brief A wrapper class for objects that need to be accessed by more than one thread.
+ *
+ * The same as AIThreadSafeSimpleDC except that this class creates its own AIAPRRootPool
+ * for the internally used mutexes and condition, instead of using the current threads
+ * root pool. The advantage of this is that it can be used for objects that need to
+ * be accessed from the destructors of global objects (after main). The disadvantage
+ * is that it's less efficient to use your own root pool, therefore it's use should be
+ * restricted to those cases where it is absolutely necessary.
+ */
+template
+class AIThreadSafeSimpleDCRootPool : private AIThreadSafeSimpleDCRootPool_pbase, public AIThreadSafeSimpleDC
+{
+public:
+ // Construct a wrapper around a default constructed object, using memory allocated
+ // from the operating system for the internal APR objects (mutexes and conditional),
+ // as opposed to allocated from the current threads root pool.
+ AIThreadSafeSimpleDCRootPool(void) :
+ AIThreadSafeSimpleDCRootPool_pbase(),
+ AIThreadSafeSimpleDC(mRootPool) { }
+};
+
+/**
+ * @brief Write lock object and provide read/write access.
+ */
+template
+struct AIAccess
+{
+ //! Construct a AIAccess from a non-constant AIThreadSafeSimple.
+ AIAccess(AIThreadSafeSimple& wrapper) : mWrapper(wrapper) { this->mWrapper.mMutex.lock(); }
+
+ //! 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(); }
+
+ ~AIAccess() { this->mWrapper.mMutex.unlock(); }
+
+protected:
+ AIThreadSafeSimple& mWrapper; //!< Reference to the object that we provide access to.
+
+private:
+ // Disallow copy constructing directly.
+ AIAccess(AIAccess const&);
+};
+
+#endif
diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h
index 8fa1b59ae..2146216aa 100644
--- a/indra/llcommon/llthread.h
+++ b/indra/llcommon/llthread.h
@@ -239,6 +239,101 @@ private:
LLMutexBase* mMutex;
};
+class AIRWLock
+{
+public:
+ AIRWLock(AIAPRPool& parent = LLThread::tldata().mRootPool) :
+ mWriterWaitingMutex(parent), mNoHoldersCondition(parent), mHoldersCount(0), mWriterIsWaiting(false) { }
+
+private:
+ LLMutex mWriterWaitingMutex; //!< This mutex is locked while some writer is waiting for access.
+ LLCondition mNoHoldersCondition; //!< Access control for mHoldersCount. Condition true when there are no more holders.
+ int mHoldersCount; //!< Number of readers or -1 if a writer locked this object.
+ // This is volatile because we read it outside the critical area of mWriterWaitingMutex, at [1].
+ // That means that other threads can change it while we are already in the (inlined) function rdlock.
+ // Without volatile, the following assembly would fail:
+ // register x = mWriterIsWaiting;
+ // /* some thread changes mWriterIsWaiting */
+ // if (x ...
+ // However, because the function is fuzzy to begin with (we don't mind that this race
+ // condition exists) it would work fine without volatile. So, basically it's just here
+ // out of principle ;). -- Aleric
+ bool volatile mWriterIsWaiting; //!< True when there is a writer waiting for write access.
+
+public:
+ void rdlock(bool high_priority = false)
+ {
+ // Give a writer a higher priority (kinda fuzzy).
+ if (mWriterIsWaiting && !high_priority) // [1] If there is a writer interested,
+ {
+ mWriterWaitingMutex.lock(); // [2] then give it precedence and wait here.
+ // If we get here then the writer got it's access; mHoldersCount == -1.
+ mWriterWaitingMutex.unlock();
+ }
+ mNoHoldersCondition.lock(); // [3] Get exclusive access to mHoldersCount.
+ while (mHoldersCount == -1) // [4]
+ {
+ mNoHoldersCondition.wait(); // [5] Wait till mHoldersCount is (or just was) 0.
+ }
+ ++mHoldersCount; // One more reader.
+ mNoHoldersCondition.unlock(); // Release lock on mHoldersCount.
+ }
+ void rdunlock(void)
+ {
+ mNoHoldersCondition.lock(); // Get exclusive access to mHoldersCount.
+ if (--mHoldersCount == 0) // Was this the last reader?
+ {
+ mNoHoldersCondition.signal(); // Tell waiting threads, see [5], [6] and [7].
+ }
+ mNoHoldersCondition.unlock(); // Release lock on mHoldersCount.
+ }
+ void wrlock(void)
+ {
+ mWriterWaitingMutex.lock(); // Block new readers, see [2],
+ mWriterIsWaiting = true; // from this moment on, see [1].
+ mNoHoldersCondition.lock(); // Get exclusive access to mHoldersCount.
+ while (mHoldersCount != 0) // Other readers or writers have this lock?
+ {
+ mNoHoldersCondition.wait(); // [6] Wait till mHoldersCount is (or just was) 0.
+ }
+ mWriterIsWaiting = false; // Stop checking the lock for new readers, see [1].
+ mWriterWaitingMutex.unlock(); // Release blocked readers, they will still hang at [3].
+ mHoldersCount = -1; // We are a writer now (will cause a hang at [5], see [4]).
+ mNoHoldersCondition.unlock(); // Release lock on mHolders (readers go from [3] to [5]).
+ }
+ void wrunlock(void)
+ {
+ mNoHoldersCondition.lock(); // Get exclusive access to mHoldersCount.
+ mHoldersCount = 0; // We have no writer anymore.
+ mNoHoldersCondition.signal(); // Tell waiting threads, see [5], [6] and [7].
+ mNoHoldersCondition.unlock(); // Release lock on mHoldersCount.
+ }
+ void rd2wrlock(void)
+ {
+ mNoHoldersCondition.lock(); // Get exclusive access to mHoldersCount. Blocks new readers at [3].
+ if (--mHoldersCount > 0) // Any other reads left?
+ {
+ mWriterWaitingMutex.lock(); // Block new readers, see [2],
+ mWriterIsWaiting = true; // from this moment on, see [1].
+ while (mHoldersCount != 0) // Other readers (still) have this lock?
+ {
+ mNoHoldersCondition.wait(); // [7] Wait till mHoldersCount is (or just was) 0.
+ }
+ mWriterIsWaiting = false; // Stop checking the lock for new readers, see [1].
+ mWriterWaitingMutex.unlock(); // Release blocked readers, they will still hang at [3].
+ }
+ mHoldersCount = -1; // We are a writer now (will cause a hang at [5], see [4]).
+ mNoHoldersCondition.unlock(); // Release lock on mHolders (readers go from [3] to [5]).
+ }
+ void wr2rdlock(void)
+ {
+ mNoHoldersCondition.lock(); // Get exclusive access to mHoldersCount.
+ mHoldersCount = 1; // Turn writer into a reader.
+ mNoHoldersCondition.signal(); // Tell waiting readers, see [5].
+ mNoHoldersCondition.unlock(); // Release lock on mHoldersCount.
+ }
+};
+
//============================================================================
void LLThread::lockData()
@@ -251,7 +346,6 @@ void LLThread::unlockData()
mRunCondition->unlock();
}
-
//============================================================================
// see llmemory.h for LLPointer<> definition