diff options
Diffstat (limited to 'indra/newview')
| -rw-r--r-- | indra/newview/VIEWER_VERSION.txt | 2 | ||||
| -rwxr-xr-x | indra/newview/app_settings/settings.xml | 36 | ||||
| -rwxr-xr-x | indra/newview/llappcorehttp.cpp | 249 | ||||
| -rwxr-xr-x | indra/newview/llappcorehttp.h | 129 | ||||
| -rwxr-xr-x | indra/newview/llmeshrepository.cpp | 2523 | ||||
| -rwxr-xr-x | indra/newview/llmeshrepository.h | 141 | ||||
| -rwxr-xr-x | indra/newview/lltexturefetch.cpp | 26 | ||||
| -rwxr-xr-x | indra/newview/lltextureview.cpp | 41 | ||||
| -rwxr-xr-x | indra/newview/llviewerregion.cpp | 3 | 
9 files changed, 2191 insertions, 959 deletions
| diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 0b2eb36f50..c1e43e6d45 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -3.7.2 +3.7.3 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 1d62bba39f..75e6c4abb4 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -10068,11 +10068,21 @@      <key>Value</key>      <real>16</real>    </map> - +  <key>Mesh2MaxConcurrentRequests</key> +  <map> +    <key>Comment</key> +    <string>Number of connections to use for loading meshes.</string> +    <key>Persist</key> +    <integer>1</integer> +    <key>Type</key> +    <string>U32</string> +    <key>Value</key> +    <integer>8</integer> +  </map>    <key>MeshMaxConcurrentRequests</key>    <map>      <key>Comment</key> -    <string>Number of threads to use for loading meshes.</string> +    <string>Number of connections to use for loading meshes (legacy system).</string>      <key>Persist</key>      <integer>1</integer>      <key>Type</key> @@ -10080,6 +10090,28 @@      <key>Value</key>      <integer>32</integer>    </map> +  <key>MeshUseHttpRetryAfter</key> +  <map> +    <key>Comment</key> +    <string>If TRUE, use Retry-After response headers when rescheduling a mesh request that fails with an HTTP 503 status.  Static.</string> +    <key>Persist</key> +    <integer>1</integer> +    <key>Type</key> +    <string>Boolean</string> +    <key>Value</key> +    <boolean>1</boolean> +  </map> +  <key>MeshUseGetMesh1</key> +  <map> +    <key>Comment</key> +    <string>If TRUE, use the legacy GetMesh capability for mesh download requests.  Semi-dynamic (read at region crossings).</string> +    <key>Persist</key> +    <integer>1</integer> +    <key>Type</key> +    <string>Boolean</string> +    <key>Value</key> +    <boolean>0</boolean> +  </map>     <key>RunMultipleThreads</key>      <map>        <key>Comment</key> diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index 0d7d41304d..70dcffefb2 100755 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -4,7 +4,7 @@   *   * $LicenseInfo:firstyear=2012&license=viewerlgpl$   * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, Linden Research, Inc.   *   * This library is free software; you can redistribute it and/or   * modify it under the terms of the GNU Lesser General Public @@ -28,18 +28,81 @@  #include "llappcorehttp.h" +#include "llappviewer.h"  #include "llviewercontrol.h" +// Here is where we begin to get our connection usage under control. +// This establishes llcorehttp policy classes that, among other +// things, limit the maximum number of connections to outside +// services.  Each of the entries below maps to a policy class and +// has a limit, sometimes configurable, of how many connections can +// be open at a time. +  const F64 LLAppCoreHttp::MAX_THREAD_WAIT_TIME(10.0); +static const struct +{ +	LLAppCoreHttp::EAppPolicy	mPolicy; +	U32							mDefault; +	U32							mMin; +	U32							mMax; +	U32							mRate; +	std::string					mKey; +	const char *				mUsage; +} init_data[] =					//  Default and dynamic values for classes +{ +	{ +		LLAppCoreHttp::AP_DEFAULT,			8,		8,		8,		0, +		"", +		"other" +	}, +	{ +		LLAppCoreHttp::AP_TEXTURE,			8,		1,		12,		0, +		"TextureFetchConcurrency", +		"texture fetch" +	}, +	{ +		LLAppCoreHttp::AP_MESH1,			32,		1,		128,	100, +		"MeshMaxConcurrentRequests", +		"mesh fetch" +	}, +	{ +		LLAppCoreHttp::AP_MESH2,			8,		1,		32,		100, +		"Mesh2MaxConcurrentRequests", +		"mesh2 fetch" +	}, +	{ +		LLAppCoreHttp::AP_LARGE_MESH,		2,		1,		8,		0, +		"", +		"large mesh fetch" +	}, +	{ +		LLAppCoreHttp::AP_UPLOADS,			2,		1,		8,		0, +		"", +		"asset upload" +	}, +	{ +		LLAppCoreHttp::AP_LONG_POLL,		32,		32,		32,		0, +		"", +		"long poll" +	} +}; + +static void setting_changed(); +  LLAppCoreHttp::LLAppCoreHttp()  	: mRequest(NULL),  	  mStopHandle(LLCORE_HTTP_HANDLE_INVALID),  	  mStopRequested(0.0), -	  mStopped(false), -	  mPolicyDefault(-1) -{} +	  mStopped(false) +{ +	for (int i(0); i < LL_ARRAY_SIZE(mPolicies); ++i) +	{ +		mPolicies[i] = LLCore::HttpRequest::DEFAULT_POLICY_ID; +		mSettings[i] = 0U; +	} +}  LLAppCoreHttp::~LLAppCoreHttp() @@ -54,30 +117,28 @@ void LLAppCoreHttp::init()  	LLCore::HttpStatus status = LLCore::HttpRequest::createService();  	if (! status)  	{ -		LL_ERRS("Init") << "Failed to initialize HTTP services.  Reason:  " -						<< status.toString() +		LL_ERRS("Init") << "Failed to initialize HTTP services.  Reason:  " << status.toString()  						<< LL_ENDL;  	}  	// Point to our certs or SSH/https: will fail on connect -	status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_CA_FILE, -														gDirUtilp->getCAFile()); +	status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_CA_FILE, +														LLCore::HttpRequest::GLOBAL_POLICY_ID, +														gDirUtilp->getCAFile(), NULL);  	if (! status)  	{ -		LL_ERRS("Init") << "Failed to set CA File for HTTP services.  Reason:  " -						<< status.toString() +		LL_ERRS("Init") << "Failed to set CA File for HTTP services.  Reason:  " << status.toString()  						<< LL_ENDL;  	} -	// Establish HTTP Proxy.  "LLProxy" is a special string which directs -	// the code to use LLProxy::applyProxySettings() to establish any -	// HTTP or SOCKS proxy for http operations. -	status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_LLPROXY, 1); +	// Establish HTTP Proxy, if desired. +	status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_LLPROXY, +														LLCore::HttpRequest::GLOBAL_POLICY_ID, +														1, NULL);  	if (! status)  	{ -		LL_ERRS("Init") << "Failed to set HTTP proxy for HTTP services.  Reason:  " -						<< status.toString() -						<< LL_ENDL; +		LL_WARNS("Init") << "Failed to set HTTP proxy for HTTP services.  Reason:  " << status.toString() +						 << LL_ENDL;  	}  	// Tracing levels for library & libcurl (note that 2 & 3 are beyond spammy): @@ -90,47 +151,74 @@ void LLAppCoreHttp::init()  	{  		long trace_level(0L);  		trace_level = long(gSavedSettings.getU32(http_trace)); -		status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, trace_level); +		status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_TRACE, +															LLCore::HttpRequest::GLOBAL_POLICY_ID, +															trace_level, NULL);  	}  	// Setup default policy and constrain if directed to -	mPolicyDefault = LLCore::HttpRequest::DEFAULT_POLICY_ID; -	static const std::string texture_concur("TextureFetchConcurrency"); -	if (gSavedSettings.controlExists(texture_concur)) +	mPolicies[AP_DEFAULT] = LLCore::HttpRequest::DEFAULT_POLICY_ID; + +	// Setup additional policies based on table and some special rules +	for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i)  	{ -		U32 concur(llmin(gSavedSettings.getU32(texture_concur), U32(12))); +		const EAppPolicy policy(init_data[i].mPolicy); -		if (concur > 0) +		if (AP_DEFAULT == policy)  		{ -			LLCore::HttpStatus status; -			status = LLCore::HttpRequest::setPolicyClassOption(mPolicyDefault, -															   LLCore::HttpRequest::CP_CONNECTION_LIMIT, -															   concur); -			if (! status) -			{ -				LL_WARNS("Init") << "Unable to set texture fetch concurrency.  Reason:  " -								 << status.toString() -								 << LL_ENDL; -			} -			else -			{ -				LL_INFOS("Init") << "Application settings overriding default texture fetch concurrency.  New value:  " -								 << concur -								 << LL_ENDL; -			} +			// Pre-created +			continue; +		} + +		mPolicies[policy] = LLCore::HttpRequest::createPolicyClass(); +		if (! mPolicies[policy]) +		{ +			// Use default policy (but don't accidentally modify default) +			LL_WARNS("Init") << "Failed to create HTTP policy class for " << init_data[i].mUsage +							 << ".  Using default policy." +							 << LL_ENDL; +			mPolicies[policy] = mPolicies[AP_DEFAULT]; +			continue;  		}  	} + +	// Need a request object to handle dynamic options before setting them +	mRequest = new LLCore::HttpRequest; + +	// Apply initial settings +	refreshSettings(true);  	// Kick the thread  	status = LLCore::HttpRequest::startThread();  	if (! status)  	{ -		LL_ERRS("Init") << "Failed to start HTTP servicing thread.  Reason:  " -						<< status.toString() +		LL_ERRS("Init") << "Failed to start HTTP servicing thread.  Reason:  " << status.toString()  						<< LL_ENDL;  	} -	mRequest = new LLCore::HttpRequest; +	// Register signals for settings and state changes +	for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) +	{ +		if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey)) +		{ +			LLPointer<LLControlVariable> cntrl_ptr = gSavedSettings.getControl(init_data[i].mKey); +			if (cntrl_ptr.isNull()) +			{ +				LL_WARNS("Init") << "Unable to set signal on global setting '" << init_data[i].mKey +								 << "'" << LL_ENDL; +			} +			else +			{ +				mSettingsSignal[i] = cntrl_ptr->getCommitSignal()->connect(boost::bind(&setting_changed)); +			} +		} +	} +} + + +void setting_changed() +{ +	LLAppViewer::instance()->getAppCoreHttp().refreshSettings(false);  } @@ -173,6 +261,11 @@ void LLAppCoreHttp::cleanup()  		}  	} +	for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) +	{ +		mSettingsSignal[i].disconnect(); +	} +	  	delete mRequest;  	mRequest = NULL; @@ -185,6 +278,78 @@ void LLAppCoreHttp::cleanup()  	}  } +void LLAppCoreHttp::refreshSettings(bool initial) +{ +	LLCore::HttpStatus status; +	 +	for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) +	{ +		const EAppPolicy policy(init_data[i].mPolicy); + +		// Set any desired throttle +		if (initial && init_data[i].mRate) +		{ +			// Init-time only, can use the static setters here +			status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_THROTTLE_RATE, +																mPolicies[policy], +																init_data[i].mRate, +																NULL); +			if (! status) +			{ +				LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage +								 << " throttle rate.  Reason:  " << status.toString() +								 << LL_ENDL; +			} +		} + +		// Get target connection concurrency value +		U32 setting(init_data[i].mDefault); +		if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey)) +		{ +			U32 new_setting(gSavedSettings.getU32(init_data[i].mKey)); +			if (new_setting) +			{ +				// Treat zero settings as an ask for default +				setting = llclamp(new_setting, init_data[i].mMin, init_data[i].mMax); +			} +		} + +		if (! initial && setting == mSettings[policy]) +		{ +			// Unchanged, try next setting +			continue; +		} +		 +		// Set it and report +		// *TODO:  These are intended to be per-host limits when we can +		// support that in llcorehttp/libcurl. +		LLCore::HttpHandle handle; +		handle = mRequest->setPolicyOption(LLCore::HttpRequest::PO_CONNECTION_LIMIT, +										   mPolicies[policy], +										   setting, NULL); +		if (LLCORE_HTTP_HANDLE_INVALID == handle) +		{ +			status = mRequest->getStatus(); +			LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage +							 << " concurrency.  Reason:  " << status.toString() +							 << LL_ENDL; +		} +		else +		{ +			LL_DEBUGS("Init") << "Changed " << init_data[i].mUsage +							  << " concurrency.  New value:  " << setting +							  << LL_ENDL; +			mSettings[policy] = setting; +			if (initial && setting != init_data[i].mDefault) +			{ +				LL_INFOS("Init") << "Application settings overriding default " << init_data[i].mUsage +								 << " concurrency.  New value:  " << setting +								 << LL_ENDL; +			} +		} +	} +} +  void LLAppCoreHttp::onCompleted(LLCore::HttpHandle, LLCore::HttpResponse *)  { diff --git a/indra/newview/llappcorehttp.h b/indra/newview/llappcorehttp.h index 241d73ad52..40e3042b84 100755 --- a/indra/newview/llappcorehttp.h +++ b/indra/newview/llappcorehttp.h @@ -4,7 +4,7 @@   *   * $LicenseInfo:firstyear=2012&license=viewerlgpl$   * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, Linden Research, Inc.   *   * This library is free software; you can redistribute it and/or   * modify it under the terms of the GNU Lesser General Public @@ -41,6 +41,117 @@  class LLAppCoreHttp : public LLCore::HttpHandler  {  public: +	typedef LLCore::HttpRequest::policy_t policy_t; + +	enum EAppPolicy +	{ +		/// Catchall policy class.  Not used yet +		/// but will have a generous concurrency +		/// limit.  Deep queueing possible by having +		/// a chatty HTTP user. +		/// +		/// Destination:     anywhere +		/// Protocol:        http: or https: +		/// Transfer size:   KB-MB +		/// Long poll:       no +		/// Concurrency:     high  +		/// Request rate:    unknown +		/// Pipelined:       no +		AP_DEFAULT, + +		/// Texture fetching policy class.  Used to +		/// download textures via capability or SSA +		/// baking service.  Deep queueing of requests. +		/// Do not share. +		/// +		/// Destination:     simhost:12046 & bake-texture:80 +		/// Protocol:        http: +		/// Transfer size:   KB-MB +		/// Long poll:       no +		/// Concurrency:     high +		/// Request rate:    high +		/// Pipelined:       soon +		AP_TEXTURE, + +		/// Legacy mesh fetching policy class.  Used to +		/// download textures via 'GetMesh' capability. +		/// To be deprecated.  Do not share. +		/// +		/// Destination:     simhost:12046 +		/// Protocol:        http: +		/// Transfer size:   KB-MB +		/// Long poll:       no +		/// Concurrency:     dangerously high +		/// Request rate:    high +		/// Pipelined:       no +		AP_MESH1, + +		/// New mesh fetching policy class.  Used to +		/// download textures via 'GetMesh2' capability. +		/// Used when fetch request (typically one LOD) +		/// is 'small', currently defined as 2MB. +		/// Very deeply queued.  Do not share. +		/// +		/// Destination:     simhost:12046 +		/// Protocol:        http: +		/// Transfer size:   KB-MB +		/// Long poll:       no +		/// Concurrency:     high +		/// Request rate:    high +		/// Pipelined:       soon +		AP_MESH2, + +		/// Large mesh fetching policy class.  Used to +		/// download textures via 'GetMesh' or 'GetMesh2' +		/// capability.  Used when fetch request +		/// is not small to avoid head-of-line problem +		/// when large requests block a sequence of small, +		/// fast requests.  Can be shared with similar +		/// traffic that can wait for longish stalls +		/// (default timeout 600S). +		/// +		/// Destination:     simhost:12046 +		/// Protocol:        http: +		/// Transfer size:   MB +		/// Long poll:       no +		/// Concurrency:     low +		/// Request rate:    low +		/// Pipelined:       soon +		AP_LARGE_MESH, + +		/// Asset upload policy class.  Used to store +		/// assets (mesh only at the moment) via +		/// changeable URL.  Responses may take some +		/// time (default timeout 240S). +		/// +		/// Destination:     simhost:12043 +		/// Protocol:        https: +		/// Transfer size:   KB-MB +		/// Long poll:       no +		/// Concurrency:     low +		/// Request rate:    low +		/// Pipelined:       no +		AP_UPLOADS, + +		/// Long-poll-type HTTP requests.  Not +		/// bound by a connection limit.  Requests +		/// will typically hang around for a long +		/// time (~30S).  Only shareable with other +		/// long-poll requests. +		/// +		/// Destination:     simhost:12043 +		/// Protocol:        https: +		/// Transfer size:   KB +		/// Long poll:       yes +		/// Concurrency:     unlimited but low in practice +		/// Request rate:    low +		/// Pipelined:       no +		AP_LONG_POLL, + +		AP_COUNT						// Must be last +	}; +	 +public:  	LLAppCoreHttp();  	~LLAppCoreHttp(); @@ -65,21 +176,27 @@ public:  	// Notification when the stop request is complete.  	virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); -	// Retrieve the policy class for default operations. -	int getPolicyDefault() const +	// Retrieve a policy class identifier for desired +	// application function. +	policy_t getPolicy(EAppPolicy policy) const  		{ -			return mPolicyDefault; +			return mPolicies[policy];  		} + +	// Apply initial or new settings from the environment. +	void refreshSettings(bool initial);  private:  	static const F64			MAX_THREAD_WAIT_TIME;  private: -	LLCore::HttpRequest *		mRequest; +	LLCore::HttpRequest *		mRequest;						// Request queue to issue shutdowns  	LLCore::HttpHandle			mStopHandle;  	F64							mStopRequested;  	bool						mStopped; -	int							mPolicyDefault; +	policy_t					mPolicies[AP_COUNT];			// Policy class id for each connection set +	U32							mSettings[AP_COUNT]; +	boost::signals2::connection mSettingsSignal[AP_COUNT];		// Signals to global settings that affect us  }; diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index ff4d96eb54..269c361754 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -5,7 +5,7 @@   *   * $LicenseInfo:firstyear=2005&license=viewerlgpl$   * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2010-2014, Linden Research, Inc.   *    * This library is free software; you can redistribute it and/or   * modify it under the terms of the GNU Lesser General Public @@ -38,11 +38,13 @@  #include "llcallbacklist.h"  #include "llcurl.h"  #include "lldatapacker.h" +#include "lldeadmantimer.h"  #include "llfloatermodelpreview.h"  #include "llfloaterperms.h"  #include "lleconomy.h"  #include "llimagej2c.h"  #include "llhost.h" +#include "llmath.h"  #include "llnotificationsutil.h"  #include "llsd.h"  #include "llsdutil_math.h" @@ -52,6 +54,7 @@  #include "llviewercontrol.h"  #include "llviewerinventory.h"  #include "llviewermenufile.h" +#include "llviewermessage.h"  #include "llviewerobjectlist.h"  #include "llviewerregion.h"  #include "llviewertexturelist.h" @@ -65,6 +68,9 @@  #include "llfoldertype.h"  #include "llviewerparcelmgr.h"  #include "lluploadfloaterobservers.h" +#include "bufferarray.h" +#include "bufferstream.h" +#include "llfasttimer.h"  #include "boost/lexical_cast.hpp" @@ -72,11 +78,296 @@  #include "netdb.h"  #endif -#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 +//     mGetMeshVersion                 none            rw.main.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               mMutex        rw.repo.mMutex, rw.main.mMutex [5] (was:  [0]) +//     mDecompositionRequests   mMutex        rw.repo.mMutex, ro.repo.none [5] +//     mPhysicsShapeRequests    mMutex        rw.repo.mMutex, ro.repo.none [5] +//     mDecompositionQ          mMutex        rw.repo.mMutex, rw.main.mMutex [5] (was:  [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 +//     mGetMeshCapability       mMutex        rw.main.mMutex, ro.repo.mMutex (was:  [0]) +//     mGetMesh2Capability      mMutex        rw.main.mMutex, ro.repo.mMutex (was:  [0]) +//     mGetMeshVersion          mMutex        rw.main.mMutex, ro.repo.mMutex +//     mHttp*                   none          rw.repo.none +// +//   LLMeshUploadThread: +// +//     mDiscarded               mMutex        rw.main.mMutex, ro.uploadN.none [1] +//     ... more ... +// +// 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. +// +//   For major changes, see the LL_MESH_FASTTIMER_ENABLE below and +//   instructions for looking for frame stalls using fast timers. +// +// *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. +//   * Work queue for uploads?  Any need for this or is the current scheme good +//     enough? +//   * Various temp buffers used in VFS I/O might be allocated once or even +//     statically.  Look for some wins here. +//   * Move data structures holding mesh data used by main thread into main- +//     thread-only access so that no locking is needed.  May require duplication +//     of some data so that worker thread has a minimal data set to guide +//     operations. +// +// -------------------------------------------------------------------------- +//                    Development/Debug/QA Tools +// +// Enable here or in build environment to get fasttimer data on mesh fetches. +// +// Typically, this is used to perform A/B testing using the +// fasttimer console (shift-ctrl-9).  This is done by looking +// for stalls due to lock contention between the main thread +// and the repository and HTTP code.  In a release viewer, +// these appear as ping-time or worse spikes in frame time. +// With this instrumentation enabled, a stall will appear +// under the 'Mesh Fetch' timer which will be either top-level +// or under 'Render' time. + +#ifndef	LL_MESH_FASTTIMER_ENABLE +#define LL_MESH_FASTTIMER_ENABLE		1 +#endif +#if LL_MESH_FASTTIMER_ENABLE +static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); + +#define	MESH_FASTTIMER_DEFBLOCK			LLFastTimer meshtimer(FTM_MESH_FETCH) +#else +#define	MESH_FASTTIMER_DEFBLOCK +#endif // LL_MESH_FASTTIMER_ENABLE + + +// Random failure testing for development/QA. +// +// Set the MESH_*_FAILED macros to either 'false' or to +// an invocation of MESH_RANDOM_NTH_TRUE() with some +// suitable number.  In production, all must be false. +// +// Example: +// #define	MESH_HTTP_RESPONSE_FAILED				MESH_RANDOM_NTH_TRUE(9) + +// 1-in-N calls will test true +#define	MESH_RANDOM_NTH_TRUE(_N)				( ll_rand(S32(_N)) == 0 ) + +#define	MESH_HTTP_RESPONSE_FAILED				false +#define	MESH_HEADER_PROCESS_FAILED				false +#define	MESH_LOD_PROCESS_FAILED					false +#define	MESH_SKIN_INFO_PROCESS_FAILED			false +#define	MESH_DECOMP_PROCESS_FAILED				false +#define MESH_PHYS_SHAPE_PROCESS_FAILED			false + +// -------------------------------------------------------------------------- +  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 = 120L;				// Seconds to complete xfer, small mesh downloads +const long LARGE_MESH_XFER_TIMEOUT = 600L;				// Seconds to complete xfer, large downloads + +// Would normally like to retry on uploads as some +// retryable failures would be recoverable.  Unfortunately, +// the mesh service is using 500 (retryable) rather than +// 400/bad request (permanent) for a bad payload and +// retrying that just leads to revocation of the one-shot +// cap which then produces a 404 on retry destroying some +// (occasionally) useful error information.  We'll leave +// upload retries to the user as in the past.  SH-4667. +const long UPLOAD_RETRY_LIMIT = 0L;  // 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 @@ -87,35 +378,45 @@ 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; -	 +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)  {  	return prefix + boost::lexical_cast<std::string>(num) + std::string(".xml"); -	  }  void dump_llsd_to_file(const LLSD& content, std::string filename);  LLSD llsd_from_file(std::string filename); -std::string header_lod[] =  +const std::string header_lod[] =   {  	"lowest_lod",  	"low_lod",  	"medium_lod",  	"high_lod"  }; +const char * const LOG_MESH = "Mesh"; +// Static data and functions to measure mesh load +// time metrics for a new region scene. +static unsigned int metrics_teleport_start_count = 0; +boost::signals2::connection metrics_teleport_started_signal; +static void teleport_started();  //get the number of bytes resident in memory for given volume  U32 get_volume_memory_size(const LLVolume* volume) @@ -197,200 +498,233 @@ 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  { -	LOG_CLASS(LLMeshHeaderResponder);  public: -	LLVolumeParams mMeshParams; -	bool mProcessed; +	LOG_CLASS(LLMeshHandlerBase); +	LLMeshHandlerBase() +		: LLCore::HttpHandler(), +		  mMeshParams(), +		  mProcessed(false), +		  mHttpHandle(LLCORE_HTTP_HANDLE_INVALID) +		{} -	LLMeshHeaderResponder(const LLVolumeParams& mesh_params) -		: mMeshParams(mesh_params) -	{ -		LLMeshRepoThread::incActiveHeaderRequests(); -		mProcessed = false; -	} +	virtual ~LLMeshHandlerBase() +		{} -	~LLMeshHeaderResponder() -	{ -		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); -			} +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; +}; -			LLMeshRepoThread::decActiveHeaderRequests(); -		} -	} -	virtual void completedRaw(const LLChannelDescriptors& channels, -							  const LLIOPipe::buffer_ptr_t& buffer); +// Subclass for header fetches. +// +// Thread:  repo +class LLMeshHeaderHandler : public LLMeshHandlerBase +{ +public: +	LOG_CLASS(LLMeshHeaderHandler); +	LLMeshHeaderHandler(const LLVolumeParams & mesh_params) +		: LLMeshHandlerBase() +	{ +		mMeshParams = mesh_params; +		LLMeshRepoThread::incActiveHeaderRequests(); +	} +	virtual ~LLMeshHeaderHandler(); +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  { -	LOG_CLASS(LLMeshLODResponder);  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(const LLChannelDescriptors& channels, -							  const LLIOPipe::buffer_ptr_t& buffer); - -}; - -class LLMeshSkinInfoResponder : public LLCurl::Responder -{ -	LOG_CLASS(LLMeshSkinInfoResponder);  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() -	{ -		if (!LLApp::isQuitting() && -			!mProcessed && -			mMeshID.notNull()) -		{	// Something went wrong, retry -			llwarns << "Timeout or service unavailable, retrying loadMeshSkinInfo() for " << mMeshID << llendl; -			LLMeshRepository::sHTTPRetryCount++; -			gMeshRepo.mThread->loadMeshSkinInfo(mMeshID); -		} -	} -	virtual void completedRaw(const LLChannelDescriptors& channels, -							  const LLIOPipe::buffer_ptr_t& buffer); +// Subclass for skin info fetches. +// +// Thread:  repo +class LLMeshSkinInfoHandler : public LLMeshHandlerBase +{ +public: +	LOG_CLASS(LLMeshSkinInfoHandler); +	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); -class LLMeshDecompositionResponder : public LLCurl::Responder -{ -	LOG_CLASS(LLMeshDecompositionResponder);  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() -	{ -		if (!LLApp::isQuitting() && -			!mProcessed && -			mMeshID.notNull()) -		{	// Something went wrong, retry -			llwarns << "Timeout or service unavailable, retrying loadMeshDecomposition() for " << mMeshID << llendl; -			LLMeshRepository::sHTTPRetryCount++; -			gMeshRepo.mThread->loadMeshDecomposition(mMeshID); -		} -	} -	virtual void completedRaw(const LLChannelDescriptors& channels, -							  const LLIOPipe::buffer_ptr_t& buffer); +// Subclass for decomposition fetches. +// +// Thread:  repo +class LLMeshDecompositionHandler : public LLMeshHandlerBase +{ +public: +	LOG_CLASS(LLMeshDecompositionHandler); +	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); -class LLMeshPhysicsShapeResponder : public LLCurl::Responder -{ -	LOG_CLASS(LLMeshPhysicsShapeResponder);  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() -	{ -		if (!LLApp::isQuitting() && -			!mProcessed && -			mMeshID.notNull()) -		{	// Something went wrong, retry -			llwarns << "Timeout or service unavailable, retrying loadMeshPhysicsShape() for " << mMeshID << llendl; -			LLMeshRepository::sHTTPRetryCount++; -			gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID); -		} -	} +// Subclass for physics shape fetches. +// +// Thread:  repo +class LLMeshPhysicsShapeHandler : public LLMeshHandlerBase +{ +public: +	LOG_CLASS(LLMeshPhysicsShapeHandler); +	LLMeshPhysicsShapeHandler(const LLUUID& id, U32 offset, U32 size) +		: LLMeshHandlerBase(), +		  mMeshID(id), +		  mRequestedBytes(size), +		  mOffset(offset) +	{} +	virtual ~LLMeshPhysicsShapeHandler(); -	virtual void completedRaw(const LLChannelDescriptors& channels, -							  const LLIOPipe::buffer_ptr_t& buffer); +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.toTerseString() << ")" << 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; @@ -400,13 +734,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++;  			} @@ -414,154 +748,71 @@ 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 +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), +  mGetMeshVersion(2) +{  +	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); +	mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); +	mHttpLargeOptions = new LLCore::HttpOptions; +	mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT); +	mHttpLargeOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); +	mHttpHeaders = new LLCore::HttpHeaders; +	mHttpHeaders->append("Accept", "application/vnd.ll.mesh"); +	mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH2); +	mHttpLegacyPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH1); +	mHttpLargePolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_LARGE_MESH); +} + + +LLMeshRepoThread::~LLMeshRepoThread()  { -	LOG_CLASS(LLWholeModelFeeResponder); -	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(); -		} -	} +	LL_INFOS(LOG_MESH) << "Small GETs issued:  " << LLMeshRepository::sHTTPRequestCount +					   << ", Large GETs issued:  " << LLMeshRepository::sHTTPLargeRequestCount +					   << ", Max Lock Holdoffs:  " << LLMeshRepository::sMaxLockHoldoffs +					   << LL_ENDL; -	~LLWholeModelFeeResponder() +	for (http_request_set::iterator iter(mHttpRequestSet.begin()); +		 iter != mHttpRequestSet.end(); +		 ++iter)  	{ -		if (mThread) -		{ -			mThread->stopRequest(); -		} +		delete *iter;  	} - -protected: -	virtual void httpCompleted() +	mHttpRequestSet.clear(); +	if (mHttpHeaders)  	{ -		LLSD cc = getContent(); -		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(); - -		if (isGoodStatus() && -			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 " << dumpResponse() << llendl; -			S32 status = getStatus(); -			log_upload_error(status,cc,"fee",mModelData["name"]); -			mThread->mWholeModelUploadURL = ""; - -			if (observer) -			{ -				observer->setModelPhysicsFeeErrorStatus(status, getReason()); -			} -		} +		mHttpHeaders->release(); +		mHttpHeaders = NULL;  	} - -}; - -class LLWholeModelUploadResponder: public LLCurl::Responder -{ -	LOG_CLASS(LLWholeModelUploadResponder); -	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) +	if (mHttpOptions)  	{ -		if (mThread) -		{ -			mThread->startRequest(); -		} +		mHttpOptions->release(); +		mHttpOptions = NULL;  	} - -	~LLWholeModelUploadResponder() +	if (mHttpLargeOptions)  	{ -		if (mThread) -		{ -			mThread->stopRequest(); -		} -	} - -protected: -	virtual void httpCompleted() -	{ -		LLSD cc = getContent(); -		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() && -			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 " << dumpResponse() << llendl; -			std::string model_name = mModelData["name"].asString(); -			log_upload_error(getStatus(),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; @@ -572,109 +823,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())  		{ -			static U32 count = 0; +			break; +		} +		 +		if (! mHttpRequestSet.empty()) +		{ +			// Dispatch all HttpHandler notifications +			mHttpRequest->update(0L); +		} +		sRequestWaterLevel = mHttpRequestSet.size();			// Stats data update +			 +		// NOTE: order of queue processing intentionally favors LOD requests over header requests -			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()) @@ -685,25 +1007,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);  } @@ -716,7 +1038,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); @@ -748,31 +1069,122 @@ void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod)  	}  } -//static  -std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id) +// Mutex:  must be holding mMutex when called +void LLMeshRepoThread::setGetMeshCaps(const std::string & get_mesh1, +									  const std::string & get_mesh2, +									  int pref_version)  { -	std::string http_url; +	mGetMeshCapability = get_mesh1; +	mGetMesh2Capability = get_mesh2; +	mGetMeshVersion = pref_version; +} + + +// Constructs a Cap URL for the mesh.  Prefers a GetMesh2 cap +// over a GetMesh cap. +// +// Mutex:  acquires mMutex +void LLMeshRepoThread::constructUrl(LLUUID mesh_id, std::string * url, int * version) +{ +	std::string res_url; +	int res_version(2);  	if (gAgent.getRegion())  	{ -		http_url = gMeshRepo.mGetMeshCapability;  +		LLMutexLock lock(mMutex); + +		// Get a consistent pair of (cap string, version).  The +		// locking could be eliminated here without loss of safety +		// by using a set of staging values in setGetMeshCaps(). +		 +		if (! mGetMesh2Capability.empty() && mGetMeshVersion > 1) +		{ +			res_url = mGetMesh2Capability; +			res_version = 2; +		} +		else +		{ +			res_url = mGetMeshCapability; +			res_version = 1; +		}  	} -	if (!http_url.empty()) +	if (! res_url.empty())  	{ -		http_url += "/?mesh_id="; -		http_url += mesh_id.asString().c_str(); +		res_url += "/?mesh_id="; +		res_url += mesh_id.asString().c_str();  	}  	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; +	*url = res_url; +	*version = res_version;  } +// 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)  	{ @@ -787,7 +1199,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) @@ -805,6 +1218,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); @@ -829,17 +1243,28 @@ 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(HTTP_OUT_HEADER_ACCEPT + ": " + HTTP_CONTENT_OCTET_STREAM); +			int cap_version(2); +			std::string http_url; +			constructUrl(mesh_id, &http_url, &cap_version); -			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.toTerseString() << ")" +									   << LL_ENDL; +					delete handler; +					ret = false; + +				} +				else  				{ -					LLMeshRepository::sHTTPRequestCount++; +					handler->mHttpHandle = handle; +					mHttpRequestSet.insert(handler);  				}  			}  		} @@ -854,7 +1279,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)  }  bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) -{ //protected by mMutex +{  	if (!mHeaderMutex)  	{  		return false; @@ -868,8 +1293,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)  	{ @@ -886,6 +1312,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]; @@ -911,17 +1338,27 @@ 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(HTTP_OUT_HEADER_ACCEPT + ": " + HTTP_CONTENT_OCTET_STREAM); - -			std::string http_url = constructUrl(mesh_id); +			int cap_version(2); +			std::string http_url; +			constructUrl(mesh_id, &http_url, &cap_version); +			  			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.toTerseString() << ")" +									   << LL_ENDL; +					delete handler; +					ret = false; +				} +				else  				{ -					LLMeshRepository::sHTTPRequestCount++; +					handler->mHttpHandle = handle; +					mHttpRequestSet.insert(handler);  				}  			}  		} @@ -936,7 +1373,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)  }  bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) -{ //protected by mMutex +{  	if (!mHeaderMutex)  	{  		return false; @@ -950,8 +1387,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)  	{ @@ -968,6 +1406,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); @@ -992,18 +1431,27 @@ 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(HTTP_OUT_HEADER_ACCEPT + ": " + HTTP_CONTENT_OCTET_STREAM); - -			std::string http_url = constructUrl(mesh_id); +			int cap_version(2); +			std::string http_url; +			constructUrl(mesh_id, &http_url, &cap_version); +			  			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.toTerseString() << ")" +									   << LL_ENDL; +					delete handler; +					ret = false; +				} +				else  				{ -					LLMeshRepository::sHTTPRequestCount++; +					handler->mHttpHandle = handle; +					mHttpRequestSet.insert(handler);  				}  			}  		} @@ -1050,8 +1498,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); @@ -1059,43 +1509,57 @@ 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(HTTP_OUT_HEADER_ACCEPT + ": " + HTTP_CONTENT_OCTET_STREAM); - -	std::string http_url = constructUrl(mesh_params.getSculptID()); +	bool retval = true; +	int cap_version(2); +	std::string http_url; +	constructUrl(mesh_params.getSculptID(), &http_url, &cap_version); +	  	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.toTerseString() << ")" +							   << 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; @@ -1103,6 +1567,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,  	mHeaderMutex->lock(); +	++LLMeshRepository::sMeshRequestCount;  	bool retval = true;  	LLUUID mesh_id = mesh_params.getSculptID(); @@ -1124,6 +1589,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); @@ -1148,20 +1614,29 @@ 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(HTTP_OUT_HEADER_ACCEPT + ": " + HTTP_CONTENT_OCTET_STREAM); - -			std::string http_url = constructUrl(mesh_id); +			int cap_version(2); +			std::string http_url; +			constructUrl(mesh_id, &http_url, &cap_version); +			  			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) +				{ +					LL_WARNS(LOG_MESH) << "HTTP GET request failed for LOD on mesh " << mID +									   << ".  Reason:  " << mHttpStatus.toString() +									   << " (" << mHttpStatus.toTerseString() << ")" +									   << LL_ENDL; +					delete handler; +					retval = false; +				} +				else  				{ -					LLMeshRepository::sHTTPRequestCount++; +					handler->mHttpHandle = handle; +					mHttpRequestSet.insert(handler); +					// *NOTE:  Allowing a re-request, not marking as unavailable.  Is that correct?  				} -				count++;  			}  			else  			{ @@ -1183,6 +1658,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; @@ -1203,7 +1679,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;  		} @@ -1211,13 +1688,12 @@ 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); @@ -1225,6 +1701,7 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat  			mMeshHeader[mesh_id] = header;  		} +		  		LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time.  		//check for pending requests @@ -1278,7 +1755,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;  		}  	} @@ -1287,8 +1765,11 @@ 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; -		mSkinInfoQ.push(info); +		// LL_DEBUGS(LOG_MESH) << "info pelvis offset" << info.mPelvisOffset << LL_ENDL; +		{ +			LLMutexLock lock(mMutex); +			mSkinInfoQ.push_back(info); +		}  	}  	return true; @@ -1306,7 +1787,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;  		}  	} @@ -1314,7 +1796,10 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3  	{  		LLModel::Decomposition* d = new LLModel::Decomposition(decomp);  		d->mMeshID = mesh_id; -		mDecompositionQ.push(d); +		{ +			LLMutexLock lock(mMutex); +			mDecompositionQ.push_back(d); +		}  	}  	return true; @@ -1373,15 +1858,20 @@ bool LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32  		}  	} -	mDecompositionQ.push(d); +	{ +		LLMutexLock lock(mMutex); +		mDecompositionQ.push_back(d); +	}  	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"), -	mDiscarded(FALSE), +									   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),  	mFeeObserverHandle(fee_observer), @@ -1392,7 +1882,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(); @@ -1402,12 +1891,33 @@ 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); +	mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); +	mHttpOptions->setRetries(UPLOAD_RETRY_LIMIT); +	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) @@ -1449,14 +1959,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() @@ -1707,9 +2217,15 @@ void LLMeshUploadThread::generateHulls()  		}  	} -	if(has_valid_requests) -	{ -		while (!mPhysicsComplete) +	if (has_valid_requests) +	{ +		// *NOTE:  Interesting livelock condition on shutdown.  If there +		// is an upload request in generateHulls() when shutdown starts, +		// the main thread isn't available to manage communication between +		// the decomposition thread and the upload thread and this loop +		// wouldn't complete in turn stalling the main thread.  The check +		// on isDiscarded() prevents that. +		while (! mPhysicsComplete && ! isDiscarded())  		{  			apr_sleep(100);  		} @@ -1718,86 +2234,266 @@ 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);		// <- Enabling 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) +		{ +			mHttpStatus = mHttpRequest->getStatus(); +		 +			LL_WARNS(LOG_MESH) << "Couldn't issue request for full model upload.  Reason:  " << mHttpStatus.toString() +							   << " (" << mHttpStatus.toTerseString() << ")" +							   << LL_ENDL; +		} +		else  		{ -			LLCurl::ResponderPtr responder = new LLWholeModelUploadResponder(this, full_model_data, mUploadObserverHandle) ; +			U32 sleep_time(10); +		 +			LL_DEBUGS(LOG_MESH) << "POST request issued." << LL_ENDL; +			 +			mHttpRequest->update(0); +			while (! LLApp::isQuitting() && ! finished() && ! isDiscarded()) +			{ +				ms_sleep(sleep_time); +				sleep_time = llmin(250U, sleep_time + sleep_time); +				mHttpRequest->update(0); +			} -			while(!mCurlRequest->post(mWholeModelUploadURL, headers, body, responder, mMeshUploadTimeOut)) +			if (isDiscarded()) +			{ +				LL_DEBUGS(LOG_MESH) << "Mesh upload operation discarded." << LL_ENDL; +			} +			else  			{ -				//sleep for 10ms to prevent eating a whole core -				apr_sleep(10000); +				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.toTerseString() << ")" +						   << 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() && ! finished() && ! isDiscarded())  		{ -			//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); +		} +		if (isDiscarded()) +		{ +			LL_DEBUGS(LOG_MESH) << "Mesh fee query operation discarded." << LL_ENDL;  		}  	} +} -	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.toTerseString() << ")" +							   << 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.toTerseString() << ")" +							   << 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; @@ -1806,10 +2502,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); @@ -1824,24 +2526,59 @@ 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);  	} -	while (!mSkinInfoQ.empty()) +	if (! mSkinInfoQ.empty() || ! mDecompositionQ.empty())  	{ -		gMeshRepo.notifySkinInfoReceived(mSkinInfoQ.front()); -		mSkinInfoQ.pop(); +		if (mMutex->trylock()) +		{ +			std::list<LLMeshSkinInfo> skin_info_q; +			std::list<LLModel::Decomposition*> decomp_q; + +			if (! mSkinInfoQ.empty()) +			{ +				skin_info_q.swap(mSkinInfoQ); +			} +			if (! mDecompositionQ.empty()) +			{ +				decomp_q.swap(mDecompositionQ); +			} + +			mMutex->unlock(); + +			// Process the elements free of the lock +			while (! skin_info_q.empty()) +			{ +				gMeshRepo.notifySkinInfoReceived(skin_info_q.front()); +				skin_info_q.pop_front(); +			} + +			while (! decomp_q.empty()) +			{ +				gMeshRepo.notifyDecompositionReceived(decomp_q.front()); +				decomp_q.pop_front(); +			} +		}  	} -	while (!mDecompositionQ.empty()) +	if (update_metrics)  	{ -		gMeshRepo.notifyDecompositionReceived(mDecompositionQ.front()); -		mDecompositionQ.pop(); +		// Ping time-to-load metrics for mesh download operations. +		LLMeshRepository::metricsProgress(0);  	} +	  }  S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod)  @@ -1920,249 +2657,250 @@ void LLMeshRepository::cacheOutgoingMesh(LLMeshUploadData& data, LLSD& header)  } -void LLMeshLODResponder::completedRaw(const LLChannelDescriptors& channels, -									  const LLIOPipe::buffer_ptr_t& buffer) +void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response)  { -	S32 status = getStatus();  	mProcessed = true; -	 -	// thread could have already be destroyed during logout -	if( !gMeshRepo.mThread ) -	{ -		return; -	} -	 -	S32 data_size = buffer->countAfter(channels.in(), NULL); -	// *TODO: What about 3xx redirect codes? What about status 400 (Bad Request)? -	if (status < 200 || status > 400) +	unsigned int retries(0U); +	response->getRetries(NULL, &retries); +	LLMeshRepository::sHTTPRetryCount += retries; + +	LLCore::HttpStatus status(response->getStatus()); +	if (! status || MESH_HTTP_RESPONSE_FAILED)  	{ -		llwarns << dumpResponse() << llendl; +		processFailure(status); +		++LLMeshRepository::sHTTPErrorCount;  	} - -	if (data_size < mRequestedBytes) +	else  	{ -		if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE) -		{ //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)  		{ -			llwarns << "Unhandled status " << dumpResponse() << llendl; -			llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint +			LL_WARNS_ONCE(LOG_MESH) << "Non-206 successful status received for fetch:  " +									<< status.toTerseString() << 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(const LLChannelDescriptors& channels, -										   const LLIOPipe::buffer_ptr_t& buffer) +void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status)  { -	S32 status = getStatus(); -	mProcessed = true; +	LL_WARNS(LOG_MESH) << "Error during mesh header handling.  ID:  " << mMeshParams.getSculptID() +					   << ", Reason:  " << status.toString() +					   << " (" << status.toTerseString() << ").  Not retrying." +					   << LL_ENDL; -	// thread could have already be destroyed during logout -	if( !gMeshRepo.mThread ) +	// Can't get the header so none of the LODs will be available +	LLMutexLock lock(gMeshRepo.mThread->mMutex); +	for (int i(0); i < 4; ++i)  	{ -		return; +		gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, i));  	} +} -	S32 data_size = buffer->countAfter(channels.in(), NULL); - -	// *TODO: What about 3xx redirect codes? What about status 400 (Bad Request)? -	if (status < 200 || status > 400) +void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ +	LLUUID mesh_id = mMeshParams.getSculptID(); +	bool success = (! MESH_HEADER_PROCESS_FAILED) && gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); +	llassert(success); +	if (! success)  	{ -		llwarns << dumpResponse() << llendl; -	} +		// *TODO:  Get real reason for parse failure here.  Might we want to retry? +		LL_WARNS(LOG_MESH) << "Unable to parse mesh header.  ID:  " << mesh_id +						   << ", Unknown reason.  Not retrying." +						   << LL_ENDL; -	if (data_size < mRequestedBytes) -	{ -		if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE) -		{ //timeout or service unavailable, try again -			llwarns << "Timeout or service unavailable, retrying loadMeshSkinInfo() for " << mMeshID << llendl; -			LLMeshRepository::sHTTPRetryCount++; -			gMeshRepo.mThread->loadMeshSkinInfo(mMeshID); -		} -		else +		// Can't get the header so none of the LODs will be available +		LLMutexLock lock(gMeshRepo.mThread->mMutex); +		for (int i(0); i < 4; ++i)  		{ -			llwarns << "Unhandled status " << dumpResponse() << llendl; -			llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint +			gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, i));  		} -		return;  	} +	else if (data && data_size > 0) +	{ +		// header was successfully retrieved from sim, cache in vfs +		LLSD header = gMeshRepo.mThread->mMeshHeader[mesh_id]; -	LLMeshRepository::sBytesReceived += mRequestedBytes; +		S32 version = header["version"].asInteger(); -	U8* data = NULL; +		if (version <= MAX_MESH_VERSION) +		{ +			std::stringstream str; -	if (data_size > 0) -	{ -		data = new U8[data_size]; -		buffer->readAfter(channels.in(), NULL, data, data_size); -	} +			S32 lod_bytes = 0; -	if (gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) -	{ -		//good fetch from sim, write to VFS for caching -		LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); +			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 offset = mOffset; -		S32 size = mRequestedBytes; +			S32 header_bytes = (S32) gMeshRepo.mThread->mMeshHeaderSize[mesh_id]; +			S32 bytes = lod_bytes + header_bytes;  -		if (file.getSize() >= offset+size) -		{ -			LLMeshRepository::sCacheBytesWritten += size; -			file.seek(offset); -			file.write(data, size); -		} -	} +		 +			// 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); -	delete [] data; -} +			LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH, LLVFile::WRITE); +			if (file.getMaxSize() >= bytes || file.setMaxSize(bytes)) +			{ +				LLMeshRepository::sCacheBytesWritten += data_size; +				++LLMeshRepository::sCacheWrites; -void LLMeshDecompositionResponder::completedRaw(const LLChannelDescriptors& channels, -												const LLIOPipe::buffer_ptr_t& buffer) -{ -	S32 status = getStatus(); -	mProcessed = true; -	 -	if( !gMeshRepo.mThread ) -	{ -		return; -	} +				file.write(data, data_size); +			 +				// zero out the rest of the file  +				U8 block[MESH_HEADER_SIZE]; +				memset(block, 0, sizeof(block)); -	S32 data_size = buffer->countAfter(channels.in(), NULL); +				while (bytes-file.tell() > sizeof(block)) +				{ +					file.write(block, sizeof(block)); +				} -	// *TODO: What about 3xx redirect codes? What about status 400 (Bad Request)? -	if (status < 200 || status > 400) -	{ -		llwarns << dumpResponse() << llendl; +				S32 remaining = bytes-file.tell(); +				if (remaining > 0) +				{ +					file.write(block, remaining); +				} +			} +		}  	} +} -	if (data_size < mRequestedBytes) +LLMeshLODHandler::~LLMeshLODHandler() +{ +	if (! LLApp::isQuitting())  	{ -		if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE) -		{ //timeout or service unavailable, try again -			llwarns << "Timeout or service unavailable, retrying loadMeshDecomposition() for " << mMeshID << llendl; -			LLMeshRepository::sHTTPRetryCount++; -			gMeshRepo.mThread->loadMeshDecomposition(mMeshID); -		} -		else +		if (! mProcessed)  		{ -			llwarns << "Unhandled status " << dumpResponse() << llendl; -			llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint +			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) +{ +	LL_WARNS(LOG_MESH) << "Error during mesh LOD handling.  ID:  " << mMeshParams.getSculptID() +					   << ", Reason:  " << status.toString() +					   << " (" << status.toTerseString() << ").  Not retrying." +					   << LL_ENDL; -	if (data_size > 0) -	{ -		data = new U8[data_size]; -		buffer->readAfter(channels.in(), NULL, data, data_size); -	} +	LLMutexLock lock(gMeshRepo.mThread->mMutex); +	gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); +} -	if (gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size)) +void LLMeshLODHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ +	if ((! MESH_LOD_PROCESS_FAILED) && 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; -} - -void LLMeshPhysicsShapeResponder::completedRaw(const LLChannelDescriptors& channels, -											   const LLIOPipe::buffer_ptr_t& buffer) -{ -	S32 status = getStatus(); -	mProcessed = true; - -	// thread could have already be destroyed during logout -	if( !gMeshRepo.mThread ) -	{ -		return; -	} - -	S32 data_size = buffer->countAfter(channels.in(), NULL); - -	// *TODO: What about 3xx redirect codes? What about status 400 (Bad Request)? -	if (status < 200 || status > 400) -	{ -		llwarns << dumpResponse() << llendl; -	} - -	if (data_size < mRequestedBytes) +	else  	{ -		if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE) -		{ //timeout or service unavailable, try again -			llwarns << "Timeout or service unavailable, retrying loadMeshPhysicsShape() for " << mMeshID << llendl; -			LLMeshRepository::sHTTPRetryCount++; -			gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID); -		} -		else -		{ -			llwarns << "Unhandled status " << dumpResponse() << llendl; -			llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint -		} -		return; +		LL_WARNS(LOG_MESH) << "Error during mesh LOD processing.  ID:  " << mMeshParams.getSculptID() +						   << ", Unknown reason.  Not retrying." +						   << LL_ENDL; +		LLMutexLock lock(gMeshRepo.mThread->mMutex); +		gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, mLOD));  	} +} -	LLMeshRepository::sBytesReceived += mRequestedBytes; +LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() +{ +		llassert(mProcessed); +} -	U8* data = NULL; +void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) +{ +	LL_WARNS(LOG_MESH) << "Error during mesh skin info handling.  ID:  " << mMeshID +					   << ", Reason:  " << status.toString() +					   << " (" << status.toTerseString() << ").  Not retrying." +					   << LL_ENDL; -	if (data_size > 0) -	{ -		data = new U8[data_size]; -		buffer->readAfter(channels.in(), NULL, data, data_size); -	} +	// *TODO:  Mark mesh unavailable on error.  For now, simply leave +	// request unfulfilled rather than retry forever. +} -	if (gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size)) +void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ +	if ((! MESH_SKIN_INFO_PROCESS_FAILED) && 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; @@ -2171,142 +2909,108 @@ void LLMeshPhysicsShapeResponder::completedRaw(const LLChannelDescriptors& chann  		if (file.getSize() >= offset+size)  		{  			LLMeshRepository::sCacheBytesWritten += size; +			++LLMeshRepository::sCacheWrites;  			file.seek(offset);  			file.write(data, size);  		}  	} - -	delete [] data; +	else +	{ +		LL_WARNS(LOG_MESH) << "Error during mesh skin info processing.  ID:  " << mMeshID +						   << ", Unknown reason.  Not retrying." +						   << LL_ENDL; +		// *TODO:  Mark mesh unavailable on error +	}  } -void LLMeshHeaderResponder::completedRaw(const LLChannelDescriptors& channels, -										 const LLIOPipe::buffer_ptr_t& buffer) +LLMeshDecompositionHandler::~LLMeshDecompositionHandler()  { -	S32 status = getStatus(); -	mProcessed = true; +		llassert(mProcessed); +} -	// thread could have already be destroyed during logout -	if( !gMeshRepo.mThread ) -	{ -		return; -	} +void LLMeshDecompositionHandler::processFailure(LLCore::HttpStatus status) +{ +	LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling.  ID:  " << mMeshID +					   << ", Reason:  " << status.toString() +					   << " (" << status.toTerseString() << ").  Not retrying." +					   << LL_ENDL; +	// *TODO:  Mark mesh unavailable on error.  For now, simply leave +	// request unfulfilled rather than retry forever. +} -	// *TODO: What about 3xx redirect codes? What about status 400 (Bad Request)? -	if (status < 200 || status > 400) +void LLMeshDecompositionHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ +	if ((! MESH_DECOMP_PROCESS_FAILED) && gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size))  	{ -		//llwarns -		//	<< "Header responder failed with status: " -		//	<< status << ": " << reason << llendl; - -		// 503 (service unavailable) or 499 (internal Linden-generated error) -		// 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 +		// good fetch from sim, write to VFS for caching +		LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); -		if (status == HTTP_SERVICE_UNAVAILABLE || status == HTTP_REQUEST_TIME_OUT || status == HTTP_INTERNAL_ERROR) -		{ //retry -			llwarns << "Timeout or service unavailable, retrying." << llendl; -			LLMeshRepository::sHTTPRetryCount++; -			LLMeshRepoThread::HeaderRequest req(mMeshParams); -			LLMutexLock lock(gMeshRepo.mThread->mMutex); -			gMeshRepo.mThread->mHeaderReqQ.push(req); +		S32 offset = mOffset; +		S32 size = mRequestedBytes; -			return; -		} -		else +		if (file.getSize() >= offset+size)  		{ -			llwarns << "Unhandled status " << dumpResponse() << llendl; +			LLMeshRepository::sCacheBytesWritten += size; +			++LLMeshRepository::sCacheWrites; +			file.seek(offset); +			file.write(data, size);  		}  	} - -	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); +		LL_WARNS(LOG_MESH) << "Error during mesh decomposition processing.  ID:  " << mMeshID +						   << ", Unknown reason.  Not retrying." +						   << LL_ENDL; +		// *TODO:  Mark mesh unavailable on error  	} +} -	LLMeshRepository::sBytesReceived += llmin(data_size, 4096); +LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler() +{ +		llassert(mProcessed); +} -	bool success = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); -	 -	llassert(success); +void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status) +{ +	LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling.  ID:  " << mMeshID +					   << ", Reason:  " << status.toString() +					   << " (" << status.toTerseString() << ").  Not retrying." +					   << LL_ENDL; +	// *TODO:  Mark mesh unavailable on error +} -	if (!success) -	{ -		llwarns << "Unable to parse mesh header: " << dumpResponse() << llendl; -	} -	else if (data && data_size > 0) +void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ +	if ((! MESH_PHYS_SHAPE_PROCESS_FAILED) && 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; +	else +	{ +		LL_WARNS(LOG_MESH) << "Error during mesh physics shape processing.  ID:  " << mMeshID +						   << ", Unknown reason.  Not retrying." +						   << LL_ENDL; +		// *TODO:  Mark mesh unavailable on error +	}  } -  LLMeshRepository::LLMeshRepository()  : mMeshMutex(NULL),    mMeshThreadCount(0), -  mThread(NULL) +  mThread(NULL), +  mGetMeshVersion(2)  {  } @@ -2325,7 +3029,7 @@ void LLMeshRepository::init()  		apr_sleep(100);  	} -	 +	metrics_teleport_started_signal = LLViewerMessage::getInstance()->setTeleportStartedCallback(teleport_started);  	mThread = new LLMeshRepoThread();  	mThread->start(); @@ -2333,11 +3037,13 @@ void LLMeshRepository::init()  void LLMeshRepository::shutdown()  { -	llinfos << "Shutting down mesh repository." << llendl; +	LL_INFOS(LOG_MESH) << "Shutting down mesh repository." << LL_ENDL; + +	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.  	} @@ -2352,7 +3058,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 + 1) << "/" << mUploads.size() << LL_ENDL;  		while (!mUploads[i]->isStopped())  		{  			apr_sleep(10); @@ -2365,7 +3071,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)  	{ @@ -2380,6 +3086,9 @@ void LLMeshRepository::shutdown()  //called in the main thread.  S32 LLMeshRepository::update()  { +	// Conditionally log a mesh metrics event +	metricsUpdate(); +	  	if(mUploadWaitList.empty())  	{  		return 0 ; @@ -2399,7 +3108,12 @@ S32 LLMeshRepository::update()  S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod)  { -	if (detail < 0 || detail > 4) +	MESH_FASTTIMER_DEFBLOCK; +	 +	// Manage time-to-load metrics for mesh download operations. +	metricsProgress(1); + +	if (detail < 0 || detail >= 4)  	{  		return detail;  	} @@ -2477,9 +3191,32 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para  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(); )  	{ @@ -2556,26 +3293,46 @@ 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(); +				const bool use_v1(gSavedSettings.getBOOL("MeshUseGetMesh1")); +				const std::string mesh1(gAgent.getRegion()->getCapability("GetMesh")); +				const std::string mesh2(gAgent.getRegion()->getCapability("GetMesh2")); +				mGetMeshVersion = (mesh2.empty() || use_v1) ? 1 : 2; +				mThread->setGetMeshCaps(mesh1, mesh2, mGetMeshVersion); +				LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name +									<< "', GetMesh2:  " << mesh2 +									<< ", GetMesh:  " << mesh1 +									<< ", using version:  " << mGetMeshVersion +									<< LL_ENDL; +			}  		} -	} - -	{ -		LLMutexLock lock1(mMeshMutex); -		LLMutexLock lock2(mThread->mMutex);  		//popup queued error messages from background threads  		while (!mUploadErrorQ.empty()) @@ -2584,47 +3341,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)  			{ @@ -2678,9 +3443,8 @@ void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo& info)  				vobj->notifyMeshLoaded();  			}  		} +		mLoadingSkins.erase(info.mMeshID);  	} - -	mLoadingSkins.erase(info.mMeshID);  }  void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decomp) @@ -2689,14 +3453,14 @@ void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decom  	if (iter == mDecompositionMap.end())  	{ //just insert decomp into map  		mDecompositionMap[decomp->mMeshID] = decomp; +		mLoadingDecompositions.erase(decomp->mMeshID);  	}  	else  	{ //merge decomp with existing entry  		iter->second->merge(decomp); +		mLoadingDecompositions.erase(decomp->mMeshID);  		delete decomp;  	} - -	mLoadingDecompositions.erase(decomp->mMeshID);  }  void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume) @@ -2711,7 +3475,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 @@ -2724,7 +3489,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;  			}  		} @@ -2778,6 +3544,8 @@ S32 LLMeshRepository::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lo  const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, const LLVOVolume* requesting_obj)  { +	MESH_FASTTIMER_DEFBLOCK; +  	if (mesh_id.notNull())  	{  		skin_map::iterator iter = mSkinMap.find(mesh_id); @@ -2804,6 +3572,8 @@ const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, const  void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id)  { +	MESH_FASTTIMER_DEFBLOCK; +  	if (mesh_id.notNull())  	{  		LLModel::Decomposition* decomp = NULL; @@ -2821,6 +3591,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);  			} @@ -2831,6 +3602,8 @@ void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id)  LLModel::Decomposition* LLMeshRepository::getDecomposition(const LLUUID& mesh_id)  { +	MESH_FASTTIMER_DEFBLOCK; +  	LLModel::Decomposition* ret = NULL;  	if (mesh_id.notNull()) @@ -2893,6 +3666,8 @@ bool LLMeshRepository::hasPhysicsShape(const LLUUID& mesh_id)  LLSD& LLMeshRepository::getMeshHeader(const LLUUID& mesh_id)  { +	MESH_FASTTIMER_DEFBLOCK; +  	return mThread->getMeshHeader(mesh_id);  } @@ -2914,7 +3689,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,  @@ -2943,7 +3718,6 @@ S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod)  	}  	return -1; -  }  void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation, @@ -3208,7 +3982,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;  		}  	}  } @@ -3284,7 +4058,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(); @@ -3413,9 +4188,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 @@ -3720,3 +4495,63 @@ bool LLMeshRepository::meshRezEnabled()  	}  	return false;  } + +// Threading:  main thread only +// static +void LLMeshRepository::metricsStart() +{ +	++metrics_teleport_start_count; +	sQuiescentTimer.start(0); +} + +// Threading:  main thread only +// static +void LLMeshRepository::metricsStop() +{ +	sQuiescentTimer.stop(0); +} + +// Threading:  main thread only +// static +void LLMeshRepository::metricsProgress(unsigned int this_count) +{ +	static bool first_start(true); + +	if (first_start) +	{ +		metricsStart(); +		first_start = false; +	} +	sQuiescentTimer.ringBell(0, this_count); +} + +// Threading:  main thread only +// static +void LLMeshRepository::metricsUpdate() +{ +	F64 started, stopped; +	U64 total_count(U64L(0)), user_cpu(U64L(0)), sys_cpu(U64L(0)); +	 +	if (sQuiescentTimer.isExpired(0, started, stopped, total_count, user_cpu, sys_cpu)) +	{ +		LLSD metrics; + +		metrics["reason"] = "Mesh Download Quiescent"; +		metrics["scope"] = metrics_teleport_start_count > 1 ? "Teleport" : "Login"; +		metrics["start"] = started; +		metrics["stop"] = stopped; +		metrics["fetches"] = LLSD::Integer(total_count); +		metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count); +		metrics["user_cpu"] = double(user_cpu) / 1.0e6; +		metrics["sys_cpu"] = double(sys_cpu) / 1.0e6; +		LL_INFOS(LOG_MESH) << "EventMarker " << metrics << LL_ENDL; +	} +} + +// Threading:  main thread only +// static +void teleport_started() +{ +	LLMeshRepository::metricsStart(); +} + diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 8eaf691d6f..39280bea3a 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -4,7 +4,7 @@   *   * $LicenseInfo:firstyear=2001&license=viewerlgpl$   * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2010-2013, Linden Research, Inc.   *    * This library is free software; you can redistribute it and/or   * modify it under the terms of the GNU Lesser General Public @@ -32,6 +32,12 @@  #include "lluuid.h"  #include "llviewertexture.h"  #include "llvolume.h" +#include "lldeadmantimer.h" +#include "httpcommon.h" +#include "httprequest.h" +#include "httpoptions.h" +#include "httpheaders.h" +#include "httphandler.h"  #define LLCONVEXDECOMPINTER_STATIC 1 @@ -39,8 +45,6 @@  #include "lluploadfloaterobservers.h"  class LLVOVolume; -class LLMeshResponder; -class LLCurlRequest;  class LLMutex;  class LLCondition;  class LLVFS; @@ -215,17 +219,17 @@ class LLMeshRepoThread : public LLThread  {  public: -	static S32 sActiveHeaderRequests; -	static S32 sActiveLODRequests; +	volatile static S32 sActiveHeaderRequests; +	volatile static S32 sActiveLODRequests;  	static U32 sMaxConcurrentRequests; +	static S32 sRequestLowWater; +	static S32 sRequestHighWater; +	static S32 sRequestWaterLevel;			// Stats-use only, may read outside of thread -	LLCurlRequest* mCurlRequest;  	LLMutex*	mMutex;  	LLMutex*	mHeaderMutex;  	LLCondition* mSignal; -	bool mWaiting; -  	//map of known mesh headers  	typedef std::map<LLUUID, LLSD> mesh_header_map;  	mesh_header_map mMeshHeader; @@ -287,8 +291,8 @@ public:  	//set of requested skin info  	std::set<LLUUID> mSkinRequests; -	//queue of completed skin info requests -	std::queue<LLMeshSkinInfo> mSkinInfoQ; +	// list of completed skin info requests +	std::list<LLMeshSkinInfo> mSkinInfoQ;  	//set of requested decompositions  	std::set<LLUUID> mDecompositionRequests; @@ -296,8 +300,8 @@ public:  	//set of requested physics shapes  	std::set<LLUUID> mPhysicsShapeRequests; -	//queue of completed Decomposition info requests -	std::queue<LLModel::Decomposition*> mDecompositionQ; +	// list of completed Decomposition info requests +	std::list<LLModel::Decomposition*> mDecompositionQ;  	//queue of requested headers  	std::queue<HeaderRequest> mHeaderReqQ; @@ -315,7 +319,23 @@ public:  	typedef std::map<LLVolumeParams, std::vector<S32> > pending_lod_map;  	pending_lod_map mPendingLOD; -	static std::string constructUrl(LLUUID mesh_id); +	// llcorehttp library interface objects. +	LLCore::HttpStatus					mHttpStatus; +	LLCore::HttpRequest *				mHttpRequest; +	LLCore::HttpOptions *				mHttpOptions; +	LLCore::HttpOptions *				mHttpLargeOptions; +	LLCore::HttpHeaders *				mHttpHeaders; +	LLCore::HttpRequest::policy_t		mHttpPolicyClass; +	LLCore::HttpRequest::policy_t		mHttpLegacyPolicyClass; +	LLCore::HttpRequest::policy_t		mHttpLargePolicyClass; +	LLCore::HttpRequest::priority_t		mHttpPriority; + +	typedef std::set<LLCore::HttpHandler *> http_request_set; +	http_request_set					mHttpRequestSet;			// Outstanding HTTP requests + +	std::string mGetMeshCapability; +	std::string mGetMesh2Capability; +	int mGetMeshVersion;  	LLMeshRepoThread();  	~LLMeshRepoThread(); @@ -325,8 +345,8 @@ public:  	void lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod);  	void loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); -	bool fetchMeshHeader(const LLVolumeParams& mesh_params, U32& count); -	bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count); +	bool fetchMeshHeader(const LLVolumeParams& mesh_params); +	bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod);  	bool headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size);  	bool lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size);  	bool skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size); @@ -358,9 +378,37 @@ public:  	static void incActiveHeaderRequests();  	static void decActiveHeaderRequests(); +	// Set the caps strings and preferred version for constructing +	// mesh fetch URLs. +	// +	// Mutex:  must be holding mMutex when called +	void setGetMeshCaps(const std::string & get_mesh1, +						const std::string & get_mesh2, +						int pref_version); + +	// Mutex:  acquires mMutex +	void constructUrl(LLUUID mesh_id, std::string * url, int * version); + +private: +	// Issue a GET request to a URL with 'Range' header using +	// the correct policy class and other attributes.  If an invalid +	// handle is returned, the request failed and caller must retry +	// or dispose of handler. +	// +	// Threads:  Repo thread only +	LLCore::HttpHandle getByteRange(const std::string & url, int cap_version, +									size_t offset, size_t len,  +									LLCore::HttpHandler * handler);  }; -class LLMeshUploadThread : public LLThread  + +// Class whose instances represent a single upload-type request for +// meshes:  one fee query or one actual upload attempt.  Yes, it creates +// a unique thread for that single request.  As it is 1:1, it can also +// trivially serve as the HttpHandler object for request completion +// notifications. + +class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler   {  private:  	S32 mMeshUploadTimeOut ; //maximum time in seconds to execute an uploading request. @@ -381,44 +429,41 @@ public:  	};  	LLPointer<DecompRequest> mFinalDecomp; -	bool mPhysicsComplete; +	volatile bool	mPhysicsComplete;  	typedef std::map<LLPointer<LLModel>, std::vector<LLVector3> > hull_map; -	hull_map mHullMap; +	hull_map		mHullMap;  	typedef std::vector<LLModelInstance> instance_list; -	instance_list mInstanceList; +	instance_list	mInstanceList;  	typedef std::map<LLPointer<LLModel>, instance_list> instance_map; -	instance_map mInstance; +	instance_map	mInstance; -	LLMutex*					mMutex; -	LLCurlRequest* mCurlRequest; +	LLMutex*		mMutex;  	S32				mPendingUploads;  	LLVector3		mOrigin;  	bool			mFinished;	  	bool			mUploadTextures;  	bool			mUploadSkin;  	bool			mUploadJoints; -	BOOL            mDiscarded ; +	volatile bool	mDiscarded;  	LLHost			mHost;  	std::string		mWholeModelFeeCapability;  	std::string		mWholeModelUploadURL;  	LLMeshUploadThread(instance_list& data, LLVector3& scale, bool upload_textures, -			bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload = true, -					   LLHandle<LLWholeModelFeeObserver> fee_observer= (LLHandle<LLWholeModelFeeObserver>()), LLHandle<LLWholeModelUploadObserver> upload_observer = (LLHandle<LLWholeModelUploadObserver>())); +					   bool upload_skin, bool upload_joints, const std::string & upload_url, bool do_upload = true, +					   LLHandle<LLWholeModelFeeObserver> fee_observer = (LLHandle<LLWholeModelFeeObserver>()), +					   LLHandle<LLWholeModelUploadObserver> upload_observer = (LLHandle<LLWholeModelUploadObserver>()));  	~LLMeshUploadThread(); -	void startRequest() { ++mPendingUploads; } -	void stopRequest() { --mPendingUploads; } - -	bool finished() { return mFinished; } +	bool finished() const { return mFinished; }  	virtual void run();  	void preStart();  	void discard() ; -	BOOL isDiscarded(); +	bool isDiscarded() const;  	void generateHulls(); @@ -435,11 +480,23 @@ public:  	void setFeeObserverHandle(LLHandle<LLWholeModelFeeObserver> observer_handle) { mFeeObserverHandle = observer_handle; }  	void setUploadObserverHandle(LLHandle<LLWholeModelUploadObserver> observer_handle) { mUploadObserverHandle = observer_handle; } +	// Inherited from LLCore::HttpHandler +	virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); +  private:  	LLHandle<LLWholeModelFeeObserver> mFeeObserverHandle;  	LLHandle<LLWholeModelUploadObserver> mUploadObserverHandle;  	bool mDoUpload; // if FALSE only model data will be requested, otherwise the model will be uploaded +	LLSD mModelData; +	 +	// llcorehttp library interface objects. +	LLCore::HttpStatus					mHttpStatus; +	LLCore::HttpRequest *				mHttpRequest; +	LLCore::HttpOptions *				mHttpOptions; +	LLCore::HttpHeaders *				mHttpHeaders; +	LLCore::HttpRequest::policy_t		mHttpPolicyClass; +	LLCore::HttpRequest::priority_t		mHttpPriority;  };  class LLMeshRepository @@ -448,21 +505,28 @@ public:  	//metrics  	static U32 sBytesReceived; -	static U32 sHTTPRequestCount; -	static U32 sHTTPRetryCount; +	static U32 sMeshRequestCount;				// Total request count, http or cached, all component types +	static U32 sHTTPRequestCount;				// Http GETs issued (not large) +	static U32 sHTTPLargeRequestCount;			// Http GETs issued for large requests +	static U32 sHTTPRetryCount;					// Total request retries whether successful or failed +	static U32 sHTTPErrorCount;					// Requests ending in error  	static U32 sLODPending;  	static U32 sLODProcessing;  	static U32 sCacheBytesRead;  	static U32 sCacheBytesWritten; -	static U32 sPeakKbps; +	static U32 sCacheReads;						 +	static U32 sCacheWrites; +	static U32 sMaxLockHoldoffs;				// Maximum sequential locking failures +	static LLDeadmanTimer sQuiescentTimer;		// Time-to-complete-mesh-downloads after significant events +  	static F32 getStreamingCost(LLSD& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL);  	LLMeshRepository();  	void init();  	void shutdown(); -	S32 update() ; +	S32 update();  	//mesh management functions  	S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 detail = 0, S32 last_lod = -1); @@ -495,6 +559,12 @@ public:  	S32 getMeshSize(const LLUUID& mesh_id, S32 lod); +	// Quiescent timer management, main thread only. +	static void metricsStart(); +	static void metricsStop(); +	static void metricsProgress(unsigned int count); +	static void metricsUpdate(); +	  	typedef std::map<LLVolumeParams, std::set<LLUUID> > mesh_load_map;  	mesh_load_map mLoadingMeshes[4]; @@ -556,8 +626,7 @@ public:  	void uploadError(LLSD& args);  	void updateInventory(inventory_data data); -	std::string mGetMeshCapability; - +	int mGetMeshVersion;		// Shadows value in LLMeshRepoThread  };  extern LLMeshRepository gMeshRepo; diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index d0ab9f55ff..6ed54ce019 100755 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -1606,8 +1606,8 @@ bool LLTextureFetchWorker::doWork(S32 param)  				}  				else  				{ -					llinfos << "other: HTTP GET failed for: " << mUrl -							<< " Status: " << mGetStatus.toHex() +					llinfos << "HTTP GET failed for: " << mUrl +							<< " Status: " << mGetStatus.toTerseString()  							<< " Reason: '" << mGetReason << "'"  							<< llendl;  				} @@ -1987,7 +1987,7 @@ void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRe  	}  	LL_DEBUGS("Texture") << "HTTP COMPLETE: " << mID -						 << " status: " << status.toHex() +						 << " status: " << status.toTerseString()  						 << " '" << status.toString() << "'"  						 << llendl; @@ -2006,7 +2006,9 @@ void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRe  		success = false;  		if (mFTType != FTT_MAP_TILE) // missing map tiles are normal, don't complain about them.  		{ -			llwarns << mID << " CURL GET FAILED, status: " << status.toHex() +			std::string reason(status.toString()); +			setGetStatus(status, reason); +			llwarns << "CURL GET FAILED, status: " << status.toTerseString()  					<< " reason: " << reason << llendl;  		}  	} @@ -2505,11 +2507,10 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image  	mHttpOptionsWithHeaders = new LLCore::HttpOptions;  	mHttpOptionsWithHeaders->setWantHeaders(true);  	mHttpHeaders = new LLCore::HttpHeaders; -	// *TODO: Should this be 'image/j2c' instead of 'image/x-j2c' ? -	mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_IMAGE_X_J2C); +	mHttpHeaders->append("Accept", "image/x-j2c");  	mHttpMetricsHeaders = new LLCore::HttpHeaders; -	mHttpMetricsHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); -	mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicyDefault(); +	mHttpMetricsHeaders->append("Content-Type", "application/llsd+xml"); +	mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_TEXTURE);  }  LLTextureFetch::~LLTextureFetch() @@ -3884,7 +3885,7 @@ public:  		else  		{  			LL_WARNS("Texture") << "Error delivering asset metrics to grid.  Status:  " -								<< status.toHex() +								<< status.toTerseString()  								<< ", Reason:  " << status.toString() << LL_ENDL;  		}  	} @@ -4153,8 +4154,7 @@ void LLTextureFetchDebugger::init()  	if (! mHttpHeaders)  	{  		mHttpHeaders = new LLCore::HttpHeaders; -		// *TODO: Should this be 'image/j2c' instead of 'image/x-j2c' ? -		mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_IMAGE_X_J2C); +		mHttpHeaders->append("Accept", "image/x-j2c");  	}  } @@ -4574,7 +4574,7 @@ S32 LLTextureFetchDebugger::fillCurlQueue()  			LL_WARNS("Texture") << "Couldn't issue HTTP request in debugger for texture "  								<< mFetchingHistory[i].mID -								<< ", status: " << status.toHex() +								<< ", status: " << status.toTerseString()  								<< " reason:  " << status.toString()  								<< LL_ENDL;  			mFetchingHistory[i].mCurlState = FetchEntry::CURL_DONE; @@ -4967,7 +4967,7 @@ void LLTextureFetchDebugger::callbackHTTP(FetchEntry & fetch, LLCore::HttpRespon  	else //failed  	{  		llinfos << "Fetch Debugger : CURL GET FAILED,  ID = " << fetch.mID -				<< ", status: " << status.toHex() +				<< ", status: " << status.toTerseString()  				<< " reason:  " << status.toString() << llendl;  	}  } diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index dea1c6c1b2..0582bf4162 100755 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -4,7 +4,7 @@   *   * $LicenseInfo:firstyear=2001&license=viewerlgpl$   * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, Linden Research, Inc.   *    * This library is free software; you can redistribute it and/or   * modify it under the terms of the GNU Lesser General Public @@ -49,6 +49,7 @@  #include "llviewertexturelist.h"  #include "llvovolume.h"  #include "llviewerstats.h" +#include "llmeshrepository.h"  // For avatar texture view  #include "llvoavatarself.h" @@ -509,6 +510,8 @@ void LLGLTexMemBar::draw()  	F32 total_texture_downloaded = (F32)gTotalTextureBytes / (1024 * 1024);  	F32 total_object_downloaded = (F32)gTotalObjectBytes / (1024 * 1024);  	U32 total_http_requests = LLAppViewer::getTextureFetch()->getTotalNumHTTPRequests(); +	F32 x_right = 0.0; +	  	//----------------------------------------------------------------------------  	LLGLSUIDefault gls_ui;  	LLColor4 text_color(1.f, 1.f, 1.f, 0.75f); @@ -535,7 +538,7 @@ void LLGLTexMemBar::draw()  					cache_max_usage);  	//, cache_entries, cache_max_entries -	LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*4, +	LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*5,  											 text_color, LLFontGL::LEFT, LLFontGL::TOP);  	U32 cache_read(0U), cache_write(0U), res_wait(0U); @@ -549,13 +552,12 @@ void LLGLTexMemBar::draw()  					cache_write,  					res_wait); -	LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3, +	LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*4,  											 text_color, LLFontGL::LEFT, LLFontGL::TOP); -	S32 left = 0 ;  	//---------------------------------------------------------------------------- -	text = llformat("Textures: %d Fetch: %d(%d) Pkts:%d(%d) Cache R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d", +	text = llformat("Textures: %d Fetch: %d(%d) Pkts:%d(%d) Cache R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d ",  					gTextureList.getNumImages(),  					LLAppViewer::getTextureFetch()->getNumRequests(), LLAppViewer::getTextureFetch()->getNumDeletes(),  					LLAppViewer::getTextureFetch()->mPacketCount, LLAppViewer::getTextureFetch()->mBadPacketCount,  @@ -566,19 +568,30 @@ void LLGLTexMemBar::draw()  					LLAppViewer::getImageDecodeThread()->getPending(),   					gTextureList.mCreateTextureList.size()); -	LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*2, -									 text_color, LLFontGL::LEFT, LLFontGL::TOP); - +	x_right = 550.0; +	LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3, +											 text_color, LLFontGL::LEFT, LLFontGL::TOP, +											 LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, +											 &x_right, FALSE); -	left = 550;  	F32 bandwidth = LLAppViewer::getTextureFetch()->getTextureBandwidth();  	F32 max_bandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS"); -	color = bandwidth > max_bandwidth ? LLColor4::red : bandwidth > max_bandwidth*.75f ? LLColor4::yellow : text_color; +	color = bandwidth > max_bandwidth ? LLColor4::red : bandwidth > max_bandwidth * .75f ? LLColor4::yellow : text_color;  	color[VALPHA] = text_color[VALPHA]; -	text = llformat("BW:%.0f/%.0f",bandwidth, max_bandwidth); -	LLFontGL::getFontMonospace()->renderUTF8(text, 0, left, v_offset + line_height*2, +	text = llformat("BW:%.0f/%.0f", bandwidth, max_bandwidth); +	LLFontGL::getFontMonospace()->renderUTF8(text, 0, x_right, v_offset + line_height*3,  											 color, LLFontGL::LEFT, LLFontGL::TOP); -	 + +	// Mesh status line +	text = llformat("Mesh: Reqs(Tot/Htp/Big): %u/%u/%u Rtr/Err: %u/%u Cread/Cwrite: %u/%u Low/At/High: %d/%d/%d", +					LLMeshRepository::sMeshRequestCount, LLMeshRepository::sHTTPRequestCount, LLMeshRepository::sHTTPLargeRequestCount, +					LLMeshRepository::sHTTPRetryCount, LLMeshRepository::sHTTPErrorCount, +					LLMeshRepository::sCacheReads, LLMeshRepository::sCacheWrites, +					LLMeshRepoThread::sRequestLowWater, LLMeshRepoThread::sRequestWaterLevel, LLMeshRepoThread::sRequestHighWater); +	LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*2, +											 text_color, LLFontGL::LEFT, LLFontGL::TOP); + +	// Header for texture table columns  	S32 dx1 = 0;  	if (LLAppViewer::getTextureFetch()->mDebugPause)  	{ @@ -621,7 +634,7 @@ BOOL LLGLTexMemBar::handleMouseDown(S32 x, S32 y, MASK mask)  LLRect LLGLTexMemBar::getRequiredRect()  {  	LLRect rect; -	rect.mTop = 50; //LLFontGL::getFontMonospace()->getLineHeight() * 6; +	rect.mTop = 68; //LLFontGL::getFontMonospace()->getLineHeight() * 6;  	return rect;  } diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index e6cc75f1ce..76d155d5f2 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -4,7 +4,7 @@   *   * $LicenseInfo:firstyear=2000&license=viewerlgpl$   * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2010-2013, Linden Research, Inc.   *    * This library is free software; you can redistribute it and/or   * modify it under the terms of the GNU Lesser General Public @@ -1653,6 +1653,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)  	capabilityNames.append("GetDisplayNames");  	capabilityNames.append("GetMesh"); +	capabilityNames.append("GetMesh2");  	capabilityNames.append("GetObjectCost");  	capabilityNames.append("GetObjectPhysicsData");  	capabilityNames.append("GetTexture"); | 
