summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rwxr-xr-xindra/llcommon/llthread.cpp32
-rwxr-xr-xindra/llcommon/llthread.h46
-rwxr-xr-xindra/newview/llmeshrepository.cpp292
-rwxr-xr-xindra/newview/llmeshrepository.h17
4 files changed, 257 insertions, 130 deletions
diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp
index 1d56a52c32..7563975959 100755
--- a/indra/llcommon/llthread.cpp
+++ b/indra/llcommon/llthread.cpp
@@ -3,7 +3,7 @@
*
* $LicenseInfo:firstyear=2004&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2010-2013, 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
@@ -372,6 +372,36 @@ void LLMutex::lock()
#endif
}
+bool LLMutex::trylock()
+{
+ if(isSelfLocked())
+ { //redundant lock
+ mCount++;
+ return true;
+ }
+
+ apr_status_t status(apr_thread_mutex_trylock(mAPRMutexp));
+ if (APR_STATUS_IS_EBUSY(status))
+ {
+ return false;
+ }
+
+#if MUTEX_DEBUG
+ // Have to have the lock before we can access the debug info
+ U32 id = LLThread::currentID();
+ if (mIsLocked[id] != FALSE)
+ llerrs << "Already locked in Thread: " << id << llendl;
+ mIsLocked[id] = TRUE;
+#endif
+
+#if LL_DARWIN
+ mLockingThread = LLThread::currentID();
+#else
+ mLockingThread = sThreadID;
+#endif
+ return true;
+}
+
void LLMutex::unlock()
{
if (mCount > 0)
diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h
index 0fb89c5613..376df398d7 100755
--- a/indra/llcommon/llthread.h
+++ b/indra/llcommon/llthread.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2004&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2010-2013, 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
@@ -156,7 +156,8 @@ public:
virtual ~LLMutex();
void lock(); // blocks
- void unlock();
+ bool trylock(); // non-blocking, returns true if lock held.
+ void unlock(); // undefined behavior when called on mutex not being held
bool isLocked(); // non-blocking, but does do a lock/unlock so not free
bool isSelfLocked(); //return true if locked in a same thread
U32 lockingThread() const; //get ID of locking thread
@@ -174,6 +175,8 @@ protected:
#endif
};
+//============================================================================
+
// Actually a condition/mutex pair (since each condition needs to be associated with a mutex).
class LL_COMMON_API LLCondition : public LLMutex
{
@@ -189,6 +192,8 @@ protected:
apr_thread_cond_t *mAPRCondp;
};
+//============================================================================
+
class LLMutexLock
{
public:
@@ -210,6 +215,43 @@ private:
//============================================================================
+// Scoped locking class similar in function to LLMutexLock but uses
+// the trylock() method to conditionally acquire lock without
+// blocking. Caller resolves the resulting condition by calling
+// the isLocked() method and either punts or continues as indicated.
+//
+// Mostly of interest to callers needing to avoid stalls who can
+// guarantee another attempt at a later time.
+
+class LLMutexTrylock
+{
+public:
+ LLMutexTrylock(LLMutex* mutex)
+ : mMutex(mutex),
+ mLocked(false)
+ {
+ if (mMutex)
+ mLocked = mMutex->trylock();
+ }
+
+ ~LLMutexTrylock()
+ {
+ if (mMutex && mLocked)
+ mMutex->unlock();
+ }
+
+ bool isLocked() const
+ {
+ return mLocked;
+ }
+
+private:
+ LLMutex* mMutex;
+ bool mLocked;
+};
+
+//============================================================================
+
void LLThread::lockData()
{
mDataLock->lock();
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index 97d6c57a78..59100a68f9 100755
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -197,7 +197,6 @@
// sActiveHeaderRequests mMutex rw.any.mMutex, ro.repo.none [1]
// sActiveLODRequests mMutex rw.any.mMutex, ro.repo.none [1]
// sMaxConcurrentRequests mMutex wo.main.none, ro.repo.none, ro.main.mMutex
-// mWaiting mMutex rw.repo.none, ro.main.none [2] (race - hint)
// mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0]
// mMeshHeaderSize mHeaderMutex rw.repo.mHeaderMutex
// mSkinRequests none rw.repo.none, rw.main.none [0]
@@ -264,6 +263,7 @@ U32 LLMeshRepository::sCacheBytesRead = 0;
U32 LLMeshRepository::sCacheBytesWritten = 0;
U32 LLMeshRepository::sCacheReads = 0;
U32 LLMeshRepository::sCacheWrites = 0;
+U32 LLMeshRepository::sMaxLockHoldoffs = 0;
LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); // true -> gather cpu metrics
@@ -288,7 +288,7 @@ const char * const LOG_MESH = "Mesh";
// Static data and functions to measure mesh load
// time metrics for a new region scene.
-static unsigned int metrics_teleport_start_count(0);
+static unsigned int metrics_teleport_start_count = 0;
boost::signals2::connection metrics_teleport_started_signal;
static void teleport_started();
static bool is_retryable(LLCore::HttpStatus status);
@@ -396,6 +396,7 @@ S32 LLMeshRepoThread::sRequestWaterLevel = 0;
// LLMeshSkinInfoHandler
// LLMeshDecompositionHandler
// LLMeshPhysicsShapeHandler
+// LLMeshUploadThread
class LLMeshHandlerBase : public LLCore::HttpHandler
{
@@ -624,7 +625,6 @@ void log_upload_error(LLCore::HttpStatus status, const LLSD& content,
LLMeshRepoThread::LLMeshRepoThread()
: LLThread("mesh repo"),
- mWaiting(false),
mHttpRequest(NULL),
mHttpOptions(NULL),
mHttpLargeOptions(NULL),
@@ -654,6 +654,7 @@ LLMeshRepoThread::~LLMeshRepoThread()
{
LL_INFOS(LOG_MESH) << "Small GETs issued: " << LLMeshRepository::sHTTPRequestCount
<< ", Large GETs issued: " << LLMeshRepository::sHTTPLargeRequestCount
+ << ", Max Lock Holdoffs: " << LLMeshRepository::sMaxLockHoldoffs
<< LL_ENDL;
for (http_request_set::iterator iter(mHttpRequestSet.begin());
@@ -698,138 +699,171 @@ void LLMeshRepoThread::run()
while (!LLApp::isQuitting())
{
+ // *TODO: Revise sleep/wake strategy and try to move away'
+ // from polling operations in this thread. We can sleep
+ // this thread hard when:
+ // * All Http requests are serviced
+ // * LOD request queue empty
+ // * Header request queue empty
+ // * Skin info request queue empty
+ // * Decomposition request queue empty
+ // * Physics shape request queue empty
+ // We wake the thread when any of the above become untrue.
+ // Will likely need a correctly-implemented condition variable to do this.
+
+ mSignal->wait();
+
+ if (LLApp::isQuitting())
+ {
+ break;
+ }
+
if (! mHttpRequestSet.empty())
{
// Dispatch all HttpHandler notifications
mHttpRequest->update(0L);
}
+ sRequestWaterLevel = mHttpRequestSet.size(); // Stats data update
+
+ // NOTE: order of queue processing intentionally favors LOD requests over header requests
- mWaiting = true;
- mSignal->wait();
- mWaiting = false;
-
- if (! LLApp::isQuitting())
+ while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater)
{
- // NOTE: order of queue processing intentionally favors LOD requests over header requests
-
- sRequestWaterLevel = mHttpRequestSet.size();
- while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ if (! mMutex)
+ {
+ break;
+ }
+ mMutex->lock();
+ LODRequest req = mLODReqQ.front();
+ mLODReqQ.pop();
+ LLMeshRepository::sLODProcessing--;
+ mMutex->unlock();
+ if (!fetchMeshLOD(req.mMeshParams, req.mLOD))//failed, resubmit
{
- if (! mMutex)
- {
- break;
- }
mMutex->lock();
- LODRequest req = mLODReqQ.front();
- mLODReqQ.pop();
- LLMeshRepository::sLODProcessing--;
+ mLODReqQ.push(req) ;
+ ++LLMeshRepository::sLODProcessing;
mMutex->unlock();
- if (!fetchMeshLOD(req.mMeshParams, req.mLOD))//failed, resubmit
- {
- mMutex->lock();
- mLODReqQ.push(req) ;
- ++LLMeshRepository::sLODProcessing;
- mMutex->unlock();
- }
}
+ }
- while (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ while (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ {
+ if (! mMutex)
+ {
+ break;
+ }
+ mMutex->lock();
+ HeaderRequest req = mHeaderReqQ.front();
+ mHeaderReqQ.pop();
+ mMutex->unlock();
+ if (!fetchMeshHeader(req.mMeshParams))//failed, resubmit
{
- if (! mMutex)
- {
- break;
- }
mMutex->lock();
- HeaderRequest req = mHeaderReqQ.front();
- mHeaderReqQ.pop();
+ mHeaderReqQ.push(req) ;
mMutex->unlock();
- if (!fetchMeshHeader(req.mMeshParams))//failed, resubmit
- {
- mMutex->lock();
- mHeaderReqQ.push(req) ;
- mMutex->unlock();
- }
}
+ }
- // For the final three request lists, if we scan any part of one
- // list, we scan the entire thing. This gets us through any requests
- // which can be resolved in the cache. It also keeps the request
- // set somewhat fresher otherwise items at the end of the set
- // order will lose.
+ // For the final three request lists, similar goal to above but
+ // slightly different queue structures. Stay off the mutex when
+ // performing long-duration actions.
+
+ if (mHttpRequestSet.size() < sRequestHighWater
+ && (! mSkinRequests.empty()
+ || ! mDecompositionRequests.empty()
+ || ! mPhysicsShapeRequests.empty()))
+ {
+ // Something to do probably, lock and double-check. We don't want
+ // to hold the lock long here. That will stall main thread activities
+ // so we bounce it.
+
+ mMutex->lock();
if (! mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater)
{
- // *FIXME: this really does need a lock as do the following ones
std::set<LLUUID> incomplete;
- for (std::set<LLUUID>::iterator iter = mSkinRequests.begin(); iter != mSkinRequests.end(); ++iter)
+ std::set<LLUUID>::iterator iter(mSkinRequests.begin());
+ while (iter != mSkinRequests.end() && mHttpRequestSet.size() < sRequestHighWater)
{
- if (mHttpRequestSet.size() < sRequestHighWater)
- {
- LLUUID mesh_id = *iter;
- if (!fetchMeshSkinInfo(mesh_id))
- {
- incomplete.insert(mesh_id);
- }
- }
- else
+ LLUUID mesh_id = *iter;
+ mSkinRequests.erase(iter);
+ mMutex->unlock();
+
+ if (! fetchMeshSkinInfo(mesh_id))
{
- // Hit high-water mark, copy remaining to incomplete.
- incomplete.insert(iter, mSkinRequests.end());
- break;
+ incomplete.insert(mesh_id);
}
+
+ mMutex->lock();
+ iter = mSkinRequests.begin();
+ }
+
+ if (! incomplete.empty())
+ {
+ mSkinRequests.insert(incomplete.begin(), incomplete.end());
}
- mSkinRequests.swap(incomplete);
}
+ // holding lock, try next list
+ // *TODO: For UI/debug-oriented lists, we might drop the fine-
+ // grained locking as there's lowered expectations of smoothness
+ // in these cases.
if (! mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater)
{
std::set<LLUUID> incomplete;
- for (std::set<LLUUID>::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter)
+ std::set<LLUUID>::iterator iter(mDecompositionRequests.begin());
+ while (iter != mDecompositionRequests.end() && mHttpRequestSet.size() < sRequestHighWater)
{
- if (mHttpRequestSet.size() < sRequestHighWater)
- {
- LLUUID mesh_id = *iter;
- if (!fetchMeshDecomposition(mesh_id))
- {
- incomplete.insert(mesh_id);
- }
- }
- else
+ LLUUID mesh_id = *iter;
+ mDecompositionRequests.erase(iter);
+ mMutex->unlock();
+
+ if (! fetchMeshDecomposition(mesh_id))
{
- // Hit high-water mark, copy remaining to incomplete.
- incomplete.insert(iter, mDecompositionRequests.end());
- break;
+ incomplete.insert(mesh_id);
}
+
+ mMutex->lock();
+ iter = mDecompositionRequests.begin();
+ }
+
+ if (! incomplete.empty())
+ {
+ mDecompositionRequests.insert(incomplete.begin(), incomplete.end());
}
- mDecompositionRequests.swap(incomplete);
}
+ // holding lock, final list
if (! mPhysicsShapeRequests.empty() && mHttpRequestSet.size() < sRequestHighWater)
{
std::set<LLUUID> incomplete;
- for (std::set<LLUUID>::iterator iter = mPhysicsShapeRequests.begin(); iter != mPhysicsShapeRequests.end(); ++iter)
+ std::set<LLUUID>::iterator iter(mPhysicsShapeRequests.begin());
+ while (iter != mPhysicsShapeRequests.end() && mHttpRequestSet.size() < sRequestHighWater)
{
- if (mHttpRequestSet.size() < sRequestHighWater)
- {
- LLUUID mesh_id = *iter;
- if (!fetchMeshPhysicsShape(mesh_id))
- {
- incomplete.insert(mesh_id);
- }
- }
- else
+ LLUUID mesh_id = *iter;
+ mPhysicsShapeRequests.erase(iter);
+ mMutex->unlock();
+
+ if (! fetchMeshPhysicsShape(mesh_id))
{
- // Hit high-water mark, copy remaining to incomplete.
- incomplete.insert(iter, mPhysicsShapeRequests.end());
- break;
+ incomplete.insert(mesh_id);
}
+
+ mMutex->lock();
+ iter = mPhysicsShapeRequests.begin();
}
- mPhysicsShapeRequests.swap(incomplete);
- }
- // For dev purposes, a dynamic change could make this false
- // and that shouldn't assert.
- // llassert_always(mHttpRequestSet.size() <= sRequestHighWater);
+ if (! incomplete.empty())
+ {
+ mPhysicsShapeRequests.insert(incomplete.begin(), incomplete.end());
+ }
+ }
+ mMutex->unlock();
}
+
+ // For dev purposes only. A dynamic change could make this false
+ // and that shouldn't assert.
+ // llassert_always(mHttpRequestSet.size() <= sRequestHighWater);
}
if (mSignal->isLocked())
@@ -844,18 +878,21 @@ void LLMeshRepoThread::run()
}
}
+// Mutex: LLMeshRepoThread::mMutex must be held on entry
void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id)
-{ //protected by mSignal, no locking needed here
+{
mSkinRequests.insert(mesh_id);
}
+// Mutex: LLMeshRepoThread::mMutex must be held on entry
void LLMeshRepoThread::loadMeshDecomposition(const LLUUID& mesh_id)
-{ //protected by mSignal, no locking needed here
+{
mDecompositionRequests.insert(mesh_id);
}
+// Mutex: LLMeshRepoThread::mMutex must be held on entry
void LLMeshRepoThread::loadMeshPhysicsShape(const LLUUID& mesh_id)
-{ //protected by mSignal, no locking needed here
+{
mPhysicsShapeRequests.insert(mesh_id);
}
@@ -2406,13 +2443,18 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo
}
else
{
- // From texture fetch code and applies here:
+ // From texture fetch code and may apply here:
//
// A warning about partial (HTTP 206) data. Some grid services
// do *not* return a 'Content-Range' header in the response to
// Range requests with a 206 status. We're forced to assume
// we get what we asked for in these cases until we can fix
// the services.
+ //
+ // May also need to deal with 200 status (full asset returned
+ // rather than partial) and 416 (request completely unsatisfyable).
+ // Always been exposed to these but are less likely here where
+ // speculative loads aren't done.
static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT);
LLCore::BufferArray * body(response->getBody());
@@ -2422,7 +2464,9 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo
if (data_size > 0)
{
// *TODO: Try to get rid of data copying and add interfaces
- // that support BufferArray directly.
+ // that support BufferArray directly. Introduce a two-phase
+ // handler, optional first that takes a body, fallback second
+ // that requires a temporary allocation and data copy.
data = new U8[data_size];
body->read(0, (char *) data, data_size);
LLMeshRepository::sBytesReceived += data_size;
@@ -2459,6 +2503,10 @@ void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status)
{
if (is_retryable(status))
{
+ // *TODO: This and the other processFailure() methods should
+ // probably just fail hard (as llcorehttp has done the retries).
+ // Or we could implement a slow/forever retry class.
+
LL_WARNS(LOG_MESH) << "Error during mesh header handling. Reason: " << status.toString()
<< " (" << status.toHex() << "). Retrying."
<< LL_ENDL;
@@ -3026,32 +3074,40 @@ void LLMeshRepository::notifyLoadedMeshes()
//call completed callbacks on finished decompositions
mDecompThread->notifyCompleted();
-
- if (!mThread->mWaiting && mPendingRequests.empty())
- { //curl thread is churning, wait for it to go idle
- return;
- }
- static std::string region_name("never name a region this");
+ // For major operations, attempt to get the required locks
+ // without blocking and punt if they're not available.
+ {
+ LLMutexTrylock lock1(mMeshMutex);
+ LLMutexTrylock lock2(mThread->mMutex);
- if (gAgent.getRegion())
- { //update capability url
- if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived())
+ static U32 hold_offs(0);
+ if (! lock1.isLocked() || ! lock2.isLocked())
{
- region_name = gAgent.getRegion()->getName();
- mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh");
- mGetMesh2Capability = gAgent.getRegion()->getCapability("GetMesh2");
- mGetMeshVersion = mGetMesh2Capability.empty() ? 1 : 2;
- LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name
- << "', GetMesh2: " << mGetMesh2Capability
- << ", GetMesh: " << mGetMeshCapability
- << LL_ENDL;
+ // If we can't get the locks, skip and pick this up later.
+ ++hold_offs;
+ sMaxLockHoldoffs = llmax(sMaxLockHoldoffs, hold_offs);
+ return;
+ }
+ hold_offs = 0;
+
+ if (gAgent.getRegion())
+ {
+ // Update capability urls
+ static std::string region_name("never name a region this");
+
+ if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived())
+ {
+ region_name = gAgent.getRegion()->getName();
+ mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh");
+ mGetMesh2Capability = gAgent.getRegion()->getCapability("GetMesh2");
+ mGetMeshVersion = mGetMesh2Capability.empty() ? 1 : 2;
+ LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name
+ << "', GetMesh2: " << mGetMesh2Capability
+ << ", GetMesh: " << mGetMeshCapability
+ << LL_ENDL;
+ }
}
- }
-
- {
- LLMutexLock lock1(mMeshMutex);
- LLMutexLock lock2(mThread->mMutex);
//popup queued error messages from background threads
while (!mUploadErrorQ.empty())
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index 7e89f60bc3..c79278da1a 100755
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -230,8 +230,6 @@ public:
LLMutex* mHeaderMutex;
LLCondition* mSignal;
- volatile bool mWaiting;
-
//map of known mesh headers
typedef std::map<LLUUID, LLSD> mesh_header_map;
mesh_header_map mMeshHeader;
@@ -494,19 +492,20 @@ public:
//metrics
static U32 sBytesReceived;
- static U32 sMeshRequestCount;
- static U32 sHTTPRequestCount;
- static U32 sHTTPLargeRequestCount;
- static U32 sHTTPRetryCount;
- static U32 sHTTPErrorCount;
+ static U32 sMeshRequestCount; // Total request count, http or cached, all component types
+ static U32 sHTTPRequestCount; // Http GETs issued (not large)
+ static U32 sHTTPLargeRequestCount; // Http GETs issued for large requests
+ static U32 sHTTPRetryCount; // Total request retries whether successful or failed
+ static U32 sHTTPErrorCount; // Requests ending in error
static U32 sLODPending;
static U32 sLODProcessing;
static U32 sCacheBytesRead;
static U32 sCacheBytesWritten;
- static U32 sCacheReads;
+ static U32 sCacheReads;
static U32 sCacheWrites;
+ static U32 sMaxLockHoldoffs; // Maximum sequential locking failures
- static LLDeadmanTimer sQuiescentTimer; // time-to-complete-mesh-downloads after significant events
+ static LLDeadmanTimer sQuiescentTimer; // Time-to-complete-mesh-downloads after significant events
static F32 getStreamingCost(LLSD& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL);