Merge remote-tracking branch 'aleric/V2MultiWear' into AltCompilers
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -121,6 +121,7 @@ set(llcommon_HEADER_FILES
|
||||
llaprpool.h
|
||||
llassettype.h
|
||||
llassoclist.h
|
||||
llatomic.h
|
||||
llavatarconstants.h
|
||||
llbase32.h
|
||||
llbase64.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> 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> foo; // Default constructed Foo.
|
||||
// AIThreadSafeDC<Foo> foo(3.4); // Foo with one constructor parameter.
|
||||
//
|
||||
#ifndef AITHREADSAFE_H
|
||||
#define AITHREADSAFE_H
|
||||
|
||||
@@ -49,15 +106,10 @@
|
||||
template<typename T> struct AIReadAccessConst;
|
||||
template<typename T> struct AIReadAccess;
|
||||
template<typename T> struct AIWriteAccess;
|
||||
template<typename T> struct AIAccessConst;
|
||||
template<typename T> struct AIAccess;
|
||||
|
||||
#if LL_WINDOWS
|
||||
template<typename T> class AIThreadSafeBits;
|
||||
template<typename T>
|
||||
struct AIThreadSafeWindowsHack {
|
||||
AIThreadSafeWindowsHack(AIThreadSafeBits<T>& var, T* object);
|
||||
};
|
||||
#endif
|
||||
template<typename T> struct AISTAccessConst;
|
||||
template<typename T> struct AISTAccess;
|
||||
|
||||
template<typename T>
|
||||
class AIThreadSafeBits
|
||||
@@ -80,10 +132,15 @@ public:
|
||||
// Only for use by AITHREADSAFE, see below.
|
||||
void* memory() const { return const_cast<long*>(&mMemory[0]); }
|
||||
|
||||
// Cast a T* back to AIThreadSafeBits<T>. This is the inverse of memory().
|
||||
template<typename T2>
|
||||
static AIThreadSafeBits<T2>* wrapper_cast(T2* ptr)
|
||||
{ return reinterpret_cast<AIThreadSafeBits<T2>*>(reinterpret_cast<char*>(ptr) - offsetof(AIThreadSafeBits<T2>, mMemory[0])); }
|
||||
template<typename T2>
|
||||
static AIThreadSafeBits<T2> const* wrapper_cast(T2 const* ptr)
|
||||
{ return reinterpret_cast<AIThreadSafeBits<T2> const*>(reinterpret_cast<char const*>(ptr) - offsetof(AIThreadSafeBits<T2>, mMemory[0])); }
|
||||
|
||||
protected:
|
||||
#if LL_WINDOWS
|
||||
template<typename T2> friend struct AIThreadSafeWindowsHack;
|
||||
#endif
|
||||
// Accessors.
|
||||
T const* ptr() const { return reinterpret_cast<T const*>(mMemory); }
|
||||
T* ptr() { return reinterpret_cast<T*>(mMemory); }
|
||||
@@ -185,6 +242,14 @@ public:
|
||||
llassert(object == AIThreadSafeBits<T>::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
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -194,48 +259,39 @@ public:
|
||||
*
|
||||
* <code>
|
||||
* Foo foo(x, y);
|
||||
* static Bar bar;
|
||||
* static Bar bar(0.1, true);
|
||||
* </code>
|
||||
*
|
||||
* One can instantiate a thread-safe instance with
|
||||
*
|
||||
* <code>
|
||||
* AITHREADSAFE(Foo, foo, (x, y));
|
||||
* static AITHREADSAFE(Bar, bar, );
|
||||
* static AITHREADSAFE(Bar, bar, (0.1, true));
|
||||
* </code>
|
||||
*
|
||||
* 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<typename T>
|
||||
AIThreadSafeWindowsHack<T>::AIThreadSafeWindowsHack(AIThreadSafeBits<T>& var, T* object)
|
||||
{
|
||||
llassert(object == var.ptr());
|
||||
}
|
||||
#define AITHREADSAFE(type, var, paramlist) \
|
||||
AIThreadSafe<type> var(NULL); \
|
||||
AIThreadSafeWindowsHack<type> dummy_##var(var, new (var.memory()) type paramlist)
|
||||
#else
|
||||
#define AITHREADSAFE(type, var, paramlist) AIThreadSafe<type> 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
|
||||
*
|
||||
* <code>
|
||||
* Foo foo;
|
||||
* Bar bar(3);
|
||||
* </code>
|
||||
*
|
||||
* One would use
|
||||
*
|
||||
* <code>
|
||||
* AIThreadSafeDC<Foo> foo;
|
||||
* AIThreadSafeDC<Bar> bar(3);
|
||||
* </code>
|
||||
*
|
||||
* The advantage over AITHREADSAFE is that this object can be allocated with
|
||||
@@ -253,6 +309,8 @@ class AIThreadSafeDC : public AIThreadSafe<T>
|
||||
public:
|
||||
// Construct a wrapper around a default constructed object.
|
||||
AIThreadSafeDC(void) { new (AIThreadSafe<T>::ptr()) T; }
|
||||
// Allow an arbitrary parameter to be passed for construction.
|
||||
template<typename T2> AIThreadSafeDC(T2 const& val) { new (AIThreadSafe<T>::ptr()) T(val); }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -390,6 +448,7 @@ struct AIWriteAccess : public AIReadAccess<T>
|
||||
*
|
||||
* AIAccess<Foo> foo_w(foo);
|
||||
* // Use foo_w-> for read and write access.
|
||||
* </code>
|
||||
*
|
||||
* See also AIThreadSafe
|
||||
*/
|
||||
@@ -398,6 +457,7 @@ class AIThreadSafeSimple : public AIThreadSafeBits<T>
|
||||
{
|
||||
protected:
|
||||
// Only this one may access the object (through ptr()).
|
||||
friend struct AIAccessConst<T>;
|
||||
friend struct AIAccess<T>;
|
||||
|
||||
// Locking control.
|
||||
@@ -411,6 +471,14 @@ protected:
|
||||
public:
|
||||
// Only for use by AITHREADSAFESIMPLE, see below.
|
||||
AIThreadSafeSimple(T* object) { llassert(object == AIThreadSafeBits<T>::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
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -420,14 +488,14 @@ public:
|
||||
*
|
||||
* <code>
|
||||
* Foo foo(x, y);
|
||||
* static Bar bar;
|
||||
* static Bar bar(0.1, true);
|
||||
* </code>
|
||||
*
|
||||
* One can instantiate a thread-safe instance with
|
||||
*
|
||||
* <code>
|
||||
* AITHREADSAFESIMPLE(Foo, foo, (x, y));
|
||||
* static AITHREADSAFESIMPLE(Bar, bar, );
|
||||
* static AITHREADSAFESIMPLE(Bar, bar, (0.1, true));
|
||||
* </code>
|
||||
*
|
||||
* Note: This macro does not allow to allocate such object on the heap.
|
||||
@@ -439,18 +507,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
|
||||
*
|
||||
* <code>
|
||||
* Foo foo;
|
||||
* Bar bar(0.1);
|
||||
* </code>
|
||||
*
|
||||
* One would use
|
||||
*
|
||||
* <code>
|
||||
* AIThreadSafeSimpleDC<Foo> foo;
|
||||
* AIThreadSafeSimpleDC<Bar> bar(0.1);
|
||||
* </code>
|
||||
*
|
||||
* The advantage over AITHREADSAFESIMPLE is that this object can be allocated with
|
||||
@@ -468,10 +538,12 @@ class AIThreadSafeSimpleDC : public AIThreadSafeSimple<T>
|
||||
public:
|
||||
// Construct a wrapper around a default constructed object.
|
||||
AIThreadSafeSimpleDC(void) { new (AIThreadSafeSimple<T>::ptr()) T; }
|
||||
// Allow an arbitrary parameter to be passed for construction.
|
||||
template<typename T2> AIThreadSafeSimpleDC(T2 const& val) { new (AIThreadSafeSimple<T>::ptr()) T(val); }
|
||||
|
||||
protected:
|
||||
// For use by AIThreadSafeSimpleDCRootPool
|
||||
AIThreadSafeSimpleDC(LLAPRPool& parent) : AIThreadSafeSimple<T>(parent) { new (AIThreadSafeSimple<T>::ptr()) T; }
|
||||
AIThreadSafeSimpleDC(LLAPRRootPool& parent) : AIThreadSafeSimple<T>(parent) { new (AIThreadSafeSimple<T>::ptr()) T; }
|
||||
};
|
||||
|
||||
// Helper class for AIThreadSafeSimpleDCRootPool to assure initialization of
|
||||
@@ -509,13 +581,13 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Write lock object and provide read/write access.
|
||||
* @brief Write lock object and provide read access.
|
||||
*/
|
||||
template<typename T>
|
||||
struct AIAccess
|
||||
struct AIAccessConst
|
||||
{
|
||||
//! Construct a AIAccess from a non-constant AIThreadSafeSimple.
|
||||
AIAccess(AIThreadSafeSimple<T>& wrapper) : mWrapper(wrapper)
|
||||
//! Construct a AIAccessConst from a constant AIThreadSafeSimple.
|
||||
AIAccessConst(AIThreadSafeSimple<T> const& wrapper) : mWrapper(const_cast<AIThreadSafeSimple<T>&>(wrapper))
|
||||
#if AI_NEED_ACCESS_CC
|
||||
, mIsCopyConstructed(false)
|
||||
#endif
|
||||
@@ -524,12 +596,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 +610,229 @@ struct AIAccess
|
||||
}
|
||||
|
||||
protected:
|
||||
AIThreadSafeSimple<T>& mWrapper; //!< Reference to the object that we provide access to.
|
||||
AIThreadSafeSimple<T>& 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<typename T>
|
||||
struct AIAccess : public AIAccessConst<T>
|
||||
{
|
||||
//! Construct a AIAccess from a non-constant AIThreadSafeSimple.
|
||||
AIAccess(AIThreadSafeSimple<T>& wrapper) : AIAccessConst<T>(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,
|
||||
*
|
||||
* <code>
|
||||
* class Foo { public: Foo(int, int); };
|
||||
*
|
||||
* AITHREADSAFESINGLETHREAD(Foo, foo, (2, 3));
|
||||
*
|
||||
* AISTAccess<Foo> foo_w(foo);
|
||||
* // Use foo_w-> for read and write access.
|
||||
* </code>
|
||||
*/
|
||||
template<typename T>
|
||||
class AIThreadSafeSingleThread : public AIThreadSafeBits<T>
|
||||
{
|
||||
protected:
|
||||
// Only these one may access the object (through ptr()).
|
||||
friend struct AISTAccessConst<T>;
|
||||
friend struct AISTAccess<T>;
|
||||
|
||||
// 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<T>::ptr());
|
||||
}
|
||||
|
||||
private:
|
||||
// Disallow copying or assignments.
|
||||
AIThreadSafeSingleThread(AIThreadSafeSingleThread const&);
|
||||
void operator=(AIThreadSafeSingleThread const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @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, or constructed with one parameter.
|
||||
*
|
||||
* For example, instead of
|
||||
*
|
||||
* <code>
|
||||
* Foo foo;
|
||||
* Bar bar(0.1);
|
||||
* </code>
|
||||
*
|
||||
* One would use
|
||||
*
|
||||
* <code>
|
||||
* AIThreadSafeSingleThreadDC<Foo> foo;
|
||||
* AIThreadSafeSingleThreadDC<Bar> bar(0.1);
|
||||
* </code>
|
||||
*
|
||||
* The advantage over AITHREADSAFESINGLETHREAD is that this object can be allocated with
|
||||
* new on the heap. For example:
|
||||
*
|
||||
* <code>
|
||||
* AIThreadSafeSingleThreadDC<Foo>* ptr = new AIThreadSafeSingleThreadDC<Foo>;
|
||||
* </code>
|
||||
*
|
||||
* 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<typename T>
|
||||
class AIThreadSafeSingleThreadDC : public AIThreadSafeSingleThread<T>
|
||||
{
|
||||
public:
|
||||
// Construct a wrapper around a default constructed object.
|
||||
AIThreadSafeSingleThreadDC(void) { new (AIThreadSafeSingleThread<T>::ptr()) T; }
|
||||
// Allow an arbitrary parameter to be passed for construction.
|
||||
template<typename T2> AIThreadSafeSingleThreadDC(T2 const& val) { new (AIThreadSafeSingleThread<T>::ptr()) T(val); }
|
||||
|
||||
// Allow assigning with T.
|
||||
AIThreadSafeSingleThreadDC& operator=(T const& val) { AIThreadSafeSingleThread<T>::accessed(); *AIThreadSafeSingleThread<T>::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<T>::accessed(); return *AIThreadSafeSingleThread<T>::ptr(); }
|
||||
operator T const&(void) const { AIThreadSafeSingleThread<T>::accessed(); return *AIThreadSafeSingleThread<T>::ptr(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Instantiate a static, global or local object of a given type wrapped in AIThreadSafeSingleThread, using an arbitrary constructor.
|
||||
*
|
||||
* For example, instead of doing
|
||||
*
|
||||
* <code>
|
||||
* Foo foo(x, y);
|
||||
* static Bar bar;
|
||||
* </code>
|
||||
*
|
||||
* One can instantiate a thread-safe instance with
|
||||
*
|
||||
* <code>
|
||||
* AITHREADSAFESINGLETHREAD(Foo, foo, (x, y));
|
||||
* static AITHREADSAFESINGLETHREAD(Bar, bar, );
|
||||
* </code>
|
||||
*
|
||||
* 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<type> var(new (var.memory()) type paramlist)
|
||||
|
||||
/**
|
||||
* @brief Access single threaded object for read access.
|
||||
*/
|
||||
template<typename T>
|
||||
struct AISTAccessConst
|
||||
{
|
||||
//! Construct a AISTAccessConst from a constant AIThreadSafeSingleThread.
|
||||
AISTAccessConst(AIThreadSafeSingleThread<T> const& wrapper) : mWrapper(const_cast<AIThreadSafeSingleThread<T>&>(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<T>& 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<typename T>
|
||||
struct AISTAccess : public AISTAccessConst<T>
|
||||
{
|
||||
//! Construct a AISTAccess from a non-constant AIThreadSafeSingleThread.
|
||||
AISTAccess(AIThreadSafeSingleThread<T>& wrapper) : AISTAccessConst<T>(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
|
||||
|
||||
@@ -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 ;
|
||||
|
||||
@@ -91,29 +91,8 @@ protected:
|
||||
apr_thread_mutex_t* mMutex;
|
||||
};
|
||||
|
||||
template <typename Type> class LLAtomic32
|
||||
{
|
||||
public:
|
||||
LLAtomic32<Type>() {};
|
||||
LLAtomic32<Type>(Type x) {apr_atomic_set32(&mData, apr_uint32_t(x)); };
|
||||
~LLAtomic32<Type>() {};
|
||||
|
||||
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<U32> LLAtomicU32;
|
||||
typedef LLAtomic32<S32> 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 +110,7 @@ typedef LLAtomic32<S32> 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 +168,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 <code>true</code> if status is an error condition.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @file LLAPRPool.cpp
|
||||
* @file llaprpool.cpp
|
||||
*
|
||||
* Copyright (c) 2010, Aleric Inglewood.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @file LLAPRPool.h
|
||||
* @file llaprpool.h
|
||||
* @brief Implementation of LLAPRPool.
|
||||
*
|
||||
* Copyright (c) 2010, Aleric Inglewood.
|
||||
@@ -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.
|
||||
|
||||
61
indra/llcommon/llatomic.h
Normal file
61
indra/llcommon/llatomic.h
Normal file
@@ -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 <typename Type> class LLAtomic32
|
||||
{
|
||||
public:
|
||||
LLAtomic32(void) { }
|
||||
LLAtomic32(LLAtomic32 const& atom) { apr_uint32_t data = apr_atomic_read32(const_cast<apr_uint32_t*>(&atom.mData)); apr_atomic_set32(&mData, data); }
|
||||
LLAtomic32(Type x) { apr_atomic_set32(&mData, static_cast<apr_uint32_t>(x)); }
|
||||
LLAtomic32& operator=(LLAtomic32 const& atom) { apr_uint32_t data = apr_atomic_read32(const_cast<apr_uint32_t*>(&atom.mData)); apr_atomic_set32(&mData, data); return *this; }
|
||||
|
||||
operator Type() const { apr_uint32_t data = apr_atomic_read32(const_cast<apr_uint32_t*>(&mData)); return static_cast<Type>(data); }
|
||||
void operator=(Type x) { apr_atomic_set32(&mData, static_cast<apr_uint32_t>(x)); }
|
||||
void operator-=(Type x) { apr_atomic_sub32(&mData, static_cast<apr_uint32_t>(x)); }
|
||||
void operator+=(Type x) { apr_atomic_add32(&mData, static_cast<apr_uint32_t>(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<U32> LLAtomicU32;
|
||||
typedef LLAtomic32<S32> LLAtomicS32;
|
||||
|
||||
#endif
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class LLSD;
|
||||
class LLOptionInterface
|
||||
{
|
||||
public:
|
||||
virtual ~LLOptionInterface() = 0;
|
||||
virtual ~LLOptionInterface();
|
||||
virtual LLSD getOption(const std::string& name) const = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -178,7 +183,7 @@ void LLThread::shutdown()
|
||||
}
|
||||
mAPRThreadp = NULL;
|
||||
}
|
||||
sCount--;
|
||||
--sCount;
|
||||
delete mRunCondition;
|
||||
mRunCondition = 0;
|
||||
}
|
||||
@@ -402,6 +407,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
|
||||
@@ -435,37 +449,6 @@ void LLMutexBase::unlock()
|
||||
apr_thread_mutex_unlock(mAPRMutexp);
|
||||
}
|
||||
|
||||
bool LLMutexBase::isSelfLocked()
|
||||
{
|
||||
#if LL_DARWIN
|
||||
return mLockingThread == LLThread::currentID();
|
||||
#else
|
||||
return mLockingThread == local_thread_ID;
|
||||
#endif
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
//static
|
||||
LLMutex* LLThreadSafeRefCount::sMutex = 0;
|
||||
|
||||
//static
|
||||
void LLThreadSafeRefCount::initThreadSafeRefCount()
|
||||
{
|
||||
if (!sMutex)
|
||||
{
|
||||
sMutex = new LLMutex;
|
||||
}
|
||||
}
|
||||
|
||||
//static
|
||||
void LLThreadSafeRefCount::cleanupThreadSafeRefCount()
|
||||
{
|
||||
delete sMutex;
|
||||
sMutex = NULL;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
LLThreadSafeRefCount::LLThreadSafeRefCount() :
|
||||
|
||||
@@ -38,9 +38,13 @@
|
||||
#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);
|
||||
#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;
|
||||
@@ -74,7 +78,7 @@ class LL_COMMON_API LLThread
|
||||
private:
|
||||
static U32 sIDIter;
|
||||
static LLAtomicS32 sCount;
|
||||
|
||||
|
||||
public:
|
||||
typedef enum e_thread_status
|
||||
{
|
||||
@@ -181,15 +185,18 @@ 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
|
||||
bool isSelfLocked(); //return true if locked in a same thread
|
||||
U32 lockingThread() const { return mLockingThread; }
|
||||
|
||||
protected:
|
||||
@@ -394,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
|
||||
@@ -413,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;
|
||||
};
|
||||
|
||||
//============================================================================
|
||||
|
||||
@@ -269,7 +269,7 @@ U8* LLImageBase::allocateDataSize(S32 width, S32 height, S32 ncomponents, S32 si
|
||||
// LLImageRaw
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
AITHREADSAFESIMPLE(S32, LLImageRaw::sGlobalRawMemory, );
|
||||
AIThreadSafeSimpleDC<S32> LLImageRaw::sGlobalRawMemory;
|
||||
S32 LLImageRaw::sRawImageCount = 0;
|
||||
S32 LLImageRaw::sRawImageCachedCount = 0;
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@ protected:
|
||||
void setDataAndSize(U8 *data, S32 width, S32 height, S8 components) ;
|
||||
|
||||
public:
|
||||
static AIThreadSafeSimple<S32> sGlobalRawMemory;
|
||||
static AIThreadSafeSimpleDC<S32> sGlobalRawMemory;
|
||||
static S32 sRawImageCount;
|
||||
|
||||
static S32 sRawImageCachedCount;
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<LLUUID,LLTempAssetData> 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1400,13 +1400,13 @@ void LLVertexBuffer::setupVertexArray()
|
||||
//glVertexattribIPointer requires GLSL 1.30 or later
|
||||
if (gGLManager.mGLSLVersionMajor > 1 || gGLManager.mGLSLVersionMinor >= 30)
|
||||
{
|
||||
glVertexAttribIPointer(i, attrib_size[i], attrib_type[i], sTypeSize[i], (void*) mOffsets[i]);
|
||||
glVertexAttribIPointer(i, attrib_size[i], attrib_type[i], sTypeSize[i], reinterpret_cast<void*>(mOffsets[i]));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
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<void*>(mOffsets[i]));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#endif
|
||||
|
||||
#include "linden_common.h"
|
||||
#include "llaprpool.h"
|
||||
|
||||
extern "C" {
|
||||
#include <dbus/dbus-glib.h>
|
||||
@@ -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,18 +75,18 @@ 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
|
||||
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());
|
||||
@@ -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"
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
BOOL gHackGodmode = FALSE;
|
||||
#endif
|
||||
|
||||
AITHREADSAFE(settings_map_type, gSettings,);
|
||||
AIThreadSafeDC<settings_map_type> 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
|
||||
|
||||
@@ -51,7 +51,7 @@ extern BOOL gHackGodmode;
|
||||
void settings_setup_listeners();
|
||||
|
||||
typedef std::map<std::string, LLControlGroup*> settings_map_type;
|
||||
extern AIThreadSafe<settings_map_type> gSettings;
|
||||
extern AIThreadSafeDC<settings_map_type> gSettings;
|
||||
|
||||
// for the graphics settings
|
||||
void create_graphics_group(LLControlGroup& group);
|
||||
|
||||
@@ -64,22 +64,8 @@ inline void AIRegisteredStateMachines::trigger(void)
|
||||
}
|
||||
|
||||
// A list (array) with all AIRegisteredStateMachines maps, one for each event type.
|
||||
struct AIRegisteredStateMachinesList {
|
||||
AIThreadSafeSimple<AIRegisteredStateMachines> mRegisteredStateMachinesList[AIEvent::number_of_events];
|
||||
AIRegisteredStateMachinesList(void);
|
||||
AIThreadSafeSimple<AIRegisteredStateMachines>& 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<AIRegisteredStateMachines> registered_statemachines_list[AIEvent::number_of_events];
|
||||
typedef AIAccess<AIRegisteredStateMachines> 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<AIRegisteredStateMachines> 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<AIRegisteredStateMachines> 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<AIRegisteredStateMachines> registered_statemachines_w(registered_statemachines_list[event]);
|
||||
registered_statemachines_wat registered_statemachines_w(registered_statemachines_list[event]);
|
||||
registered_statemachines_w->trigger();
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ AIFilePicker::AIFilePicker(void) : mPluginManager(NULL), mAutoKill(false), mCanc
|
||||
}
|
||||
|
||||
// static
|
||||
AITHREADSAFESIMPLE(AIFilePicker::context_map_type, AIFilePicker::sContextMap, );
|
||||
AIThreadSafeSimpleDC<AIFilePicker::context_map_type> AIFilePicker::sContextMap;
|
||||
|
||||
// static
|
||||
void AIFilePicker::store_folder(std::string const& context, std::string const& folder)
|
||||
|
||||
@@ -185,7 +185,7 @@ public:
|
||||
private:
|
||||
LLPointer<LLViewerPluginManager> mPluginManager; //!< Pointer to the plugin manager.
|
||||
typedef std::map<std::string, std::string> context_map_type; //!< Type of mContextMap.
|
||||
static AIThreadSafeSimple<context_map_type> sContextMap; //!< Map context (ie, "snapshot" or "image") to last used folder.
|
||||
static AIThreadSafeSimpleDC<context_map_type> 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.
|
||||
|
||||
|
||||
@@ -65,18 +65,18 @@ namespace {
|
||||
};
|
||||
|
||||
typedef std::vector<QueueElement> active_statemachines_type;
|
||||
static active_statemachines_type active_statemachines;
|
||||
active_statemachines_type active_statemachines;
|
||||
typedef std::vector<AIStateMachine*> continued_statemachines_type;
|
||||
struct cscm_type
|
||||
{
|
||||
continued_statemachines_type continued_statemachines;
|
||||
bool calling_mainloop;
|
||||
};
|
||||
static AITHREADSAFE(cscm_type, continued_statemachines_and_calling_mainloop, );
|
||||
AIThreadSafeDC<cscm_type> continued_statemachines_and_calling_mainloop;
|
||||
}
|
||||
|
||||
// static
|
||||
AITHREADSAFESIMPLE(U64, AIStateMachine::sMaxCount, );
|
||||
AIThreadSafeSimpleDC<U64> AIStateMachine::sMaxCount;
|
||||
|
||||
void AIStateMachine::updateSettings(void)
|
||||
{
|
||||
@@ -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_type> 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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
@@ -224,7 +225,7 @@ class AIStateMachine {
|
||||
};
|
||||
callback_type* mCallback; //!< Pointer to signal/connection, or NULL when not connected.
|
||||
|
||||
static AIThreadSafeSimple<U64> sMaxCount; //!< Number of cpu clocks below which we start a new state machine within the same frame.
|
||||
static AIThreadSafeSimpleDC<U64> 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.
|
||||
|
||||
Reference in New Issue
Block a user