diff options
Diffstat (limited to 'indra/newview/llmediadataclient.cpp')
-rwxr-xr-x | indra/newview/llmediadataclient.cpp | 1030 |
1 files changed, 383 insertions, 647 deletions
diff --git a/indra/newview/llmediadataclient.cpp b/indra/newview/llmediadataclient.cpp index 075f0ba3ec..b8da368bd7 100755 --- a/indra/newview/llmediadataclient.cpp +++ b/indra/newview/llmediadataclient.cpp @@ -58,32 +58,6 @@ // - 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 // @@ -97,54 +71,6 @@ 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 @@ -161,36 +87,117 @@ LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay, mMaxNumRetries(max_retries), mMaxSortedQueueSize(max_sorted_queue_size), mMaxRoundRobinQueueSize(max_round_robin_queue_size), - mQueueTimerIsRunning(false) + mQueueTimerIsRunning(false), + mCurrentQueueIsTheSortedQueue(true) { } 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 mQueue.empty(); + return mSortedQueue.empty() && mRoundRobinQueue.empty(); } bool LLMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) { - if(find_matching_request(mQueue, object->getID()) != mQueue.end()) - return true; - - if(find_matching_request(mUnQueuedRequests, object->getID()) != mUnQueuedRequests.end()) - return true; + 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; - return false; + // Push the object on the queue + enqueue(new Request(getCapabilityName(), payload, object, this)); } -void LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) +void LLMediaDataClient::enqueue(const Request *request) { - LL_DEBUGS("LLMediaDataClient") << "removing requests matching ID " << object->getID() << LL_ENDL; - remove_matching_requests(mQueue, object->getID()); - remove_matching_requests(mUnQueuedRequests, object->getID()); + 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(); } void LLMediaDataClient::startQueueTimer() @@ -202,7 +209,7 @@ void LLMediaDataClient::startQueueTimer() new QueueTimer(mQueueTimerDelay, this); } else { - LL_DEBUGS("LLMediaDataClient") << "queue timer is already running" << LL_ENDL; + LL_DEBUGS("LLMediaDataClient") << "not starting queue timer (it's already running, right???)" << LL_ENDL; } } @@ -213,138 +220,179 @@ void LLMediaDataClient::stopQueueTimer() bool LLMediaDataClient::processQueueTimer() { - 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; - + 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; + } + serviceQueue(); - 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; + 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; return isEmpty(); } -LLMediaDataClient::request_ptr_t LLMediaDataClient::dequeue() +void LLMediaDataClient::sortQueue() { - request_ptr_t request; - request_queue_t *queue_p = getQueue(); - - if (queue_p->empty()) + if(!mSortedQueue.empty()) { - LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL; - } - else - { - request = queue_p->front(); - - if(canServiceRequest(request)) + // Score all items first + request_queue_t::iterator iter = mSortedQueue.begin(); + request_queue_t::iterator end = mSortedQueue.end(); + while (iter != end) { - // We will be returning this request, so remove it from the queue. - queue_p->pop_front(); + (*iter)->updateScore(); + iter++; } - else + + // 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) { - // Don't return this request -- it's not ready to be serviced. - request = NULL; + 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(); + } } } - - return request; -} - -void LLMediaDataClient::pushBack(request_ptr_t request) -{ - 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) +// static +bool LLMediaDataClient::compareRequests(const request_ptr_t &o1, const request_ptr_t &o2) { - 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; - } + if (o2.isNull()) return true; + if (o1.isNull()) return false; + return ( o1->getScore() > o2->getScore() ); } void LLMediaDataClient::serviceQueue() { - // Peel one off of the items from the queue and execute it - request_ptr_t request; + request_queue_t *queue_p = getCurrentQueue(); - do + // quick retry loop for cases where we shouldn't wait for the next timer tick + while(true) { - request = dequeue(); - - if(request.isNull()) - { - // Queue is empty. - return; - } - - if(request->isDead()) + if (queue_p->empty()) { - LL_INFOS("LLMediaDataClient") << "Skipping dead request " << *request << LL_ENDL; - continue; + LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL; + break; } - - } while(false); - // 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; + // 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); - // Add this request to the non-queued tracking list - trackRequest(request); + // 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.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 + } - // and make the post - LLHTTPClient::post(url, sd_payload, request->createResponder()); - } - else - { - // Cap url doesn't exist. + // 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; + } - if(request->getRetryCount() < mMaxNumRetries) + // Finally, try to send the HTTP message to the cap url + std::string url = request->getCapability(); + bool maybe_retry = false; + if (!url.empty()) { - 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); + 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 + 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 { - // 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; + // We got an empty cap, but in that case we will retry again next + // timer fire. + request->incRetryCount(); } + 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); + } + } + + // 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) @@ -354,7 +402,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)->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")"; + s << "\t" << i << "]: " << (*iter)->getObject()->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")"; iter++; i++; } @@ -374,24 +422,18 @@ 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() { - BOOL result = TRUE; - - if (!mMDC.isNull()) - { - result = mMDC->processQueueTimer(); - - if(result) - { - // This timer won't fire again. - mMDC->setIsRunning(false); - mMDC = NULL; - } - } - - return result; + if (mMDC.isNull()) return TRUE; + return mMDC->processQueueTimer(); } @@ -401,30 +443,29 @@ BOOL LLMediaDataClient::QueueTimer::tick() // ////////////////////////////////////////////////////////////////////////////////////// -LLMediaDataClient::RetryTimer::RetryTimer(F32 time, request_ptr_t request) -: LLEventTimer(time), mRequest(request) +LLMediaDataClient::Responder::RetryTimer::RetryTimer(F32 time, Responder *mdr) +: LLEventTimer(time), mResponder(mdr) { - mRequest->startTracking(); } -// virtual -BOOL LLMediaDataClient::RetryTimer::tick() +// virtual +LLMediaDataClient::Responder::RetryTimer::~RetryTimer() { - 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(); - } + LL_DEBUGS("LLMediaDataClient") << "~RetryTimer" << *(mResponder->getRequest()) << LL_ENDL; - // Release the ref to the request. - mRequest = NULL; + // 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(); + + // Release the ref to the responder. + mResponder = NULL; +} +// virtual +BOOL LLMediaDataClient::Responder::RetryTimer::tick() +{ // Don't fire again return TRUE; } @@ -437,37 +478,56 @@ BOOL LLMediaDataClient::RetryTimer::tick() ////////////////////////////////////////////////////////////////////////////////////// /*static*/U32 LLMediaDataClient::Request::sNum = 0; -LLMediaDataClient::Request::Request(Type in_type, +LLMediaDataClient::Request::Request(const char *cap_name, + const LLSD& sd_payload, LLMediaDataClientObject *obj, - LLMediaDataClient *mdc, - S32 face) -: mType(in_type), + LLMediaDataClient *mdc) +: mCapName(cap_name), + mPayload(sd_payload), mObject(obj), mNum(++sNum), mRetryCount(0), mMDC(mdc), - mScore((F64)0.0), - mFace(face) + mMarkedSent(false), + mScore((F64)0.0) { - mObjectID = mObject->getID(); } -const char *LLMediaDataClient::Request::getCapName() const +LLMediaDataClient::Request::~Request() { - if(mMDC) - return mMDC->getCapabilityName(); - - return ""; + LL_DEBUGS("LLMediaDataClient") << "~Request" << (*this) << LL_ENDL; + mMDC = NULL; + mObject = NULL; } + std::string LLMediaDataClient::Request::getCapability() const { - if(mMDC) + 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")) { - return getObject()->getCapabilityUrl(getCapName()); + return NAVIGATE; } - - return ""; + else if (0 == strcmp(mCapName, "ObjectMedia")) + { + const std::string &verb = mPayload["verb"]; + if (verb == "GET") + { + return GET; + } + else if (verb == "UPDATE") + { + return UPDATE; + } + } + llassert(false); + return GET; } const char *LLMediaDataClient::Request::getTypeAsString() const @@ -492,30 +552,35 @@ const char *LLMediaDataClient::Request::getTypeAsString() const } -void LLMediaDataClient::Request::reEnqueue() +void LLMediaDataClient::Request::reEnqueue() const { - if(mMDC) - { - mMDC->enqueue(this); - } + // I sure hope this doesn't deref a bad pointer: + mMDC->enqueue(this); } F32 LLMediaDataClient::Request::getRetryTimerDelay() const { - if(mMDC) - return mMDC->mRetryTimerDelay; - - return 0.0f; + return (mMDC == NULL) ? LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY : + mMDC->mRetryTimerDelay; } U32 LLMediaDataClient::Request::getMaxNumRetries() const { - if(mMDC) - return mMDC->mMaxNumRetries; - - return 0; + return (mMDC == NULL) ? LLMediaDataClient::MAX_RETRIES : mMDC->mMaxNumRetries; } +void LLMediaDataClient::Request::markSent(bool flag) +{ + if (mMarkedSent != flag) + { + mMarkedSent = flag; + if (!mMarkedSent) + { + mNum = ++sNum; + } + } +} + void LLMediaDataClient::Request::updateScore() { F64 tmp = mObject->getMediaInterest(); @@ -526,37 +591,15 @@ 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.getID() - << " face=" << r.getFace() + << " ID=" << r.getObject()->getID() << " #retries=" << r.getRetryCount(); return s; } + ////////////////////////////////////////////////////////////////////////////////////// // @@ -569,17 +612,15 @@ 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(); @@ -592,16 +633,14 @@ 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*/), mRequest); + new RetryTimer(F32(retry_timeout/*secs*/), this); } - 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; } @@ -610,14 +649,6 @@ 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; } @@ -628,10 +659,9 @@ void LLMediaDataClient::Responder::result(const LLSD& content) // ////////////////////////////////////////////////////////////////////////////////////// -void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object) +LLMediaDataClient::Responder *LLObjectMediaDataClient::createResponder(const request_ptr_t &request) const { - // Create a get request and put it in the queue. - enqueue(new RequestGet(object, this)); + return new LLObjectMediaDataClient::Responder(request); } const char *LLObjectMediaDataClient::getCapabilityName() const @@ -639,286 +669,70 @@ const char *LLObjectMediaDataClient::getCapabilityName() const return "ObjectMedia"; } -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) -{ - 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() +void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object) { - return new LLObjectMediaDataClient::Responder(this); + LLSD sd_payload; + sd_payload["verb"] = "GET"; + sd_payload[LLTextureEntry::OBJECT_ID_KEY] = object->getID(); + request(object, sd_payload); } - void LLObjectMediaDataClient::updateMedia(LLMediaDataClientObject *object) { - // 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 sd_payload; + sd_payload["verb"] = "UPDATE"; + sd_payload[LLTextureEntry::OBJECT_ID_KEY] = object->getID(); LLSD object_media_data; int i = 0; - int end = mObject->getMediaDataCount(); + int end = object->getMediaDataCount(); for ( ; i < end ; ++i) { - object_media_data.append(mObject->getMediaDataLLSD(i)); + object_media_data.append(object->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; - result[LLTextureEntry::OBJECT_MEDIA_DATA_KEY] = object_media_data; - - return result; + request(object, sd_payload); } -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) { - getRequest()->stopTracking(); - - if(getRequest()->isDead()) + const LLMediaDataClient::Request::Type type = getRequest()->getType(); + llassert(type == LLMediaDataClient::Request::GET || type == LLMediaDataClient::Request::UPDATE) + if (type == LLMediaDataClient::Request::GET) { - 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; + LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " GET returned: " << ll_print_sd(content) << LL_ENDL; - // XXX Warn user? - } - else - { - // Check the data - const LLUUID &object_id = content[LLTextureEntry::OBJECT_ID_KEY]; - if (object_id != getRequest()->getObject()->getID()) + // Look for an error + if (content.has("error")) { - // NOT good, wrong object id!! - LL_WARNS("LLMediaDataClient") << *(getRequest()) << " DROPPING response with wrong object id (" << object_id << ")" << LL_ENDL; - return; + 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? } - - // Otherwise, update with object media data - getRequest()->getObject()->updateObjectMediaData(content[LLTextureEntry::OBJECT_MEDIA_DATA_KEY], - content[LLTextureEntry::MEDIA_VERSION_KEY]); + 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]); + } + } + else if (type == LLMediaDataClient::Request::UPDATE) + { + // just do what our superclass does + LLMediaDataClient::Responder::result(content); } } @@ -928,105 +742,38 @@ void LLObjectMediaDataClient::Responder::result(const LLSD& content) // Subclass of LLMediaDataClient for the ObjectMediaNavigate cap // ////////////////////////////////////////////////////////////////////////////////////// - -const char *LLObjectMediaNavigateClient::getCapabilityName() const +LLMediaDataClient::Responder *LLObjectMediaNavigateClient::createResponder(const request_ptr_t &request) const { - return "ObjectMediaNavigate"; + return new LLObjectMediaNavigateClient::Responder(request); } -void LLObjectMediaNavigateClient::enqueue(Request *request) +const char *LLObjectMediaNavigateClient::getCapabilityName() const { - 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(); - } + return "ObjectMediaNavigate"; } void LLObjectMediaNavigateClient::navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url) { - -// LL_INFOS("LLMediaDataClient") << "navigate() initiated: " << ll_print_sd(sd_payload) << LL_ENDL; + 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; - // 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(); + LL_INFOS("LLMediaDataClient") << "navigate() initiated: " << ll_print_sd(sd_payload) << LL_ENDL; - return result; -} - -LLMediaDataClient::Responder *LLObjectMediaNavigateClient::RequestNavigate::createResponder() -{ - return new LLObjectMediaNavigateClient::Responder(this); + request(object, sd_payload); } /*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(); @@ -1038,14 +785,6 @@ 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")) @@ -1060,17 +799,14 @@ 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 - { - // No action required. - LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " result : " << ll_print_sd(content) << LL_ENDL; + else { + // just do what our superclass does + LLMediaDataClient::Responder::result(content); } } |