summaryrefslogtreecommitdiff
path: root/indra/newview/llmeshrepository.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llmeshrepository.cpp')
-rwxr-xr-xindra/newview/llmeshrepository.cpp1562
1 files changed, 1028 insertions, 534 deletions
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index 8d3539d297..2d003dd6d7 100755
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -5,7 +5,7 @@
*
* $LicenseInfo:firstyear=2005&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
@@ -38,6 +38,7 @@
#include "llcallbacklist.h"
#include "llcurl.h"
#include "lldatapacker.h"
+#include "lldeadmantimer.h"
#include "llfloatermodelpreview.h"
#include "llfloaterperms.h"
#include "lleconomy.h"
@@ -52,6 +53,7 @@
#include "llviewercontrol.h"
#include "llviewerinventory.h"
#include "llviewermenufile.h"
+#include "llviewermessage.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "llviewertexturelist.h"
@@ -65,6 +67,7 @@
#include "llfoldertype.h"
#include "llviewerparcelmgr.h"
#include "lluploadfloaterobservers.h"
+#include "bufferarray.h"
#include "boost/lexical_cast.hpp"
@@ -74,9 +77,151 @@
#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
+// and the asset system as regards to mesh objects.
+//
+// * High-throughput download of mesh assets from servers while
+// following best industry practices for network profile.
+// * Reliable expensing and upload of new mesh assets.
+// * Recovery and retry from errors when appropriate.
+// * Decomposition of mesh assets for preview and uploads.
+// * And most important: all of the above without exposing the
+// main thread to stalls due to deep processing or thread
+// locking actions. In particular, the following operations
+// on LLMeshRepository are very averse to any stalls:
+// * loadMesh
+// * getMeshHeader (For structural details, see:
+// http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format)
+// * notifyLoadedMeshes
+//
+// Threads
+//
+// main Main rendering thread, very sensitive to locking and other stalls
+// 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
+//
+// Mutexes
+//
+// LLMeshRepository::mMeshMutex
+// LLMeshRepoThread::mMutex
+// LLMeshRepoThread::mHeaderMutex
+// LLMeshRepoThread::mSignal (LLCondition)
+// LLPhysicsDecomp::mSignal (LLCondition)
+// LLPhysicsDecomp::mMutex
+// LLMeshUploadThread::mMutex
+//
+// Mutex Order Rules
+//
+// 1. LLMeshRepoThread::mMutex before LLMeshRepoThread::mHeaderMutex
+// 2. LLMeshRepository::mMeshMutex before LLMeshRepoThread::mMutex
+// (There are more rules, haven't been extracted.)
+//
+// Data Member Access/Locking
+//
+// Description of how shared access to static and instance data
+// members is performed. Each member is followed by the name of
+// the mutex, if any, covering the data and then a list of data
+// access models each of which is a triplet of the following form:
+//
+// {ro, wo, rw}.{main, repo, any}.{mutex, none}
+// Type of access: read-only, write-only, read-write.
+// Accessing thread or 'any'
+// Relevant mutex held during access (several may be held) or 'none'
+//
+// A careful eye will notice some unsafe operations. Many of these
+// have an alibi of some form. Several types of alibi are identified
+// and listed here:
+//
+// [0] No alibi. Probably unsafe.
+// [1] Single-writer, self-consistent readers. Old data must
+// be tolerated by any reader but data will come true eventually.
+// [2] Like [1] but provides a hint about thread state. These
+// may be unsafe.
+// [3] empty() check outside of lock. Can me made safish when
+// done in double-check lock style. But this depends on
+// std:: implementation and memory model.
+// [4] Appears to be covered by a mutex but doesn't need one.
+// [5] Read of a double-checked lock.
+//
+// So, in addition to documentation, take this as a to-do/review
+// list and see if you can improve things. For porters to non-x86
+// architectures, including amd64, the weaker memory models will
+// make these platforms probabilistically more susceptible to hitting
+// race conditions. True here and in other multi-thread code such
+// as texture fetching.
+//
+// LLMeshRepository:
+//
+// sBytesReceived
+// sHTTPRequestCount
+// sHTTPRetryCount
+// sLODPending
+// sLODProcessing
+// sCacheBytesRead
+// sCacheBytesWritten
+// mLoadingMeshes none rw.main.none, rw.main.mMeshMutex [4]
+// mSkinMap none rw.main.none
+// mDecompositionMap none rw.main.none
+// mPendingRequests mMeshMutex [4] rw.main.mMeshMutex
+// mLoadingSkins mMeshMutex [4] rw.main.mMeshMutex
+// mPendingSkinRequests mMeshMutex [4] rw.main.mMeshMutex
+// mLoadingDecompositions mMeshMutex [4] rw.main.mMeshMutex
+// mPendingDecompositionRequests mMeshMutex [4] rw.main.mMeshMutex
+// mLoadingPhysicsShapes mMeshMutex [4] rw.main.mMeshMutex
+// mPendingPhysicsShapeRequests mMeshMutex [4] rw.main.mMeshMutex
+// mUploads none rw.main.none (upload thread accessing objects)
+// mUploadWaitList none rw.main.none (upload thread accessing objects)
+// mInventoryQ mMeshMutex [4] rw.main.mMeshMutex, ro.main.none [5]
+// mUploadErrorQ mMeshMutex rw.main.mMeshMutex, rw.any.mMeshMutex
+// mGetMeshCapability none rw.main.none [0], ro.any.none
+// mGetMesh2Capability none rw.main.none [0], ro.any.none
+//
+// LLMeshRepoThread:
+//
+// 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]
+// 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
+// mPendingLOD mMutex rw.repo.mMutex, rw.any.mMutex
+//
+// LLPhysicsDecomp:
+//
+// mRequestQ
+// mCurRequest
+// mCompletedQ
+//
+
+
LLMeshRepository gMeshRepo;
+const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space
const U32 MAX_MESH_REQUESTS_PER_SECOND = 100;
+const S32 REQUEST_HIGH_WATER_MIN = 32;
+const S32 REQUEST_HIGH_WATER_MAX = 80;
+const S32 REQUEST_LOW_WATER_MIN = 16;
+const S32 REQUEST_LOW_WATER_MAX = 40;
+const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue
+const long LARGE_MESH_XFER_TIMEOUT = 240L; // Seconds to complete xfer
// Maximum mesh version to support. Three least significant digits are reserved for the minor version,
// with major version changes indicating a format change that is not backwards compatible and should not
@@ -94,11 +239,9 @@ U32 LLMeshRepository::sLODPending = 0;
U32 LLMeshRepository::sCacheBytesRead = 0;
U32 LLMeshRepository::sCacheBytesWritten = 0;
-U32 LLMeshRepository::sPeakKbps = 0;
-
-
-const U32 MAX_TEXTURE_UPLOAD_RETRIES = 5;
+LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); // true -> gather cpu metrics
+
static S32 dump_num = 0;
std::string make_dump_name(std::string prefix, S32 num)
{
@@ -108,14 +251,21 @@ std::string make_dump_name(std::string prefix, S32 num)
void dump_llsd_to_file(const LLSD& content, std::string filename);
LLSD llsd_from_file(std::string filename);
-std::string header_lod[] =
+const std::string header_lod[] =
{
"lowest_lod",
"low_lod",
"medium_lod",
"high_lod"
};
+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);
+boost::signals2::connection metrics_teleport_started_signal;
+static void teleport_started();
+static bool is_retryable(LLCore::HttpStatus status);
//get the number of bytes resident in memory for given volume
U32 get_volume_memory_size(const LLVolume* volume)
@@ -197,157 +347,193 @@ void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res,
}
}
-S32 LLMeshRepoThread::sActiveHeaderRequests = 0;
-S32 LLMeshRepoThread::sActiveLODRequests = 0;
+volatile S32 LLMeshRepoThread::sActiveHeaderRequests = 0;
+volatile S32 LLMeshRepoThread::sActiveLODRequests = 0;
U32 LLMeshRepoThread::sMaxConcurrentRequests = 1;
-
-class LLMeshHeaderResponder : public LLCurl::Responder
+S32 LLMeshRepoThread::sRequestLowWater = REQUEST_LOW_WATER_MIN;
+S32 LLMeshRepoThread::sRequestHighWater = REQUEST_HIGH_WATER_MIN;
+
+// Base handler class for all mesh users of llcorehttp.
+// This is roughly equivalent to a Responder class in
+// traditional LL code. The base is going to perform
+// common response/data handling in the inherited
+// onCompleted() method. Derived classes, one for each
+// type of HTTP action, define processData() and
+// processFailure() methods to customize handling and
+// error messages.
+//
+class LLMeshHandlerBase : public LLCore::HttpHandler
{
public:
- LLVolumeParams mMeshParams;
- bool mProcessed;
+ LLMeshHandlerBase()
+ : LLCore::HttpHandler(),
+ mMeshParams(),
+ mProcessed(false),
+ mHttpHandle(LLCORE_HTTP_HANDLE_INVALID)
+ {}
+
+ virtual ~LLMeshHandlerBase()
+ {}
+
+protected:
+ LLMeshHandlerBase(const LLMeshHandlerBase &); // Not defined
+ void operator=(const LLMeshHandlerBase &); // Not defined
+
+public:
+ virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response);
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size) = 0;
+ virtual void processFailure(LLCore::HttpStatus status) = 0;
+
+public:
+ LLVolumeParams mMeshParams;
+ bool mProcessed;
+ LLCore::HttpHandle mHttpHandle;
+};
- LLMeshHeaderResponder(const LLVolumeParams& mesh_params)
- : mMeshParams(mesh_params)
- {
- LLMeshRepoThread::incActiveHeaderRequests();
- mProcessed = false;
- }
- ~LLMeshHeaderResponder()
+// Subclass for header fetches.
+//
+// Thread: repo
+class LLMeshHeaderHandler : public LLMeshHandlerBase
+{
+public:
+ LLMeshHeaderHandler(const LLVolumeParams & mesh_params)
+ : LLMeshHandlerBase()
{
- if (!LLApp::isQuitting())
- {
- if (!mProcessed)
- { //something went wrong, retry
- llwarns << "Timeout or service unavailable, retrying." << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- LLMeshRepoThread::HeaderRequest req(mMeshParams);
- LLMutexLock lock(gMeshRepo.mThread->mMutex);
- gMeshRepo.mThread->mHeaderReqQ.push(req);
- }
-
- LLMeshRepoThread::decActiveHeaderRequests();
- }
+ mMeshParams = mesh_params;
+ LLMeshRepoThread::incActiveHeaderRequests();
}
+ virtual ~LLMeshHeaderHandler();
- virtual void completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer);
-
+protected:
+ LLMeshHeaderHandler(const LLMeshHeaderHandler &); // Not defined
+ void operator=(const LLMeshHeaderHandler &); // Not defined
+
+public:
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size);
+ virtual void processFailure(LLCore::HttpStatus status);
};
-class LLMeshLODResponder : public LLCurl::Responder
+
+// Subclass for LOD fetches.
+//
+// Thread: repo
+class LLMeshLODHandler : public LLMeshHandlerBase
{
public:
- LLVolumeParams mMeshParams;
- S32 mLOD;
- U32 mRequestedBytes;
- U32 mOffset;
- bool mProcessed;
-
- LLMeshLODResponder(const LLVolumeParams& mesh_params, S32 lod, U32 offset, U32 requested_bytes)
- : mMeshParams(mesh_params), mLOD(lod), mOffset(offset), mRequestedBytes(requested_bytes)
+ LLMeshLODHandler(const LLVolumeParams & mesh_params, S32 lod, U32 offset, U32 requested_bytes)
+ : LLMeshHandlerBase(),
+ mLOD(lod),
+ mRequestedBytes(requested_bytes),
+ mOffset(offset)
{
+ mMeshParams = mesh_params;
LLMeshRepoThread::incActiveLODRequests();
- mProcessed = false;
- }
-
- ~LLMeshLODResponder()
- {
- if (!LLApp::isQuitting())
- {
- if (!mProcessed)
- {
- llwarns << "Killed without being processed, retrying." << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD);
- }
- LLMeshRepoThread::decActiveLODRequests();
- }
}
+ virtual ~LLMeshLODHandler();
+
+protected:
+ LLMeshLODHandler(const LLMeshLODHandler &); // Not defined
+ void operator=(const LLMeshLODHandler &); // Not defined
+
+public:
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size);
+ virtual void processFailure(LLCore::HttpStatus status);
- virtual void completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer);
-
-};
-
-class LLMeshSkinInfoResponder : public LLCurl::Responder
-{
public:
- LLUUID mMeshID;
+ S32 mLOD;
U32 mRequestedBytes;
U32 mOffset;
- bool mProcessed;
-
- LLMeshSkinInfoResponder(const LLUUID& id, U32 offset, U32 size)
- : mMeshID(id), mRequestedBytes(size), mOffset(offset)
- {
- mProcessed = false;
- }
-
- ~LLMeshSkinInfoResponder()
- {
- llassert(mProcessed);
- }
-
- virtual void completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer);
-
};
-class LLMeshDecompositionResponder : public LLCurl::Responder
+
+// Subclass for skin info fetches.
+//
+// Thread: repo
+class LLMeshSkinInfoHandler : public LLMeshHandlerBase
{
public:
+ LLMeshSkinInfoHandler(const LLUUID& id, U32 offset, U32 size)
+ : LLMeshHandlerBase(),
+ mMeshID(id),
+ mRequestedBytes(size),
+ mOffset(offset)
+ {}
+ virtual ~LLMeshSkinInfoHandler();
+
+protected:
+ LLMeshSkinInfoHandler(const LLMeshSkinInfoHandler &); // Not defined
+ void operator=(const LLMeshSkinInfoHandler &); // Not defined
+
+public:
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size);
+ virtual void processFailure(LLCore::HttpStatus status);
+
+public:
LLUUID mMeshID;
U32 mRequestedBytes;
U32 mOffset;
- bool mProcessed;
-
- LLMeshDecompositionResponder(const LLUUID& id, U32 offset, U32 size)
- : mMeshID(id), mRequestedBytes(size), mOffset(offset)
- {
- mProcessed = false;
- }
-
- ~LLMeshDecompositionResponder()
- {
- llassert(mProcessed);
- }
-
- virtual void completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer);
-
};
-class LLMeshPhysicsShapeResponder : public LLCurl::Responder
+
+// Subclass for decomposition fetches.
+//
+// Thread: repo
+class LLMeshDecompositionHandler : public LLMeshHandlerBase
{
public:
+ LLMeshDecompositionHandler(const LLUUID& id, U32 offset, U32 size)
+ : LLMeshHandlerBase(),
+ mMeshID(id),
+ mRequestedBytes(size),
+ mOffset(offset)
+ {}
+ virtual ~LLMeshDecompositionHandler();
+
+protected:
+ LLMeshDecompositionHandler(const LLMeshDecompositionHandler &); // Not defined
+ void operator=(const LLMeshDecompositionHandler &); // Not defined
+
+public:
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size);
+ virtual void processFailure(LLCore::HttpStatus status);
+
+public:
LLUUID mMeshID;
U32 mRequestedBytes;
U32 mOffset;
- bool mProcessed;
+};
- LLMeshPhysicsShapeResponder(const LLUUID& id, U32 offset, U32 size)
- : mMeshID(id), mRequestedBytes(size), mOffset(offset)
- {
- mProcessed = false;
- }
- ~LLMeshPhysicsShapeResponder()
- {
- llassert(mProcessed);
- }
-
- virtual void completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer);
+// Subclass for physics shape fetches.
+//
+// Thread: repo
+class LLMeshPhysicsShapeHandler : public LLMeshHandlerBase
+{
+public:
+ LLMeshPhysicsShapeHandler(const LLUUID& id, U32 offset, U32 size)
+ : LLMeshHandlerBase(),
+ mMeshID(id),
+ mRequestedBytes(size),
+ mOffset(offset)
+ {}
+ virtual ~LLMeshPhysicsShapeHandler();
+
+protected:
+ LLMeshPhysicsShapeHandler(const LLMeshPhysicsShapeHandler &); // Not defined
+ void operator=(const LLMeshPhysicsShapeHandler &); // Not defined
+
+public:
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size);
+ virtual void processFailure(LLCore::HttpStatus status);
+public:
+ LLUUID mMeshID;
+ U32 mRequestedBytes;
+ U32 mOffset;
};
+
void log_upload_error(S32 status, const LLSD& content, std::string stage, std::string model_name)
{
// Add notification popup.
@@ -530,16 +716,65 @@ public:
};
LLMeshRepoThread::LLMeshRepoThread()
-: LLThread("mesh repo")
+: LLThread("mesh repo"),
+ mWaiting(false),
+ mHttpRetries(0U),
+ mHttpRequest(NULL),
+ mHttpOptions(NULL),
+ mHttpLargeOptions(NULL),
+ mHttpHeaders(NULL),
+ mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID),
+ mHttpLegacyPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID),
+ mHttpLargePolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID),
+ mHttpPriority(0),
+ mHttpGetCount(0U),
+ mHttpLargeGetCount(0U)
{
- mWaiting = false;
mMutex = new LLMutex(NULL);
mHeaderMutex = new LLMutex(NULL);
mSignal = new LLCondition(NULL);
+ mHttpRequest = new LLCore::HttpRequest;
+ mHttpOptions = new LLCore::HttpOptions;
+ mHttpLargeOptions = new LLCore::HttpOptions;
+ mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT);
+ mHttpHeaders = new LLCore::HttpHeaders;
+ mHttpHeaders->append("Accept", "application/vnd.ll.mesh");
+ mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH2);
+ mHttpLegacyPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH1);
+ mHttpLargePolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_LARGE_MESH);
}
+
LLMeshRepoThread::~LLMeshRepoThread()
{
+ LL_INFOS(LOG_MESH) << "Small GETs issued: " << mHttpGetCount
+ << ", Large GETs issued: " << mHttpLargeGetCount
+ << LL_ENDL;
+
+ for (http_request_set::iterator iter(mHttpRequestSet.begin());
+ iter != mHttpRequestSet.end();
+ ++iter)
+ {
+ delete *iter;
+ }
+ mHttpRequestSet.clear();
+ if (mHttpHeaders)
+ {
+ mHttpHeaders->release();
+ mHttpHeaders = NULL;
+ }
+ if (mHttpOptions)
+ {
+ mHttpOptions->release();
+ mHttpOptions = NULL;
+ }
+ if (mHttpLargeOptions)
+ {
+ mHttpLargeOptions->release();
+ mHttpLargeOptions = NULL;
+ }
+ delete mHttpRequest;
+ mHttpRequest = NULL;
delete mMutex;
mMutex = NULL;
delete mHeaderMutex;
@@ -550,7 +785,6 @@ LLMeshRepoThread::~LLMeshRepoThread()
void LLMeshRepoThread::run()
{
- mCurlRequest = new LLCurlRequest();
LLCDResult res = LLConvexDecomposition::initThread();
if (res != LLCD_OK)
{
@@ -559,99 +793,125 @@ void LLMeshRepoThread::run()
while (!LLApp::isQuitting())
{
+ if (! mHttpRequestSet.empty())
+ {
+ // Dispatch all HttpHandler notifications
+ mHttpRequest->update(0L);
+ }
+
mWaiting = true;
mSignal->wait();
mWaiting = false;
-
- if (!LLApp::isQuitting())
+
+ if (! LLApp::isQuitting())
{
static U32 count = 0;
-
static F32 last_hundred = gFrameTimeSeconds;
if (gFrameTimeSeconds - last_hundred > 1.f)
{ //a second has gone by, clear count
last_hundred = gFrameTimeSeconds;
- count = 0;
+ count = 0;
}
-
+ else
+ {
+ count += mHttpRetries;
+ }
+ mHttpRetries = 0U;
+
// NOTE: throttling intentionally favors LOD requests over header requests
- while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveLODRequests < sMaxConcurrentRequests)
+ while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater)
{
- if (mMutex)
+ if (! mMutex)
+ {
+ break;
+ }
+ mMutex->lock();
+ LODRequest req = mLODReqQ.front();
+ mLODReqQ.pop();
+ LLMeshRepository::sLODProcessing--;
+ mMutex->unlock();
+ if (!fetchMeshLOD(req.mMeshParams, req.mLOD, count))//failed, resubmit
{
mMutex->lock();
- LODRequest req = mLODReqQ.front();
- mLODReqQ.pop();
- LLMeshRepository::sLODProcessing--;
+ mLODReqQ.push(req) ;
+ ++LLMeshRepository::sLODProcessing;
mMutex->unlock();
- if (!fetchMeshLOD(req.mMeshParams, req.mLOD, count))//failed, resubmit
- {
- mMutex->lock();
- mLODReqQ.push(req) ;
- mMutex->unlock();
- }
}
}
- while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveHeaderRequests < sMaxConcurrentRequests)
+ while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater)
{
- if (mMutex)
+ if (! mMutex)
+ {
+ break;
+ }
+ mMutex->lock();
+ HeaderRequest req = mHeaderReqQ.front();
+ mHeaderReqQ.pop();
+ mMutex->unlock();
+ if (!fetchMeshHeader(req.mMeshParams, count))//failed, resubmit
{
mMutex->lock();
- HeaderRequest req = mHeaderReqQ.front();
- mHeaderReqQ.pop();
+ mHeaderReqQ.push(req) ;
mMutex->unlock();
- if (!fetchMeshHeader(req.mMeshParams, count))//failed, resubmit
- {
- mMutex->lock();
- mHeaderReqQ.push(req) ;
- mMutex->unlock();
- }
}
}
- { //mSkinRequests is protected by mSignal
+ // 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. Keep to the throttle enforcement and pay
+ // attention to the highwater level (enforced in each fetchXXX()
+ // method).
+ if (! mSkinRequests.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && 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)
{
LLUUID mesh_id = *iter;
- if (!fetchMeshSkinInfo(mesh_id))
+ if (!fetchMeshSkinInfo(mesh_id, count))
{
incomplete.insert(mesh_id);
}
}
- mSkinRequests = incomplete;
+ mSkinRequests.swap(incomplete);
}
- { //mDecompositionRequests is protected by mSignal
+ if (! mDecompositionRequests.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater)
+ {
std::set<LLUUID> incomplete;
for (std::set<LLUUID>::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter)
{
LLUUID mesh_id = *iter;
- if (!fetchMeshDecomposition(mesh_id))
+ if (!fetchMeshDecomposition(mesh_id, count))
{
incomplete.insert(mesh_id);
}
}
- mDecompositionRequests = incomplete;
+ mDecompositionRequests.swap(incomplete);
}
- { //mPhysicsShapeRequests is protected by mSignal
+ if (! mPhysicsShapeRequests.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater)
+ {
std::set<LLUUID> incomplete;
for (std::set<LLUUID>::iterator iter = mPhysicsShapeRequests.begin(); iter != mPhysicsShapeRequests.end(); ++iter)
{
LLUUID mesh_id = *iter;
- if (!fetchMeshPhysicsShape(mesh_id))
+ if (!fetchMeshPhysicsShape(mesh_id, count))
{
incomplete.insert(mesh_id);
}
}
- mPhysicsShapeRequests = incomplete;
+ mPhysicsShapeRequests.swap(incomplete);
}
- mCurlRequest->process();
+ // For dev purposes, a dynamic change could make this false
+ // and that shouldn't assert.
+ // llassert_always(mHttpRequestSet.size() <= sRequestHighWater);
}
}
@@ -665,9 +925,6 @@ void LLMeshRepoThread::run()
{
llwarns << "convex decomposition unable to be quit" << llendl;
}
-
- delete mCurlRequest;
- mCurlRequest = NULL;
}
void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id)
@@ -726,6 +983,9 @@ void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
}
}
+// Constructs a Cap URL for the mesh. Prefers a GetMesh2 cap
+// over a GetMesh cap.
+//
//static
std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id)
{
@@ -733,7 +993,14 @@ std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id)
if (gAgent.getRegion())
{
- http_url = gMeshRepo.mGetMeshCapability;
+ if (! gMeshRepo.mGetMesh2Capability.empty())
+ {
+ http_url = gMeshRepo.mGetMesh2Capability;
+ }
+ else
+ {
+ http_url = gMeshRepo.mGetMeshCapability;
+ }
}
if (!http_url.empty())
@@ -743,14 +1010,68 @@ std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id)
}
else
{
- llwarns << "Current region does not have GetMesh capability! Cannot load " << mesh_id << ".mesh" << llendl;
+ LL_WARNS_ONCE(LOG_MESH) << "Current region does not have GetMesh capability! Cannot load "
+ << mesh_id << ".mesh" << LL_ENDL;
}
return http_url;
}
-bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
-{ //protected by mMutex
+// Issue an HTTP GET request with byte range using the right
+// policy class. Large requests go to the large request class.
+// If the current region supports GetMesh2, we prefer that for
+// smaller requests otherwise we try to use the traditional
+// GetMesh capability and connection concurrency.
+//
+// @return Valid handle or LLCORE_HTTP_HANDLE_INVALID.
+// If the latter, actual status is found in
+// mHttpStatus member which is valid until the
+// next call to this method.
+//
+// Thread: repo
+LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, int cap_version,
+ size_t offset, size_t len,
+ LLCore::HttpHandler * handler)
+{
+ LLCore::HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID);
+
+ if (len < LARGE_MESH_FETCH_THRESHOLD)
+ {
+ handle = mHttpRequest->requestGetByteRange((2 == cap_version
+ ? mHttpPolicyClass
+ : mHttpLegacyPolicyClass),
+ mHttpPriority,
+ url,
+ offset,
+ len,
+ mHttpOptions,
+ mHttpHeaders,
+ handler);
+ ++mHttpGetCount;
+ }
+ else
+ {
+ handle = mHttpRequest->requestGetByteRange(mHttpLargePolicyClass,
+ mHttpPriority,
+ url,
+ offset,
+ len,
+ mHttpLargeOptions,
+ mHttpHeaders,
+ handler);
+ ++mHttpLargeGetCount;
+ }
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ // Something went wrong, capture the error code for caller.
+ mHttpStatus = mHttpRequest->getStatus();
+ }
+ return handle;
+}
+
+
+bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, U32& count)
+{
if (!mHeaderMutex)
{
@@ -807,17 +1128,30 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
}
//reading from VFS failed for whatever reason, fetch from sim
- std::vector<std::string> headers;
- headers.push_back("Accept: application/octet-stream");
-
+ if (count >= MAX_MESH_REQUESTS_PER_SECOND || mHttpRequestSet.size() >= sRequestHighWater)
+ {
+ return false;
+ }
+ int cap_version(gMeshRepo.mGetMeshVersion);
std::string http_url = constructUrl(mesh_id);
if (!http_url.empty())
- {
- ret = mCurlRequest->getByteRange(http_url, headers, offset, size,
- new LLMeshSkinInfoResponder(mesh_id, offset, size));
- if(ret)
+ {
+ LLMeshSkinInfoHandler * handler = new LLMeshSkinInfoHandler(mesh_id, offset, size);
+ LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ LL_WARNS(LOG_MESH) << "HTTP GET request failed for skin info on mesh " << mID
+ << ". Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toHex() << ")"
+ << LL_ENDL;
+ delete handler;
+ ret = false;
+ }
+ else
{
- LLMeshRepository::sHTTPRequestCount++;
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
+ ++LLMeshRepository::sHTTPRequestCount;
}
}
}
@@ -831,8 +1165,8 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
return ret;
}
-bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
-{ //protected by mMutex
+bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id, U32& count)
+{
if (!mHeaderMutex)
{
return false;
@@ -889,17 +1223,30 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
}
//reading from VFS failed for whatever reason, fetch from sim
- std::vector<std::string> headers;
- headers.push_back("Accept: application/octet-stream");
-
+ if (count >= MAX_MESH_REQUESTS_PER_SECOND || mHttpRequestSet.size() >= sRequestHighWater)
+ {
+ return false;
+ }
+ int cap_version(gMeshRepo.mGetMeshVersion);
std::string http_url = constructUrl(mesh_id);
if (!http_url.empty())
- {
- ret = mCurlRequest->getByteRange(http_url, headers, offset, size,
- new LLMeshDecompositionResponder(mesh_id, offset, size));
- if(ret)
+ {
+ LLMeshDecompositionHandler * handler = new LLMeshDecompositionHandler(mesh_id, offset, size);
+ LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ LL_WARNS(LOG_MESH) << "HTTP GET request failed for decomposition mesh " << mID
+ << ". Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toHex() << ")"
+ << LL_ENDL;
+ delete handler;
+ ret = false;
+ }
+ else
{
- LLMeshRepository::sHTTPRequestCount++;
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
+ ++LLMeshRepository::sHTTPRequestCount;
}
}
}
@@ -913,8 +1260,8 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
return ret;
}
-bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
-{ //protected by mMutex
+bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id, U32& count)
+{
if (!mHeaderMutex)
{
return false;
@@ -970,18 +1317,30 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
}
//reading from VFS failed for whatever reason, fetch from sim
- std::vector<std::string> headers;
- headers.push_back("Accept: application/octet-stream");
-
+ if (count >= MAX_MESH_REQUESTS_PER_SECOND || mHttpRequestSet.size() >= sRequestHighWater)
+ {
+ return false;
+ }
+ int cap_version(gMeshRepo.mGetMeshVersion);
std::string http_url = constructUrl(mesh_id);
if (!http_url.empty())
- {
- ret = mCurlRequest->getByteRange(http_url, headers, offset, size,
- new LLMeshPhysicsShapeResponder(mesh_id, offset, size));
-
- if(ret)
+ {
+ LLMeshPhysicsShapeHandler * handler = new LLMeshPhysicsShapeHandler(mesh_id, offset, size);
+ LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
{
- LLMeshRepository::sHTTPRequestCount++;
+ LL_WARNS(LOG_MESH) << "HTTP GET request failed for physics shape on mesh " << mID
+ << ". Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toHex() << ")"
+ << LL_ENDL;
+ delete handler;
+ ret = false;
+ }
+ else
+ {
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
+ ++LLMeshRepository::sHTTPRequestCount;
}
}
}
@@ -1037,35 +1396,48 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c
S32 size = file.getSize();
if (size > 0)
- { //NOTE -- if the header size is ever more than 4KB, this will break
- U8 buffer[4096];
- S32 bytes = llmin(size, 4096);
+ {
+ // *NOTE: if the header size is ever more than 4KB, this will break
+ U8 buffer[MESH_HEADER_SIZE];
+ S32 bytes = llmin(size, MESH_HEADER_SIZE);
LLMeshRepository::sCacheBytesRead += bytes;
file.read(buffer, bytes);
if (headerReceived(mesh_params, buffer, bytes))
- { //did not do an HTTP request, return false
+ {
+ // Found mesh in VFS cache
return true;
}
}
}
//either cache entry doesn't exist or is corrupt, request header from simulator
- bool retval = true ;
- std::vector<std::string> headers;
- headers.push_back("Accept: application/octet-stream");
-
+ bool retval = true;
+ int cap_version(gMeshRepo.mGetMeshVersion);
std::string http_url = constructUrl(mesh_params.getSculptID());
if (!http_url.empty())
{
//grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits
//within the first 4KB
//NOTE -- this will break of headers ever exceed 4KB
- retval = mCurlRequest->getByteRange(http_url, headers, 0, 4096, new LLMeshHeaderResponder(mesh_params));
- if(retval)
+
+ LLMeshHeaderHandler * handler = new LLMeshHeaderHandler(mesh_params);
+ LLCore::HttpHandle handle = getByteRange(http_url, cap_version, 0, MESH_HEADER_SIZE, handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
{
- LLMeshRepository::sHTTPRequestCount++;
+ LL_WARNS(LOG_MESH) << "HTTP GET request failed for mesh header " << mID
+ << ". Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toHex() << ")"
+ << LL_ENDL;
+ delete handler;
+ retval = false;
+ }
+ else
+ {
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
+ ++LLMeshRepository::sHTTPRequestCount;
+ ++count;
}
- count++;
}
return retval;
@@ -1073,7 +1445,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c
//return false if failed to get mesh lod.
bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count)
-{ //protected by mMutex
+{
if (!mHeaderMutex)
{
return false;
@@ -1126,20 +1498,28 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
}
//reading from VFS failed for whatever reason, fetch from sim
- std::vector<std::string> headers;
- headers.push_back("Accept: application/octet-stream");
-
+ int cap_version(gMeshRepo.mGetMeshVersion);
std::string http_url = constructUrl(mesh_id);
if (!http_url.empty())
- {
- retval = mCurlRequest->getByteRange(constructUrl(mesh_id), headers, offset, size,
- new LLMeshLODResponder(mesh_params, lod, offset, size));
-
- if(retval)
+ {
+ LLMeshLODHandler * handler = new LLMeshLODHandler(mesh_params, lod, offset, size);
+ LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
{
- LLMeshRepository::sHTTPRequestCount++;
+ LL_WARNS(LOG_MESH) << "HTTP GET request failed for LOD on mesh " << mID
+ << ". Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toHex() << ")"
+ << LL_ENDL;
+ delete handler;
+ retval = false;
+ }
+ else
+ {
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
+ ++LLMeshRepository::sHTTPRequestCount;
+ ++count;
}
- count++;
}
else
{
@@ -1161,6 +1541,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size)
{
+ const LLUUID mesh_id = mesh_params.getSculptID();
LLSD header;
U32 header_size = 0;
@@ -1181,7 +1562,8 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat
if (!LLSDSerialize::fromBinary(header, stream, data_size))
{
- llwarns << "Mesh header parse error. Not a valid mesh asset!" << llendl;
+ LL_WARNS(LOG_MESH) << "Mesh header parse error. Not a valid mesh asset! ID: " << mesh_id
+ << LL_ENDL;
return false;
}
@@ -1189,19 +1571,18 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat
}
else
{
- llinfos
- << "Marking header as non-existent, will not retry." << llendl;
+ LL_INFOS(LOG_MESH) << "Non-positive data size. Marking header as non-existent, will not retry. ID: " << mesh_id
+ << LL_ENDL;
header["404"] = 1;
}
{
- LLUUID mesh_id = mesh_params.getSculptID();
{
LLMutexLock lock(mHeaderMutex);
mMeshHeaderSize[mesh_id] = header_size;
mMeshHeader[mesh_id] = header;
- }
+ }
LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time.
@@ -1257,7 +1638,8 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat
if (!unzip_llsd(skin, stream, data_size))
{
- llwarns << "Mesh skin info parse error. Not a valid mesh asset!" << llendl;
+ LL_WARNS(LOG_MESH) << "Mesh skin info parse error. Not a valid mesh asset! ID: " << mesh_id
+ << LL_ENDL;
return false;
}
}
@@ -1285,7 +1667,8 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3
if (!unzip_llsd(decomp, stream, data_size))
{
- llwarns << "Mesh decomposition parse error. Not a valid mesh asset!" << llendl;
+ LL_WARNS(LOG_MESH) << "Mesh decomposition parse error. Not a valid mesh asset! ID: " << mesh_id
+ << LL_ENDL;
return false;
}
}
@@ -1701,7 +2084,8 @@ void LLMeshUploadThread::doWholeModelUpload()
if (mWholeModelUploadURL.empty())
{
- llinfos << "unable to upload, fee request failed" << llendl;
+ LL_WARNS(LOG_MESH) << "Missing mesh upload capability, unable to upload, fee request failed."
+ << LL_ENDL;
}
else
{
@@ -1782,6 +2166,12 @@ void LLMeshRepoThread::notifyLoadedMeshes()
return;
}
+ if (!mLoadedQ.empty() || !mUnavailableQ.empty())
+ {
+ // Ping time-to-load metrics for mesh download operations.
+ LLMeshRepository::metricsProgress(0);
+ }
+
while (!mLoadedQ.empty())
{
mMutex->lock();
@@ -1899,181 +2289,250 @@ void LLMeshRepository::cacheOutgoingMesh(LLMeshUploadData& data, LLSD& header)
}
-void LLMeshLODResponder::completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
+void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response)
{
mProcessed = true;
-
- // thread could have already be destroyed during logout
- if( !gMeshRepo.mThread )
- {
- return;
- }
-
- S32 data_size = buffer->countAfter(channels.in(), NULL);
- if (status < 200 || status > 400)
+ // Accumulate retries, we'll use these to offset the HTTP
+ // count and maybe hold to a throttle better.
+ unsigned int retries(0U);
+ response->getRetries(NULL, &retries);
+ gMeshRepo.mThread->mHttpRetries += retries;
+ LLMeshRepository::sHTTPRetryCount += retries;
+
+ LLCore::HttpStatus status(response->getStatus());
+ if (! status)
{
- llwarns << status << ": " << reason << llendl;
+ processFailure(status);
}
-
- if (data_size < mRequestedBytes)
+ else
{
- if (status == 499 || status == 503)
- { //timeout or service unavailable, try again
- llwarns << "Timeout or service unavailable, retrying." << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD);
- }
- else
+ // From texture fetch code and applies 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.
+ static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT);
+
+ LLCore::BufferArray * body(response->getBody());
+ S32 data_size(body ? body->size() : 0);
+ U8 * data(NULL);
+
+ if (data_size > 0)
{
- llassert(status == 499 || status == 503); //intentionally trigger a breakpoint
- llwarns << "Unhandled status " << status << llendl;
+ // *TODO: Try to get rid of data copying and add interfaces
+ // that support BufferArray directly.
+ data = new U8[data_size];
+ body->read(0, (char *) data, data_size);
+ LLMeshRepository::sBytesReceived += data_size;
}
- return;
- }
- LLMeshRepository::sBytesReceived += mRequestedBytes;
+ processData(body, data, data_size);
- U8* data = NULL;
-
- if (data_size > 0)
- {
- data = new U8[data_size];
- buffer->readAfter(channels.in(), NULL, data, data_size);
+ delete [] data;
}
- if (gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size))
- {
- //good fetch from sim, write to VFS for caching
- LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE);
+ // Release handler
+ gMeshRepo.mThread->mHttpRequestSet.erase(this);
+ delete this; // Must be last statement
+}
- S32 offset = mOffset;
- S32 size = mRequestedBytes;
- if (file.getSize() >= offset+size)
+LLMeshHeaderHandler::~LLMeshHeaderHandler()
+{
+ if (!LLApp::isQuitting())
+ {
+ if (! mProcessed)
{
- file.seek(offset);
- file.write(data, size);
- LLMeshRepository::sCacheBytesWritten += size;
+ // something went wrong, retry
+ LL_WARNS(LOG_MESH) << "Mesh header fetch canceled unexpectedly, retrying." << LL_ENDL;
+ LLMeshRepoThread::HeaderRequest req(mMeshParams);
+ LLMutexLock lock(gMeshRepo.mThread->mMutex);
+ gMeshRepo.mThread->mHeaderReqQ.push(req);
}
+ LLMeshRepoThread::decActiveHeaderRequests();
}
-
- delete [] data;
}
-void LLMeshSkinInfoResponder::completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
+void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status)
{
- mProcessed = true;
+ if (is_retryable(status))
+ {
+ LL_WARNS(LOG_MESH) << "Error during mesh header handling. Reason: " << status.toString()
+ << " (" << status.toHex() << "). Retrying."
+ << LL_ENDL;
+ LLMeshRepoThread::HeaderRequest req(mMeshParams);
+ LLMutexLock lock(gMeshRepo.mThread->mMutex);
+ gMeshRepo.mThread->mHeaderReqQ.push(req);
+ }
+ else
+ {
+ // *TODO: Mark mesh unavailable
+ LL_WARNS(LOG_MESH) << "Error during mesh header handling. Reason: " << status.toString()
+ << " (" << status.toHex() << "). Not retrying."
+ << LL_ENDL;
+ }
+}
- // thread could have already be destroyed during logout
- if( !gMeshRepo.mThread )
+void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size)
+{
+ LLUUID mesh_id = mMeshParams.getSculptID();
+ bool success = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size);
+ llassert(success);
+ if (! success)
{
- return;
+ // *TODO: Mark mesh unavailable
+ // *TODO: Get real reason for parse failure here
+ LL_WARNS(LOG_MESH) << "Unable to parse mesh header. ID: " << mesh_id
+ << LL_ENDL;
}
+ else if (data && data_size > 0)
+ {
+ // header was successfully retrieved from sim, cache in vfs
+ LLSD header = gMeshRepo.mThread->mMeshHeader[mesh_id];
- S32 data_size = buffer->countAfter(channels.in(), NULL);
+ S32 version = header["version"].asInteger();
- if (status < 200 || status > 400)
- {
- llwarns << status << ": " << reason << llendl;
+ if (version <= MAX_MESH_VERSION)
+ {
+ std::stringstream str;
+
+ S32 lod_bytes = 0;
+
+ for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i)
+ {
+ // figure out how many bytes we'll need to reserve in the file
+ const std::string & lod_name = header_lod[i];
+ lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger());
+ }
+
+ // just in case skin info or decomposition is at the end of the file (which it shouldn't be)
+ lod_bytes = llmax(lod_bytes, header["skin"]["offset"].asInteger() + header["skin"]["size"].asInteger());
+ lod_bytes = llmax(lod_bytes, header["physics_convex"]["offset"].asInteger() + header["physics_convex"]["size"].asInteger());
+
+ S32 header_bytes = (S32) gMeshRepo.mThread->mMeshHeaderSize[mesh_id];
+ S32 bytes = lod_bytes + header_bytes;
+
+
+ // It's possible for the remote asset to have more data than is needed for the local cache
+ // only allocate as much space in the VFS as is needed for the local cache
+ data_size = llmin(data_size, bytes);
+
+ LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH, LLVFile::WRITE);
+ if (file.getMaxSize() >= bytes || file.setMaxSize(bytes))
+ {
+ LLMeshRepository::sCacheBytesWritten += data_size;
+
+ file.write(data, data_size);
+
+ // zero out the rest of the file
+ U8 block[MESH_HEADER_SIZE];
+ memset(block, 0, sizeof(block));
+
+ while (bytes-file.tell() > sizeof(block))
+ {
+ file.write(block, sizeof(block));
+ }
+
+ S32 remaining = bytes-file.tell();
+ if (remaining > 0)
+ {
+ file.write(block, remaining);
+ }
+ }
+ }
}
+}
- if (data_size < mRequestedBytes)
+LLMeshLODHandler::~LLMeshLODHandler()
+{
+ if (! LLApp::isQuitting())
{
- if (status == 499 || status == 503)
- { //timeout or service unavailable, try again
- llwarns << "Timeout or service unavailable, retrying." << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->loadMeshSkinInfo(mMeshID);
- }
- else
+ if (! mProcessed)
{
- llassert(status == 499 || status == 503); //intentionally trigger a breakpoint
- llwarns << "Unhandled status " << status << llendl;
+ LL_WARNS(LOG_MESH) << "Mesh LOD fetch canceled unexpectedly, retrying." << LL_ENDL;
+ gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD);
}
- return;
+ LLMeshRepoThread::decActiveLODRequests();
}
+}
- LLMeshRepository::sBytesReceived += mRequestedBytes;
-
- U8* data = NULL;
+void LLMeshLODHandler::processFailure(LLCore::HttpStatus status)
+{
+ if (is_retryable(status))
+ {
+ LL_WARNS(LOG_MESH) << "Error during mesh header handling. Reason: " << status.toString()
+ << " (" << status.toHex() << "). Retrying."
+ << LL_ENDL;
+ {
+ LLMutexLock lock(gMeshRepo.mThread->mMutex);
- if (data_size > 0)
+ gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD);
+ }
+ }
+ else
{
- data = new U8[data_size];
- buffer->readAfter(channels.in(), NULL, data, data_size);
+ // *TODO: Mark mesh unavailable
+ LL_WARNS(LOG_MESH) << "Error during mesh LOD handling. Reason: " << status.toString()
+ << " (" << status.toHex() << "). Not retrying."
+ << LL_ENDL;
}
+}
- if (gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size))
+void LLMeshLODHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size)
+{
+ if (gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size))
{
- //good fetch from sim, write to VFS for caching
- LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
+ // good fetch from sim, write to VFS for caching
+ LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE);
S32 offset = mOffset;
S32 size = mRequestedBytes;
if (file.getSize() >= offset+size)
{
- LLMeshRepository::sCacheBytesWritten += size;
file.seek(offset);
file.write(data, size);
+ LLMeshRepository::sCacheBytesWritten += size;
}
}
-
- delete [] data;
+ // *TODO: Mark mesh unavailable on error
}
-void LLMeshDecompositionResponder::completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
+LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler()
{
- mProcessed = true;
-
- if( !gMeshRepo.mThread )
- {
- return;
- }
-
- S32 data_size = buffer->countAfter(channels.in(), NULL);
-
- if (status < 200 || status > 400)
- {
- llwarns << status << ": " << reason << llendl;
- }
+ llassert(mProcessed);
+}
- if (data_size < mRequestedBytes)
+void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status)
+{
+ if (is_retryable(status))
{
- if (status == 499 || status == 503)
- { //timeout or service unavailable, try again
- llwarns << "Timeout or service unavailable, retrying." << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->loadMeshDecomposition(mMeshID);
- }
- else
+ LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. Reason: " << status.toString()
+ << " (" << status.toHex() << "). Retrying."
+ << LL_ENDL;
{
- llassert(status == 499 || status == 503); //intentionally trigger a breakpoint
- llwarns << "Unhandled status " << status << llendl;
+ LLMutexLock lock(gMeshRepo.mThread->mMutex);
+
+ gMeshRepo.mThread->loadMeshSkinInfo(mMeshID);
}
- return;
}
-
- LLMeshRepository::sBytesReceived += mRequestedBytes;
-
- U8* data = NULL;
-
- if (data_size > 0)
+ else
{
- data = new U8[data_size];
- buffer->readAfter(channels.in(), NULL, data, data_size);
+ // *TODO: Mark mesh unavailable on error
+ LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. Reason: " << status.toString()
+ << " (" << status.toHex() << "). Not retrying."
+ << LL_ENDL;
}
+}
- if (gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size))
+void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size)
+{
+ if (gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size))
{
- //good fetch from sim, write to VFS for caching
+ // good fetch from sim, write to VFS for caching
LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
S32 offset = mOffset;
@@ -2086,58 +2545,41 @@ void LLMeshDecompositionResponder::completedRaw(U32 status, const std::string& r
file.write(data, size);
}
}
-
- delete [] data;
+ // *TODO: Mark mesh unavailable on error
}
-void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
+LLMeshDecompositionHandler::~LLMeshDecompositionHandler()
{
- mProcessed = true;
-
- // thread could have already be destroyed during logout
- if( !gMeshRepo.mThread )
- {
- return;
- }
-
- S32 data_size = buffer->countAfter(channels.in(), NULL);
-
- if (status < 200 || status > 400)
- {
- llwarns << status << ": " << reason << llendl;
- }
+ llassert(mProcessed);
+}
- if (data_size < mRequestedBytes)
+void LLMeshDecompositionHandler::processFailure(LLCore::HttpStatus status)
+{
+ if (is_retryable(status))
{
- if (status == 499 || status == 503)
- { //timeout or service unavailable, try again
- llwarns << "Timeout or service unavailable, retrying." << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID);
- }
- else
+ LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. Reason: " << status.toString()
+ << " (" << status.toHex() << "). Retrying."
+ << LL_ENDL;
{
- llassert(status == 499 || status == 503); //intentionally trigger a breakpoint
- llwarns << "Unhandled status " << status << llendl;
+ LLMutexLock lock(gMeshRepo.mThread->mMutex);
+
+ gMeshRepo.mThread->loadMeshDecomposition(mMeshID);
}
- return;
}
-
- LLMeshRepository::sBytesReceived += mRequestedBytes;
-
- U8* data = NULL;
-
- if (data_size > 0)
+ else
{
- data = new U8[data_size];
- buffer->readAfter(channels.in(), NULL, data, data_size);
+ // *TODO: Mark mesh unavailable on error
+ LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. Reason: " << status.toString()
+ << " (" << status.toHex() << "). Not retrying."
+ << LL_ENDL;
}
+}
- if (gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size))
+void LLMeshDecompositionHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size)
+{
+ if (gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size))
{
- //good fetch from sim, write to VFS for caching
+ // good fetch from sim, write to VFS for caching
LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
S32 offset = mOffset;
@@ -2150,141 +2592,61 @@ void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& re
file.write(data, size);
}
}
-
- delete [] data;
+ // *TODO: Mark mesh unavailable on error
}
-void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
+LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler()
{
- mProcessed = true;
-
- // thread could have already be destroyed during logout
- if( !gMeshRepo.mThread )
- {
- return;
- }
+ llassert(mProcessed);
+}
- if (status < 200 || status > 400)
+void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status)
+{
+ if (is_retryable(status))
{
- //llwarns
- // << "Header responder failed with status: "
- // << status << ": " << reason << llendl;
-
- // 503 (service unavailable) or 499 (timeout)
- // can be due to server load and can be retried
-
- // TODO*: Add maximum retry logic, exponential backoff
- // and (somewhat more optional than the others) retries
- // again after some set period of time
-
- llassert(status == 503 || status == 499);
-
- if (status == 503 || status == 499)
- { //retry
- llwarns << "Timeout or service unavailable, retrying." << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- LLMeshRepoThread::HeaderRequest req(mMeshParams);
+ LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. Reason: " << status.toString()
+ << " (" << status.toHex() << "). Retrying."
+ << LL_ENDL;
+ {
LLMutexLock lock(gMeshRepo.mThread->mMutex);
- gMeshRepo.mThread->mHeaderReqQ.push(req);
- return;
- }
- else
- {
- llwarns << "Unhandled status." << llendl;
+ gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID);
}
}
-
- S32 data_size = buffer->countAfter(channels.in(), NULL);
-
- U8* data = NULL;
-
- if (data_size > 0)
+ else
{
- data = new U8[data_size];
- buffer->readAfter(channels.in(), NULL, data, data_size);
+ // *TODO: Mark mesh unavailable on error
+ LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. Reason: " << status.toString()
+ << " (" << status.toHex() << "). Not retrying."
+ << LL_ENDL;
}
+}
- LLMeshRepository::sBytesReceived += llmin(data_size, 4096);
-
- bool success = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size);
-
- llassert(success);
-
- if (!success)
- {
- llwarns
- << "Unable to parse mesh header: "
- << status << ": " << reason << llendl;
- }
- else if (data && data_size > 0)
+void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size)
+{
+ if (gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size))
{
- //header was successfully retrieved from sim, cache in vfs
- LLUUID mesh_id = mMeshParams.getSculptID();
- LLSD header = gMeshRepo.mThread->mMeshHeader[mesh_id];
+ // good fetch from sim, write to VFS for caching
+ LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
- S32 version = header["version"].asInteger();
+ S32 offset = mOffset;
+ S32 size = mRequestedBytes;
- if (version <= MAX_MESH_VERSION)
+ if (file.getSize() >= offset+size)
{
- std::stringstream str;
-
- S32 lod_bytes = 0;
-
- for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i)
- { //figure out how many bytes we'll need to reserve in the file
- std::string lod_name = header_lod[i];
- lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger());
- }
-
- //just in case skin info or decomposition is at the end of the file (which it shouldn't be)
- lod_bytes = llmax(lod_bytes, header["skin"]["offset"].asInteger() + header["skin"]["size"].asInteger());
- lod_bytes = llmax(lod_bytes, header["physics_convex"]["offset"].asInteger() + header["physics_convex"]["size"].asInteger());
-
- S32 header_bytes = (S32) gMeshRepo.mThread->mMeshHeaderSize[mesh_id];
- S32 bytes = lod_bytes + header_bytes;
-
-
- //it's possible for the remote asset to have more data than is needed for the local cache
- //only allocate as much space in the VFS as is needed for the local cache
- data_size = llmin(data_size, bytes);
-
- LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH, LLVFile::WRITE);
- if (file.getMaxSize() >= bytes || file.setMaxSize(bytes))
- {
- LLMeshRepository::sCacheBytesWritten += data_size;
-
- file.write((const U8*) data, data_size);
-
- //zero out the rest of the file
- U8 block[4096];
- memset(block, 0, 4096);
-
- while (bytes-file.tell() > 4096)
- {
- file.write(block, 4096);
- }
-
- S32 remaining = bytes-file.tell();
-
- if (remaining > 0)
- {
- file.write(block, remaining);
- }
- }
+ LLMeshRepository::sCacheBytesWritten += size;
+ file.seek(offset);
+ file.write(data, size);
}
}
-
- delete [] data;
+ // *TODO: Mark mesh unavailable on error
}
-
LLMeshRepository::LLMeshRepository()
: mMeshMutex(NULL),
mMeshThreadCount(0),
- mThread(NULL)
+ mThread(NULL),
+ mGetMeshVersion(2)
{
}
@@ -2303,16 +2665,19 @@ void LLMeshRepository::init()
apr_sleep(100);
}
-
+ metrics_teleport_started_signal = LLViewerMessage::getInstance()->setTeleportStartedCallback(teleport_started);
mThread = new LLMeshRepoThread();
mThread->start();
+
}
void LLMeshRepository::shutdown()
{
llinfos << "Shutting down mesh repository." << llendl;
+ metrics_teleport_started_signal.disconnect();
+
for (U32 i = 0; i < mUploads.size(); ++i)
{
llinfos << "Discard the pending mesh uploads " << llendl;
@@ -2358,6 +2723,9 @@ void LLMeshRepository::shutdown()
//called in the main thread.
S32 LLMeshRepository::update()
{
+ // Conditionally log a mesh metrics event
+ metricsUpdate();
+
if(mUploadWaitList.empty())
{
return 0 ;
@@ -2377,6 +2745,9 @@ S32 LLMeshRepository::update()
S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod)
{
+ // Manage time-to-load metrics for mesh download operations.
+ metricsProgress(1);
+
if (detail < 0 || detail > 4)
{
return detail;
@@ -2455,9 +2826,28 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para
void LLMeshRepository::notifyLoadedMeshes()
{ //called from main thread
-
- LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests");
-
+ if (1 == mGetMeshVersion)
+ {
+ // Legacy GetMesh operation with high connection concurrency
+ LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests");
+ LLMeshRepoThread::sRequestHighWater = llclamp(2 * S32(LLMeshRepoThread::sMaxConcurrentRequests),
+ REQUEST_HIGH_WATER_MIN,
+ REQUEST_HIGH_WATER_MAX);
+ }
+ else
+ {
+ // GetMesh2 operation with keepalives, etc.
+ // *TODO: Logic here is replicated from llappcorehttp.cpp, should really
+ // unify this and keep it in one place only.
+ LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests") / 4;
+ LLMeshRepoThread::sRequestHighWater = llclamp(5 * S32(LLMeshRepoThread::sMaxConcurrentRequests),
+ REQUEST_HIGH_WATER_MIN,
+ REQUEST_HIGH_WATER_MAX);
+ }
+ LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2,
+ REQUEST_LOW_WATER_MIN,
+ REQUEST_LOW_WATER_MAX);
+
//clean up completed upload threads
for (std::vector<LLMeshUploadThread*>::iterator iter = mUploads.begin(); iter != mUploads.end(); )
{
@@ -2535,7 +2925,7 @@ void LLMeshRepository::notifyLoadedMeshes()
//call completed callbacks on finished decompositions
mDecompThread->notifyCompleted();
- if (!mThread->mWaiting)
+ if (!mThread->mWaiting && mPendingRequests.empty())
{ //curl thread is churning, wait for it to go idle
return;
}
@@ -2548,6 +2938,12 @@ void LLMeshRepository::notifyLoadedMeshes()
{
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;
}
}
@@ -2562,47 +2958,55 @@ void LLMeshRepository::notifyLoadedMeshes()
mUploadErrorQ.pop();
}
- S32 push_count = LLMeshRepoThread::sMaxConcurrentRequests-(LLMeshRepoThread::sActiveHeaderRequests+LLMeshRepoThread::sActiveLODRequests);
-
- if (push_count > 0)
+ S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests;
+ if (active_count < LLMeshRepoThread::sRequestLowWater)
{
- //calculate "score" for pending requests
-
- //create score map
- std::map<LLUUID, F32> score_map;
+ S32 push_count = LLMeshRepoThread::sRequestHighWater - active_count;
- for (U32 i = 0; i < 4; ++i)
+ if (mPendingRequests.size() > push_count)
{
- for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter)
+ // More requests than the high-water limit allows so
+ // sort and forward the most important.
+
+ //calculate "score" for pending requests
+
+ //create score map
+ std::map<LLUUID, F32> score_map;
+
+ for (U32 i = 0; i < 4; ++i)
{
- F32 max_score = 0.f;
- for (std::set<LLUUID>::iterator obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter)
+ for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter)
{
- LLViewerObject* object = gObjectList.findObject(*obj_iter);
-
- if (object)
+ F32 max_score = 0.f;
+ for (std::set<LLUUID>::iterator obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter)
{
- LLDrawable* drawable = object->mDrawable;
- if (drawable)
+ LLViewerObject* object = gObjectList.findObject(*obj_iter);
+
+ if (object)
{
- F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f);
- max_score = llmax(max_score, cur_score);
+ LLDrawable* drawable = object->mDrawable;
+ if (drawable)
+ {
+ F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f);
+ max_score = llmax(max_score, cur_score);
+ }
}
}
- }
- score_map[iter->first.getSculptID()] = max_score;
+ score_map[iter->first.getSculptID()] = max_score;
+ }
}
- }
- //set "score" for pending requests
- for (std::vector<LLMeshRepoThread::LODRequest>::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter)
- {
- iter->mScore = score_map[iter->mMeshParams.getSculptID()];
- }
+ //set "score" for pending requests
+ for (std::vector<LLMeshRepoThread::LODRequest>::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter)
+ {
+ iter->mScore = score_map[iter->mMeshParams.getSculptID()];
+ }
- //sort by "score"
- std::sort(mPendingRequests.begin(), mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater());
+ //sort by "score"
+ std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count,
+ mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater());
+ }
while (!mPendingRequests.empty() && push_count > 0)
{
@@ -2656,9 +3060,8 @@ void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo& info)
vobj->notifyMeshLoaded();
}
}
+ mLoadingSkins.erase(info.mMeshID);
}
-
- mLoadingSkins.erase(info.mMeshID);
}
void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decomp)
@@ -2667,14 +3070,14 @@ void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decom
if (iter == mDecompositionMap.end())
{ //just insert decomp into map
mDecompositionMap[decomp->mMeshID] = decomp;
+ mLoadingDecompositions.erase(decomp->mMeshID);
}
else
{ //merge decomp with existing entry
iter->second->merge(decomp);
+ mLoadingDecompositions.erase(decomp->mMeshID);
delete decomp;
}
-
- mLoadingDecompositions.erase(decomp->mMeshID);
}
void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume)
@@ -2689,7 +3092,8 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol
//make sure target volume is still valid
if (volume->getNumVolumeFaces() <= 0)
{
- llwarns << "Mesh loading returned empty volume." << llendl;
+ LL_WARNS(LOG_MESH) << "Mesh loading returned empty volume. ID: " << mesh_params.getSculptID()
+ << LL_ENDL;
}
{ //update system volume
@@ -2702,7 +3106,8 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol
}
else
{
- llwarns << "Couldn't find system volume for given mesh." << llendl;
+ LL_WARNS(LOG_MESH) << "Couldn't find system volume for mesh " << mesh_params.getSculptID()
+ << LL_ENDL;
}
}
@@ -2799,6 +3204,7 @@ void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id)
std::set<LLUUID>::iterator iter = mLoadingPhysicsShapes.find(mesh_id);
if (iter == mLoadingPhysicsShapes.end())
{ //no request pending for this skin info
+ // *FIXME: Nothing ever deletes entries, can't be right
mLoadingPhysicsShapes.insert(mesh_id);
mPendingPhysicsShapeRequests.push(mesh_id);
}
@@ -2892,7 +3298,7 @@ LLSD& LLMeshRepoThread::getMeshHeader(const LLUUID& mesh_id)
void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, LLVector3& scale, bool upload_textures,
- bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload,
+ bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload,
LLHandle<LLWholeModelFeeObserver> fee_observer, LLHandle<LLWholeModelUploadObserver> upload_observer)
{
LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures, upload_skin, upload_joints, upload_url,
@@ -2921,7 +3327,6 @@ S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod)
}
return -1;
-
}
void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation,
@@ -3698,3 +4103,92 @@ bool LLMeshRepository::meshRezEnabled()
}
return false;
}
+
+// Threading: main thread only
+// static
+void LLMeshRepository::metricsStart()
+{
+ ++metrics_teleport_start_count;
+ sQuiescentTimer.start(0);
+}
+
+// Threading: main thread only
+// static
+void LLMeshRepository::metricsStop()
+{
+ sQuiescentTimer.stop(0);
+}
+
+// Threading: main thread only
+// static
+void LLMeshRepository::metricsProgress(unsigned int this_count)
+{
+ static bool first_start(true);
+
+ if (first_start)
+ {
+ metricsStart();
+ first_start = false;
+ }
+ sQuiescentTimer.ringBell(0, this_count);
+}
+
+// Threading: main thread only
+// static
+void LLMeshRepository::metricsUpdate()
+{
+ F64 started, stopped;
+ U64 total_count(U64L(0)), user_cpu(U64L(0)), sys_cpu(U64L(0));
+
+ if (sQuiescentTimer.isExpired(0, started, stopped, total_count, user_cpu, sys_cpu))
+ {
+ LLSD metrics;
+
+ metrics["reason"] = "Mesh Download Quiescent";
+ metrics["scope"] = "Login";
+ metrics["start"] = started;
+ metrics["stop"] = stopped;
+ metrics["fetches"] = LLSD::Integer(total_count);
+ metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count);
+ metrics["user_cpu"] = double(user_cpu) / 1.0e6;
+ metrics["sys_cpu"] = double(sys_cpu) / 1.0e6;
+ llinfos << "EventMarker " << metrics << llendl;
+ }
+}
+
+// Threading: main thread only
+// static
+void teleport_started()
+{
+ LLMeshRepository::metricsStart();
+}
+
+// *TODO: This comes from an edit in viewer-cat. Unify this once that's
+// available everywhere.
+bool is_retryable(LLCore::HttpStatus status)
+{
+ static const LLCore::HttpStatus cant_connect(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT);
+ static const LLCore::HttpStatus cant_res_proxy(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_RESOLVE_PROXY);
+ static const LLCore::HttpStatus cant_res_host(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_RESOLVE_HOST);
+ static const LLCore::HttpStatus send_error(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_SEND_ERROR);
+ static const LLCore::HttpStatus recv_error(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_RECV_ERROR);
+ static const LLCore::HttpStatus upload_failed(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_UPLOAD_FAILED);
+ static const LLCore::HttpStatus op_timedout(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT);
+ static const LLCore::HttpStatus post_error(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_HTTP_POST_ERROR);
+ static const LLCore::HttpStatus partial_file(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_PARTIAL_FILE);
+ static const LLCore::HttpStatus inv_cont_range(LLCore::HttpStatus::LLCORE, LLCore::HE_INV_CONTENT_RANGE_HDR);
+
+ return ((! status) &&
+ ((status.isHttpStatus() && status.mType >= 499 && status.mType <= 599) || // Include special 499 in retryables
+ status == cant_connect || // Connection reset/endpoint problems
+ status == cant_res_proxy || // DNS problems
+ status == cant_res_host || // DNS problems
+ status == send_error || // General socket problems
+ status == recv_error || // General socket problems
+ status == upload_failed || // Transport problem
+ status == op_timedout || // Timer expired
+ status == post_error || // Transport problem
+ status == partial_file || // Data inconsistency in response
+ status == inv_cont_range)); // Short data read disagrees with content-range
+}
+