summaryrefslogtreecommitdiff
path: root/indra/newview/llmeshrepository.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llmeshrepository.cpp')
-rwxr-xr-xindra/newview/llmeshrepository.cpp2273
1 files changed, 1454 insertions, 819 deletions
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index 0599fcfd2d..2f43ee3ccb 100755
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -67,6 +67,8 @@
#include "llfoldertype.h"
#include "llviewerparcelmgr.h"
#include "lluploadfloaterobservers.h"
+#include "bufferarray.h"
+#include "bufferstream.h"
#include "llfasttimer.h"
#include "boost/lexical_cast.hpp"
@@ -77,9 +79,222 @@
#include <queue>
+
+// 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
+// * getSkinInfo
+//
+// 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 (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
+//
+// 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, 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.
+// (Strong memory models make weak programmers. Weak memory models
+// make strong programmers. Ref: arm, ppc, mips, alpha)
+//
+// LLMeshRepository:
+//
+// 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
+// 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
+// mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0]
+// mMeshHeaderSize mHeaderMutex rw.repo.mHeaderMutex
+// 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
+//
+// QA/Development Testing
+//
+// Debug variable 'MeshUploadFakeErrors' takes a mask of bits that will
+// simulate an error on fee query or upload. Defined bits are:
+//
+// 0x01 Simulate application error on fee check reading
+// response body from file "fake_upload_error.xml"
+// 0x02 Same as 0x01 but for actual upload attempt.
+// 0x04 Simulate a transport problem on fee check with a
+// 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 U32 MAX_MESH_REQUESTS_PER_SECOND = 100;
+const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space
+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 = 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;
+const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue
+const long SMALL_MESH_XFER_TIMEOUT = 60L; // Seconds to complete xfer, small mesh downloads
+const long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large downloads
// 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
@@ -90,19 +305,23 @@ const U32 MAX_MESH_REQUESTS_PER_SECOND = 100;
const S32 MAX_MESH_VERSION = 999;
U32 LLMeshRepository::sBytesReceived = 0;
+U32 LLMeshRepository::sMeshRequestCount = 0;
U32 LLMeshRepository::sHTTPRequestCount = 0;
+U32 LLMeshRepository::sHTTPLargeRequestCount = 0;
U32 LLMeshRepository::sHTTPRetryCount = 0;
+U32 LLMeshRepository::sHTTPErrorCount = 0;
U32 LLMeshRepository::sLODProcessing = 0;
U32 LLMeshRepository::sLODPending = 0;
U32 LLMeshRepository::sCacheBytesRead = 0;
U32 LLMeshRepository::sCacheBytesWritten = 0;
-U32 LLMeshRepository::sPeakKbps = 0;
-LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); // true -> gather cpu metrics
+U32 LLMeshRepository::sCacheReads = 0;
+U32 LLMeshRepository::sCacheWrites = 0;
+U32 LLMeshRepository::sMaxLockHoldoffs = 0;
-
-const U32 MAX_TEXTURE_UPLOAD_RETRIES = 5;
+LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, false); // true -> gather cpu metrics
+
static S32 dump_num = 0;
std::string make_dump_name(std::string prefix, S32 num)
{
@@ -119,13 +338,14 @@ const std::string header_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 bool metrics_inited(false);
-static boost::signals2::connection metrics_teleport_connection;
-static unsigned int metrics_teleport_start_count(0);
-static void metrics_teleport_started();
+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);
static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch");
// Enable here or in build environment to get fasttimer data on mesh fetches.
@@ -136,7 +356,6 @@ static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch");
#define MESH_FASTTIMER_DEFBLOCK
#endif // LL_FASTTIMER_MESH_ENABLE
-
//get the number of bytes resident in memory for given volume
U32 get_volume_memory_size(const LLVolume* volume)
{
@@ -217,179 +436,228 @@ 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 = REQUEST2_LOW_WATER_MIN;
+S32 LLMeshRepoThread::sRequestHighWater = REQUEST2_HIGH_WATER_MIN;
+S32 LLMeshRepoThread::sRequestWaterLevel = 0;
+
+// 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.
+//
+// LLCore::HttpHandler
+// LLMeshHandlerBase
+// LLMeshHeaderHandler
+// LLMeshLODHandler
+// LLMeshSkinInfoHandler
+// LLMeshDecompositionHandler
+// LLMeshPhysicsShapeHandler
+// LLMeshUploadThread
+
+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)
+
+void log_upload_error(LLCore::HttpStatus status, const LLSD& content,
+ const char * const stage, const std::string & model_name)
{
// Add notification popup.
LLSD args;
- std::string message = content["error"]["message"];
- std::string identifier = content["error"]["identifier"];
+ std::string message = content["error"]["message"].asString();
+ std::string identifier = content["error"]["identifier"].asString();
args["MESSAGE"] = message;
args["IDENTIFIER"] = identifier;
args["LABEL"] = model_name;
gMeshRepo.uploadError(args);
// Log details.
- llwarns << "stage: " << stage << " http status: " << status << llendl;
+ LL_WARNS(LOG_MESH) << "Error in stage: " << stage
+ << ", Reason: " << status.toString()
+ << " (" << status.toHex() << ")" << LL_ENDL;
if (content.has("error"))
{
const LLSD& err = content["error"];
- llwarns << "err: " << err << llendl;
- llwarns << "mesh upload failed, stage '" << stage
- << "' error '" << err["error"].asString()
- << "', message '" << err["message"].asString()
- << "', id '" << err["identifier"].asString()
- << "'" << llendl;
+ LL_WARNS(LOG_MESH) << "error: " << err << LL_ENDL;
+ LL_WARNS(LOG_MESH) << " mesh upload failed, stage '" << stage
+ << "', error '" << err["error"].asString()
+ << "', message '" << err["message"].asString()
+ << "', id '" << err["identifier"].asString()
+ << "'" << LL_ENDL;
if (err.has("errors"))
{
S32 error_num = 0;
@@ -399,13 +667,13 @@ void log_upload_error(S32 status, const LLSD& content, std::string stage, std::s
++it)
{
const LLSD& err_entry = *it;
- llwarns << "error[" << error_num << "]:" << llendl;
+ LL_WARNS(LOG_MESH) << " error[" << error_num << "]:" << LL_ENDL;
for (LLSD::map_const_iterator map_it = err_entry.beginMap();
map_it != err_entry.endMap();
++map_it)
{
- llwarns << "\t" << map_it->first << ": "
- << map_it->second << llendl;
+ LL_WARNS(LOG_MESH) << " " << map_it->first << ": "
+ << map_it->second << LL_ENDL;
}
error_num++;
}
@@ -413,153 +681,69 @@ void log_upload_error(S32 status, const LLSD& content, std::string stage, std::s
}
else
{
- llwarns << "bad mesh, no error information available" << llendl;
+ LL_WARNS(LOG_MESH) << "Bad response to mesh request, no additional error information available." << LL_ENDL;
}
}
-class LLWholeModelFeeResponder: public LLCurl::Responder
-{
- LLMeshUploadThread* mThread;
- LLSD mModelData;
- LLHandle<LLWholeModelFeeObserver> mObserverHandle;
-public:
- LLWholeModelFeeResponder(LLMeshUploadThread* thread, LLSD& model_data, LLHandle<LLWholeModelFeeObserver> observer_handle):
- mThread(thread),
- mModelData(model_data),
- mObserverHandle(observer_handle)
- {
- if (mThread)
- {
- mThread->startRequest();
- }
- }
- ~LLWholeModelFeeResponder()
- {
- if (mThread)
- {
- mThread->stopRequest();
- }
- }
-
- virtual void completed(U32 status,
- const std::string& reason,
- const LLSD& content)
- {
- LLSD cc = content;
- if (gSavedSettings.getS32("MeshUploadFakeErrors")&1)
- {
- cc = llsd_from_file("fake_upload_error.xml");
- }
-
- dump_llsd_to_file(cc,make_dump_name("whole_model_fee_response_",dump_num));
-
- LLWholeModelFeeObserver* observer = mObserverHandle.get();
+LLMeshRepoThread::LLMeshRepoThread()
+: LLThread("mesh repo"),
+ 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)
+{
+ mMutex = new LLMutex(NULL);
+ mHeaderMutex = new LLMutex(NULL);
+ mSignal = new LLCondition(NULL);
+ mHttpRequest = new LLCore::HttpRequest;
+ mHttpOptions = new LLCore::HttpOptions;
+ mHttpOptions->setTransferTimeout(SMALL_MESH_XFER_TIMEOUT);
+ 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);
+}
- if (isGoodStatus(status) &&
- cc["state"].asString() == "upload")
- {
- mThread->mWholeModelUploadURL = cc["uploader"].asString();
- if (observer)
- {
- cc["data"]["upload_price"] = cc["upload_price"];
- observer->onModelPhysicsFeeReceived(cc["data"], mThread->mWholeModelUploadURL);
- }
- }
- else
- {
- llwarns << "fee request failed" << llendl;
- log_upload_error(status,cc,"fee",mModelData["name"]);
- mThread->mWholeModelUploadURL = "";
+LLMeshRepoThread::~LLMeshRepoThread()
+{
+ LL_INFOS(LOG_MESH) << "Small GETs issued: " << LLMeshRepository::sHTTPRequestCount
+ << ", Large GETs issued: " << LLMeshRepository::sHTTPLargeRequestCount
+ << ", Max Lock Holdoffs: " << LLMeshRepository::sMaxLockHoldoffs
+ << LL_ENDL;
- if (observer)
- {
- observer->setModelPhysicsFeeErrorStatus(status, reason);
- }
- }
+ for (http_request_set::iterator iter(mHttpRequestSet.begin());
+ iter != mHttpRequestSet.end();
+ ++iter)
+ {
+ delete *iter;
}
-
-};
-
-class LLWholeModelUploadResponder: public LLCurl::Responder
-{
- LLMeshUploadThread* mThread;
- LLSD mModelData;
- LLHandle<LLWholeModelUploadObserver> mObserverHandle;
-
-public:
- LLWholeModelUploadResponder(LLMeshUploadThread* thread, LLSD& model_data, LLHandle<LLWholeModelUploadObserver> observer_handle):
- mThread(thread),
- mModelData(model_data),
- mObserverHandle(observer_handle)
+ mHttpRequestSet.clear();
+ if (mHttpHeaders)
{
- if (mThread)
- {
- mThread->startRequest();
- }
+ mHttpHeaders->release();
+ mHttpHeaders = NULL;
}
-
- ~LLWholeModelUploadResponder()
+ if (mHttpOptions)
{
- if (mThread)
- {
- mThread->stopRequest();
- }
+ mHttpOptions->release();
+ mHttpOptions = NULL;
}
-
- virtual void completed(U32 status,
- const std::string& reason,
- const LLSD& content)
+ if (mHttpLargeOptions)
{
- LLSD cc = content;
- if (gSavedSettings.getS32("MeshUploadFakeErrors")&2)
- {
- cc = llsd_from_file("fake_upload_error.xml");
- }
-
- dump_llsd_to_file(cc,make_dump_name("whole_model_upload_response_",dump_num));
-
- LLWholeModelUploadObserver* observer = mObserverHandle.get();
-
- // requested "mesh" asset type isn't actually the type
- // of the resultant object, fix it up here.
- if (isGoodStatus(status) &&
- cc["state"].asString() == "complete")
- {
- mModelData["asset_type"] = "object";
- gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData,cc));
-
- if (observer)
- {
- doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer));
- }
- }
- else
- {
- llwarns << "upload failed" << llendl;
- std::string model_name = mModelData["name"].asString();
- log_upload_error(status,cc,"upload",model_name);
-
- if (observer)
- {
- doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer));
- }
- }
+ mHttpLargeOptions->release();
+ mHttpLargeOptions = NULL;
}
-};
-
-LLMeshRepoThread::LLMeshRepoThread()
-: LLThread("mesh repo")
-{
- mWaiting = false;
- mMutex = new LLMutex(NULL);
- mHeaderMutex = new LLMutex(NULL);
- mSignal = new LLCondition(NULL);
-}
-
-LLMeshRepoThread::~LLMeshRepoThread()
-{
+ delete mHttpRequest;
+ mHttpRequest = NULL;
delete mMutex;
mMutex = NULL;
delete mHeaderMutex;
@@ -570,109 +754,180 @@ LLMeshRepoThread::~LLMeshRepoThread()
void LLMeshRepoThread::run()
{
- mCurlRequest = new LLCurlRequest();
LLCDResult res = LLConvexDecomposition::initThread();
if (res != LLCD_OK)
{
- llwarns << "convex decomposition unable to be loaded" << llendl;
+ LL_WARNS(LOG_MESH) << "Convex decomposition unable to be loaded. Expect severe problems." << LL_ENDL;
}
while (!LLApp::isQuitting())
{
- mWaiting = true;
+ // *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.
+ // On the other hand, this may actually be an effective and efficient scheme...
+
mSignal->wait();
- mWaiting = false;
- if (!LLApp::isQuitting())
+ if (LLApp::isQuitting())
+ {
+ break;
+ }
+
+ if (! mHttpRequestSet.empty())
{
- static U32 count = 0;
+ // 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
- static F32 last_hundred = gFrameTimeSeconds;
+ 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
+ {
+ mMutex->lock();
+ mLODReqQ.push(req) ;
+ ++LLMeshRepository::sLODProcessing;
+ mMutex->unlock();
+ }
+ }
- if (gFrameTimeSeconds - last_hundred > 1.f)
- { //a second has gone by, clear count
- last_hundred = gFrameTimeSeconds;
- count = 0;
+ 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
+ {
+ mMutex->lock();
+ mHeaderReqQ.push(req) ;
+ mMutex->unlock();
+ }
+ }
- // NOTE: throttling intentionally favors LOD requests over header requests
-
- while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveLODRequests < sMaxConcurrentRequests)
+ // 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)
{
- if (mMutex)
+ std::set<LLUUID> incomplete;
+ std::set<LLUUID>::iterator iter(mSkinRequests.begin());
+ while (iter != mSkinRequests.end() && mHttpRequestSet.size() < sRequestHighWater)
{
- mMutex->lock();
- LODRequest req = mLODReqQ.front();
- mLODReqQ.pop();
- LLMeshRepository::sLODProcessing--;
+ LLUUID mesh_id = *iter;
+ mSkinRequests.erase(iter);
mMutex->unlock();
- if (!fetchMeshLOD(req.mMeshParams, req.mLOD, count))//failed, resubmit
+
+ if (! fetchMeshSkinInfo(mesh_id))
{
- mMutex->lock();
- mLODReqQ.push(req) ;
- mMutex->unlock();
+ incomplete.insert(mesh_id);
}
+
+ mMutex->lock();
+ iter = mSkinRequests.begin();
}
- }
- while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveHeaderRequests < sMaxConcurrentRequests)
- {
- if (mMutex)
+ if (! incomplete.empty())
{
- mMutex->lock();
- HeaderRequest req = mHeaderReqQ.front();
- mHeaderReqQ.pop();
- mMutex->unlock();
- if (!fetchMeshHeader(req.mMeshParams, count))//failed, resubmit
- {
- mMutex->lock();
- mHeaderReqQ.push(req) ;
- mMutex->unlock();
- }
+ mSkinRequests.insert(incomplete.begin(), incomplete.end());
}
}
- { //mSkinRequests is protected by mSignal
+ // holding lock, try next list
+ // *TODO: For UI/debug-oriented lists, we might drop the fine-
+ // grained locking as there's a lowered expectation of smoothness
+ // in these cases.
+ if (! mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ {
std::set<LLUUID> incomplete;
- for (std::set<LLUUID>::iterator iter = mSkinRequests.begin(); iter != mSkinRequests.end(); ++iter)
+ std::set<LLUUID>::iterator iter(mDecompositionRequests.begin());
+ while (iter != mDecompositionRequests.end() && mHttpRequestSet.size() < sRequestHighWater)
{
LLUUID mesh_id = *iter;
- if (!fetchMeshSkinInfo(mesh_id))
+ mDecompositionRequests.erase(iter);
+ mMutex->unlock();
+
+ if (! fetchMeshDecomposition(mesh_id))
{
incomplete.insert(mesh_id);
}
+
+ mMutex->lock();
+ iter = mDecompositionRequests.begin();
}
- mSkinRequests = incomplete;
- }
- { //mDecompositionRequests is protected by mSignal
- std::set<LLUUID> incomplete;
- for (std::set<LLUUID>::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter)
+ if (! incomplete.empty())
{
- LLUUID mesh_id = *iter;
- if (!fetchMeshDecomposition(mesh_id))
- {
- incomplete.insert(mesh_id);
- }
+ mDecompositionRequests.insert(incomplete.begin(), incomplete.end());
}
- mDecompositionRequests = incomplete;
}
- { //mPhysicsShapeRequests is protected by mSignal
+ // 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)
{
LLUUID mesh_id = *iter;
- if (!fetchMeshPhysicsShape(mesh_id))
+ mPhysicsShapeRequests.erase(iter);
+ mMutex->unlock();
+
+ if (! fetchMeshPhysicsShape(mesh_id))
{
incomplete.insert(mesh_id);
}
+
+ mMutex->lock();
+ iter = mPhysicsShapeRequests.begin();
}
- mPhysicsShapeRequests = incomplete;
- }
- mCurlRequest->process();
+ 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())
@@ -683,25 +938,25 @@ void LLMeshRepoThread::run()
res = LLConvexDecomposition::quitThread();
if (res != LLCD_OK)
{
- llwarns << "convex decomposition unable to be quit" << llendl;
+ LL_WARNS(LOG_MESH) << "Convex decomposition unable to be quit." << LL_ENDL;
}
-
- delete mCurlRequest;
- mCurlRequest = NULL;
}
+// 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);
}
@@ -714,7 +969,6 @@ void LLMeshRepoThread::lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32
}
-
void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
{ //could be called from any thread
LLMutexLock lock(mMutex);
@@ -746,6 +1000,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)
{
@@ -753,7 +1010,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())
@@ -763,14 +1027,74 @@ 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;
}
+// 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);
+ if (LLCORE_HTTP_HANDLE_INVALID != handle)
+ {
+ ++LLMeshRepository::sHTTPRequestCount;
+ }
+ }
+ else
+ {
+ handle = mHttpRequest->requestGetByteRange(mHttpLargePolicyClass,
+ mHttpPriority,
+ url,
+ offset,
+ len,
+ mHttpLargeOptions,
+ mHttpHeaders,
+ handler);
+ if (LLCORE_HTTP_HANDLE_INVALID != handle)
+ {
+ ++LLMeshRepository::sHTTPLargeRequestCount;
+ }
+ }
+ 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)
-{ //protected by mMutex
+{
if (!mHeaderMutex)
{
@@ -785,7 +1109,8 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
return false;
}
- bool ret = true ;
+ ++LLMeshRepository::sMeshRequestCount;
+ bool ret = true;
U32 header_size = mMeshHeaderSize[mesh_id];
if (header_size > 0)
@@ -803,6 +1128,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
if (file.getSize() >= offset+size)
{
LLMeshRepository::sCacheBytesRead += size;
+ ++LLMeshRepository::sCacheReads;
file.seek(offset);
U8* buffer = new U8[size];
file.read(buffer, size);
@@ -827,17 +1153,26 @@ 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");
-
+ 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);
}
}
}
@@ -852,7 +1187,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
}
bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
-{ //protected by mMutex
+{
if (!mHeaderMutex)
{
return false;
@@ -866,8 +1201,9 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
return false;
}
+ ++LLMeshRepository::sMeshRequestCount;
U32 header_size = mMeshHeaderSize[mesh_id];
- bool ret = true ;
+ bool ret = true;
if (header_size > 0)
{
@@ -884,6 +1220,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
if (file.getSize() >= offset+size)
{
LLMeshRepository::sCacheBytesRead += size;
+ ++LLMeshRepository::sCacheReads;
file.seek(offset);
U8* buffer = new U8[size];
@@ -909,17 +1246,25 @@ 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");
-
+ 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);
}
}
}
@@ -934,7 +1279,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
}
bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
-{ //protected by mMutex
+{
if (!mHeaderMutex)
{
return false;
@@ -948,8 +1293,9 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
return false;
}
+ ++LLMeshRepository::sMeshRequestCount;
U32 header_size = mMeshHeaderSize[mesh_id];
- bool ret = true ;
+ bool ret = true;
if (header_size > 0)
{
@@ -966,6 +1312,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
if (file.getSize() >= offset+size)
{
LLMeshRepository::sCacheBytesRead += size;
+ ++LLMeshRepository::sCacheReads;
file.seek(offset);
U8* buffer = new U8[size];
file.read(buffer, size);
@@ -990,18 +1337,25 @@ 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");
-
+ 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)
+ {
+ 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
{
- LLMeshRepository::sHTTPRequestCount++;
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
}
}
}
@@ -1048,8 +1402,10 @@ void LLMeshRepoThread::decActiveHeaderRequests()
}
//return false if failed to get header
-bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& count)
+bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params)
{
+ ++LLMeshRepository::sMeshRequestCount;
+
{
//look for mesh in asset in vfs
LLVFile file(gVFS, mesh_params.getSculptID(), LLAssetType::AT_MESH);
@@ -1057,43 +1413,55 @@ 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;
+ ++LLMeshRepository::sCacheReads;
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)
+ {
+ LL_WARNS(LOG_MESH) << "HTTP GET request failed for mesh header " << mID
+ << ". Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toHex() << ")"
+ << LL_ENDL;
+ delete handler;
+ retval = false;
+ }
+ else
{
- LLMeshRepository::sHTTPRequestCount++;
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
}
- count++;
}
return retval;
}
//return false if failed to get mesh lod.
-bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count)
-{ //protected by mMutex
+bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
+{
if (!mHeaderMutex)
{
return false;
@@ -1101,6 +1469,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
mHeaderMutex->lock();
+ ++LLMeshRepository::sMeshRequestCount;
bool retval = true;
LLUUID mesh_id = mesh_params.getSculptID();
@@ -1122,6 +1491,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
if (file.getSize() >= offset+size)
{
LLMeshRepository::sCacheBytesRead += size;
+ ++LLMeshRepository::sCacheReads;
file.seek(offset);
U8* buffer = new U8[size];
file.read(buffer, size);
@@ -1146,20 +1516,26 @@ 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);
}
- count++;
}
else
{
@@ -1181,6 +1557,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;
@@ -1201,7 +1578,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;
}
@@ -1209,19 +1587,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.
@@ -1277,7 +1654,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;
}
}
@@ -1286,7 +1664,7 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat
LLMeshSkinInfo info(skin);
info.mMeshID = mesh_id;
- //llinfos<<"info pelvis offset"<<info.mPelvisOffset<<llendl;
+ // LL_DEBUGS(LOG_MESH) << "info pelvis offset" << info.mPelvisOffset << LL_ENDL;
mSkinInfoQ.push(info);
}
@@ -1305,7 +1683,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;
}
}
@@ -1376,10 +1755,13 @@ bool LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32
return true;
}
+
LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures,
- bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload,
- LLHandle<LLWholeModelFeeObserver> fee_observer, LLHandle<LLWholeModelUploadObserver> upload_observer)
-: LLThread("mesh upload"),
+ bool upload_skin, bool upload_joints, const std::string & upload_url, bool do_upload,
+ LLHandle<LLWholeModelFeeObserver> fee_observer,
+ LLHandle<LLWholeModelUploadObserver> upload_observer)
+ : LLThread("mesh upload"),
+ LLCore::HttpHandler(),
mDiscarded(FALSE),
mDoUpload(do_upload),
mWholeModelUploadURL(upload_url),
@@ -1391,7 +1773,6 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data,
mUploadSkin = upload_skin;
mUploadJoints = upload_joints;
mMutex = new LLMutex(NULL);
- mCurlRequest = NULL;
mPendingUploads = 0;
mFinished = false;
mOrigin = gAgent.getPositionAgent();
@@ -1401,12 +1782,31 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data,
mOrigin += gAgent.getAtAxis() * scale.magVec();
- mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut") ;
+ mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut");
+
+ mHttpRequest = new LLCore::HttpRequest;
+ mHttpOptions = new LLCore::HttpOptions;
+ mHttpOptions->setTransferTimeout(mMeshUploadTimeOut);
+ mHttpHeaders = new LLCore::HttpHeaders;
+ mHttpHeaders->append("Content-Type", "application/llsd+xml");
+ mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_UPLOADS);
+ mHttpPriority = 0;
}
LLMeshUploadThread::~LLMeshUploadThread()
{
-
+ if (mHttpHeaders)
+ {
+ mHttpHeaders->release();
+ mHttpHeaders = NULL;
+ }
+ if (mHttpOptions)
+ {
+ mHttpOptions->release();
+ mHttpOptions = NULL;
+ }
+ delete mHttpRequest;
+ mHttpRequest = NULL;
}
LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread)
@@ -1448,14 +1848,14 @@ void LLMeshUploadThread::preStart()
void LLMeshUploadThread::discard()
{
- LLMutexLock lock(mMutex) ;
- mDiscarded = TRUE ;
+ LLMutexLock lock(mMutex);
+ mDiscarded = TRUE;
}
-BOOL LLMeshUploadThread::isDiscarded()
+BOOL LLMeshUploadThread::isDiscarded() const
{
- LLMutexLock lock(mMutex) ;
- return mDiscarded ;
+ LLMutexLock lock(mMutex);
+ return mDiscarded;
}
void LLMeshUploadThread::run()
@@ -1706,7 +2106,7 @@ void LLMeshUploadThread::generateHulls()
}
}
- if(has_valid_requests)
+ if (has_valid_requests)
{
while (!mPhysicsComplete)
{
@@ -1717,86 +2117,254 @@ void LLMeshUploadThread::generateHulls()
void LLMeshUploadThread::doWholeModelUpload()
{
- mCurlRequest = new LLCurlRequest();
+ LL_DEBUGS(LOG_MESH) << "Starting model upload. Instances: " << mInstance.size() << LL_ENDL;
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
{
generateHulls();
-
- LLSD full_model_data;
- wholeModelToLLSD(full_model_data, true);
- LLSD body = full_model_data["asset_resources"];
- dump_llsd_to_file(body,make_dump_name("whole_model_body_",dump_num));
- LLCurlRequest::headers_t headers;
-
+ LL_DEBUGS(LOG_MESH) << "Hull generation completed." << LL_ENDL;
+
+ mModelData = LLSD::emptyMap();
+ wholeModelToLLSD(mModelData, true);
+ LLSD body = mModelData["asset_resources"];
+ dump_llsd_to_file(body, make_dump_name("whole_model_body_", dump_num));
+
+ LLCore::BufferArray * ba = new LLCore::BufferArray;
+ LLCore::BufferArrayStream bas(ba);
+ LLSDSerialize::toXML(body, bas);
+ // LLSDSerialize::toXML(mModelData, bas); // <- This will generate a convenient upload error
+ LLCore::HttpHandle handle = mHttpRequest->requestPost(mHttpPolicyClass,
+ mHttpPriority,
+ mWholeModelUploadURL,
+ ba,
+ mHttpOptions,
+ mHttpHeaders,
+ this);
+ ba->release();
+
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
{
- LLCurl::ResponderPtr responder = new LLWholeModelUploadResponder(this, full_model_data, mUploadObserverHandle) ;
-
- while(!mCurlRequest->post(mWholeModelUploadURL, headers, body, responder, mMeshUploadTimeOut))
+ mHttpStatus = mHttpRequest->getStatus();
+
+ LL_WARNS(LOG_MESH) << "Couldn't issue request for full model upload. Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toHex() << ")"
+ << LL_ENDL;
+ }
+ else
+ {
+ U32 sleep_time(10);
+
+ LL_DEBUGS(LOG_MESH) << "POST request issued." << LL_ENDL;
+
+ mHttpRequest->update(0);
+ while (! LLApp::isQuitting() && ! mFinished)
{
- //sleep for 10ms to prevent eating a whole core
- apr_sleep(10000);
+ ms_sleep(sleep_time);
+ sleep_time = llmin(250U, sleep_time + sleep_time);
+ mHttpRequest->update(0);
}
+ LL_DEBUGS(LOG_MESH) << "Mesh upload operation completed." << LL_ENDL;
}
-
- do
- {
- mCurlRequest->process();
- //sleep for 10ms to prevent eating a whole core
- apr_sleep(10000);
- } while (!LLAppViewer::isQuitting() && mPendingUploads > 0);
}
-
- delete mCurlRequest;
- mCurlRequest = NULL;
-
- // Currently a no-op.
- mFinished = true;
}
void LLMeshUploadThread::requestWholeModelFee()
{
dump_num++;
- mCurlRequest = new LLCurlRequest();
-
generateHulls();
- LLSD model_data;
- wholeModelToLLSD(model_data,false);
- dump_llsd_to_file(model_data,make_dump_name("whole_model_fee_request_",dump_num));
-
- LLCurlRequest::headers_t headers;
+ mModelData = LLSD::emptyMap();
+ wholeModelToLLSD(mModelData, false);
+ dump_llsd_to_file(mModelData, make_dump_name("whole_model_fee_request_", dump_num));
+ LLCore::BufferArray * ba = new LLCore::BufferArray;
+ LLCore::BufferArrayStream bas(ba);
+ LLSDSerialize::toXML(mModelData, bas);
+
+ LLCore::HttpHandle handle = mHttpRequest->requestPost(mHttpPolicyClass,
+ mHttpPriority,
+ mWholeModelFeeCapability,
+ ba,
+ mHttpOptions,
+ mHttpHeaders,
+ this);
+ ba->release();
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ mHttpStatus = mHttpRequest->getStatus();
+
+ LL_WARNS(LOG_MESH) << "Couldn't issue request for model fee. Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toHex() << ")"
+ << LL_ENDL;
+ }
+ else
{
- LLCurl::ResponderPtr responder = new LLWholeModelFeeResponder(this,model_data, mFeeObserverHandle) ;
- while(!mCurlRequest->post(mWholeModelFeeCapability, headers, model_data, responder, mMeshUploadTimeOut))
+ U32 sleep_time(10);
+
+ mHttpRequest->update(0);
+ while (! LLApp::isQuitting() && ! mFinished)
{
- //sleep for 10ms to prevent eating a whole core
- apr_sleep(10000);
+ ms_sleep(sleep_time);
+ sleep_time = llmin(250U, sleep_time + sleep_time);
+ mHttpRequest->update(0);
}
}
+}
- do
- {
- mCurlRequest->process();
- //sleep for 10ms to prevent eating a whole core
- apr_sleep(10000);
- } while (!LLApp::isQuitting() && mPendingUploads > 0);
- delete mCurlRequest;
- mCurlRequest = NULL;
+// Does completion duty for both fee queries and actual uploads.
+void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response)
+{
+ // QA/Devel: 0x2 to enable fake error import on upload, 0x1 on fee check
+ const S32 fake_error(gSavedSettings.getS32("MeshUploadFakeErrors") & (mDoUpload ? 0xa : 0x5));
+ LLCore::HttpStatus status(response->getStatus());
+ if (fake_error)
+ {
+ status = (fake_error & 0x0c) ? LLCore::HttpStatus(500) : LLCore::HttpStatus(200);
+ }
+ std::string reason(status.toString());
+ LLSD body;
- // Currently a no-op.
mFinished = true;
+
+ if (mDoUpload)
+ {
+ // model upload case
+ LLWholeModelUploadObserver * observer(mUploadObserverHandle.get());
+
+ if (! status)
+ {
+ LL_WARNS(LOG_MESH) << "Upload failed. Reason: " << reason
+ << " (" << status.toHex() << ")"
+ << LL_ENDL;
+
+ // Build a fake body for the alert generator
+ body["error"] = LLSD::emptyMap();
+ body["error"]["message"] = reason;
+ body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py
+ log_upload_error(status, body, "upload", mModelData["name"].asString());
+
+ if (observer)
+ {
+ doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer));
+ }
+ }
+ else
+ {
+ if (fake_error & 0x2)
+ {
+ body = llsd_from_file("fake_upload_error.xml");
+ }
+ else
+ {
+ LLCore::BufferArray * ba(response->getBody());
+ if (ba && ba->size())
+ {
+ LLCore::BufferArrayStream bas(ba);
+ LLSDSerialize::fromXML(body, bas);
+ }
+ }
+ dump_llsd_to_file(body, make_dump_name("whole_model_upload_response_", dump_num));
+
+ if (body["state"].asString() == "complete")
+ {
+ // requested "mesh" asset type isn't actually the type
+ // of the resultant object, fix it up here.
+ mModelData["asset_type"] = "object";
+ gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData, body));
+
+ if (observer)
+ {
+ doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer));
+ }
+ }
+ else
+ {
+ LL_WARNS(LOG_MESH) << "Upload failed. Not in expected 'complete' state." << LL_ENDL;
+ log_upload_error(status, body, "upload", mModelData["name"].asString());
+
+ if (observer)
+ {
+ doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer));
+ }
+ }
+ }
+ }
+ else
+ {
+ // model fee case
+ LLWholeModelFeeObserver* observer(mFeeObserverHandle.get());
+ mWholeModelUploadURL.clear();
+
+ if (! status)
+ {
+ LL_WARNS(LOG_MESH) << "Fee request failed. Reason: " << reason
+ << " (" << status.toHex() << ")"
+ << LL_ENDL;
+
+ // Build a fake body for the alert generator
+ body["error"] = LLSD::emptyMap();
+ body["error"]["message"] = reason;
+ body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py
+ log_upload_error(status, body, "fee", mModelData["name"].asString());
+
+ if (observer)
+ {
+ observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason);
+ }
+ }
+ else
+ {
+ if (fake_error & 0x1)
+ {
+ body = llsd_from_file("fake_upload_error.xml");
+ }
+ else
+ {
+ LLCore::BufferArray * ba(response->getBody());
+ if (ba && ba->size())
+ {
+ LLCore::BufferArrayStream bas(ba);
+ LLSDSerialize::fromXML(body, bas);
+ }
+ }
+ dump_llsd_to_file(body, make_dump_name("whole_model_fee_response_", dump_num));
+
+ if (body["state"].asString() == "upload")
+ {
+ mWholeModelUploadURL = body["uploader"].asString();
+
+ if (observer)
+ {
+ body["data"]["upload_price"] = body["upload_price"];
+ observer->onModelPhysicsFeeReceived(body["data"], mWholeModelUploadURL);
+ }
+ }
+ else
+ {
+ LL_WARNS(LOG_MESH) << "Fee request failed. Not in expected 'upload' state." << LL_ENDL;
+ log_upload_error(status, body, "fee", mModelData["name"].asString());
+
+ if (observer)
+ {
+ observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason);
+ }
+ }
+ }
+ }
}
+
void LLMeshRepoThread::notifyLoadedMeshes()
{
+ bool update_metrics(false);
+
if (!mMutex)
{
return;
@@ -1805,10 +2373,16 @@ void LLMeshRepoThread::notifyLoadedMeshes()
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);
@@ -1823,10 +2397,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);
}
@@ -1841,6 +2422,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)
@@ -1919,181 +2507,267 @@ 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)
+ unsigned int retries(0U);
+ response->getRetries(NULL, &retries);
+ LLMeshRepository::sHTTPRetryCount += retries;
+
+ LLCore::HttpStatus status(response->getStatus());
+ if (! status)
{
- llwarns << status << ": " << reason << llendl;
+ processFailure(status);
+ ++LLMeshRepository::sHTTPErrorCount;
}
-
- 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 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);
+
+ if (par_status != status)
{
- llassert(status == 499 || status == 503); //intentionally trigger a breakpoint
- llwarns << "Unhandled status " << status << llendl;
+ LL_WARNS_ONCE(LOG_MESH) << "Non-206 successful status received for fetch: "
+ << status.toHex() << LL_ENDL;
}
- return;
- }
+
+ LLCore::BufferArray * body(response->getBody());
+ S32 data_size(body ? body->size() : 0);
+ U8 * data(NULL);
- LLMeshRepository::sBytesReceived += mRequestedBytes;
+ if (data_size > 0)
+ {
+ // *TODO: Try to get rid of data copying and add interfaces
+ // 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;
+ }
- U8* data = NULL;
+ processData(body, data, data_size);
- 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))
+ {
+ // *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;
+ 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;
+ ++LLMeshRepository::sCacheWrites;
+
+ 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;
+ ++LLMeshRepository::sCacheWrites;
}
}
-
- 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;
@@ -2102,62 +2776,46 @@ void LLMeshDecompositionResponder::completedRaw(U32 status, const std::string& r
if (file.getSize() >= offset+size)
{
LLMeshRepository::sCacheBytesWritten += size;
+ ++LLMeshRepository::sCacheWrites;
file.seek(offset);
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;
@@ -2166,145 +2824,67 @@ void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& re
if (file.getSize() >= offset+size)
{
LLMeshRepository::sCacheBytesWritten += size;
+ ++LLMeshRepository::sCacheWrites;
file.seek(offset);
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;
+ ++LLMeshRepository::sCacheWrites;
+ 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)
{
}
@@ -2323,33 +2903,21 @@ void LLMeshRepository::init()
apr_sleep(100);
}
-
+ metrics_teleport_started_signal = LLViewerMessage::getInstance()->setTeleportStartedCallback(teleport_started);
mThread = new LLMeshRepoThread();
mThread->start();
-
- if (! metrics_inited)
- {
- // Get teleport started signals to restart timings.
- metrics_teleport_connection = LLViewerMessage::getInstance()->
- setTeleportStartedCallback(metrics_teleport_started);
- metrics_inited = true;
- }
}
void LLMeshRepository::shutdown()
{
- llinfos << "Shutting down mesh repository." << llendl;
+ LL_INFOS(LOG_MESH) << "Shutting down mesh repository." << LL_ENDL;
- if (metrics_inited)
- {
- metrics_teleport_connection.disconnect();
- metrics_inited = false;
- }
+ metrics_teleport_started_signal.disconnect();
for (U32 i = 0; i < mUploads.size(); ++i)
{
- llinfos << "Discard the pending mesh uploads " << llendl;
+ LL_INFOS(LOG_MESH) << "Discard the pending mesh uploads." << LL_ENDL;
mUploads[i]->discard() ; //discard the uploading requests.
}
@@ -2364,7 +2932,7 @@ void LLMeshRepository::shutdown()
for (U32 i = 0; i < mUploads.size(); ++i)
{
- llinfos << "Waiting for pending mesh upload " << i << "/" << mUploads.size() << llendl;
+ LL_INFOS(LOG_MESH) << "Waiting for pending mesh upload " << i << "/" << mUploads.size() << LL_ENDL;
while (!mUploads[i]->isStopped())
{
apr_sleep(10);
@@ -2377,7 +2945,7 @@ void LLMeshRepository::shutdown()
delete mMeshMutex;
mMeshMutex = NULL;
- llinfos << "Shutting down decomposition system." << llendl;
+ LL_INFOS(LOG_MESH) << "Shutting down decomposition system." << LL_ENDL;
if (mDecompThread)
{
@@ -2499,8 +3067,30 @@ void LLMeshRepository::notifyLoadedMeshes()
{ //called from main thread
MESH_FASTTIMER_DEFBLOCK;
- 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);
+ LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2,
+ REQUEST_LOW_WATER_MIN,
+ REQUEST_LOW_WATER_MAX);
+ }
+ else
+ {
+ // 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,
+ REQUEST2_HIGH_WATER_MAX);
+ LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2,
+ REQUEST2_LOW_WATER_MIN,
+ REQUEST2_LOW_WATER_MAX);
+ }
+
//clean up completed upload threads
for (std::vector<LLMeshUploadThread*>::iterator iter = mUploads.begin(); iter != mUploads.end(); )
{
@@ -2577,26 +3167,43 @@ void LLMeshRepository::notifyLoadedMeshes()
//call completed callbacks on finished decompositions
mDecompThread->notifyCompleted();
-
- if (!mThread->mWaiting)
- { //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. 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);
- 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");
+ // 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())
@@ -2605,47 +3212,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)
{
@@ -2699,9 +3314,8 @@ void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo& info)
vobj->notifyMeshLoaded();
}
}
+ mLoadingSkins.erase(info.mMeshID);
}
-
- mLoadingSkins.erase(info.mMeshID);
}
void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decomp)
@@ -2710,21 +3324,18 @@ 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)
{ //called from main thread
- // Manage time-to-load metrics for mesh download operations.
- metricsProgress(0);
-
S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail());
//get list of objects waiting to be notified this mesh is loaded
@@ -2735,7 +3346,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
@@ -2748,7 +3360,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;
}
}
@@ -2768,9 +3381,6 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol
void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod)
{ //called from main thread
- // Manage time-to-load metrics for mesh download operations.
- metricsProgress(0);
-
//get list of objects waiting to be notified this mesh is loaded
mesh_load_map::iterator obj_iter = mLoadingMeshes[lod].find(mesh_params);
@@ -2852,6 +3462,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);
}
@@ -2949,7 +3560,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,
@@ -2978,7 +3589,6 @@ S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod)
}
return -1;
-
}
void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation,
@@ -3243,7 +3853,7 @@ void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh, bool vertex_based)
if (ret)
{
- llerrs << "Convex Decomposition thread valid but could not set mesh data" << llendl;
+ LL_ERRS(LOG_MESH) << "Convex Decomposition thread valid but could not set mesh data." << LL_ENDL;
}
}
}
@@ -3319,7 +3929,8 @@ void LLPhysicsDecomp::doDecomposition()
if (ret)
{
- llwarns << "Convex Decomposition thread valid but could not execute stage " << stage << llendl;
+ LL_WARNS(LOG_MESH) << "Convex Decomposition thread valid but could not execute stage " << stage << "."
+ << LL_ENDL;
LLMutexLock lock(mMutex);
mCurRequest->mHull.clear();
@@ -3448,9 +4059,9 @@ void LLPhysicsDecomp::doDecompositionSingleHull()
setMeshData(mesh, true);
LLCDResult ret = decomp->buildSingleHull() ;
- if(ret)
+ if (ret)
{
- llwarns << "Could not execute decomposition stage when attempting to create single hull." << llendl;
+ LL_WARNS(LOG_MESH) << "Could not execute decomposition stage when attempting to create single hull." << LL_ENDL;
make_box(mCurRequest);
}
else
@@ -3760,6 +4371,7 @@ bool LLMeshRepository::meshRezEnabled()
// static
void LLMeshRepository::metricsStart()
{
+ ++metrics_teleport_start_count;
sQuiescentTimer.start(0);
}
@@ -3778,7 +4390,6 @@ void LLMeshRepository::metricsProgress(unsigned int this_count)
if (first_start)
{
- ++metrics_teleport_start_count;
metricsStart();
first_start = false;
}
@@ -3797,26 +4408,50 @@ void LLMeshRepository::metricsUpdate()
LLSD metrics;
metrics["reason"] = "Mesh Download Quiescent";
- metrics["scope"] = "Login";
+ metrics["scope"] = metrics_teleport_start_count > 1 ? "Teleport" : "Login";
metrics["start"] = started;
metrics["stop"] = stopped;
- metrics["downloads"] = LLSD::Integer(total_count);
+ 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;
+ LL_INFOS(LOG_MESH) << "EventMarker " << metrics << LL_ENDL;
}
}
-// Will use a request to start a teleport as a signal to
-// restart a timing sequence. We don't get one of these
-// for login so initial start is done above.
-//
// Threading: main thread only
// static
-void metrics_teleport_started()
+void teleport_started()
{
LLMeshRepository::metricsStart();
- ++metrics_teleport_start_count;
+}
+
+// *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
}