diff options
| author | Aaron Terrell (Enus) <enus@lindenlab.com> | 2010-08-30 11:56:33 -0700 | 
|---|---|---|
| committer | Aaron Terrell (Enus) <enus@lindenlab.com> | 2010-08-30 11:56:33 -0700 | 
| commit | d04e8d757575dbb50877ef861adcfde427837bb6 (patch) | |
| tree | b214237a4246e523754b356b003d1d2a163f21ab /indra/newview/llmediadataclient.cpp | |
| parent | 3172b4db1ee4d2059d2605ce616bdb10c840fc03 (diff) | |
| parent | d84c6625c707b5d61b71749b74f3cc4fb8f02048 (diff) | |
merging up from viewer-development
Diffstat (limited to 'indra/newview/llmediadataclient.cpp')
| -rw-r--r-- | indra/newview/llmediadataclient.cpp | 1030 | 
1 files changed, 647 insertions, 383 deletions
| diff --git a/indra/newview/llmediadataclient.cpp b/indra/newview/llmediadataclient.cpp index da656d8b5e..31038b4aac 100644 --- a/indra/newview/llmediadataclient.cpp +++ b/indra/newview/llmediadataclient.cpp @@ -52,6 +52,32 @@  // - Any request that gets a 503 still goes through the retry logic  // +/*************************************************************************************************************** +	What's up with this queueing code? + +	First, a bit of background: + +	Media on a prim was added into the system in the Viewer 2.0 timeframe.  In order to avoid changing the  +	network format of objects, an unused field in the object (the "MediaURL" string) was repurposed to  +	indicate that the object had media data, and also hold a sequence number and the UUID of the agent  +	who last updated the data.  The actual media data for objects is accessed via the "ObjectMedia" capability.   +	Due to concerns about sim performance, requests to this capability are rate-limited to 5 requests every  +	5 seconds per agent. + +	The initial implementation of LLMediaDataClient used a single queue to manage requests to the "ObjectMedia" cap.   +	Requests to the cap were queued so that objects closer to the avatar were loaded in first, since they were most  +	likely to be the ones the media performance manager would load. + +	This worked in some cases, but we found that it was possible for a scripted object that constantly updated its  +	media data to starve other objects, since the same queue contained both requests to load previously unseen media  +	data and requests to fetch media data in response to object updates. + +	The solution for this we came up with was to have two queues.  The sorted queue contains requests to fetch media  +	data for objects that don't have it yet, and the round-robin queue contains requests to update media data for  +	objects that have already completed their initial load.  When both queues are non-empty, the code ping-pongs  +	between them so that updates can't completely block initial load-in. +**************************************************************************************************************/ +  //  // Forward decls  // @@ -65,6 +91,54 @@ const U32 LLMediaDataClient::MAX_ROUND_ROBIN_QUEUE_SIZE = 10000;  std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q);  std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &q); +template <typename T> +static typename T::iterator find_matching_request(T &c, const LLMediaDataClient::Request *request, LLMediaDataClient::Request::Type match_type) +{ +	for(typename T::iterator iter = c.begin(); iter != c.end(); ++iter) +	{ +		if(request->isMatch(*iter, match_type)) +		{ +			return iter; +		} +	} +	 +	return c.end(); +} + +template <typename T> +static typename T::iterator find_matching_request(T &c, const LLUUID &id, LLMediaDataClient::Request::Type match_type) +{ +	for(typename T::iterator iter = c.begin(); iter != c.end(); ++iter) +	{ +		if(((*iter)->getID() == id) && ((match_type == LLMediaDataClient::Request::ANY) || (match_type == (*iter)->getType()))) +		{ +			return iter; +		} +	} +	 +	return c.end(); +} + +// NOTE: remove_matching_requests will not work correctly for containers where deleting an element may invalidate iterators +// to other elements in the container (such as std::vector). +// If the implementation is changed to use a container with this property, this will need to be revisited. +template <typename T> +static void remove_matching_requests(T &c, const LLUUID &id, LLMediaDataClient::Request::Type match_type) +{ +	for(typename T::iterator iter = c.begin(); iter != c.end();) +	{ +		typename T::value_type i = *iter; +		typename T::iterator next = iter; +		next++; +		if((i->getID() == id) && ((match_type == LLMediaDataClient::Request::ANY) || (match_type == i->getType()))) +		{ +			i->markDead(); +			c.erase(iter); +		} +		iter = next; +	} +} +  //////////////////////////////////////////////////////////////////////////////////////  //  // LLMediaDataClient @@ -81,117 +155,36 @@ LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay,  	  mMaxNumRetries(max_retries),  	  mMaxSortedQueueSize(max_sorted_queue_size),  	  mMaxRoundRobinQueueSize(max_round_robin_queue_size), -	  mQueueTimerIsRunning(false), -	  mCurrentQueueIsTheSortedQueue(true) +	  mQueueTimerIsRunning(false)  {  }  LLMediaDataClient::~LLMediaDataClient()  {  	stopQueueTimer(); - -	// This should clear the queue, and hopefully call all the destructors. -	LL_DEBUGS("LLMediaDataClient") << "~LLMediaDataClient destructor: queue: " <<  -		(isEmpty() ? "<empty> " : "<not empty> ") << LL_ENDL; -	 -	mSortedQueue.clear(); -	mRoundRobinQueue.clear();  }  bool LLMediaDataClient::isEmpty() const  { -	return mSortedQueue.empty() && mRoundRobinQueue.empty(); +	return mQueue.empty();  }  bool LLMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object)  { -	return (LLMediaDataClient::findOrRemove(mSortedQueue, object, false/*remove*/, LLMediaDataClient::Request::ANY).notNull() -		|| (LLMediaDataClient::findOrRemove(mRoundRobinQueue, object, false/*remove*/, LLMediaDataClient::Request::ANY).notNull())); -} - -bool LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) -{ -	bool removedFromSortedQueue = LLMediaDataClient::findOrRemove(mSortedQueue, object, true/*remove*/, LLMediaDataClient::Request::ANY).notNull(); -	bool removedFromRoundRobinQueue = LLMediaDataClient::findOrRemove(mRoundRobinQueue, object, true/*remove*/, LLMediaDataClient::Request::ANY).notNull(); -	return removedFromSortedQueue || removedFromRoundRobinQueue; -} - -//static -LLMediaDataClient::request_ptr_t LLMediaDataClient::findOrRemove(request_queue_t &queue, const LLMediaDataClientObject::ptr_t &obj, bool remove, LLMediaDataClient::Request::Type type) -{ -	request_ptr_t result; -	request_queue_t::iterator iter = queue.begin(); -	request_queue_t::iterator end = queue.end(); -	while (iter != end) -	{ -		if (obj->getID() == (*iter)->getObject()->getID() && (type == LLMediaDataClient::Request::ANY || type == (*iter)->getType())) -		{ -			result = *iter; -			if (remove) queue.erase(iter); -			break; -		} -		iter++; -	} -	return result; -} - -void LLMediaDataClient::request(const LLMediaDataClientObject::ptr_t &object, const LLSD &payload) -{ -	if (object.isNull() || ! object->hasMedia()) return;  +	if(find_matching_request(mQueue, object->getID()) != mQueue.end()) +		return true; +	 +	if(find_matching_request(mUnQueuedRequests, object->getID()) != mUnQueuedRequests.end()) +		return true; -	// Push the object on the queue -	enqueue(new Request(getCapabilityName(), payload, object, this)); +	return false;  } -void LLMediaDataClient::enqueue(const Request *request) +void LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object)  { -	if (request->isNew()) -	{		 -		// Add to sorted queue -		if (LLMediaDataClient::findOrRemove(mSortedQueue, request->getObject(), true/*remove*/, request->getType()).notNull()) -		{ -			LL_DEBUGS("LLMediaDataClient") << "REMOVING OLD request for " << *request << " ALREADY THERE!" << LL_ENDL; -		} -		 -		LL_DEBUGS("LLMediaDataClient") << "Queuing SORTED request for " << *request << LL_ENDL; -		 -		// Sadly, we have to const-cast because items put into the queue are not const -		mSortedQueue.push_back(const_cast<LLMediaDataClient::Request*>(request)); -		 -		LL_DEBUGS("LLMediaDataClientQueue") << "SORTED queue:" << mSortedQueue << LL_ENDL; -	} -	else { -		if (mRoundRobinQueue.size() > mMaxRoundRobinQueueSize)  -		{ -			LL_INFOS_ONCE("LLMediaDataClient") << "RR QUEUE MAXED OUT!!!" << LL_ENDL; -			LL_DEBUGS("LLMediaDataClient") << "Not queuing " << *request << LL_ENDL; -			return; -		} -				 -		// ROUND ROBIN: if it is there, and it is a GET request, leave it.  If not, put at front!		 -		request_ptr_t existing_request; -		if (request->getType() == Request::GET) -		{ -			existing_request = LLMediaDataClient::findOrRemove(mRoundRobinQueue, request->getObject(), false/*remove*/, request->getType()); -		} -		if (existing_request.isNull()) -		{ -			LL_DEBUGS("LLMediaDataClient") << "Queuing RR request for " << *request << LL_ENDL; -			// Push the request on the pending queue -			// Sadly, we have to const-cast because items put into the queue are not const -			mRoundRobinQueue.push_front(const_cast<LLMediaDataClient::Request*>(request)); -			 -			LL_DEBUGS("LLMediaDataClientQueue") << "RR queue:" << mRoundRobinQueue << LL_ENDL;			 -		} -		else -		{ -			LL_DEBUGS("LLMediaDataClient") << "ALREADY THERE: NOT Queuing request for " << *request << LL_ENDL; -						 -			existing_request->markSent(false); -		} -	}	 -	// Start the timer if not already running -	startQueueTimer(); +	LL_DEBUGS("LLMediaDataClient") << "removing requests matching ID " << object->getID() << LL_ENDL; +	remove_matching_requests(mQueue, object->getID()); +	remove_matching_requests(mUnQueuedRequests, object->getID());  }  void LLMediaDataClient::startQueueTimer()  @@ -203,7 +196,7 @@ void LLMediaDataClient::startQueueTimer()  		new QueueTimer(mQueueTimerDelay, this);  	}  	else {  -		LL_DEBUGS("LLMediaDataClient") << "not starting queue timer (it's already running, right???)" << LL_ENDL; +		LL_DEBUGS("LLMediaDataClient") << "queue timer is already running" << LL_ENDL;  	}  } @@ -214,179 +207,138 @@ void LLMediaDataClient::stopQueueTimer()  bool LLMediaDataClient::processQueueTimer()  { -	sortQueue(); -	 -	if(!isEmpty()) -	{ -		LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, SORTED queue size is:	  " << mSortedQueue.size()  -			<< ", RR queue size is:	  " << mRoundRobinQueue.size() << LL_ENDL; -		LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, SORTED queue is:	  " << mSortedQueue << LL_ENDL; -		LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, RR queue is:	  " << mRoundRobinQueue << LL_ENDL; -	} -	 +	if(isEmpty()) +		return true; + +	LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, queue size is:	  " << mQueue.size() << LL_ENDL; +	LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, SORTED queue is:	  " << mQueue << LL_ENDL; +			  	serviceQueue(); -	LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, SORTED queue size is:	  " << mSortedQueue.size()  -		<< ", RR queue size is:	  " << mRoundRobinQueue.size() << LL_ENDL; -	LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, SORTED queue is:	  " << mSortedQueue << LL_ENDL; -	LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, RR queue is:	  " << mRoundRobinQueue << LL_ENDL; +	LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, queue size is:	  " << mQueue.size() << LL_ENDL; +	LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, SORTED queue is:	  " << mQueue << LL_ENDL;  	return isEmpty();  } -void LLMediaDataClient::sortQueue() +LLMediaDataClient::request_ptr_t LLMediaDataClient::dequeue()  { -	if(!mSortedQueue.empty()) +	request_ptr_t request; +	request_queue_t *queue_p = getQueue(); +	 +	if (queue_p->empty())  	{ -		// Score all items first -		request_queue_t::iterator iter = mSortedQueue.begin(); -		request_queue_t::iterator end = mSortedQueue.end(); -		while (iter != end) +		LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL; +	} +	else +	{ +		request = queue_p->front(); +		 +		if(canServiceRequest(request))  		{ -			(*iter)->updateScore(); -			iter++; +			// We will be returning this request, so remove it from the queue. +			queue_p->pop_front();  		} -		 -		// Re-sort the list... -		// NOTE: should this be a stable_sort?  If so we need to change to using a vector. -		mSortedQueue.sort(LLMediaDataClient::compareRequests); -		 -		// ...then cull items over the max -		U32 size = mSortedQueue.size(); -		if (size > mMaxSortedQueueSize)  +		else  		{ -			U32 num_to_cull = (size - mMaxSortedQueueSize); -			LL_INFOS_ONCE("LLMediaDataClient") << "sorted queue MAXED OUT!  Culling "  -				<< num_to_cull << " items" << LL_ENDL; -			while (num_to_cull-- > 0) -			{ -				mSortedQueue.pop_back(); -			} +			// Don't return this request -- it's not ready to be serviced. +			request = NULL;  		}  	} + +	return request;  } -// static -bool LLMediaDataClient::compareRequests(const request_ptr_t &o1, const request_ptr_t &o2) +void LLMediaDataClient::pushBack(request_ptr_t request)  { -	if (o2.isNull()) return true; -	if (o1.isNull()) return false; -	return ( o1->getScore() > o2->getScore() ); +	request_queue_t *queue_p = getQueue(); +	queue_p->push_front(request); +} + +void LLMediaDataClient::trackRequest(request_ptr_t request) +{ +	request_set_t::iterator iter = mUnQueuedRequests.find(request); +	 +	if(iter != mUnQueuedRequests.end()) +	{ +		LL_WARNS("LLMediaDataClient") << "Tracking already tracked request: " << *request << LL_ENDL; +	} +	else +	{ +		mUnQueuedRequests.insert(request); +	} +} + +void LLMediaDataClient::stopTrackingRequest(request_ptr_t request) +{ +	request_set_t::iterator iter = mUnQueuedRequests.find(request); +	 +	if (iter != mUnQueuedRequests.end()) +	{ +		mUnQueuedRequests.erase(iter); +	} +	else +	{ +		LL_WARNS("LLMediaDataClient") << "Removing an untracked request: " << *request << LL_ENDL; +	}  }  void LLMediaDataClient::serviceQueue()  {	 -	request_queue_t *queue_p = getCurrentQueue(); +	// Peel one off of the items from the queue and execute it +	request_ptr_t request; -	// quick retry loop for cases where we shouldn't wait for the next timer tick -	while(true) +	do  	{ -		if (queue_p->empty()) +		request = dequeue(); + +		if(request.isNull())  		{ -			LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL; -			break; +			// Queue is empty. +			return;  		} -		 -		// Peel one off of the items from the queue, and execute request -		request_ptr_t request = queue_p->front(); -		llassert(!request.isNull()); -		const LLMediaDataClientObject *object = (request.isNull()) ? NULL : request->getObject(); -		llassert(NULL != object); -		 -		// Check for conditions that would make us just pop and rapidly loop through -		// the queue. -		if(request.isNull() || -		   request->isMarkedSent() || -		   NULL == object || -		   object->isDead() || -		   !object->hasMedia()) + +		if(request->isDead())  		{ -			if (request.isNull())  -			{ -				LL_WARNS("LLMediaDataClient") << "Skipping NULL request" << LL_ENDL; -			} -			else { -				LL_INFOS("LLMediaDataClient") << "Skipping : " << *request << " "  -				<< ((request->isMarkedSent()) ? " request is marked sent" : -					((NULL == object) ? " object is NULL " : -					 ((object->isDead()) ? "object is dead" :  -					  ((!object->hasMedia()) ? "object has no media!" : "BADNESS!")))) << LL_ENDL; -			} -			queue_p->pop_front(); -			continue;	// jump back to the start of the quick retry loop +			LL_INFOS("LLMediaDataClient") << "Skipping dead request " << *request << LL_ENDL; +			continue;  		} + +	} while(false); -		// Next, ask if this is "interesting enough" to fetch.  If not, just stop -		// and wait for the next timer go-round.  Only do this for the sorted  -		// queue. -		if (mCurrentQueueIsTheSortedQueue && !object->isInterestingEnough()) -		{ -			LL_DEBUGS("LLMediaDataClient") << "Not fetching " << *request << ": not interesting enough" << LL_ENDL; -			break; -		} +	// try to send the HTTP message to the cap url +	std::string url = request->getCapability(); +	if (!url.empty()) +	{ +		const LLSD &sd_payload = request->getPayload(); +		LL_INFOS("LLMediaDataClient") << "Sending request for " << *request << LL_ENDL; -		// Finally, try to send the HTTP message to the cap url -		std::string url = request->getCapability(); -		bool maybe_retry = false; -		if (!url.empty()) -		{ -			const LLSD &sd_payload = request->getPayload(); -			LL_INFOS("LLMediaDataClient") << "Sending request for " << *request << LL_ENDL; -			 -			// Call the subclass for creating the responder -			LLHTTPClient::post(url, sd_payload, createResponder(request)); -		} -		else { -			LL_INFOS("LLMediaDataClient") << "NOT Sending request for " << *request << ": empty cap url!" << LL_ENDL; -			maybe_retry = true; -		} - -		bool exceeded_retries = request->getRetryCount() > mMaxNumRetries; -		if (maybe_retry && ! exceeded_retries) // Try N times before giving up  +		// Add this request to the non-queued tracking list +		trackRequest(request); +		 +		// and make the post +		LLHTTPClient::post(url, sd_payload, request->createResponder()); +	} +	else  +	{ +		// Cap url doesn't exist. +		 +		if(request->getRetryCount() < mMaxNumRetries)  		{ -			// We got an empty cap, but in that case we will retry again next -			// timer fire. +			LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " (empty cap url), will retry." << LL_ENDL;  +			// Put this request back at the head of its queue, and retry next time the queue timer fires.  			request->incRetryCount(); +			pushBack(request);  		} -		else { -			if (exceeded_retries) -			{ -				LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for "  -					<< mMaxNumRetries << " tries...popping object id " << object->getID() << LL_ENDL;  -				// XXX Should we bring up a warning dialog?? -			} -			 -			queue_p->pop_front(); -			 -			if (! mCurrentQueueIsTheSortedQueue) { -				// Round robin -				request->markSent(true); -				mRoundRobinQueue.push_back(request);				 -			} +		else +		{ +			// This request has exceeded its maxumim retry count.  It will be dropped. +			LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for " << mMaxNumRetries << " tries, dropping request." << LL_ENDL;   		} -		 - 		// end of quick loop -- any cases where we want to loop will use 'continue' to jump back to the start. - 		break; -	} -	 -	swapCurrentQueue(); -} -void LLMediaDataClient::swapCurrentQueue() -{ -	// Swap -	mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; -	// If its empty, swap back -	if (getCurrentQueue()->empty())  -	{ -		mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue;  	}  } -LLMediaDataClient::request_queue_t *LLMediaDataClient::getCurrentQueue() -{ -	return (mCurrentQueueIsTheSortedQueue) ? &mSortedQueue : &mRoundRobinQueue; -}  // dump the queue  std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q) @@ -396,7 +348,7 @@ std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue  	LLMediaDataClient::request_queue_t::const_iterator end = q.end();  	while (iter != end)  	{ -		s << "\t" << i << "]: " << (*iter)->getObject()->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")"; +		s << "\t" << i << "]: " << (*iter)->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")";  		iter++;  		i++;  	} @@ -416,18 +368,24 @@ LLMediaDataClient::QueueTimer::QueueTimer(F32 time, LLMediaDataClient *mdc)  	mMDC->setIsRunning(true);  } -LLMediaDataClient::QueueTimer::~QueueTimer() -{ -	LL_DEBUGS("LLMediaDataClient") << "~QueueTimer" << LL_ENDL; -	mMDC->setIsRunning(false); -	mMDC = NULL; -} -  // virtual  BOOL LLMediaDataClient::QueueTimer::tick()  { -	if (mMDC.isNull()) return TRUE; -	return mMDC->processQueueTimer(); +	BOOL result = TRUE; +	 +	if (!mMDC.isNull()) +	{ +		result = mMDC->processQueueTimer(); +	 +		if(result) +		{ +			// This timer won't fire again.   +			mMDC->setIsRunning(false); +			mMDC = NULL; +		} +	} + +	return result;  } @@ -437,29 +395,30 @@ BOOL LLMediaDataClient::QueueTimer::tick()  //  ////////////////////////////////////////////////////////////////////////////////////// -LLMediaDataClient::Responder::RetryTimer::RetryTimer(F32 time, Responder *mdr) -: LLEventTimer(time), mResponder(mdr) +LLMediaDataClient::RetryTimer::RetryTimer(F32 time, request_ptr_t request) +: LLEventTimer(time), mRequest(request)  { +	mRequest->startTracking();  } -// virtual  -LLMediaDataClient::Responder::RetryTimer::~RetryTimer()  +// virtual +BOOL LLMediaDataClient::RetryTimer::tick()  { -	LL_DEBUGS("LLMediaDataClient") << "~RetryTimer" << *(mResponder->getRequest()) << LL_ENDL; -	 -	// XXX This is weird: Instead of doing the work in tick()  (which re-schedules -	// a timer, which might be risky), do it here, in the destructor.  Yes, it is very odd. -	// Instead of retrying, we just put the request back onto the queue -	LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *(mResponder->getRequest()) << " retrying" << LL_ENDL; -	mResponder->getRequest()->reEnqueue(); +	mRequest->stopTracking(); + +	if(mRequest->isDead()) +	{ +		LL_INFOS("LLMediaDataClient") << "RetryTimer fired for dead request: " << *mRequest << ", aborting." << LL_ENDL; +	} +	else +	{ +		LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *mRequest << ", retrying." << LL_ENDL; +		mRequest->reEnqueue(); +	} -	// Release the ref to the responder. -	mResponder = NULL; -} +	// Release the ref to the request. +	mRequest = NULL; -// virtual -BOOL LLMediaDataClient::Responder::RetryTimer::tick() -{  	// Don't fire again  	return TRUE;  } @@ -472,56 +431,37 @@ BOOL LLMediaDataClient::Responder::RetryTimer::tick()  //////////////////////////////////////////////////////////////////////////////////////  /*static*/U32 LLMediaDataClient::Request::sNum = 0; -LLMediaDataClient::Request::Request(const char *cap_name,  -									const LLSD& sd_payload, +LLMediaDataClient::Request::Request(Type in_type,  									LLMediaDataClientObject *obj,  -									LLMediaDataClient *mdc) -: mCapName(cap_name),  -  mPayload(sd_payload),  +									LLMediaDataClient *mdc, +									S32 face) +: mType(in_type),    mObject(obj),    mNum(++sNum),     mRetryCount(0),    mMDC(mdc), -  mMarkedSent(false), -  mScore((F64)0.0) +  mScore((F64)0.0), +  mFace(face)  { +	mObjectID = mObject->getID();  } -LLMediaDataClient::Request::~Request() +const char *LLMediaDataClient::Request::getCapName() const  { -	LL_DEBUGS("LLMediaDataClient") << "~Request" << (*this) << LL_ENDL; -	mMDC = NULL; -	mObject = NULL; +	if(mMDC) +		return mMDC->getCapabilityName(); +	 +	return "";  } -  std::string LLMediaDataClient::Request::getCapability() const  { -	return getObject()->getCapabilityUrl(getCapName()); -} - -// Helper function to get the "type" of request, which just pokes around to -// discover it. -LLMediaDataClient::Request::Type LLMediaDataClient::Request::getType() const -{ -	if (0 == strcmp(mCapName, "ObjectMediaNavigate")) +	if(mMDC)  	{ -		return NAVIGATE; -	} -	else if (0 == strcmp(mCapName, "ObjectMedia")) -	{ -		const std::string &verb = mPayload["verb"]; -		if (verb == "GET") -		{ -			return GET; -		} -		else if (verb == "UPDATE") -		{ -			return UPDATE; -		} +		return getObject()->getCapabilityUrl(getCapName());  	} -	llassert(false); -	return GET; +	 +	return "";  }  const char *LLMediaDataClient::Request::getTypeAsString() const @@ -546,35 +486,30 @@ const char *LLMediaDataClient::Request::getTypeAsString() const  } -void LLMediaDataClient::Request::reEnqueue() const +void LLMediaDataClient::Request::reEnqueue()  { -	// I sure hope this doesn't deref a bad pointer: -	mMDC->enqueue(this); +	if(mMDC) +	{ +		mMDC->enqueue(this); +	}  }  F32 LLMediaDataClient::Request::getRetryTimerDelay() const  { -	return (mMDC == NULL) ? LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY : -	mMDC->mRetryTimerDelay;  +	if(mMDC) +		return mMDC->mRetryTimerDelay;  +		 +	return 0.0f;  }  U32 LLMediaDataClient::Request::getMaxNumRetries() const  { -	return (mMDC == NULL) ? LLMediaDataClient::MAX_RETRIES : mMDC->mMaxNumRetries; +	if(mMDC) +		return mMDC->mMaxNumRetries; +	 +	return 0;  } -void LLMediaDataClient::Request::markSent(bool flag) -{ -	 if (mMarkedSent != flag) -	 { -		 mMarkedSent = flag; -		 if (!mMarkedSent) -		 { -			 mNum = ++sNum; -		 } -	 } -} -		     void LLMediaDataClient::Request::updateScore()  {				  	F64 tmp = mObject->getMediaInterest(); @@ -585,15 +520,37 @@ void LLMediaDataClient::Request::updateScore()  	}  } +void LLMediaDataClient::Request::markDead()  +{  +	mMDC = NULL; +} + +bool LLMediaDataClient::Request::isDead()  +{  +	return ((mMDC == NULL) || mObject->isDead());  +} + +void LLMediaDataClient::Request::startTracking()  +{  +	if(mMDC)  +		mMDC->trackRequest(this);  +} + +void LLMediaDataClient::Request::stopTracking()  +{  +	if(mMDC)  +		mMDC->stopTrackingRequest(this);  +} +  std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &r)  {  	s << "request: num=" << r.getNum()   	<< " type=" << r.getTypeAsString()  -	<< " ID=" << r.getObject()->getID()  +	<< " ID=" << r.getID()  +	<< " face=" << r.getFace()   	<< " #retries=" << r.getRetryCount();  	return s;  } -  //////////////////////////////////////////////////////////////////////////////////////  // @@ -606,15 +563,17 @@ LLMediaDataClient::Responder::Responder(const request_ptr_t &request)  {  } -LLMediaDataClient::Responder::~Responder() -{ -	LL_DEBUGS("LLMediaDataClient") << "~Responder" << *(getRequest()) << LL_ENDL; -	mRequest = NULL; -} -  /*virtual*/  void LLMediaDataClient::Responder::error(U32 status, const std::string& reason)  { +	mRequest->stopTracking(); + +	if(mRequest->isDead()) +	{ +		LL_WARNS("LLMediaDataClient") << "dead request " << *mRequest << LL_ENDL; +		return; +	} +	  	if (status == HTTP_SERVICE_UNAVAILABLE)  	{  		F32 retry_timeout = mRequest->getRetryTimerDelay(); @@ -627,14 +586,16 @@ void LLMediaDataClient::Responder::error(U32 status, const std::string& reason)  			// Start timer (instances are automagically tracked by  			// InstanceTracker<> and LLEventTimer) -			new RetryTimer(F32(retry_timeout/*secs*/), this); +			new RetryTimer(F32(retry_timeout/*secs*/), mRequest);  		} -		else { +		else  +		{  			LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retry count "   				<< mRequest->getRetryCount() << " exceeds " << mRequest->getMaxNumRetries() << ", not retrying" << LL_ENDL;  		}  	} -	else { +	else  +	{  		std::string msg = boost::lexical_cast<std::string>(status) + ": " + reason;  		LL_WARNS("LLMediaDataClient") << *mRequest << " http error(" << msg << ")" << LL_ENDL;  	} @@ -643,6 +604,14 @@ void LLMediaDataClient::Responder::error(U32 status, const std::string& reason)  /*virtual*/  void LLMediaDataClient::Responder::result(const LLSD& content)  { +	mRequest->stopTracking(); + +	if(mRequest->isDead()) +	{ +		LL_WARNS("LLMediaDataClient") << "dead request " << *mRequest << LL_ENDL; +		return; +	} +  	LL_DEBUGS("LLMediaDataClientResponse") << *mRequest << " result : " << ll_print_sd(content) << LL_ENDL;  } @@ -653,9 +622,10 @@ void LLMediaDataClient::Responder::result(const LLSD& content)  //  ////////////////////////////////////////////////////////////////////////////////////// -LLMediaDataClient::Responder *LLObjectMediaDataClient::createResponder(const request_ptr_t &request) const +void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object)  { -	return new LLObjectMediaDataClient::Responder(request); +	// Create a get request and put it in the queue. +	enqueue(new RequestGet(object, this));  }  const char *LLObjectMediaDataClient::getCapabilityName() const  @@ -663,70 +633,286 @@ const char *LLObjectMediaDataClient::getCapabilityName() const  	return "ObjectMedia";  } -void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object) +LLObjectMediaDataClient::request_queue_t *LLObjectMediaDataClient::getQueue() +{ +	return (mCurrentQueueIsTheSortedQueue) ? &mQueue : &mRoundRobinQueue; +} + +void LLObjectMediaDataClient::sortQueue() +{ +	if(!mQueue.empty()) +	{ +		// score all elements in the sorted queue. +		for(request_queue_t::iterator iter = mQueue.begin(); iter != mQueue.end(); iter++) +		{ +			(*iter)->updateScore(); +		} +		 +		// Re-sort the list... +		mQueue.sort(compareRequestScores); +		 +		// ...then cull items over the max +		U32 size = mQueue.size(); +		if (size > mMaxSortedQueueSize)  +		{ +			U32 num_to_cull = (size - mMaxSortedQueueSize); +			LL_INFOS_ONCE("LLMediaDataClient") << "sorted queue MAXED OUT!  Culling "  +				<< num_to_cull << " items" << LL_ENDL; +			while (num_to_cull-- > 0) +			{ +				mQueue.back()->markDead(); +				mQueue.pop_back(); +			} +		} +	} +	 +} + +// static +bool LLObjectMediaDataClient::compareRequestScores(const request_ptr_t &o1, const request_ptr_t &o2)  { -	LLSD sd_payload; -	sd_payload["verb"] = "GET"; -	sd_payload[LLTextureEntry::OBJECT_ID_KEY] = object->getID(); -	request(object, sd_payload); +	if (o2.isNull()) return true; +	if (o1.isNull()) return false; +	return ( o1->getScore() > o2->getScore() );  } +void LLObjectMediaDataClient::enqueue(Request *request) +{ +	if(request->isDead()) +	{ +		LL_DEBUGS("LLMediaDataClient") << "not queueing dead request " << *request << LL_ENDL; +		return; +	} + +	// Invariants: +	// new requests always go into the sorted queue. +	//   +	 +	bool is_new = request->isNew(); +	 +	if(!is_new && (request->getType() == Request::GET)) +	{ +		// For GET requests that are not new, if a matching request is already in the round robin queue,  +		// in flight, or being retried, leave it at its current position. +		request_queue_t::iterator iter = find_matching_request(mRoundRobinQueue, request->getID(), Request::GET); +		request_set_t::iterator iter2 = find_matching_request(mUnQueuedRequests, request->getID(), Request::GET); +		 +		if( (iter != mRoundRobinQueue.end()) || (iter2 != mUnQueuedRequests.end()) ) +		{ +			LL_DEBUGS("LLMediaDataClient") << "ALREADY THERE: NOT Queuing request for " << *request << LL_ENDL; + +			return; +		} +	} +	 +	// TODO: should an UPDATE cause pending GET requests for the same object to be removed from the queue? +	// IF the update will cause an object update message to be sent out at some point in the future, it probably should. +	 +	// Remove any existing requests of this type for this object +	remove_matching_requests(mQueue, request->getID(), request->getType()); +	remove_matching_requests(mRoundRobinQueue, request->getID(), request->getType()); +	remove_matching_requests(mUnQueuedRequests, request->getID(), request->getType()); + +	if (is_new) +	{ +		LL_DEBUGS("LLMediaDataClient") << "Queuing SORTED request for " << *request << LL_ENDL; +		 +		mQueue.push_back(request); +		 +		LL_DEBUGS("LLMediaDataClientQueue") << "SORTED queue:" << mQueue << LL_ENDL; +	} +	else +	{ +		if (mRoundRobinQueue.size() > mMaxRoundRobinQueueSize)  +		{ +			LL_INFOS_ONCE("LLMediaDataClient") << "RR QUEUE MAXED OUT!!!" << LL_ENDL; +			LL_DEBUGS("LLMediaDataClient") << "Not queuing " << *request << LL_ENDL; +			return; +		} +				 +		LL_DEBUGS("LLMediaDataClient") << "Queuing RR request for " << *request << LL_ENDL; +		// Push the request on the pending queue +		mRoundRobinQueue.push_back(request); +		 +		LL_DEBUGS("LLMediaDataClientQueue") << "RR queue:" << mRoundRobinQueue << LL_ENDL;			 +	}	 +	// Start the timer if not already running +	startQueueTimer(); +} + +bool LLObjectMediaDataClient::canServiceRequest(request_ptr_t request)  +{ +	if(mCurrentQueueIsTheSortedQueue) +	{ +		if(!request->getObject()->isInterestingEnough()) +		{ +			LL_DEBUGS("LLMediaDataClient") << "Not fetching " << *request << ": not interesting enough" << LL_ENDL; +			return false; +		} +	} +	 +	return true;  +}; + +void LLObjectMediaDataClient::swapCurrentQueue() +{ +	// Swap +	mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; +	// If its empty, swap back +	if (getQueue()->empty())  +	{ +		mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; +	} +} + +bool LLObjectMediaDataClient::isEmpty() const +{ +	return mQueue.empty() && mRoundRobinQueue.empty(); +} + +bool LLObjectMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) +{ +	// First, call parent impl. +	if(LLMediaDataClient::isInQueue(object)) +		return true; + +	if(find_matching_request(mRoundRobinQueue, object->getID()) != mRoundRobinQueue.end()) +		return true; +	 +	return false; +} + +void LLObjectMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) +{ +	// First, call parent impl. +	LLMediaDataClient::removeFromQueue(object); +	 +	remove_matching_requests(mRoundRobinQueue, object->getID()); +} + +bool LLObjectMediaDataClient::processQueueTimer() +{ +	if(isEmpty()) +		return true; +		 +	LL_DEBUGS("LLMediaDataClient") << "started, SORTED queue size is:	  " << mQueue.size()  +		<< ", RR queue size is:	  " << mRoundRobinQueue.size() << LL_ENDL; +	LL_DEBUGS("LLMediaDataClientQueue") << "    SORTED queue is:	  " << mQueue << LL_ENDL; +	LL_DEBUGS("LLMediaDataClientQueue") << "    RR queue is:	  " << mRoundRobinQueue << LL_ENDL; + +//	purgeDeadRequests(); + +	sortQueue(); + +	LL_DEBUGS("LLMediaDataClientQueue") << "after sort, SORTED queue is:	  " << mQueue << LL_ENDL; +	 +	serviceQueue(); + +	swapCurrentQueue(); +	 +	LL_DEBUGS("LLMediaDataClient") << "finished, SORTED queue size is:	  " << mQueue.size()  +		<< ", RR queue size is:	  " << mRoundRobinQueue.size() << LL_ENDL; +	LL_DEBUGS("LLMediaDataClientQueue") << "    SORTED queue is:	  " << mQueue << LL_ENDL; +	LL_DEBUGS("LLMediaDataClientQueue") << "    RR queue is:	  " << mRoundRobinQueue << LL_ENDL; +	 +	return isEmpty(); +} + +LLObjectMediaDataClient::RequestGet::RequestGet(LLMediaDataClientObject *obj, LLMediaDataClient *mdc): +	LLMediaDataClient::Request(LLMediaDataClient::Request::GET, obj, mdc) +{ +} + +LLSD LLObjectMediaDataClient::RequestGet::getPayload() const +{ +	LLSD result; +	result["verb"] = "GET"; +	result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID(); +	 +	return result; +} + +LLMediaDataClient::Responder *LLObjectMediaDataClient::RequestGet::createResponder() +{ +	return new LLObjectMediaDataClient::Responder(this); +} + +  void LLObjectMediaDataClient::updateMedia(LLMediaDataClientObject *object)  { -	LLSD sd_payload; -	sd_payload["verb"] = "UPDATE"; -	sd_payload[LLTextureEntry::OBJECT_ID_KEY] = object->getID(); +	// Create an update request and put it in the queue. +	enqueue(new RequestUpdate(object, this)); +} + +LLObjectMediaDataClient::RequestUpdate::RequestUpdate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc): +	LLMediaDataClient::Request(LLMediaDataClient::Request::UPDATE, obj, mdc) +{ +} + +LLSD LLObjectMediaDataClient::RequestUpdate::getPayload() const +{ +	LLSD result; +	result["verb"] = "UPDATE"; +	result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID(); +  	LLSD object_media_data;  	int i = 0; -	int end = object->getMediaDataCount(); +	int end = mObject->getMediaDataCount();  	for ( ; i < end ; ++i)   	{ -		object_media_data.append(object->getMediaDataLLSD(i)); +		object_media_data.append(mObject->getMediaDataLLSD(i));  	} -	sd_payload[LLTextureEntry::OBJECT_MEDIA_DATA_KEY] = object_media_data; -		 -	LL_DEBUGS("LLMediaDataClient") << "update media data: " << object->getID() << " " << ll_print_sd(sd_payload) << LL_ENDL; -	request(object, sd_payload); +	result[LLTextureEntry::OBJECT_MEDIA_DATA_KEY] = object_media_data; +	 +	return result;  } +LLMediaDataClient::Responder *LLObjectMediaDataClient::RequestUpdate::createResponder() +{ +	// This just uses the base class's responder. +	return new LLMediaDataClient::Responder(this); +} + +  /*virtual*/  void LLObjectMediaDataClient::Responder::result(const LLSD& content)  { -	const LLMediaDataClient::Request::Type type = getRequest()->getType(); -	llassert(type == LLMediaDataClient::Request::GET || type == LLMediaDataClient::Request::UPDATE) -	if (type == LLMediaDataClient::Request::GET) +	getRequest()->stopTracking(); + +	if(getRequest()->isDead())  	{ -		LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " GET returned: " << ll_print_sd(content) << LL_ENDL; +		LL_WARNS("LLMediaDataClient") << "dead request " << *(getRequest()) << LL_ENDL; +		return; +	} + +	// This responder is only used for GET requests, not UPDATE. + +	LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " GET returned: " << ll_print_sd(content) << LL_ENDL; +	 +	// Look for an error +	if (content.has("error")) +	{ +		const LLSD &error = content["error"]; +		LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error getting media data for object: code=" <<  +			error["code"].asString() << ": " << error["message"].asString() << LL_ENDL; -		// Look for an error -		if (content.has("error")) -		{ -			const LLSD &error = content["error"]; -			LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error getting media data for object: code=" <<  -				error["code"].asString() << ": " << error["message"].asString() << LL_ENDL; -			 -			// XXX Warn user? -		} -		else { -			// Check the data -			const LLUUID &object_id = content[LLTextureEntry::OBJECT_ID_KEY]; -			if (object_id != getRequest()->getObject()->getID())  -			{ -				// NOT good, wrong object id!! -				LL_WARNS("LLMediaDataClient") << *(getRequest()) << " DROPPING response with wrong object id (" << object_id << ")" << LL_ENDL; -				return; -			} -			 -			// Otherwise, update with object media data -			getRequest()->getObject()->updateObjectMediaData(content[LLTextureEntry::OBJECT_MEDIA_DATA_KEY], -															 content[LLTextureEntry::MEDIA_VERSION_KEY]); -		} +		// XXX Warn user?  	} -	else if (type == LLMediaDataClient::Request::UPDATE) +	else   	{ -		// just do what our superclass does -		LLMediaDataClient::Responder::result(content); +		// Check the data +		const LLUUID &object_id = content[LLTextureEntry::OBJECT_ID_KEY]; +		if (object_id != getRequest()->getObject()->getID())  +		{ +			// NOT good, wrong object id!! +			LL_WARNS("LLMediaDataClient") << *(getRequest()) << " DROPPING response with wrong object id (" << object_id << ")" << LL_ENDL; +			return; +		} +		 +		// Otherwise, update with object media data +		getRequest()->getObject()->updateObjectMediaData(content[LLTextureEntry::OBJECT_MEDIA_DATA_KEY], +														 content[LLTextureEntry::MEDIA_VERSION_KEY]);  	}  } @@ -736,38 +922,105 @@ void LLObjectMediaDataClient::Responder::result(const LLSD& content)  // Subclass of LLMediaDataClient for the ObjectMediaNavigate cap  //  ////////////////////////////////////////////////////////////////////////////////////// -LLMediaDataClient::Responder *LLObjectMediaNavigateClient::createResponder(const request_ptr_t &request) const -{ -	return new LLObjectMediaNavigateClient::Responder(request); -}  const char *LLObjectMediaNavigateClient::getCapabilityName() const   {  	return "ObjectMediaNavigate";  } +void LLObjectMediaNavigateClient::enqueue(Request *request) +{ +	if(request->isDead()) +	{ +		LL_DEBUGS("LLMediaDataClient") << "not queueing dead request " << *request << LL_ENDL; +		return; +	} +	 +	// If there's already a matching request in the queue, remove it. +	request_queue_t::iterator iter = find_matching_request(mQueue, request); +	if(iter != mQueue.end()) +	{ +		LL_DEBUGS("LLMediaDataClient") << "removing matching queued request " << (**iter) << LL_ENDL; +		mQueue.erase(iter); +	} +	else +	{ +		request_set_t::iterator set_iter = find_matching_request(mUnQueuedRequests, request); +		if(set_iter != mUnQueuedRequests.end()) +		{ +			LL_DEBUGS("LLMediaDataClient") << "removing matching unqueued request " << (**set_iter) << LL_ENDL; +			mUnQueuedRequests.erase(set_iter); +		} +	} + +#if 0 +	// Sadly, this doesn't work.  It ends up creating a race condition when the user navigates and then hits the "back" button +	// where the navigate-back appears to be spurious and doesn't get broadcast.	 +	if(request->getObject()->isCurrentMediaUrl(request->getFace(), request->getURL())) +	{ +		// This navigate request is trying to send the face to the current URL.  Drop it. +		LL_DEBUGS("LLMediaDataClient") << "dropping spurious request " << (*request) << LL_ENDL; +	} +	else +#endif +	{ +		LL_DEBUGS("LLMediaDataClient") << "queueing new request " << (*request) << LL_ENDL; +		mQueue.push_back(request); +		 +		// Start the timer if not already running +		startQueueTimer(); +	} +} +  void LLObjectMediaNavigateClient::navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url)  { -	LLSD sd_payload; -	sd_payload[LLTextureEntry::OBJECT_ID_KEY] = object->getID(); -	sd_payload[LLMediaEntry::CURRENT_URL_KEY] = url; -	sd_payload[LLTextureEntry::TEXTURE_INDEX_KEY] = (LLSD::Integer)texture_index; + +//	LL_INFOS("LLMediaDataClient") << "navigate() initiated: " << ll_print_sd(sd_payload) << LL_ENDL; -	LL_INFOS("LLMediaDataClient") << "navigate() initiated: " << ll_print_sd(sd_payload) << LL_ENDL; +	// Create a get request and put it in the queue. +	enqueue(new RequestNavigate(object, this, texture_index, url)); +} + +LLObjectMediaNavigateClient::RequestNavigate::RequestNavigate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc, U8 texture_index, const std::string &url): +	LLMediaDataClient::Request(LLMediaDataClient::Request::NAVIGATE, obj, mdc, (S32)texture_index), +	mURL(url) +{ +} + +LLSD LLObjectMediaNavigateClient::RequestNavigate::getPayload() const +{ +	LLSD result; +	result[LLTextureEntry::OBJECT_ID_KEY] = getID(); +	result[LLMediaEntry::CURRENT_URL_KEY] = mURL; +	result[LLTextureEntry::TEXTURE_INDEX_KEY] = (LLSD::Integer)getFace(); -	request(object, sd_payload); +	return result; +} + +LLMediaDataClient::Responder *LLObjectMediaNavigateClient::RequestNavigate::createResponder() +{ +	return new LLObjectMediaNavigateClient::Responder(this);  }  /*virtual*/  void LLObjectMediaNavigateClient::Responder::error(U32 status, const std::string& reason)  { +	getRequest()->stopTracking(); + +	if(getRequest()->isDead()) +	{ +		LL_WARNS("LLMediaDataClient") << "dead request " << *(getRequest()) << LL_ENDL; +		return; +	} +  	// Bounce back (unless HTTP_SERVICE_UNAVAILABLE, in which case call base  	// class  	if (status == HTTP_SERVICE_UNAVAILABLE)  	{  		LLMediaDataClient::Responder::error(status, reason);  	} -	else { +	else +	{  		// bounce the face back  		LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: http code=" << status << LL_ENDL;  		const LLSD &payload = getRequest()->getPayload(); @@ -779,6 +1032,14 @@ void LLObjectMediaNavigateClient::Responder::error(U32 status, const std::string  /*virtual*/  void LLObjectMediaNavigateClient::Responder::result(const LLSD& content)  { +	getRequest()->stopTracking(); + +	if(getRequest()->isDead()) +	{ +		LL_WARNS("LLMediaDataClient") << "dead request " << *(getRequest()) << LL_ENDL; +		return; +	} +  	LL_INFOS("LLMediaDataClient") << *(getRequest()) << " NAVIGATE returned " << ll_print_sd(content) << LL_ENDL;  	if (content.has("error")) @@ -793,14 +1054,17 @@ void LLObjectMediaNavigateClient::Responder::result(const LLSD& content)  			// bounce the face back  			getRequest()->getObject()->mediaNavigateBounceBack((LLSD::Integer)payload[LLTextureEntry::TEXTURE_INDEX_KEY]);  		} -		else { +		else +		{  			LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: code=" <<   				error["code"].asString() << ": " << error["message"].asString() << LL_ENDL;  		}			  +  		// XXX Warn user?  	} -	else { -		// just do what our superclass does -		LLMediaDataClient::Responder::result(content); +	else  +	{ +		// No action required. +		LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " result : " << ll_print_sd(content) << LL_ENDL;  	}  } | 
