diff options
author | Monty Brandenberg <monty@lindenlab.com> | 2013-08-15 19:00:43 -0400 |
---|---|---|
committer | Monty Brandenberg <monty@lindenlab.com> | 2013-08-15 19:00:43 -0400 |
commit | f84a8104b80eacad78b30c09cb016774e5c459c5 (patch) | |
tree | 489d0c4d4f4146a86b598fdb98fbb3ebf3376459 | |
parent | 8b78642a475d9a1095970a3be39de47117b35d9f (diff) |
SH-4410 Internal Documentation. Update and correct the mutex/data
lists to reflect current. Describe the functional flow of things
for a single LOD request. Put together to-do list for follow on
work. Knock down the low/high water limits for GetMesh a bit,
100/200 too high, 75/150 should be better, vis-a-vis pathological
failures.
-rwxr-xr-x | indra/newview/llmeshrepository.cpp | 178 |
1 files changed, 127 insertions, 51 deletions
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index b19f6281e7..7d64a9f63f 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -79,10 +79,6 @@ #include <queue> -// [ Disclaimer: this documentation isn't by one of the original authors -// but by someone coming through later and extracting intent and function. -// Some of this will be wrong so use judgement. ] -// // Purpose // // The purpose of this module is to provide access between the viewer @@ -101,6 +97,7 @@ // * getMeshHeader (For structural details, see: // http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format) // * notifyLoadedMeshes +// * getSkinInfo // // Threads // @@ -108,7 +105,54 @@ // repo Overseeing worker thread associated with the LLMeshRepoThread class // decom Worker thread for mesh decomposition requests // core HTTP worker thread: does the work but doesn't intrude here -// uploadN 0-N temporary mesh upload threads +// uploadN 0-N temporary mesh upload threads (0-1 in practice) +// +// Sequence of Operations +// +// What follows is a description of the retrieval of one LOD for +// a new mesh object. Work is performed by a series of short, quick +// actions distributed over a number of threads. Each is meant +// to proceed without stalling and the whole forms a deep request +// pipeline to achieve throughput. Ellipsis indicates a return +// or break in processing which is resumed elsewhere. +// +// main thread repo thread (run() method) +// +// loadMesh() invoked to request LOD +// append LODRequest to mPendingRequests +// ... +// other mesh requests may be made +// ... +// notifyLoadedMeshes() invoked to stage work +// append HeaderRequest to mHeaderReqQ +// ... +// scan mHeaderReqQ +// issue 4096-byte GET for header +// ... +// onCompleted() invoked for GET +// data copied +// headerReceived() invoked +// LLSD parsed +// mMeshHeader, mMeshHeaderSize updated +// scan mPendingLOD for LOD request +// push LODRequest to mLODReqQ +// ... +// scan mLODReqQ +// fetchMeshLOD() invoked +// issue Byte-Range GET for LOD +// ... +// onCompleted() invoked for GET +// data copied +// lodReceived() invoked +// unpack data into LLVolume +// append LoadedMesh to mLoadedQ +// ... +// notifyLoadedMeshes() invoked again +// scan mLoadedQ +// notifyMeshLoaded() for LOD +// setMeshAssetLoaded() invoked for system volume +// notifyMeshLoaded() invoked for each interested object +// ... // // Mutexes // @@ -163,19 +207,19 @@ // // LLMeshRepository: // -// sBytesReceived -// sMeshRequestCount -// sHTTPRequestCount -// sHTTPLargeRequestCount -// sHTTPRetryCount -// sHTTPErrorCount -// sLODPending -// sLODProcessing -// sCacheBytesRead -// sCacheBytesWritten -// sCacheReads -// sCacheWrites -// mLoadingMeshes none rw.main.none, rw.main.mMeshMutex [4] +// sBytesReceived none rw.repo.none, ro.main.none [1] +// sMeshRequestCount " +// sHTTPRequestCount " +// sHTTPLargeRequestCount " +// sHTTPRetryCount " +// sHTTPErrorCount " +// sLODPending mMeshMutex [4] rw.main.mMeshMutex +// sLODProcessing Repo::mMutex rw.any.Repo::mMutex +// sCacheBytesRead none rw.repo.none, ro.main.none [1] +// sCacheBytesWritten " +// sCacheReads " +// sCacheWrites " +// mLoadingMeshes mMeshMutex [4] rw.main.none, rw.any.mMeshMutex // mSkinMap none rw.main.none // mDecompositionMap none rw.main.none // mPendingRequests mMeshMutex [4] rw.main.mMeshMutex @@ -199,25 +243,18 @@ // sMaxConcurrentRequests mMutex wo.main.none, ro.repo.none, ro.main.mMutex // 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] -// mSkinInfoQ none rw.repo.none, rw.main.none [0] -// mDecompositionRequests none rw.repo.none, rw.main.none [0] -// mPhysicsShapeRequests none rw.repo.none, rw.main.none [0] -// mDecompositionQ none rw.repo.none, rw.main.none [0] -// mHeaderReqQ mMutex ro.repo.none [3], rw.repo.mMutex, rw.any.mMutex -// mLODReqQ mMutex ro.repo.none [3], rw.repo.mMutex, rw.any.mMutex -// mUnavailableQ mMutex rw.repo.none [0], ro.main.none [3], rw.main.mMutex -// mLoadedQ mMutex rw.repo.mMutex, ro.main.none [3], rw.main.mMutex +// mSkinRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mSkinInfoQ none rw.repo.none, rw.main.mMutex [0] +// mDecompositionRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mPhysicsShapeRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mDecompositionQ none rw.repo.none, rw.main.mMutex [0] +// mHeaderReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex +// mLODReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex +// mUnavailableQ mMutex rw.repo.none [0], ro.main.none [5], rw.main.mMutex +// mLoadedQ mMutex rw.repo.mMutex, ro.main.none [5], rw.main.mMutex // mPendingLOD mMutex rw.repo.mMutex, rw.any.mMutex // mHttp* none rw.repo.none // -// LLPhysicsDecomp: -// -// mRequestQ -// mCurRequest -// mCompletedQ -// -// // QA/Development Testing // // Debug variable 'MeshUploadFakeErrors' takes a mask of bits that will @@ -230,15 +267,27 @@ // locally-generated 500 status. // 0x08 As with 0x04 but for the upload operation. // +// *TODO: Work list for followup actions: +// * Review anything marked as unsafe above, verify if there are real issues. +// * See if we can put ::run() into a hard sleep. May not actually perform better +// than the current scheme so be prepared for disappointment. You'll likely +// need to introduce a condition variable class that references a mutex in +// methods rather than derives from mutex which isn't correct. +// * On upload failures, make more information available to the alerting +// dialog. Get the structured information going into the log into a +// tree there. +// * Header parse failures come without much explanation. Elaborate. +// * Need a final failure state for requests that are retried and just won't +// complete. We can fail a LOD request, others we don't. LLMeshRepository gMeshRepo; const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space -const S32 REQUEST_HIGH_WATER_MIN = 32; -const S32 REQUEST_HIGH_WATER_MAX = 200; +const S32 REQUEST_HIGH_WATER_MIN = 32; // Limits for GetMesh regions +const S32 REQUEST_HIGH_WATER_MAX = 150; // Should remain under 2X throttle const S32 REQUEST_LOW_WATER_MIN = 16; -const S32 REQUEST_LOW_WATER_MAX = 100; -const S32 REQUEST2_HIGH_WATER_MIN = 32; +const S32 REQUEST_LOW_WATER_MAX = 75; +const S32 REQUEST2_HIGH_WATER_MIN = 32; // Limits for GetMesh2 regions const S32 REQUEST2_HIGH_WATER_MAX = 80; const S32 REQUEST2_LOW_WATER_MIN = 16; const S32 REQUEST2_LOW_WATER_MAX = 40; @@ -269,7 +318,7 @@ U32 LLMeshRepository::sCacheReads = 0; U32 LLMeshRepository::sCacheWrites = 0; U32 LLMeshRepository::sMaxLockHoldoffs = 0; -LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); // true -> gather cpu metrics +LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, false); // true -> gather cpu metrics static S32 dump_num = 0; @@ -703,7 +752,7 @@ void LLMeshRepoThread::run() while (!LLApp::isQuitting()) { - // *TODO: Revise sleep/wake strategy and try to move away' + // *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 @@ -714,7 +763,8 @@ void LLMeshRepoThread::run() // * 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. - + // On the other hand, this may actually be an effective and efficient scheme... + mSignal->wait(); if (LLApp::isQuitting()) @@ -810,7 +860,7 @@ void LLMeshRepoThread::run() // 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 + // grained locking as there's a lowered expectation of smoothness // in these cases. if (! mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) { @@ -2303,24 +2353,26 @@ void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResp void LLMeshRepoThread::notifyLoadedMeshes() { + bool update_metrics(false); + if (!mMutex) { return; } - if (!mLoadedQ.empty() || !mUnavailableQ.empty()) - { - // Ping time-to-load metrics for mesh download operations. - LLMeshRepository::metricsProgress(0); - } - while (!mLoadedQ.empty()) { mMutex->lock(); + if (mLoadedQ.empty()) + { + mMutex->unlock(); + break; + } LoadedMesh mesh = mLoadedQ.front(); mLoadedQ.pop(); mMutex->unlock(); + update_metrics = true; if (mesh.mVolume && mesh.mVolume->getNumVolumeFaces() > 0) { gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume); @@ -2335,10 +2387,17 @@ void LLMeshRepoThread::notifyLoadedMeshes() while (!mUnavailableQ.empty()) { mMutex->lock(); + if (mUnavailableQ.empty()) + { + mMutex->unlock(); + break; + } + LODRequest req = mUnavailableQ.front(); mUnavailableQ.pop(); mMutex->unlock(); - + + update_metrics = true; gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD); } @@ -2353,6 +2412,13 @@ void LLMeshRepoThread::notifyLoadedMeshes() gMeshRepo.notifyDecompositionReceived(mDecompositionQ.front()); mDecompositionQ.pop(); } + + if (update_metrics) + { + // Ping time-to-load metrics for mesh download operations. + LLMeshRepository::metricsProgress(0); + } + } S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) @@ -2461,6 +2527,12 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo // speculative loads aren't done. static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); + if (par_status != status) + { + LL_WARNS_ONCE(LOG_MESH) << "Non-206 successful status received for fetch: " + << status.toHex() << LL_ENDL; + } + LLCore::BufferArray * body(response->getBody()); S32 data_size(body ? body->size() : 0); U8 * data(NULL); @@ -2995,7 +3067,8 @@ void LLMeshRepository::notifyLoadedMeshes() } else { - // GetMesh2 operation with keepalives, etc. + // GetMesh2 operation with keepalives, etc. With pipelining, + // we'll increase this. LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("Mesh2MaxConcurrentRequests"); LLMeshRepoThread::sRequestHighWater = llclamp(5 * S32(LLMeshRepoThread::sMaxConcurrentRequests), REQUEST2_HIGH_WATER_MIN, @@ -3083,7 +3156,10 @@ void LLMeshRepository::notifyLoadedMeshes() mDecompThread->notifyCompleted(); // For major operations, attempt to get the required locks - // without blocking and punt if they're not available. + // without blocking and punt if they're not available. The + // longest run of holdoffs is kept in sMaxLockHoldoffs just + // to collect the data. In testing, I've never seen a value + // greater than 2 (written to log on exit). { LLMutexTrylock lock1(mMeshMutex); LLMutexTrylock lock2(mThread->mMutex); |