summaryrefslogtreecommitdiff
path: root/indra/newview/lltexturefetch.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/lltexturefetch.cpp')
-rwxr-xr-x[-rw-r--r--]indra/newview/lltexturefetch.cpp4465
1 files changed, 3778 insertions, 687 deletions
diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp
index 88fc7f98c0..fab4203ec3 100644..100755
--- a/indra/newview/lltexturefetch.cpp
+++ b/indra/newview/lltexturefetch.cpp
@@ -2,155 +2,308 @@
* @file lltexturefetch.cpp
* @brief Object which fetches textures from the cache and/or network
*
- * $LicenseInfo:firstyear=2000&license=viewergpl$
- *
- * Copyright (c) 2000-2009, Linden Research, Inc.
- *
+ * $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
- * The source code in this file ("Source Code") is provided by Linden Lab
- * to you under the terms of the GNU General Public License, version 2.0
- * ("GPL"), unless you have obtained a separate licensing agreement
- * ("Other License"), formally executed by you and Linden Lab. Terms of
- * the GPL can be found in doc/GPL-license.txt in this distribution, or
- * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * Copyright (C) 2012-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
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
*
- * There are special exceptions to the terms and conditions of the GPL as
- * it is applied to this Source Code. View the full text of the exception
- * in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
*
- * By copying, modifying or distributing this software, you acknowledge
- * that you have read and understood your obligations described above,
- * and agree to abide by those obligations.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
+#include <iostream>
+#include <map>
+#include <algorithm>
+
#include "llstl.h"
#include "lltexturefetch.h"
-#include "llcurl.h"
+#include "lldir.h"
#include "llhttpclient.h"
+#include "llhttpconstants.h"
#include "llimage.h"
+#include "llimagej2c.h"
#include "llimageworker.h"
#include "llworkerthread.h"
+#include "message.h"
#include "llagent.h"
#include "lltexturecache.h"
+#include "llviewercontrol.h"
#include "llviewertexturelist.h"
#include "llviewertexture.h"
#include "llviewerregion.h"
+#include "llviewerstats.h"
+#include "llviewerstatsrecorder.h"
+#include "llviewerassetstats.h"
+#include "llworld.h"
+#include "llsdparam.h"
+#include "llsdutil.h"
+#include "llstartup.h"
+
+#include "httprequest.h"
+#include "httphandler.h"
+#include "httpresponse.h"
+#include "bufferarray.h"
+#include "bufferstream.h"
+#include "llcorehttputil.h"
+
+#include "llhttpretrypolicy.h"
+
+bool LLTextureFetchDebugger::sDebuggerEnabled = false ;
+LLTrace::EventStatHandle<LLUnit<F32, LLUnits::Percent> > LLTextureFetch::sCacheHitRate("texture_cache_hits");
+LLTrace::EventStatHandle<F64Milliseconds > LLTextureFetch::sCacheReadLatency("texture_cache_read_latency");
+
//////////////////////////////////////////////////////////////////////////////
-//static
-class LLTextureFetchWorker : public LLWorkerClass
+//
+// Introduction
+//
+// This is an attempt to document what's going on in here after-the-fact.
+// It's a sincere attempt to be accurate but there will be mistakes.
+//
+//
+// Purpose
+//
+// What is this module trying to do? It accepts requests to load textures
+// at a given priority and discard level and notifies the caller when done
+// (successfully or not). Additional constraints are:
+//
+// * Support a local texture cache. Don't hit network when possible
+// to avoid it.
+// * Use UDP or HTTP as directed or as fallback. HTTP is tried when
+// not disabled and a URL is available. UDP when a URL isn't
+// available or HTTP attempts fail.
+// * Asynchronous (using threads). Main thread is not to be blocked or
+// burdened.
+// * High concurrency. Many requests need to be in-flight and at various
+// stages of completion.
+// * Tolerate frequent re-prioritizations of requests. Priority is
+// a reflection of a camera's viewpoint and as that viewpoint changes,
+// objects and textures become more and less relevant and that is
+// expressed at this level by priority changes and request cancelations.
+//
+// The caller interfaces that fall out of the above and shape the
+// implementation are:
+// * createRequest - Load j2c image via UDP or HTTP at given discard level and priority
+// * deleteRequest - Request removal of prior request
+// * getRequestFinished - Test if request is finished returning data to caller
+// * updateRequestPriority - Change priority of existing request
+// * getFetchState - Retrieve progress on existing request
+//
+// Everything else in here is mostly plumbing, metrics and debug.
+//
+//
+// The Work Queue
+//
+// The two central classes are LLTextureFetch and LLTextureFetchWorker.
+// LLTextureFetch combines threading with a priority queue of work
+// requests. The priority queue is sorted by a U32 priority derived
+// from the F32 priority in the APIs. The *only* work request that
+// receives service time by this thread is the highest priority
+// request. All others wait until it is complete or a dynamic priority
+// change has re-ordered work.
+//
+// LLTextureFetchWorker implements the work request and is 1:1 with
+// texture fetch requests. Embedded in each is a state machine that
+// walks it through the cache, HTTP, UDP, image decode and retry
+// steps of texture acquisition.
+//
+//
+// Threads
+//
+// Several threads are actively invoking code in this module. They
+// include:
+//
+// 1. Tmain Main thread of execution
+// 2. Ttf LLTextureFetch's worker thread provided by LLQueuedThread
+// 3. Tcurl LLCurl's worker thread (should disappear over time)
+// 4. Ttc LLTextureCache's worker thread
+// 5. Tid Image decoder's worker thread
+// 6. Thl HTTP library's worker thread
+//
+//
+// Mutexes/Condition Variables
+//
+// 1. Mt Mutex defined for LLThread's condition variable (base class of
+// LLTextureFetch)
+// 2. Ct Condition variable for LLThread and used by lock/unlockData().
+// 3. Mwtd Special LLWorkerThread mutex used for request deletion
+// operations (base class of LLTextureFetch)
+// 4. Mfq LLTextureFetch's mutex covering request and command queue
+// data.
+// 5. Mfnq LLTextureFetch's mutex covering udp and http request
+// queue data.
+// 6. Mwc Mutex covering LLWorkerClass's members (base class of
+// LLTextureFetchWorker). One per request.
+// 7. Mw LLTextureFetchWorker's mutex. One per request.
+//
+//
+// Lock Ordering Rules
+//
+// Not an exhaustive list but shows the order of lock acquisition
+// needed to prevent deadlocks. 'A < B' means acquire 'A' before
+// acquiring 'B'.
+//
+// 1. Mw < Mfnq
+// (there are many more...)
+//
+//
+// Method and Member Definitions
+//
+// With the above, we'll try to document what threads can call what
+// methods (using T* for any), what locks must be held on entry and
+// are taken out during execution and what data is covered by which
+// lock (if any). This latter category will be especially prone to
+// error so be skeptical.
+//
+// A line like: "// Locks: M<xxx>" indicates a method that must
+// be invoked by a caller holding the 'M<xxx>' lock. Similarly,
+// "// Threads: T<xxx>" means that a caller should be running in
+// the indicated thread.
+//
+// For data members, a trailing comment like "// M<xxx>" means that
+// the data member is covered by the specified lock. Absence of a
+// comment can mean the member is unlocked or that I didn't bother
+// to do the archaeology. In the case of LLTextureFetchWorker,
+// most data members added by the leaf class are actually covered
+// by the Mw lock. You may also see "// T<xxx>" which means that
+// the member's usage is restricted to one thread (except for
+// perhaps construction and destruction) and so explicit locking
+// isn't used.
+//
+// In code, a trailing comment like "// [-+]M<xxx>" indicates a
+// lock acquision or release point.
+//
+//
+// Worker Lifecycle
+//
+// The threading and responder model makes it very likely that
+// other components are holding on to a pointer to a worker request.
+// So, uncoordinated deletions of requests is a guarantee of memory
+// corruption in a short time. So destroying a request involves
+// invocations's of LLQueuedThread/LLWorkerThread's abort/stop
+// logic that removes workers and puts them ona delete queue for
+// 2-phase destruction. That second phase is deferrable by calls
+// to deleteOK() which only allow final destruction (via dtor)
+// once deleteOK has determined that the request is in a safe
+// state.
+//
+//
+// Worker State Machine
+//
+// (ASCII art needed)
+//
+//
+// Priority Scheme
+//
+// [PRIORITY_LOW, PRIORITY_NORMAL) - for WAIT_HTTP_RESOURCE state
+// and other wait states
+// [PRIORITY_HIGH, PRIORITY_URGENT) - External event delivered,
+// rapidly transitioning through states,
+// no waiting allowed
+//
+// By itself, the above work queue model would fail the concurrency
+// and liveness requirements of the interface. A high priority
+// request could find itself on the head and stalled for external
+// reasons (see VWR-28996). So a few additional constraints are
+// required to keep things running:
+// * Anything that can make forward progress must be kept at a
+// higher priority than anything that can't.
+// * On completion of external events, the associated request
+// needs to be elevated beyond the normal range to handle
+// any data delivery and release any external resource.
+//
+// This effort is made to keep higher-priority entities moving
+// forward in their state machines at every possible step of
+// processing. It's not entirely proven that this produces the
+// experiencial benefits promised.
+//
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+// Tuning/Parameterization Constants
+
+static const S32 HTTP_PIPE_REQUESTS_HIGH_WATER = 100; // Maximum requests to have active in HTTP (pipelined)
+static const S32 HTTP_PIPE_REQUESTS_LOW_WATER = 50; // Active level at which to refill
+static const S32 HTTP_NONPIPE_REQUESTS_HIGH_WATER = 40;
+static const S32 HTTP_NONPIPE_REQUESTS_LOW_WATER = 20;
+
+// BUG-3323/SH-4375
+// *NOTE: This is a heuristic value. Texture fetches have a habit of using a
+// value of 32MB to indicate 'get the rest of the image'. Certain ISPs and
+// network equipment get confused when they see this in a Range: header. So,
+// if the request end is beyond this value, we issue an open-ended Range:
+// request (e.g. 'Range: <start>-') which seems to fix the problem.
+static const S32 HTTP_REQUESTS_RANGE_END_MAX = 20000000;
+
+//////////////////////////////////////////////////////////////////////////////
+
+static const char* e_state_name[] =
{
-friend class LLTextureFetch;
+ "INVALID",
+ "INIT",
+ "LOAD_FROM_TEXTURE_CACHE",
+ "CACHE_POST",
+ "LOAD_FROM_NETWORK",
+ "LOAD_FROM_SIMULATOR",
+ "WAIT_HTTP_RESOURCE",
+ "WAIT_HTTP_RESOURCE2",
+ "SEND_HTTP_REQ",
+ "WAIT_HTTP_REQ",
+ "DECODE_IMAGE",
+ "DECODE_IMAGE_UPDATE",
+ "WRITE_TO_CACHE",
+ "WAIT_ON_WRITE",
+ "DONE"
+};
-private:
-#if 0
- class URLResponder : public LLHTTPClient::Responder
- {
- public:
- URLResponder(LLTextureFetch* fetcher, const LLUUID& id)
- : mFetcher(fetcher), mID(id)
- {
- }
- ~URLResponder()
- {
- }
- virtual void error(U32 status, const std::string& reason)
- {
- mFetcher->lockQueue();
- LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
- if (worker)
- {
-// llwarns << "LLTextureFetchWorker::URLResponder::error " << status << ": " << reason << llendl;
- worker->callbackURLReceived(LLSD(), false);
- }
- mFetcher->unlockQueue();
- }
- virtual void result(const LLSD& content)
- {
- mFetcher->lockQueue();
- LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
- if (worker)
- {
- worker->callbackURLReceived(content, true);
- }
- mFetcher->unlockQueue();
- }
- private:
- LLTextureFetch* mFetcher;
- LLUUID mID;
- };
+// Log scope
+static const char * const LOG_TXT = "Texture";
- class HTTPGetResponder : public LLHTTPClient::Responder
- {
- public:
- HTTPGetResponder(LLTextureFetch* fetcher, const LLUUID& id)
- : mFetcher(fetcher), mID(id)
- {
- }
- ~HTTPGetResponder()
- {
- }
- virtual void completed(U32 status, const std::stringstream& content)
- {
- mFetcher->lockQueue();
- LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
- if (worker)
- {
- const std::string& cstr = content.str();
- if (200 <= status && status < 300)
- {
- if (203 == status) // partial information (i.e. last block)
- {
- worker->callbackHttpGet((U8*)cstr.c_str(), cstr.length(), true);
- }
- else
- {
- worker->callbackHttpGet((U8*)cstr.c_str(), cstr.length(), false);
- }
- }
- else
- {
-// llinfos << "LLTextureFetchWorker::HTTPGetResponder::error " << status << ": " << cstr << llendl;
- worker->callbackHttpGet(NULL, -1, true);
- }
- }
- mFetcher->unlockQueue();
- }
- private:
- LLTextureFetch* mFetcher;
- LLUUID mID;
- };
-#endif
+class LLTextureFetchWorker : public LLWorkerClass, public LLCore::HttpHandler
+
+{
+ friend class LLTextureFetch;
+ friend class LLTextureFetchDebugger;
+private:
class CacheReadResponder : public LLTextureCache::ReadResponder
{
public:
+
+ // Threads: Ttf
CacheReadResponder(LLTextureFetch* fetcher, const LLUUID& id, LLImageFormatted* image)
: mFetcher(fetcher), mID(id)
{
setImage(image);
}
+
+ // Threads: Ttc
virtual void completed(bool success)
{
- mFetcher->lockQueue();
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
if (worker)
{
worker->callbackCacheRead(success, mFormattedImage, mImageSize, mImageLocal);
}
- mFetcher->unlockQueue();
}
private:
LLTextureFetch* mFetcher;
@@ -160,46 +313,49 @@ private:
class CacheWriteResponder : public LLTextureCache::WriteResponder
{
public:
+
+ // Threads: Ttf
CacheWriteResponder(LLTextureFetch* fetcher, const LLUUID& id)
: mFetcher(fetcher), mID(id)
{
}
+
+ // Threads: Ttc
virtual void completed(bool success)
{
- mFetcher->lockQueue();
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
if (worker)
{
worker->callbackCacheWrite(success);
}
- mFetcher->unlockQueue();
}
private:
LLTextureFetch* mFetcher;
LLUUID mID;
};
- class DecodeResponder : public LLResponder
+ class DecodeResponder : public LLImageDecodeThread::Responder
{
public:
+
+ // Threads: Ttf
DecodeResponder(LLTextureFetch* fetcher, const LLUUID& id, LLTextureFetchWorker* worker)
- : mFetcher(fetcher), mID(id), mWorker(worker)
+ : mFetcher(fetcher), mID(id)
{
}
- virtual void completed(bool success)
+
+ // Threads: Tid
+ virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux)
{
- mFetcher->lockQueue();
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
if (worker)
{
- worker->callbackDecoded(success);
+ worker->callbackDecoded(success, raw, aux);
}
- mFetcher->unlockQueue();
}
private:
LLTextureFetch* mFetcher;
LLUUID mID;
- LLTextureFetchWorker* mWorker; // debug only (may get deleted from under us, use mFetcher/mID)
};
struct Compare
@@ -220,47 +376,137 @@ private:
};
public:
+
+ // Threads: Ttf
/*virtual*/ bool doWork(S32 param); // Called from LLWorkerThread::processRequest()
+
+ // Threads: Ttf
/*virtual*/ void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD)
- /*virtual*/ bool deleteOK(); // called from update() (WORK THREAD)
+
+ // Threads: Tmain
+ /*virtual*/ bool deleteOK(); // called from update()
~LLTextureFetchWorker();
- void relese() { --mActiveCount; }
+
+ // Threads: Ttf
+ // Locks: Mw
+ S32 callbackHttpGet(LLCore::HttpResponse * response,
+ bool partial, bool success);
+
+ // Threads: Ttc
+ void callbackCacheRead(bool success, LLImageFormatted* image,
+ S32 imagesize, BOOL islocal);
+
+ // Threads: Ttc
+ void callbackCacheWrite(bool success);
+
+ // Threads: Tid
+ void callbackDecoded(bool success, LLImageRaw* raw, LLImageRaw* aux);
+
+ // Threads: T*
+ void setGetStatus(LLCore::HttpStatus status, const std::string& reason)
+ {
+ LLMutexLock lock(&mWorkMutex);
+
+ mGetStatus = status;
+ mGetReason = reason;
+ }
+
+ void setCanUseHTTP(bool can_use_http) { mCanUseHTTP = can_use_http; }
+ bool getCanUseHTTP() const { return mCanUseHTTP; }
+
+ void setUrl(const std::string& url) { mUrl = url; }
+
+ LLTextureFetch & getFetcher() { return *mFetcher; }
+
+ // Inherited from LLCore::HttpHandler
+ // Threads: Ttf
+ virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response);
protected:
- LLTextureFetchWorker(LLTextureFetch* fetcher, const LLUUID& id, const LLHost& host,
+ LLTextureFetchWorker(LLTextureFetch* fetcher, FTType f_type,
+ const std::string& url, const LLUUID& id, const LLHost& host,
F32 priority, S32 discard, S32 size);
private:
+
+ // Threads: Tmain
/*virtual*/ void startWork(S32 param); // called from addWork() (MAIN THREAD)
+
+ // Threads: Tmain
/*virtual*/ void endWork(S32 param, bool aborted); // called from doWork() (MAIN THREAD)
- virtual std::string getName() { return LLStringUtil::null; }
+ // Locks: Mw
void resetFormattedData();
+ // Locks: Mw
void setImagePriority(F32 priority);
+
+ // Locks: Mw (ctor invokes without lock)
void setDesiredDiscard(S32 discard, S32 size);
+
+ // Threads: T*
+ // Locks: Mw
bool insertPacket(S32 index, U8* data, S32 size);
+
+ // Locks: Mw
void clearPackets();
+
+ // Locks: Mw
+ void setupPacketData();
+
+ // Locks: Mw (ctor invokes without lock)
U32 calcWorkPriority();
+
+ // Locks: Mw
void removeFromCache();
+
+ // Threads: Ttf
+ // Locks: Mw
bool processSimulatorPackets();
- bool decodeImage();
+
+ // Threads: Ttf
bool writeToCacheComplete();
- void lockWorkData() { mWorkMutex.lock(); }
- void unlockWorkData() { mWorkMutex.unlock(); }
+ // Threads: Ttf
+ void recordTextureStart(bool is_http);
- void callbackURLReceived(const LLSD& data, bool success);
- void callbackHttpGet(U8* data, S32 data_size, bool last_block);
- void callbackCacheRead(bool success, LLImageFormatted* image,
- S32 imagesize, BOOL islocal);
- void callbackCacheWrite(bool success);
- void callbackDecoded(bool success);
+ // Threads: Ttf
+ void recordTextureDone(bool is_http);
+
+ void lockWorkMutex() { mWorkMutex.lock(); }
+ void unlockWorkMutex() { mWorkMutex.unlock(); }
+
+ // Threads: Ttf
+ // Locks: Mw
+ bool acquireHttpSemaphore()
+ {
+ llassert(! mHttpHasResource);
+ if (mFetcher->mHttpSemaphore >= mFetcher->mHttpHighWater)
+ {
+ return false;
+ }
+ mHttpHasResource = true;
+ mFetcher->mHttpSemaphore++;
+ return true;
+ }
+
+ // Threads: Ttf
+ // Locks: Mw
+ void releaseHttpSemaphore()
+ {
+ llassert(mHttpHasResource);
+ mHttpHasResource = false;
+ mFetcher->mHttpSemaphore--;
+ llassert_always(mFetcher->mHttpSemaphore >= 0);
+ }
private:
enum e_state // mState
{
+ // *NOTE: Do not change the order/value of state variables, some code
+ // depends upon specific ordering/adjacency.
+
// NOTE: Affects LLTextureBar::draw in lltextureview.cpp (debug hack)
INVALID = 0,
INIT,
@@ -268,8 +514,10 @@ private:
CACHE_POST,
LOAD_FROM_NETWORK,
LOAD_FROM_SIMULATOR,
- LOAD_FROM_HTTP_GET_URL,
- LOAD_FROM_HTTP_GET_DATA,
+ WAIT_HTTP_RESOURCE, // Waiting for HTTP resources
+ WAIT_HTTP_RESOURCE2, // Waiting for HTTP resources
+ SEND_HTTP_REQ, // Commit to sending as HTTP
+ WAIT_HTTP_REQ, // Request sent, wait for completion
DECODE_IMAGE,
DECODE_IMAGE_UPDATE,
WRITE_TO_CACHE,
@@ -280,56 +528,75 @@ private:
{
UNSENT = 0,
QUEUED = 1,
- SENT_SIM = 2,
- SENT_URL = 3,
- SENT_HTTP = 4
+ SENT_SIM = 2
+ };
+ enum e_write_to_cache_state //mWriteToCacheState
+ {
+ NOT_WRITE = 0,
+ CAN_WRITE = 1,
+ SHOULD_WRITE = 2
};
static const char* sStateDescs[];
e_state mState;
+ void setState(e_state new_state);
+
+ e_write_to_cache_state mWriteToCacheState;
LLTextureFetch* mFetcher;
- LLImageWorker* mImageWorker;
LLPointer<LLImageFormatted> mFormattedImage;
- LLPointer<LLImageRaw> mRawImage;
- LLPointer<LLImageRaw> mAuxImage;
+ LLPointer<LLImageRaw> mRawImage,
+ mAuxImage;
+ FTType mFTType;
LLUUID mID;
LLHost mHost;
+ std::string mUrl;
U8 mType;
F32 mImagePriority;
U32 mWorkPriority;
F32 mRequestedPriority;
- S32 mDesiredDiscard;
- S32 mSimRequestedDiscard;
- S32 mRequestedDiscard;
- S32 mLoadedDiscard;
- S32 mDecodedDiscard;
- LLFrameTimer mRequestedTimer;
- LLFrameTimer mFetchTimer;
- LLTextureCache::handle_t mCacheReadHandle;
- LLTextureCache::handle_t mCacheWriteHandle;
- U8* mBuffer;
- S32 mBufferSize;
- S32 mRequestedSize;
- S32 mDesiredSize;
- S32 mFileSize;
- S32 mCachedSize;
- BOOL mLoaded;
+ S32 mDesiredDiscard,
+ mSimRequestedDiscard,
+ mRequestedDiscard,
+ mLoadedDiscard,
+ mDecodedDiscard;
+ LLFrameTimer mRequestedTimer,
+ mFetchTimer;
+ LLTimer mCacheReadTimer;
+ F32 mCacheReadTime;
+ LLTextureCache::handle_t mCacheReadHandle,
+ mCacheWriteHandle;
+ S32 mRequestedSize,
+ mRequestedOffset,
+ mDesiredSize,
+ mFileSize,
+ mCachedSize;
e_request_state mSentRequest;
+ handle_t mDecodeHandle;
+ BOOL mLoaded;
BOOL mDecoded;
BOOL mWritten;
BOOL mNeedsAux;
BOOL mHaveAllData;
BOOL mInLocalCache;
+ BOOL mInCache;
+ bool mCanUseHTTP,
+ mCanUseNET ; //can get from asset server.
S32 mRetryAttempt;
- std::string mURL;
S32 mActiveCount;
+ LLCore::HttpStatus mGetStatus;
+ std::string mGetReason;
+ LLAdaptiveRetryPolicy mFetchRetryPolicy;
+
// Work Data
LLMutex mWorkMutex;
struct PacketData
{
- PacketData(U8* data, S32 size) { mData = data; mSize = size; }
+ PacketData(U8* data, S32 size)
+ : mData(data), mSize(size)
+ {}
~PacketData() { clearData(); }
void clearData() { delete[] mData; mData = NULL; }
+
U8* mData;
U32 mSize;
};
@@ -338,27 +605,247 @@ private:
S32 mLastPacket;
U16 mTotalPackets;
U8 mImageCodec;
+
+ LLViewerAssetStats::duration_t mMetricsStartTime;
+
+ LLCore::HttpHandle mHttpHandle; // Handle of any active request
+ LLCore::BufferArray * mHttpBufferArray; // Refcounted pointer to response data
+ S32 mHttpPolicyClass;
+ bool mHttpActive; // Active request to http library
+ U32 mHttpReplySize, // Actual received data size
+ mHttpReplyOffset; // Actual received data offset
+ bool mHttpHasResource; // Counts against Fetcher's mHttpSemaphore
+
+ // State history
+ U32 mCacheReadCount,
+ mCacheWriteCount,
+ mResourceWaitCount; // Requests entering WAIT_HTTP_RESOURCE2
};
-class LLTextureFetchLocalFileWorker : public LLTextureFetchWorker
+//////////////////////////////////////////////////////////////////////////////
+
+// Cross-thread messaging for asset metrics.
+
+/**
+ * @brief Base class for cross-thread requests made of the fetcher
+ *
+ * I believe the intent of the LLQueuedThread class was to
+ * have these operations derived from LLQueuedThread::QueuedRequest
+ * but the texture fetcher has elected to manage the queue
+ * in its own manner. So these are free-standing objects which are
+ * managed in simple FIFO order on the mCommands queue of the
+ * LLTextureFetch object.
+ *
+ * What each represents is a simple command sent from an
+ * outside thread into the TextureFetch thread to be processed
+ * in order and in a timely fashion (though not an absolute
+ * higher priority than other operations of the thread).
+ * Each operation derives a new class from the base customizing
+ * members, constructors and the doWork() method to effect
+ * the command.
+ *
+ * The flow is one-directional. There are two global instances
+ * of the LLViewerAssetStats collector, one for the main program's
+ * thread pointed to by gViewerAssetStatsMain and one for the
+ * TextureFetch thread pointed to by gViewerAssetStatsThread1.
+ * Common operations has each thread recording metrics events
+ * into the respective collector unconcerned with locking and
+ * the state of any other thread. But when the agent moves into
+ * a different region or the metrics timer expires and a report
+ * needs to be sent back to the grid, messaging across threads
+ * is required to distribute data and perform global actions.
+ * In pseudo-UML, it looks like:
+ *
+ * Main Thread1
+ * . .
+ * . .
+ * +-----+ .
+ * | AM | .
+ * +--+--+ .
+ * +-------+ | .
+ * | Main | +--+--+ .
+ * | | | SRE |---. .
+ * | Stats | +-----+ \ .
+ * | | | \ (uuid) +-----+
+ * | Coll. | +--+--+ `-------->| SR |
+ * +-------+ | MSC | +--+--+
+ * | ^ +-----+ |
+ * | | (uuid) / . +-----+ (uuid)
+ * | `--------' . | MSC |---------.
+ * | . +-----+ |
+ * | +-----+ . v
+ * | | TE | . +-------+
+ * | +--+--+ . | Thd1 |
+ * | | . | |
+ * | +-----+ . | Stats |
+ * `--------->| RSC | . | |
+ * +--+--+ . | Coll. |
+ * | . +-------+
+ * +--+--+ . |
+ * | SME |---. . |
+ * +-----+ \ . |
+ * . \ (clone) +-----+ |
+ * . `-------->| SM | |
+ * . +--+--+ |
+ * . | |
+ * . +-----+ |
+ * . | RSC |<--------'
+ * . +-----+
+ * . |
+ * . +-----+
+ * . | CP |--> HTTP POST
+ * . +-----+
+ * . .
+ * . .
+ *
+ *
+ * Key:
+ *
+ * SRE - Set Region Enqueued. Enqueue a 'Set Region' command in
+ * the other thread providing the new UUID of the region.
+ * TFReqSetRegion carries the data.
+ * SR - Set Region. New region UUID is sent to the thread-local
+ * collector.
+ * SME - Send Metrics Enqueued. Enqueue a 'Send Metrics' command
+ * including an ownership transfer of a cloned LLViewerAssetStats.
+ * TFReqSendMetrics carries the data.
+ * SM - Send Metrics. Global metrics reporting operation. Takes
+ * the cloned stats from the command, merges it with the
+ * thread's local stats, converts to LLSD and sends it on
+ * to the grid.
+ * AM - Agent Moved. Agent has completed some sort of move to a
+ * new region.
+ * TE - Timer Expired. Metrics timer has expired (on the order
+ * of 10 minutes).
+ * CP - CURL Post
+ * MSC - Modify Stats Collector. State change in the thread-local
+ * collector. Typically a region change which affects the
+ * global pointers used to find the 'current stats'.
+ * RSC - Read Stats Collector. Extract collector data cloning it
+ * (i.e. deep copy) when necessary.
+ *
+ */
+class LLTextureFetch::TFRequest // : public LLQueuedThread::QueuedRequest
{
-friend class LLTextureFetch;
+public:
+ // Default ctors and assignment operator are correct.
-protected:
- LLTextureFetchLocalFileWorker(LLTextureFetch* fetcher, const std::string& filename, const LLUUID& id, const LLHost& host,
- F32 priority, S32 discard, S32 size)
- : LLTextureFetchWorker(fetcher, id, host, priority, discard, size),
- mFileName(filename)
- {}
+ virtual ~TFRequest()
+ {}
-private:
- /*virtual*/ std::string getName() { return mFileName; }
+ // Patterned after QueuedRequest's method but expected behavior
+ // is different. Always expected to complete on the first call
+ // and work dispatcher will assume the same and delete the
+ // request after invocation.
+ virtual bool doWork(LLTextureFetch * fetcher) = 0;
+};
+namespace
+{
-private:
- std::string mFileName;
+/**
+ * @brief Implements a 'Set Region' cross-thread command.
+ *
+ * When an agent moves to a new region, subsequent metrics need
+ * to be binned into a new or existing stats collection in 1:1
+ * relationship with the region. We communicate this region
+ * change across the threads involved in the communication with
+ * this message.
+ *
+ * Corresponds to LLTextureFetch::commandSetRegion()
+ */
+class TFReqSetRegion : public LLTextureFetch::TFRequest
+{
+public:
+ TFReqSetRegion(U64 region_handle)
+ : LLTextureFetch::TFRequest(),
+ mRegionHandle(region_handle)
+ {}
+ TFReqSetRegion & operator=(const TFReqSetRegion &); // Not defined
+
+ virtual ~TFReqSetRegion()
+ {}
+
+ virtual bool doWork(LLTextureFetch * fetcher);
+
+public:
+ const U64 mRegionHandle;
+};
+
+
+/**
+ * @brief Implements a 'Send Metrics' cross-thread command.
+ *
+ * This is the big operation. The main thread gathers metrics
+ * for a period of minutes into LLViewerAssetStats and other
+ * objects then makes a snapshot of the data by cloning the
+ * collector. This command transfers the clone, along with a few
+ * additional arguments (UUIDs), handing ownership to the
+ * TextureFetch thread. It then merges its own data into the
+ * cloned copy, converts to LLSD and kicks off an HTTP POST of
+ * the resulting data to the currently active metrics collector.
+ *
+ * Corresponds to LLTextureFetch::commandSendMetrics()
+ */
+class TFReqSendMetrics : public LLTextureFetch::TFRequest
+{
+public:
+ /**
+ * Construct the 'Send Metrics' command to have the TextureFetch
+ * thread add and log metrics data.
+ *
+ * @param caps_url URL of a "ViewerMetrics" Caps target
+ * to receive the data. Does not have to
+ * be associated with a particular region.
+ *
+ * @param session_id UUID of the agent's session.
+ *
+ * @param agent_id UUID of the agent. (Being pure here...)
+ *
+ * @param main_stats Pointer to a clone of the main thread's
+ * LLViewerAssetStats data. Thread1 takes
+ * ownership of the copy and disposes of it
+ * when done.
+ */
+ TFReqSendMetrics(const std::string & caps_url,
+ const LLUUID & session_id,
+ const LLUUID & agent_id,
+ LLViewerAssetStats * main_stats)
+ : LLTextureFetch::TFRequest(),
+ mCapsURL(caps_url),
+ mSessionID(session_id),
+ mAgentID(agent_id),
+ mMainStats(main_stats)
+ {}
+ TFReqSendMetrics & operator=(const TFReqSendMetrics &); // Not defined
+
+ virtual ~TFReqSendMetrics();
+
+ virtual bool doWork(LLTextureFetch * fetcher);
+
+public:
+ const std::string mCapsURL;
+ const LLUUID mSessionID;
+ const LLUUID mAgentID;
+ LLViewerAssetStats * mMainStats;
};
+/*
+ * Examines the merged viewer metrics report and if found to be too long,
+ * will attempt to truncate it in some reasonable fashion.
+ *
+ * @param max_regions Limit of regions allowed in report.
+ *
+ * @param metrics Full, merged viewer metrics report.
+ *
+ * @returns If data was truncated, returns true.
+ */
+bool truncate_viewer_metrics(int max_regions, LLSD & metrics);
+
+} // end of anonymous namespace
+
+
+//////////////////////////////////////////////////////////////////////////////
//static
const char* LLTextureFetchWorker::sStateDescs[] = {
@@ -368,29 +855,39 @@ const char* LLTextureFetchWorker::sStateDescs[] = {
"CACHE_POST",
"LOAD_FROM_NETWORK",
"LOAD_FROM_SIMULATOR",
- "LOAD_FROM_HTTP_URL",
- "LOAD_FROM_HTTP_DATA",
+ "WAIT_HTTP_RESOURCE",
+ "WAIT_HTTP_RESOURCE2",
+ "SEND_HTTP_REQ",
+ "WAIT_HTTP_REQ",
"DECODE_IMAGE",
"DECODE_IMAGE_UPDATE",
"WRITE_TO_CACHE",
"WAIT_ON_WRITE",
- "DONE",
+ "DONE"
};
+// static
+volatile bool LLTextureFetch::svMetricsDataBreak(true); // Start with a data break
+
// called from MAIN THREAD
LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher,
+ FTType f_type, // Fetched image type
+ const std::string& url, // Optional URL
const LLUUID& id, // Image UUID
const LLHost& host, // Simulator host
F32 priority, // Priority
S32 discard, // Desired discard
S32 size) // Desired size
: LLWorkerClass(fetcher, "TextureFetch"),
+ LLCore::HttpHandler(),
mState(INIT),
+ mWriteToCacheState(NOT_WRITE),
mFetcher(fetcher),
- mImageWorker(NULL),
+ mFTType(f_type),
mID(id),
mHost(host),
+ mUrl(url),
mImagePriority(priority),
mWorkPriority(0),
mRequestedPriority(0.f),
@@ -399,32 +896,49 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher,
mRequestedDiscard(-1),
mLoadedDiscard(-1),
mDecodedDiscard(-1),
+ mCacheReadTime(0.f),
mCacheReadHandle(LLTextureCache::nullHandle()),
mCacheWriteHandle(LLTextureCache::nullHandle()),
- mBuffer(NULL),
- mBufferSize(0),
mRequestedSize(0),
- mDesiredSize(FIRST_PACKET_SIZE),
+ mRequestedOffset(0),
+ mDesiredSize(TEXTURE_CACHE_ENTRY_SIZE),
mFileSize(0),
mCachedSize(0),
mLoaded(FALSE),
mSentRequest(UNSENT),
+ mDecodeHandle(0),
mDecoded(FALSE),
mWritten(FALSE),
mNeedsAux(FALSE),
mHaveAllData(FALSE),
mInLocalCache(FALSE),
+ mInCache(FALSE),
+ mCanUseHTTP(true),
mRetryAttempt(0),
mActiveCount(0),
mWorkMutex(NULL),
mFirstPacket(0),
mLastPacket(-1),
mTotalPackets(0),
- mImageCodec(IMG_CODEC_INVALID)
+ mImageCodec(IMG_CODEC_INVALID),
+ mMetricsStartTime(0),
+ mHttpHandle(LLCORE_HTTP_HANDLE_INVALID),
+ mHttpBufferArray(NULL),
+ mHttpPolicyClass(mFetcher->mHttpPolicyClass),
+ mHttpActive(false),
+ mHttpReplySize(0U),
+ mHttpReplyOffset(0U),
+ mHttpHasResource(false),
+ mCacheReadCount(0U),
+ mCacheWriteCount(0U),
+ mResourceWaitCount(0U),
+ mFetchRetryPolicy(10.0,3600.0,2.0,10)
{
+ mCanUseNET = mUrl.empty() ;
+
calcWorkPriority();
mType = host.isOk() ? LLImageBase::TYPE_AVATAR_BAKE : LLImageBase::TYPE_NORMAL;
-// llinfos << "Create: " << mID << " mHost:" << host << " Discard=" << discard << llendl;
+// LL_INFOS(LOG_TXT) << "Create: " << mID << " mHost:" << host << " Discard=" << discard << LL_ENDL;
if (!mFetcher->mDebugPause)
{
U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
@@ -435,29 +949,47 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher,
LLTextureFetchWorker::~LLTextureFetchWorker()
{
-// llinfos << "Destroy: " << mID
+// LL_INFOS(LOG_TXT) << "Destroy: " << mID
// << " Decoded=" << mDecodedDiscard
// << " Requested=" << mRequestedDiscard
-// << " Desired=" << mDesiredDiscard << llendl;
+// << " Desired=" << mDesiredDiscard << LL_ENDL;
llassert_always(!haveWork());
- lockWorkData();
- if (mCacheReadHandle != LLTextureCache::nullHandle())
+
+ lockWorkMutex(); // +Mw (should be useless)
+ if (mHttpHasResource)
{
- mFetcher->mTextureCache->readComplete(mCacheReadHandle, true);
+ // Last-chance catchall to recover the resource. Using an
+ // atomic datatype solely because this can be running in
+ // another thread.
+ releaseHttpSemaphore();
}
- if (mCacheWriteHandle != LLTextureCache::nullHandle())
+ if (mHttpActive)
{
- mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true);
+ // Issue a cancel on a live request...
+ mFetcher->getHttpRequest().requestCancel(mHttpHandle, NULL);
}
- if (mImageWorker)
+ if (mCacheReadHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache)
{
- mImageWorker->scheduleDelete();
+ mFetcher->mTextureCache->readComplete(mCacheReadHandle, true);
+ }
+ if (mCacheWriteHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache)
+ {
+ mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true);
}
mFormattedImage = NULL;
clearPackets();
- unlockWorkData();
+ if (mHttpBufferArray)
+ {
+ mHttpBufferArray->release();
+ mHttpBufferArray = NULL;
+ }
+ unlockWorkMutex(); // -Mw
+ mFetcher->removeFromHTTPQueue(mID, (S32Bytes)0);
+ mFetcher->removeHttpWaiter(mID);
+ mFetcher->updateStateStats(mCacheReadCount, mCacheWriteCount, mResourceWaitCount);
}
+// Locks: Mw
void LLTextureFetchWorker::clearPackets()
{
for_each(mPackets.begin(), mPackets.end(), DeletePointer());
@@ -467,15 +999,50 @@ void LLTextureFetchWorker::clearPackets()
mFirstPacket = 0;
}
+// Locks: Mw
+void LLTextureFetchWorker::setupPacketData()
+{
+ S32 data_size = 0;
+ if (mFormattedImage.notNull())
+ {
+ data_size = mFormattedImage->getDataSize();
+ }
+ if (data_size > 0)
+ {
+ // Only used for simulator requests
+ mFirstPacket = (data_size - FIRST_PACKET_SIZE) / MAX_IMG_PACKET_SIZE + 1;
+ if (FIRST_PACKET_SIZE + (mFirstPacket-1) * MAX_IMG_PACKET_SIZE != data_size)
+ {
+ LL_WARNS(LOG_TXT) << "Bad CACHED TEXTURE size: " << data_size << " removing." << LL_ENDL;
+ removeFromCache();
+ resetFormattedData();
+ clearPackets();
+ }
+ else if (mFileSize > 0)
+ {
+ mLastPacket = mFirstPacket-1;
+ mTotalPackets = (mFileSize - FIRST_PACKET_SIZE + MAX_IMG_PACKET_SIZE-1) / MAX_IMG_PACKET_SIZE + 1;
+ }
+ else
+ {
+ // This file was cached using HTTP so we have to refetch the first packet
+ resetFormattedData();
+ clearPackets();
+ }
+ }
+}
+
+// Locks: Mw (ctor invokes without lock)
U32 LLTextureFetchWorker::calcWorkPriority()
{
-// llassert_always(mImagePriority >= 0 && mImagePriority <= LLViewerTexture::maxDecodePriority());
- F32 priority_scale = (F32)LLWorkerThread::PRIORITY_LOWBITS / LLViewerFetchedTexture::maxDecodePriority();
- mWorkPriority = (U32)(mImagePriority * priority_scale);
+ //llassert_always(mImagePriority >= 0 && mImagePriority <= LLViewerFetchedTexture::maxDecodePriority());
+ static const F32 PRIORITY_SCALE = (F32)LLWorkerThread::PRIORITY_LOWBITS / LLViewerFetchedTexture::maxDecodePriority();
+
+ mWorkPriority = llmin((U32)LLWorkerThread::PRIORITY_LOWBITS, (U32)(mImagePriority * PRIORITY_SCALE));
return mWorkPriority;
}
-// mWorkMutex is locked
+// Locks: Mw (ctor invokes without lock)
void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size)
{
bool prioritize = false;
@@ -502,14 +1069,16 @@ void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size)
mDesiredSize = size;
prioritize = true;
}
+ mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE);
if ((prioritize && mState == INIT) || mState == DONE)
{
- mState = INIT;
+ setState(INIT);
U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
setPriority(work_priority);
}
}
+// Locks: Mw
void LLTextureFetchWorker::setImagePriority(F32 priority)
{
// llassert_always(priority >= 0 && priority <= LLViewerTexture::maxDecodePriority());
@@ -523,31 +1092,60 @@ void LLTextureFetchWorker::setImagePriority(F32 priority)
}
}
+// Locks: Mw
void LLTextureFetchWorker::resetFormattedData()
{
- delete[] mBuffer;
- mBuffer = NULL;
- mBufferSize = 0;
+ if (mHttpBufferArray)
+ {
+ mHttpBufferArray->release();
+ mHttpBufferArray = NULL;
+ }
if (mFormattedImage.notNull())
{
mFormattedImage->deleteData();
}
+ mHttpReplySize = 0;
+ mHttpReplyOffset = 0;
mHaveAllData = FALSE;
}
-// Called from MAIN thread
+// Threads: Tmain
void LLTextureFetchWorker::startWork(S32 param)
{
- llassert(mImageWorker == NULL);
llassert(mFormattedImage.isNull());
}
-#include "llviewertexturelist.h" // debug
-
-// Called from LLWorkerThread::processRequest()
+// Threads: Ttf
bool LLTextureFetchWorker::doWork(S32 param)
{
- LLMutexLock lock(&mWorkMutex);
+ static const LLCore::HttpStatus http_not_found(HTTP_NOT_FOUND); // 404
+ static const LLCore::HttpStatus http_service_unavail(HTTP_SERVICE_UNAVAILABLE); // 503
+ static const LLCore::HttpStatus http_not_sat(HTTP_REQUESTED_RANGE_NOT_SATISFIABLE); // 416;
+
+ LLMutexLock lock(&mWorkMutex); // +Mw
+
+ if ((mFetcher->isQuitting() || getFlags(LLWorkerClass::WCF_DELETE_REQUESTED)))
+ {
+ if (mState < DECODE_IMAGE)
+ {
+ return true; // abort
+ }
+ }
+
+ if(mImagePriority < F_ALMOST_ZERO)
+ {
+ if (mState == INIT || mState == LOAD_FROM_NETWORK || mState == LOAD_FROM_SIMULATOR)
+ {
+ LL_DEBUGS(LOG_TXT) << mID << " abort: mImagePriority < F_ALMOST_ZERO" << LL_ENDL;
+ return true; // abort
+ }
+ }
+ if(mState > CACHE_POST && !mCanUseNET && !mCanUseHTTP)
+ {
+ //nowhere to get data, abort.
+ LL_WARNS(LOG_TXT) << mID << " abort, nowhere to get data" << LL_ENDL;
+ return true ;
+ }
if (mFetcher->mDebugPause)
{
@@ -563,35 +1161,37 @@ bool LLTextureFetchWorker::doWork(S32 param)
mFetchTimer.reset();
}
- if (mImagePriority <= 0.0f)
- {
- if (mState < WRITE_TO_CACHE)
- {
- return true; // cancel request
- }
- }
-
if (mState == INIT)
- {
+ {
+ mRawImage = NULL ;
mRequestedDiscard = -1;
mLoadedDiscard = -1;
mDecodedDiscard = -1;
mRequestedSize = 0;
+ mRequestedOffset = 0;
mFileSize = 0;
mCachedSize = 0;
mLoaded = FALSE;
mSentRequest = UNSENT;
mDecoded = FALSE;
mWritten = FALSE;
- delete[] mBuffer;
- mBuffer = NULL;
- mBufferSize = 0;
+ if (mHttpBufferArray)
+ {
+ mHttpBufferArray->release();
+ mHttpBufferArray = NULL;
+ }
+ mHttpReplySize = 0;
+ mHttpReplyOffset = 0;
mHaveAllData = FALSE;
clearPackets(); // TODO: Shouldn't be necessary
mCacheReadHandle = LLTextureCache::nullHandle();
mCacheWriteHandle = LLTextureCache::nullHandle();
- mURL.clear();
- mState = LOAD_FROM_TEXTURE_CACHE;
+ setState(LOAD_FROM_TEXTURE_CACHE);
+ mInCache = FALSE;
+ mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE); // min desired size is TEXTURE_CACHE_ENTRY_SIZE
+ LL_DEBUGS(LOG_TXT) << mID << ": Priority: " << llformat("%8.0f",mImagePriority)
+ << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL;
+
// fall through
}
@@ -604,24 +1204,43 @@ bool LLTextureFetchWorker::doWork(S32 param)
S32 size = mDesiredSize - offset;
if (size <= 0)
{
- mState = CACHE_POST;
+ setState(CACHE_POST);
return false;
}
mFileSize = 0;
- mLoaded = FALSE;
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
+ mLoaded = FALSE;
+
+ if (mUrl.compare(0, 7, "file://") == 0)
+ {
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
- CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage);
- if (getName().empty())
+ // read file from local disk
+ ++mCacheReadCount;
+ std::string filename = mUrl.substr(7, std::string::npos);
+ CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage);
+ mCacheReadHandle = mFetcher->mTextureCache->readFromCache(filename, mID, cache_priority,
+ offset, size, responder);
+ mCacheReadTimer.reset();
+ }
+ else if ((mUrl.empty() || mFTType==FTT_SERVER_BAKE) && mFetcher->canLoadFromCache())
{
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
+
+ ++mCacheReadCount;
+ CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage);
mCacheReadHandle = mFetcher->mTextureCache->readFromCache(mID, cache_priority,
offset, size, responder);
+ mCacheReadTimer.reset();
+ }
+ else if(!mUrl.empty() && mCanUseHTTP)
+ {
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ setState(WAIT_HTTP_RESOURCE);
}
else
{
- // read file from local disk
- mCacheReadHandle = mFetcher->mTextureCache->readFromCache(getName(), mID, cache_priority,
- offset, size, responder);
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ setState(LOAD_FROM_NETWORK);
}
}
@@ -631,11 +1250,15 @@ bool LLTextureFetchWorker::doWork(S32 param)
if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, false))
{
mCacheReadHandle = LLTextureCache::nullHandle();
- mState = CACHE_POST;
+ setState(CACHE_POST);
// fall through
}
else
{
+ //
+ //This should never happen
+ //
+ LL_DEBUGS(LOG_TXT) << mID << " this should never happen" << LL_ENDL;
return false;
}
}
@@ -647,266 +1270,627 @@ bool LLTextureFetchWorker::doWork(S32 param)
if (mState == CACHE_POST)
{
- mDesiredSize = llmax(mDesiredSize, FIRST_PACKET_SIZE);
mCachedSize = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
// Successfully loaded
if ((mCachedSize >= mDesiredSize) || mHaveAllData)
{
// we have enough data, decode it
llassert_always(mFormattedImage->getDataSize() > 0);
- mState = DECODE_IMAGE;
- // fall through
+ mLoadedDiscard = mDesiredDiscard;
+ if (mLoadedDiscard < 0)
+ {
+ LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard
+ << ", should be >=0" << LL_ENDL;
+ }
+ setState(DECODE_IMAGE);
+ mInCache = TRUE;
+ mWriteToCacheState = NOT_WRITE ;
+ LL_DEBUGS(LOG_TXT) << mID << ": Cached. Bytes: " << mFormattedImage->getDataSize()
+ << " Size: " << llformat("%dx%d",mFormattedImage->getWidth(),mFormattedImage->getHeight())
+ << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL;
+ record(LLTextureFetch::sCacheHitRate, LLUnits::Ratio::fromValue(1));
}
else
{
- if (!getName().empty())
+ if (mUrl.compare(0, 7, "file://") == 0)
{
// failed to load local file, we're done.
+ LL_WARNS(LOG_TXT) << mID << ": abort, failed to load local file " << mUrl << LL_ENDL;
return true;
}
// need more data
- mState = LOAD_FROM_NETWORK;
+ else
+ {
+ LL_DEBUGS(LOG_TXT) << mID << ": Not in Cache" << LL_ENDL;
+ setState(LOAD_FROM_NETWORK);
+ }
+
// fall through
+ record(LLTextureFetch::sCacheHitRate, LLUnits::Ratio::fromValue(0));
}
}
if (mState == LOAD_FROM_NETWORK)
{
- if (mSentRequest == UNSENT)
+ // Check for retries to previous server failures.
+ F32 wait_seconds;
+ if (mFetchRetryPolicy.shouldRetry(wait_seconds))
{
- if (mFormattedImage.isNull())
+ if (wait_seconds <= 0.0)
{
- mFormattedImage = new LLImageJ2C;
+ LL_INFOS(LOG_TXT) << mID << " retrying now" << LL_ENDL;
}
- // Add this to the network queue and sit here.
- // LLTextureFetch::update() will send off a request which will change our state
- S32 data_size = mFormattedImage->getDataSize();
- if (data_size > 0)
+ else
+ {
+ //LL_INFOS(LOG_TXT) << mID << " waiting to retry for " << wait_seconds << " seconds" << LL_ENDL;
+ return false;
+ }
+ }
+
+ static LLCachedControl<bool> use_http(gSavedSettings, "ImagePipelineUseHTTP", true);
+
+// if (mHost != LLHost::invalid) get_url = false;
+ if ( use_http && mCanUseHTTP && mUrl.empty())//get http url.
+ {
+ LLViewerRegion* region = NULL;
+ if (mHost == LLHost::invalid)
+ region = gAgent.getRegion();
+ else
+ region = LLWorld::getInstance()->getRegion(mHost);
+
+ if (region)
{
- // Only used for simulator requests
- mFirstPacket = (data_size - FIRST_PACKET_SIZE) / MAX_IMG_PACKET_SIZE + 1;
- if (FIRST_PACKET_SIZE + (mFirstPacket-1) * MAX_IMG_PACKET_SIZE != data_size)
+ std::string http_url = region->getHttpUrl() ;
+ if (!http_url.empty())
{
-// llwarns << "Bad CACHED TEXTURE size: " << data_size << " removing." << llendl;
- removeFromCache();
- resetFormattedData();
- clearPackets();
+ if (mFTType != FTT_DEFAULT)
+ {
+ LL_WARNS(LOG_TXT) << "trying to seek a non-default texture on the sim. Bad!" << LL_ENDL;
+ }
+ setUrl(http_url + "/?texture_id=" + mID.asString().c_str());
+ LL_DEBUGS(LOG_TXT) << "Texture URL: " << mUrl << LL_ENDL;
+ mWriteToCacheState = CAN_WRITE ; //because this texture has a fixed texture id.
}
else
{
- mLastPacket = mFirstPacket-1;
- mTotalPackets = (mFileSize - FIRST_PACKET_SIZE + MAX_IMG_PACKET_SIZE-1) / MAX_IMG_PACKET_SIZE + 1;
+ mCanUseHTTP = false ;
+ LL_DEBUGS(LOG_TXT) << "Texture not available via HTTP: empty URL." << LL_ENDL;
}
}
+ else
+ {
+ // This will happen if not logged in or if a region deoes not have HTTP Texture enabled
+ //LL_WARNS(LOG_TXT) << "Region not found for host: " << mHost << LL_ENDL;
+ LL_DEBUGS(LOG_TXT) << "Texture not available via HTTP: no region " << mUrl << LL_ENDL;
+ mCanUseHTTP = false;
+ }
+ }
+ else if (mFTType == FTT_SERVER_BAKE)
+ {
+ mWriteToCacheState = CAN_WRITE;
+ }
+
+ if (mCanUseHTTP && !mUrl.empty())
+ {
+ setState(WAIT_HTTP_RESOURCE);
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ if(mWriteToCacheState != NOT_WRITE)
+ {
+ mWriteToCacheState = CAN_WRITE ;
+ }
+ // don't return, fall through to next state
+ }
+ else if (mSentRequest == UNSENT && mCanUseNET)
+ {
+ // Add this to the network queue and sit here.
+ // LLTextureFetch::update() will send off a request which will change our state
+ mWriteToCacheState = CAN_WRITE ;
mRequestedSize = mDesiredSize;
mRequestedDiscard = mDesiredDiscard;
mSentRequest = QUEUED;
- mFetcher->lockQueue();
mFetcher->addToNetworkQueue(this);
- mFetcher->unlockQueue();
+ recordTextureStart(false);
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+
+ return false;
+ }
+ else
+ {
+ // Shouldn't need to do anything here
+ //llassert_always(mFetcher->mNetworkQueue.find(mID) != mFetcher->mNetworkQueue.end());
+ // Make certain this is in the network queue
+ //mFetcher->addToNetworkQueue(this);
+ //recordTextureStart(false);
+ //setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+
+ return false;
}
- return false;
}
if (mState == LOAD_FROM_SIMULATOR)
{
+ if (mFormattedImage.isNull())
+ {
+ mFormattedImage = new LLImageJ2C;
+ }
if (processSimulatorPackets())
{
- mFetcher->lockQueue();
- mFetcher->removeFromNetworkQueue(this);
- mFetcher->unlockQueue();
+ LL_DEBUGS(LOG_TXT) << mID << ": Loaded from Sim. Bytes: " << mFormattedImage->getDataSize() << LL_ENDL;
+ mFetcher->removeFromNetworkQueue(this, false);
if (mFormattedImage.isNull() || !mFormattedImage->getDataSize())
{
// processSimulatorPackets() failed
-// llwarns << "processSimulatorPackets() failed to load buffer" << llendl;
+// LL_WARNS(LOG_TXT) << "processSimulatorPackets() failed to load buffer" << LL_ENDL;
+ LL_WARNS(LOG_TXT) << mID << " processSimulatorPackets() failed to load buffer" << LL_ENDL;
return true; // failed
}
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- mState = DECODE_IMAGE;
+ if (mLoadedDiscard < 0)
+ {
+ LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard
+ << ", should be >=0" << LL_ENDL;
+ }
+ setState(DECODE_IMAGE);
+ mWriteToCacheState = SHOULD_WRITE;
+ recordTextureDone(false);
}
else
{
+ mFetcher->addToNetworkQueue(this); // failsafe
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ recordTextureStart(false);
}
return false;
}
-#if 0
- if (mState == LOAD_FROM_HTTP_GET_URL)
+ if (mState == WAIT_HTTP_RESOURCE)
{
- if (!mSentRequest)
+ // NOTE:
+ // control the number of the http requests issued for:
+ // 1, not openning too many file descriptors at the same time;
+ // 2, control the traffic of http so udp gets bandwidth.
+ //
+ // If it looks like we're busy, keep this request here.
+ // Otherwise, advance into the HTTP states.
+ if (mFetcher->getHttpWaitersCount() || ! acquireHttpSemaphore())
{
- mSentRequest = TRUE;
- mLoaded = FALSE;
- std::string url;
- LLViewerRegion* region = gAgent.getRegion();
- if (region)
- {
- url = region->getCapability("RequestTextureDownload");
- }
- if (!url.empty())
- {
- LLSD sd;
- sd = mID.asString();
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- LLHTTPClient::post(url, sd, new URLResponder(mFetcher, mID));
- return false;
- }
- else
- {
-// llwarns << mID << ": HTTP get url failed, requesting from simulator" << llendl;
- mSentRequest = FALSE;
- mState = LOAD_FROM_SIMULATOR;
- return false;
- }
+ setState(WAIT_HTTP_RESOURCE2);
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ mFetcher->addHttpWaiter(this->mID);
+ ++mResourceWaitCount;
+ return false;
}
- else
+
+ setState(SEND_HTTP_REQ);
+ // *NOTE: You must invoke releaseHttpSemaphore() if you transition
+ // to a state other than SEND_HTTP_REQ or WAIT_HTTP_REQ or abort
+ // the request.
+ }
+
+ if (mState == WAIT_HTTP_RESOURCE2)
+ {
+ // Just idle it if we make it to the head...
+ return false;
+ }
+
+ if (mState == SEND_HTTP_REQ)
+ {
+ // Also used in llmeshrepository
+ static LLCachedControl<bool> disable_range_req(gSavedSettings, "HttpRangeRequestsDisable", false);
+
+ if (! mCanUseHTTP)
{
- if (mLoaded)
+ releaseHttpSemaphore();
+ LL_WARNS(LOG_TXT) << mID << " abort: SEND_HTTP_REQ but !mCanUseHTTP" << LL_ENDL;
+ return true; // abort
+ }
+
+ mFetcher->removeFromNetworkQueue(this, false);
+
+ S32 cur_size = 0;
+ if (mFormattedImage.notNull())
+ {
+ cur_size = mFormattedImage->getDataSize(); // amount of data we already have
+ if (mFormattedImage->getDiscardLevel() == 0)
{
- if (!mURL.empty())
+ if (cur_size > 0)
{
- mState = LOAD_FROM_HTTP_GET_DATA;
- mSentRequest = FALSE; // reset
- mLoaded = FALSE; // reset
+ // We already have all the data, just decode it
+ mLoadedDiscard = mFormattedImage->getDiscardLevel();
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ if (mLoadedDiscard < 0)
+ {
+ LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard
+ << ", should be >=0" << LL_ENDL;
+ }
+ setState(DECODE_IMAGE);
+ releaseHttpSemaphore();
+ return false;
}
else
{
-// llwarns << mID << ": HTTP get url is empty, requesting from simulator" << llendl;
- mSentRequest = FALSE;
- mState = LOAD_FROM_SIMULATOR;
- return false;
+ releaseHttpSemaphore();
+ LL_WARNS(LOG_TXT) << mID << " SEND_HTTP_REQ abort: cur_size " << cur_size << " <=0" << LL_ENDL;
+ return true; // abort.
}
}
}
+ mRequestedSize = mDesiredSize;
+ mRequestedDiscard = mDesiredDiscard;
+ mRequestedSize -= cur_size;
+ mRequestedOffset = cur_size;
+ if (mRequestedOffset)
+ {
+ // Texture fetching often issues 'speculative' loads that
+ // start beyond the end of the actual asset. Some cache/web
+ // systems, e.g. Varnish, will respond to this not with a
+ // 416 but with a 200 and the entire asset in the response
+ // body. By ensuring that we always have a partially
+ // satisfiable Range request, we avoid that hit to the network.
+ // We just have to deal with the overlapping data which is made
+ // somewhat harder by the fact that grid services don't necessarily
+ // return the Content-Range header on 206 responses. *Sigh*
+ mRequestedOffset -= 1;
+ mRequestedSize += 1;
+ }
+ mHttpHandle = LLCORE_HTTP_HANDLE_INVALID;
+
+ if (mUrl.empty())
+ {
+ // *FIXME: This should not be reachable except it has become
+ // so after some recent 'work'. Need to track this down
+ // and illuminate the unenlightened.
+ LL_WARNS(LOG_TXT) << "HTTP GET request failed for " << mID
+ << " on empty URL." << LL_ENDL;
+ resetFormattedData();
+ releaseHttpSemaphore();
+ return true; // failed
+ }
+
+ mRequestedTimer.reset();
+ mLoaded = FALSE;
+ mGetStatus = LLCore::HttpStatus();
+ mGetReason.clear();
+ LL_DEBUGS(LOG_TXT) << "HTTP GET: " << mID << " Offset: " << mRequestedOffset
+ << " Bytes: " << mRequestedSize
+ << " Bandwidth(kbps): " << mFetcher->getTextureBandwidth() << "/" << mFetcher->mMaxBandwidth
+ << LL_ENDL;
+
+ // Will call callbackHttpGet when curl request completes
+ // Only server bake images use the returned headers currently, for getting retry-after field.
+ LLCore::HttpOptions *options = (mFTType == FTT_SERVER_BAKE) ? mFetcher->mHttpOptionsWithHeaders: mFetcher->mHttpOptions;
+ if (disable_range_req)
+ {
+ // 'Range:' requests may be disabled in which case all HTTP
+ // texture fetches result in full fetches. This can be used
+ // by people with questionable ISPs or networking gear that
+ // doesn't handle these well.
+ mHttpHandle = mFetcher->mHttpRequest->requestGet(mHttpPolicyClass,
+ mWorkPriority,
+ mUrl,
+ options,
+ mFetcher->mHttpHeaders,
+ this);
+ }
+ else
+ {
+ mHttpHandle = mFetcher->mHttpRequest->requestGetByteRange(mHttpPolicyClass,
+ mWorkPriority,
+ mUrl,
+ mRequestedOffset,
+ (mRequestedOffset + mRequestedSize) > HTTP_REQUESTS_RANGE_END_MAX
+ ? 0
+ : mRequestedSize,
+ options,
+ mFetcher->mHttpHeaders,
+ this);
+ }
+ if (LLCORE_HTTP_HANDLE_INVALID == mHttpHandle)
+ {
+ LLCore::HttpStatus status(mFetcher->mHttpRequest->getStatus());
+ LL_WARNS(LOG_TXT) << "HTTP GET request failed for " << mID
+ << ", Status: " << status.toTerseString()
+ << " Reason: '" << status.toString() << "'"
+ << LL_ENDL;
+ resetFormattedData();
+ releaseHttpSemaphore();
+ return true; // failed
+ }
+
+ mHttpActive = true;
+ mFetcher->addToHTTPQueue(mID);
+ recordTextureStart(true);
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ setState(WAIT_HTTP_REQ);
+
// fall through
}
- if (mState == LOAD_FROM_HTTP_GET_DATA)
+ if (mState == WAIT_HTTP_REQ)
{
- if (!mSentRequest)
+ // *NOTE: As stated above, all transitions out of this state should
+ // call releaseHttpSemaphore().
+ if (mLoaded)
{
- mSentRequest = TRUE;
- S32 cur_size = mFormattedImage->getDataSize(); // amount of data we already have
- mRequestedSize = mDesiredSize;
- mRequestedDiscard = mDesiredDiscard;
-#if 1 // *TODO: LLCurl::getByteRange is broken (ignores range)
- cur_size = 0;
- mFormattedImage->deleteData();
-#endif
- mRequestedSize -= cur_size;
- // F32 priority = mImagePriority / (F32)LLViewerTexture::maxDecodePriority(); // 0-1
- S32 offset = cur_size;
- mBufferSize = cur_size; // This will get modified by callbackHttpGet()
- std::string url;
- if (mURL.empty())
- {
- //url = "http://asset.agni/0000002f-38ae-0e17-8e72-712e58964e9c.texture";
- std::stringstream urlstr;
- urlstr << "http://asset.agni/" << mID.asString() << ".texture";
- url = urlstr.str();
+ S32 cur_size = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
+ if (mRequestedSize < 0)
+ {
+ if (http_not_found == mGetStatus)
+ {
+ if (mFTType != FTT_MAP_TILE)
+ {
+ LL_WARNS(LOG_TXT) << "Texture missing from server (404): " << mUrl << LL_ENDL;
+ }
+
+ if(mWriteToCacheState == NOT_WRITE) //map tiles or server bakes
+ {
+ setState(DONE);
+ releaseHttpSemaphore();
+ if (mFTType != FTT_MAP_TILE)
+ {
+ LL_WARNS(LOG_TXT) << mID << " abort: WAIT_HTTP_REQ not found" << LL_ENDL;
+ }
+ return true;
+ }
+
+ // roll back to try UDP
+ if (mCanUseNET)
+ {
+ setState(INIT);
+ mCanUseHTTP = false;
+ mUrl.clear();
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ releaseHttpSemaphore();
+ return false;
+ }
+ }
+ else if (http_service_unavail == mGetStatus)
+ {
+ LL_INFOS_ONCE(LOG_TXT) << "Texture server busy (503): " << mUrl << LL_ENDL;
+ }
+ else if (http_not_sat == mGetStatus)
+ {
+ // Allowed, we'll accept whatever data we have as complete.
+ mHaveAllData = TRUE;
+ }
+ else
+ {
+ LL_INFOS(LOG_TXT) << "HTTP GET failed for: " << mUrl
+ << " Status: " << mGetStatus.toTerseString()
+ << " Reason: '" << mGetReason << "'"
+ << LL_ENDL;
+ }
+
+ if (mFTType != FTT_SERVER_BAKE)
+ {
+ mUrl.clear();
+ }
+ if (cur_size > 0)
+ {
+ // Use available data
+ mLoadedDiscard = mFormattedImage->getDiscardLevel();
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ if (mLoadedDiscard < 0)
+ {
+ LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard
+ << ", should be >=0" << LL_ENDL;
+ }
+ setState(DECODE_IMAGE);
+ releaseHttpSemaphore();
+ return false;
+ }
+
+ // Fail harder
+ resetFormattedData();
+ setState(DONE);
+ releaseHttpSemaphore();
+ LL_WARNS(LOG_TXT) << mID << " abort: fail harder" << LL_ENDL;
+ return true; // failed
}
- else
+
+ // Clear the url since we're done with the fetch
+ // Note: mUrl is used to check is fetching is required so failure to clear it will force an http fetch
+ // next time the texture is requested, even if the data have already been fetched.
+ if(mWriteToCacheState != NOT_WRITE && mFTType != FTT_SERVER_BAKE)
{
- url = mURL;
+ // Why do we want to keep url if NOT_WRITE - is this a proxy for map tiles?
+ mUrl.clear();
}
- mLoaded = FALSE;
-// llinfos << "HTTP GET: " << mID << " Offset: " << offset << " Bytes: " << mRequestedSize << llendl;
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- LLCurl::getByteRange(url, offset, mRequestedSize,
- new HTTPGetResponder(mFetcher, mID)); // *TODO: use mWorkPriority
- return false; // not done
- }
+
+ if (! mHttpBufferArray || ! mHttpBufferArray->size())
+ {
+ // no data received.
+ if (mHttpBufferArray)
+ {
+ mHttpBufferArray->release();
+ mHttpBufferArray = NULL;
+ }
- if (mLoaded)
- {
- S32 cur_size = mFormattedImage->getDataSize();
- if (mRequestedSize < 0)
+ // abort.
+ setState(DONE);
+ LL_WARNS(LOG_TXT) << mID << " abort: no data received" << LL_ENDL;
+ releaseHttpSemaphore();
+ return true;
+ }
+
+ S32 append_size(mHttpBufferArray->size());
+ S32 total_size(cur_size + append_size);
+ S32 src_offset(0);
+ llassert_always(append_size == mRequestedSize);
+ if (mHttpReplyOffset && mHttpReplyOffset != cur_size)
{
-// llwarns << "http get failed for: " << mID << llendl;
- if (cur_size == 0)
+ // In case of a partial response, our offset may
+ // not be trivially contiguous with the data we have.
+ // Get back into alignment.
+ if (mHttpReplyOffset > cur_size)
{
- resetFormattedData();
- return true; // failed
+ LL_WARNS(LOG_TXT) << "Partial HTTP response produces break in image data for texture "
+ << mID << ". Aborting load." << LL_ENDL;
+ setState(DONE);
+ releaseHttpSemaphore();
+ return true;
}
- else
+ src_offset = cur_size - mHttpReplyOffset;
+ append_size -= src_offset;
+ total_size -= src_offset;
+ mRequestedSize -= src_offset; // Make requested values reflect useful part
+ mRequestedOffset += src_offset;
+ }
+
+ if (mFormattedImage.isNull())
+ {
+ // For now, create formatted image based on extension
+ std::string extension = gDirUtilp->getExtension(mUrl);
+ mFormattedImage = LLImageFormatted::createFromType(LLImageBase::getCodecFromExtension(extension));
+ if (mFormattedImage.isNull())
{
- mState = DECODE_IMAGE;
- return false; // use what we have
+ mFormattedImage = new LLImageJ2C; // default
}
}
- llassert_always(mBufferSize == cur_size + mRequestedSize);
- if (mHaveAllData)
+
+ if (mHaveAllData) //the image file is fully loaded.
{
- mFileSize = mBufferSize;
+ mFileSize = total_size;
}
- U8* buffer = new U8[mBufferSize];
+ else //the file size is unknown.
+ {
+ mFileSize = total_size + 1 ; //flag the file is not fully loaded.
+ }
+
+ U8 * buffer = (U8 *) ALLOCATE_MEM(LLImageBase::getPrivatePool(), total_size);
if (cur_size > 0)
{
memcpy(buffer, mFormattedImage->getData(), cur_size);
}
- memcpy(buffer + cur_size, mBuffer, mRequestedSize); // append
+ mHttpBufferArray->read(src_offset, (char *) buffer + cur_size, append_size);
+
// NOTE: setData releases current data and owns new data (buffer)
- mFormattedImage->setData(buffer, mBufferSize);
- // delete temp data
- delete[] mBuffer; // Note: not 'buffer' (assigned in setData())
- mBuffer = NULL;
- mBufferSize = 0;
+ mFormattedImage->setData(buffer, total_size);
+
+ // Done with buffer array
+ mHttpBufferArray->release();
+ mHttpBufferArray = NULL;
+ mHttpReplySize = 0;
+ mHttpReplyOffset = 0;
+
mLoadedDiscard = mRequestedDiscard;
+ if (mLoadedDiscard < 0)
+ {
+ LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard
+ << ", should be >=0" << LL_ENDL;
+ }
+ setState(DECODE_IMAGE);
+ if (mWriteToCacheState != NOT_WRITE)
+ {
+ mWriteToCacheState = SHOULD_WRITE ;
+ }
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- mState = DECODE_IMAGE;
+ releaseHttpSemaphore();
return false;
}
+ else
+ {
+ // *HISTORY: There was a texture timeout test here originally that
+ // would cancel a request that was over 120 seconds old. That's
+ // probably not a good idea. Particularly rich regions can take
+ // an enormous amount of time to load textures. We'll revisit the
+ // various possible timeout components (total request time, connection
+ // time, I/O time, with and without retries, etc.) in the future.
- // NOTE: Priority gets updated when the http get completes (in callbackHTTPGet())
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- return false;
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ return false;
+ }
}
-#endif
if (mState == DECODE_IMAGE)
{
- llassert_always(mFormattedImage->getDataSize() > 0);
+ static LLCachedControl<bool> textures_decode_disabled(gSavedSettings, "TextureDecodeDisabled", false);
+
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
+ if (textures_decode_disabled)
+ {
+ // for debug use, don't decode
+ setState(DONE);
+ return true;
+ }
+
+ if (mDesiredDiscard < 0)
+ {
+ // We aborted, don't decode
+ setState(DONE);
+ LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: desired discard " << mDesiredDiscard << "<0" << LL_ENDL;
+ return true;
+ }
+
+ if (mFormattedImage->getDataSize() <= 0)
+ {
+ LL_WARNS(LOG_TXT) << "Decode entered with invalid mFormattedImage. ID = " << mID << LL_ENDL;
+
+ //abort, don't decode
+ setState(DONE);
+ LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: (mFormattedImage->getDataSize() <= 0)" << LL_ENDL;
+ return true;
+ }
+ if (mLoadedDiscard < 0)
+ {
+ LL_WARNS(LOG_TXT) << "Decode entered with invalid mLoadedDiscard. ID = " << mID << LL_ENDL;
+
+ //abort, don't decode
+ setState(DONE);
+ LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: mLoadedDiscard < 0" << LL_ENDL;
+ return true;
+ }
+
mRawImage = NULL;
mAuxImage = NULL;
- llassert_always(mImageWorker == NULL);
llassert_always(mFormattedImage.notNull());
S32 discard = mHaveAllData ? 0 : mLoadedDiscard;
U32 image_priority = LLWorkerThread::PRIORITY_NORMAL | mWorkPriority;
mDecoded = FALSE;
- mState = DECODE_IMAGE_UPDATE;
- mImageWorker = new LLImageWorker(mFormattedImage, image_priority, discard, new DecodeResponder(mFetcher, mID, this));
- // fall though (need to call requestDecodedData() to start work)
+ setState(DECODE_IMAGE_UPDATE);
+ LL_DEBUGS(LOG_TXT) << mID << ": Decoding. Bytes: " << mFormattedImage->getDataSize() << " Discard: " << discard
+ << " All Data: " << mHaveAllData << LL_ENDL;
+ mDecodeHandle = mFetcher->mImageDecodeThread->decodeImage(mFormattedImage, image_priority, discard, mNeedsAux,
+ new DecodeResponder(mFetcher, mID, this));
+ // fall though
}
if (mState == DECODE_IMAGE_UPDATE)
{
- if (decodeImage())
+ if (mDecoded)
{
+ if(mFetcher->getFetchDebugger() && !mInLocalCache)
+ {
+ mFetcher->getFetchDebugger()->addHistoryEntry(this);
+ }
+
if (mDecodedDiscard < 0)
{
+ LL_DEBUGS(LOG_TXT) << mID << ": Failed to Decode." << LL_ENDL;
if (mCachedSize > 0 && !mInLocalCache && mRetryAttempt == 0)
{
// Cache file should be deleted, try again
-// llwarns << mID << ": Decode of cached file failed (removed), retrying" << llendl;
+ LL_WARNS(LOG_TXT) << mID << ": Decode of cached file failed (removed), retrying" << LL_ENDL;
+ llassert_always(mDecodeHandle == 0);
mFormattedImage = NULL;
++mRetryAttempt;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- mState = INIT;
+ setState(INIT);
return false;
}
else
{
-// llwarns << "UNABLE TO LOAD TEXTURE: " << mID << " RETRIES: " << mRetryAttempt << llendl;
- mState = DONE; // failed
+// LL_WARNS(LOG_TXT) << "UNABLE TO LOAD TEXTURE: " << mID << " RETRIES: " << mRetryAttempt << LL_ENDL;
+ setState(DONE); // failed
}
}
else
{
+ llassert_always(mRawImage.notNull());
+ LL_DEBUGS(LOG_TXT) << mID << ": Decoded. Discard: " << mDecodedDiscard
+ << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- mState = WRITE_TO_CACHE;
+ setState(WRITE_TO_CACHE);
}
// fall through
}
@@ -918,22 +1902,35 @@ bool LLTextureFetchWorker::doWork(S32 param)
if (mState == WRITE_TO_CACHE)
{
- if (mInLocalCache || !mFileSize || mSentRequest == UNSENT)
+ if (mWriteToCacheState != SHOULD_WRITE || mFormattedImage.isNull())
{
- // If we're in a local cache or we didn't actually receive any new data, skip
- mState = DONE;
+ // If we're in a local cache or we didn't actually receive any new data,
+ // or we failed to load anything, skip
+ setState(DONE);
return false;
}
S32 datasize = mFormattedImage->getDataSize();
+ if(mFileSize < datasize)//This could happen when http fetching and sim fetching mixed.
+ {
+ if(mHaveAllData)
+ {
+ mFileSize = datasize ;
+ }
+ else
+ {
+ mFileSize = datasize + 1 ; //flag not fully loaded.
+ }
+ }
llassert_always(datasize);
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
U32 cache_priority = mWorkPriority;
mWritten = FALSE;
- mState = WAIT_ON_WRITE;
+ setState(WAIT_ON_WRITE);
+ ++mCacheWriteCount;
CacheWriteResponder* responder = new CacheWriteResponder(mFetcher, mID);
mCacheWriteHandle = mFetcher->mTextureCache->writeToCache(mID, cache_priority,
mFormattedImage->getData(), datasize,
- mFileSize, responder);
+ mFileSize, mRawImage, mDecodedDiscard, responder);
// fall through
}
@@ -941,7 +1938,7 @@ bool LLTextureFetchWorker::doWork(S32 param)
{
if (writeToCacheComplete())
{
- mState = DONE;
+ setState(DONE);
// fall through
}
else
@@ -962,7 +1959,10 @@ bool LLTextureFetchWorker::doWork(S32 param)
if (mDecodedDiscard >= 0 && mDesiredDiscard < mDecodedDiscard)
{
// More data was requested, return to INIT
- mState = INIT;
+ setState(INIT);
+ LL_DEBUGS(LOG_TXT) << mID << " more data requested, returning to INIT: "
+ << " mDecodedDiscard " << mDecodedDiscard << ">= 0 && mDesiredDiscard " << mDesiredDiscard
+ << "<" << " mDecodedDiscard " << mDecodedDiscard << LL_ENDL;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
return false;
}
@@ -974,21 +1974,126 @@ bool LLTextureFetchWorker::doWork(S32 param)
}
return false;
-}
+} // -Mw
+
+// Threads: Ttf
+// virtual
+void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response)
+{
+ static LLCachedControl<bool> log_to_viewer_log(gSavedSettings, "LogTextureDownloadsToViewerLog", false);
+ static LLCachedControl<bool> log_to_sim(gSavedSettings, "LogTextureDownloadsToSimulator", false);
+ static LLCachedControl<bool> log_texture_traffic(gSavedSettings, "LogTextureNetworkTraffic", false) ;
+
+ LLMutexLock lock(&mWorkMutex); // +Mw
+
+ mHttpActive = false;
+
+ if (log_to_viewer_log || log_to_sim)
+ {
+ mFetcher->mTextureInfo.setRequestStartTime(mID, mMetricsStartTime.value());
+ mFetcher->mTextureInfo.setRequestType(mID, LLTextureInfoDetails::REQUEST_TYPE_HTTP);
+ mFetcher->mTextureInfo.setRequestSize(mID, mRequestedSize);
+ mFetcher->mTextureInfo.setRequestOffset(mID, mRequestedOffset);
+ mFetcher->mTextureInfo.setRequestCompleteTimeAndLog(mID, LLTimer::getTotalTime());
+ }
-// Called from MAIN thread
+ static LLCachedControl<F32> fake_failure_rate(gSavedSettings, "TextureFetchFakeFailureRate", 0.0f);
+ F32 rand_val = ll_frand();
+ F32 rate = fake_failure_rate;
+ if (mFTType == FTT_SERVER_BAKE && (fake_failure_rate > 0.0) && (rand_val < fake_failure_rate))
+ {
+ LL_WARNS(LOG_TXT) << mID << " for debugging, setting fake failure status for texture " << mID
+ << " (rand was " << rand_val << "/" << rate << ")" << LL_ENDL;
+ response->setStatus(LLCore::HttpStatus(503));
+ }
+ bool success = true;
+ bool partial = false;
+ LLCore::HttpStatus status(response->getStatus());
+ if (!status && (mFTType == FTT_SERVER_BAKE))
+ {
+ LL_INFOS(LOG_TXT) << mID << " state " << e_state_name[mState] << LL_ENDL;
+ mFetchRetryPolicy.onFailure(response);
+ F32 retry_after;
+ if (mFetchRetryPolicy.shouldRetry(retry_after))
+ {
+ LL_INFOS(LOG_TXT) << mID << " will retry after " << retry_after << " seconds, resetting state to LOAD_FROM_NETWORK" << LL_ENDL;
+ mFetcher->removeFromHTTPQueue(mID, S32Bytes(0));
+ std::string reason(status.toString());
+ setGetStatus(status, reason);
+ releaseHttpSemaphore();
+ setState(LOAD_FROM_NETWORK);
+ return;
+ }
+ else
+ {
+ LL_INFOS(LOG_TXT) << mID << " will not retry" << LL_ENDL;
+ }
+ }
+ else
+ {
+ mFetchRetryPolicy.onSuccess();
+ }
+
+ std::string reason(status.toString());
+ setGetStatus(status, reason);
+ LL_DEBUGS(LOG_TXT) << "HTTP COMPLETE: " << mID
+ << " status: " << status.toTerseString()
+ << " '" << reason << "'"
+ << LL_ENDL;
+
+ if (! status)
+ {
+ success = false;
+ if (mFTType != FTT_MAP_TILE) // missing map tiles are normal, don't complain about them.
+ {
+ LL_WARNS(LOG_TXT) << "CURL GET FAILED, status: " << status.toTerseString()
+ << " reason: " << reason << LL_ENDL;
+ }
+ }
+ else
+ {
+ // A warning about partial (HTTP 206) data. Some grid services
+ // do *not* return a 'Content-Range' header in the response to
+ // Range requests with a 206 status. We're forced to assume
+ // we get what we asked for in these cases until we can fix
+ // the services.
+ static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT);
+
+ partial = (par_status == status);
+ }
+
+ S32BytesImplicit data_size = callbackHttpGet(response, partial, success);
+
+ if (log_texture_traffic && data_size > 0)
+ {
+ LLViewerTexture* tex = LLViewerTextureManager::findTexture(mID);
+ if (tex)
+ {
+ gTotalTextureBytesPerBoostLevel[tex->getBoostLevel()] += data_size ;
+ }
+ }
+
+ mFetcher->removeFromHTTPQueue(mID, data_size);
+
+ recordTextureDone(true);
+} // -Mw
+
+
+// Threads: Tmain
void LLTextureFetchWorker::endWork(S32 param, bool aborted)
{
- if (mImageWorker)
+ if (mDecodeHandle != 0)
{
- mImageWorker->scheduleDelete();
- mImageWorker = NULL;
+ mFetcher->mImageDecodeThread->abortRequest(mDecodeHandle, false);
+ mDecodeHandle = 0;
}
mFormattedImage = NULL;
}
//////////////////////////////////////////////////////////////////////////////
+// Threads: Ttf
+
// virtual
void LLTextureFetchWorker::finishWork(S32 param, bool completed)
{
@@ -1005,10 +2110,37 @@ void LLTextureFetchWorker::finishWork(S32 param, bool completed)
}
}
+// LLQueuedThread's update() method is asking if it's okay to
+// delete this worker. You'll notice we're not locking in here
+// which is a slight concern. Caller is expected to have made
+// this request 'quiet' by whatever means...
+//
+// Threads: Tmain
+
// virtual
bool LLTextureFetchWorker::deleteOK()
{
bool delete_ok = true;
+
+ if (mHttpActive)
+ {
+ // HTTP library has a pointer to this worker
+ // and will dereference it to do notification.
+ delete_ok = false;
+ }
+
+ if (WAIT_HTTP_RESOURCE2 == mState)
+ {
+ if (mFetcher->isHttpWaiter(mID))
+ {
+ // Don't delete the worker out from under the releaseHttpWaiters()
+ // method. Keep the pointers valid, clean up after that method
+ // has recognized the cancelation and removed the UUID from the
+ // waiter list.
+ delete_ok = false;
+ }
+ }
+
// Allow any pending reads or writes to complete
if (mCacheReadHandle != LLTextureCache::nullHandle())
{
@@ -1035,8 +2167,7 @@ bool LLTextureFetchWorker::deleteOK()
if ((haveWork() &&
// not ok to delete from these states
- ((mState >= LOAD_FROM_HTTP_GET_URL && mState <= LOAD_FROM_HTTP_GET_DATA) ||
- (mState >= WRITE_TO_CACHE && mState <= WAIT_ON_WRITE))))
+ ((mState >= WRITE_TO_CACHE && mState <= WAIT_ON_WRITE))))
{
delete_ok = false;
}
@@ -1044,7 +2175,7 @@ bool LLTextureFetchWorker::deleteOK()
return delete_ok;
}
-
+// Threads: Ttf
void LLTextureFetchWorker::removeFromCache()
{
if (!mInLocalCache)
@@ -1056,11 +2187,14 @@ void LLTextureFetchWorker::removeFromCache()
//////////////////////////////////////////////////////////////////////////////
+// Threads: Ttf
+// Locks: Mw
bool LLTextureFetchWorker::processSimulatorPackets()
{
if (mFormattedImage.isNull() || mRequestedSize < 0)
{
// not sure how we got here, but not a valid state, abort!
+ llassert_always(mDecodeHandle == 0);
mFormattedImage = NULL;
return true;
}
@@ -1074,6 +2208,12 @@ bool LLTextureFetchWorker::processSimulatorPackets()
buffer_size += mPackets[i]->mSize;
}
bool have_all_data = mLastPacket >= mTotalPackets-1;
+ if (mRequestedSize <= 0)
+ {
+ // We received a packed but haven't requested anything yet (edge case)
+ // Return true (we're "done") since we didn't request anything
+ return true;
+ }
if (buffer_size >= mRequestedSize || have_all_data)
{
/// We have enough (or all) data
@@ -1085,7 +2225,7 @@ bool LLTextureFetchWorker::processSimulatorPackets()
if (buffer_size > cur_size)
{
/// We have new data
- U8* buffer = new U8[buffer_size];
+ U8* buffer = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), buffer_size);
S32 offset = 0;
if (cur_size > 0 && mFirstPacket > 0)
{
@@ -1109,62 +2249,90 @@ bool LLTextureFetchWorker::processSimulatorPackets()
//////////////////////////////////////////////////////////////////////////////
-void LLTextureFetchWorker::callbackURLReceived(const LLSD& data, bool success)
+// Threads: Ttf
+// Locks: Mw
+S32 LLTextureFetchWorker::callbackHttpGet(LLCore::HttpResponse * response,
+ bool partial, bool success)
{
-#if 0
- LLMutexLock lock(&mWorkMutex);
- if (!mSentRequest || mState != LOAD_FROM_HTTP_GET_URL)
- {
- llwarns << "callbackURLReceived for unrequested fetch worker, req="
- << mSentRequest << " state= " << mState << llendl;
- return;
- }
- if (success)
- {
- mURL = data.asString();
- }
- mLoaded = TRUE;
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
-#endif
-}
+ S32 data_size = 0 ;
-//////////////////////////////////////////////////////////////////////////////
-
-void LLTextureFetchWorker::callbackHttpGet(U8* data, S32 data_size, bool last_block)
-{
-#if 0
- LLMutexLock lock(&mWorkMutex);
- if (!mSentRequest || mState != LOAD_FROM_HTTP_GET_DATA)
+ if (mState != WAIT_HTTP_REQ)
{
- llwarns << "callbackHttpGet for unrequested fetch worker, req="
- << mSentRequest << " state= " << mState << llendl;
- return;
+ LL_WARNS(LOG_TXT) << "callbackHttpGet for unrequested fetch worker: " << mID
+ << " req=" << mSentRequest << " state= " << mState << LL_ENDL;
+ return data_size;
}
-// llinfos << "HTTP RECEIVED: " << mID.asString() << " Bytes: " << data_size << llendl;
if (mLoaded)
{
- llwarns << "Duplicate callback for " << mID.asString() << llendl;
- return; // ignore duplicate callback
+ LL_WARNS(LOG_TXT) << "Duplicate callback for " << mID.asString() << LL_ENDL;
+ return data_size ; // ignore duplicate callback
}
- if (data_size >= 0)
+ if (success)
{
+ // get length of stream:
+ LLCore::BufferArray * body(response->getBody());
+ data_size = body ? body->size() : 0;
+
+ LL_DEBUGS(LOG_TXT) << "HTTP RECEIVED: " << mID.asString() << " Bytes: " << data_size << LL_ENDL;
if (data_size > 0)
{
- mBuffer = new U8[data_size];
- // *TODO: set the formatted image data here
- memcpy(mBuffer, data, data_size);
- mBufferSize += data_size;
- if (data_size < mRequestedSize || last_block == true)
+ LLViewerStatsRecorder::instance().textureFetch(data_size);
+ // *TODO: set the formatted image data here directly to avoid the copy
+
+ // Hold on to body for later copy
+ llassert_always(NULL == mHttpBufferArray);
+ body->addRef();
+ mHttpBufferArray = body;
+
+ if (partial)
+ {
+ unsigned int offset(0), length(0), full_length(0);
+ response->getRange(&offset, &length, &full_length);
+ if (! offset && ! length)
+ {
+ // This is the case where we receive a 206 status but
+ // there wasn't a useful Content-Range header in the response.
+ // This could be because it was badly formatted but is more
+ // likely due to capabilities services which scrub headers
+ // from responses. Assume we got what we asked for...
+ mHttpReplySize = data_size;
+ mHttpReplyOffset = mRequestedOffset;
+ }
+ else
+ {
+ mHttpReplySize = length;
+ mHttpReplyOffset = offset;
+ }
+ }
+
+ if (! partial)
+ {
+ // Response indicates this is the entire asset regardless
+ // of our asking for a byte range. Mark it so and drop
+ // any partial data we might have so that the current
+ // response body becomes the entire dataset.
+ if (data_size <= mRequestedOffset)
+ {
+ LL_WARNS(LOG_TXT) << "Fetched entire texture " << mID
+ << " when it was expected to be marked complete. mImageSize: "
+ << mFileSize << " datasize: " << mFormattedImage->getDataSize()
+ << LL_ENDL;
+ }
+ mHaveAllData = TRUE;
+ llassert_always(mDecodeHandle == 0);
+ mFormattedImage = NULL; // discard any previous data we had
+ }
+ else if (data_size < mRequestedSize)
{
mHaveAllData = TRUE;
}
else if (data_size > mRequestedSize)
{
- // *TODO: This will happen until we fix LLCurl::getByteRange()
-// llinfos << "HUH?" << llendl;
+ // *TODO: This shouldn't be happening any more (REALLY don't expect this anymore)
+ LL_WARNS(LOG_TXT) << "data_size = " << data_size << " > requested: " << mRequestedSize << LL_ENDL;
mHaveAllData = TRUE;
- mFormattedImage->deleteData();
- mBufferSize = data_size;
+ llassert_always(mDecodeHandle == 0);
+ mFormattedImage = NULL; // discard any previous data we had
}
}
else
@@ -1179,25 +2347,29 @@ void LLTextureFetchWorker::callbackHttpGet(U8* data, S32 data_size, bool last_bl
{
mRequestedSize = -1; // error
}
+
mLoaded = TRUE;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
-#endif
+
+ LLViewerStatsRecorder::instance().log(0.2f);
+ return data_size ;
}
//////////////////////////////////////////////////////////////////////////////
+// Threads: Ttc
void LLTextureFetchWorker::callbackCacheRead(bool success, LLImageFormatted* image,
S32 imagesize, BOOL islocal)
{
- LLMutexLock lock(&mWorkMutex);
+ LLMutexLock lock(&mWorkMutex); // +Mw
if (mState != LOAD_FROM_TEXTURE_CACHE)
{
-// llwarns << "Read callback for " << mID << " with state = " << mState << llendl;
+// LL_WARNS(LOG_TXT) << "Read callback for " << mID << " with state = " << mState << LL_ENDL;
return;
}
if (success)
{
- llassert_always(imagesize > 0);
+ llassert_always(imagesize >= 0);
mFileSize = imagesize;
mFormattedImage = image;
mImageCodec = image->getCodec();
@@ -1209,85 +2381,64 @@ void LLTextureFetchWorker::callbackCacheRead(bool success, LLImageFormatted* ima
}
mLoaded = TRUE;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
-}
+} // -Mw
+// Threads: Ttc
void LLTextureFetchWorker::callbackCacheWrite(bool success)
{
- LLMutexLock lock(&mWorkMutex);
+ LLMutexLock lock(&mWorkMutex); // +Mw
if (mState != WAIT_ON_WRITE)
{
-// llwarns << "Write callback for " << mID << " with state = " << mState << llendl;
+// LL_WARNS(LOG_TXT) << "Write callback for " << mID << " with state = " << mState << LL_ENDL;
return;
}
mWritten = TRUE;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
-}
+} // -Mw
//////////////////////////////////////////////////////////////////////////////
-void LLTextureFetchWorker::callbackDecoded(bool success)
+// Threads: Tid
+void LLTextureFetchWorker::callbackDecoded(bool success, LLImageRaw* raw, LLImageRaw* aux)
{
- if (mState != DECODE_IMAGE_UPDATE)
+ LLMutexLock lock(&mWorkMutex); // +Mw
+ if (mDecodeHandle == 0)
{
-// llwarns << "Decode callback for " << mID << " with state = " << mState << llendl;
- return;
+ return; // aborted, ignore
}
-// llinfos << mID << " : DECODE COMPLETE " << llendl;
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-bool LLTextureFetchWorker::decodeImage()
-{
- if(!mImageWorker)
+ if (mState != DECODE_IMAGE_UPDATE)
{
- //LLTextureFetchWorker is aborted, skip image decoding.
- return true ;
+// LL_WARNS(LOG_TXT) << "Decode callback for " << mID << " with state = " << mState << LL_ENDL;
+ mDecodeHandle = 0;
+ return;
}
-
- bool res = true;
- if (mRawImage.isNull())
+ llassert_always(mFormattedImage.notNull());
+
+ mDecodeHandle = 0;
+ if (success)
{
- res = false;
- if (mImageWorker->requestDecodedData(mRawImage, -1))
- {
- res = true;
-// llinfos << mID << " : BASE DECODE FINISHED" << llendl;
- }
+ llassert_always(raw);
+ mRawImage = raw;
+ mAuxImage = aux;
+ mDecodedDiscard = mFormattedImage->getDiscardLevel();
+ LL_DEBUGS(LOG_TXT) << mID << ": Decode Finished. Discard: " << mDecodedDiscard
+ << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL;
}
- if (res &&
- (mRawImage.notNull() && mRawImage->getDataSize() > 0) &&
- (mNeedsAux && mAuxImage.isNull()))
+ else
{
- res = false;
- if (mImageWorker->requestDecodedAuxData(mAuxImage, 4, -1))
- {
- res = true;
-// llinfos << mID << " : AUX DECODE FINISHED" << llendl;
- }
+ LL_WARNS(LOG_TXT) << "DECODE FAILED: " << mID << " Discard: " << (S32)mFormattedImage->getDiscardLevel() << LL_ENDL;
+ removeFromCache();
+ mDecodedDiscard = -1; // Redundant, here for clarity and paranoia
}
- if (res)
- {
- if ((mRawImage.notNull() && mRawImage->getDataSize() > 0) &&
- (!mNeedsAux || (mAuxImage.notNull() && mAuxImage->getDataSize() > 0)))
- {
- mDecodedDiscard = mFormattedImage->getDiscardLevel();
-// llinfos << mID << " : DECODE FINISHED. DISCARD: " << mDecodedDiscard << llendl;
- }
- else
- {
-// llwarns << "DECODE FAILED: " << mID << " Discard: " << (S32)mFormattedImage->getDiscardLevel() << llendl;
- removeFromCache();
- }
- mImageWorker->scheduleDelete();
- mImageWorker = NULL;
- }
- return res;
-}
+ mDecoded = TRUE;
+// LL_INFOS(LOG_TXT) << mID << " : DECODE COMPLETE " << LL_ENDL;
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ mCacheReadTime = mCacheReadTimer.getElapsedTimeF32();
+} // -Mw
//////////////////////////////////////////////////////////////////////////////
+// Threads: Ttf
bool LLTextureFetchWorker::writeToCacheComplete()
{
// Complete write to cache
@@ -1310,50 +2461,172 @@ bool LLTextureFetchWorker::writeToCacheComplete()
}
+// Threads: Ttf
+void LLTextureFetchWorker::recordTextureStart(bool is_http)
+{
+ if (! mMetricsStartTime.value())
+ {
+ mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
+ }
+ LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE,
+ is_http,
+ LLImageBase::TYPE_AVATAR_BAKE == mType);
+}
+
+
+// Threads: Ttf
+void LLTextureFetchWorker::recordTextureDone(bool is_http)
+{
+ if (mMetricsStartTime.value())
+ {
+ LLViewerAssetStatsFF::record_response(LLViewerAssetType::AT_TEXTURE,
+ is_http,
+ LLImageBase::TYPE_AVATAR_BAKE == mType,
+ LLViewerAssetStatsFF::get_timestamp() - mMetricsStartTime);
+ mMetricsStartTime = (U32Seconds)0;
+ }
+ LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE,
+ is_http,
+ LLImageBase::TYPE_AVATAR_BAKE == mType);
+}
+
+
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// public
-LLTextureFetch::LLTextureFetch(LLTextureCache* cache, bool threaded)
- : LLWorkerThread("TextureFetch", threaded),
+LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded, bool qa_mode)
+ : LLWorkerThread("TextureFetch", threaded, true),
mDebugCount(0),
mDebugPause(FALSE),
mPacketCount(0),
mBadPacketCount(0),
mQueueMutex(getAPRPool()),
- mTextureCache(cache)
+ mNetworkQueueMutex(getAPRPool()),
+ mTextureCache(cache),
+ mImageDecodeThread(imagedecodethread),
+ mTextureBandwidth(0),
+ mHTTPTextureBits(0),
+ mTotalHTTPRequests(0),
+ mQAMode(qa_mode),
+ mHttpRequest(NULL),
+ mHttpOptions(NULL),
+ mHttpOptionsWithHeaders(NULL),
+ mHttpHeaders(NULL),
+ mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID),
+ mHttpMetricsHeaders(NULL),
+ mHttpMetricsPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID),
+ mTotalCacheReadCount(0U),
+ mTotalCacheWriteCount(0U),
+ mTotalResourceWaitCount(0U),
+ mFetchDebugger(NULL),
+ mFetchSource(LLTextureFetch::FROM_ALL),
+ mOriginFetchSource(LLTextureFetch::FROM_ALL),
+ mFetcherLocked(FALSE)
{
+ mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS");
+ mTextureInfo.setUpLogging(gSavedSettings.getBOOL("LogTextureDownloadsToViewerLog"), gSavedSettings.getBOOL("LogTextureDownloadsToSimulator"), U32Bytes(gSavedSettings.getU32("TextureLoggingThreshold")));
+
+ LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp());
+ mHttpRequest = new LLCore::HttpRequest;
+ mHttpOptions = new LLCore::HttpOptions;
+ mHttpOptionsWithHeaders = new LLCore::HttpOptions;
+ mHttpOptionsWithHeaders->setWantHeaders(true);
+ mHttpHeaders = new LLCore::HttpHeaders;
+ mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_IMAGE_X_J2C);
+ mHttpPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_TEXTURE);
+ mHttpMetricsHeaders = new LLCore::HttpHeaders;
+ mHttpMetricsHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML);
+ mHttpMetricsPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_REPORTING);
+ mHttpHighWater = HTTP_NONPIPE_REQUESTS_HIGH_WATER;
+ mHttpLowWater = HTTP_NONPIPE_REQUESTS_LOW_WATER;
+ mHttpSemaphore = 0;
+
+ // Conditionally construct debugger object after 'this' is
+ // fully initialized.
+ LLTextureFetchDebugger::sDebuggerEnabled = gSavedSettings.getBOOL("TextureFetchDebuggerEnabled");
+ if(LLTextureFetchDebugger::isEnabled())
+ {
+ mFetchDebugger = new LLTextureFetchDebugger(this, cache, imagedecodethread) ;
+ mFetchSource = (e_tex_source)gSavedSettings.getS32("TextureFetchSource");
+ if(mFetchSource < 0 && mFetchSource >= INVALID_SOURCE)
+ {
+ mFetchSource = LLTextureFetch::FROM_ALL;
+ gSavedSettings.setS32("TextureFetchSource", 0);
+ }
+ mOriginFetchSource = mFetchSource;
+ }
}
LLTextureFetch::~LLTextureFetch()
{
- // ~LLQueuedThread() called here
-}
+ clearDeleteList();
-bool LLTextureFetch::createRequest(const LLUUID& id, const LLHost& host, F32 priority,
- S32 w, S32 h, S32 c, S32 desired_discard, bool needs_aux)
-{
- return createRequest(LLStringUtil::null, id, host, priority, w, h, c, desired_discard, needs_aux);
+ while (! mCommands.empty())
+ {
+ TFRequest * req(mCommands.front());
+ mCommands.erase(mCommands.begin());
+ delete req;
+ }
+
+ if (mHttpOptions)
+ {
+ mHttpOptions->release();
+ mHttpOptions = NULL;
+ }
+
+ if (mHttpOptionsWithHeaders)
+ {
+ mHttpOptionsWithHeaders->release();
+ mHttpOptionsWithHeaders = NULL;
+ }
+
+ if (mHttpHeaders)
+ {
+ mHttpHeaders->release();
+ mHttpHeaders = NULL;
+ }
+
+ if (mHttpMetricsHeaders)
+ {
+ mHttpMetricsHeaders->release();
+ mHttpMetricsHeaders = NULL;
+ }
+
+ mHttpWaitResource.clear();
+
+ delete mHttpRequest;
+ mHttpRequest = NULL;
+
+ delete mFetchDebugger;
+ mFetchDebugger = NULL;
+
+ // ~LLQueuedThread() called here
}
-bool LLTextureFetch::createRequest(const std::string& filename, const LLUUID& id, const LLHost& host, F32 priority,
- S32 w, S32 h, S32 c, S32 desired_discard, bool needs_aux)
+bool LLTextureFetch::createRequest(FTType f_type, const std::string& url, const LLUUID& id, const LLHost& host, F32 priority,
+ S32 w, S32 h, S32 c, S32 desired_discard, bool needs_aux, bool can_use_http)
{
+ if(mFetcherLocked)
+ {
+ return false;
+ }
if (mDebugPause)
{
return false;
}
-
- LLTextureFetchWorker* worker = NULL;
- LLMutexLock lock(&mQueueMutex);
- map_t::iterator iter = mRequestMap.find(id);
- if (iter != mRequestMap.end())
+
+ if (f_type == FTT_SERVER_BAKE)
+ {
+ LL_DEBUGS("Avatar") << " requesting " << id << " " << w << "x" << h << " discard " << desired_discard << " type " << f_type << LL_ENDL;
+ }
+ LLTextureFetchWorker* worker = getWorker(id) ;
+ if (worker)
{
- worker = iter->second;
if (worker->mHost != host)
{
- llwarns << "LLTextureFetch::createRequest " << id << " called with multiple hosts: "
- << host << " != " << worker->mHost << llendl;
+ LL_WARNS(LOG_TXT) << "LLTextureFetch::createRequest " << id << " called with multiple hosts: "
+ << host << " != " << worker->mHost << LL_ENDL;
removeRequest(worker, true);
worker = NULL;
return false;
@@ -1361,7 +2634,27 @@ bool LLTextureFetch::createRequest(const std::string& filename, const LLUUID& id
}
S32 desired_size;
- if (desired_discard == 0)
+ std::string exten = gDirUtilp->getExtension(url);
+ //if (f_type == FTT_SERVER_BAKE)
+ if ((f_type == FTT_SERVER_BAKE) && !url.empty() && !exten.empty() && (LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C))
+ {
+ // SH-4030: This case should be redundant with the following one, just
+ // breaking it out here to clarify that it's intended behavior.
+ llassert(!url.empty() && (!exten.empty() && LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C));
+
+ // Do full requests for baked textures to reduce interim blurring.
+ LL_DEBUGS(LOG_TXT) << "full request for " << id << " texture is FTT_SERVER_BAKE" << LL_ENDL;
+ desired_size = MAX_IMAGE_DATA_SIZE;
+ desired_discard = 0;
+ }
+ else if (!url.empty() && (!exten.empty() && LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C))
+ {
+ LL_DEBUGS(LOG_TXT) << "full request for " << id << " exten is not J2C: " << exten << LL_ENDL;
+ // Only do partial requests for J2C at the moment
+ desired_size = MAX_IMAGE_DATA_SIZE;
+ desired_discard = 0;
+ }
+ else if (desired_discard == 0)
{
// if we want the entire image, and we know its size, then get it all
// (calcDataSizeJ2C() below makes assumptions about how the image
@@ -1378,7 +2671,9 @@ bool LLTextureFetch::createRequest(const std::string& filename, const LLUUID& id
}
else
{
- desired_size = FIRST_PACKET_SIZE;
+ // If the requester knows nothing about the file, we fetch the smallest
+ // amount of data at the lowest resolution (highest discard level) possible.
+ desired_size = TEXTURE_CACHE_ENTRY_SIZE;
desired_discard = MAX_DISCARD_LEVEL;
}
@@ -1389,52 +2684,58 @@ bool LLTextureFetch::createRequest(const std::string& filename, const LLUUID& id
{
return false; // need to wait for previous aborted request to complete
}
- worker->lockWorkData();
+ worker->lockWorkMutex(); // +Mw
+ worker->mActiveCount++;
+ worker->mNeedsAux = needs_aux;
worker->setImagePriority(priority);
worker->setDesiredDiscard(desired_discard, desired_size);
- worker->unlockWorkData();
+ worker->setCanUseHTTP(can_use_http);
+
+ //MAINT-4184 url is always empty. Do not set with it.
+
if (!worker->haveWork())
{
- worker->mState = LLTextureFetchWorker::INIT;
+ worker->setState(LLTextureFetchWorker::INIT);
+ worker->unlockWorkMutex(); // -Mw
+
worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
}
- }
- else
- {
- if (filename.empty())
- {
- // do remote fetch
- worker = new LLTextureFetchWorker(this, id, host, priority, desired_discard, desired_size);
- }
else
{
- // do local file fetch
- worker = new LLTextureFetchLocalFileWorker(this, filename, id, host, priority, desired_discard, desired_size);
+ worker->unlockWorkMutex(); // -Mw
}
+ }
+ else
+ {
+ worker = new LLTextureFetchWorker(this, f_type, url, id, host, priority, desired_discard, desired_size);
+ lockQueue(); // +Mfq
mRequestMap[id] = worker;
+ unlockQueue(); // -Mfq
+
+ worker->lockWorkMutex(); // +Mw
+ worker->mActiveCount++;
+ worker->mNeedsAux = needs_aux;
+ worker->setCanUseHTTP(can_use_http) ;
+ worker->unlockWorkMutex(); // -Mw
}
- worker->mActiveCount++;
- worker->mNeedsAux = needs_aux;
-// llinfos << "REQUESTED: " << id << " Discard: " << desired_discard << llendl;
+
+ LL_DEBUGS(LOG_TXT) << "REQUESTED: " << id << " f_type " << fttype_to_string(f_type)
+ << " Discard: " << desired_discard << " size " << desired_size << LL_ENDL;
return true;
}
-void LLTextureFetch::deleteRequest(const LLUUID& id, bool cancel)
-{
- LLMutexLock lock(&mQueueMutex);
- LLTextureFetchWorker* worker = getWorker(id);
- if (worker)
- {
- removeRequest(worker, cancel);
- }
-}
-// protected
+// Threads: T* (but Ttf in practice)
-// call lockQueue() first!
+// protected
void LLTextureFetch::addToNetworkQueue(LLTextureFetchWorker* worker)
{
- if (mRequestMap.find(worker->mID) != mRequestMap.end())
+ lockQueue(); // +Mfq
+ bool in_request_map = (mRequestMap.find(worker->mID) != mRequestMap.end()) ;
+ unlockQueue(); // -Mfq
+
+ LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq
+ if (in_request_map)
{
// only add to the queue if in the request map
// i.e. a delete has not been requested
@@ -1445,31 +2746,138 @@ void LLTextureFetch::addToNetworkQueue(LLTextureFetchWorker* worker)
{
iter1->second.erase(worker->mID);
}
-}
+} // -Mfnq
-// call lockQueue() first!
-void LLTextureFetch::removeFromNetworkQueue(LLTextureFetchWorker* worker)
+// Threads: T*
+void LLTextureFetch::removeFromNetworkQueue(LLTextureFetchWorker* worker, bool cancel)
+{
+ LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq
+ size_t erased = mNetworkQueue.erase(worker->mID);
+ if (cancel && erased > 0)
+ {
+ mCancelQueue[worker->mHost].insert(worker->mID);
+ }
+} // -Mfnq
+
+// Threads: T*
+//
+// protected
+void LLTextureFetch::addToHTTPQueue(const LLUUID& id)
+{
+ LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq
+ mHTTPTextureQueue.insert(id);
+ mTotalHTTPRequests++;
+} // -Mfnq
+
+// Threads: T*
+void LLTextureFetch::removeFromHTTPQueue(const LLUUID& id, S32Bytes received_size)
+{
+ LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq
+ mHTTPTextureQueue.erase(id);
+ mHTTPTextureBits += received_size; // Approximate - does not include header bits
+} // -Mfnq
+
+// NB: If you change deleteRequest() you should probably make
+// parallel changes in removeRequest(). They're functionally
+// identical with only argument variations.
+//
+// Threads: T*
+void LLTextureFetch::deleteRequest(const LLUUID& id, bool cancel)
{
- mNetworkQueue.erase(worker->mID);
+ lockQueue(); // +Mfq
+ LLTextureFetchWorker* worker = getWorkerAfterLock(id);
+ if (worker)
+ {
+ size_t erased_1 = mRequestMap.erase(worker->mID);
+ unlockQueue(); // -Mfq
+
+ llassert_always(erased_1 > 0) ;
+ removeFromNetworkQueue(worker, cancel);
+ llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ;
+
+ worker->scheduleDelete();
+ }
+ else
+ {
+ unlockQueue(); // -Mfq
+ }
}
-// call lockQueue() first!
+// NB: If you change removeRequest() you should probably make
+// parallel changes in deleteRequest(). They're functionally
+// identical with only argument variations.
+//
+// Threads: T*
void LLTextureFetch::removeRequest(LLTextureFetchWorker* worker, bool cancel)
{
- size_t erased_1 = mRequestMap.erase(worker->mID);
- llassert_always(erased_1 > 0) ;
- size_t erased = mNetworkQueue.erase(worker->mID);
- if (cancel && erased > 0)
+ if(!worker)
{
- mCancelQueue[worker->mHost].insert(worker->mID);
+ return;
}
+
+ lockQueue(); // +Mfq
+ size_t erased_1 = mRequestMap.erase(worker->mID);
+ unlockQueue(); // -Mfq
+
+ llassert_always(erased_1 > 0) ;
+ removeFromNetworkQueue(worker, cancel);
llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ;
worker->scheduleDelete();
}
+void LLTextureFetch::deleteAllRequests()
+{
+ while(1)
+ {
+ lockQueue();
+ if(mRequestMap.empty())
+ {
+ unlockQueue() ;
+ break;
+ }
+
+ LLTextureFetchWorker* worker = mRequestMap.begin()->second;
+ unlockQueue() ;
+
+ removeRequest(worker, true);
+ }
+}
+
+// Threads: T*
+S32 LLTextureFetch::getNumRequests()
+{
+ lockQueue(); // +Mfq
+ S32 size = (S32)mRequestMap.size();
+ unlockQueue(); // -Mfq
+
+ return size;
+}
+
+// Threads: T*
+S32 LLTextureFetch::getNumHTTPRequests()
+{
+ mNetworkQueueMutex.lock(); // +Mfq
+ S32 size = (S32)mHTTPTextureQueue.size();
+ mNetworkQueueMutex.unlock(); // -Mfq
+
+ return size;
+}
+
+// Threads: T*
+U32 LLTextureFetch::getTotalNumHTTPRequests()
+{
+ mNetworkQueueMutex.lock(); // +Mfq
+ U32 size = mTotalHTTPRequests;
+ mNetworkQueueMutex.unlock(); // -Mfq
+
+ return size;
+}
+
// call lockQueue() first!
-LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id)
+// Threads: T*
+// Locks: Mfq
+LLTextureFetchWorker* LLTextureFetch::getWorkerAfterLock(const LLUUID& id)
{
LLTextureFetchWorker* res = NULL;
map_t::iterator iter = mRequestMap.find(id);
@@ -1480,12 +2888,21 @@ LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id)
return res;
}
+// Threads: T*
+LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id)
+{
+ LLMutexLock lock(&mQueueMutex); // +Mfq
+
+ return getWorkerAfterLock(id);
+} // -Mfq
+
+// Threads: T*
bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level,
- LLPointer<LLImageRaw>& raw, LLPointer<LLImageRaw>& aux)
+ LLPointer<LLImageRaw>& raw, LLPointer<LLImageRaw>& aux,
+ LLCore::HttpStatus& last_http_get_status)
{
bool res = false;
- LLMutexLock lock(&mQueueMutex);
LLTextureFetchWorker* worker = getWorker(id);
if (worker)
{
@@ -1498,30 +2915,39 @@ bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level,
// Should only happen if we set mDebugPause...
if (!mDebugPause)
{
-// llwarns << "Adding work for inactive worker: " << id << llendl;
+// LL_WARNS(LOG_TXT) << "Adding work for inactive worker: " << id << LL_ENDL;
worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
}
}
else if (worker->checkWork())
{
+ worker->lockWorkMutex(); // +Mw
+ last_http_get_status = worker->mGetStatus;
discard_level = worker->mDecodedDiscard;
- raw = worker->mRawImage; worker->mRawImage = NULL;
- aux = worker->mAuxImage; worker->mAuxImage = NULL;
+ raw = worker->mRawImage;
+ aux = worker->mAuxImage;
+ F32Seconds cache_read_time(worker->mCacheReadTime);
+ if (cache_read_time != (F32Seconds)0.f)
+ {
+ record(sCacheReadLatency, cache_read_time);
+ }
res = true;
+ LL_DEBUGS(LOG_TXT) << id << ": Request Finished. State: " << worker->mState << " Discard: " << discard_level << LL_ENDL;
+ worker->unlockWorkMutex(); // -Mw
}
else
{
- worker->lockWorkData();
+ worker->lockWorkMutex(); // +Mw
if ((worker->mDecodedDiscard >= 0) &&
(worker->mDecodedDiscard < discard_level || discard_level < 0) &&
(worker->mState >= LLTextureFetchWorker::WAIT_ON_WRITE))
{
// Not finished, but data is ready
discard_level = worker->mDecodedDiscard;
- if (worker->mRawImage) raw = worker->mRawImage;
- if (worker->mAuxImage) aux = worker->mAuxImage;
+ raw = worker->mRawImage;
+ aux = worker->mAuxImage;
}
- worker->unlockWorkData();
+ worker->unlockWorkMutex(); // -Mw
}
}
else
@@ -1531,125 +2957,319 @@ bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level,
return res;
}
+// Threads: T*
bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority)
{
bool res = false;
- LLMutexLock lock(&mQueueMutex);
LLTextureFetchWorker* worker = getWorker(id);
if (worker)
{
- worker->lockWorkData();
+ worker->lockWorkMutex(); // +Mw
worker->setImagePriority(priority);
- worker->unlockWorkData();
+ worker->unlockWorkMutex(); // -Mw
res = true;
}
return res;
}
-//////////////////////////////////////////////////////////////////////////////
+// Replicates and expands upon the base class's
+// getPending() implementation. getPending() and
+// runCondition() replicate one another's logic to
+// an extent and are sometimes used for the same
+// function (deciding whether or not to sleep/pause
+// a thread). So the implementations need to stay
+// in step, at least until this can be refactored and
+// the redundancy eliminated.
+//
+// Threads: T*
//virtual
-S32 LLTextureFetch::update(U32 max_time_ms)
+S32 LLTextureFetch::getPending()
{
S32 res;
- res = LLWorkerThread::update(max_time_ms);
+ lockData(); // +Ct
+ {
+ LLMutexLock lock(&mQueueMutex); // +Mfq
+
+ res = mRequestQueue.size();
+ res += mCommands.size();
+ } // -Mfq
+ unlockData(); // -Ct
+ return res;
+}
+
+// Locks: Ct
+// virtual
+bool LLTextureFetch::runCondition()
+{
+ // Caller is holding the lock on LLThread's condition variable.
- const F32 REQUEST_TIME = 1.f;
+ // LLQueuedThread, unlike its base class LLThread, makes this a
+ // private method which is unfortunate. I want to use it directly
+ // but I'm going to have to re-implement the logic here (or change
+ // declarations, which I don't want to do right now).
+ //
+ // Changes here may need to be reflected in getPending().
+
+ bool have_no_commands(false);
+ {
+ LLMutexLock lock(&mQueueMutex); // +Mfq
+
+ have_no_commands = mCommands.empty();
+ } // -Mfq
+
+ return ! (have_no_commands
+ && (mRequestQueue.empty() && mIdleThread)); // From base class
+}
- // Periodically, gather the list of textures that need data from the network
- // And send the requests out to the simulators
- if (mNetworkTimer.getElapsedTimeF32() >= REQUEST_TIME)
+//////////////////////////////////////////////////////////////////////////////
+
+// Threads: Ttf
+void LLTextureFetch::commonUpdate()
+{
+ // Update low/high water levels based on pipelining. We pick
+ // up setting eventually, so the semaphore/request level can
+ // fall outside the [0..HIGH_WATER] range. Expect that.
+ if (LLAppViewer::instance()->getAppCoreHttp().isPipelined(LLAppCoreHttp::AP_TEXTURE))
+ {
+ mHttpHighWater = HTTP_PIPE_REQUESTS_HIGH_WATER;
+ mHttpLowWater = HTTP_PIPE_REQUESTS_LOW_WATER;
+ }
+ else
{
- mNetworkTimer.reset();
- sendRequestListToSimulators();
+ mHttpHighWater = HTTP_NONPIPE_REQUESTS_HIGH_WATER;
+ mHttpLowWater = HTTP_NONPIPE_REQUESTS_LOW_WATER;
}
+
+ // Release waiters
+ releaseHttpWaiters();
+
+ // Run a cross-thread command, if any.
+ cmdDoWork();
+ // Deliver all completion notifications
+ LLCore::HttpStatus status = mHttpRequest->update(0);
+ if (! status)
+ {
+ LL_INFOS_ONCE(LOG_TXT) << "Problem during HTTP servicing. Reason: "
+ << status.toString()
+ << LL_ENDL;
+ }
+}
+
+
+// Threads: Tmain
+
+//virtual
+S32 LLTextureFetch::update(F32 max_time_ms)
+{
+ static LLCachedControl<F32> band_width(gSavedSettings,"ThrottleBandwidthKBPS", 500.0);
+
+ {
+ mNetworkQueueMutex.lock(); // +Mfnq
+ mMaxBandwidth = band_width();
+
+ add(LLStatViewer::TEXTURE_NETWORK_DATA_RECEIVED, mHTTPTextureBits);
+ mHTTPTextureBits = (U32Bits)0;
+
+ mNetworkQueueMutex.unlock(); // -Mfnq
+ }
+
+ S32 res = LLWorkerThread::update(max_time_ms);
+
+ if (!mDebugPause)
+ {
+ // this is the startup state when send_complete_agent_movement() message is sent.
+ // Before this, the RequestImages message sent by sendRequestListToSimulators
+ // won't work so don't bother trying
+ if (LLStartUp::getStartupState() > STATE_AGENT_SEND)
+ {
+ sendRequestListToSimulators();
+ }
+ }
+
+ if (!mThreaded)
+ {
+ commonUpdate();
+ }
+
+ if (mFetchDebugger)
+ {
+ mFetchDebugger->tryToStopDebug(); //check if need to stop debugger.
+ }
+
return res;
}
+// called in the MAIN thread after the TextureCacheThread shuts down.
+//
+// Threads: Tmain
+void LLTextureFetch::shutDownTextureCacheThread()
+{
+ if(mTextureCache)
+ {
+ llassert_always(mTextureCache->isQuitting() || mTextureCache->isStopped()) ;
+ mTextureCache = NULL ;
+ }
+}
+
+// called in the MAIN thread after the ImageDecodeThread shuts down.
+//
+// Threads: Tmain
+void LLTextureFetch::shutDownImageDecodeThread()
+{
+ if(mImageDecodeThread)
+ {
+ llassert_always(mImageDecodeThread->isQuitting() || mImageDecodeThread->isStopped()) ;
+ mImageDecodeThread = NULL ;
+ }
+}
+
+// Threads: Ttf
+void LLTextureFetch::startThread()
+{
+}
+
+// Threads: Ttf
+void LLTextureFetch::endThread()
+{
+ LL_INFOS(LOG_TXT) << "CacheReads: " << mTotalCacheReadCount
+ << ", CacheWrites: " << mTotalCacheWriteCount
+ << ", ResWaits: " << mTotalResourceWaitCount
+ << ", TotalHTTPReq: " << getTotalNumHTTPRequests()
+ << LL_ENDL;
+}
+
+// Threads: Ttf
+void LLTextureFetch::threadedUpdate()
+{
+ llassert_always(mHttpRequest);
+
+#if 0
+ // Limit update frequency
+ const F32 PROCESS_TIME = 0.05f;
+ static LLFrameTimer process_timer;
+ if (process_timer.getElapsedTimeF32() < PROCESS_TIME)
+ {
+ return;
+ }
+ process_timer.reset();
+#endif
+
+ commonUpdate();
+
+#if 0
+ const F32 INFO_TIME = 1.0f;
+ static LLFrameTimer info_timer;
+ if (info_timer.getElapsedTimeF32() >= INFO_TIME)
+ {
+ S32 q = mCurlGetRequest->getQueued();
+ if (q > 0)
+ {
+ LL_INFOS(LOG_TXT) << "Queued gets: " << q << LL_ENDL;
+ info_timer.reset();
+ }
+ }
+#endif
+}
+
//////////////////////////////////////////////////////////////////////////////
+// Threads: Tmain
void LLTextureFetch::sendRequestListToSimulators()
{
+ // All requests
+ const F32 REQUEST_DELTA_TIME = 0.10f; // 10 fps
+
+ // Sim requests
const S32 IMAGES_PER_REQUEST = 50;
- const F32 LAZY_FLUSH_TIMEOUT = 15.f; // 10.0f // temp
+ const F32 SIM_LAZY_FLUSH_TIMEOUT = 10.0f; // temp
const F32 MIN_REQUEST_TIME = 1.0f;
const F32 MIN_DELTA_PRIORITY = 1000.f;
- LLMutexLock lock(&mQueueMutex);
+ // Periodically, gather the list of textures that need data from the network
+ // And send the requests out to the simulators
+ static LLFrameTimer timer;
+ if (timer.getElapsedTimeF32() < REQUEST_DELTA_TIME)
+ {
+ return;
+ }
+ timer.reset();
// Send requests
typedef std::set<LLTextureFetchWorker*,LLTextureFetchWorker::Compare> request_list_t;
typedef std::map< LLHost, request_list_t > work_request_map_t;
work_request_map_t requests;
- for (queue_t::iterator iter = mNetworkQueue.begin(); iter != mNetworkQueue.end(); )
{
- queue_t::iterator curiter = iter++;
- LLTextureFetchWorker* req = getWorker(*curiter);
- if (!req)
+ LLMutexLock lock2(&mNetworkQueueMutex); // +Mfnq
+ for (queue_t::iterator iter = mNetworkQueue.begin(); iter != mNetworkQueue.end(); )
{
- mNetworkQueue.erase(curiter);
- continue; // paranoia
- }
- if (req->mID == mDebugID)
- {
- mDebugCount++; // for setting breakpoints
- }
- if (req->mTotalPackets > 0 && req->mLastPacket >= req->mTotalPackets-1)
- {
- // We have all the packets... make sure this is high priority
+ queue_t::iterator curiter = iter++;
+ LLTextureFetchWorker* req = getWorker(*curiter);
+ if (!req)
+ {
+ mNetworkQueue.erase(curiter);
+ continue; // paranoia
+ }
+ if ((req->mState != LLTextureFetchWorker::LOAD_FROM_NETWORK) &&
+ (req->mState != LLTextureFetchWorker::LOAD_FROM_SIMULATOR))
+ {
+ // We already received our URL, remove from the queue
+ LL_WARNS(LOG_TXT) << "Worker: " << req->mID << " in mNetworkQueue but in wrong state: " << req->mState << LL_ENDL;
+ mNetworkQueue.erase(curiter);
+ continue;
+ }
+ if (req->mID == mDebugID)
+ {
+ mDebugCount++; // for setting breakpoints
+ }
+ if (req->mSentRequest == LLTextureFetchWorker::SENT_SIM &&
+ req->mTotalPackets > 0 &&
+ req->mLastPacket >= req->mTotalPackets-1)
+ {
+ // We have all the packets... make sure this is high priority
// req->setPriority(LLWorkerThread::PRIORITY_HIGH | req->mWorkPriority);
- continue;
- }
- F32 elapsed = req->mRequestedTimer.getElapsedTimeF32();
- F32 delta_priority = llabs(req->mRequestedPriority - req->mImagePriority);
- if ((req->mSimRequestedDiscard != req->mDesiredDiscard) ||
- (delta_priority > MIN_DELTA_PRIORITY && elapsed >= MIN_REQUEST_TIME) ||
- (elapsed >= LAZY_FLUSH_TIMEOUT))
- {
- requests[req->mHost].insert(req);
+ continue;
+ }
+ F32 elapsed = req->mRequestedTimer.getElapsedTimeF32();
+ {
+ F32 delta_priority = llabs(req->mRequestedPriority - req->mImagePriority);
+ if ((req->mSimRequestedDiscard != req->mDesiredDiscard) ||
+ (delta_priority > MIN_DELTA_PRIORITY && elapsed >= MIN_REQUEST_TIME) ||
+ (elapsed >= SIM_LAZY_FLUSH_TIMEOUT))
+ {
+ requests[req->mHost].insert(req);
+ }
+ }
}
- }
+ } // -Mfnq
- std::string http_url;
-#if 0
- if (gSavedSettings.getBOOL("ImagePipelineUseHTTP"))
- {
- LLViewerRegion* region = gAgent.getRegion();
- if (region)
- {
- http_url = region->getCapability("RequestTextureDownload");
- }
- }
-#endif
-
for (work_request_map_t::iterator iter1 = requests.begin();
iter1 != requests.end(); ++iter1)
{
- bool use_http = http_url.empty() ? false : true;
LLHost host = iter1->first;
// invalid host = use agent host
if (host == LLHost::invalid)
{
host = gAgent.getRegionHost();
}
- else
- {
- use_http = false;
- }
- if (use_http)
+ S32 sim_request_count = 0;
+
+ for (request_list_t::iterator iter2 = iter1->second.begin();
+ iter2 != iter1->second.end(); ++iter2)
{
- }
- else
- {
- S32 request_count = 0;
- for (request_list_t::iterator iter2 = iter1->second.begin();
- iter2 != iter1->second.end(); ++iter2)
+ LLTextureFetchWorker* req = *iter2;
+ if (gMessageSystem)
{
- LLTextureFetchWorker* req = *iter2;
- req->mSentRequest = LLTextureFetchWorker::SENT_SIM;
- if (0 == request_count)
+ if (req->mSentRequest != LLTextureFetchWorker::SENT_SIM)
+ {
+ // Initialize packet data based on data read from cache
+ req->lockWorkMutex(); // +Mw
+ req->setupPacketData();
+ req->unlockWorkMutex(); // -Mw
+ }
+ if (0 == sim_request_count)
{
gMessageSystem->newMessageFast(_PREHASH_RequestImage);
gMessageSystem->nextBlockFast(_PREHASH_AgentData);
@@ -1663,90 +3283,107 @@ void LLTextureFetch::sendRequestListToSimulators()
gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, req->mImagePriority);
gMessageSystem->addU32Fast(_PREHASH_Packet, packet);
gMessageSystem->addU8Fast(_PREHASH_Type, req->mType);
-// llinfos << "IMAGE REQUEST: " << req->mID << " Discard: " << req->mDesiredDiscard
-// << " Packet: " << packet << " Priority: " << req->mImagePriority << llendl;
+// LL_INFOS(LOG_TXT) << "IMAGE REQUEST: " << req->mID << " Discard: " << req->mDesiredDiscard
+// << " Packet: " << packet << " Priority: " << req->mImagePriority << LL_ENDL;
+
+ static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog", false);
+ static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator", false);
+ if (log_to_viewer_log || log_to_sim)
+ {
+ mTextureInfo.setRequestStartTime(req->mID, LLTimer::getTotalTime());
+ mTextureInfo.setRequestOffset(req->mID, 0);
+ mTextureInfo.setRequestSize(req->mID, 0);
+ mTextureInfo.setRequestType(req->mID, LLTextureInfoDetails::REQUEST_TYPE_UDP);
+ }
- req->lockWorkData();
+ req->lockWorkMutex(); // +Mw
+ req->mSentRequest = LLTextureFetchWorker::SENT_SIM;
req->mSimRequestedDiscard = req->mDesiredDiscard;
req->mRequestedPriority = req->mImagePriority;
req->mRequestedTimer.reset();
- req->unlockWorkData();
- request_count++;
- if (request_count >= IMAGES_PER_REQUEST)
+ req->unlockWorkMutex(); // -Mw
+ sim_request_count++;
+ if (sim_request_count >= IMAGES_PER_REQUEST)
{
-// llinfos << "REQUESTING " << request_count << " IMAGES FROM HOST: " << host.getIPString() << llendl;
+// LL_INFOS(LOG_TXT) << "REQUESTING " << sim_request_count << " IMAGES FROM HOST: " << host.getIPString() << LL_ENDL;
+
gMessageSystem->sendSemiReliable(host, NULL, NULL);
- request_count = 0;
+ sim_request_count = 0;
}
}
- if (request_count > 0 && request_count < IMAGES_PER_REQUEST)
- {
-// llinfos << "REQUESTING " << request_count << " IMAGES FROM HOST: " << host.getIPString() << llendl;
- gMessageSystem->sendSemiReliable(host, NULL, NULL);
- request_count = 0;
- }
+ }
+ if (gMessageSystem && sim_request_count > 0 && sim_request_count < IMAGES_PER_REQUEST)
+ {
+// LL_INFOS(LOG_TXT) << "REQUESTING " << sim_request_count << " IMAGES FROM HOST: " << host.getIPString() << LL_ENDL;
+ gMessageSystem->sendSemiReliable(host, NULL, NULL);
+ sim_request_count = 0;
}
}
// Send cancelations
- if (!mCancelQueue.empty())
{
- for (cancel_queue_t::iterator iter1 = mCancelQueue.begin();
- iter1 != mCancelQueue.end(); ++iter1)
+ LLMutexLock lock2(&mNetworkQueueMutex); // +Mfnq
+ if (gMessageSystem && !mCancelQueue.empty())
{
- LLHost host = iter1->first;
- if (host == LLHost::invalid)
+ for (cancel_queue_t::iterator iter1 = mCancelQueue.begin();
+ iter1 != mCancelQueue.end(); ++iter1)
{
- host = gAgent.getRegionHost();
- }
- S32 request_count = 0;
- for (queue_t::iterator iter2 = iter1->second.begin();
- iter2 != iter1->second.end(); ++iter2)
- {
- if (0 == request_count)
+ LLHost host = iter1->first;
+ if (host == LLHost::invalid)
{
- gMessageSystem->newMessageFast(_PREHASH_RequestImage);
- gMessageSystem->nextBlockFast(_PREHASH_AgentData);
- gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
- gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
+ host = gAgent.getRegionHost();
}
- gMessageSystem->nextBlockFast(_PREHASH_RequestImage);
- gMessageSystem->addUUIDFast(_PREHASH_Image, *iter2);
- gMessageSystem->addS8Fast(_PREHASH_DiscardLevel, -1);
- gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, 0);
- gMessageSystem->addU32Fast(_PREHASH_Packet, 0);
- gMessageSystem->addU8Fast(_PREHASH_Type, 0);
-// llinfos << "CANCELING IMAGE REQUEST: " << (*iter2) << llendl;
-
- request_count++;
- if (request_count >= IMAGES_PER_REQUEST)
+ S32 request_count = 0;
+ for (queue_t::iterator iter2 = iter1->second.begin();
+ iter2 != iter1->second.end(); ++iter2)
+ {
+ if (0 == request_count)
+ {
+ gMessageSystem->newMessageFast(_PREHASH_RequestImage);
+ gMessageSystem->nextBlockFast(_PREHASH_AgentData);
+ gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
+ gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
+ }
+ gMessageSystem->nextBlockFast(_PREHASH_RequestImage);
+ gMessageSystem->addUUIDFast(_PREHASH_Image, *iter2);
+ gMessageSystem->addS8Fast(_PREHASH_DiscardLevel, -1);
+ gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, 0);
+ gMessageSystem->addU32Fast(_PREHASH_Packet, 0);
+ gMessageSystem->addU8Fast(_PREHASH_Type, 0);
+// LL_INFOS(LOG_TXT) << "CANCELING IMAGE REQUEST: " << (*iter2) << LL_ENDL;
+
+ request_count++;
+ if (request_count >= IMAGES_PER_REQUEST)
+ {
+ gMessageSystem->sendSemiReliable(host, NULL, NULL);
+ request_count = 0;
+ }
+ }
+ if (request_count > 0 && request_count < IMAGES_PER_REQUEST)
{
gMessageSystem->sendSemiReliable(host, NULL, NULL);
- request_count = 0;
}
}
- if (request_count > 0 && request_count < IMAGES_PER_REQUEST)
- {
- gMessageSystem->sendSemiReliable(host, NULL, NULL);
- }
+ mCancelQueue.clear();
}
- mCancelQueue.clear();
- }
+ } // -Mfnq
}
//////////////////////////////////////////////////////////////////////////////
+// Threads: T*
+// Locks: Mw
bool LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size)
{
mRequestedTimer.reset();
if (index >= mTotalPackets)
{
-// llwarns << "Received Image Packet " << index << " > max: " << mTotalPackets << " for image: " << mID << llendl;
+// LL_WARNS(LOG_TXT) << "Received Image Packet " << index << " > max: " << mTotalPackets << " for image: " << mID << LL_ENDL;
return false;
}
if (index > 0 && index < mTotalPackets-1 && size != MAX_IMG_PACKET_SIZE)
{
-// llwarns << "Received bad sized packet: " << index << ", " << size << " != " << MAX_IMG_PACKET_SIZE << " for image: " << mID << llendl;
+// LL_WARNS(LOG_TXT) << "Received bad sized packet: " << index << ", " << size << " != " << MAX_IMG_PACKET_SIZE << " for image: " << mID << LL_ENDL;
return false;
}
@@ -1756,7 +3393,7 @@ bool LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size)
}
else if (mPackets[index] != NULL)
{
-// llwarns << "Received duplicate packet: " << index << " for image: " << mID << llendl;
+// LL_WARNS(LOG_TXT) << "Received duplicate packet: " << index << " for image: " << mID << LL_ENDL;
return false;
}
@@ -1768,10 +3405,23 @@ bool LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size)
return true;
}
+void LLTextureFetchWorker::setState(e_state new_state)
+{
+ if (mFTType == FTT_SERVER_BAKE)
+ {
+ // NOTE: turning on these log statements is a reliable way to get
+ // blurry images fairly frequently. Presumably this is an
+ // indication of some subtle timing or locking issue.
+
+// LL_INFOS(LOG_TXT) << "id: " << mID << " FTType: " << mFTType << " disc: " << mDesiredDiscard << " sz: " << mDesiredSize << " state: " << e_state_name[mState] << " => " << e_state_name[new_state] << LL_ENDL;
+ }
+ mState = new_state;
+}
+
+// Threads: T*
bool LLTextureFetch::receiveImageHeader(const LLHost& host, const LLUUID& id, U8 codec, U16 packets, U32 totalbytes,
U16 data_size, U8* data)
{
- LLMutexLock lock(&mQueueMutex);
LLTextureFetchWorker* worker = getWorker(id);
bool res = true;
@@ -1779,36 +3429,42 @@ bool LLTextureFetch::receiveImageHeader(const LLHost& host, const LLUUID& id, U8
if (!worker)
{
-// llwarns << "Received header for non active worker: " << id << llendl;
+// LL_WARNS(LOG_TXT) << "Received header for non active worker: " << id << LL_ENDL;
res = false;
}
else if (worker->mState != LLTextureFetchWorker::LOAD_FROM_NETWORK ||
worker->mSentRequest != LLTextureFetchWorker::SENT_SIM)
{
-// llwarns << "receiveImageHeader for worker: " << id
+// LL_WARNS(LOG_TXT) << "receiveImageHeader for worker: " << id
// << " in state: " << LLTextureFetchWorker::sStateDescs[worker->mState]
-// << " sent: " << worker->mSentRequest << llendl;
+// << " sent: " << worker->mSentRequest << LL_ENDL;
res = false;
}
else if (worker->mLastPacket != -1)
{
// check to see if we've gotten this packet before
-// llwarns << "Received duplicate header for: " << id << llendl;
+// LL_WARNS(LOG_TXT) << "Received duplicate header for: " << id << LL_ENDL;
res = false;
}
else if (!data_size)
{
-// llwarns << "Img: " << id << ":" << " Empty Image Header" << llendl;
+// LL_WARNS(LOG_TXT) << "Img: " << id << ":" << " Empty Image Header" << LL_ENDL;
res = false;
}
if (!res)
{
+ mNetworkQueueMutex.lock(); // +Mfnq
++mBadPacketCount;
mCancelQueue[host].insert(id);
+ mNetworkQueueMutex.unlock(); // -Mfnq
return false;
}
- worker->lockWorkData();
+ LLViewerStatsRecorder::instance().textureFetch(data_size);
+ LLViewerStatsRecorder::instance().log(0.1f);
+
+ worker->lockWorkMutex();
+
// Copy header data into image object
worker->mImageCodec = codec;
@@ -1818,14 +3474,15 @@ bool LLTextureFetch::receiveImageHeader(const LLHost& host, const LLUUID& id, U8
llassert_always(data_size == FIRST_PACKET_SIZE || data_size == worker->mFileSize);
res = worker->insertPacket(0, data, data_size);
worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
- worker->mState = LLTextureFetchWorker::LOAD_FROM_SIMULATOR;
- worker->unlockWorkData();
+ worker->setState(LLTextureFetchWorker::LOAD_FROM_SIMULATOR);
+ worker->unlockWorkMutex(); // -Mw
return res;
}
+
+// Threads: T*
bool LLTextureFetch::receiveImagePacket(const LLHost& host, const LLUUID& id, U16 packet_num, U16 data_size, U8* data)
{
- LLMutexLock lock(&mQueueMutex);
LLTextureFetchWorker* worker = getWorker(id);
bool res = true;
@@ -1833,27 +3490,33 @@ bool LLTextureFetch::receiveImagePacket(const LLHost& host, const LLUUID& id, U1
if (!worker)
{
-// llwarns << "Received packet " << packet_num << " for non active worker: " << id << llendl;
+// LL_WARNS(LOG_TXT) << "Received packet " << packet_num << " for non active worker: " << id << LL_ENDL;
res = false;
}
else if (worker->mLastPacket == -1)
{
-// llwarns << "Received packet " << packet_num << " before header for: " << id << llendl;
+// LL_WARNS(LOG_TXT) << "Received packet " << packet_num << " before header for: " << id << LL_ENDL;
res = false;
}
else if (!data_size)
{
-// llwarns << "Img: " << id << ":" << " Empty Image Header" << llendl;
+// LL_WARNS(LOG_TXT) << "Img: " << id << ":" << " Empty Image Header" << LL_ENDL;
res = false;
}
if (!res)
{
+ mNetworkQueueMutex.lock(); // +Mfnq
++mBadPacketCount;
mCancelQueue[host].insert(id);
+ mNetworkQueueMutex.unlock(); // -Mfnq
return false;
}
+
+ LLViewerStatsRecorder::instance().textureFetch(data_size);
+ LLViewerStatsRecorder::instance().log(0.1f);
+
+ worker->lockWorkMutex();
- worker->lockWorkData();
res = worker->insertPacket(packet_num, data, data_size);
@@ -1861,40 +3524,53 @@ bool LLTextureFetch::receiveImagePacket(const LLHost& host, const LLUUID& id, U1
(worker->mState == LLTextureFetchWorker::LOAD_FROM_NETWORK))
{
worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
- worker->mState = LLTextureFetchWorker::LOAD_FROM_SIMULATOR;
+ worker->setState(LLTextureFetchWorker::LOAD_FROM_SIMULATOR);
}
else
{
-// llwarns << "receiveImagePacket " << packet_num << "/" << worker->mLastPacket << " for worker: " << id
-// << " in state: " << LLTextureFetchWorker::sStateDescs[worker->mState] << llendl;
- removeFromNetworkQueue(worker); // failsafe
- mCancelQueue[host].insert(id);
+// LL_WARNS(LOG_TXT) << "receiveImagePacket " << packet_num << "/" << worker->mLastPacket << " for worker: " << id
+// << " in state: " << LLTextureFetchWorker::sStateDescs[worker->mState] << LL_ENDL;
+ removeFromNetworkQueue(worker, true); // failsafe
}
-
- worker->unlockWorkData();
+
+ if (packet_num >= (worker->mTotalPackets - 1))
+ {
+ static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog", false);
+ static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator", false);
+
+ if (log_to_viewer_log || log_to_sim)
+ {
+ U64Microseconds timeNow = LLTimer::getTotalTime();
+ mTextureInfo.setRequestSize(id, worker->mFileSize);
+ mTextureInfo.setRequestCompleteTimeAndLog(id, timeNow);
+ }
+ }
+ worker->unlockWorkMutex(); // -Mw
return res;
}
//////////////////////////////////////////////////////////////////////////////
+
+// Threads: T*
BOOL LLTextureFetch::isFromLocalCache(const LLUUID& id)
{
BOOL from_cache = FALSE ;
- LLMutexLock lock(&mQueueMutex);
LLTextureFetchWorker* worker = getWorker(id);
if (worker)
{
- worker->lockWorkData();
- from_cache = worker->mInLocalCache ;
- worker->unlockWorkData();
+ worker->lockWorkMutex(); // +Mw
+ from_cache = worker->mInLocalCache;
+ worker->unlockWorkMutex(); // -Mw
}
return from_cache ;
}
+// Threads: T*
S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& requested_priority_p,
- U32& fetch_priority_p, F32& fetch_dtime_p, F32& request_dtime_p)
+ U32& fetch_priority_p, F32& fetch_dtime_p, F32& request_dtime_p, bool& can_use_http)
{
S32 state = LLTextureFetchWorker::INVALID;
F32 data_progress = 0.0f;
@@ -1903,11 +3579,10 @@ S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& r
F32 request_dtime = 999999.f;
U32 fetch_priority = 0;
- LLMutexLock lock(&mQueueMutex);
LLTextureFetchWorker* worker = getWorker(id);
if (worker && worker->haveWork())
{
- worker->lockWorkData();
+ worker->lockWorkMutex(); // +Mw
state = worker->mState;
fetch_dtime = worker->mFetchTimer.getElapsedTimeF32();
request_dtime = worker->mRequestedTimer.getElapsedTimeF32();
@@ -1924,7 +3599,7 @@ S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& r
data_progress = (F32)worker->mFormattedImage->getDataSize() / (F32)worker->mFileSize;
}
}
- if (state >= LLTextureFetchWorker::LOAD_FROM_NETWORK && state <= LLTextureFetchWorker::LOAD_FROM_HTTP_GET_DATA)
+ if (state >= LLTextureFetchWorker::LOAD_FROM_NETWORK && state <= LLTextureFetchWorker::WAIT_HTTP_REQ)
{
requested_priority = worker->mRequestedPriority;
}
@@ -1933,7 +3608,8 @@ S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& r
requested_priority = worker->mImagePriority;
}
fetch_priority = worker->getPriority();
- worker->unlockWorkData();
+ can_use_http = worker->getCanUseHTTP() ;
+ worker->unlockWorkMutex(); // -Mw
}
data_progress_p = data_progress;
requested_priority_p = requested_priority;
@@ -1945,19 +3621,1434 @@ S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& r
void LLTextureFetch::dump()
{
- llinfos << "LLTextureFetch REQUESTS:" << llendl;
+ LL_INFOS(LOG_TXT) << "LLTextureFetch REQUESTS:" << LL_ENDL;
for (request_queue_t::iterator iter = mRequestQueue.begin();
iter != mRequestQueue.end(); ++iter)
{
LLQueuedThread::QueuedRequest* qreq = *iter;
LLWorkerThread::WorkRequest* wreq = (LLWorkerThread::WorkRequest*)qreq;
LLTextureFetchWorker* worker = (LLTextureFetchWorker*)wreq->getWorkerClass();
- llinfos << " ID: " << worker->mID
- << " PRI: " << llformat("0x%08x",wreq->getPriority())
- << " STATE: " << worker->sStateDescs[worker->mState]
- << llendl;
+ LL_INFOS(LOG_TXT) << " ID: " << worker->mID
+ << " PRI: " << llformat("0x%08x",wreq->getPriority())
+ << " STATE: " << worker->sStateDescs[worker->mState]
+ << LL_ENDL;
+ }
+
+ LL_INFOS(LOG_TXT) << "LLTextureFetch ACTIVE_HTTP:" << LL_ENDL;
+ for (queue_t::const_iterator iter(mHTTPTextureQueue.begin());
+ mHTTPTextureQueue.end() != iter;
+ ++iter)
+ {
+ LL_INFOS(LOG_TXT) << " ID: " << (*iter) << LL_ENDL;
+ }
+
+ LL_INFOS(LOG_TXT) << "LLTextureFetch WAIT_HTTP_RESOURCE:" << LL_ENDL;
+ for (wait_http_res_queue_t::const_iterator iter(mHttpWaitResource.begin());
+ mHttpWaitResource.end() != iter;
+ ++iter)
+ {
+ LL_INFOS(LOG_TXT) << " ID: " << (*iter) << LL_ENDL;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// HTTP Resource Waiting Methods
+
+// Threads: Ttf
+void LLTextureFetch::addHttpWaiter(const LLUUID & tid)
+{
+ mNetworkQueueMutex.lock(); // +Mfnq
+ mHttpWaitResource.insert(tid);
+ mNetworkQueueMutex.unlock(); // -Mfnq
+}
+
+// Threads: Ttf
+void LLTextureFetch::removeHttpWaiter(const LLUUID & tid)
+{
+ mNetworkQueueMutex.lock(); // +Mfnq
+ wait_http_res_queue_t::iterator iter(mHttpWaitResource.find(tid));
+ if (mHttpWaitResource.end() != iter)
+ {
+ mHttpWaitResource.erase(iter);
+ }
+ mNetworkQueueMutex.unlock(); // -Mfnq
+}
+
+// Threads: T*
+bool LLTextureFetch::isHttpWaiter(const LLUUID & tid)
+{
+ mNetworkQueueMutex.lock(); // +Mfnq
+ wait_http_res_queue_t::iterator iter(mHttpWaitResource.find(tid));
+ const bool ret(mHttpWaitResource.end() != iter);
+ mNetworkQueueMutex.unlock(); // -Mfnq
+ return ret;
+}
+
+// Release as many requests as permitted from the WAIT_HTTP_RESOURCE2
+// state to the SEND_HTTP_REQ state based on their current priority.
+//
+// This data structures and code associated with this looks a bit
+// indirect and naive but it's done in the name of safety. An
+// ordered container may become invalid from time to time due to
+// priority changes caused by actions in other threads. State itself
+// could also suffer the same fate with canceled operations. Even
+// done this way, I'm not fully trusting we're truly safe. This
+// module is due for a major refactoring and we'll deal with it then.
+//
+// Threads: Ttf
+// Locks: -Mw (must not hold any worker when called)
+void LLTextureFetch::releaseHttpWaiters()
+{
+ // Use mHttpSemaphore rather than mHTTPTextureQueue.size()
+ // to avoid a lock.
+ if (mHttpSemaphore >= mHttpLowWater)
+ return;
+ S32 needed(mHttpHighWater - mHttpSemaphore);
+ if (needed <= 0)
+ {
+ // Would only happen if High/LowWater were changed behind
+ // our back. In that case, defer fill until usage falls within
+ // limits.
+ return;
+ }
+
+ // Quickly make a copy of all the LLUIDs. Get off the
+ // mutex as early as possible.
+ typedef std::vector<LLUUID> uuid_vec_t;
+ uuid_vec_t tids;
+
+ {
+ LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq
+
+ if (mHttpWaitResource.empty())
+ return;
+ tids.reserve(mHttpWaitResource.size());
+ tids.assign(mHttpWaitResource.begin(), mHttpWaitResource.end());
+ } // -Mfnq
+
+ // Now lookup the UUUIDs to find valid requests and sort
+ // them in priority order, highest to lowest. We're going
+ // to modify priority later as a side-effect of releasing
+ // these objects. That, in turn, would violate the partial
+ // ordering assumption of std::set, std::map, etc. so we
+ // don't use those containers. We use a vector and an explicit
+ // sort to keep the containers valid later.
+ typedef std::vector<LLTextureFetchWorker *> worker_list_t;
+ worker_list_t tids2;
+
+ tids2.reserve(tids.size());
+ for (uuid_vec_t::iterator iter(tids.begin());
+ tids.end() != iter;
+ ++iter)
+ {
+ LLTextureFetchWorker * worker(getWorker(* iter));
+ if (worker)
+ {
+ tids2.push_back(worker);
+ }
+ else
+ {
+ // If worker isn't found, this should be due to a request
+ // for deletion. We signal our recognition that this
+ // uuid shouldn't be used for resource waiting anymore by
+ // erasing it from the resource waiter list. That allows
+ // deleteOK to do final deletion on the worker.
+ removeHttpWaiter(* iter);
+ }
+ }
+ tids.clear();
+
+ // Sort into priority order, if necessary and only as much as needed
+ if (tids2.size() > needed)
+ {
+ LLTextureFetchWorker::Compare compare;
+ std::partial_sort(tids2.begin(), tids2.begin() + needed, tids2.end(), compare);
+ }
+
+ // Release workers up to the high water mark. Since we aren't
+ // holding any locks at this point, we can be in competition
+ // with other callers. Do defensive things like getting
+ // refreshed counts of requests and checking if someone else
+ // has moved any worker state around....
+ for (worker_list_t::iterator iter2(tids2.begin()); tids2.end() != iter2; ++iter2)
+ {
+ LLTextureFetchWorker * worker(* iter2);
+
+ worker->lockWorkMutex(); // +Mw
+ if (LLTextureFetchWorker::WAIT_HTTP_RESOURCE2 != worker->mState)
+ {
+ // Not in expected state, remove it, try the next one
+ worker->unlockWorkMutex(); // -Mw
+ LL_WARNS(LOG_TXT) << "Resource-waited texture " << worker->mID
+ << " in unexpected state: " << worker->mState
+ << ". Removing from wait list."
+ << LL_ENDL;
+ removeHttpWaiter(worker->mID);
+ continue;
+ }
+
+ if (! worker->acquireHttpSemaphore())
+ {
+ // Out of active slots, quit
+ worker->unlockWorkMutex(); // -Mw
+ break;
+ }
+
+ worker->setState(LLTextureFetchWorker::SEND_HTTP_REQ);
+ worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
+ worker->unlockWorkMutex(); // -Mw
+
+ removeHttpWaiter(worker->mID);
+ }
+}
+
+// Threads: T*
+void LLTextureFetch::cancelHttpWaiters()
+{
+ mNetworkQueueMutex.lock(); // +Mfnq
+ mHttpWaitResource.clear();
+ mNetworkQueueMutex.unlock(); // -Mfnq
+}
+
+// Threads: T*
+int LLTextureFetch::getHttpWaitersCount()
+{
+ mNetworkQueueMutex.lock(); // +Mfnq
+ int ret(mHttpWaitResource.size());
+ mNetworkQueueMutex.unlock(); // -Mfnq
+ return ret;
+}
+
+
+// Threads: T*
+void LLTextureFetch::updateStateStats(U32 cache_read, U32 cache_write, U32 res_wait)
+{
+ LLMutexLock lock(&mQueueMutex); // +Mfq
+
+ mTotalCacheReadCount += cache_read;
+ mTotalCacheWriteCount += cache_write;
+ mTotalResourceWaitCount += res_wait;
+} // -Mfq
+
+
+// Threads: T*
+void LLTextureFetch::getStateStats(U32 * cache_read, U32 * cache_write, U32 * res_wait)
+{
+ U32 ret1(0U), ret2(0U), ret3(0U);
+
+ {
+ LLMutexLock lock(&mQueueMutex); // +Mfq
+ ret1 = mTotalCacheReadCount;
+ ret2 = mTotalCacheWriteCount;
+ ret3 = mTotalResourceWaitCount;
+ } // -Mfq
+
+ *cache_read = ret1;
+ *cache_write = ret2;
+ *res_wait = ret3;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// cross-thread command methods
+
+// Threads: T*
+void LLTextureFetch::commandSetRegion(U64 region_handle)
+{
+ TFReqSetRegion * req = new TFReqSetRegion(region_handle);
+
+ cmdEnqueue(req);
+}
+
+// Threads: T*
+void LLTextureFetch::commandSendMetrics(const std::string & caps_url,
+ const LLUUID & session_id,
+ const LLUUID & agent_id,
+ LLViewerAssetStats * main_stats)
+{
+ TFReqSendMetrics * req = new TFReqSendMetrics(caps_url, session_id, agent_id, main_stats);
+
+ cmdEnqueue(req);
+}
+
+// Threads: T*
+void LLTextureFetch::commandDataBreak()
+{
+ // The pedantically correct way to implement this is to create a command
+ // request object in the above fashion and enqueue it. However, this is
+ // simple data of an advisorial not operational nature and this case
+ // of shared-write access is tolerable.
+
+ LLTextureFetch::svMetricsDataBreak = true;
+}
+
+// Threads: T*
+void LLTextureFetch::cmdEnqueue(TFRequest * req)
+{
+ lockQueue(); // +Mfq
+ mCommands.push_back(req);
+ unlockQueue(); // -Mfq
+
+ unpause();
+}
+
+// Threads: T*
+LLTextureFetch::TFRequest * LLTextureFetch::cmdDequeue()
+{
+ TFRequest * ret = 0;
+
+ lockQueue(); // +Mfq
+ if (! mCommands.empty())
+ {
+ ret = mCommands.front();
+ mCommands.erase(mCommands.begin());
}
+ unlockQueue(); // -Mfq
+
+ return ret;
}
+// Threads: Ttf
+void LLTextureFetch::cmdDoWork()
+{
+ if (mDebugPause)
+ {
+ return; // debug: don't do any work
+ }
+
+ TFRequest * req = cmdDequeue();
+ if (req)
+ {
+ // One request per pass should really be enough for this.
+ req->doWork(this);
+ delete req;
+ }
+}
//////////////////////////////////////////////////////////////////////////////
+
+// Private (anonymous) class methods implementing the command scheme.
+
+namespace
+{
+
+
+// Example of a simple notification handler for metrics
+// delivery notification. Earlier versions of the code used
+// a Responder that tried harder to detect delivery breaks
+// but it really isn't that important. If someone wants to
+// revisit that effort, here is a place to start.
+class AssetReportHandler : public LLCore::HttpHandler
+{
+public:
+
+ // Threads: Ttf
+ virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response)
+ {
+ LLCore::HttpStatus status(response->getStatus());
+
+ if (status)
+ {
+ LL_DEBUGS(LOG_TXT) << "Successfully delivered asset metrics to grid."
+ << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS(LOG_TXT) << "Error delivering asset metrics to grid. Status: "
+ << status.toTerseString()
+ << ", Reason: " << status.toString() << LL_ENDL;
+ }
+ }
+}; // end class AssetReportHandler
+
+AssetReportHandler stats_handler;
+
+
+/**
+ * Implements the 'Set Region' command.
+ *
+ * Thread: Thread1 (TextureFetch)
+ */
+bool
+TFReqSetRegion::doWork(LLTextureFetch *)
+{
+ LLViewerAssetStatsFF::set_region(mRegionHandle);
+
+ return true;
+}
+
+
+TFReqSendMetrics::~TFReqSendMetrics()
+{
+ delete mMainStats;
+ mMainStats = 0;
+}
+
+
+/**
+ * Implements the 'Send Metrics' command. Takes over
+ * ownership of the passed LLViewerAssetStats pointer.
+ *
+ * Thread: Thread1 (TextureFetch)
+ */
+bool
+TFReqSendMetrics::doWork(LLTextureFetch * fetcher)
+{
+ static const U32 report_priority(1);
+ static LLCore::HttpHandler * const handler(fetcher->isQAMode() || true ? &stats_handler : NULL);
+
+ //if (! gViewerAssetStatsThread1)
+ // return true;
+
+ static volatile bool reporting_started(false);
+ static volatile S32 report_sequence(0);
+
+ // We've taken over ownership of the stats copy at this
+ // point. Get a working reference to it for merging here
+ // but leave it in 'this'. Destructor will rid us of it.
+ LLViewerAssetStats & main_stats = *mMainStats;
+
+ LLViewerAssetStats::AssetStats stats;
+ main_stats.getStats(stats, true);
+ //LLSD merged_llsd = main_stats.asLLSD();
+
+ bool initial_report = !reporting_started;
+ stats.session_id = mSessionID;
+ stats.agent_id = mAgentID;
+ stats.message = "ViewerAssetMetrics";
+ stats.sequence = static_cast<bool>(report_sequence);
+ stats.initial = initial_report;
+ stats.break_ = static_cast<bool>(LLTextureFetch::svMetricsDataBreak);
+
+ LLSD sd;
+ LLParamSDParser parser;
+ parser.writeSD(sd, stats);
+
+ // Update sequence number
+ if (S32_MAX == ++report_sequence)
+ {
+ report_sequence = 0;
+ }
+ reporting_started = true;
+
+ // Limit the size of the stats report if necessary.
+
+ sd["truncated"] = truncate_viewer_metrics(10, sd);
+
+ if (! mCapsURL.empty())
+ {
+ // Don't care about handle, this is a fire-and-forget operation.
+ LLCoreHttpUtil::requestPostWithLLSD(&fetcher->getHttpRequest(),
+ fetcher->getMetricsPolicyClass(),
+ report_priority,
+ mCapsURL,
+ sd,
+ NULL,
+ fetcher->getMetricsHeaders(),
+ handler);
+ LLTextureFetch::svMetricsDataBreak = false;
+ }
+ else
+ {
+ LLTextureFetch::svMetricsDataBreak = true;
+ }
+
+ // In QA mode, Metrics submode, log the result for ease of testing
+ if (fetcher->isQAMode())
+ {
+ LL_INFOS(LOG_TXT) << ll_pretty_print_sd(sd) << LL_ENDL;
+ }
+
+ return true;
+}
+
+
+bool
+truncate_viewer_metrics(int max_regions, LLSD & metrics)
+{
+ static const LLSD::String reg_tag("regions");
+ static const LLSD::String duration_tag("duration");
+
+ LLSD & reg_map(metrics[reg_tag]);
+ if (reg_map.size() <= max_regions)
+ {
+ return false;
+ }
+
+ // Build map of region hashes ordered by duration
+ typedef std::multimap<LLSD::Real, int> reg_ordered_list_t;
+ reg_ordered_list_t regions_by_duration;
+
+ int ind(0);
+ LLSD::array_const_iterator it_end(reg_map.endArray());
+ for (LLSD::array_const_iterator it(reg_map.beginArray()); it_end != it; ++it, ++ind)
+ {
+ LLSD::Real duration = (*it)[duration_tag].asReal();
+ regions_by_duration.insert(reg_ordered_list_t::value_type(duration, ind));
+ }
+
+ // Build a replacement regions array with the longest-persistence regions
+ LLSD new_region(LLSD::emptyArray());
+ reg_ordered_list_t::const_reverse_iterator it2_end(regions_by_duration.rend());
+ reg_ordered_list_t::const_reverse_iterator it2(regions_by_duration.rbegin());
+ for (int i(0); i < max_regions && it2_end != it2; ++i, ++it2)
+ {
+ new_region.append(reg_map[it2->second]);
+ }
+ reg_map = new_region;
+
+ return true;
+}
+
+} // end of anonymous namespace
+
+
+///////////////////////////////////////////////////////////////////////////////////////////
+//Start LLTextureFetchDebugger
+///////////////////////////////////////////////////////////////////////////////////////////
+//---------------------
+class LLDebuggerCacheReadResponder : public LLTextureCache::ReadResponder
+{
+public:
+ LLDebuggerCacheReadResponder(LLTextureFetchDebugger* debugger, S32 id, LLImageFormatted* image)
+ : mDebugger(debugger), mID(id)
+ {
+ setImage(image);
+ }
+ virtual void completed(bool success)
+ {
+ mDebugger->callbackCacheRead(mID, success, mFormattedImage, mImageSize, mImageLocal);
+ }
+private:
+ LLTextureFetchDebugger* mDebugger;
+ S32 mID;
+};
+
+class LLDebuggerCacheWriteResponder : public LLTextureCache::WriteResponder
+{
+public:
+ LLDebuggerCacheWriteResponder(LLTextureFetchDebugger* debugger, S32 id)
+ : mDebugger(debugger), mID(id)
+ {
+ }
+ virtual void completed(bool success)
+ {
+ mDebugger->callbackCacheWrite(mID, success);
+ }
+private:
+ LLTextureFetchDebugger* mDebugger;
+ S32 mID;
+};
+
+class LLDebuggerDecodeResponder : public LLImageDecodeThread::Responder
+{
+public:
+ LLDebuggerDecodeResponder(LLTextureFetchDebugger* debugger, S32 id)
+ : mDebugger(debugger), mID(id)
+ {
+ }
+ virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux)
+ {
+ mDebugger->callbackDecoded(mID, success, raw, aux);
+ }
+private:
+ LLTextureFetchDebugger* mDebugger;
+ S32 mID;
+};
+
+
+LLTextureFetchDebugger::LLTextureFetchDebugger(LLTextureFetch* fetcher, LLTextureCache* cache, LLImageDecodeThread* imagedecodethread) :
+ LLCore::HttpHandler(),
+ mFetcher(fetcher),
+ mTextureCache(cache),
+ mImageDecodeThread(imagedecodethread),
+ mHttpHeaders(NULL),
+ mHttpPolicyClass(fetcher->getPolicyClass()),
+ mNbCurlCompleted(0),
+ mTempIndex(0),
+ mHistoryListIndex(0)
+{
+ init();
+}
+
+LLTextureFetchDebugger::~LLTextureFetchDebugger()
+{
+ mFetchingHistory.clear();
+ mStopDebug = TRUE;
+ tryToStopDebug();
+ if (mHttpHeaders)
+ {
+ mHttpHeaders->release();
+ mHttpHeaders = NULL;
+ }
+}
+
+void LLTextureFetchDebugger::init()
+{
+ setDebuggerState(IDLE);
+
+ mCacheReadTime = -1.f;
+ mCacheWriteTime = -1.f;
+ mDecodingTime = -1.f;
+ mHTTPTime = -1.f;
+ mGLCreationTime = -1.f;
+
+ mTotalFetchingTime = 0.f;
+ mRefetchVisCacheTime = -1.f;
+ mRefetchVisHTTPTime = -1.f;
+ mRefetchAllCacheTime = -1.f;
+ mRefetchAllHTTPTime = -1.f;
+
+ mNumFetchedTextures = 0;
+ mNumCacheHits = 0;
+ mNumVisibleFetchedTextures = 0;
+ mNumVisibleFetchingRequests = 0;
+ mFetchedData = 0;
+ mDecodedData = 0;
+ mVisibleFetchedData = 0;
+ mVisibleDecodedData = 0;
+ mRenderedData = 0;
+ mRenderedDecodedData = 0;
+ mFetchedPixels = 0;
+ mRenderedPixels = 0;
+ mRefetchedVisData = 0;
+ mRefetchedVisPixels = 0;
+ mRefetchedAllData = 0;
+ mRefetchedAllPixels = 0;
+
+ mFreezeHistory = FALSE;
+ mStopDebug = FALSE;
+ mClearHistory = FALSE;
+ mRefetchNonVis = FALSE;
+
+ mNbCurlRequests = 0;
+
+ if (! mHttpHeaders)
+ {
+ mHttpHeaders = new LLCore::HttpHeaders;
+ mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_IMAGE_X_J2C);
+ }
+}
+
+void LLTextureFetchDebugger::startWork(e_debug_state state)
+{
+ switch(state)
+ {
+ case IDLE:
+ break;
+ case START_DEBUG:
+ startDebug();
+ break;
+ case READ_CACHE:
+ debugCacheRead();
+ break;
+ case WRITE_CACHE:
+ debugCacheWrite();
+ break;
+ case DECODING:
+ debugDecoder();
+ break;
+ case HTTP_FETCHING:
+ debugHTTP();
+ break;
+ case GL_TEX:
+ debugGLTextureCreation();
+ break;
+ case REFETCH_VIS_CACHE:
+ debugRefetchVisibleFromCache();
+ break;
+ case REFETCH_VIS_HTTP:
+ debugRefetchVisibleFromHTTP();
+ break;
+ case REFETCH_ALL_CACHE:
+ debugRefetchAllFromCache();
+ break;
+ case REFETCH_ALL_HTTP:
+ debugRefetchAllFromHTTP();
+ break;
+ default:
+ break;
+ }
+ return;
+}
+
+void LLTextureFetchDebugger::startDebug()
+{
+ //lock the fetcher
+ mFetcher->lockFetcher(true);
+ mFreezeHistory = TRUE;
+ mFetcher->resetLoadSource();
+
+ //clear the current fetching queue
+ gTextureList.clearFetchingRequests();
+
+ setDebuggerState(START_DEBUG);
+}
+
+bool LLTextureFetchDebugger::processStartDebug(F32 max_time)
+{
+ mTimer.reset();
+
+ //wait for all works to be done
+ while(1)
+ {
+ S32 pending = 0;
+ pending += LLAppViewer::getTextureCache()->update(1);
+ pending += LLAppViewer::getImageDecodeThread()->update(1);
+ // pending += LLAppViewer::getTextureFetch()->update(1); // This causes infinite recursion in some cases
+ pending += mNbCurlRequests;
+ if(!pending)
+ {
+ break;
+ }
+
+ if(mTimer.getElapsedTimeF32() > max_time)
+ {
+ return false;
+ }
+ }
+
+ //collect statistics
+ mTotalFetchingTime = gTextureTimer.getElapsedTimeF32() - mTotalFetchingTime;
+
+ std::set<LLUUID> fetched_textures;
+ S32 size = mFetchingHistory.size();
+ for(S32 i = 0 ; i < size; i++)
+ {
+ bool in_list = true;
+ if(fetched_textures.find(mFetchingHistory[i].mID) == fetched_textures.end())
+ {
+ fetched_textures.insert(mFetchingHistory[i].mID);
+ in_list = false;
+ }
+
+ LLViewerFetchedTexture* tex = LLViewerTextureManager::findFetchedTexture(mFetchingHistory[i].mID);
+ if(tex && tex->isJustBound()) //visible
+ {
+ if(!in_list)
+ {
+ mNumVisibleFetchedTextures++;
+ }
+ mNumVisibleFetchingRequests++;
+
+ mVisibleFetchedData += mFetchingHistory[i].mFetchedSize;
+ mVisibleDecodedData += mFetchingHistory[i].mDecodedSize;
+
+ if(tex->getDiscardLevel() >= mFetchingHistory[i].mDecodedLevel)
+ {
+ mRenderedData += mFetchingHistory[i].mFetchedSize;
+ mRenderedDecodedData += mFetchingHistory[i].mDecodedSize;
+ mRenderedPixels += tex->getWidth() * tex->getHeight();
+ }
+ }
+ }
+
+ mNumFetchedTextures = fetched_textures.size();
+
+ return true;
+}
+
+void LLTextureFetchDebugger::tryToStopDebug()
+{
+ if(!mStopDebug)
+ {
+ return;
+ }
+
+ //clear the current debug work
+ S32 size = mFetchingHistory.size();
+ switch(mDebuggerState)
+ {
+ case READ_CACHE:
+ for(S32 i = 0 ; i < size; i++)
+ {
+ if (mFetchingHistory[i]. mCacheHandle != LLTextureCache::nullHandle())
+ {
+ mTextureCache->readComplete(mFetchingHistory[i].mCacheHandle, true);
+ }
+ }
+ break;
+ case WRITE_CACHE:
+ for(S32 i = 0 ; i < size; i++)
+ {
+ if (mFetchingHistory[i].mCacheHandle != LLTextureCache::nullHandle())
+ {
+ mTextureCache->writeComplete(mFetchingHistory[i].mCacheHandle, true);
+ }
+ }
+ break;
+ case DECODING:
+ break;
+ case HTTP_FETCHING:
+ break;
+ case GL_TEX:
+ break;
+ case REFETCH_VIS_CACHE:
+ break;
+ case REFETCH_VIS_HTTP:
+ break;
+ case REFETCH_ALL_CACHE:
+ mRefetchList.clear();
+ break;
+ case REFETCH_ALL_HTTP:
+ mRefetchList.clear();
+ break;
+ default:
+ break;
+ }
+
+ if(update(0.005f))
+ {
+ //unlock the fetcher
+ mFetcher->lockFetcher(false);
+ mFetcher->resetLoadSource();
+ mFreezeHistory = FALSE;
+ mStopDebug = FALSE;
+
+ if(mClearHistory)
+ {
+ mFetchingHistory.clear();
+ mHandleToFetchIndex.clear();
+ init();
+ mTotalFetchingTime = gTextureTimer.getElapsedTimeF32(); //reset
+ }
+ }
+}
+
+//called in the main thread and when the fetching queue is empty
+void LLTextureFetchDebugger::clearHistory()
+{
+ mClearHistory = TRUE;
+}
+
+void LLTextureFetchDebugger::addHistoryEntry(LLTextureFetchWorker* worker)
+{
+ if(worker->mRawImage.isNull() || worker->mFormattedImage.isNull())
+ {
+ return;
+ }
+
+ if(mFreezeHistory)
+ {
+ if(mDebuggerState == REFETCH_VIS_CACHE || mDebuggerState == REFETCH_VIS_HTTP)
+ {
+ mRefetchedVisPixels += worker->mRawImage->getWidth() * worker->mRawImage->getHeight();
+ mRefetchedVisData += worker->mFormattedImage->getDataSize();
+ }
+ else
+ {
+ mRefetchedAllPixels += worker->mRawImage->getWidth() * worker->mRawImage->getHeight();
+ mRefetchedAllData += worker->mFormattedImage->getDataSize();
+
+ LLViewerFetchedTexture* tex = LLViewerTextureManager::findFetchedTexture(worker->mID);
+ if(tex && mRefetchList[tex].begin() != mRefetchList[tex].end())
+ {
+ if(worker->mDecodedDiscard == mFetchingHistory[mRefetchList[tex][0]].mDecodedLevel)
+ {
+ mRefetchList[tex].erase(mRefetchList[tex].begin());
+ }
+ }
+ }
+ return;
+ }
+
+ if(worker->mInCache)
+ {
+ mNumCacheHits++;
+ }
+ mFetchedData += worker->mFormattedImage->getDataSize();
+ mDecodedData += worker->mRawImage->getDataSize();
+ mFetchedPixels += worker->mRawImage->getWidth() * worker->mRawImage->getHeight();
+
+ mFetchingHistory.push_back(FetchEntry(worker->mID, worker->mDesiredSize, worker->mDecodedDiscard,
+ worker->mFormattedImage->getDataSize(), worker->mRawImage->getDataSize()));
+}
+
+void LLTextureFetchDebugger::lockCache()
+{
+}
+
+void LLTextureFetchDebugger::unlockCache()
+{
+}
+
+void LLTextureFetchDebugger::debugCacheRead()
+{
+ lockCache();
+ llassert_always(mDebuggerState == IDLE);
+ mTimer.reset();
+ setDebuggerState(READ_CACHE);
+ mCacheReadTime = -1.f;
+
+ S32 size = mFetchingHistory.size();
+ for(S32 i = 0 ; i < size ; i++)
+ {
+ mFetchingHistory[i].mFormattedImage = NULL;
+ mFetchingHistory[i].mCacheHandle = mTextureCache->readFromCache(mFetchingHistory[i].mID, LLWorkerThread::PRIORITY_NORMAL, 0, mFetchingHistory[i].mFetchedSize,
+ new LLDebuggerCacheReadResponder(this, i, mFetchingHistory[i].mFormattedImage));
+ }
+}
+
+void LLTextureFetchDebugger::clearCache()
+{
+ S32 size = mFetchingHistory.size();
+ {
+ std::set<LLUUID> deleted_list;
+ for(S32 i = 0 ; i < size ; i++)
+ {
+ if(deleted_list.find(mFetchingHistory[i].mID) == deleted_list.end())
+ {
+ deleted_list.insert(mFetchingHistory[i].mID);
+ mTextureCache->removeFromCache(mFetchingHistory[i].mID);
+ }
+ }
+ }
+}
+
+void LLTextureFetchDebugger::debugCacheWrite()
+{
+ //remove from cache
+ clearCache();
+
+ lockCache();
+ llassert_always(mDebuggerState == IDLE);
+ mTimer.reset();
+ setDebuggerState(WRITE_CACHE);
+ mCacheWriteTime = -1.f;
+
+ S32 size = mFetchingHistory.size();
+ for(S32 i = 0 ; i < size ; i++)
+ {
+ if(mFetchingHistory[i].mFormattedImage.notNull())
+ {
+ mFetchingHistory[i].mCacheHandle = mTextureCache->writeToCache(mFetchingHistory[i].mID, LLWorkerThread::PRIORITY_NORMAL,
+ mFetchingHistory[i].mFormattedImage->getData(), mFetchingHistory[i].mFetchedSize,
+ mFetchingHistory[i].mDecodedLevel == 0 ? mFetchingHistory[i].mFetchedSize : mFetchingHistory[i].mFetchedSize + 1,
+ NULL, 0, new LLDebuggerCacheWriteResponder(this, i));
+ }
+ }
+}
+
+void LLTextureFetchDebugger::lockDecoder()
+{
+}
+
+void LLTextureFetchDebugger::unlockDecoder()
+{
+}
+
+void LLTextureFetchDebugger::debugDecoder()
+{
+ lockDecoder();
+ llassert_always(mDebuggerState == IDLE);
+ mTimer.reset();
+ setDebuggerState(DECODING);
+ mDecodingTime = -1.f;
+
+ S32 size = mFetchingHistory.size();
+ for(S32 i = 0 ; i < size ; i++)
+ {
+ if(mFetchingHistory[i].mFormattedImage.isNull())
+ {
+ continue;
+ }
+
+ mImageDecodeThread->decodeImage(mFetchingHistory[i].mFormattedImage, LLWorkerThread::PRIORITY_NORMAL,
+ mFetchingHistory[i].mDecodedLevel, mFetchingHistory[i].mNeedsAux,
+ new LLDebuggerDecodeResponder(this, i));
+ }
+}
+
+void LLTextureFetchDebugger::debugHTTP()
+{
+ llassert_always(mDebuggerState == IDLE);
+
+ LLViewerRegion* region = gAgent.getRegion();
+ if (!region)
+ {
+ LL_INFOS(LOG_TXT) << "Fetch Debugger : Current region undefined. Cannot fetch textures through HTTP." << LL_ENDL;
+ return;
+ }
+
+ mHTTPUrl = region->getHttpUrl();
+ if (mHTTPUrl.empty())
+ {
+ LL_INFOS(LOG_TXT) << "Fetch Debugger : Current region URL undefined. Cannot fetch textures through HTTP." << LL_ENDL;
+ return;
+ }
+
+ mTimer.reset();
+ setDebuggerState(HTTP_FETCHING);
+ mHTTPTime = -1.f;
+
+ S32 size = mFetchingHistory.size();
+ for (S32 i = 0 ; i < size ; i++)
+ {
+ mFetchingHistory[i].mCurlState = FetchEntry::CURL_NOT_DONE;
+ mFetchingHistory[i].mCurlReceivedSize = 0;
+ mFetchingHistory[i].mFormattedImage = NULL;
+ }
+ mNbCurlRequests = 0;
+ mNbCurlCompleted = 0;
+
+ fillCurlQueue();
+}
+
+S32 LLTextureFetchDebugger::fillCurlQueue()
+{
+ if(mStopDebug) //stop
+ {
+ mNbCurlCompleted = mFetchingHistory.size();
+ return 0;
+ }
+ if (mNbCurlRequests > HTTP_NONPIPE_REQUESTS_LOW_WATER)
+ {
+ return mNbCurlRequests;
+ }
+
+ S32 size = mFetchingHistory.size();
+ for (S32 i = 0 ; i < size ; i++)
+ {
+ if (mFetchingHistory[i].mCurlState != FetchEntry::CURL_NOT_DONE)
+ {
+ continue;
+ }
+ std::string texture_url = mHTTPUrl + "/?texture_id=" + mFetchingHistory[i].mID.asString().c_str();
+ S32 requestedSize = mFetchingHistory[i].mRequestedSize;
+ // We request the whole file if the size was not set.
+ requestedSize = llmax(0,requestedSize);
+ // We request the whole file if the size was set to an absurdly high value (meaning all file)
+ requestedSize = (requestedSize == 33554432 ? 0 : requestedSize);
+
+ LLCore::HttpHandle handle = mFetcher->getHttpRequest().requestGetByteRange(mHttpPolicyClass,
+ LLWorkerThread::PRIORITY_LOWBITS,
+ texture_url,
+ 0,
+ requestedSize,
+ NULL,
+ mHttpHeaders,
+ this);
+ if (LLCORE_HTTP_HANDLE_INVALID != handle)
+ {
+ mHandleToFetchIndex[handle] = i;
+ mFetchingHistory[i].mHttpHandle = handle;
+ mFetchingHistory[i].mCurlState = FetchEntry::CURL_IN_PROGRESS;
+ mNbCurlRequests++;
+ if (mNbCurlRequests >= HTTP_NONPIPE_REQUESTS_HIGH_WATER) // emulate normal pipeline
+ {
+ break;
+ }
+ }
+ else
+ {
+ // Failed to queue request, log it and mark it done.
+ LLCore::HttpStatus status(mFetcher->getHttpRequest().getStatus());
+
+ LL_WARNS(LOG_TXT) << "Couldn't issue HTTP request in debugger for texture "
+ << mFetchingHistory[i].mID
+ << ", status: " << status.toTerseString()
+ << " reason: " << status.toString()
+ << LL_ENDL;
+ mFetchingHistory[i].mCurlState = FetchEntry::CURL_DONE;
+ }
+ }
+ //LL_INFOS(LOG_TXT) << "Fetch Debugger : Having " << mNbCurlRequests << " requests through the curl thread." << LL_ENDL;
+ return mNbCurlRequests;
+}
+
+void LLTextureFetchDebugger::debugGLTextureCreation()
+{
+ llassert_always(mDebuggerState == IDLE);
+ setDebuggerState(GL_TEX);
+ mTempTexList.clear();
+
+ S32 size = mFetchingHistory.size();
+ for(S32 i = 0 ; i < size ; i++)
+ {
+ if(mFetchingHistory[i].mRawImage.notNull())
+ {
+ LLViewerFetchedTexture* tex = gTextureList.findImage(mFetchingHistory[i].mID) ;
+ if(tex && !tex->isForSculptOnly())
+ {
+ tex->destroyGLTexture() ;
+ mTempTexList.push_back(tex);
+ }
+ }
+ }
+
+ mGLCreationTime = -1.f;
+ mTempIndex = 0;
+ mHistoryListIndex = 0;
+
+ return;
+}
+
+bool LLTextureFetchDebugger::processGLCreation(F32 max_time)
+{
+ mTimer.reset();
+
+ bool done = true;
+ S32 size = mFetchingHistory.size();
+ S32 size1 = mTempTexList.size();
+ for(; mHistoryListIndex < size && mTempIndex < size1; mHistoryListIndex++)
+ {
+ if(mFetchingHistory[mHistoryListIndex].mRawImage.notNull())
+ {
+ if(mFetchingHistory[mHistoryListIndex].mID == mTempTexList[mTempIndex]->getID())
+ {
+ mTempTexList[mTempIndex]->createGLTexture(mFetchingHistory[mHistoryListIndex].mDecodedLevel,
+ mFetchingHistory[mHistoryListIndex].mRawImage, 0, TRUE, mTempTexList[mTempIndex]->getBoostLevel());
+ mTempIndex++;
+ }
+ }
+
+ if(mTimer.getElapsedTimeF32() > max_time)
+ {
+ done = false;
+ break;
+ }
+ }
+
+ if(mGLCreationTime < 0.f)
+ {
+ mGLCreationTime = mTimer.getElapsedTimeF32() ;
+ }
+ else
+ {
+ mGLCreationTime += mTimer.getElapsedTimeF32() ;
+ }
+
+ return done;
+}
+
+//clear fetching results of all textures.
+void LLTextureFetchDebugger::clearTextures()
+{
+ S32 size = mFetchingHistory.size();
+ for(S32 i = 0 ; i < size ; i++)
+ {
+ LLViewerFetchedTexture* tex = gTextureList.findImage(mFetchingHistory[i].mID) ;
+ if(tex)
+ {
+ tex->clearFetchedResults() ;
+ }
+ }
+}
+
+void LLTextureFetchDebugger::makeRefetchList()
+{
+ mRefetchList.clear();
+ S32 size = mFetchingHistory.size();
+ for(S32 i = 0 ; i < size; i++)
+ {
+ LLViewerFetchedTexture* tex = LLViewerTextureManager::getFetchedTexture(mFetchingHistory[i].mID);
+ if(tex && tex->isJustBound()) //visible
+ {
+ continue; //the texture fetch pipeline will take care of visible textures.
+ }
+
+ mRefetchList[tex].push_back(i);
+ }
+}
+
+void LLTextureFetchDebugger::scanRefetchList()
+{
+ if(mStopDebug)
+ {
+ return;
+ }
+ if(!mRefetchNonVis)
+ {
+ return;
+ }
+
+ for(std::map< LLPointer<LLViewerFetchedTexture>, std::vector<S32> >::iterator iter = mRefetchList.begin();
+ iter != mRefetchList.end(); )
+ {
+ if(iter->second.empty())
+ {
+ gTextureList.setDebugFetching(iter->first, -1);
+ mRefetchList.erase(iter++); // This is the correct method to "erase and move on" in an std::map
+ }
+ else
+ {
+ gTextureList.setDebugFetching(iter->first, mFetchingHistory[iter->second[0]].mDecodedLevel);
+ ++iter;
+ }
+ }
+}
+
+void LLTextureFetchDebugger::debugRefetchVisibleFromCache()
+{
+ llassert_always(mDebuggerState == IDLE);
+ setDebuggerState(REFETCH_VIS_CACHE);
+
+ clearTextures();
+ mFetcher->setLoadSource(LLTextureFetch::FROM_ALL);
+
+ mTimer.reset();
+ mFetcher->lockFetcher(false);
+ mRefetchVisCacheTime = -1.f;
+ mRefetchedVisData = 0;
+ mRefetchedVisPixels = 0;
+}
+
+void LLTextureFetchDebugger::debugRefetchVisibleFromHTTP()
+{
+ llassert_always(mDebuggerState == IDLE);
+ setDebuggerState(REFETCH_VIS_HTTP);
+
+ clearTextures();
+ mFetcher->setLoadSource(LLTextureFetch::FROM_HTTP_ONLY);
+
+ mTimer.reset();
+ mFetcher->lockFetcher(false);
+ mRefetchVisHTTPTime = -1.f;
+ mRefetchedVisData = 0;
+ mRefetchedVisPixels = 0;
+}
+
+void LLTextureFetchDebugger::debugRefetchAllFromCache()
+{
+ llassert_always(mDebuggerState == IDLE);
+ setDebuggerState(REFETCH_ALL_CACHE);
+
+ clearTextures();
+ makeRefetchList();
+ mFetcher->setLoadSource(LLTextureFetch::FROM_ALL);
+
+ mTimer.reset();
+ mFetcher->lockFetcher(false);
+ mRefetchAllCacheTime = -1.f;
+ mRefetchedAllData = 0;
+ mRefetchedAllPixels = 0;
+ mRefetchNonVis = FALSE;
+}
+
+void LLTextureFetchDebugger::debugRefetchAllFromHTTP()
+{
+ llassert_always(mDebuggerState == IDLE);
+ setDebuggerState(REFETCH_ALL_HTTP);
+
+ clearTextures();
+ makeRefetchList();
+ mFetcher->setLoadSource(LLTextureFetch::FROM_HTTP_ONLY);
+
+ mTimer.reset();
+ mFetcher->lockFetcher(false);
+ mRefetchAllHTTPTime = -1.f;
+ mRefetchedAllData = 0;
+ mRefetchedAllPixels = 0;
+ mRefetchNonVis = TRUE;
+}
+
+bool LLTextureFetchDebugger::update(F32 max_time)
+{
+ switch(mDebuggerState)
+ {
+ case START_DEBUG:
+ if(processStartDebug(max_time))
+ {
+ setDebuggerState(IDLE);
+ }
+ break;
+ case READ_CACHE:
+ if(!mTextureCache->update(1))
+ {
+ mCacheReadTime = mTimer.getElapsedTimeF32() ;
+ setDebuggerState(IDLE);
+ unlockCache();
+ }
+ break;
+ case WRITE_CACHE:
+ if(!mTextureCache->update(1))
+ {
+ mCacheWriteTime = mTimer.getElapsedTimeF32() ;
+ setDebuggerState(IDLE);
+ unlockCache();
+ }
+ break;
+ case DECODING:
+ if(!mImageDecodeThread->update(1))
+ {
+ mDecodingTime = mTimer.getElapsedTimeF32() ;
+ setDebuggerState(IDLE);
+ unlockDecoder();
+ }
+ break;
+ case HTTP_FETCHING:
+ // Do some notifications...
+ mFetcher->getHttpRequest().update(10);
+ if (!fillCurlQueue() && mNbCurlCompleted == mFetchingHistory.size())
+ {
+ mHTTPTime = mTimer.getElapsedTimeF32() ;
+ setDebuggerState(IDLE);
+ }
+ break;
+ case GL_TEX:
+ if(processGLCreation(max_time))
+ {
+ setDebuggerState(IDLE);
+ mTempTexList.clear();
+ }
+ break;
+ case REFETCH_VIS_CACHE:
+ if (LLAppViewer::getTextureFetch()->getNumRequests() == 0)
+ {
+ mRefetchVisCacheTime = mTimer.getElapsedTimeF32() ;
+ setDebuggerState(IDLE);
+ mFetcher->lockFetcher(true);
+ mFetcher->resetLoadSource();
+ }
+ break;
+ case REFETCH_VIS_HTTP:
+ if (LLAppViewer::getTextureFetch()->getNumRequests() == 0)
+ {
+ mRefetchVisHTTPTime = mTimer.getElapsedTimeF32() ;
+ setDebuggerState(IDLE);
+ mFetcher->lockFetcher(true);
+ mFetcher->resetLoadSource();
+ }
+ break;
+ case REFETCH_ALL_CACHE:
+ scanRefetchList();
+ if (LLAppViewer::getTextureFetch()->getNumRequests() == 0)
+ {
+ if(!mRefetchNonVis)
+ {
+ mRefetchNonVis = TRUE; //start to fetch non-vis
+ scanRefetchList();
+ break;
+ }
+
+ mRefetchAllCacheTime = mTimer.getElapsedTimeF32() ;
+ setDebuggerState(IDLE);
+ mFetcher->lockFetcher(true);
+ mFetcher->resetLoadSource();
+ mRefetchList.clear();
+ mRefetchNonVis = FALSE;
+ }
+ break;
+ case REFETCH_ALL_HTTP:
+ scanRefetchList();
+ if (LLAppViewer::getTextureFetch()->getNumRequests() == 0)
+ {
+ mRefetchAllHTTPTime = mTimer.getElapsedTimeF32() ;
+ setDebuggerState(IDLE);
+ mFetcher->lockFetcher(true);
+ mFetcher->resetLoadSource();
+ mRefetchList.clear();
+ mRefetchNonVis = FALSE;
+ }
+ break;
+ default:
+ setDebuggerState(IDLE);
+ break;
+ }
+
+ return mDebuggerState == IDLE;
+}
+
+void LLTextureFetchDebugger::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response)
+{
+ handle_fetch_map_t::iterator iter(mHandleToFetchIndex.find(handle));
+ if (mHandleToFetchIndex.end() == iter)
+ {
+ LL_INFOS(LOG_TXT) << "Fetch Debugger : Couldn't find handle " << handle << " in fetch list." << LL_ENDL;
+ return;
+ }
+
+ S32 fetch_ind(iter->second);
+ mHandleToFetchIndex.erase(iter);
+ if (fetch_ind >= mFetchingHistory.size() || mFetchingHistory[fetch_ind].mHttpHandle != handle)
+ {
+ LL_INFOS(LOG_TXT) << "Fetch Debugger : Handle and fetch object in disagreement. Punting." << LL_ENDL;
+ }
+ else
+ {
+ callbackHTTP(mFetchingHistory[fetch_ind], response);
+ mFetchingHistory[fetch_ind].mHttpHandle = LLCORE_HTTP_HANDLE_INVALID; // Not valid after notification
+ }
+}
+
+void LLTextureFetchDebugger::callbackCacheRead(S32 id, bool success, LLImageFormatted* image,
+ S32 imagesize, BOOL islocal)
+{
+ if (success)
+ {
+ mFetchingHistory[id].mFormattedImage = image;
+ }
+ mTextureCache->readComplete(mFetchingHistory[id].mCacheHandle, false);
+ mFetchingHistory[id].mCacheHandle = LLTextureCache::nullHandle();
+}
+
+void LLTextureFetchDebugger::callbackCacheWrite(S32 id, bool success)
+{
+ mTextureCache->writeComplete(mFetchingHistory[id].mCacheHandle);
+ mFetchingHistory[id].mCacheHandle = LLTextureCache::nullHandle();
+}
+
+void LLTextureFetchDebugger::callbackDecoded(S32 id, bool success, LLImageRaw* raw, LLImageRaw* aux)
+{
+ if (success)
+ {
+ llassert_always(raw);
+ mFetchingHistory[id].mRawImage = raw;
+ }
+}
+
+void LLTextureFetchDebugger::callbackHTTP(FetchEntry & fetch, LLCore::HttpResponse * response)
+{
+ static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT);
+
+ LLCore::HttpStatus status(response->getStatus());
+ mNbCurlRequests--;
+ mNbCurlCompleted++;
+ fetch.mCurlState = FetchEntry::CURL_DONE;
+ if (status)
+ {
+ const bool partial(par_status == status);
+ LLCore::BufferArray * ba(response->getBody()); // *Not* holding reference to body
+
+ S32 data_size = ba ? ba->size() : 0;
+ fetch.mCurlReceivedSize += data_size;
+ //LL_INFOS(LOG_TXT) << "Fetch Debugger : got results for " << fetch.mID << ", data_size = " << data_size << ", received = " << fetch.mCurlReceivedSize << ", requested = " << fetch.mRequestedSize << ", partial = " << partial << LL_ENDL;
+ if ((fetch.mCurlReceivedSize >= fetch.mRequestedSize) || !partial || (fetch.mRequestedSize == 600))
+ {
+ U8* d_buffer = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), data_size);
+ if (ba)
+ {
+ ba->read(0, d_buffer, data_size);
+ }
+
+ llassert_always(fetch.mFormattedImage.isNull());
+ {
+ // For now, create formatted image based on extension
+ std::string texture_url = mHTTPUrl + "/?texture_id=" + fetch.mID.asString().c_str();
+ std::string extension = gDirUtilp->getExtension(texture_url);
+ fetch.mFormattedImage = LLImageFormatted::createFromType(LLImageBase::getCodecFromExtension(extension));
+ if (fetch.mFormattedImage.isNull())
+ {
+ fetch.mFormattedImage = new LLImageJ2C; // default
+ }
+ }
+
+ fetch.mFormattedImage->setData(d_buffer, data_size);
+ }
+ }
+ else //failed
+ {
+ LL_INFOS(LOG_TXT) << "Fetch Debugger : CURL GET FAILED, ID = " << fetch.mID
+ << ", status: " << status.toTerseString()
+ << " reason: " << status.toString() << LL_ENDL;
+ }
+}
+
+
+//---------------------
+///////////////////////////////////////////////////////////////////////////////////////////
+//End LLTextureFetchDebugger
+///////////////////////////////////////////////////////////////////////////////////////////
+
+