Files
SingularityViewer/indra/newview/lltexturefetch.cpp
Aleric Inglewood b9b5f13624 Run HTTPGetResponder in any thread.
This fixes a bug where unref() was called when a state machine was
aborted before it reached bs_initialized. Debug code was added to detect
errors related to that.

In order to run HTTPGetResponder in any thread, I needed direct access
to LLHTTPClient::request, so I had to move that to the header file,
and therefore had to move ERequestAction from LLURLRequest to
LLHTTPClient to avoid include problems.

With this, textures are fetched with no latency: call to
LLHTTPClient::request runs all the way till the state machine is idle
(AICurlEasyRequestStateMachine_waitAdded). There is small delay till the
curl thread wakes up, which then processes the request and opens the url
etc. When the transaction is finished, it calls
AIStateMachine::advance_state(AICurlEasyRequestStateMachine_removed_after_finished)
which subsequently doesn't return until the state machine is completely
finished (bs_killed). The LLURLRequest isn't deleted yet at that point
because the AITimer of the LLURLRequest runs in the main thread: it is
aborted, but only the next time the main thread state engines run that
is deleted and the timer keeps an LLPointer to it's parent, the
LLURLRequest, so only then the LLURLRequest object is destructed. This
however has nothing to do with the texture-bandwidth loop.
2013-03-07 01:52:21 +01:00

2666 lines
73 KiB
C++

/**
* @file lltexturefetch.cpp
* @brief Object which fetches textures from the cache and/or network
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, 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.
*
* 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.
*
* 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
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include <iostream>
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>
#include "llstl.h"
#include "message.h"
#include "lltexturefetch.h"
#include "llcurl.h"
#include "lldir.h"
#include "llhttpclient.h"
#include "llhttpstatuscodes.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 "llworld.h"
#include "llstartup.h"
#include "llbuffer.h"
class AIHTTPTimeoutPolicy;
extern AIHTTPTimeoutPolicy HTTPGetResponder_timeout;
extern AIHTTPTimeoutPolicy lcl_responder_timeout;
LLStat LLTextureFetch::sCacheHitRate("texture_cache_hits", 128);
LLStat LLTextureFetch::sCacheReadLatency("texture_cache_read_latency", 128);
//////////////////////////////////////////////////////////////////////////////
class LLTextureFetchWorker : public LLWorkerClass
{
friend class LLTextureFetch;
friend class HTTPGetResponder;
private:
class CacheReadResponder : public LLTextureCache::ReadResponder
{
public:
CacheReadResponder(LLTextureFetch* fetcher, const LLUUID& id, LLImageFormatted* image)
: mFetcher(fetcher), mID(id)
{
setImage(image);
}
virtual void completed(bool success)
{
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
if (worker)
{
worker->callbackCacheRead(success, mFormattedImage, mImageSize, mImageLocal);
}
}
private:
LLTextureFetch* mFetcher;
LLUUID mID;
};
class CacheWriteResponder : public LLTextureCache::WriteResponder
{
public:
CacheWriteResponder(LLTextureFetch* fetcher, const LLUUID& id)
: mFetcher(fetcher), mID(id)
{
}
virtual void completed(bool success)
{
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
if (worker)
{
worker->callbackCacheWrite(success);
}
}
private:
LLTextureFetch* mFetcher;
LLUUID mID;
};
class DecodeResponder : public LLImageDecodeThread::Responder
{
public:
DecodeResponder(LLTextureFetch* fetcher, const LLUUID& id, LLTextureFetchWorker* worker)
: mFetcher(fetcher), mID(id), mWorker(worker)
{
}
virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux)
{
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
if (worker)
{
worker->callbackDecoded(success, raw, aux);
}
}
private:
LLTextureFetch* mFetcher;
LLUUID mID;
LLTextureFetchWorker* mWorker; // debug only (may get deleted from under us, use mFetcher/mID)
};
struct Compare
{
// lhs < rhs
bool operator()(const LLTextureFetchWorker* lhs, const LLTextureFetchWorker* rhs) const
{
// greater priority is "less"
const F32 lpriority = lhs->mImagePriority;
const F32 rpriority = rhs->mImagePriority;
if (lpriority > rpriority) // higher priority
return true;
else if (lpriority < rpriority)
return false;
else
return lhs < rhs;
}
};
public:
/*virtual*/ bool doWork(S32 param); // Called from LLWorkerThread::processRequest()
/*virtual*/ void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD)
/*virtual*/ bool deleteOK(); // called from update() (WORK THREAD)
~LLTextureFetchWorker();
// void relese() { --mActiveCount; }
S32 callbackHttpGet(const LLChannelDescriptors& channels,
const LLHTTPClient::ResponderBase::buffer_ptr_t& buffer,
bool partial, bool success);
void callbackCacheRead(bool success, LLImageFormatted* image,
S32 imagesize, BOOL islocal);
void callbackCacheWrite(bool success);
void callbackDecoded(bool success, LLImageRaw* raw, LLImageRaw* aux);
void setGetStatus(U32 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; }
LLTextureFetch & getFetcher() { return *mFetcher; }
protected:
LLTextureFetchWorker(LLTextureFetch* fetcher, const std::string& url, const LLUUID& id, const LLHost& host,
F32 priority, S32 discard, S32 size);
private:
/*virtual*/ void startWork(S32 param); // called from addWork() (MAIN THREAD)
/*virtual*/ void endWork(S32 param, bool aborted); // called from doWork() (MAIN THREAD)
void resetFormattedData();
void setImagePriority(F32 priority);
void setDesiredDiscard(S32 discard, S32 size);
bool insertPacket(S32 index, U8* data, S32 size);
void clearPackets();
void setupPacketData();
U32 calcWorkPriority();
void removeFromCache();
bool processSimulatorPackets();
bool writeToCacheComplete();
void lockWorkMutex() { mWorkMutex.lock(); }
void unlockWorkMutex() { mWorkMutex.unlock(); }
private:
enum e_state // mState
{
// NOTE: Affects LLTextureBar::draw in lltextureview.cpp (debug hack)
INVALID = 0,
INIT,
LOAD_FROM_TEXTURE_CACHE,
CACHE_POST,
LOAD_FROM_NETWORK,
LOAD_FROM_SIMULATOR,
SEND_HTTP_REQ,
WAIT_HTTP_REQ,
DECODE_IMAGE,
DECODE_IMAGE_UPDATE,
WRITE_TO_CACHE,
WAIT_ON_WRITE,
DONE
};
enum e_request_state // mSentRequest
{
UNSENT = 0,
QUEUED = 1,
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;
e_write_to_cache_state mWriteToCacheState;
LLTextureFetch* mFetcher;
LLPointer<LLImageFormatted> mFormattedImage;
LLPointer<LLImageRaw> mRawImage;
LLPointer<LLImageRaw> mAuxImage;
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;
LLTimer mCacheReadTimer;
F32 mCacheReadTime;
LLTextureCache::handle_t mCacheReadHandle;
LLTextureCache::handle_t mCacheWriteHandle;
std::vector<U8> mHttpBuffer;
S32 mRequestedSize;
S32 mRequestedOffset;
S32 mDesiredSize;
S32 mFileSize;
S32 mCachedSize;
e_request_state mSentRequest;
handle_t mDecodeHandle;
BOOL mLoaded;
BOOL mDecoded;
BOOL mWritten;
BOOL mNeedsAux;
BOOL mHaveAllData;
BOOL mInLocalCache;
bool mCanUseHTTP ;
bool mCanUseNET ; //can get from asset server.
S32 mHTTPFailCount;
S32 mRetryAttempt;
S32 mActiveCount;
U32 mGetStatus;
std::string mGetReason;
// Work Data
LLMutex mWorkMutex;
struct PacketData
{
PacketData(U8* data, S32 size) { mData = data; mSize = size; }
~PacketData() { clearData(); }
void clearData() { delete[] mData; mData = NULL; }
U8* mData;
U32 mSize;
};
std::vector<PacketData*> mPackets;
S32 mFirstPacket;
S32 mLastPacket;
U16 mTotalPackets;
U8 mImageCodec;
};
//////////////////////////////////////////////////////////////////////////////
class HTTPGetResponder : public LLHTTPClient::ResponderWithCompleted
{
LOG_CLASS(HTTPGetResponder);
public:
HTTPGetResponder(LLTextureFetch* fetcher, const LLUUID& id, U64 startTime, S32 requestedSize, U32 offset, bool redir)
: mFetcher(fetcher)
, mID(id)
, mStartTime(startTime)
, mRequestedSize(requestedSize)
, mRequestedOffset(offset)
, mFollowRedir(redir)
{
}
~HTTPGetResponder()
{
}
#if 0 //Apparently, SL never sends content-range and instead sends transfer-encoding: chunked, so disabling for now
/*virtual*/ bool needsHeaders(void) const { return true; }
/*virtual*/ void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers) {
llinfos << "Texture fetch HTTP status: " << status << llendl;
llinfos << "Texture fetch headers: " << headers << llendl;
//example: Content-Range: 1000-3979/3980 Content-Length: 2980
static const boost::regex pattern("\\w*bytes\\w+(\\d+)-(\\d+)/(\\d+)");
std::string rangehdr;
if (headers.getFirstValue("content-range", rangehdr)){
llinfos << "Have content-range header" <<llendl;
boost::smatch match;
llassert_always(boost::regex_match(rangehdr, match, pattern));
llassert(match.length() == 3);
std::string lengthhdr;
if(headers.getFirstValue("content-length",lengthhdr)) {
//insert length checking
}
}
}
#endif
/*virtual*/ void completedRaw(U32 status, const std::string& reason,
const LLChannelDescriptors& channels,
const buffer_ptr_t& buffer)
{
static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog");
static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator");
static LLCachedControl<bool> log_texture_traffic(gSavedSettings,"LogTextureNetworkTraffic") ;
if (log_to_viewer_log || log_to_sim)
{
mFetcher->mTextureInfo.setRequestStartTime(mID, mStartTime);
U64 timeNow = LLTimer::getTotalTime();
mFetcher->mTextureInfo.setRequestType(mID, LLTextureInfoDetails::REQUEST_TYPE_HTTP);
mFetcher->mTextureInfo.setRequestSize(mID, mRequestedSize);
mFetcher->mTextureInfo.setRequestOffset(mID, mRequestedOffset);
mFetcher->mTextureInfo.setRequestCompleteTimeAndLog(mID, timeNow);
}
LL_DEBUGS("Texture") << "HTTP COMPLETE: " << mID << LL_ENDL;
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
if (worker)
{
bool success = false;
bool partial = false;
if (HTTP_OK <= status && status < HTTP_MULTIPLE_CHOICES)
{
success = true;
if (HTTP_PARTIAL_CONTENT == status) // partial information
{
partial = true;
}
}
if (!success)
{
worker->setGetStatus(status, reason);
// llwarns << "CURL GET FAILED, status:" << status << " reason:" << reason << llendl;
}
S32 data_size = worker->callbackHttpGet(channels, buffer, 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);
}
else
{
mFetcher->removeFromHTTPQueue(mID);
llwarns << "Worker not found: " << mID << llendl;
}
}
/*virtual*/ bool followRedir() const
{
return mFollowRedir;
}
/*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return HTTPGetResponder_timeout; }
/*virtual*/ char const* getName(void) const { return "HTTPGetResponder"; }
private:
LLTextureFetch* mFetcher;
LLUUID mID;
U64 mStartTime;
S32 mRequestedSize;
U32 mRequestedOffset;
bool mFollowRedir;
};
//////////////////////////////////////////////////////////////////////////////
class SGHostBlackList{
static const int MAX_ERRORCOUNT = 20;
struct BlackListEntry {
std::string host;
U64 timeUntil;
U32 reason;
int errorCount;
};
typedef std::vector<BlackListEntry> blacklist_t;
//Why is it a vector? because using std::map for just 1-2 values is insane-ish.
typedef blacklist_t::iterator iter;
static blacklist_t blacklist;
static bool is_obsolete(BlackListEntry entry) {
U64 now = LLTimer::getTotalTime();
return (now > entry.timeUntil);
} //should make a functor. if i cared.
static void cleanup() {
std::remove_if(blacklist.begin(), blacklist.end(), is_obsolete);
}
static iter find(std::string host) {
cleanup();
for(blacklist_t::iterator i = blacklist.begin(); i != blacklist.end(); ++i) {
if (i->host.find(host) == 0) return i;
}
return blacklist.end();
}
public:
static bool isBlacklisted(std::string url) {
iter found = find(url);
bool r = (found != blacklist.end()) && (found->errorCount > MAX_ERRORCOUNT);
return r;
}
static void add(std::string url, float timeout, U32 reason) {
llwarns << "Requested adding to blacklist: " << url << llendl;
BlackListEntry entry;
entry.host = url.substr(0, url.rfind("/"));
if (entry.host.empty()) return;
entry.timeUntil = LLTimer::getTotalTime() + timeout*1000;
entry.reason = reason;
entry.errorCount = 0;
iter found = find(entry.host);
if(found != blacklist.end()) {
entry.errorCount = found->errorCount + 1;
*found = entry;
if (entry.errorCount > MAX_ERRORCOUNT) {
std::string s;
microsecondsToTimecodeString(entry.timeUntil, s);
llwarns << "Blacklisting address " << entry.host
<< "is blacklisted for " << timeout
<< " seconds because of error " << reason
<< llendl;
}
}
else blacklist.push_back(entry);
}
};
SGHostBlackList::blacklist_t SGHostBlackList::blacklist;
#if 0
//call every time a connection is opened
//return true if connecting allowed
static bool sgConnectionThrottle() {
const U32 THROTTLE_TIMESTEPS_PER_SECOND = 10;
static const LLCachedControl<U32> max_connections_per_second("HTTPRequestRate", 30);
U32 max_connections = max_connections_per_second/THROTTLE_TIMESTEPS_PER_SECOND;
const U32 timestep = USEC_PER_SEC/THROTTLE_TIMESTEPS_PER_SECOND;
U64 now = LLTimer::getTotalTime();
std::deque<U64> timestamps;
while(!timestamps.empty() && (timestamps[0]<=now-timestep)) {
timestamps.pop_front();
}
if(timestamps.size() < max_connections) {
//llinfos << "throttle pass" << llendl;
timestamps.push_back(now);
return true;
} else {
//llinfos << "throttle fail" << llendl;
return false;
}
}
#endif
//////////////////////////////////////////////////////////////////////////////
//static
const char* LLTextureFetchWorker::sStateDescs[] = {
"INVALID",
"INIT",
"LOAD_FROM_TEXTURE_CACHE",
"CACHE_POST",
"LOAD_FROM_NETWORK",
"LOAD_FROM_SIMULATOR",
"SEND_HTTP_REQ",
"WAIT_HTTP_REQ",
"DECODE_IMAGE",
"DECODE_IMAGE_UPDATE",
"WRITE_TO_CACHE",
"WAIT_ON_WRITE",
"DONE",
};
// called from MAIN THREAD
LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher,
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"),
mState(INIT),
mWriteToCacheState(NOT_WRITE),
mFetcher(fetcher),
mID(id),
mHost(host),
mUrl(url),
mImagePriority(priority),
mWorkPriority(0),
mRequestedPriority(0.f),
mDesiredDiscard(-1),
mSimRequestedDiscard(-1),
mRequestedDiscard(-1),
mLoadedDiscard(-1),
mDecodedDiscard(-1),
mCacheReadTime(0.f),
mCacheReadHandle(LLTextureCache::nullHandle()),
mCacheWriteHandle(LLTextureCache::nullHandle()),
mRequestedSize(0),
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),
mCanUseHTTP(true),
mHTTPFailCount(0),
mRetryAttempt(0),
mActiveCount(0),
mGetStatus(0),
mFirstPacket(0),
mLastPacket(-1),
mTotalPackets(0),
mImageCodec(IMG_CODEC_INVALID)
{
mCanUseNET = mUrl.empty() ;
calcWorkPriority();
mType = host.isOk() ? LLImageBase::TYPE_AVATAR_BAKE : LLImageBase::TYPE_NORMAL;
//llinfos << "Create: " << mID << " mHost:" << host << " Discard=" << discard << " URL:"<< mUrl << llendl;
if (!mFetcher->mDebugPause)
{
U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
addWork(0, work_priority );
}
setDesiredDiscard(discard, size);
}
LLTextureFetchWorker::~LLTextureFetchWorker()
{
// llinfos << "Destroy: " << mID
// << " Decoded=" << mDecodedDiscard
// << " Requested=" << mRequestedDiscard
// << " Desired=" << mDesiredDiscard << llendl;
llassert_always(!haveWork());
lockWorkMutex();
if (mCacheReadHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache)
{
mFetcher->mTextureCache->readComplete(mCacheReadHandle, true);
}
if (mCacheWriteHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache)
{
mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true);
}
mFormattedImage = NULL;
clearPackets();
unlockWorkMutex();
mFetcher->removeFromHTTPQueue(mID);
}
void LLTextureFetchWorker::clearPackets()
{
for_each(mPackets.begin(), mPackets.end(), DeletePointer());
mPackets.clear();
mTotalPackets = 0;
mLastPacket = -1;
mFirstPacket = 0;
}
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)
{
llwarns << "Bad CACHED TEXTURE size: " << data_size << " removing." << llendl;
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();
}
}
}
U32 LLTextureFetchWorker::calcWorkPriority()
{
// llassert_always(mImagePriority >= 0 && mImagePriority <= LLViewerTexture::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
void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size)
{
bool prioritize = false;
if (mDesiredDiscard != discard)
{
if (!haveWork())
{
calcWorkPriority();
if (!mFetcher->mDebugPause)
{
U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
addWork(0, work_priority);
}
}
else if (mDesiredDiscard < discard)
{
prioritize = true;
}
mDesiredDiscard = discard;
mDesiredSize = size;
}
else if (size > mDesiredSize)
{
mDesiredSize = size;
prioritize = true;
}
mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE);
if ((prioritize && mState == INIT) || mState == DONE)
{
mState = INIT;
U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
setPriority(work_priority);
}
}
void LLTextureFetchWorker::setImagePriority(F32 priority)
{
// llassert_always(priority >= 0 && priority <= LLViewerTexture::maxDecodePriority());
F32 delta = fabs(priority - mImagePriority);
if (delta > (mImagePriority * .05f) || mState == DONE)
{
mImagePriority = priority;
calcWorkPriority();
U32 work_priority = mWorkPriority | (getPriority() & LLWorkerThread::PRIORITY_HIGHBITS);
setPriority(work_priority);
}
}
void LLTextureFetchWorker::resetFormattedData()
{
std::vector<U8>().swap(mHttpBuffer);
if (mFormattedImage.notNull())
{
mFormattedImage->deleteData();
}
mHaveAllData = FALSE;
}
// Called from MAIN thread
void LLTextureFetchWorker::startWork(S32 param)
{
llassert(mFormattedImage.isNull());
}
#include "llviewertexturelist.h" // debug
// Called from LLWorkerThread::processRequest()
bool LLTextureFetchWorker::doWork(S32 param)
{
static const F32 FETCHING_TIMEOUT = 15.f;//seconds
LLMutexLock lock(&mWorkMutex);
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)
{
return true; // abort
}
}
if(mState > CACHE_POST && !mCanUseNET && !mCanUseHTTP)
{
//nowhere to get data, abort.
return true ;
}
if (mFetcher->mDebugPause)
{
return false; // debug: don't do any work
}
if (mID == mFetcher->mDebugID)
{
mFetcher->mDebugCount++; // for setting breakpoints
}
if (mState != DONE)
{
mFetchTimer.reset();
}
if (mState == INIT)
{
if(gAssetStorage && std::find(gAssetStorage->mBlackListedAsset.begin(),
gAssetStorage->mBlackListedAsset.end(),mID) != gAssetStorage->mBlackListedAsset.end())
{
llinfos << "Blacklisted asset " << mID.asString() << " was trying to be accessed!!!!!!" << llendl;
mState = DONE;
return true;
}
mRawImage = NULL ;
mRequestedDiscard = -1;
mLoadedDiscard = -1;
mDecodedDiscard = -1;
mRequestedSize = 0;
mRequestedOffset = 0;
mFileSize = 0;
mCachedSize = 0;
mLoaded = FALSE;
mSentRequest = UNSENT;
mDecoded = FALSE;
mWritten = FALSE;
std::vector<U8>().swap(mHttpBuffer);
mHaveAllData = FALSE;
clearPackets(); // TODO: Shouldn't be necessary
mCacheReadHandle = LLTextureCache::nullHandle();
mCacheWriteHandle = LLTextureCache::nullHandle();
mState = LOAD_FROM_TEXTURE_CACHE;
mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE); // min desired size is TEXTURE_CACHE_ENTRY_SIZE
LL_DEBUGS("Texture") << mID << ": Priority: " << llformat("%8.0f",mImagePriority)
<< " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL;
// fall through
}
if (mState == LOAD_FROM_TEXTURE_CACHE)
{
if (mCacheReadHandle == LLTextureCache::nullHandle())
{
U32 cache_priority = mWorkPriority;
S32 offset = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
S32 size = mDesiredSize - offset;
if (size <= 0)
{
mState = CACHE_POST;
return false;
}
mFileSize = 0;
mLoaded = FALSE;
if (mUrl.compare(0, 7, "file://") == 0)
{
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
// read file from local disk
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())
{
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage);
mCacheReadHandle = mFetcher->mTextureCache->readFromCache(mID, cache_priority,
offset, size, responder);
mCacheReadTimer.reset();
}
else if(!mUrl.empty() && mCanUseHTTP)
{
if (!(mUrl.compare(0, 7, "http://") == 0))
{
// *TODO:?remove this warning
llwarns << "Unknown URL Type: " << mUrl << llendl;
}
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
mState = SEND_HTTP_REQ;
}
else
{
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
mState = LOAD_FROM_NETWORK;
}
}
if (mLoaded)
{
// Make sure request is complete. *TODO: make this auto-complete
if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, false))
{
mCacheReadHandle = LLTextureCache::nullHandle();
mState = CACHE_POST;
// fall through
}
else
{
//
//This should never happen
//
return false;
}
}
else
{
return false;
}
}
if (mState == CACHE_POST)
{
mCachedSize = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
// Successfully loaded
if ((mCachedSize >= mDesiredSize) || mHaveAllData)
{
// we have enough data, decode it
llassert_always(mFormattedImage->getDataSize() > 0);
mLoadedDiscard = mDesiredDiscard;
mState = DECODE_IMAGE;
mWriteToCacheState = NOT_WRITE ;
LL_DEBUGS("Texture") << mID << ": Cached. Bytes: " << mFormattedImage->getDataSize()
<< " Size: " << llformat("%dx%d",mFormattedImage->getWidth(),mFormattedImage->getHeight())
<< " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL;
LLTextureFetch::sCacheHitRate.addValue(100.f);
}
else
{
if (mUrl.compare(0, 7, "file://") == 0)
{
// failed to load local file, we're done.
return true;
}
// need more data
else
{
LL_DEBUGS("Texture") << mID << ": Not in Cache" << LL_ENDL;
mState = LOAD_FROM_NETWORK;
}
// fall through
LLTextureFetch::sCacheHitRate.addValue(0.f);
}
}
if (mState == LOAD_FROM_NETWORK)
{
static LLCachedControl<bool> use_http(gSavedSettings,"ImagePipelineUseHTTP");
// if (mHost != LLHost::invalid) use_http = 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)
{
std::string http_url = region->getHttpUrl() ;
if (!http_url.empty())
{
mUrl = http_url + "/?texture_id=" + mID.asString().c_str();
mWriteToCacheState = CAN_WRITE ; //because this texture has a fixed texture id.
}
else
{
mCanUseHTTP = false ;
}
}
else
{
// This will happen if not logged in or if a region deoes not have HTTP Texture enabled
//llwarns << "Region not found for host: " << mHost << llendl;
mCanUseHTTP = false;
}
}
if (!mUrl.empty() && SGHostBlackList::isBlacklisted(mUrl)){
mCanUseHTTP = false;
}
if (mCanUseHTTP && !mUrl.empty())
{
mState = LLTextureFetchWorker::SEND_HTTP_REQ;
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->addToNetworkQueue(this);
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);
//setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
return false;
}
}
if (mState == LOAD_FROM_SIMULATOR)
{
if (mFormattedImage.isNull())
{
mFormattedImage = new LLImageJ2C;
}
if (processSimulatorPackets())
{
LL_DEBUGS("Texture") << 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;
return true; // failed
}
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
mState = DECODE_IMAGE;
mWriteToCacheState = SHOULD_WRITE;
}
else
{
mFetcher->addToNetworkQueue(this); // failsafe
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
}
return false;
}
if (mState == SEND_HTTP_REQ)
{
if(mCanUseHTTP)
{
//NOTE:
//control the number of the http requests issued for:
//1, not opening too many file descriptors at the same time;
//2, control the traffic of http so udp gets bandwidth.
//
static const LLCachedControl<U32> max_http_requests("HTTPMaxRequests", 8);
static const LLCachedControl<U32> min_http_requests("HTTPMinRequests", 2);
static const LLCachedControl<F32> throttle_bandwidth("HTTPThrottleBandwidth", 2000);
if(((U32)mFetcher->getNumHTTPRequests() >= max_http_requests) ||
((mFetcher->getTextureBandwidth() > throttle_bandwidth) &&
((U32)mFetcher->getNumHTTPRequests() > min_http_requests)))
{
return false ; //wait.
}
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(cur_size > 0)
{
// We already have all the data, just decode it
mLoadedDiscard = mFormattedImage->getDiscardLevel();
mState = DECODE_IMAGE;
return false;
}
else
{
return true ; //abort.
}
}
}
mRequestedSize = mDesiredSize - cur_size;
mRequestedDiscard = mDesiredDiscard;
mRequestedOffset = cur_size;
bool res = false;
if (!mUrl.empty())
{
mRequestedTimer.reset();
mLoaded = FALSE;
mGetStatus = 0;
mGetReason.clear();
static const LLCachedControl<F32> throttle_bandwidth("HTTPThrottleBandwidth", 2000);
LL_DEBUGS("Texture") << "HTTP GET: " << mID << " Offset: " << mRequestedOffset
<< " Bytes: " << mRequestedSize
<< " Bandwidth(kbps): " << mFetcher->getTextureBandwidth() << "/" << throttle_bandwidth
<< LL_ENDL;
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
mState = WAIT_HTTP_REQ;
mFetcher->addToHTTPQueue(mID);
if(mRequestedOffset>0)
{
// 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*
mRequestedSize++;
mRequestedOffset--;
}
// Will call callbackHttpGet when curl request completes
AIHTTPHeaders headers("Accept", "image/x-j2c");
// Call LLHTTPClient::request directly instead of LLHTTPClient::getByteRange, because we want to pass a NULL AIEngine.
if (mRequestedOffset > 0 || mRequestedSize > 0)
{
headers.addHeader("Range", llformat("bytes=%d-%d", mRequestedOffset, mRequestedOffset + mRequestedSize - 1));
}
LLHTTPClient::request(mUrl, LLHTTPClient::HTTP_GET, NULL,
new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, mRequestedOffset, true),
headers/*,*/ DEBUG_CURLIO_PARAM(false), keep_alive, false, false, NULL, 0, NULL);
res = true;
}
if (!res)
{
llwarns << "HTTP GET request failed for " << mID << llendl;
resetFormattedData();
++mHTTPFailCount;
return true; // failed
}
// fall through
}
else //can not use http fetch.
{
return true ; //abort
}
}
if (mState == WAIT_HTTP_REQ)
{
if (mLoaded)
{
S32 cur_size = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
if (mRequestedSize < 0)
{
S32 max_attempts;
if (mGetStatus == HTTP_NOT_FOUND || mGetStatus == 499)
{
mHTTPFailCount = max_attempts = 1; // Don't retry
if(mGetStatus == HTTP_NOT_FOUND)
llwarns << "Texture missing from server (404): " << mUrl << llendl;
else if (mGetStatus == 499)
{
llwarns << "No response from server (499): " << mUrl << llendl;
SGHostBlackList::add(mUrl, 60.0, mGetStatus);
}
//roll back to try UDP
if(mCanUseNET)
{
resetFormattedData();
mState = INIT ;
mCanUseHTTP = false ;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
return false ;
}
else
{
// UDP is not an option, we are dead
resetFormattedData();
return true; // failed
}
}
else if (mGetStatus == HTTP_SERVICE_UNAVAILABLE)
{
// *TODO: Should probably introduce a timer here to delay future HTTP requsts
// for a short time (~1s) to ease server load? Ideally the server would queue
// requests instead of returning 503... we already limit the number pending.
++mHTTPFailCount;
max_attempts = mHTTPFailCount+1; // Keep retrying
LL_INFOS_ONCE("Texture") << "Texture server busy (503): " << mUrl << LL_ENDL;
}
else
{
const S32 HTTP_MAX_RETRY_COUNT = 3;
max_attempts = HTTP_MAX_RETRY_COUNT + 1;
++mHTTPFailCount;
llinfos << "HTTP GET failed for: " << mUrl
<< " Status: " << mGetStatus << " Reason: '" << mGetReason << "'"
<< " Attempt:" << mHTTPFailCount+1 << "/" << max_attempts << llendl;
}
if (mHTTPFailCount >= max_attempts)
{
// Make max_attempts attempt at decoding what data we have,
// then bail forever on this image
if (cur_size > 0 && (mHTTPFailCount < (max_attempts+1)) )
{
// Use available data
mLoadedDiscard = mFormattedImage->getDiscardLevel();
mState = DECODE_IMAGE;
return false;
}
else
{
//roll back to try UDP
if(mCanUseNET)
{
resetFormattedData();
mState = INIT ;
mCanUseHTTP = false ;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
return false ;
}
else
{
// UDP is not an option, we are dead
resetFormattedData();
mState = DONE;
return true; // failed
}
}
}
else
{
mState = SEND_HTTP_REQ;
return false; // retry
}
}
if(mHttpBuffer.empty())//no data received.
{
//abort.
mState = DONE;
return true;
}
S32 total_size(cur_size + mRequestedSize);
S32 src_offset(0);
if(mRequestedOffset && mRequestedOffset != cur_size)
{
// In case of a partial response, our offset may
// not be trivially contiguous with the data we have.
// Get back into alignment.
if (mRequestedOffset > cur_size)
{
LL_WARNS("Texture") << "Partial HTTP response produces break in image data for texture "
<< mID << ". Aborting load." << LL_ENDL;
mState = DONE;
return true;
}
src_offset = cur_size - mRequestedOffset;
total_size -= src_offset;
mRequestedSize -= src_offset; // Make requested values reflect useful part
mRequestedOffset += src_offset;
}
llassert(total_size == cur_size + mRequestedSize);
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())
{
mFormattedImage = new LLImageJ2C; // default
}
}
if (mHaveAllData && mRequestedDiscard == 0) //the image file is fully loaded.
{
mFileSize = total_size;
}
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);
}
if (mRequestedSize > 0)
{
memcpy(buffer + mRequestedOffset, &mHttpBuffer[src_offset], mRequestedSize); // append
}
// NOTE: setData releases current data and owns new data (buffer)
mFormattedImage->setData(buffer, total_size);
// delete temp data
std::vector<U8>().swap(mHttpBuffer);
mLoadedDiscard = mRequestedDiscard;
mState = DECODE_IMAGE;
if(mWriteToCacheState != NOT_WRITE)
{
mWriteToCacheState = SHOULD_WRITE ;
}
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
return false;
}
else
{
if(FETCHING_TIMEOUT < mRequestedTimer.getElapsedTimeF32())
{
//timeout, abort.
mState = DONE;
return true;
}
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
return false;
}
}
if (mState == DECODE_IMAGE)
{
static LLCachedControl<bool> textures_decode_disabled(gSavedSettings,"TextureDecodeDisabled");
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
if (textures_decode_disabled)
{
// for debug use, don't decode
mState = DONE;
return true;
}
if (mDesiredDiscard < 0)
{
// We aborted, don't decode
mState = DONE;
return true;
}
if (mFormattedImage->getDataSize() <= 0)
{
//llerrs << "Decode entered with invalid mFormattedImage. ID = " << mID << llendl;
//abort, don't decode
mState = DONE;
return true;
}
if (mLoadedDiscard < 0)
{
//llerrs << "Decode entered with invalid mLoadedDiscard. ID = " << mID << llendl;
//abort, don't decode
mState = DONE;
return true;
}
mRawImage = NULL;
mAuxImage = NULL;
llassert_always(mFormattedImage.notNull());
S32 discard = mHaveAllData ? 0 : mLoadedDiscard;
U32 image_priority = LLWorkerThread::PRIORITY_NORMAL | mWorkPriority;
mDecoded = FALSE;
mState = DECODE_IMAGE_UPDATE;
LL_DEBUGS("Texture") << 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 (mDecoded)
{
if (mDecodedDiscard < 0)
{
LL_DEBUGS("Texture") << 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;
llassert_always(mDecodeHandle == 0);
mFormattedImage = NULL;
++mRetryAttempt;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
mState = INIT;
return false;
}
else
{
// llwarns << "UNABLE TO LOAD TEXTURE: " << mID << " RETRIES: " << mRetryAttempt << llendl;
mState = DONE; // failed
}
}
else
{
llassert_always(mRawImage.notNull());
LL_DEBUGS("Texture") << mID << ": Decoded. Discard: " << mDecodedDiscard
<< " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
mState = WRITE_TO_CACHE;
}
// fall through
}
else
{
return false;
}
}
if (mState == WRITE_TO_CACHE)
{
if (mWriteToCacheState != SHOULD_WRITE || mFormattedImage.isNull())
{
// If we're in a local cache or we didn't actually receive any new data,
// or we failed to load anything, skip
mState = 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;
CacheWriteResponder* responder = new CacheWriteResponder(mFetcher, mID);
mCacheWriteHandle = mFetcher->mTextureCache->writeToCache(mID, cache_priority,
mFormattedImage->getData(), datasize,
mFileSize, responder);
// fall through
}
if (mState == WAIT_ON_WRITE)
{
if (writeToCacheComplete())
{
mState = DONE;
// fall through
}
else
{
if (mDesiredDiscard < mDecodedDiscard)
{
// We're waiting for this write to complete before we can receive more data
// (we can't touch mFormattedImage until the write completes)
// Prioritize the write
mFetcher->mTextureCache->prioritizeWrite(mCacheWriteHandle);
}
return false;
}
}
if (mState == DONE)
{
if (mDecodedDiscard >= 0 && mDesiredDiscard < mDecodedDiscard)
{
// More data was requested, return to INIT
mState = INIT;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
return false;
}
else
{
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
return true;
}
}
return false;
}
// Called from MAIN thread
void LLTextureFetchWorker::endWork(S32 param, bool aborted)
{
if (mDecodeHandle != 0)
{
mFetcher->mImageDecodeThread->abortRequest(mDecodeHandle, false);
mDecodeHandle = 0;
}
mFormattedImage = NULL;
}
//////////////////////////////////////////////////////////////////////////////
// virtual
void LLTextureFetchWorker::finishWork(S32 param, bool completed)
{
// The following are required in case the work was aborted
if (mCacheReadHandle != LLTextureCache::nullHandle())
{
mFetcher->mTextureCache->readComplete(mCacheReadHandle, true);
mCacheReadHandle = LLTextureCache::nullHandle();
}
if (mCacheWriteHandle != LLTextureCache::nullHandle())
{
mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true);
mCacheWriteHandle = LLTextureCache::nullHandle();
}
}
// virtual
bool LLTextureFetchWorker::deleteOK()
{
bool delete_ok = true;
// Allow any pending reads or writes to complete
if (mCacheReadHandle != LLTextureCache::nullHandle())
{
if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, true))
{
mCacheReadHandle = LLTextureCache::nullHandle();
}
else
{
delete_ok = false;
}
}
if (mCacheWriteHandle != LLTextureCache::nullHandle())
{
if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle))
{
mCacheWriteHandle = LLTextureCache::nullHandle();
}
else
{
delete_ok = false;
}
}
if ((haveWork() &&
// not ok to delete from these states
((mState >= WRITE_TO_CACHE && mState <= WAIT_ON_WRITE))))
{
delete_ok = false;
}
return delete_ok;
}
void LLTextureFetchWorker::removeFromCache()
{
if (!mInLocalCache)
{
mFetcher->mTextureCache->removeFromCache(mID);
}
}
//////////////////////////////////////////////////////////////////////////////
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;
}
if (mLastPacket >= mFirstPacket)
{
S32 buffer_size = mFormattedImage->getDataSize();
for (S32 i = mFirstPacket; i<=mLastPacket; i++)
{
llassert_always(mPackets[i]);
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
if (have_all_data)
{
mHaveAllData = TRUE;
}
S32 cur_size = mFormattedImage->getDataSize();
if (buffer_size > cur_size)
{
/// We have new data
U8* buffer = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), buffer_size);
S32 offset = 0;
if (cur_size > 0 && mFirstPacket > 0)
{
memcpy(buffer, mFormattedImage->getData(), cur_size);
offset = cur_size;
}
for (S32 i=mFirstPacket; i<=mLastPacket; i++)
{
memcpy(buffer + offset, mPackets[i]->mData, mPackets[i]->mSize);
offset += mPackets[i]->mSize;
}
// NOTE: setData releases current data
mFormattedImage->setData(buffer, buffer_size);
}
mLoadedDiscard = mRequestedDiscard;
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////////
S32 LLTextureFetchWorker::callbackHttpGet(const LLChannelDescriptors& channels,
const LLHTTPClient::ResponderBase::buffer_ptr_t& buffer,
bool partial, bool success)
{
S32 data_size = 0 ;
LLMutexLock lock(&mWorkMutex);
if (mState != WAIT_HTTP_REQ)
{
llwarns << "callbackHttpGet for unrequested fetch worker: " << mID
<< " req=" << mSentRequest << " state= " << mState << llendl;
return data_size;
}
if (mLoaded)
{
llwarns << "Duplicate callback for " << mID.asString() << llendl;
return data_size; // ignore duplicate callback
}
if (success)
{
// get length of stream:
data_size = buffer->countAfter(channels.in(), NULL);
LL_DEBUGS("Texture") << "HTTP RECEIVED: " << mID.asString() << " Bytes: " << data_size << LL_ENDL;
if (data_size > 0)
{
LLViewerStatsRecorder::instance().textureFetch(data_size);
// *TODO: set the formatted image data here directly to avoid the copy
llassert(mHttpBuffer.empty());
mHttpBuffer.resize(data_size);
buffer->readAfter(channels.in(), NULL, &mHttpBuffer[0], data_size);
if (data_size < mRequestedSize && mRequestedDiscard == 0)
{
mHaveAllData = TRUE;
}
else if (data_size > mRequestedSize)
{
// *TODO: This shouldn't be happening any more
llwarns << "data_size = " << data_size << " > requested: " << mRequestedSize << llendl;
mHaveAllData = TRUE;
mRequestedOffset = 0;
llassert_always(mDecodeHandle == 0);
mFormattedImage = NULL; // discard any previous data we had
}
}
else
{
// We requested data but received none (and no error),
// so presumably we have all of it
mHaveAllData = TRUE;
}
mRequestedSize = data_size;
}
else
{
mRequestedSize = -1; // error
}
mLoaded = TRUE;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
LLViewerStatsRecorder::instance().log(0.2f);
return data_size ;
}
//////////////////////////////////////////////////////////////////////////////
void LLTextureFetchWorker::callbackCacheRead(bool success, LLImageFormatted* image,
S32 imagesize, BOOL islocal)
{
LLMutexLock lock(&mWorkMutex);
if (mState != LOAD_FROM_TEXTURE_CACHE)
{
// llwarns << "Read callback for " << mID << " with state = " << mState << llendl;
return;
}
if (success)
{
llassert_always(imagesize >= 0);
mFileSize = imagesize;
mFormattedImage = image;
mImageCodec = image->getCodec();
mInLocalCache = islocal;
if (mFileSize != 0 && mFormattedImage->getDataSize() >= mFileSize)
{
mHaveAllData = TRUE;
}
}
mLoaded = TRUE;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
}
void LLTextureFetchWorker::callbackCacheWrite(bool success)
{
LLMutexLock lock(&mWorkMutex);
if (mState != WAIT_ON_WRITE)
{
// llwarns << "Write callback for " << mID << " with state = " << mState << llendl;
return;
}
mWritten = TRUE;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
}
//////////////////////////////////////////////////////////////////////////////
void LLTextureFetchWorker::callbackDecoded(bool success, LLImageRaw* raw, LLImageRaw* aux)
{
LLMutexLock lock(&mWorkMutex);
if (mDecodeHandle == 0)
{
return; // aborted, ignore
}
if (mState != DECODE_IMAGE_UPDATE)
{
// llwarns << "Decode callback for " << mID << " with state = " << mState << llendl;
mDecodeHandle = 0;
return;
}
llassert_always(mFormattedImage.notNull());
mDecodeHandle = 0;
if (success)
{
llassert_always(raw);
mRawImage = raw;
mAuxImage = aux;
mDecodedDiscard = mFormattedImage->getDiscardLevel();
LL_DEBUGS("Texture") << mID << ": Decode Finished. Discard: " << mDecodedDiscard
<< " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL;
}
else
{
if (mFormattedImage.notNull())
{
LL_WARNS("Texture") << "DECODE FAILED: id = " << mID << ", Discard = " << (S32)mFormattedImage->getDiscardLevel() << LL_ENDL;
}
else
{
LL_WARNS("Texture") << "DECODE FAILED: id = " << mID << ", mFormattedImage is Null!" << LL_ENDL;
}
removeFromCache();
mDecodedDiscard = -1; // Redundant, here for clarity and paranoia
}
mDecoded = TRUE;
// llinfos << mID << " : DECODE COMPLETE " << llendl;
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
mCacheReadTime = mCacheReadTimer.getElapsedTimeF32();
}
//////////////////////////////////////////////////////////////////////////////
bool LLTextureFetchWorker::writeToCacheComplete()
{
// Complete write to cache
if (mCacheWriteHandle != LLTextureCache::nullHandle())
{
if (!mWritten)
{
return false;
}
if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle))
{
mCacheWriteHandle = LLTextureCache::nullHandle();
}
else
{
return false;
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// public
LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded, bool qa_mode)
: LLWorkerThread("TextureFetch", threaded, true),
mDebugCount(0),
mDebugPause(FALSE),
mPacketCount(0),
mBadPacketCount(0),
mTextureCache(cache),
mImageDecodeThread(imagedecodethread),
mTextureBandwidth(0),
mHTTPTextureBits(0),
mTotalHTTPRequests(0)
{
mTextureInfo.setUpLogging(gSavedSettings.getBOOL("LogTextureDownloadsToViewerLog"), gSavedSettings.getBOOL("LogTextureDownloadsToSimulator"), gSavedSettings.getU32("TextureLoggingThreshold"));
}
LLTextureFetch::~LLTextureFetch()
{
clearDeleteList() ;
// ~LLQueuedThread() called here
}
bool LLTextureFetch::createRequest(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 (mDebugPause)
{
return false;
}
LLTextureFetchWorker* worker = getWorker(id);
if (worker)
{
if (worker->mHost != host)
{
llwarns << "LLTextureFetch::createRequest " << id << " called with multiple hosts: "
<< host << " != " << worker->mHost << llendl;
removeRequest(worker, true);
worker = NULL;
return false;
}
}
S32 desired_size;
std::string exten = gDirUtilp->getExtension(url);
if (!url.empty() && (!exten.empty() && LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C))
{
// Only do partial requests for J2C at the moment
//llinfos << "Merov : LLTextureFetch::createRequest(), blocking fetch on " << url << llendl;
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
// was compressed - this code ensures that when we request the entire image,
// we really do get it.)
desired_size = MAX_IMAGE_DATA_SIZE;
}
else if (w*h*c > 0)
{
// If the requester knows the dimensions of the image,
// this will calculate how much data we need without having to parse the header
desired_size = LLImageJ2C::calcDataSizeJ2C(w, h, c, desired_discard);
}
else
{
desired_size = TEXTURE_CACHE_ENTRY_SIZE;
desired_discard = MAX_DISCARD_LEVEL;
}
if (worker)
{
if (worker->wasAborted())
{
return false; // need to wait for previous aborted request to complete
}
worker->lockWorkMutex();
worker->mActiveCount++;
worker->mNeedsAux = needs_aux;
worker->setImagePriority(priority);
worker->setDesiredDiscard(desired_discard, desired_size);
worker->setCanUseHTTP(can_use_http) ;
if (!worker->haveWork())
{
worker->mState = LLTextureFetchWorker::INIT;
worker->unlockWorkMutex();
worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
}
else
{
worker->unlockWorkMutex();
}
}
else
{
worker = new LLTextureFetchWorker(this, url, id, host, priority, desired_discard, desired_size);
lockQueue() ;
mRequestMap[id] = worker;
unlockQueue() ;
worker->lockWorkMutex();
worker->mActiveCount++;
worker->mNeedsAux = needs_aux;
worker->setCanUseHTTP(can_use_http) ;
worker->unlockWorkMutex();
}
//llinfos << "REQUESTED: " << id << " Discard: " << desired_discard << llendl;
return true;
}
// protected
void LLTextureFetch::addToNetworkQueue(LLTextureFetchWorker* worker)
{
lockQueue();
bool in_request_map = (mRequestMap.find(worker->mID) != mRequestMap.end());
unlockQueue();
LLMutexLock lock(&mNetworkQueueMutex);
if (in_request_map)
{
// only add to the queue if in the request map
// i.e. a delete has not been requested
mNetworkQueue.insert(worker->mID);
}
for (cancel_queue_t::iterator iter1 = mCancelQueue.begin();
iter1 != mCancelQueue.end(); ++iter1)
{
iter1->second.erase(worker->mID);
}
}
void LLTextureFetch::removeFromNetworkQueue(LLTextureFetchWorker* worker, bool cancel)
{
LLMutexLock lock(&mNetworkQueueMutex);
size_t erased = mNetworkQueue.erase(worker->mID);
if (cancel && erased > 0)
{
mCancelQueue[worker->mHost].insert(worker->mID);
}
}
// protected
void LLTextureFetch::addToHTTPQueue(const LLUUID& id)
{
LLMutexLock lock(&mNetworkQueueMutex);
mHTTPTextureQueue.insert(id);
mTotalHTTPRequests++;
}
void LLTextureFetch::removeFromHTTPQueue(const LLUUID& id, S32 received_size)
{
LLMutexLock lock(&mNetworkQueueMutex);
mHTTPTextureQueue.erase(id);
mHTTPTextureBits += received_size * 8; // Approximate - does not include header bits
}
void LLTextureFetch::deleteRequest(const LLUUID& id, bool cancel)
{
lockQueue() ;
LLTextureFetchWorker* worker = getWorkerAfterLock(id);
removeRequest(worker, cancel, false);
}
void LLTextureFetch::removeRequest(LLTextureFetchWorker* worker, bool cancel, bool bNeedsLock)
{
if(!worker)
{
if(!bNeedsLock)
unlockQueue() ;
return;
}
if(bNeedsLock)
lockQueue() ;
size_t erased_1 = mRequestMap.erase(worker->mID);
unlockQueue() ;
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;
removeRequest(worker, true, false);
}
}
S32 LLTextureFetch::getNumRequests()
{
lockQueue() ;
S32 size = (S32)mRequestMap.size();
unlockQueue() ;
return size ;
}
S32 LLTextureFetch::getNumHTTPRequests()
{
mNetworkQueueMutex.lock() ;
S32 size = (S32)mHTTPTextureQueue.size();
mNetworkQueueMutex.unlock() ;
return size ;
}
U32 LLTextureFetch::getTotalNumHTTPRequests()
{
mNetworkQueueMutex.lock() ;
U32 size = mTotalHTTPRequests ;
mNetworkQueueMutex.unlock() ;
return size ;
}
// call lockQueue() first!
LLTextureFetchWorker* LLTextureFetch::getWorkerAfterLock(const LLUUID& id)
{
LLTextureFetchWorker* res = NULL;
map_t::iterator iter = mRequestMap.find(id);
if (iter != mRequestMap.end())
{
res = iter->second;
}
return res;
}
LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id)
{
LLMutexLock lock(&mQueueMutex) ;
return getWorkerAfterLock(id) ;
}
bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level,
LLPointer<LLImageRaw>& raw, LLPointer<LLImageRaw>& aux)
{
bool res = false;
LLTextureFetchWorker* worker = getWorker(id);
if (worker)
{
if (worker->wasAborted())
{
res = true;
}
else if (!worker->haveWork())
{
// Should only happen if we set mDebugPause...
if (!mDebugPause)
{
// llwarns << "Adding work for inactive worker: " << id << llendl;
worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
}
}
else if (worker->checkWork())
{
worker->lockWorkMutex();
discard_level = worker->mDecodedDiscard;
raw = worker->mRawImage;
aux = worker->mAuxImage;
F32 cache_read_time = worker->mCacheReadTime;
if (cache_read_time != 0.f)
{
sCacheReadLatency.addValue(cache_read_time * 1000.f);
}
res = true;
LL_DEBUGS("Texture") << id << ": Request Finished. State: " << worker->mState << " Discard: " << discard_level << LL_ENDL;
worker->unlockWorkMutex();
}
else
{
worker->lockWorkMutex();
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;
raw = worker->mRawImage;
aux = worker->mAuxImage;
}
worker->unlockWorkMutex();
}
}
else
{
res = true;
}
return res;
}
bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority)
{
bool res = false;
LLTextureFetchWorker* worker = getWorker(id);
if (worker)
{
worker->lockWorkMutex();
worker->setImagePriority(priority);
worker->unlockWorkMutex();
res = true;
}
return res;
}
//
// May be called from any thread
//virtual
S32 LLTextureFetch::getPending()
{
S32 res;
lockData();
{
LLMutexLock lock(&mQueueMutex);
res = mRequestQueue.size();
}
unlockData();
return res;
}
// virtual
bool LLTextureFetch::runCondition()
{
// Caller is holding the lock on LLThread's condition variable.
// 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().
return ! (mRequestQueue.empty() && mIdleThread); // From base class
}
//////////////////////////////////////////////////////////////////////////////
// MAIN THREAD (unthreaded envs), WORKER THREAD (threaded envs)
void LLTextureFetch::commonUpdate()
{
}
// MAIN THREAD
//virtual
S32 LLTextureFetch::update(F32 max_time_ms)
{
{
mNetworkQueueMutex.lock() ;
gTextureList.sTextureBits += mHTTPTextureBits ;
mHTTPTextureBits = 0 ;
mNetworkQueueMutex.unlock() ;
}
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();
}
return res;
}
//called in the MAIN thread after the TextureCacheThread shuts down.
void LLTextureFetch::shutDownTextureCacheThread()
{
if(mTextureCache)
{
llassert_always(mTextureCache->isQuitting() || mTextureCache->isStopped()) ;
mTextureCache = NULL ;
}
}
//called in the MAIN thread after the ImageDecodeThread shuts down.
void LLTextureFetch::shutDownImageDecodeThread()
{
if(mImageDecodeThread)
{
llassert_always(mImageDecodeThread->isQuitting() || mImageDecodeThread->isStopped()) ;
mImageDecodeThread = NULL ;
}
}
// WORKER THREAD
void LLTextureFetch::threadedUpdate()
{
// Limit update frequency
const F32 PROCESS_TIME = 0.05f;
static LLFrameTimer process_timer;
if (process_timer.getElapsedTimeF32() < PROCESS_TIME)
{
return;
}
process_timer.reset();
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)
{
llinfos << "Queued gets: " << q << llendl;
info_timer.reset();
}
}
#endif
}
//////////////////////////////////////////////////////////////////////////////
void LLTextureFetch::sendRequestListToSimulators()
{
// All requests
const F32 REQUEST_DELTA_TIME = 0.10f; // 10 fps
// Sim requests
const S32 IMAGES_PER_REQUEST = 50;
const F32 SIM_LAZY_FLUSH_TIMEOUT = 10.0f; // temp
const F32 MIN_REQUEST_TIME = 1.0f;
const F32 MIN_DELTA_PRIORITY = 1000.f;
// 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;
{
LLMutexLock lock2(&mNetworkQueueMutex);
for (queue_t::iterator iter = mNetworkQueue.begin(); iter != mNetworkQueue.end(); )
{
queue_t::iterator curiter = iter++;
LLTextureFetchWorker* req = getWorker(*curiter);
if (!req)
{
// This happens when a request was removed from mRequestMap in a race
// with adding it to mNetworkQueue by doWork (see SNOW-196).
mNetworkQueue.erase(curiter);
continue;
}
if ((req->mState != LLTextureFetchWorker::LOAD_FROM_NETWORK) &&
(req->mState != LLTextureFetchWorker::LOAD_FROM_SIMULATOR))
{
// We already received our URL, remove from the queue
llwarns << "Worker: " << req->mID << " in mNetworkQueue but in wrong state: " << req->mState << llendl;
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 >= SIM_LAZY_FLUSH_TIMEOUT))
{
requests[req->mHost].insert(req);
}
}
}
}
for (work_request_map_t::iterator iter1 = requests.begin();
iter1 != requests.end(); ++iter1)
{
LLHost host = iter1->first;
// invalid host = use agent host
if (host == LLHost::invalid)
{
host = gAgent.getRegionHost();
}
S32 sim_request_count = 0;
for (request_list_t::iterator iter2 = iter1->second.begin();
iter2 != iter1->second.end(); ++iter2)
{
LLTextureFetchWorker* req = *iter2;
if (gMessageSystem)
{
if (req->mSentRequest != LLTextureFetchWorker::SENT_SIM)
{
// Initialize packet data based on data read from cache
req->lockWorkMutex();
req->setupPacketData();
req->unlockWorkMutex();
}
if (0 == sim_request_count)
{
gMessageSystem->newMessageFast(_PREHASH_RequestImage);
gMessageSystem->nextBlockFast(_PREHASH_AgentData);
gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
}
S32 packet = req->mLastPacket + 1;
gMessageSystem->nextBlockFast(_PREHASH_RequestImage);
gMessageSystem->addUUIDFast(_PREHASH_Image, req->mID);
gMessageSystem->addS8Fast(_PREHASH_DiscardLevel, (S8)req->mDesiredDiscard);
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;
static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog");
static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator");
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->lockWorkMutex();
req->mSentRequest = LLTextureFetchWorker::SENT_SIM;
req->mSimRequestedDiscard = req->mDesiredDiscard;
req->mRequestedPriority = req->mImagePriority;
req->mRequestedTimer.reset();
req->unlockWorkMutex();
sim_request_count++;
if (sim_request_count >= IMAGES_PER_REQUEST)
{
// llinfos << "REQUESTING " << sim_request_count << " IMAGES FROM HOST: " << host.getIPString() << llendl;
gMessageSystem->sendSemiReliable(host, NULL, NULL);
sim_request_count = 0;
}
}
}
if (gMessageSystem && sim_request_count > 0 && sim_request_count < IMAGES_PER_REQUEST)
{
// llinfos << "REQUESTING " << sim_request_count << " IMAGES FROM HOST: " << host.getIPString() << llendl;
gMessageSystem->sendSemiReliable(host, NULL, NULL);
sim_request_count = 0;
}
}
// Send cancelations
{
LLMutexLock lock2(&mNetworkQueueMutex);
if (gMessageSystem && !mCancelQueue.empty())
{
for (cancel_queue_t::iterator iter1 = mCancelQueue.begin();
iter1 != mCancelQueue.end(); ++iter1)
{
LLHost host = iter1->first;
if (host == LLHost::invalid)
{
host = gAgent.getRegionHost();
}
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);
// llinfos << "CANCELING IMAGE REQUEST: " << (*iter2) << llendl;
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);
}
}
mCancelQueue.clear();
}
}
}
//////////////////////////////////////////////////////////////////////////////
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;
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;
return false;
}
if (index >= (S32)mPackets.size())
{
mPackets.resize(index+1, (PacketData*)NULL); // initializes v to NULL pointers
}
else if (mPackets[index] != NULL)
{
// llwarns << "Received duplicate packet: " << index << " for image: " << mID << llendl;
return false;
}
mPackets[index] = new PacketData(data, size);
while (mLastPacket+1 < (S32)mPackets.size() && mPackets[mLastPacket+1] != NULL)
{
++mLastPacket;
}
return true;
}
bool LLTextureFetch::receiveImageHeader(const LLHost& host, const LLUUID& id, U8 codec, U16 packets, U32 totalbytes,
U16 data_size, U8* data)
{
LLTextureFetchWorker* worker = getWorker(id);
bool res = true;
++mPacketCount;
if (!worker)
{
// llwarns << "Received header for non active worker: " << id << llendl;
res = false;
}
else if (worker->mState != LLTextureFetchWorker::LOAD_FROM_NETWORK ||
worker->mSentRequest != LLTextureFetchWorker::SENT_SIM)
{
// llwarns << "receiveImageHeader for worker: " << id
// << " in state: " << LLTextureFetchWorker::sStateDescs[worker->mState]
// << " sent: " << worker->mSentRequest << llendl;
res = false;
}
else if (worker->mLastPacket != -1)
{
// check to see if we've gotten this packet before
// llwarns << "Received duplicate header for: " << id << llendl;
res = false;
}
else if (!data_size)
{
// llwarns << "Img: " << id << ":" << " Empty Image Header" << llendl;
res = false;
}
if (!res)
{
++mBadPacketCount;
mNetworkQueueMutex.lock() ;
mCancelQueue[host].insert(id);
mNetworkQueueMutex.unlock() ;
return false;
}
LLViewerStatsRecorder::instance().textureFetch(data_size);
LLViewerStatsRecorder::instance().log(0.1f);
worker->lockWorkMutex();
// Copy header data into image object
worker->mImageCodec = codec;
worker->mTotalPackets = packets;
worker->mFileSize = (S32)totalbytes;
llassert_always(totalbytes > 0);
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->unlockWorkMutex();
return res;
}
bool LLTextureFetch::receiveImagePacket(const LLHost& host, const LLUUID& id, U16 packet_num, U16 data_size, U8* data)
{
LLTextureFetchWorker* worker = getWorker(id);
bool res = true;
++mPacketCount;
if (!worker)
{
// llwarns << "Received packet " << packet_num << " for non active worker: " << id << llendl;
res = false;
}
else if (worker->mLastPacket == -1)
{
// llwarns << "Received packet " << packet_num << " before header for: " << id << llendl;
res = false;
}
else if (!data_size)
{
// llwarns << "Img: " << id << ":" << " Empty Image Header" << llendl;
res = false;
}
if (!res)
{
++mBadPacketCount;
mNetworkQueueMutex.lock() ;
mCancelQueue[host].insert(id);
mNetworkQueueMutex.unlock() ;
return false;
}
LLViewerStatsRecorder::instance().textureFetch(data_size);
LLViewerStatsRecorder::instance().log(0.1f);
worker->lockWorkMutex();
res = worker->insertPacket(packet_num, data, data_size);
if ((worker->mState == LLTextureFetchWorker::LOAD_FROM_SIMULATOR) ||
(worker->mState == LLTextureFetchWorker::LOAD_FROM_NETWORK))
{
worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
worker->mState = LLTextureFetchWorker::LOAD_FROM_SIMULATOR;
}
else
{
// llwarns << "receiveImagePacket " << packet_num << "/" << worker->mLastPacket << " for worker: " << id
// << " in state: " << LLTextureFetchWorker::sStateDescs[worker->mState] << llendl;
removeFromNetworkQueue(worker, true); // failsafe
}
if(packet_num >= (worker->mTotalPackets - 1))
{
static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog");
static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator");
if (log_to_viewer_log || log_to_sim)
{
U64 timeNow = LLTimer::getTotalTime();
mTextureInfo.setRequestSize(id, worker->mFileSize);
mTextureInfo.setRequestCompleteTimeAndLog(id, timeNow);
}
}
worker->unlockWorkMutex();
return res;
}
//////////////////////////////////////////////////////////////////////////////
BOOL LLTextureFetch::isFromLocalCache(const LLUUID& id)
{
BOOL from_cache = FALSE;
LLTextureFetchWorker* worker = getWorker(id);
if (worker)
{
worker->lockWorkMutex();
from_cache = worker->mInLocalCache;
worker->unlockWorkMutex();
}
return from_cache;
}
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, bool& can_use_http)
{
S32 state = LLTextureFetchWorker::INVALID;
F32 data_progress = 0.0f;
F32 requested_priority = 0.0f;
F32 fetch_dtime = 999999.f;
F32 request_dtime = 999999.f;
U32 fetch_priority = 0;
LLTextureFetchWorker* worker = getWorker(id);
if (worker && worker->haveWork())
{
worker->lockWorkMutex();
state = worker->mState;
fetch_dtime = worker->mFetchTimer.getElapsedTimeF32();
request_dtime = worker->mRequestedTimer.getElapsedTimeF32();
if (worker->mFileSize > 0)
{
if (state == LLTextureFetchWorker::LOAD_FROM_SIMULATOR)
{
S32 data_size = FIRST_PACKET_SIZE + (worker->mLastPacket-1) * MAX_IMG_PACKET_SIZE;
data_size = llmax(data_size, 0);
data_progress = (F32)data_size / (F32)worker->mFileSize;
}
else if (worker->mFormattedImage.notNull())
{
data_progress = (F32)worker->mFormattedImage->getDataSize() / (F32)worker->mFileSize;
}
}
if (state >= LLTextureFetchWorker::LOAD_FROM_NETWORK && state <= LLTextureFetchWorker::WAIT_HTTP_REQ)
{
requested_priority = worker->mRequestedPriority;
}
else
{
requested_priority = worker->mImagePriority;
}
fetch_priority = worker->getPriority();
can_use_http = worker->getCanUseHTTP() ;
worker->unlockWorkMutex();
}
data_progress_p = data_progress;
requested_priority_p = requested_priority;
fetch_priority_p = fetch_priority;
fetch_dtime_p = fetch_dtime;
request_dtime_p = request_dtime;
return state;
}
void LLTextureFetch::dump()
{
llinfos << "LLTextureFetch REQUESTS:" << llendl;
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;
}
llinfos << "LLTextureFetch ACTIVE_HTTP:" << llendl;
for (queue_t::const_iterator iter(mHTTPTextureQueue.begin());
mHTTPTextureQueue.end() != iter;
++iter)
{
llinfos << " ID: " << (*iter) << llendl;
}
}