summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorOz Linden <oz@lindenlab.com>2014-03-03 17:01:19 -0500
committerOz Linden <oz@lindenlab.com>2014-03-03 17:01:19 -0500
commitfbdbea1b180a8dbee24ec0909979f1a25844b5e0 (patch)
tree9f15883b61d240c3dfd7b6467f0be693cafd0f83 /indra
parentbfdd6a689caf04d1cb69d5bf90bef1886f774bd1 (diff)
parentde8fea13627cc5978b8a6135802a52864a11c39a (diff)
merge changes for 3.7.2-release
Diffstat (limited to 'indra')
-rwxr-xr-xindra/edit-me-to-trigger-new-build.txt1
-rwxr-xr-xindra/lib/python/indra/base/lluuid.py2
-rwxr-xr-xindra/llaudio/llaudiodecodemgr.cpp56
-rwxr-xr-xindra/llaudio/llaudioengine.cpp84
-rwxr-xr-xindra/llcommon/CMakeLists.txt6
-rw-r--r--indra/llcommon/lldeadmantimer.cpp188
-rw-r--r--indra/llcommon/lldeadmantimer.h214
-rw-r--r--indra/llcommon/llprocinfo.cpp94
-rw-r--r--indra/llcommon/llprocinfo.h68
-rwxr-xr-xindra/llcommon/llthread.cpp32
-rwxr-xr-xindra/llcommon/llthread.h46
-rwxr-xr-xindra/llcommon/lltimer.h7
-rw-r--r--indra/llcommon/tests/lldeadmantimer_test.cpp628
-rw-r--r--indra/llcommon/tests/llprocinfo_test.cpp91
-rw-r--r--indra/llcorehttp/README.Linden671
-rwxr-xr-xindra/llcorehttp/_httpinternal.h23
-rwxr-xr-xindra/llcorehttp/_httplibcurl.cpp63
-rwxr-xr-xindra/llcorehttp/_httplibcurl.h16
-rwxr-xr-xindra/llcorehttp/_httpoperation.cpp10
-rwxr-xr-xindra/llcorehttp/_httpoperation.h8
-rwxr-xr-xindra/llcorehttp/_httpoprequest.cpp430
-rwxr-xr-xindra/llcorehttp/_httpoprequest.h7
-rwxr-xr-xindra/llcorehttp/_httpopsetget.cpp93
-rwxr-xr-xindra/llcorehttp/_httpopsetget.h27
-rwxr-xr-xindra/llcorehttp/_httppolicy.cpp223
-rwxr-xr-xindra/llcorehttp/_httppolicy.h39
-rwxr-xr-xindra/llcorehttp/_httppolicyclass.cpp54
-rwxr-xr-xindra/llcorehttp/_httppolicyclass.h20
-rwxr-xr-xindra/llcorehttp/_httppolicyglobal.cpp66
-rwxr-xr-xindra/llcorehttp/_httppolicyglobal.h23
-rwxr-xr-xindra/llcorehttp/_httpservice.cpp174
-rwxr-xr-xindra/llcorehttp/_httpservice.h52
-rwxr-xr-xindra/llcorehttp/examples/http_texture_load.cpp117
-rwxr-xr-xindra/llcorehttp/httpcommon.cpp42
-rwxr-xr-xindra/llcorehttp/httpcommon.h120
-rwxr-xr-xindra/llcorehttp/httpheaders.cpp141
-rwxr-xr-xindra/llcorehttp/httpheaders.h112
-rwxr-xr-xindra/llcorehttp/httpoptions.cpp17
-rwxr-xr-xindra/llcorehttp/httpoptions.h25
-rwxr-xr-xindra/llcorehttp/httprequest.cpp107
-rwxr-xr-xindra/llcorehttp/httprequest.h146
-rwxr-xr-xindra/llcorehttp/httpresponse.cpp6
-rwxr-xr-xindra/llcorehttp/httpresponse.h23
-rwxr-xr-xindra/llcorehttp/tests/test_httpheaders.hpp345
-rwxr-xr-xindra/llcorehttp/tests/test_httprequest.hpp701
-rwxr-xr-xindra/llcorehttp/tests/test_httpstatus.hpp59
-rwxr-xr-xindra/llcorehttp/tests/test_llcorehttp_peer.py72
-rwxr-xr-xindra/llmath/llvolume.cpp2
-rwxr-xr-xindra/llmessage/llcurl.cpp11
-rwxr-xr-xindra/llrender/llglslshader.cpp9
-rwxr-xr-xindra/llrender/llshadermgr.cpp30
-rwxr-xr-xindra/llrender/llshadermgr.h3
-rwxr-xr-xindra/llui/llbutton.cpp2
-rwxr-xr-xindra/llui/llcommandmanager.cpp4
-rwxr-xr-xindra/llui/llcommandmanager.h7
-rwxr-xr-xindra/llui/lltabcontainer.cpp23
-rwxr-xr-xindra/llui/lltabcontainer.h11
-rwxr-xr-xindra/llui/lltoolbar.cpp2
-rw-r--r--indra/llwindow/llopenglview-objc.mm7
-rwxr-xr-xindra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/VIEWER_VERSION.txt2
-rwxr-xr-xindra/newview/app_settings/commands.xml48
-rwxr-xr-xindra/newview/app_settings/settings.xml58
-rwxr-xr-xindra/newview/app_settings/shaders/class1/avatar/objectSkinV.glsl36
-rwxr-xr-xindra/newview/app_settings/shaders/class1/deferred/alphaF.glsl1
-rwxr-xr-xindra/newview/character/avatar_lad.xml181
-rwxr-xr-xindra/newview/character/avatar_skeleton.xml9
-rwxr-xr-xindra/newview/llagent.cpp67
-rwxr-xr-xindra/newview/llagent.h46
-rwxr-xr-xindra/newview/llappcorehttp.cpp249
-rwxr-xr-xindra/newview/llappcorehttp.h129
-rwxr-xr-xindra/newview/lldrawpoolavatar.cpp47
-rwxr-xr-xindra/newview/llenvmanager.cpp76
-rwxr-xr-xindra/newview/llenvmanager.h9
-rwxr-xr-xindra/newview/llfloatereditdaycycle.cpp2
-rwxr-xr-xindra/newview/llfloaterimsession.cpp82
-rwxr-xr-xindra/newview/llfloaterimsession.h4
-rwxr-xr-xindra/newview/llfloaterland.cpp11
-rwxr-xr-xindra/newview/llfloatermodelpreview.cpp11
-rwxr-xr-xindra/newview/llfloaterpathfindingconsole.cpp4
-rwxr-xr-xindra/newview/llfloaterpathfindingobjects.cpp3
-rwxr-xr-xindra/newview/llfloaterregioninfo.cpp3
-rw-r--r--indra/newview/llfloaterregionrestarting.cpp176
-rw-r--r--indra/newview/llfloaterregionrestarting.h69
-rwxr-xr-xindra/newview/llfloaterwebcontent.cpp5
-rwxr-xr-xindra/newview/llfloaterworldmap.cpp6
-rwxr-xr-xindra/newview/llinventorybridge.cpp34
-rwxr-xr-xindra/newview/llinventorybridge.h4
-rwxr-xr-xindra/newview/lllocationinputctrl.cpp4
-rwxr-xr-xindra/newview/llmenuoptionpathfindingrebakenavmesh.cpp2
-rwxr-xr-xindra/newview/llmeshrepository.cpp2521
-rwxr-xr-xindra/newview/llmeshrepository.h141
-rwxr-xr-xindra/newview/llmoveview.cpp2
-rwxr-xr-xindra/newview/llpanelplaces.cpp2
-rwxr-xr-xindra/newview/llpanelteleporthistory.cpp5
-rwxr-xr-xindra/newview/llpaneltopinfobar.cpp2
-rw-r--r--indra/newview/lltexturefetch.cpp31
-rwxr-xr-xindra/newview/lltexturefetch.h3
-rwxr-xr-xindra/newview/lltextureview.cpp41
-rwxr-xr-xindra/newview/lltracker.cpp152
-rwxr-xr-xindra/newview/lltracker.h2
-rwxr-xr-xindra/newview/llvieweraudio.cpp1
-rwxr-xr-xindra/newview/llviewerfloaterreg.cpp2
-rwxr-xr-xindra/newview/llviewermenu.cpp4
-rwxr-xr-xindra/newview/llviewermessage.cpp68
-rwxr-xr-xindra/newview/llviewerparcelmgr.cpp9
-rwxr-xr-xindra/newview/llviewerparcelmgr.h10
-rwxr-xr-xindra/newview/llviewerregion.cpp3
-rwxr-xr-xindra/newview/llvoavatar.cpp96
-rwxr-xr-xindra/newview/llvoavatar.h1
-rwxr-xr-xindra/newview/pipeline.h19
-rwxr-xr-xindra/newview/skins/default/colors.xml6
-rwxr-xr-xindra/newview/skins/default/textures/textures.xml3
-rwxr-xr-xindra/newview/skins/default/xui/en/floater_im_session.xml2
-rwxr-xr-xindra/newview/skins/default/xui/en/floater_model_preview.xml24
-rw-r--r--indra/newview/skins/default/xui/en/floater_region_restarting.xml75
-rwxr-xr-xindra/newview/skins/default/xui/en/menu_inventory.xml8
-rwxr-xr-xindra/newview/skins/default/xui/en/menu_teleport_history_item.xml2
-rwxr-xr-xindra/newview/skins/default/xui/en/menu_viewer.xml10
-rwxr-xr-xindra/newview/skins/default/xui/en/notifications.xml4
-rwxr-xr-xindra/newview/skins/default/xui/en/panel_people.xml4
-rwxr-xr-xindra/newview/skins/default/xui/en/widgets/location_input.xml1
-rwxr-xr-xindra/newview/skins/default/xui/en/widgets/tab_container.xml15
123 files changed, 8175 insertions, 2134 deletions
diff --git a/indra/edit-me-to-trigger-new-build.txt b/indra/edit-me-to-trigger-new-build.txt
index 774e8c0676..beeb570496 100755
--- a/indra/edit-me-to-trigger-new-build.txt
+++ b/indra/edit-me-to-trigger-new-build.txt
@@ -4,3 +4,4 @@ Wed Nov 7 00:25:19 UTC 2012
+
diff --git a/indra/lib/python/indra/base/lluuid.py b/indra/lib/python/indra/base/lluuid.py
index 369ae4e92f..7413ffe10d 100755
--- a/indra/lib/python/indra/base/lluuid.py
+++ b/indra/lib/python/indra/base/lluuid.py
@@ -72,7 +72,7 @@ class UUID(object):
ip = ''
try:
ip = socket.gethostbyname(socket.gethostname())
- except(socket.gaierror):
+ except(socket.gaierror, socket.error):
# no ip address, so just default to somewhere in 10.x.x.x
ip = '10'
for i in range(3):
diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp
index 6c97a64ed7..8c31f8b4de 100755
--- a/indra/llaudio/llaudiodecodemgr.cpp
+++ b/indra/llaudio/llaudiodecodemgr.cpp
@@ -135,7 +135,7 @@ int vfs_seek(void *datasource, ogg_int64_t offset, int whence)
origin = -1;
break;
default:
- llerrs << "Invalid whence argument to vfs_seek" << llendl;
+ LL_ERRS("AudioEngine") << "Invalid whence argument to vfs_seek" << LL_ENDL;
return -1;
}
@@ -197,12 +197,12 @@ BOOL LLVorbisDecodeState::initDecode()
vfs_callbacks.close_func = vfs_close;
vfs_callbacks.tell_func = vfs_tell;
- //llinfos << "Initing decode from vfile: " << mUUID << llendl;
+ LL_DEBUGS("AudioEngine") << "Initing decode from vfile: " << mUUID << LL_ENDL;
mInFilep = new LLVFile(gVFS, mUUID, LLAssetType::AT_SOUND);
if (!mInFilep || !mInFilep->getSize())
{
- llwarns << "unable to open vorbis source vfile for reading" << llendl;
+ LL_WARNS("AudioEngine") << "unable to open vorbis source vfile for reading" << LL_ENDL;
delete mInFilep;
mInFilep = NULL;
return FALSE;
@@ -211,7 +211,7 @@ BOOL LLVorbisDecodeState::initDecode()
int r = ov_open_callbacks(mInFilep, &mVF, NULL, 0, vfs_callbacks);
if(r < 0)
{
- llwarns << r << " Input to vorbis decode does not appear to be an Ogg bitstream: " << mUUID << llendl;
+ LL_WARNS("AudioEngine") << r << " Input to vorbis decode does not appear to be an Ogg bitstream: " << mUUID << LL_ENDL;
return(FALSE);
}
@@ -229,36 +229,36 @@ BOOL LLVorbisDecodeState::initDecode()
if( vi->channels < 1 || vi->channels > LLVORBIS_CLIP_MAX_CHANNELS )
{
abort_decode = true;
- llwarns << "Bad channel count: " << vi->channels << llendl;
+ LL_WARNS("AudioEngine") << "Bad channel count: " << vi->channels << LL_ENDL;
}
}
else // !vi
{
abort_decode = true;
- llwarns << "No default bitstream found" << llendl;
+ LL_WARNS("AudioEngine") << "No default bitstream found" << LL_ENDL;
}
if( (size_t)sample_count > LLVORBIS_CLIP_REJECT_SAMPLES ||
(size_t)sample_count <= 0)
{
abort_decode = true;
- llwarns << "Illegal sample count: " << sample_count << llendl;
+ LL_WARNS("AudioEngine") << "Illegal sample count: " << sample_count << LL_ENDL;
}
if( size_guess > LLVORBIS_CLIP_REJECT_SIZE ||
size_guess < 0)
{
abort_decode = true;
- llwarns << "Illegal sample size: " << size_guess << llendl;
+ LL_WARNS("AudioEngine") << "Illegal sample size: " << size_guess << LL_ENDL;
}
if( abort_decode )
{
- llwarns << "Canceling initDecode. Bad asset: " << mUUID << llendl;
+ LL_WARNS("AudioEngine") << "Canceling initDecode. Bad asset: " << mUUID << LL_ENDL;
vorbis_comment* comment = ov_comment(&mVF,-1);
if (comment && comment->vendor)
{
- llwarns << "Bad asset encoded by: " << comment->vendor << llendl;
+ LL_WARNS("AudioEngine") << "Bad asset encoded by: " << comment->vendor << LL_ENDL;
}
delete mInFilep;
mInFilep = NULL;
@@ -359,12 +359,12 @@ BOOL LLVorbisDecodeState::decodeSection()
{
if (!mInFilep)
{
- llwarns << "No VFS file to decode in vorbis!" << llendl;
+ LL_WARNS("AudioEngine") << "No VFS file to decode in vorbis!" << LL_ENDL;
return TRUE;
}
if (mDone)
{
-// llwarns << "Already done with decode, aborting!" << llendl;
+// LL_WARNS("AudioEngine") << "Already done with decode, aborting!" << LL_ENDL;
return TRUE;
}
char pcmout[4096]; /*Flawfinder: ignore*/
@@ -377,14 +377,14 @@ BOOL LLVorbisDecodeState::decodeSection()
eof = TRUE;
mDone = TRUE;
mValid = TRUE;
-// llinfos << "Vorbis EOF" << llendl;
+// LL_INFOS("AudioEngine") << "Vorbis EOF" << LL_ENDL;
}
else if (ret < 0)
{
/* error in the stream. Not a problem, just reporting it in
case we (the app) cares. In this case, we don't. */
- llwarns << "BAD vorbis decode in decodeSection." << llendl;
+ LL_WARNS("AudioEngine") << "BAD vorbis decode in decodeSection." << LL_ENDL;
mValid = FALSE;
mDone = TRUE;
@@ -393,7 +393,7 @@ BOOL LLVorbisDecodeState::decodeSection()
}
else
{
-// llinfos << "Vorbis read " << ret << "bytes" << llendl;
+// LL_INFOS("AudioEngine") << "Vorbis read " << ret << "bytes" << LL_ENDL;
/* we don't bother dealing with sample rate changes, etc, but.
you'll have to*/
std::copy(pcmout, pcmout+ret, std::back_inserter(mWAVBuffer));
@@ -405,7 +405,7 @@ BOOL LLVorbisDecodeState::finishDecode()
{
if (!isValid())
{
- llwarns << "Bogus vorbis decode state for " << getUUID() << ", aborting!" << llendl;
+ LL_WARNS("AudioEngine") << "Bogus vorbis decode state for " << getUUID() << ", aborting!" << LL_ENDL;
return TRUE; // We've finished
}
@@ -480,7 +480,7 @@ BOOL LLVorbisDecodeState::finishDecode()
if (36 == data_length)
{
- llwarns << "BAD Vorbis decode in finishDecode!" << llendl;
+ LL_WARNS("AudioEngine") << "BAD Vorbis decode in finishDecode!" << LL_ENDL;
mValid = FALSE;
return TRUE; // we've finished
}
@@ -497,7 +497,7 @@ BOOL LLVorbisDecodeState::finishDecode()
{
if (mBytesRead == 0)
{
- llwarns << "Unable to write file in LLVorbisDecodeState::finishDecode" << llendl;
+ LL_WARNS("AudioEngine") << "Unable to write file in LLVorbisDecodeState::finishDecode" << LL_ENDL;
mValid = FALSE;
return TRUE; // we've finished
}
@@ -515,7 +515,7 @@ BOOL LLVorbisDecodeState::finishDecode()
LLVFile output(gVFS, mUUID, LLAssetType::AT_SOUND_WAV);
output.write(&mWAVBuffer[0], mWAVBuffer.size());
#endif
- //llinfos << "Finished decode for " << getUUID() << llendl;
+ LL_DEBUGS("AudioEngine") << "Finished decode for " << getUUID() << LL_ENDL;
return TRUE;
}
@@ -524,7 +524,7 @@ void LLVorbisDecodeState::flushBadFile()
{
if (mInFilep)
{
- llwarns << "Flushing bad vorbis file from VFS for " << mUUID << llendl;
+ LL_WARNS("AudioEngine") << "Flushing bad vorbis file from VFS for " << mUUID << LL_ENDL;
mInFilep->remove();
}
}
@@ -568,7 +568,7 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs)
if (mCurrentDecodep->isDone() && !mCurrentDecodep->isValid())
{
// We had an error when decoding, abort.
- llwarns << mCurrentDecodep->getUUID() << " has invalid vorbis data, aborting decode" << llendl;
+ LL_WARNS("AudioEngine") << mCurrentDecodep->getUUID() << " has invalid vorbis data, aborting decode" << LL_ENDL;
mCurrentDecodep->flushBadFile();
LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID());
adp->setHasValidData(false);
@@ -590,7 +590,7 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs)
LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID());
if (!adp)
{
- llwarns << "Missing LLAudioData for decode of " << mCurrentDecodep->getUUID() << llendl;
+ LL_WARNS("AudioEngine") << "Missing LLAudioData for decode of " << mCurrentDecodep->getUUID() << LL_ENDL;
}
else if (mCurrentDecodep->isValid() && mCurrentDecodep->isDone())
{
@@ -601,12 +601,12 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs)
// At this point, we could see if anyone needs this sound immediately, but
// I'm not sure that there's a reason to - we need to poll all of the playing
// sounds anyway.
- //llinfos << "Finished the vorbis decode, now what?" << llendl;
+ //LL_INFOS("AudioEngine") << "Finished the vorbis decode, now what?" << LL_ENDL;
}
else
{
adp->setHasCompletedDecode(true);
- llinfos << "Vorbis decode failed for " << mCurrentDecodep->getUUID() << llendl;
+ LL_INFOS("AudioEngine") << "Vorbis decode failed for " << mCurrentDecodep->getUUID() << LL_ENDL;
}
mCurrentDecodep = NULL;
}
@@ -631,7 +631,7 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs)
continue;
}
- lldebugs << "Decoding " << uuid << " from audio queue!" << llendl;
+ lldebugs << "Decoding " << uuid << " from audio queue!" << LL_ENDL;
std::string uuid_str;
std::string d_path;
@@ -674,19 +674,19 @@ BOOL LLAudioDecodeMgr::addDecodeRequest(const LLUUID &uuid)
if (gAudiop->hasDecodedFile(uuid))
{
// Already have a decoded version, don't need to decode it.
- //llinfos << "addDecodeRequest for " << uuid << " has decoded file already" << llendl;
+ LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " has decoded file already" << LL_ENDL;
return TRUE;
}
if (gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND))
{
// Just put it on the decode queue.
- //llinfos << "addDecodeRequest for " << uuid << " has local asset file already" << llendl;
+ LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " has local asset file already" << LL_ENDL;
mImpl->mDecodeQueue.push(uuid);
return TRUE;
}
- //llinfos << "addDecodeRequest for " << uuid << " no file available" << llendl;
+ LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " no file available" << LL_ENDL;
return FALSE;
}
diff --git a/indra/llaudio/llaudioengine.cpp b/indra/llaudio/llaudioengine.cpp
index 06e752cf34..ca614f5395 100755
--- a/indra/llaudio/llaudioengine.cpp
+++ b/indra/llaudio/llaudioengine.cpp
@@ -123,7 +123,7 @@ bool LLAudioEngine::init(const S32 num_channels, void* userdata)
// Initialize the decode manager
gAudioDecodeMgrp = new LLAudioDecodeMgr;
- llinfos << "LLAudioEngine::init() AudioEngine successfully initialized" << llendl;
+ LL_INFOS("AudioEngine") << "LLAudioEngine::init() AudioEngine successfully initialized" << LL_ENDL;
return true;
}
@@ -308,7 +308,7 @@ void LLAudioEngine::idle(F32 max_decode_time)
LLAudioChannel *channelp = getFreeChannel(max_priority);
if (channelp)
{
- //llinfos << "Replacing source in channel due to priority!" << llendl;
+ LL_DEBUGS("AudioEngine") << "Replacing source in channel due to priority!" << LL_ENDL;
max_sourcep->setChannel(channelp);
channelp->setSource(max_sourcep);
if (max_sourcep->isSyncSlave())
@@ -479,7 +479,7 @@ void LLAudioEngine::idle(F32 max_decode_time)
{
if (!mBuffers[i]->mInUse && mBuffers[i]->mLastUseTimer.getElapsedTimeF32() > 30.f)
{
- //llinfos << "Flushing unused buffer!" << llendl;
+ LL_DEBUGS("AudioEngine") << "Flushing unused buffer!" << LL_ENDL;
mBuffers[i]->mAudioDatap->mBufferp = NULL;
delete mBuffers[i];
mBuffers[i] = NULL;
@@ -591,8 +591,8 @@ LLAudioBuffer * LLAudioEngine::getFreeBuffer()
if (buffer_id >= 0)
{
- lldebugs << "Taking over unused buffer " << buffer_id << llendl;
- //llinfos << "Flushing unused buffer!" << llendl;
+ lldebugs << "Taking over unused buffer " << buffer_id << LL_ENDL;
+ LL_DEBUGS("AudioEngine") << "Flushing unused buffer!" << LL_ENDL;
mBuffers[buffer_id]->mAudioDatap->mBufferp = NULL;
delete mBuffers[buffer_id];
mBuffers[buffer_id] = createBuffer();
@@ -673,6 +673,8 @@ void LLAudioEngine::cleanupBuffer(LLAudioBuffer *bufferp)
bool LLAudioEngine::preloadSound(const LLUUID &uuid)
{
+ LL_DEBUGS("AudioEngine")<<"( "<<uuid<<" )"<<LL_ENDL;
+
gAudiop->getAudioData(uuid); // We don't care about the return value, this is just to make sure
// that we have an entry, which will mean that the audio engine knows about this
@@ -684,7 +686,7 @@ bool LLAudioEngine::preloadSound(const LLUUID &uuid)
// At some point we need to have the audio/asset system check the static VFS
// before it goes off and fetches stuff from the server.
- //llwarns << "Used internal preload for non-local sound" << llendl;
+ LL_DEBUGS("AudioEngine") << "Used internal preload for non-local sound "<< uuid << LL_ENDL;
return false;
}
@@ -815,7 +817,7 @@ void LLAudioEngine::triggerSound(const LLUUID &audio_uuid, const LLUUID& owner_i
const S32 type, const LLVector3d &pos_global)
{
// Create a new source (since this can't be associated with an existing source.
- //llinfos << "Localized: " << audio_uuid << llendl;
+ LL_DEBUGS("AudioEngine") << "Localized: " << audio_uuid << LL_ENDL;
if (mMuted)
{
@@ -982,11 +984,14 @@ void LLAudioEngine::cleanupAudioSource(LLAudioSource *asp)
iter = mAllSources.find(asp->getID());
if (iter == mAllSources.end())
{
- llwarns << "Cleaning up unknown audio source!" << llendl;
- return;
+ LL_WARNS("AudioEngine") << "Cleaning up unknown audio source!" << LL_ENDL;
+ }
+ else
+ {
+ LL_DEBUGS("AudioEngine") << "Cleaning up audio sources for "<< asp->getID() <<LL_ENDL;
+ delete asp;
+ mAllSources.erase(iter);
}
- delete asp;
- mAllSources.erase(iter);
}
@@ -1013,16 +1018,18 @@ bool LLAudioEngine::hasDecodedFile(const LLUUID &uuid)
bool LLAudioEngine::hasLocalFile(const LLUUID &uuid)
{
// See if it's in the VFS.
- return gVFS->getExists(uuid, LLAssetType::AT_SOUND);
+ bool have_local = gVFS->getExists(uuid, LLAssetType::AT_SOUND);
+ LL_DEBUGS("AudioEngine") << "sound uuid "<<uuid<<" exists in VFS"<<LL_ENDL;
+ return have_local;
}
void LLAudioEngine::startNextTransfer()
{
- //llinfos << "LLAudioEngine::startNextTransfer()" << llendl;
+ //LL_DEBUGS("AudioEngine") << "LLAudioEngine::startNextTransfer()" << LL_ENDL;
if (mCurrentTransfer.notNull() || getMuted())
{
- //llinfos << "Transfer in progress, aborting" << llendl;
+ //LL_DEBUGS("AudioEngine") << "Transfer in progress, aborting" << LL_ENDL;
return;
}
@@ -1203,7 +1210,7 @@ void LLAudioEngine::startNextTransfer()
if (asset_id.notNull())
{
- llinfos << "Getting asset data for: " << asset_id << llendl;
+ LL_INFOS("AudioEngine") << "Getting audio asset data for: " << asset_id << LL_ENDL;
gAudiop->mCurrentTransfer = asset_id;
gAudiop->mCurrentTransferTimer.reset();
gAssetStorage->getAssetData(asset_id, LLAssetType::AT_SOUND,
@@ -1211,7 +1218,7 @@ void LLAudioEngine::startNextTransfer()
}
else
{
- //llinfos << "No pending transfers?" << llendl;
+ //LL_DEBUGS("AudioEngine") << "No pending transfers?" << LL_ENDL;
}
}
@@ -1221,7 +1228,7 @@ void LLAudioEngine::assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::E
{
if (result_code)
{
- llinfos << "Boom, error in audio file transfer: " << LLAssetStorage::getErrorString( result_code ) << " (" << result_code << ")" << llendl;
+ LL_INFOS("AudioEngine") << "Boom, error in audio file transfer: " << LLAssetStorage::getErrorString( result_code ) << " (" << result_code << ")" << LL_ENDL;
// Need to mark data as bad to avoid constant rerequests.
LLAudioData *adp = gAudiop->getAudioData(uuid);
if (adp)
@@ -1238,11 +1245,11 @@ void LLAudioEngine::assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::E
if (!adp)
{
// Should never happen
- llwarns << "Got asset callback without audio data for " << uuid << llendl;
+ LL_WARNS("AudioEngine") << "Got asset callback without audio data for " << uuid << LL_ENDL;
}
else
{
- // llinfos << "Got asset callback with good audio data for " << uuid << ", making decode request" << llendl;
+ LL_DEBUGS("AudioEngine") << "Got asset callback with good audio data for " << uuid << ", making decode request" << LL_ENDL;
adp->setHasValidData(true);
adp->setHasLocalData(true);
gAudioDecodeMgrp->addDecodeRequest(uuid);
@@ -1321,7 +1328,7 @@ void LLAudioSource::update()
}
else if (adp->hasCompletedDecode()) // Only mark corrupted after decode is done
{
- llwarns << "Marking LLAudioSource corrupted for " << adp->getID() << llendl;
+ LL_WARNS("AudioEngine") << "Marking LLAudioSource corrupted for " << adp->getID() << LL_ENDL;
mCorrupted = true ;
}
}
@@ -1357,7 +1364,6 @@ bool LLAudioSource::setupChannel()
if (!adp->getBuffer())
{
// We're not ready to play back the sound yet, so don't try and allocate a channel for it.
- //llwarns << "Aborting, no buffer" << llendl;
return false;
}
@@ -1375,7 +1381,7 @@ bool LLAudioSource::setupChannel()
// Ugh, we don't have any free channels.
// Now we have to reprioritize.
// For now, just don't play the sound.
- //llwarns << "Aborting, no free channels" << llendl;
+ //llwarns << "Aborting, no free channels" << LL_ENDL;
return false;
}
@@ -1474,7 +1480,7 @@ bool LLAudioSource::isDone() const
{
// We don't have a channel assigned, and it's been
// over 15 seconds since we tried to play it. Don't bother.
- //llinfos << "No channel assigned, source is done" << llendl;
+ LL_DEBUGS("AudioEngine") << "No channel assigned, source is done" << LL_ENDL;
return true;
}
else
@@ -1640,7 +1646,7 @@ LLAudioChannel::LLAudioChannel() :
LLAudioChannel::~LLAudioChannel()
{
// Need to disconnect any sources which are using this channel.
- //llinfos << "Cleaning up audio channel" << llendl;
+ LL_DEBUGS("AudioEngine") << "Cleaning up audio channel" << LL_ENDL;
if (mCurrentSourcep)
{
mCurrentSourcep->setChannel(NULL);
@@ -1651,29 +1657,29 @@ LLAudioChannel::~LLAudioChannel()
void LLAudioChannel::setSource(LLAudioSource *sourcep)
{
- //llinfos << this << ": setSource(" << sourcep << ")" << llendl;
-
if (!sourcep)
{
// Clearing the source for this channel, don't need to do anything.
- //llinfos << "Clearing source for channel" << llendl;
+ LL_DEBUGS("AudioEngine") << "Clearing source" << ( mCurrentSourcep ? mCurrentSourcep->getID() : LLUUID::null ) << LL_ENDL;
cleanup();
mCurrentSourcep = NULL;
mWaiting = false;
- return;
}
-
- if (sourcep == mCurrentSourcep)
+ else
{
- // Don't reallocate the channel, this will make FMOD goofy.
- //llinfos << "Calling setSource with same source!" << llendl;
- }
-
- mCurrentSourcep = sourcep;
+ LL_DEBUGS("AudioEngine") << "( id: " << sourcep->getID() << ")" << LL_ENDL;
+ if (sourcep == mCurrentSourcep)
+ {
+ // Don't reallocate the channel, this will make FMOD goofy.
+ //LL_DEBUGS("AudioEngine") << "Calling setSource with same source!" << LL_ENDL;
+ }
- updateBuffer();
- update3DPosition();
+ mCurrentSourcep = sourcep;
+
+ updateBuffer();
+ update3DPosition();
+ }
}
@@ -1768,7 +1774,7 @@ bool LLAudioData::load()
if (mBufferp)
{
// We already have this sound in a buffer, don't do anything.
- llinfos << "Already have a buffer for this sound, don't bother loading!" << llendl;
+ LL_INFOS("AudioEngine") << "Already have a buffer for this sound, don't bother loading!" << LL_ENDL;
return true;
}
@@ -1776,7 +1782,7 @@ bool LLAudioData::load()
if (!mBufferp)
{
// No free buffers, abort.
- llinfos << "Not able to allocate a new audio buffer, aborting." << llendl;
+ LL_INFOS("AudioEngine") << "Not able to allocate a new audio buffer, aborting." << LL_ENDL;
return true;
}
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 8eb0c6249d..8767616a70 100755
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -43,6 +43,7 @@ set(llcommon_SOURCE_FILES
llcriticaldamp.cpp
llcursortypes.cpp
lldate.cpp
+ lldeadmantimer.cpp
lldependencies.cpp
lldictionary.cpp
llerror.cpp
@@ -79,6 +80,7 @@ set(llcommon_SOURCE_FILES
llptrto.cpp
llprocess.cpp
llprocessor.cpp
+ llprocinfo.cpp
llqueuedthread.cpp
llrand.cpp
llrefcount.cpp
@@ -146,6 +148,7 @@ set(llcommon_HEADER_FILES
lldarray.h
lldarrayptr.h
lldate.h
+ lldeadmantimer.h
lldefs.h
lldependencies.h
lldeleteutils.h
@@ -207,6 +210,7 @@ set(llcommon_HEADER_FILES
llpriqueuemap.h
llprocess.h
llprocessor.h
+ llprocinfo.h
llptrskiplist.h
llptrskipmap.h
llptrto.h
@@ -324,12 +328,14 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llerror "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llframetimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lllazy "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}")
diff --git a/indra/llcommon/lldeadmantimer.cpp b/indra/llcommon/lldeadmantimer.cpp
new file mode 100644
index 0000000000..7d9097e344
--- /dev/null
+++ b/indra/llcommon/lldeadmantimer.cpp
@@ -0,0 +1,188 @@
+/**
+* @file lldeadmantimer.cpp
+* @brief Simple deadman-switch timer.
+* @author monty@lindenlab.com
+*
+* $LicenseInfo:firstyear=2013&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2013, Linden Research, Inc.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* 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 "lldeadmantimer.h"
+
+
+// *TODO: Currently, this uses lltimer functions for its time
+// aspects and this leaks into the apis in the U64s/F64s. Would
+// like to perhaps switch this over to TSC register-based timers
+// sometime and drop the overhead some more.
+
+
+// Flag states and their meaning:
+// mActive mDone Meaning
+// false false Nothing running, no result available
+// true false Timer running, no result available
+// false true Timer finished, result can be read once
+// true true Not allowed
+//
+LLDeadmanTimer::LLDeadmanTimer(F64 horizon, bool inc_cpu)
+ : mHorizon(time_type(llmax(horizon, F64(0.0)) * gClockFrequency)),
+ mActive(false), // If true, a timer is running.
+ mDone(false), // If true, timer has completed and can be read (once)
+ mStarted(U64L(0)),
+ mExpires(U64L(0)),
+ mStopped(U64L(0)),
+ mCount(U64L(0)),
+ mIncCPU(inc_cpu),
+ mUStartCPU(LLProcInfo::time_type(U64L(0))),
+ mUEndCPU(LLProcInfo::time_type(U64L(0))),
+ mSStartCPU(LLProcInfo::time_type(U64L(0))),
+ mSEndCPU(LLProcInfo::time_type(U64L(0)))
+{}
+
+
+// static
+LLDeadmanTimer::time_type LLDeadmanTimer::getNow()
+{
+ return LLTimer::getCurrentClockCount();
+}
+
+
+void LLDeadmanTimer::start(time_type now)
+{
+ // *TODO: If active, let's complete an existing timer and save
+ // the result to the side. I think this will be useful later.
+ // For now, wipe out anything in progress, start fresh.
+
+ if (! now)
+ {
+ now = LLTimer::getCurrentClockCount();
+ }
+ mActive = true;
+ mDone = false;
+ mStarted = now;
+ mExpires = now + mHorizon;
+ mStopped = now;
+ mCount = U64L(0);
+ if (mIncCPU)
+ {
+ LLProcInfo::getCPUUsage(mUStartCPU, mSStartCPU);
+ }
+}
+
+
+void LLDeadmanTimer::stop(time_type now)
+{
+ if (! mActive)
+ {
+ return;
+ }
+
+ if (! now)
+ {
+ now = getNow();
+ }
+ mStopped = now;
+ mActive = false;
+ mDone = true;
+ if (mIncCPU)
+ {
+ LLProcInfo::getCPUUsage(mUEndCPU, mSEndCPU);
+ }
+}
+
+
+bool LLDeadmanTimer::isExpired(time_type now, F64 & started, F64 & stopped, U64 & count,
+ U64 & user_cpu, U64 & sys_cpu)
+{
+ const bool status(isExpired(now, started, stopped, count));
+ if (status)
+ {
+ user_cpu = U64(mUEndCPU - mUStartCPU);
+ sys_cpu = U64(mSEndCPU - mSStartCPU);
+ }
+ return status;
+}
+
+
+bool LLDeadmanTimer::isExpired(time_type now, F64 & started, F64 & stopped, U64 & count)
+{
+ if (mActive && ! mDone)
+ {
+ if (! now)
+ {
+ now = getNow();
+ }
+
+ if (now >= mExpires)
+ {
+ // mStopped from ringBell() is the value we want
+ mActive = false;
+ mDone = true;
+ }
+ }
+
+ if (! mDone)
+ {
+ return false;
+ }
+
+ started = mStarted * gClockFrequencyInv;
+ stopped = mStopped * gClockFrequencyInv;
+ count = mCount;
+ mDone = false;
+
+ return true;
+}
+
+
+void LLDeadmanTimer::ringBell(time_type now, unsigned int count)
+{
+ if (! mActive)
+ {
+ return;
+ }
+
+ if (! now)
+ {
+ now = getNow();
+ }
+
+ if (now >= mExpires)
+ {
+ // Timer has expired, this event will be dropped
+ mActive = false;
+ mDone = true;
+ }
+ else
+ {
+ // Timer renewed, keep going
+ mStopped = now;
+ mExpires = now + mHorizon;
+ mCount += count;
+ if (mIncCPU)
+ {
+ LLProcInfo::getCPUUsage(mUEndCPU, mSEndCPU);
+ }
+ }
+
+ return;
+}
+
diff --git a/indra/llcommon/lldeadmantimer.h b/indra/llcommon/lldeadmantimer.h
new file mode 100644
index 0000000000..980976e176
--- /dev/null
+++ b/indra/llcommon/lldeadmantimer.h
@@ -0,0 +1,214 @@
+/**
+* @file lldeadmantimer.h
+* @brief Interface to a simple event timer with a deadman's switch
+* @author monty@lindenlab.com
+*
+* $LicenseInfo:firstyear=2013&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2013, Linden Research, Inc.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* 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$
+*/
+
+#ifndef LL_DEADMANTIMER_H
+#define LL_DEADMANTIMER_H
+
+
+#include "linden_common.h"
+
+#include "lltimer.h"
+#include "llprocinfo.h"
+
+
+/// @file lldeadmantimer.h
+///
+/// There are interesting user-experienced events in the viewer that
+/// would seem to have well-defined start and stop points but which
+/// actually lack such milestones in the code. Such events (like
+/// time to load meshes after logging in, initial inventory load,
+/// display name fetch) can be defined somewhat after-the-fact by
+/// noticing when we no longer perform operations towards their
+/// completion. This class is intended to help in such applications.
+///
+/// What it implements is a deadman's switch (also known as a
+/// keepalive switch and a doorbell switch). The basic operation is
+/// as follows:
+///
+/// * LLDeadmanTimer is instantiated with a horizon value in seconds,
+/// one for each event of interest.
+/// * When an event starts, @see start() is invoked to begin a
+/// timing operation.
+/// * As operations are performed in service of the event (issuing
+/// HTTP requests, receiving responses), @see ringBell() is invoked
+/// to inform the timer that the operation is still active.
+/// * If the operation is canceled or otherwise terminated, @see
+/// stop() can be called to end the timing operation.
+/// * Concurrent with the ringBell() calls, the program makes
+/// periodic (shorter than the horizon but not too short) calls
+/// to @see isExpired() to see if the event has expired due to
+/// either a stop() call or lack of activity (defined as a ringBell()
+/// call in the previous 'horizon' seconds). If it has expired,
+/// the caller also receives start, stop and count values for the
+/// event which the application can then report in whatever manner
+/// it sees fit.
+/// * The timer becomes passive after an isExpired() call that returns
+/// true. It can then be restarted with a new start() call.
+///
+/// Threading: Instances are not thread-safe. They also use
+/// timing code from lltimer.h which is also unsafe.
+///
+/// Allocation: Not refcounted, may be stack or heap allocated.
+///
+
+class LL_COMMON_API LLDeadmanTimer
+{
+public:
+ /// Public types
+
+ /// Low-level time type chosen for compatibility with
+ /// LLTimer::getCurrentClockCount() which is the basis
+ /// of time operations in this class. This is likely
+ /// to change in a future version in a move to TSC-based
+ /// timing.
+ typedef U64 time_type;
+
+public:
+ /// Construct and initialize an LLDeadmanTimer
+ ///
+ /// @param horizon Time, in seconds, after the last @see ringBell()
+ /// call at which point the timer will consider itself
+ /// expired.
+ ///
+ /// @param inc_cpu If true, gather system and user cpu stats while
+ /// running the timer. This does require more syscalls
+ /// during updates. If false, cpu usage data isn't
+ /// collected and will be zero if queried.
+ LLDeadmanTimer(F64 horizon, bool inc_cpu);
+
+ ~LLDeadmanTimer()
+ {}
+
+private:
+ LLDeadmanTimer(const LLDeadmanTimer &); // Not defined
+ void operator=(const LLDeadmanTimer &); // Not defined
+
+public:
+ /// Get the current time. Zero-basis for this time
+ /// representation is not defined and is different on
+ /// different platforms. Do not attempt to compute
+ /// negative times relative to the first value returned,
+ /// there may not be enough 'front porch' on the range
+ /// to prevent wraparound.
+ ///
+ /// Note: Implementation is expected to change in a
+ /// future release as well.
+ ///
+ static time_type getNow();
+
+ /// Begin timing. If the timer is already active, it is reset
+ /// and timing begins now.
+ ///
+ /// @param now Current time as returned by @see
+ /// LLTimer::getCurrentClockCount(). If zero,
+ /// method will lookup current time.
+ ///
+ void start(time_type now);
+
+ /// End timing. Actively declare the end of the event independent
+ /// of the deadman's switch operation. @see isExpired() will return
+ /// true and appropriate values will be returned.
+ ///
+ /// @param now Current time as returned by @see
+ /// LLTimer::getCurrentClockCount(). If zero,
+ /// method will lookup current time.
+ ///
+ void stop(time_type now);
+
+ /// Declare that something interesting happened. This has two
+ /// effects on an unexpired-timer. 1) The expiration time
+ /// is extended for 'horizon' seconds after the 'now' value.
+ /// 2) An internal counter associated with the event is incremented
+ /// by the @ref count parameter. This count is returned via the
+ /// @see isExpired() method.
+ ///
+ /// @param now Current time as returned by @see
+ /// LLTimer::getCurrentClockCount(). If zero,
+ /// method will lookup current time.
+ ///
+ /// @param count Count of events to be associated with
+ /// this bell ringing.
+ ///
+ void ringBell(time_type now, unsigned int count);
+
+ /// Checks the status of the timer. If the timer has expired,
+ /// also returns various timer-related stats. Unlike ringBell(),
+ /// does not extend the horizon, it only checks for expiration.
+ ///
+ /// @param now Current time as returned by @see
+ /// LLTimer::getCurrentClockCount(). If zero,
+ /// method will lookup current time.
+ ///
+ /// @param started If expired, the starting time of the event is
+ /// returned to the caller via this reference.
+ ///
+ /// @param stopped If expired, the ending time of the event is
+ /// returned to the caller via this reference.
+ /// Ending time will be that provided in the
+ /// stop() method or the last ringBell() call
+ /// leading to expiration, whichever (stop() call
+ /// or notice of expiration) happened first.
+ ///
+ /// @param count If expired, the number of ringBell() calls
+ /// made prior to expiration.
+ ///
+ /// @param user_cpu Amount of CPU spent in user mode by the process
+ /// during the event. Value in microseconds and will
+ /// read zero if not enabled by the constructor.
+ ///
+ /// @param sys_cpu Amount of CPU spent in system mode by the process.
+ ///
+ /// @return true if the timer has expired, false otherwise.
+ /// If true, it also returns the started,
+ /// stopped and count values otherwise these are
+ /// left unchanged.
+ ///
+ bool isExpired(time_type now, F64 & started, F64 & stopped, U64 & count,
+ U64 & user_cpu, U64 & sys_cpu);
+
+ /// Identical to the six-arugment form except it does without the
+ /// CPU time return if the caller isn't interested in it.
+ bool isExpired(time_type now, F64 & started, F64 & stopped, U64 & count);
+
+protected:
+ time_type mHorizon;
+ bool mActive;
+ bool mDone;
+ time_type mStarted;
+ time_type mExpires;
+ time_type mStopped;
+ time_type mCount;
+
+ const bool mIncCPU; // Include CPU metrics in timer
+ LLProcInfo::time_type mUStartCPU;
+ LLProcInfo::time_type mUEndCPU;
+ LLProcInfo::time_type mSStartCPU;
+ LLProcInfo::time_type mSEndCPU;
+};
+
+
+#endif // LL_DEADMANTIMER_H
diff --git a/indra/llcommon/llprocinfo.cpp b/indra/llcommon/llprocinfo.cpp
new file mode 100644
index 0000000000..c00f979b0b
--- /dev/null
+++ b/indra/llcommon/llprocinfo.cpp
@@ -0,0 +1,94 @@
+/**
+* @file llprocinfo.cpp
+* @brief Process, cpu and resource usage information APIs.
+* @author monty@lindenlab.com
+*
+* $LicenseInfo:firstyear=2013&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2013, Linden Research, Inc.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* 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 "llprocinfo.h"
+
+#if LL_WINDOWS
+
+#define PSAPI_VERSION 1
+#include "windows.h"
+#include "psapi.h"
+
+#elif LL_DARWIN
+
+#include <sys/resource.h>
+#include <mach/mach.h>
+
+#else
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#endif // LL_WINDOWS/LL_DARWIN
+
+
+// static
+void LLProcInfo::getCPUUsage(time_type & user_time, time_type & system_time)
+{
+#if LL_WINDOWS
+
+ HANDLE self(GetCurrentProcess()); // Does not have to be closed
+ FILETIME ft_dummy, ft_system, ft_user;
+
+ GetProcessTimes(self, &ft_dummy, &ft_dummy, &ft_system, &ft_user);
+ ULARGE_INTEGER uli;
+ uli.u.LowPart = ft_system.dwLowDateTime;
+ uli.u.HighPart = ft_system.dwHighDateTime;
+ system_time = uli.QuadPart / U64L(10); // Convert to uS
+ uli.u.LowPart = ft_user.dwLowDateTime;
+ uli.u.HighPart = ft_user.dwHighDateTime;
+ user_time = uli.QuadPart / U64L(10);
+
+#elif LL_DARWIN
+
+ struct rusage usage;
+
+ if (getrusage(RUSAGE_SELF, &usage))
+ {
+ user_time = system_time = time_type(0U);
+ return;
+ }
+ user_time = U64(usage.ru_utime.tv_sec) * U64L(1000000) + usage.ru_utime.tv_usec;
+ system_time = U64(usage.ru_stime.tv_sec) * U64L(1000000) + usage.ru_stime.tv_usec;
+
+#else // Linux
+
+ struct rusage usage;
+
+ if (getrusage(RUSAGE_SELF, &usage))
+ {
+ user_time = system_time = time_type(0U);
+ return;
+ }
+ user_time = U64(usage.ru_utime.tv_sec) * U64L(1000000) + usage.ru_utime.tv_usec;
+ system_time = U64(usage.ru_stime.tv_sec) * U64L(1000000) + usage.ru_stime.tv_usec;
+
+#endif // LL_WINDOWS/LL_DARWIN/Linux
+}
+
+
diff --git a/indra/llcommon/llprocinfo.h b/indra/llcommon/llprocinfo.h
new file mode 100644
index 0000000000..e78bcf490a
--- /dev/null
+++ b/indra/llcommon/llprocinfo.h
@@ -0,0 +1,68 @@
+/**
+* @file llprocinfo.h
+* @brief Interface to process/cpu/resource information services.
+* @author monty@lindenlab.com
+*
+* $LicenseInfo:firstyear=2013&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2013, Linden Research, Inc.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* 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$
+*/
+
+#ifndef LL_PROCINFO_H
+#define LL_PROCINFO_H
+
+
+#include "linden_common.h"
+
+/// @file llprocinfo.h
+///
+/// Right now, this is really a namespace disguised as a class.
+/// It wraps some types and functions to return information about
+/// process resource consumption in a non-OS-specific manner.
+///
+/// Threading: No instances so that's thread-safe. Implementations
+/// of static functions should be thread-safe, they mostly involve
+/// direct syscall invocations.
+///
+/// Allocation: Not instantiatable.
+
+class LL_COMMON_API LLProcInfo
+{
+public:
+ /// Public types
+
+ typedef U64 time_type; /// Relative microseconds
+
+private:
+ LLProcInfo(); // Not defined
+ ~LLProcInfo(); // Not defined
+ LLProcInfo(const LLProcInfo &); // Not defined
+ void operator=(const LLProcInfo &); // Not defined
+
+public:
+ /// Get accumulated system and user CPU time in
+ /// microseconds. Syscalls involved in every invocation.
+ ///
+ /// Threading: expected to be safe.
+ static void getCPUUsage(time_type & user_time, time_type & system_time);
+};
+
+
+#endif // LL_PROCINFO_H
diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp
index 60adeeaeb7..e67d1bc57b 100755
--- a/indra/llcommon/llthread.cpp
+++ b/indra/llcommon/llthread.cpp
@@ -3,7 +3,7 @@
*
* $LicenseInfo:firstyear=2004&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2010-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -373,6 +373,36 @@ void LLMutex::lock()
#endif
}
+bool LLMutex::trylock()
+{
+ if(isSelfLocked())
+ { //redundant lock
+ mCount++;
+ return true;
+ }
+
+ apr_status_t status(apr_thread_mutex_trylock(mAPRMutexp));
+ if (APR_STATUS_IS_EBUSY(status))
+ {
+ return false;
+ }
+
+#if MUTEX_DEBUG
+ // Have to have the lock before we can access the debug info
+ U32 id = LLThread::currentID();
+ if (mIsLocked[id] != FALSE)
+ llerrs << "Already locked in Thread: " << id << llendl;
+ mIsLocked[id] = TRUE;
+#endif
+
+#if LL_DARWIN
+ mLockingThread = LLThread::currentID();
+#else
+ mLockingThread = sThreadID;
+#endif
+ return true;
+}
+
void LLMutex::unlock()
{
if (mCount > 0)
diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h
index f51d985b5f..8c7143304f 100755
--- a/indra/llcommon/llthread.h
+++ b/indra/llcommon/llthread.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2004&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2010-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -156,7 +156,8 @@ public:
virtual ~LLMutex();
void lock(); // blocks
- void unlock();
+ bool trylock(); // non-blocking, returns true if lock held.
+ void unlock(); // undefined behavior when called on mutex not being held
bool isLocked(); // non-blocking, but does do a lock/unlock so not free
bool isSelfLocked(); //return true if locked in a same thread
U32 lockingThread() const; //get ID of locking thread
@@ -174,6 +175,8 @@ protected:
#endif
};
+//============================================================================
+
// Actually a condition/mutex pair (since each condition needs to be associated with a mutex).
class LL_COMMON_API LLCondition : public LLMutex
{
@@ -189,6 +192,8 @@ protected:
apr_thread_cond_t *mAPRCondp;
};
+//============================================================================
+
class LLMutexLock
{
public:
@@ -210,6 +215,43 @@ private:
//============================================================================
+// Scoped locking class similar in function to LLMutexLock but uses
+// the trylock() method to conditionally acquire lock without
+// blocking. Caller resolves the resulting condition by calling
+// the isLocked() method and either punts or continues as indicated.
+//
+// Mostly of interest to callers needing to avoid stalls who can
+// guarantee another attempt at a later time.
+
+class LLMutexTrylock
+{
+public:
+ LLMutexTrylock(LLMutex* mutex)
+ : mMutex(mutex),
+ mLocked(false)
+ {
+ if (mMutex)
+ mLocked = mMutex->trylock();
+ }
+
+ ~LLMutexTrylock()
+ {
+ if (mMutex && mLocked)
+ mMutex->unlock();
+ }
+
+ bool isLocked() const
+ {
+ return mLocked;
+ }
+
+private:
+ LLMutex* mMutex;
+ bool mLocked;
+};
+
+//============================================================================
+
void LLThread::lockData()
{
mDataLock->lock();
diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h
index 513de0605d..e73741217c 100755
--- a/indra/llcommon/lltimer.h
+++ b/indra/llcommon/lltimer.h
@@ -146,6 +146,13 @@ static inline time_t time_max()
}
}
+// These are really statics but they've been global for awhile
+// and they're material to other timing classes. If you are
+// not implementing a timer class, do not use these directly.
+extern LL_COMMON_API F64 gClockFrequency;
+extern LL_COMMON_API F64 gClockFrequencyInv;
+extern LL_COMMON_API F64 gClocksToMicroseconds;
+
// Correction factor used by time_corrected() above.
extern LL_COMMON_API S32 gUTCOffset;
diff --git a/indra/llcommon/tests/lldeadmantimer_test.cpp b/indra/llcommon/tests/lldeadmantimer_test.cpp
new file mode 100644
index 0000000000..7fd2dde6e0
--- /dev/null
+++ b/indra/llcommon/tests/lldeadmantimer_test.cpp
@@ -0,0 +1,628 @@
+/**
+ * @file lldeadmantimer_test.cpp
+ * @brief Tests for the LLDeadmanTimer class.
+ *
+ * $LicenseInfo:firstyear=2013&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2013, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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 "linden_common.h"
+
+#include "../lldeadmantimer.h"
+#include "../llsd.h"
+#include "../lltimer.h"
+
+#include "../test/lltut.h"
+
+// Convert between floating point time deltas and U64 time deltas.
+// Reflects an implementation detail inside lldeadmantimer.cpp
+
+static LLDeadmanTimer::time_type float_time_to_u64(F64 delta)
+{
+ return LLDeadmanTimer::time_type(delta * gClockFrequency);
+}
+
+static F64 u64_time_to_float(LLDeadmanTimer::time_type delta)
+{
+ return delta * gClockFrequencyInv;
+}
+
+
+namespace tut
+{
+
+struct deadmantimer_test
+{
+ deadmantimer_test()
+ {
+ // LLTimer internals updating
+ update_clock_frequencies();
+ }
+};
+
+typedef test_group<deadmantimer_test> deadmantimer_group_t;
+typedef deadmantimer_group_t::object deadmantimer_object_t;
+tut::deadmantimer_group_t deadmantimer_instance("LLDeadmanTimer");
+
+// Basic construction test and isExpired() call
+template<> template<>
+void deadmantimer_object_t::test<1>()
+{
+ {
+ // Without cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8));
+ LLDeadmanTimer timer(10.0, false);
+
+ ensure_equals("WOCM isExpired() returns false after ctor()", timer.isExpired(0, started, stopped, count), false);
+ ensure_approximately_equals("WOCM t1 - isExpired() does not modify started", started, F64(42.0), 2);
+ ensure_approximately_equals("WOCM t1 - isExpired() does not modify stopped", stopped, F64(97.0), 2);
+ ensure_equals("WOCM t1 - isExpired() does not modify count", count, U64L(8));
+ }
+
+ {
+ // With cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000);
+ LLDeadmanTimer timer(10.0, true);
+
+ ensure_equals("WCM isExpired() returns false after ctor()", timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), false);
+ ensure_approximately_equals("WCM t1 - isExpired() does not modify started", started, F64(42.0), 2);
+ ensure_approximately_equals("WCM t1 - isExpired() does not modify stopped", stopped, F64(97.0), 2);
+ ensure_equals("WCM t1 - isExpired() does not modify count", count, U64L(8));
+ ensure_equals("WCM t1 - isExpired() does not modify user_cpu", user_cpu, U64L(29000));
+ ensure_equals("WCM t1 - isExpired() does not modify sys_cpu", sys_cpu, U64L(57000));
+ }
+}
+
+
+// Construct with zero horizon - not useful generally but will be useful in testing
+template<> template<>
+void deadmantimer_object_t::test<2>()
+{
+ {
+ // Without cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8));
+ LLDeadmanTimer timer(0.0, false); // Zero is pre-expired
+
+ ensure_equals("WOCM isExpired() still returns false with 0.0 time ctor()",
+ timer.isExpired(0, started, stopped, count), false);
+ }
+
+ {
+ // With cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000);
+ LLDeadmanTimer timer(0.0, true); // Zero is pre-expired
+
+ ensure_equals("WCM isExpired() still returns false with 0.0 time ctor()",
+ timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), false);
+ }
+}
+
+
+// "pre-expired" timer - starting a timer with a 0.0 horizon will result in
+// expiration on first test.
+template<> template<>
+void deadmantimer_object_t::test<3>()
+{
+ {
+ // Without cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8));
+ LLDeadmanTimer timer(0.0, false);
+
+ timer.start(0);
+ ensure_equals("WOCM isExpired() returns true with 0.0 horizon time",
+ timer.isExpired(0, started, stopped, count), true);
+ ensure_approximately_equals("WOCM expired timer with no bell ringing has stopped == started", started, stopped, 8);
+ }
+ {
+ // With cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000);
+ LLDeadmanTimer timer(0.0, true);
+
+ timer.start(0);
+ ensure_equals("WCM isExpired() returns true with 0.0 horizon time",
+ timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), true);
+ ensure_approximately_equals("WCM expired timer with no bell ringing has stopped == started", started, stopped, 8);
+ }
+}
+
+
+// "pre-expired" timer - bell rings are ignored as we're already expired.
+template<> template<>
+void deadmantimer_object_t::test<4>()
+{
+ {
+ // Without cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8));
+ LLDeadmanTimer timer(0.0, false);
+
+ timer.start(0);
+ timer.ringBell(LLDeadmanTimer::getNow() + float_time_to_u64(1000.0), 1);
+ ensure_equals("WOCM isExpired() returns true with 0.0 horizon time after bell ring",
+ timer.isExpired(0, started, stopped, count), true);
+ ensure_approximately_equals("WOCM ringBell has no impact on expired timer leaving stopped == started", started, stopped, 8);
+ }
+ {
+ // With cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000);
+ LLDeadmanTimer timer(0.0, true);
+
+ timer.start(0);
+ timer.ringBell(LLDeadmanTimer::getNow() + float_time_to_u64(1000.0), 1);
+ ensure_equals("WCM isExpired() returns true with 0.0 horizon time after bell ring",
+ timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), true);
+ ensure_approximately_equals("WCM ringBell has no impact on expired timer leaving stopped == started", started, stopped, 8);
+ }
+}
+
+
+// start(0) test - unexpired timer reports unexpired
+template<> template<>
+void deadmantimer_object_t::test<5>()
+{
+ {
+ // Without cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8));
+ LLDeadmanTimer timer(10.0, false);
+
+ timer.start(0);
+ ensure_equals("WOCM isExpired() returns false after starting with 10.0 horizon time",
+ timer.isExpired(0, started, stopped, count), false);
+ ensure_approximately_equals("WOCM t5 - isExpired() does not modify started", started, F64(42.0), 2);
+ ensure_approximately_equals("WOCM t5 - isExpired() does not modify stopped", stopped, F64(97.0), 2);
+ ensure_equals("WOCM t5 - isExpired() does not modify count", count, U64L(8));
+ }
+ {
+ // With cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000);
+ LLDeadmanTimer timer(10.0, true);
+
+ timer.start(0);
+ ensure_equals("WCM isExpired() returns false after starting with 10.0 horizon time",
+ timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), false);
+ ensure_approximately_equals("WCM t5 - isExpired() does not modify started", started, F64(42.0), 2);
+ ensure_approximately_equals("WCM t5 - isExpired() does not modify stopped", stopped, F64(97.0), 2);
+ ensure_equals("WCM t5 - isExpired() does not modify count", count, U64L(8));
+ ensure_equals("WCM t5 - isExpired() does not modify user_cpu", user_cpu, U64L(29000));
+ ensure_equals("WCM t5 - isExpired() does not modify sys_cpu", sys_cpu, U64L(57000));
+ }
+}
+
+
+// start() test - start in the past but not beyond 1 horizon
+template<> template<>
+void deadmantimer_object_t::test<6>()
+{
+ {
+ // Without cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8));
+ LLDeadmanTimer timer(10.0, false);
+
+ // Would like to do subtraction on current time but can't because
+ // the implementation on Windows is zero-based. We wrap around
+ // the backside resulting in a large U64 number.
+
+ LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow());
+ LLDeadmanTimer::time_type now(the_past + float_time_to_u64(5.0));
+ timer.start(the_past);
+ ensure_equals("WOCM t6 - isExpired() returns false with 10.0 horizon time starting 5.0 in past",
+ timer.isExpired(now, started, stopped, count), false);
+ ensure_approximately_equals("WOCM t6 - isExpired() does not modify started", started, F64(42.0), 2);
+ ensure_approximately_equals("WOCM t6 - isExpired() does not modify stopped", stopped, F64(97.0), 2);
+ ensure_equals("WOCM t6 - isExpired() does not modify count", count, U64L(8));
+ }
+ {
+ // With cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000);
+ LLDeadmanTimer timer(10.0, true);
+
+ // Would like to do subtraction on current time but can't because
+ // the implementation on Windows is zero-based. We wrap around
+ // the backside resulting in a large U64 number.
+
+ LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow());
+ LLDeadmanTimer::time_type now(the_past + float_time_to_u64(5.0));
+ timer.start(the_past);
+ ensure_equals("WCM t6 - isExpired() returns false with 10.0 horizon time starting 5.0 in past",
+ timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false);
+ ensure_approximately_equals("WCM t6 - isExpired() does not modify started", started, F64(42.0), 2);
+ ensure_approximately_equals("WCM t6 - isExpired() does not modify stopped", stopped, F64(97.0), 2);
+ ensure_equals("t6 - isExpired() does not modify count", count, U64L(8));
+ ensure_equals("WCM t6 - isExpired() does not modify user_cpu", user_cpu, U64L(29000));
+ ensure_equals("WCM t6 - isExpired() does not modify sys_cpu", sys_cpu, U64L(57000));
+ }
+}
+
+
+// start() test - start in the past but well beyond 1 horizon
+template<> template<>
+void deadmantimer_object_t::test<7>()
+{
+ {
+ // Without cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8));
+ LLDeadmanTimer timer(10.0, false);
+
+ // Would like to do subtraction on current time but can't because
+ // the implementation on Windows is zero-based. We wrap around
+ // the backside resulting in a large U64 number.
+
+ LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow());
+ LLDeadmanTimer::time_type now(the_past + float_time_to_u64(20.0));
+ timer.start(the_past);
+ ensure_equals("WOCM t7 - isExpired() returns true with 10.0 horizon time starting 20.0 in past",
+ timer.isExpired(now,started, stopped, count), true);
+ ensure_approximately_equals("WOCM t7 - starting before horizon still gives equal started / stopped", started, stopped, 8);
+ }
+ {
+ // With cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000);
+ LLDeadmanTimer timer(10.0, true);
+
+ // Would like to do subtraction on current time but can't because
+ // the implementation on Windows is zero-based. We wrap around
+ // the backside resulting in a large U64 number.
+
+ LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow());
+ LLDeadmanTimer::time_type now(the_past + float_time_to_u64(20.0));
+ timer.start(the_past);
+ ensure_equals("WCM t7 - isExpired() returns true with 10.0 horizon time starting 20.0 in past",
+ timer.isExpired(now,started, stopped, count, user_cpu, sys_cpu), true);
+ ensure_approximately_equals("WOCM t7 - starting before horizon still gives equal started / stopped", started, stopped, 8);
+ }
+}
+
+
+// isExpired() test - results are read-once. Probes after first true are false.
+template<> template<>
+void deadmantimer_object_t::test<8>()
+{
+ {
+ // Without cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8));
+ LLDeadmanTimer timer(10.0, false);
+
+ // Would like to do subtraction on current time but can't because
+ // the implementation on Windows is zero-based. We wrap around
+ // the backside resulting in a large U64 number.
+
+ LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow());
+ LLDeadmanTimer::time_type now(the_past + float_time_to_u64(20.0));
+ timer.start(the_past);
+ ensure_equals("WOCM t8 - isExpired() returns true with 10.0 horizon time starting 20.0 in past",
+ timer.isExpired(now, started, stopped, count), true);
+
+ started = 42.0;
+ stopped = 97.0;
+ count = U64L(8);
+ ensure_equals("WOCM t8 - second isExpired() returns false after true",
+ timer.isExpired(now, started, stopped, count), false);
+ ensure_approximately_equals("WOCM t8 - 2nd isExpired() does not modify started", started, F64(42.0), 2);
+ ensure_approximately_equals("WOCM t8 - 2nd isExpired() does not modify stopped", stopped, F64(97.0), 2);
+ ensure_equals("WOCM t8 - 2nd isExpired() does not modify count", count, U64L(8));
+ }
+ {
+ // With cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000);
+ LLDeadmanTimer timer(10.0, true);
+
+ // Would like to do subtraction on current time but can't because
+ // the implementation on Windows is zero-based. We wrap around
+ // the backside resulting in a large U64 number.
+
+ LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow());
+ LLDeadmanTimer::time_type now(the_past + float_time_to_u64(20.0));
+ timer.start(the_past);
+ ensure_equals("WCM t8 - isExpired() returns true with 10.0 horizon time starting 20.0 in past",
+ timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true);
+
+ started = 42.0;
+ stopped = 97.0;
+ count = U64L(8);
+ user_cpu = 29000;
+ sys_cpu = 57000;
+ ensure_equals("WCM t8 - second isExpired() returns false after true",
+ timer.isExpired(now, started, stopped, count), false);
+ ensure_approximately_equals("WCM t8 - 2nd isExpired() does not modify started", started, F64(42.0), 2);
+ ensure_approximately_equals("WCM t8 - 2nd isExpired() does not modify stopped", stopped, F64(97.0), 2);
+ ensure_equals("WCM t8 - 2nd isExpired() does not modify count", count, U64L(8));
+ ensure_equals("WCM t8 - 2nd isExpired() does not modify user_cpu", user_cpu, U64L(29000));
+ ensure_equals("WCM t8 - 2nd isExpired() does not modify sys_cpu", sys_cpu, U64L(57000));
+ }
+}
+
+
+// ringBell() test - see that we can keep a timer from expiring
+template<> template<>
+void deadmantimer_object_t::test<9>()
+{
+ {
+ // Without cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8));
+ LLDeadmanTimer timer(5.0, false);
+
+ LLDeadmanTimer::time_type now(LLDeadmanTimer::getNow());
+ F64 real_start(u64_time_to_float(now));
+ timer.start(0);
+
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ ensure_equals("WOCM t9 - 5.0 horizon timer has not timed out after 10 1-second bell rings",
+ timer.isExpired(now, started, stopped, count), false);
+ F64 last_good_ring(u64_time_to_float(now));
+
+ // Jump forward and expire
+ now += float_time_to_u64(10.0);
+ ensure_equals("WOCM t9 - 5.0 horizon timer expires on 10-second jump",
+ timer.isExpired(now, started, stopped, count), true);
+ ensure_approximately_equals("WOCM t9 - started matches start() time", started, real_start, 4);
+ ensure_approximately_equals("WOCM t9 - stopped matches last ringBell() time", stopped, last_good_ring, 4);
+ ensure_equals("WOCM t9 - 10 good ringBell()s", count, U64L(10));
+ ensure_equals("WOCM t9 - single read only", timer.isExpired(now, started, stopped, count), false);
+ }
+ {
+ // With cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000);
+ LLDeadmanTimer timer(5.0, true);
+
+ LLDeadmanTimer::time_type now(LLDeadmanTimer::getNow());
+ F64 real_start(u64_time_to_float(now));
+ timer.start(0);
+
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ ensure_equals("WCM t9 - 5.0 horizon timer has not timed out after 10 1-second bell rings",
+ timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false);
+ F64 last_good_ring(u64_time_to_float(now));
+
+ // Jump forward and expire
+ now += float_time_to_u64(10.0);
+ ensure_equals("WCM t9 - 5.0 horizon timer expires on 10-second jump",
+ timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true);
+ ensure_approximately_equals("WCM t9 - started matches start() time", started, real_start, 4);
+ ensure_approximately_equals("WCM t9 - stopped matches last ringBell() time", stopped, last_good_ring, 4);
+ ensure_equals("WCM t9 - 10 good ringBell()s", count, U64L(10));
+ ensure_equals("WCM t9 - single read only", timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false);
+ }
+}
+
+
+// restart after expiration test - verify that restarts behave well
+template<> template<>
+void deadmantimer_object_t::test<10>()
+{
+ {
+ // Without cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8));
+ LLDeadmanTimer timer(5.0, false);
+
+ LLDeadmanTimer::time_type now(LLDeadmanTimer::getNow());
+ F64 real_start(u64_time_to_float(now));
+ timer.start(0);
+
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ ensure_equals("WOCM t10 - 5.0 horizon timer has not timed out after 10 1-second bell rings",
+ timer.isExpired(now, started, stopped, count), false);
+ F64 last_good_ring(u64_time_to_float(now));
+
+ // Jump forward and expire
+ now += float_time_to_u64(10.0);
+ ensure_equals("WOCM t10 - 5.0 horizon timer expires on 10-second jump",
+ timer.isExpired(now, started, stopped, count), true);
+ ensure_approximately_equals("WOCM t10 - started matches start() time", started, real_start, 4);
+ ensure_approximately_equals("WOCM t10 - stopped matches last ringBell() time", stopped, last_good_ring, 4);
+ ensure_equals("WOCM t10 - 10 good ringBell()s", count, U64L(10));
+ ensure_equals("WOCM t10 - single read only", timer.isExpired(now, started, stopped, count), false);
+
+ // Jump forward and restart
+ now += float_time_to_u64(1.0);
+ real_start = u64_time_to_float(now);
+ timer.start(now);
+
+ // Run a modified bell ring sequence
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ ensure_equals("WOCM t10 - 5.0 horizon timer has not timed out after 8 1-second bell rings",
+ timer.isExpired(now, started, stopped, count), false);
+ last_good_ring = u64_time_to_float(now);
+
+ // Jump forward and expire
+ now += float_time_to_u64(10.0);
+ ensure_equals("WOCM t10 - 5.0 horizon timer expires on 8-second jump",
+ timer.isExpired(now, started, stopped, count), true);
+ ensure_approximately_equals("WOCM t10 - 2nd started matches start() time", started, real_start, 4);
+ ensure_approximately_equals("WOCM t10 - 2nd stopped matches last ringBell() time", stopped, last_good_ring, 4);
+ ensure_equals("WOCM t10 - 8 good ringBell()s", count, U64L(8));
+ ensure_equals("WOCM t10 - single read only - 2nd start",
+ timer.isExpired(now, started, stopped, count), false);
+ }
+ {
+ // With cpu metrics
+ F64 started(42.0), stopped(97.0);
+ U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000);
+
+ LLDeadmanTimer timer(5.0, true);
+
+ LLDeadmanTimer::time_type now(LLDeadmanTimer::getNow());
+ F64 real_start(u64_time_to_float(now));
+ timer.start(0);
+
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ ensure_equals("WCM t10 - 5.0 horizon timer has not timed out after 10 1-second bell rings",
+ timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false);
+ F64 last_good_ring(u64_time_to_float(now));
+
+ // Jump forward and expire
+ now += float_time_to_u64(10.0);
+ ensure_equals("WCM t10 - 5.0 horizon timer expires on 10-second jump",
+ timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true);
+ ensure_approximately_equals("WCM t10 - started matches start() time", started, real_start, 4);
+ ensure_approximately_equals("WCM t10 - stopped matches last ringBell() time", stopped, last_good_ring, 4);
+ ensure_equals("WCM t10 - 10 good ringBell()s", count, U64L(10));
+ ensure_equals("WCM t10 - single read only", timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false);
+
+ // Jump forward and restart
+ now += float_time_to_u64(1.0);
+ real_start = u64_time_to_float(now);
+ timer.start(now);
+
+ // Run a modified bell ring sequence
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ now += float_time_to_u64(1.0);
+ timer.ringBell(now, 1);
+ ensure_equals("WCM t10 - 5.0 horizon timer has not timed out after 8 1-second bell rings",
+ timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false);
+ last_good_ring = u64_time_to_float(now);
+
+ // Jump forward and expire
+ now += float_time_to_u64(10.0);
+ ensure_equals("WCM t10 - 5.0 horizon timer expires on 8-second jump",
+ timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true);
+ ensure_approximately_equals("WCM t10 - 2nd started matches start() time", started, real_start, 4);
+ ensure_approximately_equals("WCM t10 - 2nd stopped matches last ringBell() time", stopped, last_good_ring, 4);
+ ensure_equals("WCM t10 - 8 good ringBell()s", count, U64L(8));
+ ensure_equals("WCM t10 - single read only - 2nd start",
+ timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false);
+ }
+}
+
+
+
+} // end namespace tut
diff --git a/indra/llcommon/tests/llprocinfo_test.cpp b/indra/llcommon/tests/llprocinfo_test.cpp
new file mode 100644
index 0000000000..12d5a695ee
--- /dev/null
+++ b/indra/llcommon/tests/llprocinfo_test.cpp
@@ -0,0 +1,91 @@
+/**
+ * @file llprocinfo_test.cpp
+ * @brief Tests for the LLProcInfo class.
+ *
+ * $LicenseInfo:firstyear=2013&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2013, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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 "linden_common.h"
+
+#include "../llprocinfo.h"
+
+#include "../test/lltut.h"
+#include "../lltimer.h"
+
+
+static const LLProcInfo::time_type bad_user(289375U), bad_system(275U);
+
+
+namespace tut
+{
+
+struct procinfo_test
+{
+ procinfo_test()
+ {
+ }
+};
+
+typedef test_group<procinfo_test> procinfo_group_t;
+typedef procinfo_group_t::object procinfo_object_t;
+tut::procinfo_group_t procinfo_instance("LLProcInfo");
+
+
+// Basic invocation works
+template<> template<>
+void procinfo_object_t::test<1>()
+{
+ LLProcInfo::time_type user(bad_user), system(bad_system);
+
+ set_test_name("getCPUUsage() basic function");
+
+ LLProcInfo::getCPUUsage(user, system);
+
+ ensure_not_equals("getCPUUsage() writes to its user argument", user, bad_user);
+ ensure_not_equals("getCPUUsage() writes to its system argument", system, bad_system);
+}
+
+
+// Time increases
+template<> template<>
+void procinfo_object_t::test<2>()
+{
+ LLProcInfo::time_type user(bad_user), system(bad_system);
+ LLProcInfo::time_type user2(bad_user), system2(bad_system);
+
+ set_test_name("getCPUUsage() increases over time");
+
+ LLProcInfo::getCPUUsage(user, system);
+
+ for (int i(0); i < 100000; ++i)
+ {
+ ms_sleep(0);
+ }
+
+ LLProcInfo::getCPUUsage(user2, system2);
+
+ ensure_equals("getCPUUsage() user value doesn't decrease over time", user2 >= user, true);
+ ensure_equals("getCPUUsage() system value doesn't decrease over time", system2 >= system, true);
+}
+
+
+} // end namespace tut
diff --git a/indra/llcorehttp/README.Linden b/indra/llcorehttp/README.Linden
new file mode 100644
index 0000000000..eb6ccab3bc
--- /dev/null
+++ b/indra/llcorehttp/README.Linden
@@ -0,0 +1,671 @@
+
+
+
+1. HTTP Fetching in 15 Minutes
+
+ Let's start with a trivial working example. You'll need a throwaway
+ build of the viewer. And we'll use indra/newview/llappviewer.cpp as
+ the host module for these hacks.
+
+ First, add some headers:
+
+
+ #include "httpcommon.h"
+ #include "httprequest.h"
+ #include "httphandler.h"
+
+
+ You'll need to derive a class from HttpHandler (not HttpHandle).
+ This is used to deliver notifications of HTTP completion to your
+ code. Place it near the top, before LLDeferredTaskList, say:
+
+
+ class MyHandler : public LLCore::HttpHandler
+ {
+ public:
+ MyHandler()
+ : LLCore::HttpHandler()
+ {}
+
+ virtual void onCompleted(LLCore::HttpHandle /* handle */,
+ LLCore::HttpResponse * /* response */)
+ {
+ LL_INFOS("Hack") << "It is happening again." << LL_ENDL;
+
+ delete this; // Last statement
+ }
+ };
+
+
+ Add some statics up there as well:
+
+
+ // Our request object. Allocate during initialiation.
+ static LLCore::HttpRequest * my_request(NULL);
+
+ // The policy class for HTTP traffic.
+ // Use HttpRequest::DEFAULT_POLICY_ID, but DO NOT SHIP WITH THIS VALUE!!
+ static LLCore::HttpRequest::policy_t my_policy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
+
+ // Priority for HTTP requests. Use 0U.
+ static LLCore::HttpRequest::priority_t my_priority(0U);
+
+
+ In LLAppViewer::init() after mAppCoreHttp.init(), create a request object:
+
+
+ my_request = new LLCore::HttpRequest();
+
+
+ In LLAppViewer::mainLoop(), just before entering the while loop,
+ we'll kick off one HTTP request:
+
+
+ // Construct a handler object (we'll use the heap this time):
+ MyHandler * my_handler = new MyHandler;
+
+ // Issue a GET request to 'http://www.example.com/' kicking off
+ // all the I/O, retry logic, etc.
+ LLCore::HttpHandle handle;
+ handle = my_request->requestGet(my_policy,
+ my_priority,
+ "http://www.example.com/",
+ NULL,
+ NULL,
+ my_handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ LL_WARNS("Hack") << "Failed to launch HTTP request. Try again."
+ << LL_ENDL;
+ }
+
+
+ Finally, arrange to periodically call update() on the request object
+ to find out when the request completes. This will be done by
+ calling the onCompleted() method with status information and
+ response data from the HTTP operation. Add this to the
+ LLAppViewer::idle() method after the ping:
+
+
+ my_request->update(0);
+
+
+ That's it. Build it, run it and watch the log file. You should get
+ the "It is happening again." message indicating that the HTTP
+ operation completed in some manner.
+
+
+2. What Does All That Mean
+
+ MyHandler/HttpHandler. This class replaces the Responder-style in
+ legacy code. One method is currently defined. It is used for all
+ request completions, successful or failed:
+
+
+ void onCompleted(LLCore::HttpHandle /* handle */,
+ LLCore::HttpResponse * /* response */);
+
+
+ The onCompleted() method is invoked as a callback during calls to
+ HttpRequest::update(). All I/O is completed asynchronously in
+ another thread. But notifications are polled by calling update()
+ and invoking a handler for completed requests.
+
+ In this example, the invocation also deletes the handler (which is
+ never referenced by the llcorehttp code again). But other
+ allocation models are possible including handlers shared by many
+ requests, stack-based handlers and handlers mixed in with other,
+ unrelated classes.
+
+ LLCore::HttpRequest(). Instances of this class are used to request
+ all major functions of the library. Initialization, starting
+ requests, delivering final notification of completion and various
+ utility operations are all done via instances. There is one very
+ important rule for instances:
+
+ Request objects may NOT be shared between threads.
+
+ my_priority. The APIs support the idea of priority ordering of
+ requests but it hasn't been implemented and the hope is that this
+ will become useless and removed from the interface. Use 0U except
+ as noted.
+
+ my_policy. This is an important one. This library attempts to
+ manage TCP connection usage more rigorously than in the past. This
+ is done by issuing requests to a queue that has various settable
+ properties. These establish connection usage for the queue as well
+ as how queues compete with one another. (This is patterned after
+ class-based queueing used in various networking stacks.) Several
+ classes are pre-defined. Deciding when to use an existing class and
+ when to create a new one will determine what kind of experience
+ users have. We'll pick up this question in detail below.
+
+ requestGet(). Issues an ordinary HTTP GET request to a given URL
+ and associating the request with a policy class, a priority and an
+ response handler. Two additional arguments, not used here, allow
+ for additional headers on the request and for per-request options.
+ If successful, the call returns a handle whose value is other than
+ LLCORE_HTTP_HANDLE_INVALID. The HTTP operation is then performed
+ asynchronously by another thread without any additional work by the
+ caller. If the handle returned is invalid, you can get the status
+ code by calling my_request->getStatus().
+
+ update(). To get notification that the request has completed, a
+ call to update() will invoke onCompleted() methods.
+
+
+3. Refinements, Necessary and Otherwise
+
+ MyHandler::onCompleted(). You'll want to do something useful with
+ your response. Distinguish errors from successes and getting the
+ response body back in some form.
+
+ Add a new header:
+
+
+ #include "bufferarray.h"
+
+
+ Replace the existing MyHandler::onCompleted() definition with:
+
+
+ virtual void onCompleted(LLCore::HttpHandle /* handle */,
+ LLCore::HttpResponse * response)
+ {
+ LLCore::HttpStatus status = response->getStatus();
+ if (status)
+ {
+ // Successful request. Try to fetch the data
+ LLCore::BufferArray * data = response->getBody();
+
+ if (data && data->size())
+ {
+ // There's some data. A BufferArray is a linked list
+ // of buckets. We'll create a linear buffer and copy
+ // the data into it.
+ size_t data_len = data->size();
+ char * data_blob = new char [data_len + 1];
+ data->read(0, data_blob, data_len);
+ data_blob[data_len] = '\0';
+
+ // Process the data now in NUL-terminated string.
+ // Needs more scrubbing but this will do.
+ LL_INFOS("Hack") << "Received: " << data_blob << LL_ENDL;
+
+ // Free the temporary data
+ delete [] data_blob;
+ }
+ }
+ else
+ {
+ // Something went wrong. Translate the status to
+ // a meaningful message.
+ LL_WARNS("Hack") << "HTTP GET failed. Status: "
+ << status.toTerseString()
+ << ", Reason: " << status.toString()
+ << LL_ENDL;
+ }
+
+ delete this; // Last statement
+ }
+
+
+ HttpHeaders. The header file "httprequest.h" documents the expected
+ important headers that will go out with the request. You can add to
+ these by including an HttpHeaders object with the requestGet() call.
+ These are typically setup once as part of init rather than
+ dynamically created.
+
+ Add another header:
+
+
+ #include "httpheaders.h"
+
+
+ In LLAppViewer::mainLoop(), add this alongside the allocation of
+ my_handler:
+
+
+ // Additional headers for all requests
+ LLCore::HttpHeaders * my_headers = new LLCore::HttpHeaders();
+ my_headers->append("Accept", "text/html, application/llsd+xml");
+
+
+ HttpOptions. Options are similar and include a mix of value types.
+ One interesting per-request option is the trace setting. This
+ enables various debug-type messages in the log file that show the
+ progress of the request through the library. It takes values from
+ zero to three with higher values giving more verbose logging. We'll
+ use '2' and this will also give us a chance to verify that
+ HttpHeaders works as expected.
+
+ Same as above, a new header:
+
+
+ #include "httpoptions.h"
+
+
+ And in LLAppView::mainLoop():
+
+
+ // Special options for requests
+ LLCore::HttpOptions * my_options = new LLCore::HttpOptions();
+ my_options->setTrace(2);
+
+
+ Now let's put that all together into a more complete requesting
+ sequence. Replace the existing invocation of requestGet() with this
+ slightly more elaborate block:
+
+
+ LLCore::HttpHandle handle;
+ handle = my_request->requestGet(my_policy,
+ my_priority,
+ "http://www.example.com/",
+ my_options,
+ my_headers,
+ my_handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ LLCore::HttpStatus status = my_request->getStatus();
+
+ LL_WARNS("Hack") << "Failed to request HTTP GET. Status: "
+ << status.toTerseString()
+ << ", Reason: " << status.toString()
+ << LL_ENDL;
+
+ delete my_handler; // No longer needed.
+ my_handler = NULL;
+ }
+
+
+ Build, run and examine the log file. You'll get some new data with
+ this run. First, you should get the www.example.com home page
+ content:
+
+
+----------------------------------------------------------------------------
+2013-09-17T20:26:51Z INFO: MyHandler::onCompleted: Received: <!doctype html>
+<html>
+<head>
+ <title>Example Domain</title>
+
+ <meta charset="utf-8" />
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <style type="text/css">
+ body {
+ background-color: #f0f0f2;
+ margin: 0;
+ padding: 0;
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+ }
+ div {
+ width: 600px;
+ margin: 5em auto;
+ padding: 50px;
+ background-color: #fff;
+ border-radius: 1em;
+ }
+ a:link, a:visited {
+ color: #38488f;
+ text-decoration: none;
+ }
+ @media (max-width: 700px) {
+ body {
+ background-color: #fff;
+ }
+ div {
+ width: auto;
+ margin: 0 auto;
+ border-radius: 0;
+ padding: 1em;
+ }
+ }
+ </style>
+</head>
+
+<body>
+<div>
+ <h1>Example Domain</h1>
+ <p>This domain is established to be used for illustrative examples in documents. You may use this
+ domain in examples without prior coordination or asking for permission.</p>
+ <p><a href="http://www.iana.org/domains/example">More information...</a></p>
+</div>
+</body>
+</html>
+----------------------------------------------------------------------------
+
+
+ You'll also get a detailed trace of the HTTP operation itself. Note
+ the HEADEROUT line which shows the additional header added to the
+ request.
+
+
+----------------------------------------------------------------------------
+HttpService::processRequestQueue: TRACE, FromRequestQueue, Handle: 086D3148
+HttpLibcurl::addOp: TRACE, ToActiveQueue, Handle: 086D3148, Actives: 0, Readies: 0
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: TEXT, Data: About to connect() to www.example.com port 80 (#0)
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: TEXT, Data: Trying 93.184.216.119...
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: TEXT, Data: Connected to www.example.com (93.184.216.119) port 80 (#0)
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: TEXT, Data: Connected to www.example.com (93.184.216.119) port 80 (#0)
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADEROUT, Data: GET / HTTP/1.1 Host: www.example.com Accept-Encoding: deflate, gzip Connection: keep-alive Keep-alive: 300 Accept: text/html, application/llsd+xml
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: HTTP/1.1 200 OK
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Accept-Ranges: bytes
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Cache-Control: max-age=604800
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Content-Type: text/html
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Date: Tue, 17 Sep 2013 20:26:56 GMT
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Etag: "3012602696"
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Expires: Tue, 24 Sep 2013 20:26:56 GMT
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Server: ECS (ewr/1590)
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: X-Cache: HIT
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: x-ec-custom-error: 1
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Content-Length: 1270
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data:
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: DATAIN, Data: 256 Bytes
+HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: TEXT, Data: Connection #0 to host www.example.com left intact
+HttpLibcurl::completeRequest: TRACE, RequestComplete, Handle: 086D3148, Status: Http_200
+HttpOperation::addAsReply: TRACE, ToReplyQueue, Handle: 086D3148
+----------------------------------------------------------------------------
+
+
+4. What Does All That Mean, Part 2
+
+ HttpStatus. The HttpStatus object encodes errors from libcurl, the
+ library itself and HTTP status values. It does this to avoid
+ collapsing all non-HTTP error into a single '499' HTTP status and to
+ make errors distinct.
+
+ To aid programming, the usual bool conversions are available so that
+ you can write 'if (status)' and the expected thing will happen
+ whether it's an HTTP, libcurl or library error. There's also
+ provision to override the treatment of HTTP errors (making 404 a
+ success, say).
+
+ Share data, don't copy it. The library was started with the goal of
+ avoiding data copies as much as possible. Instead, read-only data
+ sharing across threads with atomic reference counts is used for a
+ number of data types. These currently are:
+
+ * BufferArray. Linked list of data blocks/HTTP bodies.
+ * HttpHeaders. Shared headers for both requests and responses.
+ * HttpOptions. Request-only data modifying HTTP behavior.
+ * HttpResponse. HTTP response description given to onCompleted.
+
+ Using objects of these types requires a few rules:
+
+ * Constructor always gives a reference to caller.
+ * References are dropped with release() not delete.
+ * Additional references may be taken out with addRef().
+ * Unless otherwise stated, once an object is shared with another
+ thread it should be treated as read-only. There's no
+ synchronization on the objects themselves.
+
+ HttpResponse. You'll encounter this mainly in onCompleted() methods.
+ Commonly-used interfaces on this object:
+
+ * getStatus() to return the final status of the request.
+ * getBody() to retrieve the response body which may be NULL or
+ zero-length.
+ * getContentType() to return the value of the 'Content-Type'
+ header or an empty string if none was sent.
+
+ This is a reference-counted object so you can call addRef() on it
+ and hold onto the response for an arbitrary time. But you'll
+ usually just call a few methods and return from onCompleted() whose
+ caller will release the object.
+
+ BufferArray. The core data representation for request and response
+ bodies. In HTTP responses, it's fetched with the getBody() method
+ and may be NULL or non-NULL with zero length. All successful data
+ handling should check both conditions before attempting to fetch
+ data from the object. Data access model uses simple read/write
+ semantics:
+
+ * append()
+ * size()
+ * read()
+ * write()
+
+ (There is a more sophisticated stream adapter that extends these
+ methods and will be covered below.) So, one way to retrieve data
+ from a request is as follows:
+
+
+ LLCore::BufferArray * data = response->getBody();
+ if (data && data->size())
+ {
+ size_t data_len = data->size();
+ char * data_blob = new char [data_len + 1];
+ data->read(0, data_blob, data_len);
+
+
+ HttpOptions and HttpResponse. Really just simple containers of POD
+ and std::string pairs. But reference counted and the rule about not
+ modifying after sharing must be followed. You'll have the urge to
+ change options dynamically at some point. And you'll try to do that
+ by just writing new values to the shared object. And in tests
+ everything will appear to work. Then you ship and people in the
+ real world start hitting read/write races in strings and then crash.
+ Don't be lazy.
+
+ HttpHandle. Uniquely identifies a request and can be used to
+ identify it in an onCompleted() method or cancel it if it's still
+ queued. But as soon as a request's onCompleted() invocation
+ returns, the handle becomes invalid and may be reused immediately
+ for new requests. Don't hold on to handles after notification.
+
+
+5. And Still More Refinements
+
+ (Note: The following refinements are just code fragments. They
+ don't directly fit into the working example above. But they
+ demonstrate several idioms you'll want to copy.)
+
+ LLSD, std::streambuf, std::iostream. The read(), write() and
+ append() methods may be adequate for your purposes. But we use a
+ lot of LLSD. Its interfaces aren't particularly compatible with
+ BufferArray. And so two adapters are available to give
+ stream-like behaviors: BufferArrayStreamBuf and BufferArrayStream,
+ which implement the std::streambuf and std::iostream interfaces,
+ respectively.
+
+ A std::streambuf interface isn't something you'll want to use
+ directly. Instead, you'll use the much friendlier std::iostream
+ interface found in BufferArrayStream. This adapter gives you all
+ the '>>' and '<<' operators you'll want as well as working
+ directly with the LLSD conversion operators.
+
+ Some new headers:
+
+
+ #include "bufferstream.h"
+ #include "llsdserialize.h"
+
+
+ And an updated fragment based on onCompleted() above:
+
+
+ // Successful request. Try to fetch the data
+ LLCore::BufferArray * data = response->getBody();
+ LLSD resp_llsd;
+
+ if (data && data->size())
+ {
+ // There's some data and we expect this to be
+ // LLSD. Checking of content type and validation
+ // during parsing would be admirable additions.
+ // But we'll forgo that now.
+ LLCore::BufferArrayStream data_stream(data);
+ LLSDSerialize::fromXML(resp_llsd, data_stream);
+ }
+ LL_INFOS("Hack") << "LLSD Received: " << resp_llsd << LL_ENDL;
+ }
+ else
+ {
+
+
+ Converting an LLSD object into an XML stream stored in a
+ BufferArray is just the reverse of the above:
+
+
+ BufferArray * data = new BufferArray();
+ LLCore::BufferArrayStream data_stream(data);
+
+ LLSD src_llsd;
+ src_llsd["foo"] = "bar";
+
+ LLSDSerialize::toXML(src_llsd, data_stream);
+
+ // 'data' now contains an XML payload and can be sent
+ // to a web service using the requestPut() or requestPost()
+ // methods.
+ ... requestPost(...);
+
+ // And don't forget to release the BufferArray.
+ data->release();
+ data = NULL;
+
+
+ LLSD will often go hand-in-hand with BufferArray and data
+ transport. But you can also do all the streaming I/O you'd expect
+ of a std::iostream object:
+
+
+ BufferArray * data = new BufferArray();
+ LLCore::BufferArrayStream data_stream(data);
+
+ data_stream << "Hello, World!" << 29.4 << '\n';
+ std::string str;
+ data_stream >> str;
+ std::cout << str << std::endl;
+
+ data->release();
+ // Actual delete will occur when 'data_stream'
+ // falls out of scope and is destructed.
+
+
+ Scoping objects and cleaning up. The examples haven't bothered
+ with cleanup of objects that are no longer needed. Instead, most
+ objects have been allocated as if they were global and eternal.
+ You'll put the objects in more appropriate feature objects and
+ clean them up as a group. Here's a checklist for actions you may
+ need to take on cleanup:
+
+ * Call delete on:
+ o HttpHandlers created on the heap
+ o HttpRequest objects
+ * Call release() on:
+ o BufferArray objects
+ o HttpHeaders objects
+ o HttpOptions objects
+ o HttpResponse objects
+
+ On program exit, as threads wind down, the library continues to
+ operate safely. Threads don't interact via the library and even
+ dangling references to HttpHandler objects are safe. If you don't
+ call HttpRequest::update(), handler references are never
+ dereferenced.
+
+ You can take a more thorough approach to wind-down. Keep a list
+ of HttpHandles (not HttpHandlers) of outstanding requests. For
+ each of these, call HttpRequest::requestCancel() to cancel the
+ operation. (Don't add the cancel requests' handled to the list.)
+ This will cancel the outstanding requests that haven't completed.
+ Canceled or completed, all requests will queue notifications. You
+ can now cycle calling update() discarding responses. Continue
+ until all requests notify or a few seconds have passed.
+
+ Global startup and shutdown is handled in the viewer. But you can
+ learn about it in the code or in the documentation in the headers.
+
+
+6. Choosing a Policy Class
+
+ Now it's time to get rid of the default policy class. Take a look
+ at the policy class definitions in newview/llappcorehttp.h.
+ Ideally, you'll find one that's compatible with what you're doing.
+ Some of the compatibility guidelines are:
+
+ * Destination: Pair of host and port. Mixing requests with
+ different destinations may cause more connection setup and tear
+ down.
+
+ * Method: http or https. Usually moot given destination. But
+ mixing these may also cause connection churn.
+
+ * Transfer size: If you're moving 100MB at a time and you make your
+ requests to the same policy class as a lot of small, fast event
+ information that fast traffic is going to get stuck behind you
+ and someone's experience is going to be miserable.
+
+ * Long poll requests: These are long-lived, must- do operations.
+ They have a special home called AP_LONG_POLL.
+
+ * Concurrency: High concurrency (5 or more) and large transfer
+ sizes are incompatible. Another head-of-the-line problem. High
+ concurrency is tolerated when it's desired to get maximal
+ throughput. Mesh and texture downloads, for example.
+
+ * Pipelined: If your requests are not idempotent, stay away from
+ anything marked 'soon' or 'yes'. Hidden retries may be a
+ problem for you. For now, would also recommend keeping PUT and
+ POST requests out of classes that may be pipelined. Support for
+ that is still a bit new.
+
+ If you haven't found a compatible match, you can either create a
+ new class (llappcorehttp.*) or just use AP_DEFAULT, the catchall
+ class when all else fails. Inventory query operations might be a
+ candidate for a new class that supported pipelining on https:.
+ Same with display name lookups and other bursty-at-login
+ operations. For other things, AP_DEFAULT will do what it can and
+ will, in some way or another, tolerate any usage. Whether the
+ users' experiences are good are for you to determine.
+
+
+7. FAQ
+
+ Q1. What do these policy classes achieve?
+
+ A1. Previously, HTTP-using code in the viewer was written as if
+ it were some isolated, local operation that didn't have to
+ consider resources, contention or impact on services and the
+ larger environment. The result was an application with on the
+ order of 100 HTTP launch points in its codebase that could create
+ dozens or even 100's of TCP connections zeroing in on grid
+ services and disrupting networking equipment, web services and
+ innocent users. The use of policy classes (modeled on
+ http://en.wikipedia.org/wiki/Class-based_queueing) is a means to
+ restrict connection concurrency, good and necessary in itself. In
+ turn, that reduces demands on an expensive resource (connection
+ setup and concurrency) which relieves strain on network points.
+ That enables connection keepalive and opportunites for true
+ improvements in throughput and user experience.
+
+ Another aspect of the classes is that they give some control over
+ how competing demands for the network will be apportioned. If
+ mesh fetches, texture fetches and inventory queries are all being
+ made at once, the relative weights of their classes' concurrency
+ limits established that apportioning. We now have an opportunity
+ to balance the entire viewer system.
+
+ Q2. How's that data sharing with refcounts working for you?
+
+ A2. Meh. It does reduce memory churn and the frequency at which
+ free blocks must be moved between threads. But it's also a design
+ for static configuration and dynamic reconfiguration (not
+ requiring a restart) is favored. Creating new options for every
+ request isn't too bad, it a sequence of "new, fill, request,
+ release" for each requested operation. That in contrast to doing
+ the "new, fill, release" at startup. The bad comes in getting at
+ the source data. One rule in this work was "no new thread
+ problems." And one source for those is pulling setting values out
+ of gSettings in threads. None of that is thread safe though we
+ tend to get away with it.
+
+ Q3. What needs to be done?
+
+ A3. There's a To-Do list in _httpinternal.h. It has both large
+ and small projects here if someone would like to try changes.
diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h
index 008e4fd95c..f80d7f60f5 100755
--- a/indra/llcorehttp/_httpinternal.h
+++ b/indra/llcorehttp/_httpinternal.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -36,7 +36,8 @@
// General library to-do list
//
// - Implement policy classes. Structure is mostly there just didn't
-// need it for the first consumer.
+// need it for the first consumer. [Classes are there. More
+// advanced features, like borrowing, aren't there yet.]
// - Consider Removing 'priority' from the request interface. Its use
// in an always active class can lead to starvation of low-priority
// requests. Requires coodination of priority values across all
@@ -46,6 +47,7 @@
// may not really need it.
// - Set/get for global policy and policy classes is clumsy. Rework
// it heading in a direction that allows for more dynamic behavior.
+// [Mostly fixed]
// - Move HttpOpRequest::prepareRequest() to HttpLibcurl for the
// pedantic.
// - Update downloader and other long-duration services are going to
@@ -64,6 +66,12 @@
// This won't help in the face of the router problems we've looked
// at, however. Detect starvation due to UDP activity and provide
// feedback to it.
+// - Change the transfer timeout scheme. We're less interested in
+// absolute time, in most cases, than in continuous progress.
+// - Many of the policy class settings are currently applied to the
+// entire class. Some, like connection limits, would be better
+// applied to each destination target making multiple targets
+// independent.
//
// Integration to-do list
// - LLTextureFetch still needs a major refactor. The use of
@@ -73,7 +81,6 @@
// the main source file.
// - Expand areas of usage eventually leading to the removal of LLCurl.
// Rough order of expansion:
-// . Mesh fetch
// . Avatar names
// . Group membership lists
// . Caps access in general
@@ -97,8 +104,8 @@ namespace LLCore
{
// Maxium number of policy classes that can be defined.
-// *TODO: Currently limited to the default class, extend.
-const int HTTP_POLICY_CLASS_LIMIT = 1;
+// *TODO: Currently limited to the default class + 1, extend.
+const int HTTP_POLICY_CLASS_LIMIT = 8;
// Debug/informational tracing. Used both
// as a global option and in per-request traces.
@@ -129,6 +136,7 @@ const int HTTP_REDIRECTS_DEFAULT = 10;
// Retries and time-on-queue are not included and aren't
// accounted for.
const long HTTP_REQUEST_TIMEOUT_DEFAULT = 30L;
+const long HTTP_REQUEST_XFER_TIMEOUT_DEFAULT = 0L;
const long HTTP_REQUEST_TIMEOUT_MIN = 0L;
const long HTTP_REQUEST_TIMEOUT_MAX = 3600L;
@@ -137,6 +145,11 @@ const int HTTP_CONNECTION_LIMIT_DEFAULT = 8;
const int HTTP_CONNECTION_LIMIT_MIN = 1;
const int HTTP_CONNECTION_LIMIT_MAX = 256;
+// Miscellaneous defaults
+const long HTTP_PIPELINING_DEFAULT = 0L;
+const bool HTTP_USE_RETRY_AFTER_DEFAULT = true;
+const long HTTP_THROTTLE_RATE_DEFAULT = 0L;
+
// Tuning parameters
// Time worker thread sleeps after a pass through the
diff --git a/indra/llcorehttp/_httplibcurl.cpp b/indra/llcorehttp/_httplibcurl.cpp
index 6fe0bfc7d1..fc257fb0c1 100755
--- a/indra/llcorehttp/_httplibcurl.cpp
+++ b/indra/llcorehttp/_httplibcurl.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -41,7 +41,8 @@ namespace LLCore
HttpLibcurl::HttpLibcurl(HttpService * service)
: mService(service),
mPolicyCount(0),
- mMultiHandles(NULL)
+ mMultiHandles(NULL),
+ mActiveHandles(NULL)
{}
@@ -77,6 +78,9 @@ void HttpLibcurl::shutdown()
delete [] mMultiHandles;
mMultiHandles = NULL;
+
+ delete [] mActiveHandles;
+ mActiveHandles = NULL;
}
mPolicyCount = 0;
@@ -90,9 +94,12 @@ void HttpLibcurl::start(int policy_count)
mPolicyCount = policy_count;
mMultiHandles = new CURLM * [mPolicyCount];
+ mActiveHandles = new int [mPolicyCount];
+
for (int policy_class(0); policy_class < mPolicyCount; ++policy_class)
{
mMultiHandles[policy_class] = curl_multi_init();
+ mActiveHandles[policy_class] = 0;
}
}
@@ -110,8 +117,10 @@ HttpService::ELoopSpeed HttpLibcurl::processTransport()
// Give libcurl some cycles to do I/O & callbacks
for (int policy_class(0); policy_class < mPolicyCount; ++policy_class)
{
- if (! mMultiHandles[policy_class])
+ if (! mActiveHandles[policy_class] || ! mMultiHandles[policy_class])
+ {
continue;
+ }
int running(0);
CURLMcode status(CURLM_CALL_MULTI_PERFORM);
@@ -132,12 +141,10 @@ HttpService::ELoopSpeed HttpLibcurl::processTransport()
CURL * handle(msg->easy_handle);
CURLcode result(msg->data.result);
- if (completeRequest(mMultiHandles[policy_class], handle, result))
- {
- // Request is still active, don't get too sleepy
- ret = HttpService::NORMAL;
- }
- handle = NULL; // No longer valid on return
+ completeRequest(mMultiHandles[policy_class], handle, result);
+ handle = NULL; // No longer valid on return
+ ret = HttpService::NORMAL; // If anything completes, we may have a free slot.
+ // Turning around quickly reduces connection gap by 7-10mS.
}
else if (CURLMSG_NONE == msg->msg)
{
@@ -193,6 +200,7 @@ void HttpLibcurl::addOp(HttpOpRequest * op)
// On success, make operation active
mActiveOps.insert(op);
+ ++mActiveHandles[op->mReqPolicy];
}
@@ -214,6 +222,7 @@ bool HttpLibcurl::cancel(HttpHandle handle)
// Drop references
mActiveOps.erase(it);
+ --mActiveHandles[op->mReqPolicy];
op->release();
return true;
@@ -240,7 +249,7 @@ void HttpLibcurl::cancelRequest(HttpOpRequest * op)
{
LL_INFOS("CoreHttp") << "TRACE, RequestCanceled, Handle: "
<< static_cast<HttpHandle>(op)
- << ", Status: " << op->mStatus.toHex()
+ << ", Status: " << op->mStatus.toTerseString()
<< LL_ENDL;
}
@@ -275,6 +284,7 @@ bool HttpLibcurl::completeRequest(CURLM * multi_handle, CURL * handle, CURLcode
// Deactivate request
mActiveOps.erase(it);
+ --mActiveHandles[op->mReqPolicy];
op->mCurlActive = false;
// Set final status of request if it hasn't failed by other mechanisms yet
@@ -316,7 +326,7 @@ bool HttpLibcurl::completeRequest(CURLM * multi_handle, CURL * handle, CURLcode
{
LL_INFOS("CoreHttp") << "TRACE, RequestComplete, Handle: "
<< static_cast<HttpHandle>(op)
- << ", Status: " << op->mStatus.toHex()
+ << ", Status: " << op->mStatus.toTerseString()
<< LL_ENDL;
}
@@ -336,19 +346,9 @@ int HttpLibcurl::getActiveCount() const
int HttpLibcurl::getActiveCountInClass(int policy_class) const
{
- int count(0);
-
- for (active_set_t::const_iterator iter(mActiveOps.begin());
- mActiveOps.end() != iter;
- ++iter)
- {
- if ((*iter)->mReqPolicy == policy_class)
- {
- ++count;
- }
- }
-
- return count;
+ llassert_always(policy_class < mPolicyCount);
+
+ return mActiveHandles ? mActiveHandles[policy_class] : 0;
}
@@ -359,12 +359,17 @@ int HttpLibcurl::getActiveCountInClass(int policy_class) const
struct curl_slist * append_headers_to_slist(const HttpHeaders * headers, struct curl_slist * slist)
{
- for (HttpHeaders::container_t::const_iterator it(headers->mHeaders.begin());
-
- headers->mHeaders.end() != it;
- ++it)
+ const HttpHeaders::const_iterator end(headers->end());
+ for (HttpHeaders::const_iterator it(headers->begin()); end != it; ++it)
{
- slist = curl_slist_append(slist, (*it).c_str());
+ static const char sep[] = ": ";
+ std::string header;
+ header.reserve((*it).first.size() + (*it).second.size() + sizeof(sep));
+ header.append((*it).first);
+ header.append(sep);
+ header.append((*it).second);
+
+ slist = curl_slist_append(slist, header.c_str());
}
return slist;
}
diff --git a/indra/llcorehttp/_httplibcurl.h b/indra/llcorehttp/_httplibcurl.h
index 611f029ef5..67f98dd4f0 100755
--- a/indra/llcorehttp/_httplibcurl.h
+++ b/indra/llcorehttp/_httplibcurl.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -71,16 +71,22 @@ public:
///
/// @return Indication of how long this method is
/// willing to wait for next service call.
+ ///
+ /// Threading: called by worker thread.
HttpService::ELoopSpeed processTransport();
/// Add request to the active list. Caller is expected to have
/// provided us with a reference count on the op to hold the
/// request. (No additional references will be added.)
+ ///
+ /// Threading: called by worker thread.
void addOp(HttpOpRequest * op);
/// One-time call to set the number of policy classes to be
/// serviced and to create the resources for each. Value
/// must agree with HttpPolicy::setPolicies() call.
+ ///
+ /// Threading: called by init thread.
void start(int policy_count);
/// Synchronously stop libcurl operations. All active requests
@@ -91,9 +97,13 @@ public:
/// respective reply queues.
///
/// Can be restarted with a start() call.
+ ///
+ /// Threading: called by worker thread.
void shutdown();
/// Return global and per-class counts of active requests.
+ ///
+ /// Threading: called by worker thread.
int getActiveCount() const;
int getActiveCountInClass(int policy_class) const;
@@ -103,6 +113,7 @@ public:
///
/// @return True if handle was found and operation canceled.
///
+ /// Threading: called by worker thread.
bool cancel(HttpHandle handle);
protected:
@@ -121,7 +132,8 @@ protected:
HttpService * mService; // Simple reference, not owner
active_set_t mActiveOps;
int mPolicyCount;
- CURLM ** mMultiHandles;
+ CURLM ** mMultiHandles; // One handle per policy class
+ int * mActiveHandles; // Active count per policy class
}; // end class HttpLibcurl
} // end namespace LLCore
diff --git a/indra/llcorehttp/_httpoperation.cpp b/indra/llcorehttp/_httpoperation.cpp
index 5cf5bc5930..5bb0654652 100755
--- a/indra/llcorehttp/_httpoperation.cpp
+++ b/indra/llcorehttp/_httpoperation.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -53,7 +53,7 @@ HttpOperation::HttpOperation()
mUserHandler(NULL),
mReqPolicy(HttpRequest::DEFAULT_POLICY_ID),
mReqPriority(0U),
- mTracing(0)
+ mTracing(HTTP_TRACE_OFF)
{
mMetricCreated = totalTime();
}
@@ -94,7 +94,7 @@ void HttpOperation::stageFromRequest(HttpService *)
// Default implementation should never be called. This
// indicates an operation making a transition that isn't
// defined.
- LL_ERRS("HttpCore") << "Default stageFromRequest method may not be called."
+ LL_ERRS("CoreHttp") << "Default stageFromRequest method may not be called."
<< LL_ENDL;
}
@@ -104,7 +104,7 @@ void HttpOperation::stageFromReady(HttpService *)
// Default implementation should never be called. This
// indicates an operation making a transition that isn't
// defined.
- LL_ERRS("HttpCore") << "Default stageFromReady method may not be called."
+ LL_ERRS("CoreHttp") << "Default stageFromReady method may not be called."
<< LL_ENDL;
}
@@ -114,7 +114,7 @@ void HttpOperation::stageFromActive(HttpService *)
// Default implementation should never be called. This
// indicates an operation making a transition that isn't
// defined.
- LL_ERRS("HttpCore") << "Default stageFromActive method may not be called."
+ LL_ERRS("CoreHttp") << "Default stageFromActive method may not be called."
<< LL_ENDL;
}
diff --git a/indra/llcorehttp/_httpoperation.h b/indra/llcorehttp/_httpoperation.h
index 914627fad0..937a61187d 100755
--- a/indra/llcorehttp/_httpoperation.h
+++ b/indra/llcorehttp/_httpoperation.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -72,7 +72,7 @@ class HttpService;
class HttpOperation : public LLCoreInt::RefCounted
{
public:
- /// Threading: called by a consumer/application thread.
+ /// Threading: called by consumer thread.
HttpOperation();
protected:
@@ -108,7 +108,7 @@ public:
/// by the worker thread. This is passible data
/// until notification is performed.
///
- /// Threading: called by application thread.
+ /// Threading: called by consumer thread.
///
void setReplyPath(HttpReplyQueue * reply_queue,
HttpHandler * handler);
@@ -141,7 +141,7 @@ public:
/// call to HttpRequest::update(). This method does the necessary
/// dispatching.
///
- /// Threading: called by application thread.
+ /// Threading: called by consumer thread.
///
virtual void visitNotifier(HttpRequest *);
diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp
index 207ed8e1e4..926031501e 100755
--- a/indra/llcorehttp/_httpoprequest.cpp
+++ b/indra/llcorehttp/_httpoprequest.cpp
@@ -64,6 +64,15 @@ int parse_content_range_header(char * buffer,
unsigned int * last,
unsigned int * length);
+// Similar for Retry-After headers. Only parses the delta form
+// of the header, HTTP time formats aren't interesting for client
+// purposes.
+//
+// @return 0 if successfully parsed and seconds time delta
+// returned in time argument.
+//
+int parse_retry_after_header(char * buffer, int * time);
+
// Take data from libcurl's CURLOPT_DEBUGFUNCTION callback and
// escape and format it for a tracing line in logging. Absolutely
@@ -74,14 +83,16 @@ void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub,
std::string & safe_line);
-// OS-neutral string comparisons of various types
-int os_strncasecmp(const char *s1, const char *s2, size_t n);
-int os_strcasecmp(const char *s1, const char *s2);
-char * os_strtok_r(char *str, const char *delim, char **saveptr);
-
+// OS-neutral string comparisons of various types.
+int os_strcasecmp(const char * s1, const char * s2);
+char * os_strtok_r(char * str, const char * delim, char ** saveptr);
+char * os_strtrim(char * str);
+char * os_strltrim(char * str);
+void os_strlower(char * str);
-static const char * const hdr_whitespace(" \t");
-static const char * const hdr_separator(": \t");
+// Error testing and reporting for libcurl status codes
+void check_curl_easy_code(CURLcode code);
+void check_curl_easy_code(CURLcode code, int curl_setopt_option);
} // end anonymous namespace
@@ -104,12 +115,15 @@ HttpOpRequest::HttpOpRequest()
mCurlService(NULL),
mCurlHeaders(NULL),
mCurlBodyPos(0),
+ mCurlTemp(NULL),
+ mCurlTempLen(0),
mReplyBody(NULL),
mReplyOffset(0),
mReplyLength(0),
mReplyFullLength(0),
mReplyHeaders(NULL),
mPolicyRetries(0),
+ mPolicy503Retries(0),
mPolicyRetryAt(HttpTime(0)),
mPolicyRetryLimit(HTTP_RETRY_COUNT_DEFAULT)
{
@@ -153,6 +167,10 @@ HttpOpRequest::~HttpOpRequest()
mCurlHeaders = NULL;
}
+ delete [] mCurlTemp;
+ mCurlTemp = NULL;
+ mCurlTempLen = 0;
+
if (mReplyBody)
{
mReplyBody->release();
@@ -208,6 +226,11 @@ void HttpOpRequest::stageFromActive(HttpService * service)
mCurlHeaders = NULL;
}
+ // Also not needed on the other side
+ delete [] mCurlTemp;
+ mCurlTemp = NULL;
+ mCurlTempLen = 0;
+
addAsReply();
}
@@ -226,6 +249,7 @@ void HttpOpRequest::visitNotifier(HttpRequest * request)
response->setRange(mReplyOffset, mReplyLength, mReplyFullLength);
}
response->setContentType(mReplyConType);
+ response->setRetries(mPolicyRetries, mPolicy503Retries);
mUserHandler->onCompleted(static_cast<HttpHandle>(this), response);
@@ -335,6 +359,10 @@ void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id,
{
mProcFlags |= PF_SAVE_HEADERS;
}
+ if (options->getUseRetryAfter())
+ {
+ mProcFlags |= PF_USE_RETRY_AFTER;
+ }
mPolicyRetryLimit = options->getRetries();
mPolicyRetryLimit = llclamp(mPolicyRetryLimit, HTTP_RETRY_COUNT_MIN, HTTP_RETRY_COUNT_MAX);
mTracing = (std::max)(mTracing, llclamp(options->getTrace(), HTTP_TRACE_MIN, HTTP_TRACE_MAX));
@@ -350,6 +378,8 @@ void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id,
//
HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
{
+ CURLcode code;
+
// Scrub transport and result data for retried op case
mCurlActive = false;
mCurlHandle = NULL;
@@ -379,64 +409,108 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
// *FIXME: better error handling later
HttpStatus status;
- // Get policy options
+ // Get global policy options
HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions());
mCurlHandle = LLCurl::createStandardCurlHandle();
-
- curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback);
- curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION, readCallback);
- curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, this);
- curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this);
- curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str());
- curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this);
- curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT);
-
- const std::string * opt_value(NULL);
- long opt_long(0L);
- policy.get(HttpRequest::GP_LLPROXY, &opt_long);
- if (opt_long)
+ if (! mCurlHandle)
+ {
+ // We're in trouble. We'll continue but it won't go well.
+ LL_WARNS("CoreHttp") << "Failed to allocate libcurl easy handle. Continuing."
+ << LL_ENDL;
+ return HttpStatus(HttpStatus::LLCORE, HE_BAD_ALLOC);
+ }
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+ check_curl_easy_code(code, CURLOPT_IPRESOLVE);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1);
+ check_curl_easy_code(code, CURLOPT_NOSIGNAL);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1);
+ check_curl_easy_code(code, CURLOPT_NOPROGRESS);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str());
+ check_curl_easy_code(code, CURLOPT_URL);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this);
+ check_curl_easy_code(code, CURLOPT_PRIVATE);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
+ check_curl_easy_code(code, CURLOPT_ENCODING);
+
+ // The Linksys WRT54G V5 router has an issue with frequent
+ // DNS lookups from LAN machines. If they happen too often,
+ // like for every HTTP request, the router gets annoyed after
+ // about 700 or so requests and starts issuing TCP RSTs to
+ // new connections. Reuse the DNS lookups for even a few
+ // seconds and no RSTs.
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 15);
+ check_curl_easy_code(code, CURLOPT_DNS_CACHE_TIMEOUT);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_AUTOREFERER, 1);
+ check_curl_easy_code(code, CURLOPT_AUTOREFERER);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_FOLLOWLOCATION, 1);
+ check_curl_easy_code(code, CURLOPT_FOLLOWLOCATION);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT);
+ check_curl_easy_code(code, CURLOPT_MAXREDIRS);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback);
+ check_curl_easy_code(code, CURLOPT_WRITEFUNCTION);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this);
+ check_curl_easy_code(code, CURLOPT_WRITEDATA);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION, readCallback);
+ check_curl_easy_code(code, CURLOPT_READFUNCTION);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, this);
+ check_curl_easy_code(code, CURLOPT_READDATA);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1);
+ check_curl_easy_code(code, CURLOPT_SSL_VERIFYPEER);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0);
+ check_curl_easy_code(code, CURLOPT_SSL_VERIFYHOST);
+
+ if (policy.mUseLLProxy)
{
// Use the viewer-based thread-safe API which has a
// fast/safe check for proxy enable. Would like to
// encapsulate this someway...
LLProxy::getInstance()->applyProxySettings(mCurlHandle);
}
- else if (policy.get(HttpRequest::GP_HTTP_PROXY, &opt_value))
+ else if (policy.mHttpProxy.size())
{
// *TODO: This is fine for now but get fuller socks5/
// authentication thing going later....
- curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value->c_str());
- curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, policy.mHttpProxy.c_str());
+ check_curl_easy_code(code, CURLOPT_PROXY);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
+ check_curl_easy_code(code, CURLOPT_PROXYTYPE);
}
- if (policy.get(HttpRequest::GP_CA_PATH, &opt_value))
+ if (policy.mCAPath.size())
{
- curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, opt_value->c_str());
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, policy.mCAPath.c_str());
+ check_curl_easy_code(code, CURLOPT_CAPATH);
}
- if (policy.get(HttpRequest::GP_CA_FILE, &opt_value))
+ if (policy.mCAFile.size())
{
- curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, opt_value->c_str());
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, policy.mCAFile.c_str());
+ check_curl_easy_code(code, CURLOPT_CAINFO);
}
switch (mReqMethod)
{
case HOR_GET:
- curl_easy_setopt(mCurlHandle, CURLOPT_HTTPGET, 1);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_HTTPGET, 1);
+ check_curl_easy_code(code, CURLOPT_HTTPGET);
mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
break;
case HOR_POST:
{
- curl_easy_setopt(mCurlHandle, CURLOPT_POST, 1);
- curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_POST, 1);
+ check_curl_easy_code(code, CURLOPT_POST);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
+ check_curl_easy_code(code, CURLOPT_ENCODING);
long data_size(0);
if (mReqBody)
{
data_size = mReqBody->size();
}
- curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, static_cast<void *>(NULL));
- curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDSIZE, data_size);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, static_cast<void *>(NULL));
+ check_curl_easy_code(code, CURLOPT_POSTFIELDS);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDSIZE, data_size);
+ check_curl_easy_code(code, CURLOPT_POSTFIELDSIZE);
mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:");
mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
@@ -445,14 +519,17 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
case HOR_PUT:
{
- curl_easy_setopt(mCurlHandle, CURLOPT_UPLOAD, 1);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_UPLOAD, 1);
+ check_curl_easy_code(code, CURLOPT_UPLOAD);
long data_size(0);
if (mReqBody)
{
data_size = mReqBody->size();
}
- curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size);
- curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size);
+ check_curl_easy_code(code, CURLOPT_INFILESIZE);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL);
+ check_curl_easy_code(code, CURLOPT_POSTFIELDS);
mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:");
mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
@@ -469,9 +546,12 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
// Tracing
if (mTracing >= HTTP_TRACE_CURL_HEADERS)
{
- curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1);
- curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGDATA, this);
- curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGFUNCTION, debugCallback);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1);
+ check_curl_easy_code(code, CURLOPT_VERBOSE);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGDATA, this);
+ check_curl_easy_code(code, CURLOPT_DEBUGDATA);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGFUNCTION, debugCallback);
+ check_curl_easy_code(code, CURLOPT_DEBUGFUNCTION);
}
// There's a CURLOPT for this now...
@@ -499,13 +579,22 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
// Request options
long timeout(HTTP_REQUEST_TIMEOUT_DEFAULT);
+ long xfer_timeout(HTTP_REQUEST_XFER_TIMEOUT_DEFAULT);
if (mReqOptions)
- {
+ {
timeout = mReqOptions->getTimeout();
timeout = llclamp(timeout, HTTP_REQUEST_TIMEOUT_MIN, HTTP_REQUEST_TIMEOUT_MAX);
+ xfer_timeout = mReqOptions->getTransferTimeout();
+ xfer_timeout = llclamp(xfer_timeout, HTTP_REQUEST_TIMEOUT_MIN, HTTP_REQUEST_TIMEOUT_MAX);
+ }
+ if (xfer_timeout == 0L)
+ {
+ xfer_timeout = timeout;
}
- curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, timeout);
- curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, xfer_timeout);
+ check_curl_easy_code(code, CURLOPT_TIMEOUT);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout);
+ check_curl_easy_code(code, CURLOPT_CONNECTTIMEOUT);
// Request headers
if (mReqHeaders)
@@ -513,12 +602,15 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
// Caller's headers last to override
mCurlHeaders = append_headers_to_slist(mReqHeaders, mCurlHeaders);
}
- curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders);
+ check_curl_easy_code(code, CURLOPT_HTTPHEADER);
- if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS))
+ if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS | PF_USE_RETRY_AFTER))
{
- curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback);
- curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback);
+ check_curl_easy_code(code, CURLOPT_HEADERFUNCTION);
+ code = curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this);
+ check_curl_easy_code(code, CURLOPT_HEADERDATA);
}
if (status)
@@ -559,7 +651,7 @@ size_t HttpOpRequest::readCallback(void * data, size_t size, size_t nmemb, void
{
// Warn but continue if the read position moves beyond end-of-body
// for some reason.
- LL_WARNS("HttpCore") << "Request body position beyond body size. Truncating request body."
+ LL_WARNS("CoreHttp") << "Request body position beyond body size. Truncating request body."
<< LL_ENDL;
}
return 0;
@@ -576,15 +668,15 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi
{
static const char status_line[] = "HTTP/";
static const size_t status_line_len = sizeof(status_line) - 1;
-
- static const char con_ran_line[] = "content-range:";
- static const size_t con_ran_line_len = sizeof(con_ran_line) - 1;
-
+ static const char con_ran_line[] = "content-range";
+ static const char con_retry_line[] = "retry-after";
+
HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata));
const size_t hdr_size(size * nmemb);
const char * hdr_data(static_cast<const char *>(data)); // Not null terminated
-
+ bool is_header(true);
+
if (hdr_size >= status_line_len && ! strncmp(status_line, hdr_data, status_line_len))
{
// One of possibly several status lines. Reset what we know and start over
@@ -592,11 +684,13 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi
op->mReplyOffset = 0;
op->mReplyLength = 0;
op->mReplyFullLength = 0;
+ op->mReplyRetryAfter = 0;
op->mStatus = HttpStatus();
if (op->mReplyHeaders)
{
- op->mReplyHeaders->mHeaders.clear();
+ op->mReplyHeaders->clear();
}
+ is_header = false;
}
// Nothing in here wants a final CR/LF combination. Remove
@@ -609,52 +703,109 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi
--wanted_hdr_size;
}
}
+
+ // Copy and normalize header fragments for the following
+ // stages. Would like to modify the data in-place but that
+ // may not be allowed and we need one byte extra for NUL.
+ // At the end of this we will have:
+ //
+ // If ':' present in header:
+ // 1. name points to text to left of colon which
+ // will be ascii lower-cased and left and right
+ // trimmed of whitespace.
+ // 2. value points to text to right of colon which
+ // will be left trimmed of whitespace.
+ // Otherwise:
+ // 1. name points to header which will be left
+ // trimmed of whitespace.
+ // 2. value is NULL
+ // Any non-NULL pointer may point to a zero-length string.
+ //
+ if (wanted_hdr_size >= op->mCurlTempLen)
+ {
+ delete [] op->mCurlTemp;
+ op->mCurlTempLen = 2 * wanted_hdr_size + 1;
+ op->mCurlTemp = new char [op->mCurlTempLen];
+ }
+ memcpy(op->mCurlTemp, hdr_data, wanted_hdr_size);
+ op->mCurlTemp[wanted_hdr_size] = '\0';
+ char * name(op->mCurlTemp);
+ char * value(strchr(name, ':'));
+ if (value)
+ {
+ *value++ = '\0';
+ os_strlower(name);
+ name = os_strtrim(name);
+ value = os_strltrim(value);
+ }
+ else
+ {
+ // Doesn't look well-formed, do minimal normalization on it
+ name = os_strltrim(name);
+ }
+
+ // Normalized, now reject headers with empty names.
+ if (! *name)
+ {
+ // No use continuing
+ return hdr_size;
+ }
// Save header if caller wants them in the response
- if (op->mProcFlags & PF_SAVE_HEADERS)
+ if (is_header && op->mProcFlags & PF_SAVE_HEADERS)
{
// Save headers in response
if (! op->mReplyHeaders)
{
op->mReplyHeaders = new HttpHeaders;
}
- op->mReplyHeaders->mHeaders.push_back(std::string(hdr_data, wanted_hdr_size));
+ op->mReplyHeaders->append(name, value ? value : "");
}
+ // From this point, header-specific processors are free to
+ // modify the header value.
+
// Detect and parse 'Content-Range' headers
- if (op->mProcFlags & PF_SCAN_RANGE_HEADER)
+ if (is_header
+ && op->mProcFlags & PF_SCAN_RANGE_HEADER
+ && value && *value
+ && ! strcmp(name, con_ran_line))
{
- char hdr_buffer[128]; // Enough for a reasonable header
- size_t frag_size((std::min)(wanted_hdr_size, sizeof(hdr_buffer) - 1));
-
- memcpy(hdr_buffer, hdr_data, frag_size);
- hdr_buffer[frag_size] = '\0';
- if (frag_size > con_ran_line_len &&
- ! os_strncasecmp(hdr_buffer, con_ran_line, con_ran_line_len))
+ unsigned int first(0), last(0), length(0);
+ int status;
+
+ if (! (status = parse_content_range_header(value, &first, &last, &length)))
+ {
+ // Success, record the fragment position
+ op->mReplyOffset = first;
+ op->mReplyLength = last - first + 1;
+ op->mReplyFullLength = length;
+ }
+ else if (-1 == status)
{
- unsigned int first(0), last(0), length(0);
- int status;
+ // Response is badly formed and shouldn't be accepted
+ op->mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR);
+ }
+ else
+ {
+ // Ignore the unparsable.
+ LL_INFOS_ONCE("CoreHttp") << "Problem parsing odd Content-Range header: '"
+ << std::string(hdr_data, wanted_hdr_size)
+ << "'. Ignoring."
+ << LL_ENDL;
+ }
+ }
- if (! (status = parse_content_range_header(hdr_buffer, &first, &last, &length)))
- {
- // Success, record the fragment position
- op->mReplyOffset = first;
- op->mReplyLength = last - first + 1;
- op->mReplyFullLength = length;
- }
- else if (-1 == status)
- {
- // Response is badly formed and shouldn't be accepted
- op->mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR);
- }
- else
- {
- // Ignore the unparsable.
- LL_INFOS_ONCE("CoreHttp") << "Problem parsing odd Content-Range header: '"
- << std::string(hdr_data, frag_size)
- << "'. Ignoring."
- << LL_ENDL;
- }
+ // Detect and parse 'Retry-After' headers
+ if (is_header
+ && op->mProcFlags & PF_USE_RETRY_AFTER
+ && value && *value
+ && ! strcmp(name, con_retry_line))
+ {
+ int time(0);
+ if (! parse_retry_after_header(value, &time))
+ {
+ op->mReplyRetryAfter = time;
}
}
@@ -769,14 +920,16 @@ int parse_content_range_header(char * buffer,
unsigned int * last,
unsigned int * length)
{
+ static const char * const hdr_whitespace(" \t");
+
char * tok_state(NULL), * tok(NULL);
bool match(true);
- if (! os_strtok_r(buffer, hdr_separator, &tok_state))
+ if (! (tok = os_strtok_r(buffer, hdr_whitespace, &tok_state)))
match = false;
- if (match && (tok = os_strtok_r(NULL, hdr_whitespace, &tok_state)))
- match = 0 == os_strcasecmp("bytes", tok);
- if (match && ! (tok = os_strtok_r(NULL, " \t", &tok_state)))
+ else
+ match = (0 == os_strcasecmp("bytes", tok));
+ if (match && ! (tok = os_strtok_r(NULL, hdr_whitespace, &tok_state)))
match = false;
if (match)
{
@@ -815,6 +968,25 @@ int parse_content_range_header(char * buffer,
}
+int parse_retry_after_header(char * buffer, int * time)
+{
+ char * endptr(buffer);
+ long lcl_time(strtol(buffer, &endptr, 10));
+ if (*endptr == '\0' && endptr != buffer && lcl_time > 0)
+ {
+ *time = lcl_time;
+ return 0;
+ }
+
+ // Could attempt to parse HTTP time here but we're not really
+ // interested in it. Scheduling based on wallclock time on
+ // user hardware will lead to tears.
+
+ // Header is there but badly/unexpectedly formed, try to ignore it.
+ return 1;
+}
+
+
void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::string & safe_line)
{
std::string out;
@@ -851,15 +1023,6 @@ void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::strin
}
-int os_strncasecmp(const char *s1, const char *s2, size_t n)
-{
-#if LL_WINDOWS
- return _strnicmp(s1, s2, n);
-#else
- return strncasecmp(s1, s2, n);
-#endif // LL_WINDOWS
-}
-
int os_strcasecmp(const char *s1, const char *s2)
{
@@ -881,6 +1044,73 @@ char * os_strtok_r(char *str, const char *delim, char ** savestate)
}
-} // end anonymous namespace
+void os_strlower(char * str)
+{
+ for (char c(0); (c = *str); ++str)
+ {
+ *str = tolower(c);
+ }
+}
-
+
+char * os_strtrim(char * lstr)
+{
+ while (' ' == *lstr || '\t' == *lstr)
+ {
+ ++lstr;
+ }
+ if (*lstr)
+ {
+ char * rstr(lstr + strlen(lstr));
+ while (lstr < rstr && *--rstr)
+ {
+ if (' ' == *rstr || '\t' == *rstr)
+ {
+ *rstr = '\0';
+ }
+ }
+ llassert(lstr <= rstr);
+ }
+ return lstr;
+}
+
+
+char * os_strltrim(char * lstr)
+{
+ while (' ' == *lstr || '\t' == *lstr)
+ {
+ ++lstr;
+ }
+ return lstr;
+}
+
+
+void check_curl_easy_code(CURLcode code, int curl_setopt_option)
+{
+ if (CURLE_OK != code)
+ {
+ // Comment from old llcurl code which may no longer apply:
+ //
+ // linux appears to throw a curl error once per session for a bad initialization
+ // at a pretty random time (when enabling cookies).
+ LL_WARNS("CoreHttp") << "libcurl error detected: " << curl_easy_strerror(code)
+ << ", curl_easy_setopt option: " << curl_setopt_option
+ << LL_ENDL;
+ }
+}
+
+
+void check_curl_easy_code(CURLcode code)
+{
+ if (CURLE_OK != code)
+ {
+ // Comment from old llcurl code which may no longer apply:
+ //
+ // linux appears to throw a curl error once per session for a bad initialization
+ // at a pretty random time (when enabling cookies).
+ LL_WARNS("CoreHttp") << "libcurl error detected: " << curl_easy_strerror(code)
+ << LL_ENDL;
+ }
+}
+
+} // end anonymous namespace
diff --git a/indra/llcorehttp/_httpoprequest.h b/indra/llcorehttp/_httpoprequest.h
index 74a349b0bf..2f628b5aba 100755
--- a/indra/llcorehttp/_httpoprequest.h
+++ b/indra/llcorehttp/_httpoprequest.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -157,6 +157,7 @@ protected:
unsigned int mProcFlags;
static const unsigned int PF_SCAN_RANGE_HEADER = 0x00000001U;
static const unsigned int PF_SAVE_HEADERS = 0x00000002U;
+ static const unsigned int PF_USE_RETRY_AFTER = 0x00000004U;
public:
// Request data
@@ -174,6 +175,8 @@ public:
HttpService * mCurlService;
curl_slist * mCurlHeaders;
size_t mCurlBodyPos;
+ char * mCurlTemp; // Scratch buffer for header processing
+ size_t mCurlTempLen;
// Result data
HttpStatus mStatus;
@@ -183,9 +186,11 @@ public:
size_t mReplyFullLength;
HttpHeaders * mReplyHeaders;
std::string mReplyConType;
+ int mReplyRetryAfter;
// Policy data
int mPolicyRetries;
+ int mPolicy503Retries;
HttpTime mPolicyRetryAt;
int mPolicyRetryLimit;
}; // end class HttpOpRequest
diff --git a/indra/llcorehttp/_httpopsetget.cpp b/indra/llcorehttp/_httpopsetget.cpp
index 8198528a9b..a5363f9170 100755
--- a/indra/llcorehttp/_httpopsetget.cpp
+++ b/indra/llcorehttp/_httpopsetget.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -27,6 +27,7 @@
#include "_httpopsetget.h"
#include "httpcommon.h"
+#include "httprequest.h"
#include "_httpservice.h"
#include "_httppolicy.h"
@@ -43,10 +44,11 @@ namespace LLCore
HttpOpSetGet::HttpOpSetGet()
: HttpOperation(),
- mIsGlobal(false),
- mDoSet(false),
- mSetting(-1), // Nothing requested
- mLongValue(0L)
+ mReqOption(HttpRequest::PO_CONNECTION_LIMIT),
+ mReqClass(HttpRequest::INVALID_POLICY_ID),
+ mReqDoSet(false),
+ mReqLongValue(0L),
+ mReplyLongValue(0L)
{}
@@ -54,37 +56,84 @@ HttpOpSetGet::~HttpOpSetGet()
{}
-void HttpOpSetGet::setupGet(HttpRequest::EGlobalPolicy setting)
+HttpStatus HttpOpSetGet::setupGet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass)
{
- mIsGlobal = true;
- mSetting = setting;
+ HttpStatus status;
+
+ mReqOption = opt;
+ mReqClass = pclass;
+ return status;
}
-void HttpOpSetGet::setupSet(HttpRequest::EGlobalPolicy setting, const std::string & value)
+HttpStatus HttpOpSetGet::setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, long value)
{
- mIsGlobal = true;
- mDoSet = true;
- mSetting = setting;
- mStrValue = value;
+ HttpStatus status;
+
+ if (! HttpService::sOptionDesc[opt].mIsLong)
+ {
+ return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
+ }
+ if (! HttpService::sOptionDesc[opt].mIsDynamic)
+ {
+ return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
+ }
+
+ mReqOption = opt;
+ mReqClass = pclass;
+ mReqDoSet = true;
+ mReqLongValue = value;
+
+ return status;
}
-void HttpOpSetGet::stageFromRequest(HttpService * service)
+HttpStatus HttpOpSetGet::setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, const std::string & value)
{
- HttpPolicyGlobal & pol_opt(service->getPolicy().getGlobalOptions());
- HttpRequest::EGlobalPolicy setting(static_cast<HttpRequest::EGlobalPolicy>(mSetting));
+ HttpStatus status;
+
+ if (HttpService::sOptionDesc[opt].mIsLong)
+ {
+ return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
+ }
+ if (! HttpService::sOptionDesc[opt].mIsDynamic)
+ {
+ return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
+ }
+
+ mReqOption = opt;
+ mReqClass = pclass;
+ mReqDoSet = true;
+ mReqStrValue = value;
- if (mDoSet)
+ return status;
+}
+
+
+void HttpOpSetGet::stageFromRequest(HttpService * service)
+{
+ if (mReqDoSet)
{
- mStatus = pol_opt.set(setting, mStrValue);
+ if (HttpService::sOptionDesc[mReqOption].mIsLong)
+ {
+ mStatus = service->setPolicyOption(mReqOption, mReqClass,
+ mReqLongValue, &mReplyLongValue);
+ }
+ else
+ {
+ mStatus = service->setPolicyOption(mReqOption, mReqClass,
+ mReqStrValue, &mReplyStrValue);
+ }
}
- if (mStatus)
+ else
{
- const std::string * value(NULL);
- if ((mStatus = pol_opt.get(setting, &value)))
+ if (HttpService::sOptionDesc[mReqOption].mIsLong)
+ {
+ mStatus = service->getPolicyOption(mReqOption, mReqClass, &mReplyLongValue);
+ }
+ else
{
- mStrValue = *value;
+ mStatus = service->getPolicyOption(mReqOption, mReqClass, &mReplyStrValue);
}
}
diff --git a/indra/llcorehttp/_httpopsetget.h b/indra/llcorehttp/_httpopsetget.h
index 6966b9d94e..a1e76dd429 100755
--- a/indra/llcorehttp/_httpopsetget.h
+++ b/indra/llcorehttp/_httpopsetget.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -46,7 +46,10 @@ namespace LLCore
/// configuration settings.
///
/// *NOTE: Expect this to change. Don't really like it yet.
-
+///
+/// *TODO: Can't return values to caller yet. Need to do
+/// something better with HttpResponse and visitNotifier().
+///
class HttpOpSetGet : public HttpOperation
{
public:
@@ -61,19 +64,23 @@ private:
public:
/// Threading: called by application thread
- void setupGet(HttpRequest::EGlobalPolicy setting);
- void setupSet(HttpRequest::EGlobalPolicy setting, const std::string & value);
+ HttpStatus setupGet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass);
+ HttpStatus setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, long value);
+ HttpStatus setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, const std::string & value);
virtual void stageFromRequest(HttpService *);
public:
// Request data
- bool mIsGlobal;
- bool mDoSet;
- int mSetting;
- long mLongValue;
- std::string mStrValue;
-
+ HttpRequest::EPolicyOption mReqOption;
+ HttpRequest::policy_t mReqClass;
+ bool mReqDoSet;
+ long mReqLongValue;
+ std::string mReqStrValue;
+
+ // Reply Data
+ long mReplyLongValue;
+ std::string mReplyStrValue;
}; // end class HttpOpSetGet
diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp
index 014bd37e2e..fd5a93e192 100755
--- a/indra/llcorehttp/_httppolicy.cpp
+++ b/indra/llcorehttp/_httppolicy.cpp
@@ -41,57 +41,70 @@ namespace LLCore
// Per-policy-class data for a running system.
-// Collection of queues, parameters, history, metrics, etc.
+// Collection of queues, options and other data
// for a single policy class.
//
// Threading: accessed only by worker thread
-struct HttpPolicy::State
+struct HttpPolicy::ClassState
{
public:
- State()
- : mConnMax(HTTP_CONNECTION_LIMIT_DEFAULT),
- mConnAt(HTTP_CONNECTION_LIMIT_DEFAULT),
- mConnMin(1),
- mNextSample(0),
- mErrorCount(0),
- mErrorFactor(0)
+ ClassState()
+ : mThrottleEnd(0),
+ mThrottleLeft(0L),
+ mRequestCount(0L)
{}
HttpReadyQueue mReadyQueue;
HttpRetryQueue mRetryQueue;
HttpPolicyClass mOptions;
-
- long mConnMax;
- long mConnAt;
- long mConnMin;
-
- HttpTime mNextSample;
- unsigned long mErrorCount;
- unsigned long mErrorFactor;
+ HttpTime mThrottleEnd;
+ long mThrottleLeft;
+ long mRequestCount;
};
HttpPolicy::HttpPolicy(HttpService * service)
- : mActiveClasses(0),
- mState(NULL),
- mService(service)
-{}
+ : mService(service)
+{
+ // Create default class
+ mClasses.push_back(new ClassState());
+}
HttpPolicy::~HttpPolicy()
{
shutdown();
+
+ for (class_list_t::iterator it(mClasses.begin()); it != mClasses.end(); ++it)
+ {
+ delete (*it);
+ }
+ mClasses.clear();
mService = NULL;
}
+HttpRequest::policy_t HttpPolicy::createPolicyClass()
+{
+ const HttpRequest::policy_t policy_class(mClasses.size());
+ if (policy_class >= HTTP_POLICY_CLASS_LIMIT)
+ {
+ return HttpRequest::INVALID_POLICY_ID;
+ }
+ mClasses.push_back(new ClassState());
+ return policy_class;
+}
+
+
void HttpPolicy::shutdown()
{
- for (int policy_class(0); policy_class < mActiveClasses; ++policy_class)
+ for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)
{
- HttpRetryQueue & retryq(mState[policy_class].mRetryQueue);
+ ClassState & state(*mClasses[policy_class]);
+
+ HttpRetryQueue & retryq(state.mRetryQueue);
while (! retryq.empty())
{
HttpOpRequest * op(retryq.top());
@@ -101,7 +114,7 @@ void HttpPolicy::shutdown()
op->release();
}
- HttpReadyQueue & readyq(mState[policy_class].mReadyQueue);
+ HttpReadyQueue & readyq(state.mReadyQueue);
while (! readyq.empty())
{
HttpOpRequest * op(readyq.top());
@@ -111,28 +124,11 @@ void HttpPolicy::shutdown()
op->release();
}
}
- delete [] mState;
- mState = NULL;
- mActiveClasses = 0;
}
-void HttpPolicy::start(const HttpPolicyGlobal & global,
- const std::vector<HttpPolicyClass> & classes)
-{
- llassert_always(! mState);
-
- mGlobalOptions = global;
- mActiveClasses = classes.size();
- mState = new State [mActiveClasses];
- for (int i(0); i < mActiveClasses; ++i)
- {
- mState[i].mOptions = classes[i];
- mState[i].mConnMax = classes[i].mConnectionLimit;
- mState[i].mConnAt = mState[i].mConnMax;
- mState[i].mConnMin = 2;
- }
-}
+void HttpPolicy::start()
+{}
void HttpPolicy::addOp(HttpOpRequest * op)
@@ -140,7 +136,8 @@ void HttpPolicy::addOp(HttpOpRequest * op)
const int policy_class(op->mReqPolicy);
op->mPolicyRetries = 0;
- mState[policy_class].mReadyQueue.push(op);
+ op->mPolicy503Retries = 0;
+ mClasses[policy_class]->mReadyQueue.push(op);
}
@@ -155,25 +152,39 @@ void HttpPolicy::retryOp(HttpOpRequest * op)
5000000 // ... to every 5.0 S.
};
static const int delta_max(int(LL_ARRAY_SIZE(retry_deltas)) - 1);
-
+ static const HttpStatus error_503(503);
+
const HttpTime now(totalTime());
const int policy_class(op->mReqPolicy);
-
- const HttpTime delta(retry_deltas[llclamp(op->mPolicyRetries, 0, delta_max)]);
+ HttpTime delta(retry_deltas[llclamp(op->mPolicyRetries, 0, delta_max)]);
+ bool external_delta(false);
+
+ if (op->mReplyRetryAfter > 0 && op->mReplyRetryAfter < 30)
+ {
+ delta = op->mReplyRetryAfter * U64L(1000000);
+ external_delta = true;
+ }
op->mPolicyRetryAt = now + delta;
++op->mPolicyRetries;
- LL_WARNS("CoreHttp") << "HTTP request " << static_cast<HttpHandle>(op)
- << " retry " << op->mPolicyRetries
- << " scheduled for +" << (delta / HttpTime(1000))
- << " mS. Status: " << op->mStatus.toHex()
- << LL_ENDL;
- if (op->mTracing > 0)
+ if (error_503 == op->mStatus)
+ {
+ ++op->mPolicy503Retries;
+ }
+ LL_DEBUGS("CoreHttp") << "HTTP request " << static_cast<HttpHandle>(op)
+ << " retry " << op->mPolicyRetries
+ << " scheduled in " << (delta / HttpTime(1000))
+ << " mS (" << (external_delta ? "external" : "internal")
+ << "). Status: " << op->mStatus.toTerseString()
+ << LL_ENDL;
+ if (op->mTracing > HTTP_TRACE_OFF)
{
LL_INFOS("CoreHttp") << "TRACE, ToRetryQueue, Handle: "
<< static_cast<HttpHandle>(op)
+ << ", Delta: " << (delta / HttpTime(1000))
+ << ", Retries: " << op->mPolicyRetries
<< LL_ENDL;
}
- mState[policy_class].mRetryQueue.push(op);
+ mClasses[policy_class]->mRetryQueue.push(op);
}
@@ -188,21 +199,43 @@ void HttpPolicy::retryOp(HttpOpRequest * op)
// the worker thread may sleep hard otherwise will ask for
// normal polling frequency.
//
+// Implements a client-side request rate throttle as well.
+// This is intended to mimic and predict throttling behavior
+// of grid services but that is difficult to do with different
+// time bases. This also represents a rigid coupling between
+// viewer and server that makes it hard to change parameters
+// and I hope we can make this go away with pipelining.
+//
HttpService::ELoopSpeed HttpPolicy::processReadyQueue()
{
const HttpTime now(totalTime());
HttpService::ELoopSpeed result(HttpService::REQUEST_SLEEP);
HttpLibcurl & transport(mService->getTransport());
- for (int policy_class(0); policy_class < mActiveClasses; ++policy_class)
+ for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)
{
- State & state(mState[policy_class]);
- int active(transport.getActiveCountInClass(policy_class));
- int needed(state.mConnAt - active); // Expect negatives here
-
+ ClassState & state(*mClasses[policy_class]);
HttpRetryQueue & retryq(state.mRetryQueue);
HttpReadyQueue & readyq(state.mReadyQueue);
+
+ if (retryq.empty() && readyq.empty())
+ {
+ continue;
+ }
+ const bool throttle_enabled(state.mOptions.mThrottleRate > 0L);
+ const bool throttle_current(throttle_enabled && now < state.mThrottleEnd);
+
+ if (throttle_current && state.mThrottleLeft <= 0)
+ {
+ // Throttled condition, don't serve this class but don't sleep hard.
+ result = HttpService::NORMAL;
+ continue;
+ }
+
+ int active(transport.getActiveCountInClass(policy_class));
+ int needed(state.mOptions.mConnectionLimit - active); // Expect negatives here
+
if (needed > 0)
{
// First see if we have any retries...
@@ -216,10 +249,27 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue()
op->stageFromReady(mService);
op->release();
-
+
+ ++state.mRequestCount;
--needed;
+ if (throttle_enabled)
+ {
+ if (now >= state.mThrottleEnd)
+ {
+ // Throttle expired, move to next window
+ LL_DEBUGS("CoreHttp") << "Throttle expired with " << state.mThrottleLeft
+ << " requests to go and " << state.mRequestCount
+ << " requests issued." << LL_ENDL;
+ state.mThrottleLeft = state.mOptions.mThrottleRate;
+ state.mThrottleEnd = now + HttpTime(1000000);
+ }
+ if (--state.mThrottleLeft <= 0)
+ {
+ goto throttle_on;
+ }
+ }
}
-
+
// Now go on to the new requests...
while (needed > 0 && ! readyq.empty())
{
@@ -229,10 +279,29 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue()
op->stageFromReady(mService);
op->release();
+ ++state.mRequestCount;
--needed;
+ if (throttle_enabled)
+ {
+ if (now >= state.mThrottleEnd)
+ {
+ // Throttle expired, move to next window
+ LL_DEBUGS("CoreHttp") << "Throttle expired with " << state.mThrottleLeft
+ << " requests to go and " << state.mRequestCount
+ << " requests issued." << LL_ENDL;
+ state.mThrottleLeft = state.mOptions.mThrottleRate;
+ state.mThrottleEnd = now + HttpTime(1000000);
+ }
+ if (--state.mThrottleLeft <= 0)
+ {
+ goto throttle_on;
+ }
+ }
}
}
-
+
+ throttle_on:
+
if (! readyq.empty() || ! retryq.empty())
{
// If anything is ready, continue looping...
@@ -246,9 +315,9 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue()
bool HttpPolicy::changePriority(HttpHandle handle, HttpRequest::priority_t priority)
{
- for (int policy_class(0); policy_class < mActiveClasses; ++policy_class)
+ for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)
{
- State & state(mState[policy_class]);
+ ClassState & state(*mClasses[policy_class]);
// We don't scan retry queue because a priority change there
// is meaningless. The request will be issued based on retry
// intervals not priority value, which is now moot.
@@ -276,9 +345,9 @@ bool HttpPolicy::changePriority(HttpHandle handle, HttpRequest::priority_t prior
bool HttpPolicy::cancel(HttpHandle handle)
{
- for (int policy_class(0); policy_class < mActiveClasses; ++policy_class)
+ for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)
{
- State & state(mState[policy_class]);
+ ClassState & state(*mClasses[policy_class]);
// Scan retry queue
HttpRetryQueue::container_type & c1(state.mRetryQueue.get_container());
@@ -337,14 +406,14 @@ bool HttpPolicy::stageAfterCompletion(HttpOpRequest * op)
LL_WARNS("CoreHttp") << "HTTP request " << static_cast<HttpHandle>(op)
<< " failed after " << op->mPolicyRetries
<< " retries. Reason: " << op->mStatus.toString()
- << " (" << op->mStatus.toHex() << ")"
+ << " (" << op->mStatus.toTerseString() << ")"
<< LL_ENDL;
}
else if (op->mPolicyRetries)
{
- LL_WARNS("CoreHttp") << "HTTP request " << static_cast<HttpHandle>(op)
- << " succeeded on retry " << op->mPolicyRetries << "."
- << LL_ENDL;
+ LL_DEBUGS("CoreHttp") << "HTTP request " << static_cast<HttpHandle>(op)
+ << " succeeded on retry " << op->mPolicyRetries << "."
+ << LL_ENDL;
}
op->stageFromActive(mService);
@@ -352,13 +421,21 @@ bool HttpPolicy::stageAfterCompletion(HttpOpRequest * op)
return false; // not active
}
+
+HttpPolicyClass & HttpPolicy::getClassOptions(HttpRequest::policy_t pclass)
+{
+ llassert_always(pclass >= 0 && pclass < mClasses.size());
+
+ return mClasses[pclass]->mOptions;
+}
+
int HttpPolicy::getReadyCount(HttpRequest::policy_t policy_class) const
{
- if (policy_class < mActiveClasses)
+ if (policy_class < mClasses.size())
{
- return (mState[policy_class].mReadyQueue.size()
- + mState[policy_class].mRetryQueue.size());
+ return (mClasses[policy_class]->mReadyQueue.size()
+ + mClasses[policy_class]->mRetryQueue.size());
}
return 0;
}
diff --git a/indra/llcorehttp/_httppolicy.h b/indra/llcorehttp/_httppolicy.h
index 03d92c0b8e..bf1aa74267 100755
--- a/indra/llcorehttp/_httppolicy.h
+++ b/indra/llcorehttp/_httppolicy.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -60,6 +60,9 @@ private:
void operator=(const HttpPolicy &); // Not defined
public:
+ /// Threading: called by init thread.
+ HttpRequest::policy_t createPolicyClass();
+
/// Cancel all ready and retry requests sending them to
/// their notification queues. Release state resources
/// making further request handling impossible.
@@ -71,9 +74,8 @@ public:
/// requests. One-time call invoked before starting
/// the worker thread.
///
- /// Threading: called by application thread
- void start(const HttpPolicyGlobal & global,
- const std::vector<HttpPolicyClass> & classes);
+ /// Threading: called by init thread
+ void start();
/// Give the policy layer some cycles to scan the ready
/// queue promoting higher-priority requests to active
@@ -93,7 +95,7 @@ public:
/// and should not be modified by anyone until retrieved
/// from queue.
///
- /// Threading: called by any thread
+ /// Threading: called by worker thread
void addOp(HttpOpRequest *);
/// Similar to addOp, used when a caller wants to retry a
@@ -130,30 +132,39 @@ public:
/// Threading: called by worker thread
bool stageAfterCompletion(HttpOpRequest * op);
- // Get pointer to global policy options. Caller is expected
- // to do context checks like no setting once running.
+ /// Get a reference to global policy options. Caller is expected
+ /// to do context checks like no setting once running. These
+ /// are done, for example, in @see HttpService interfaces.
///
/// Threading: called by any thread *but* the object may
/// only be modified by the worker thread once running.
- ///
HttpPolicyGlobal & getGlobalOptions()
{
return mGlobalOptions;
}
+ /// Get a reference to class policy options. Caller is expected
+ /// to do context checks like no setting once running. These
+ /// are done, for example, in @see HttpService interfaces.
+ ///
+ /// Threading: called by any thread *but* the object may
+ /// only be modified by the worker thread once running and
+ /// read accesses by other threads are exposed to races at
+ /// that point.
+ HttpPolicyClass & getClassOptions(HttpRequest::policy_t pclass);
+
/// Get ready counts for a particular policy class
///
/// Threading: called by worker thread
int getReadyCount(HttpRequest::policy_t policy_class) const;
protected:
- struct State;
-
- int mActiveClasses;
- State * mState;
- HttpService * mService; // Naked pointer, not refcounted, not owner
- HttpPolicyGlobal mGlobalOptions;
+ struct ClassState;
+ typedef std::vector<ClassState *> class_list_t;
+ HttpPolicyGlobal mGlobalOptions;
+ class_list_t mClasses;
+ HttpService * mService; // Naked pointer, not refcounted, not owner
}; // end class HttpPolicy
} // end namespace LLCore
diff --git a/indra/llcorehttp/_httppolicyclass.cpp b/indra/llcorehttp/_httppolicyclass.cpp
index a23b81322c..f34a8e9f1e 100755
--- a/indra/llcorehttp/_httppolicyclass.cpp
+++ b/indra/llcorehttp/_httppolicyclass.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -34,10 +34,10 @@ namespace LLCore
HttpPolicyClass::HttpPolicyClass()
- : mSetMask(0UL),
- mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
+ : mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
mPerHostConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
- mPipelining(0)
+ mPipelining(HTTP_PIPELINING_DEFAULT),
+ mThrottleRate(HTTP_THROTTLE_RATE_DEFAULT)
{}
@@ -49,75 +49,75 @@ HttpPolicyClass & HttpPolicyClass::operator=(const HttpPolicyClass & other)
{
if (this != &other)
{
- mSetMask = other.mSetMask;
mConnectionLimit = other.mConnectionLimit;
mPerHostConnectionLimit = other.mPerHostConnectionLimit;
mPipelining = other.mPipelining;
+ mThrottleRate = other.mThrottleRate;
}
return *this;
}
HttpPolicyClass::HttpPolicyClass(const HttpPolicyClass & other)
- : mSetMask(other.mSetMask),
- mConnectionLimit(other.mConnectionLimit),
+ : mConnectionLimit(other.mConnectionLimit),
mPerHostConnectionLimit(other.mPerHostConnectionLimit),
- mPipelining(other.mPipelining)
+ mPipelining(other.mPipelining),
+ mThrottleRate(other.mThrottleRate)
{}
-HttpStatus HttpPolicyClass::set(HttpRequest::EClassPolicy opt, long value)
+HttpStatus HttpPolicyClass::set(HttpRequest::EPolicyOption opt, long value)
{
switch (opt)
{
- case HttpRequest::CP_CONNECTION_LIMIT:
+ case HttpRequest::PO_CONNECTION_LIMIT:
mConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), long(HTTP_CONNECTION_LIMIT_MAX));
break;
- case HttpRequest::CP_PER_HOST_CONNECTION_LIMIT:
+ case HttpRequest::PO_PER_HOST_CONNECTION_LIMIT:
mPerHostConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), mConnectionLimit);
break;
- case HttpRequest::CP_ENABLE_PIPELINING:
+ case HttpRequest::PO_ENABLE_PIPELINING:
mPipelining = llclamp(value, 0L, 1L);
break;
+ case HttpRequest::PO_THROTTLE_RATE:
+ mThrottleRate = llclamp(value, 0L, 1000000L);
+ break;
+
default:
return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
}
- mSetMask |= 1UL << int(opt);
return HttpStatus();
}
-HttpStatus HttpPolicyClass::get(HttpRequest::EClassPolicy opt, long * value)
+HttpStatus HttpPolicyClass::get(HttpRequest::EPolicyOption opt, long * value) const
{
- static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET);
- long * src(NULL);
-
switch (opt)
{
- case HttpRequest::CP_CONNECTION_LIMIT:
- src = &mConnectionLimit;
+ case HttpRequest::PO_CONNECTION_LIMIT:
+ *value = mConnectionLimit;
break;
- case HttpRequest::CP_PER_HOST_CONNECTION_LIMIT:
- src = &mPerHostConnectionLimit;
+ case HttpRequest::PO_PER_HOST_CONNECTION_LIMIT:
+ *value = mPerHostConnectionLimit;
break;
- case HttpRequest::CP_ENABLE_PIPELINING:
- src = &mPipelining;
+ case HttpRequest::PO_ENABLE_PIPELINING:
+ *value = mPipelining;
+ break;
+
+ case HttpRequest::PO_THROTTLE_RATE:
+ *value = mThrottleRate;
break;
default:
return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
}
- if (! (mSetMask & (1UL << int(opt))))
- return not_set;
-
- *value = *src;
return HttpStatus();
}
diff --git a/indra/llcorehttp/_httppolicyclass.h b/indra/llcorehttp/_httppolicyclass.h
index d175413cbd..38f1194ded 100755
--- a/indra/llcorehttp/_httppolicyclass.h
+++ b/indra/llcorehttp/_httppolicyclass.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -34,6 +34,18 @@
namespace LLCore
{
+/// Options struct for per-class policy options.
+///
+/// Combines both raw blob data access with semantics-enforcing
+/// set/get interfaces. For internal operations by the worker
+/// thread, just grab the setting directly from instance and test/use
+/// as needed. When attached to external APIs (the public API
+/// options interfaces) the set/get methods are available to
+/// enforce correct ranges, data types, contexts, etc. and suitable
+/// status values are returned.
+///
+/// Threading: Single-threaded. In practice, init thread before
+/// worker starts, worker thread after.
class HttpPolicyClass
{
public:
@@ -44,14 +56,14 @@ public:
HttpPolicyClass(const HttpPolicyClass &); // Not defined
public:
- HttpStatus set(HttpRequest::EClassPolicy opt, long value);
- HttpStatus get(HttpRequest::EClassPolicy opt, long * value);
+ HttpStatus set(HttpRequest::EPolicyOption opt, long value);
+ HttpStatus get(HttpRequest::EPolicyOption opt, long * value) const;
public:
- unsigned long mSetMask;
long mConnectionLimit;
long mPerHostConnectionLimit;
long mPipelining;
+ long mThrottleRate;
}; // end class HttpPolicyClass
} // end namespace LLCore
diff --git a/indra/llcorehttp/_httppolicyglobal.cpp b/indra/llcorehttp/_httppolicyglobal.cpp
index 72f409d3b1..1dc95f3dce 100755
--- a/indra/llcorehttp/_httppolicyglobal.cpp
+++ b/indra/llcorehttp/_httppolicyglobal.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -34,8 +34,7 @@ namespace LLCore
HttpPolicyGlobal::HttpPolicyGlobal()
- : mSetMask(0UL),
- mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
+ : mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
mTrace(HTTP_TRACE_OFF),
mUseLLProxy(0)
{}
@@ -49,7 +48,6 @@ HttpPolicyGlobal & HttpPolicyGlobal::operator=(const HttpPolicyGlobal & other)
{
if (this != &other)
{
- mSetMask = other.mSetMask;
mConnectionLimit = other.mConnectionLimit;
mCAPath = other.mCAPath;
mCAFile = other.mCAFile;
@@ -61,19 +59,19 @@ HttpPolicyGlobal & HttpPolicyGlobal::operator=(const HttpPolicyGlobal & other)
}
-HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, long value)
+HttpStatus HttpPolicyGlobal::set(HttpRequest::EPolicyOption opt, long value)
{
switch (opt)
{
- case HttpRequest::GP_CONNECTION_LIMIT:
+ case HttpRequest::PO_CONNECTION_LIMIT:
mConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), long(HTTP_CONNECTION_LIMIT_MAX));
break;
- case HttpRequest::GP_TRACE:
+ case HttpRequest::PO_TRACE:
mTrace = llclamp(value, long(HTTP_TRACE_MIN), long(HTTP_TRACE_MAX));
break;
- case HttpRequest::GP_LLPROXY:
+ case HttpRequest::PO_LLPROXY:
mUseLLProxy = llclamp(value, 0L, 1L);
break;
@@ -81,24 +79,23 @@ HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, long value)
return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
}
- mSetMask |= 1UL << int(opt);
return HttpStatus();
}
-HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, const std::string & value)
+HttpStatus HttpPolicyGlobal::set(HttpRequest::EPolicyOption opt, const std::string & value)
{
switch (opt)
{
- case HttpRequest::GP_CA_PATH:
+ case HttpRequest::PO_CA_PATH:
mCAPath = value;
break;
- case HttpRequest::GP_CA_FILE:
+ case HttpRequest::PO_CA_FILE:
mCAFile = value;
break;
- case HttpRequest::GP_HTTP_PROXY:
+ case HttpRequest::PO_HTTP_PROXY:
mCAFile = value;
break;
@@ -106,69 +103,54 @@ HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, const std::stri
return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
}
- mSetMask |= 1UL << int(opt);
return HttpStatus();
}
-HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, long * value)
+HttpStatus HttpPolicyGlobal::get(HttpRequest::EPolicyOption opt, long * value) const
{
- static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET);
- long * src(NULL);
-
switch (opt)
{
- case HttpRequest::GP_CONNECTION_LIMIT:
- src = &mConnectionLimit;
+ case HttpRequest::PO_CONNECTION_LIMIT:
+ *value = mConnectionLimit;
break;
- case HttpRequest::GP_TRACE:
- src = &mTrace;
+ case HttpRequest::PO_TRACE:
+ *value = mTrace;
break;
- case HttpRequest::GP_LLPROXY:
- src = &mUseLLProxy;
+ case HttpRequest::PO_LLPROXY:
+ *value = mUseLLProxy;
break;
default:
return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
}
- if (! (mSetMask & (1UL << int(opt))))
- return not_set;
-
- *value = *src;
return HttpStatus();
}
-HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, const std::string ** value)
+HttpStatus HttpPolicyGlobal::get(HttpRequest::EPolicyOption opt, std::string * value) const
{
- static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET);
- const std::string * src(NULL);
-
switch (opt)
{
- case HttpRequest::GP_CA_PATH:
- src = &mCAPath;
+ case HttpRequest::PO_CA_PATH:
+ *value = mCAPath;
break;
- case HttpRequest::GP_CA_FILE:
- src = &mCAFile;
+ case HttpRequest::PO_CA_FILE:
+ *value = mCAFile;
break;
- case HttpRequest::GP_HTTP_PROXY:
- src = &mHttpProxy;
+ case HttpRequest::PO_HTTP_PROXY:
+ *value = mHttpProxy;
break;
default:
return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
}
- if (! (mSetMask & (1UL << int(opt))))
- return not_set;
-
- *value = src;
return HttpStatus();
}
diff --git a/indra/llcorehttp/_httppolicyglobal.h b/indra/llcorehttp/_httppolicyglobal.h
index a50d0e4188..67c4ba9481 100755
--- a/indra/llcorehttp/_httppolicyglobal.h
+++ b/indra/llcorehttp/_httppolicyglobal.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -34,6 +34,18 @@
namespace LLCore
{
+/// Options struct for global policy options.
+///
+/// Combines both raw blob data access with semantics-enforcing
+/// set/get interfaces. For internal operations by the worker
+/// thread, just grab the setting directly from instance and test/use
+/// as needed. When attached to external APIs (the public API
+/// options interfaces) the set/get methods are available to
+/// enforce correct ranges, data types, contexts, etc. and suitable
+/// status values are returned.
+///
+/// Threading: Single-threaded. In practice, init thread before
+/// worker starts, worker thread after.
class HttpPolicyGlobal
{
public:
@@ -46,13 +58,12 @@ private:
HttpPolicyGlobal(const HttpPolicyGlobal &); // Not defined
public:
- HttpStatus set(HttpRequest::EGlobalPolicy opt, long value);
- HttpStatus set(HttpRequest::EGlobalPolicy opt, const std::string & value);
- HttpStatus get(HttpRequest::EGlobalPolicy opt, long * value);
- HttpStatus get(HttpRequest::EGlobalPolicy opt, const std::string ** value);
+ HttpStatus set(HttpRequest::EPolicyOption opt, long value);
+ HttpStatus set(HttpRequest::EPolicyOption opt, const std::string & value);
+ HttpStatus get(HttpRequest::EPolicyOption opt, long * value) const;
+ HttpStatus get(HttpRequest::EPolicyOption opt, std::string * value) const;
public:
- unsigned long mSetMask;
long mConnectionLimit;
std::string mCAPath;
std::string mCAFile;
diff --git a/indra/llcorehttp/_httpservice.cpp b/indra/llcorehttp/_httpservice.cpp
index 0825888d0f..c94249dc2d 100755
--- a/indra/llcorehttp/_httpservice.cpp
+++ b/indra/llcorehttp/_httpservice.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -43,6 +43,18 @@
namespace LLCore
{
+const HttpService::OptionDescriptor HttpService::sOptionDesc[] =
+{ // isLong isDynamic isGlobal isClass
+ { true, true, true, true }, // PO_CONNECTION_LIMIT
+ { true, true, false, true }, // PO_PER_HOST_CONNECTION_LIMIT
+ { false, false, true, false }, // PO_CA_PATH
+ { false, false, true, false }, // PO_CA_FILE
+ { false, true, true, false }, // PO_HTTP_PROXY
+ { true, true, true, false }, // PO_LLPROXY
+ { true, true, true, false }, // PO_TRACE
+ { true, true, false, true }, // PO_ENABLE_PIPELINING
+ { true, true, false, true } // PO_THROTTLE_RATE
+};
HttpService * HttpService::sInstance(NULL);
volatile HttpService::EState HttpService::sState(NOT_INITIALIZED);
@@ -51,15 +63,9 @@ HttpService::HttpService()
mExitRequested(0U),
mThread(NULL),
mPolicy(NULL),
- mTransport(NULL)
-{
- // Create the default policy class
- HttpPolicyClass pol_class;
- pol_class.set(HttpRequest::CP_CONNECTION_LIMIT, HTTP_CONNECTION_LIMIT_DEFAULT);
- pol_class.set(HttpRequest::CP_PER_HOST_CONNECTION_LIMIT, HTTP_CONNECTION_LIMIT_DEFAULT);
- pol_class.set(HttpRequest::CP_ENABLE_PIPELINING, 0L);
- mPolicyClasses.push_back(pol_class);
-}
+ mTransport(NULL),
+ mLastPolicy(0)
+{}
HttpService::~HttpService()
@@ -149,13 +155,8 @@ void HttpService::term()
HttpRequest::policy_t HttpService::createPolicyClass()
{
- const HttpRequest::policy_t policy_class(mPolicyClasses.size());
- if (policy_class >= HTTP_POLICY_CLASS_LIMIT)
- {
- return 0;
- }
- mPolicyClasses.push_back(HttpPolicyClass());
- return policy_class;
+ mLastPolicy = mPolicy->createPolicyClass();
+ return mLastPolicy;
}
@@ -188,8 +189,8 @@ void HttpService::startThread()
}
// Push current policy definitions, enable policy & transport components
- mPolicy->start(mPolicyGlobal, mPolicyClasses);
- mTransport->start(mPolicyClasses.size());
+ mPolicy->start();
+ mTransport->start(mLastPolicy + 1);
mThread = new LLCoreInt::HttpThread(boost::bind(&HttpService::threadRun, this, _1));
sState = RUNNING;
@@ -322,7 +323,7 @@ HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop)
{
// Setup for subsequent tracing
long tracing(HTTP_TRACE_OFF);
- mPolicy->getGlobalOptions().get(HttpRequest::GP_TRACE, &tracing);
+ mPolicy->getGlobalOptions().get(HttpRequest::PO_TRACE, &tracing);
op->mTracing = (std::max)(op->mTracing, int(tracing));
if (op->mTracing > HTTP_TRACE_OFF)
@@ -345,4 +346,137 @@ HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop)
}
+HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
+ long * ret_value)
+{
+ if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
+ || opt >= HttpRequest::PO_LAST // ditto
+ || (! sOptionDesc[opt].mIsLong) // datatype is long
+ || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy) // pclass in valid range
+ || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal) // global setting permitted
+ || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass)) // class setting permitted
+ // can always get, no dynamic check
+ {
+ return HttpStatus(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
+ }
+
+ HttpStatus status;
+ if (pclass == HttpRequest::GLOBAL_POLICY_ID)
+ {
+ HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
+
+ status = opts.get(opt, ret_value);
+ }
+ else
+ {
+ HttpPolicyClass & opts(mPolicy->getClassOptions(pclass));
+
+ status = opts.get(opt, ret_value);
+ }
+
+ return status;
+}
+
+
+HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
+ std::string * ret_value)
+{
+ HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
+
+ if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
+ || opt >= HttpRequest::PO_LAST // ditto
+ || (sOptionDesc[opt].mIsLong) // datatype is string
+ || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy) // pclass in valid range
+ || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal) // global setting permitted
+ || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass)) // class setting permitted
+ // can always get, no dynamic check
+ {
+ return status;
+ }
+
+ // Only global has string values
+ if (pclass == HttpRequest::GLOBAL_POLICY_ID)
+ {
+ HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
+
+ status = opts.get(opt, ret_value);
+ }
+
+ return status;
+}
+
+
+HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
+ long value, long * ret_value)
+{
+ HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
+
+ if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
+ || opt >= HttpRequest::PO_LAST // ditto
+ || (! sOptionDesc[opt].mIsLong) // datatype is long
+ || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy) // pclass in valid range
+ || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal) // global setting permitted
+ || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass) // class setting permitted
+ || (RUNNING == sState && ! sOptionDesc[opt].mIsDynamic)) // dynamic setting permitted
+ {
+ return status;
+ }
+
+ if (pclass == HttpRequest::GLOBAL_POLICY_ID)
+ {
+ HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
+
+ status = opts.set(opt, value);
+ if (status && ret_value)
+ {
+ status = opts.get(opt, ret_value);
+ }
+ }
+ else
+ {
+ HttpPolicyClass & opts(mPolicy->getClassOptions(pclass));
+
+ status = opts.set(opt, value);
+ if (status && ret_value)
+ {
+ status = opts.get(opt, ret_value);
+ }
+ }
+
+ return status;
+}
+
+
+HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
+ const std::string & value, std::string * ret_value)
+{
+ HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
+
+ if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
+ || opt >= HttpRequest::PO_LAST // ditto
+ || (sOptionDesc[opt].mIsLong) // datatype is string
+ || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy) // pclass in valid range
+ || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal) // global setting permitted
+ || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass) // class setting permitted
+ || (RUNNING == sState && ! sOptionDesc[opt].mIsDynamic)) // dynamic setting permitted
+ {
+ return status;
+ }
+
+ // Only string values are global at this time
+ if (pclass == HttpRequest::GLOBAL_POLICY_ID)
+ {
+ HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
+
+ status = opts.set(opt, value);
+ if (status && ret_value)
+ {
+ status = opts.get(opt, ret_value);
+ }
+ }
+
+ return status;
+}
+
+
} // end namespace LLCore
diff --git a/indra/llcorehttp/_httpservice.h b/indra/llcorehttp/_httpservice.h
index ffe0349d4d..cf23f3ab61 100755
--- a/indra/llcorehttp/_httpservice.h
+++ b/indra/llcorehttp/_httpservice.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -53,6 +53,7 @@ namespace LLCore
class HttpRequestQueue;
class HttpPolicy;
class HttpLibcurl;
+class HttpOpSetGet;
/// The HttpService class does the work behind the request queue. It
@@ -106,7 +107,7 @@ public:
NORMAL, ///< continuous polling of request, ready, active queues
REQUEST_SLEEP ///< can sleep indefinitely waiting for request queue write
};
-
+
static void init(HttpRequestQueue *);
static void term();
@@ -136,7 +137,7 @@ public:
/// acquires its weaknesses.
static bool isStopped();
- /// Threading: callable by consumer thread *once*.
+ /// Threading: callable by init thread *once*.
void startThread();
/// Threading: callable by worker thread.
@@ -181,27 +182,38 @@ public:
}
/// Threading: callable by consumer thread.
- HttpPolicyGlobal & getGlobalOptions()
- {
- return mPolicyGlobal;
- }
-
- /// Threading: callable by consumer thread.
HttpRequest::policy_t createPolicyClass();
- /// Threading: callable by consumer thread.
- HttpPolicyClass & getClassOptions(HttpRequest::policy_t policy_class)
- {
- llassert(policy_class >= 0 && policy_class < mPolicyClasses.size());
- return mPolicyClasses[policy_class];
- }
-
protected:
void threadRun(LLCoreInt::HttpThread * thread);
ELoopSpeed processRequestQueue(ELoopSpeed loop);
+
+protected:
+ friend class HttpOpSetGet;
+ friend class HttpRequest;
+
+ // Used internally to describe what operations are allowed
+ // on each policy option.
+ struct OptionDescriptor
+ {
+ bool mIsLong;
+ bool mIsDynamic;
+ bool mIsGlobal;
+ bool mIsClass;
+ };
+
+ HttpStatus getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t,
+ long * ret_value);
+ HttpStatus getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t,
+ std::string * ret_value);
+ HttpStatus setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t,
+ long value, long * ret_value);
+ HttpStatus setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t,
+ const std::string & value, std::string * ret_value);
protected:
+ static const OptionDescriptor sOptionDesc[HttpRequest::PO_LAST];
static HttpService * sInstance;
// === shared data ===
@@ -210,13 +222,13 @@ protected:
LLAtomicU32 mExitRequested;
LLCoreInt::HttpThread * mThread;
- // === consumer-thread-only data ===
- HttpPolicyGlobal mPolicyGlobal;
- std::vector<HttpPolicyClass> mPolicyClasses;
-
// === working-thread-only data ===
HttpPolicy * mPolicy; // Simple pointer, has ownership
HttpLibcurl * mTransport; // Simple pointer, has ownership
+
+ // === main-thread-only data ===
+ HttpRequest::policy_t mLastPolicy;
+
}; // end class HttpService
} // end namespace LLCore
diff --git a/indra/llcorehttp/examples/http_texture_load.cpp b/indra/llcorehttp/examples/http_texture_load.cpp
index 40ad4f047d..73c49687d7 100755
--- a/indra/llcorehttp/examples/http_texture_load.cpp
+++ b/indra/llcorehttp/examples/http_texture_load.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -39,6 +39,7 @@
#include "httprequest.h"
#include "httphandler.h"
#include "httpresponse.h"
+#include "httpoptions.h"
#include "httpheaders.h"
#include "bufferarray.h"
#include "_mutex.h"
@@ -57,6 +58,7 @@ void usage(std::ostream & out);
// Default command line settings
static int concurrency_limit(40);
+static int highwater(100);
static char url_format[1024] = "http://example.com/some/path?texture_id=%s.texture";
#if defined(WIN32)
@@ -79,11 +81,11 @@ public:
WorkingSet();
~WorkingSet();
- bool reload(LLCore::HttpRequest *);
+ bool reload(LLCore::HttpRequest *, LLCore::HttpOptions *);
virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response);
- void loadTextureUuids(FILE * in);
+ void loadAssetUuids(FILE * in);
public:
struct Spec
@@ -93,24 +95,27 @@ public:
int mLength;
};
typedef std::set<LLCore::HttpHandle> handle_set_t;
- typedef std::vector<Spec> texture_list_t;
+ typedef std::vector<Spec> asset_list_t;
public:
bool mVerbose;
bool mRandomRange;
- int mMaxConcurrency;
+ int mRequestLowWater;
+ int mRequestHighWater;
handle_set_t mHandles;
int mRemaining;
int mLimit;
int mAt;
std::string mUrl;
- texture_list_t mTextures;
+ asset_list_t mAssets;
int mErrorsApi;
int mErrorsHttp;
int mErrorsHttp404;
int mErrorsHttp416;
int mErrorsHttp500;
int mErrorsHttp503;
+ int mRetries;
+ int mRetriesHttp503;
int mSuccesses;
long mByteCount;
LLCore::HttpHeaders * mHeaders;
@@ -158,7 +163,7 @@ int main(int argc, char** argv)
bool do_verbose(false);
int option(-1);
- while (-1 != (option = getopt(argc, argv, "u:c:h?Rv")))
+ while (-1 != (option = getopt(argc, argv, "u:c:h?RvH:")))
{
switch (option)
{
@@ -182,6 +187,21 @@ int main(int argc, char** argv)
}
break;
+ case 'H':
+ {
+ unsigned long value;
+ char * end;
+
+ value = strtoul(optarg, &end, 10);
+ if (value < 1 || value > 100 || *end != '\0')
+ {
+ usage(std::cerr);
+ return 1;
+ }
+ highwater = value;
+ }
+ break;
+
case 'R':
do_random = true;
break;
@@ -216,25 +236,32 @@ int main(int argc, char** argv)
// Initialization
init_curl();
LLCore::HttpRequest::createService();
- LLCore::HttpRequest::setPolicyClassOption(LLCore::HttpRequest::DEFAULT_POLICY_ID,
- LLCore::HttpRequest::CP_CONNECTION_LIMIT,
- concurrency_limit);
+ LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_CONNECTION_LIMIT,
+ LLCore::HttpRequest::DEFAULT_POLICY_ID,
+ concurrency_limit,
+ NULL);
LLCore::HttpRequest::startThread();
// Get service point
LLCore::HttpRequest * hr = new LLCore::HttpRequest();
+ // Get request options
+ LLCore::HttpOptions * opt = new LLCore::HttpOptions();
+ opt->setRetries(12);
+ opt->setUseRetryAfter(true);
+
// Get a handler/working set
WorkingSet ws;
// Fill the working set with work
ws.mUrl = url_format;
- ws.loadTextureUuids(uuids);
+ ws.loadAssetUuids(uuids);
ws.mRandomRange = do_random;
ws.mVerbose = do_verbose;
- ws.mMaxConcurrency = 100;
+ ws.mRequestHighWater = highwater;
+ ws.mRequestLowWater = ws.mRequestHighWater / 2;
- if (! ws.mTextures.size())
+ if (! ws.mAssets.size())
{
std::cerr << "No UUIDs found in file '" << argv[optind] << "'." << std::endl;
return 1;
@@ -246,9 +273,9 @@ int main(int argc, char** argv)
// Run it
int passes(0);
- while (! ws.reload(hr))
+ while (! ws.reload(hr, opt))
{
- hr->update(5000000);
+ hr->update(0);
ms_sleep(2);
if (0 == (++passes % 200))
{
@@ -265,6 +292,8 @@ int main(int argc, char** argv)
std::cout << "HTTP 404 errors: " << ws.mErrorsHttp404 << " HTTP 416 errors: " << ws.mErrorsHttp416
<< " HTTP 500 errors: " << ws.mErrorsHttp500 << " HTTP 503 errors: " << ws.mErrorsHttp503
<< std::endl;
+ std::cout << "Retries: " << ws.mRetries << " Retries on 503: " << ws.mRetriesHttp503
+ << std::endl;
std::cout << "User CPU: " << (metrics.mEndUTime - metrics.mStartUTime)
<< " uS System CPU: " << (metrics.mEndSTime - metrics.mStartSTime)
<< " uS Wall Time: " << (metrics.mEndWallTime - metrics.mStartWallTime)
@@ -275,6 +304,8 @@ int main(int argc, char** argv)
// Clean up
hr->requestStopThread(NULL);
ms_sleep(1000);
+ opt->release();
+ opt = NULL;
delete hr;
LLCore::HttpRequest::destroyService();
term_curl();
@@ -300,8 +331,10 @@ void usage(std::ostream & out)
" -u <url_format> printf-style format string for URL generation\n"
" Default: " << url_format << "\n"
" -R Issue GETs with random Range: headers\n"
- " -c <limit> Maximum request concurrency. Range: [1..100]\n"
+ " -c <limit> Maximum connection concurrency. Range: [1..100]\n"
" Default: " << concurrency_limit << "\n"
+ " -H <limit> HTTP request highwater (requests fed to llcorehttp).\n"
+ " Range: [1..100] Default: " << highwater << "\n"
" -v Verbose mode. Issue some chatter while running\n"
" -h print this help\n"
"\n"
@@ -322,13 +355,15 @@ WorkingSet::WorkingSet()
mErrorsHttp416(0),
mErrorsHttp500(0),
mErrorsHttp503(0),
+ mRetries(0),
+ mRetriesHttp503(0),
mSuccesses(0),
mByteCount(0L)
{
- mTextures.reserve(30000);
+ mAssets.reserve(30000);
mHeaders = new LLCore::HttpHeaders;
- mHeaders->mHeaders.push_back("Accept: image/x-j2c");
+ mHeaders->append("Accept", "image/x-j2c");
}
@@ -342,29 +377,35 @@ WorkingSet::~WorkingSet()
}
-bool WorkingSet::reload(LLCore::HttpRequest * hr)
+bool WorkingSet::reload(LLCore::HttpRequest * hr, LLCore::HttpOptions * opt)
{
- int to_do((std::min)(mRemaining, mMaxConcurrency - int(mHandles.size())));
+ if (mRequestLowWater <= mHandles.size())
+ {
+ // Haven't fallen below low-water level yet.
+ return false;
+ }
+
+ int to_do((std::min)(mRemaining, mRequestHighWater - int(mHandles.size())));
for (int i(0); i < to_do; ++i)
{
char buffer[1024];
#if defined(WIN32)
- _snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, mUrl.c_str(), mTextures[mAt].mUuid.c_str());
+ _snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, mUrl.c_str(), mAssets[mAt].mUuid.c_str());
#else
- snprintf(buffer, sizeof(buffer), mUrl.c_str(), mTextures[mAt].mUuid.c_str());
+ snprintf(buffer, sizeof(buffer), mUrl.c_str(), mAssets[mAt].mUuid.c_str());
#endif
- int offset(mRandomRange ? ((unsigned long) rand()) % 1000000UL : mTextures[mAt].mOffset);
- int length(mRandomRange ? ((unsigned long) rand()) % 1000000UL : mTextures[mAt].mLength);
+ int offset(mRandomRange ? ((unsigned long) rand()) % 1000000UL : mAssets[mAt].mOffset);
+ int length(mRandomRange ? ((unsigned long) rand()) % 1000000UL : mAssets[mAt].mLength);
LLCore::HttpHandle handle;
if (offset || length)
{
- handle = hr->requestGetByteRange(0, 0, buffer, offset, length, NULL, mHeaders, this);
+ handle = hr->requestGetByteRange(0, 0, buffer, offset, length, opt, mHeaders, this);
}
else
{
- handle = hr->requestGet(0, 0, buffer, NULL, mHeaders, this);
+ handle = hr->requestGet(0, 0, buffer, opt, mHeaders, this);
}
if (! handle)
{
@@ -410,7 +451,7 @@ void WorkingSet::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * r
{
// More success
LLCore::BufferArray * data(response->getBody());
- mByteCount += data->size();
+ mByteCount += data ? data->size() : 0;
++mSuccesses;
}
else
@@ -446,6 +487,10 @@ void WorkingSet::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * r
++mErrorsApi;
}
}
+ unsigned int retry(0U), retry_503(0U);
+ response->getRetries(&retry, &retry_503);
+ mRetries += int(retry);
+ mRetriesHttp503 += int(retry_503);
mHandles.erase(it);
}
@@ -459,21 +504,21 @@ void WorkingSet::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * r
}
-void WorkingSet::loadTextureUuids(FILE * in)
+void WorkingSet::loadAssetUuids(FILE * in)
{
char buffer[1024];
while (fgets(buffer, sizeof(buffer), in))
{
- WorkingSet::Spec texture;
+ WorkingSet::Spec asset;
char * state(NULL);
char * token = strtok_r(buffer, " \t\n,", &state);
if (token && 36 == strlen(token))
{
// Close enough for this function
- texture.mUuid = token;
- texture.mOffset = 0;
- texture.mLength = 0;
+ asset.mUuid = token;
+ asset.mOffset = 0;
+ asset.mLength = 0;
token = strtok_r(buffer, " \t\n,", &state);
if (token)
{
@@ -482,14 +527,14 @@ void WorkingSet::loadTextureUuids(FILE * in)
if (token)
{
int length(atoi(token));
- texture.mOffset = offset;
- texture.mLength = length;
+ asset.mOffset = offset;
+ asset.mLength = length;
}
}
- mTextures.push_back(texture);
+ mAssets.push_back(asset);
}
}
- mRemaining = mLimit = mTextures.size();
+ mRemaining = mLimit = mAssets.size();
}
diff --git a/indra/llcorehttp/httpcommon.cpp b/indra/llcorehttp/httpcommon.cpp
index 0738760763..c2f15155ac 100755
--- a/indra/llcorehttp/httpcommon.cpp
+++ b/indra/llcorehttp/httpcommon.cpp
@@ -70,7 +70,8 @@ std::string HttpStatus::toString() const
"Invalid datatype for argument or option",
"Option has not been explicitly set",
"Option is not dynamic and must be set early",
- "Invalid HTTP status code received from server"
+ "Invalid HTTP status code received from server",
+ "Could not allocate required resource"
};
static const int llcore_errors_count(sizeof(llcore_errors) / sizeof(llcore_errors[0]));
@@ -177,6 +178,44 @@ std::string HttpStatus::toString() const
}
+std::string HttpStatus::toTerseString() const
+{
+ std::ostringstream result;
+
+ unsigned int error_value((unsigned short) mStatus);
+
+ switch (mType)
+ {
+ case EXT_CURL_EASY:
+ result << "Easy_";
+ break;
+
+ case EXT_CURL_MULTI:
+ result << "Multi_";
+ break;
+
+ case LLCORE:
+ result << "Core_";
+ break;
+
+ default:
+ if (isHttpStatus())
+ {
+ result << "Http_";
+ error_value = mType;
+ }
+ else
+ {
+ result << "Unknown_";
+ }
+ break;
+ }
+
+ result << error_value;
+ return result.str();
+}
+
+
// Pass true on statuses that might actually be cleared by a
// retry. Library failures, calling problems, etc. aren't
// going to be fixed by squirting bits all over the Net.
@@ -206,6 +245,5 @@ bool HttpStatus::isRetryable() const
*this == inv_cont_range); // Short data read disagrees with content-range
}
-
} // end namespace LLCore
diff --git a/indra/llcorehttp/httpcommon.h b/indra/llcorehttp/httpcommon.h
index 41fb5164cf..9601f94125 100755
--- a/indra/llcorehttp/httpcommon.h
+++ b/indra/llcorehttp/httpcommon.h
@@ -29,9 +29,9 @@
/// @package LLCore::HTTP
///
-/// This library implements a high-level, Indra-code-free client interface to
-/// HTTP services based on actual patterns found in the viewer and simulator.
-/// Interfaces are similar to those supplied by the legacy classes
+/// This library implements a high-level, Indra-code-free (somewhat) client
+/// interface to HTTP services based on actual patterns found in the viewer
+/// and simulator. Interfaces are similar to those supplied by the legacy classes
/// LLCurlRequest and LLHTTPClient. To that is added a policy scheme that
/// allows an application to specify connection behaviors: limits on
/// connections, HTTP keepalive, HTTP pipelining, retry-on-error limits, etc.
@@ -52,7 +52,7 @@
/// - "llcorehttp/httprequest.h"
/// - "llcorehttp/httpresponse.h"
///
-/// The library is still under early development and particular users
+/// The library is still under development and particular users
/// may need access to internal implementation details that are found
/// in the _*.h header files. But this is a crutch to be avoided if at
/// all possible and probably indicates some interface work is neeeded.
@@ -66,6 +66,8 @@
/// . CRYPTO_set_id_callback(...)
/// - HttpRequest::createService() called to instantiate singletons
/// and support objects.
+/// - HttpRequest::startThread() to kick off the worker thread and
+/// begin servicing requests.
///
/// An HTTP consumer in an application, and an application may have many
/// consumers, does a few things:
@@ -91,10 +93,12 @@
/// objects.
/// - Do completion processing in your onCompletion() method.
///
-/// Code fragments:
-/// Rather than a poorly-maintained example in comments, look in the
-/// example subdirectory which is a minimal yet functional tool to do
-/// GET request performance testing. With four calls:
+/// Code fragments.
+///
+/// Initialization. Rather than a poorly-maintained example in
+/// comments, look in the example subdirectory which is a minimal
+/// yet functional tool to do GET request performance testing.
+/// With four calls:
///
/// init_curl();
/// LLCore::HttpRequest::createService();
@@ -103,7 +107,85 @@
///
/// the program is basically ready to issue requests.
///
-
+/// HttpHandler. Having started life as a non-indra library,
+/// this code broke away from the classic Responder model and
+/// introduced a handler class to represent an interface for
+/// request responses. This is a non-reference-counted entity
+/// which can be used as a base class or a mixin. An instance
+/// of a handler can be used for each request or can be shared
+/// among any number of requests. Your choice but expect to
+/// code something like the following:
+///
+/// class AppHandler : public LLCore::HttpHandler
+/// {
+/// public:
+/// virtual void onCompleted(HttpHandle handle,
+/// HttpResponse * response)
+/// {
+/// ...
+/// }
+/// ...
+/// };
+/// ...
+/// handler = new handler(...);
+///
+///
+/// Issuing requests. Using 'hr' above,
+///
+/// hr->requestGet(HttpRequest::DEFAULT_POLICY_ID,
+/// 0, // Priority, not used yet
+/// url,
+/// NULL, // options
+/// NULL, // additional headers
+/// handler);
+///
+/// If that returns a value other than LLCORE_HTTP_HANDLE_INVALID,
+/// the request was successfully issued and there will eventally
+/// be a status delivered to the handler. If invalid is returnedd,
+/// the actual status can be retrieved by calling hr->getStatus().
+///
+/// Completing requests and delivering notifications. Operations
+/// are all performed by the worker thread and will be driven to
+/// completion regardless of caller actions. Notification of
+/// completion (success or failure) is done by calls to
+/// HttpRequest::update() which will invoke handlers for completed
+/// requests:
+///
+/// hr->update(0);
+/// // Callbacks into handler->onCompleted()
+///
+///
+/// Threads.
+///
+/// Threads are supported and used by this library. The various
+/// classes, methods and members are documented with thread
+/// constraints which programmers must follow and which are
+/// defined as follows:
+///
+/// consumer Any thread that has instanced HttpRequest and is
+/// issuing requests. A particular instance can only
+/// be used by one consumer thread but a consumer may
+/// have many instances available to it.
+/// init Special consumer thread, usually the main thread,
+/// involved in setting up the library at startup.
+/// worker Thread used internally by the library to perform
+/// HTTP operations. Consumers will not have to deal
+/// with this thread directly but some APIs are reserved
+/// to it.
+/// any Consumer or worker thread.
+///
+/// For the most part, API users will not have to do much in the
+/// way of ensuring thread safely. However, there is a tremendous
+/// amount of sharing between threads of read-only data. So when
+/// documentation declares that an option or header instance
+/// becomes shared between consumer and worker, the consumer must
+/// not modify the shared object.
+///
+/// Internally, there is almost no thread synchronization. During
+/// normal operations (non-init, non-term), only the request queue
+/// and the multiple reply queues are shared between threads and
+/// only here are mutexes used.
+///
#include "linden_common.h" // Modifies curl/curl.h interfaces
@@ -164,7 +246,10 @@ enum HttpError
HE_OPT_NOT_DYNAMIC = 8,
// Invalid HTTP status code returned by server
- HE_INVALID_HTTP_STATUS = 9
+ HE_INVALID_HTTP_STATUS = 9,
+
+ // Couldn't allocate resource, typically libcurl handle
+ HE_BAD_ALLOC = 10
}; // end enum HttpError
@@ -239,9 +324,10 @@ struct HttpStatus
return *this;
}
- static const type_enum_t EXT_CURL_EASY = 0;
- static const type_enum_t EXT_CURL_MULTI = 1;
- static const type_enum_t LLCORE = 2;
+ static const type_enum_t EXT_CURL_EASY = 0; ///< mStatus is an error from a curl_easy_*() call
+ static const type_enum_t EXT_CURL_MULTI = 1; ///< mStatus is an error from a curl_multi_*() call
+ static const type_enum_t LLCORE = 2; ///< mStatus is an HE_* error code
+ ///< 100-999 directly represent HTTP status codes
type_enum_t mType;
short mStatus;
@@ -297,6 +383,14 @@ struct HttpStatus
/// LLCore itself).
std::string toString() const;
+ /// Convert status to a compact string representation
+ /// of the form: "<type>_<value>". The <type> will be
+ /// one of: Core, Http, Easy, Multi, Unknown. And
+ /// <value> will be an unsigned integer. More easily
+ /// interpreted than the hex representation, it's still
+ /// compact and easily searched.
+ std::string toTerseString() const;
+
/// Returns true if the status value represents an
/// HTTP response status (100 - 999).
bool isHttpStatus() const
diff --git a/indra/llcorehttp/httpheaders.cpp b/indra/llcorehttp/httpheaders.cpp
index 2832696271..23ebea361c 100755
--- a/indra/llcorehttp/httpheaders.cpp
+++ b/indra/llcorehttp/httpheaders.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -26,6 +26,8 @@
#include "httpheaders.h"
+#include "llstring.h"
+
namespace LLCore
{
@@ -40,5 +42,142 @@ HttpHeaders::~HttpHeaders()
{}
+void
+HttpHeaders::clear()
+{
+ mHeaders.clear();
+}
+
+
+void HttpHeaders::append(const std::string & name, const std::string & value)
+{
+ mHeaders.push_back(value_type(name, value));
+}
+
+
+void HttpHeaders::append(const char * name, const char * value)
+{
+ mHeaders.push_back(value_type(name, value));
+}
+
+
+void HttpHeaders::appendNormal(const char * header, size_t size)
+{
+ std::string name;
+ std::string value;
+
+ int col_pos(0);
+ for (; col_pos < size; ++col_pos)
+ {
+ if (':' == header[col_pos])
+ break;
+ }
+
+ if (col_pos < size)
+ {
+ // Looks like a header, split it and normalize.
+ // Name is everything before the colon, may be zero-length.
+ name.assign(header, col_pos);
+
+ // Value is everything after the colon, may also be zero-length.
+ const size_t val_len(size - col_pos - 1);
+ if (val_len)
+ {
+ value.assign(header + col_pos + 1, val_len);
+ }
+
+ // Clean the strings
+ LLStringUtil::toLower(name);
+ LLStringUtil::trim(name);
+ LLStringUtil::trimHead(value);
+ }
+ else
+ {
+ // Uncertain what this is, we'll pack it as
+ // a name without a value. Won't clean as we don't
+ // know what it is...
+ name.assign(header, size);
+ }
+
+ mHeaders.push_back(value_type(name, value));
+}
+
+
+// Find from end to simulate a tradition of using single-valued
+// std::map for this in the past.
+const std::string * HttpHeaders::find(const char * name) const
+{
+ const_reverse_iterator iend(rend());
+ for (const_reverse_iterator iter(rbegin()); iend != iter; ++iter)
+ {
+ if ((*iter).first == name)
+ {
+ return &(*iter).second;
+ }
+ }
+ return NULL;
+}
+
+
+// Standard Iterators
+HttpHeaders::iterator HttpHeaders::begin()
+{
+ return mHeaders.begin();
+}
+
+
+HttpHeaders::const_iterator HttpHeaders::begin() const
+{
+ return mHeaders.begin();
+}
+
+
+HttpHeaders::iterator HttpHeaders::end()
+{
+ return mHeaders.end();
+}
+
+
+HttpHeaders::const_iterator HttpHeaders::end() const
+{
+ return mHeaders.end();
+}
+
+
+// Standard Reverse Iterators
+HttpHeaders::reverse_iterator HttpHeaders::rbegin()
+{
+ return mHeaders.rbegin();
+}
+
+
+HttpHeaders::const_reverse_iterator HttpHeaders::rbegin() const
+{
+ return mHeaders.rbegin();
+}
+
+
+HttpHeaders::reverse_iterator HttpHeaders::rend()
+{
+ return mHeaders.rend();
+}
+
+
+HttpHeaders::const_reverse_iterator HttpHeaders::rend() const
+{
+ return mHeaders.rend();
+}
+
+
+// Return the raw container to the caller.
+//
+// To be used FOR UNIT TESTS ONLY.
+//
+HttpHeaders::container_t & HttpHeaders::getContainerTESTONLY()
+{
+ return mHeaders;
+}
+
+
} // end namespace LLCore
diff --git a/indra/llcorehttp/httpheaders.h b/indra/llcorehttp/httpheaders.h
index 3449daa3a1..f70cd898f3 100755
--- a/indra/llcorehttp/httpheaders.h
+++ b/indra/llcorehttp/httpheaders.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -43,13 +43,26 @@ namespace LLCore
/// caller has asked that headers be returned (not the default
/// option).
///
-/// @note
-/// This is a minimally-functional placeholder at the moment
-/// to fill out the class hierarchy. The final class will be
-/// something else, probably more pair-oriented. It's also
-/// an area where shared values are desirable so refcounting is
-/// already specced and a copy-on-write scheme imagined.
-/// Expect changes here.
+/// Class is mostly a thin wrapper around a vector of pairs
+/// of strings. Methods provided are few and intended to
+/// reflect actual use patterns. These include:
+/// - Clearing the list
+/// - Appending a name/value pair to the vector
+/// - Processing a raw byte string into a normalized name/value
+/// pair and appending the result.
+/// - Simple case-sensitive find-last-by-name search
+/// - Forward and reverse iterators over all pairs
+///
+/// Container is ordered and multi-valued. Headers are
+/// written in the order in which they are appended and
+/// are stored in the order in which they're received from
+/// the wire. The same header may appear two or more times
+/// in any container. Searches using the simple find()
+/// interface will find only the last occurrence (somewhat
+/// simulates the use of std::map). Fuller searches require
+/// the use of an iterator. Headers received from the wire
+/// are only returned from the last request when redirections
+/// are involved.
///
/// Threading: Not intrinsically thread-safe. It *is* expected
/// that callers will build these objects and then share them
@@ -64,6 +77,16 @@ namespace LLCore
class HttpHeaders : public LLCoreInt::RefCounted
{
public:
+ typedef std::pair<std::string, std::string> header_t;
+ typedef std::vector<header_t> container_t;
+ typedef container_t::iterator iterator;
+ typedef container_t::const_iterator const_iterator;
+ typedef container_t::reverse_iterator reverse_iterator;
+ typedef container_t::const_reverse_iterator const_reverse_iterator;
+ typedef container_t::value_type value_type;
+ typedef container_t::size_type size_type;
+
+public:
/// @post In addition to the instance, caller has a refcount
/// to the instance. A call to @see release() will destroy
/// the instance.
@@ -76,7 +99,78 @@ protected:
void operator=(const HttpHeaders &); // Not defined
public:
- typedef std::vector<std::string> container_t;
+ // Empty the list of headers.
+ void clear();
+
+ // Append a name/value pair supplied as either std::strings
+ // or NUL-terminated char * to the header list. No normalization
+ // is performed on the strings. No conformance test is
+ // performed (names may contain spaces, colons, etc.).
+ //
+ void append(const std::string & name, const std::string & value);
+ void append(const char * name, const char * value);
+
+ // Extract a name/value pair from a raw byte array using
+ // the first colon character as a separator. Input string
+ // does not need to be NUL-terminated. Resulting name/value
+ // pair is appended to the header list.
+ //
+ // Normalization is performed on the name/value pair as
+ // follows:
+ // - name is lower-cased according to mostly ASCII rules
+ // - name is left- and right-trimmed of spaces and tabs
+ // - value is left-trimmed of spaces and tabs
+ // - either or both of name and value may be zero-length
+ //
+ // By convention, headers read from the wire will be normalized
+ // in this fashion prior to delivery to any HttpHandler code.
+ // Headers to be written to the wire are left as appended to
+ // the list.
+ void appendNormal(const char * header, size_t size);
+
+ // Perform a simple, case-sensitive search of the header list
+ // returning a pointer to the value of the last matching header
+ // in the header list. If none is found, a NULL pointer is returned.
+ //
+ // Any pointer returned references objects in the container itself
+ // and will have the same lifetime as this class. If you want
+ // the value beyond the lifetime of this instance, make a copy.
+ //
+ // @arg name C-style string giving the name of a header
+ // to search. The comparison is case-sensitive
+ // though list entries may have been normalized
+ // to lower-case.
+ //
+ // @return NULL if the header wasn't found otherwise
+ // a pointer to a std::string in the container.
+ // Pointer is valid only for the lifetime of
+ // the container or until container is modifed.
+ //
+ const std::string * find(const char * name) const;
+
+ // Count of headers currently in the list.
+ size_type size() const
+ {
+ return mHeaders.size();
+ }
+
+ // Standard std::vector-based forward iterators.
+ iterator begin();
+ const_iterator begin() const;
+ iterator end();
+ const_iterator end() const;
+
+ // Standard std::vector-based reverse iterators.
+ reverse_iterator rbegin();
+ const_reverse_iterator rbegin() const;
+ reverse_iterator rend();
+ const_reverse_iterator rend() const;
+
+public:
+ // For unit tests only - not a public API
+ container_t & getContainerTESTONLY();
+
+protected:
container_t mHeaders;
}; // end class HttpHeaders
diff --git a/indra/llcorehttp/httpoptions.cpp b/indra/llcorehttp/httpoptions.cpp
index 1699d19f8d..5bf1ecb4a5 100755
--- a/indra/llcorehttp/httpoptions.cpp
+++ b/indra/llcorehttp/httpoptions.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -38,7 +38,9 @@ HttpOptions::HttpOptions()
mWantHeaders(false),
mTracing(HTTP_TRACE_OFF),
mTimeout(HTTP_REQUEST_TIMEOUT_DEFAULT),
- mRetries(HTTP_RETRY_COUNT_DEFAULT)
+ mTransferTimeout(HTTP_REQUEST_XFER_TIMEOUT_DEFAULT),
+ mRetries(HTTP_RETRY_COUNT_DEFAULT),
+ mUseRetryAfter(HTTP_USE_RETRY_AFTER_DEFAULT)
{}
@@ -64,10 +66,21 @@ void HttpOptions::setTimeout(unsigned int timeout)
}
+void HttpOptions::setTransferTimeout(unsigned int timeout)
+{
+ mTransferTimeout = timeout;
+}
+
+
void HttpOptions::setRetries(unsigned int retries)
{
mRetries = retries;
}
+void HttpOptions::setUseRetryAfter(bool use_retry)
+{
+ mUseRetryAfter = use_retry;
+}
+
} // end namespace LLCore
diff --git a/indra/llcorehttp/httpoptions.h b/indra/llcorehttp/httpoptions.h
index 97e46a8cd3..4ab5ff18c4 100755
--- a/indra/llcorehttp/httpoptions.h
+++ b/indra/llcorehttp/httpoptions.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -68,36 +68,55 @@ protected:
void operator=(const HttpOptions &); // Not defined
public:
+ // Default: false
void setWantHeaders(bool wanted);
bool getWantHeaders() const
{
return mWantHeaders;
}
-
+
+ // Default: 0
void setTrace(int long);
int getTrace() const
{
return mTracing;
}
+ // Default: 30
void setTimeout(unsigned int timeout);
unsigned int getTimeout() const
{
return mTimeout;
}
+ // Default: 0
+ void setTransferTimeout(unsigned int timeout);
+ unsigned int getTransferTimeout() const
+ {
+ return mTransferTimeout;
+ }
+
+ // Default: 8
void setRetries(unsigned int retries);
unsigned int getRetries() const
{
return mRetries;
}
+
+ // Default: true
+ void setUseRetryAfter(bool use_retry);
+ bool getUseRetryAfter() const
+ {
+ return mUseRetryAfter;
+ }
protected:
bool mWantHeaders;
int mTracing;
unsigned int mTimeout;
+ unsigned int mTransferTimeout;
unsigned int mRetries;
-
+ bool mUseRetryAfter;
}; // end class HttpOptions
diff --git a/indra/llcorehttp/httprequest.cpp b/indra/llcorehttp/httprequest.cpp
index 9b739a8825..7b1888e3eb 100755
--- a/indra/llcorehttp/httprequest.cpp
+++ b/indra/llcorehttp/httprequest.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -54,12 +54,8 @@ namespace LLCore
// ====================================
-HttpRequest::policy_t HttpRequest::sNextPolicyID(1);
-
-
HttpRequest::HttpRequest()
- : //HttpHandler(),
- mReplyQueue(NULL),
+ : mReplyQueue(NULL),
mRequestQueue(NULL)
{
mRequestQueue = HttpRequestQueue::instanceOf();
@@ -90,45 +86,91 @@ HttpRequest::~HttpRequest()
// ====================================
-HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, long value)
+HttpRequest::policy_t HttpRequest::createPolicyClass()
{
if (HttpService::RUNNING == HttpService::instanceOf()->getState())
{
- return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
+ return 0;
}
- return HttpService::instanceOf()->getGlobalOptions().set(opt, value);
+ return HttpService::instanceOf()->createPolicyClass();
}
-HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value)
+HttpStatus HttpRequest::setStaticPolicyOption(EPolicyOption opt, policy_t pclass,
+ long value, long * ret_value)
{
if (HttpService::RUNNING == HttpService::instanceOf()->getState())
{
return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
}
- return HttpService::instanceOf()->getGlobalOptions().set(opt, value);
+ return HttpService::instanceOf()->setPolicyOption(opt, pclass, value, ret_value);
}
-HttpRequest::policy_t HttpRequest::createPolicyClass()
+HttpStatus HttpRequest::setStaticPolicyOption(EPolicyOption opt, policy_t pclass,
+ const std::string & value, std::string * ret_value)
{
if (HttpService::RUNNING == HttpService::instanceOf()->getState())
{
- return 0;
+ return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
}
- return HttpService::instanceOf()->createPolicyClass();
+ return HttpService::instanceOf()->setPolicyOption(opt, pclass, value, ret_value);
}
-HttpStatus HttpRequest::setPolicyClassOption(policy_t policy_id,
- EClassPolicy opt,
- long value)
+HttpHandle HttpRequest::setPolicyOption(EPolicyOption opt, policy_t pclass,
+ long value, HttpHandler * handler)
{
- if (HttpService::RUNNING == HttpService::instanceOf()->getState())
+ HttpStatus status;
+ HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID);
+
+ HttpOpSetGet * op = new HttpOpSetGet();
+ if (! (status = op->setupSet(opt, pclass, value)))
{
- return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
+ op->release();
+ mLastReqStatus = status;
+ return handle;
+ }
+ op->setReplyPath(mReplyQueue, handler);
+ if (! (status = mRequestQueue->addOp(op))) // transfers refcount
+ {
+ op->release();
+ mLastReqStatus = status;
+ return handle;
+ }
+
+ mLastReqStatus = status;
+ handle = static_cast<HttpHandle>(op);
+
+ return handle;
+}
+
+
+HttpHandle HttpRequest::setPolicyOption(EPolicyOption opt, policy_t pclass,
+ const std::string & value, HttpHandler * handler)
+{
+ HttpStatus status;
+ HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID);
+
+ HttpOpSetGet * op = new HttpOpSetGet();
+ if (! (status = op->setupSet(opt, pclass, value)))
+ {
+ op->release();
+ mLastReqStatus = status;
+ return handle;
+ }
+ op->setReplyPath(mReplyQueue, handler);
+ if (! (status = mRequestQueue->addOp(op))) // transfers refcount
+ {
+ op->release();
+ mLastReqStatus = status;
+ return handle;
}
- return HttpService::instanceOf()->getClassOptions(policy_id).set(opt, value);
+
+ mLastReqStatus = status;
+ handle = static_cast<HttpHandle>(op);
+
+ return handle;
}
@@ -474,31 +516,6 @@ HttpHandle HttpRequest::requestSpin(int mode)
return handle;
}
-// ====================================
-// Dynamic Policy Methods
-// ====================================
-
-HttpHandle HttpRequest::requestSetHttpProxy(const std::string & proxy, HttpHandler * handler)
-{
- HttpStatus status;
- HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID);
-
- HttpOpSetGet * op = new HttpOpSetGet();
- op->setupSet(GP_HTTP_PROXY, proxy);
- op->setReplyPath(mReplyQueue, handler);
- if (! (status = mRequestQueue->addOp(op))) // transfers refcount
- {
- op->release();
- mLastReqStatus = status;
- return handle;
- }
-
- mLastReqStatus = status;
- handle = static_cast<HttpHandle>(op);
-
- return handle;
-}
-
} // end namespace LLCore
diff --git a/indra/llcorehttp/httprequest.h b/indra/llcorehttp/httprequest.h
index ab2f302d34..651654844a 100755
--- a/indra/llcorehttp/httprequest.h
+++ b/indra/llcorehttp/httprequest.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -56,6 +56,9 @@ class BufferArray;
/// The class supports the current HTTP request operations:
///
/// - requestGetByteRange: GET with Range header for a single range of bytes
+/// - requestGet:
+/// - requestPost:
+/// - requestPut:
///
/// Policy Classes
///
@@ -100,9 +103,26 @@ public:
/// Represents a default, catch-all policy class that guarantees
/// eventual service for any HTTP request.
- static const int DEFAULT_POLICY_ID = 0;
+ static const policy_t DEFAULT_POLICY_ID = 0;
+ static const policy_t INVALID_POLICY_ID = 0xFFFFFFFFU;
+ static const policy_t GLOBAL_POLICY_ID = 0xFFFFFFFEU;
- enum EGlobalPolicy
+ /// Create a new policy class into which requests can be made.
+ ///
+ /// All class creation must occur before threads are started and
+ /// transport begins. Policy classes are limited to a small value.
+ /// Currently that limit is the default class + 1.
+ ///
+ /// @return If positive, the policy_id used to reference
+ /// the class in other methods. If 0, requests
+ /// for classes have exceeded internal limits
+ /// or caller has tried to create a class after
+ /// threads have been started. Caller must fallback
+ /// and recover.
+ ///
+ static policy_t createPolicyClass();
+
+ enum EPolicyOption
{
/// Maximum number of connections the library will use to
/// perform operations. This is somewhat soft as the underlying
@@ -113,24 +133,40 @@ public:
/// a somewhat soft value. There may be an additional five
/// connections per policy class depending upon runtime
/// behavior.
- GP_CONNECTION_LIMIT,
+ ///
+ /// Both global and per-class
+ PO_CONNECTION_LIMIT,
+
+ /// Limits the number of connections used for a single
+ /// literal address/port pair within the class.
+ ///
+ /// Per-class only
+ PO_PER_HOST_CONNECTION_LIMIT,
/// String containing a system-appropriate directory name
/// where SSL certs are stored.
- GP_CA_PATH,
+ ///
+ /// Global only
+ PO_CA_PATH,
/// String giving a full path to a file containing SSL certs.
- GP_CA_FILE,
+ ///
+ /// Global only
+ PO_CA_FILE,
/// String of host/port to use as simple HTTP proxy. This is
/// going to change in the future into something more elaborate
/// that may support richer schemes.
- GP_HTTP_PROXY,
+ ///
+ /// Global only
+ PO_HTTP_PROXY,
/// Long value that if non-zero enables the use of the
/// traditional LLProxy code for http/socks5 support. If
- /// enabled, has priority over GP_HTTP_PROXY.
- GP_LLPROXY,
+ // enabled, has priority over GP_HTTP_PROXY.
+ ///
+ /// Global only
+ PO_LLPROXY,
/// Long value setting the logging trace level for the
/// library. Possible values are:
@@ -143,50 +179,59 @@ public:
/// These values are also used in the trace modes for
/// individual requests in HttpOptions. Also be aware that
/// tracing tends to impact performance of the viewer.
- GP_TRACE
- };
-
- /// Set a parameter on a global policy option. Calls
- /// made after the start of the servicing thread are
- /// not honored and return an error status.
- ///
- /// @param opt Enum of option to be set.
- /// @param value Desired value of option.
- /// @return Standard status code.
- static HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, long value);
- static HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value);
-
- /// Create a new policy class into which requests can be made.
- ///
- /// @return If positive, the policy_id used to reference
- /// the class in other methods. If 0, an error
- /// occurred and @see getStatus() may provide more
- /// detail on the reason.
- static policy_t createPolicyClass();
-
- enum EClassPolicy
- {
- /// Limits the number of connections used for the class.
- CP_CONNECTION_LIMIT,
-
- /// Limits the number of connections used for a single
- /// literal address/port pair within the class.
- CP_PER_HOST_CONNECTION_LIMIT,
+ ///
+ /// Global only
+ PO_TRACE,
/// Suitable requests are allowed to pipeline on their
/// connections when they ask for it.
- CP_ENABLE_PIPELINING
+ ///
+ /// Per-class only
+ PO_ENABLE_PIPELINING,
+
+ /// Controls whether client-side throttling should be
+ /// performed on this policy class. Positive values
+ /// enable throttling and specify the request rate
+ /// (requests per second) that should be targetted.
+ /// A value of zero, the default, specifies no throttling.
+ ///
+ /// Per-class only
+ PO_THROTTLE_RATE,
+
+ PO_LAST // Always at end
};
-
+
+ /// Set a policy option for a global or class parameter at
+ /// startup time (prior to thread start).
+ ///
+ /// @param opt Enum of option to be set.
+ /// @param pclass For class-based options, the policy class ID to
+ /// be changed. For globals, specify GLOBAL_POLICY_ID.
+ /// @param value Desired value of option.
+ /// @param ret_value Pointer to receive effective set value
+ /// if successful. May be NULL if effective
+ /// value not wanted.
+ /// @return Standard status code.
+ static HttpStatus setStaticPolicyOption(EPolicyOption opt, policy_t pclass,
+ long value, long * ret_value);
+ static HttpStatus setStaticPolicyOption(EPolicyOption opt, policy_t pclass,
+ const std::string & value, std::string * ret_value);
+
/// Set a parameter on a class-based policy option. Calls
/// made after the start of the servicing thread are
/// not honored and return an error status.
///
- /// @param policy_id ID of class as returned by @see createPolicyClass().
- /// @param opt Enum of option to be set.
- /// @param value Desired value of option.
- /// @return Standard status code.
- static HttpStatus setPolicyClassOption(policy_t policy_id, EClassPolicy opt, long value);
+ /// @param opt Enum of option to be set.
+ /// @param pclass For class-based options, the policy class ID to
+ /// be changed. Ignored for globals but recommend
+ /// using INVALID_POLICY_ID in this case.
+ /// @param value Desired value of option.
+ /// @return Handle of dynamic request. Use @see getStatus() if
+ /// the returned handle is invalid.
+ HttpHandle setPolicyOption(EPolicyOption opt, policy_t pclass, long value,
+ HttpHandler * handler);
+ HttpHandle setPolicyOption(EPolicyOption opt, policy_t pclass, const std::string & value,
+ HttpHandler * handler);
/// @}
@@ -488,16 +533,6 @@ public:
/// @}
- /// @name DynamicPolicyMethods
- ///
- /// @{
-
- /// Request that a running transport pick up a new proxy setting.
- /// An empty string will indicate no proxy is to be used.
- HttpHandle requestSetHttpProxy(const std::string & proxy, HttpHandler * handler);
-
- /// @}
-
protected:
void generateNotification(HttpOperation * op);
@@ -519,7 +554,6 @@ private:
/// Must be established before any threading is allowed to
/// start.
///
- static policy_t sNextPolicyID;
/// @}
// End Global State
diff --git a/indra/llcorehttp/httpresponse.cpp b/indra/llcorehttp/httpresponse.cpp
index a552e48a1b..c974395b0a 100755
--- a/indra/llcorehttp/httpresponse.cpp
+++ b/indra/llcorehttp/httpresponse.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -39,7 +39,9 @@ HttpResponse::HttpResponse()
mReplyLength(0U),
mReplyFullLength(0U),
mBufferArray(NULL),
- mHeaders(NULL)
+ mHeaders(NULL),
+ mRetries(0U),
+ m503Retries(0U)
{}
diff --git a/indra/llcorehttp/httpresponse.h b/indra/llcorehttp/httpresponse.h
index f19b521fbf..aee64e2878 100755
--- a/indra/llcorehttp/httpresponse.h
+++ b/indra/llcorehttp/httpresponse.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -149,6 +149,25 @@ public:
mContentType = con_type;
}
+ /// Get and set retry attempt information on the request.
+ void getRetries(unsigned int * retries, unsigned int * retries_503) const
+ {
+ if (retries)
+ {
+ *retries = mRetries;
+ }
+ if (retries_503)
+ {
+ *retries_503 = m503Retries;
+ }
+ }
+
+ void setRetries(unsigned int retries, unsigned int retries_503)
+ {
+ mRetries = retries;
+ m503Retries = retries_503;
+ }
+
protected:
// Response data here
HttpStatus mStatus;
@@ -158,6 +177,8 @@ protected:
BufferArray * mBufferArray;
HttpHeaders * mHeaders;
std::string mContentType;
+ unsigned int mRetries;
+ unsigned int m503Retries;
};
diff --git a/indra/llcorehttp/tests/test_httpheaders.hpp b/indra/llcorehttp/tests/test_httpheaders.hpp
index ce0d19b058..668c36dc66 100755
--- a/indra/llcorehttp/tests/test_httpheaders.hpp
+++ b/indra/llcorehttp/tests/test_httpheaders.hpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -36,7 +36,6 @@
using namespace LLCoreInt;
-
namespace tut
{
@@ -63,7 +62,7 @@ void HttpHeadersTestObjectType::test<1>()
HttpHeaders * headers = new HttpHeaders();
ensure("One ref on construction of HttpHeaders", headers->getRefCount() == 1);
ensure("Memory being used", mMemTotal < GetMemTotal());
- ensure("Nothing in headers", 0 == headers->mHeaders.size());
+ ensure("Nothing in headers", 0 == headers->size());
// release the implicit reference, causing the object to be released
headers->release();
@@ -85,14 +84,340 @@ void HttpHeadersTestObjectType::test<2>()
{
// Append a few strings
- std::string str1("Pragma:");
- headers->mHeaders.push_back(str1);
- std::string str2("Accept: application/json");
- headers->mHeaders.push_back(str2);
+ std::string str1n("Pragma");
+ std::string str1v("");
+ headers->append(str1n, str1v);
+ std::string str2n("Accept");
+ std::string str2v("application/json");
+ headers->append(str2n, str2v);
+
+ ensure("Headers retained", 2 == headers->size());
+ HttpHeaders::container_t & c(headers->getContainerTESTONLY());
+
+ ensure("First name is first name", c[0].first == str1n);
+ ensure("First value is first value", c[0].second == str1v);
+ ensure("Second name is second name", c[1].first == str2n);
+ ensure("Second value is second value", c[1].second == str2v);
+ }
+
+ // release the implicit reference, causing the object to be released
+ headers->release();
+
+ // make sure we didn't leak any memory
+ ensure(mMemTotal == GetMemTotal());
+}
+
+template <> template <>
+void HttpHeadersTestObjectType::test<3>()
+{
+ set_test_name("HttpHeaders basic find");
+
+ // record the total amount of dynamically allocated memory
+ mMemTotal = GetMemTotal();
+
+ // create a new ref counted object with an implicit reference
+ HttpHeaders * headers = new HttpHeaders();
+
+ {
+ // Append a few strings
+ std::string str1n("Uno");
+ std::string str1v("1");
+ headers->append(str1n, str1v);
+ std::string str2n("doS");
+ std::string str2v("2-2-2-2");
+ headers->append(str2n, str2v);
+ std::string str3n("TRES");
+ std::string str3v("trois gymnopedie");
+ headers->append(str3n, str3v);
+
+ ensure("Headers retained", 3 == headers->size());
+
+ const std::string * result(NULL);
+
+ // Find a header
+ result = headers->find("TRES");
+ ensure("Found the last item", result != NULL);
+ ensure("Last item is a nice", result != NULL && str3v == *result);
+
+ // appends above are raw and find is case sensitive
+ result = headers->find("TReS");
+ ensure("Last item not found due to case", result == NULL);
+
+ result = headers->find("TRE");
+ ensure("Last item not found due to prefixing (1)", result == NULL);
+
+ result = headers->find("TRESS");
+ ensure("Last item not found due to prefixing (2)", result == NULL);
+ }
+
+ // release the implicit reference, causing the object to be released
+ headers->release();
+
+ // make sure we didn't leak any memory
+ ensure(mMemTotal == GetMemTotal());
+}
+
+template <> template <>
+void HttpHeadersTestObjectType::test<4>()
+{
+ set_test_name("HttpHeaders normalized header entry");
+
+ // record the total amount of dynamically allocated memory
+ mMemTotal = GetMemTotal();
+
+ // create a new ref counted object with an implicit reference
+ HttpHeaders * headers = new HttpHeaders();
+
+ {
+ static char line1[] = " AcCePT : image/yourfacehere";
+ static char line1v[] = "image/yourfacehere";
+ headers->appendNormal(line1, sizeof(line1) - 1);
+
+ ensure("First append worked in some fashion", 1 == headers->size());
+
+ const std::string * result(NULL);
+
+ // Find a header
+ result = headers->find("accept");
+ ensure("Found 'accept'", result != NULL);
+ ensure("accept value has face", result != NULL && *result == line1v);
+
+ // Left-clean on value
+ static char line2[] = " next : \t\tlinejunk \t";
+ headers->appendNormal(line2, sizeof(line2) - 1);
+ ensure("Second append worked", 2 == headers->size());
+ result = headers->find("next");
+ ensure("Found 'next'", result != NULL);
+ ensure("next value is left-clean", result != NULL &&
+ *result == "linejunk \t");
+
+ // First value unmolested
+ result = headers->find("accept");
+ ensure("Found 'accept' again", result != NULL);
+ ensure("accept value has face", result != NULL && *result == line1v);
+
+ // Colons in value are okay
+ static char line3[] = "FancY-PANTs::plop:-neuf-=vleem=";
+ static char line3v[] = ":plop:-neuf-=vleem=";
+ headers->appendNormal(line3, sizeof(line3) - 1);
+ ensure("Third append worked", 3 == headers->size());
+ result = headers->find("fancy-pants");
+ ensure("Found 'fancy-pants'", result != NULL);
+ ensure("fancy-pants value has colons", result != NULL && *result == line3v);
+
+ // Zero-length value
+ static char line4[] = "all-talk-no-walk:";
+ headers->appendNormal(line4, sizeof(line4) - 1);
+ ensure("Fourth append worked", 4 == headers->size());
+ result = headers->find("all-talk-no-walk");
+ ensure("Found 'all-talk'", result != NULL);
+ ensure("al-talk value is zero-length", result != NULL && result->size() == 0);
+
+ // Zero-length name
+ static char line5[] = ":all-talk-no-walk";
+ static char line5v[] = "all-talk-no-walk";
+ headers->appendNormal(line5, sizeof(line5) - 1);
+ ensure("Fifth append worked", 5 == headers->size());
+ result = headers->find("");
+ ensure("Found no-name", result != NULL);
+ ensure("no-name value is something", result != NULL && *result == line5v);
+
+ // Lone colon is still something
+ headers->clear();
+ static char line6[] = " :";
+ headers->appendNormal(line6, sizeof(line6) - 1);
+ ensure("Sixth append worked", 1 == headers->size());
+ result = headers->find("");
+ ensure("Found 2nd no-name", result != NULL);
+ ensure("2nd no-name value is nothing", result != NULL && result->size() == 0);
+
+ // Line without colons is taken as-is and unstripped in name
+ static char line7[] = " \toskdgioasdghaosdghoowg28342908tg8902hg0hwedfhqew890v7qh0wdebv78q0wdevbhq>?M>BNM<ZV>?NZ? \t";
+ headers->appendNormal(line7, sizeof(line7) - 1);
+ ensure("Seventh append worked", 2 == headers->size());
+ result = headers->find(line7);
+ ensure("Found whatsit line", result != NULL);
+ ensure("Whatsit line has no value", result != NULL && result->size() == 0);
+
+ // Normaling interface heeds the byte count, doesn't look for NUL-terminator
+ static char line8[] = "binary:ignorestuffontheendofthis";
+ headers->appendNormal(line8, 13);
+ ensure("Eighth append worked", 3 == headers->size());
+ result = headers->find("binary");
+ ensure("Found 'binary'", result != NULL);
+ ensure("binary value was limited to 'ignore'", result != NULL &&
+ *result == "ignore");
+
+ }
- ensure("Headers retained", 2 == headers->mHeaders.size());
- ensure("First is first", headers->mHeaders[0] == str1);
- ensure("Second is second", headers->mHeaders[1] == str2);
+ // release the implicit reference, causing the object to be released
+ headers->release();
+
+ // make sure we didn't leak any memory
+ ensure(mMemTotal == GetMemTotal());
+}
+
+// Verify forward iterator finds everything as expected
+template <> template <>
+void HttpHeadersTestObjectType::test<5>()
+{
+ set_test_name("HttpHeaders iterator tests");
+
+ // record the total amount of dynamically allocated memory
+ mMemTotal = GetMemTotal();
+
+ // create a new ref counted object with an implicit reference
+ HttpHeaders * headers = new HttpHeaders();
+
+ HttpHeaders::iterator end(headers->end()), begin(headers->begin());
+ ensure("Empty container has equal begin/end const iterators", end == begin);
+ HttpHeaders::const_iterator cend(headers->end()), cbegin(headers->begin());
+ ensure("Empty container has equal rbegin/rend const iterators", cend == cbegin);
+
+ ensure("Empty container has equal begin/end iterators", headers->end() == headers->begin());
+
+ {
+ static char line1[] = " AcCePT : image/yourfacehere";
+ static char line1v[] = "image/yourfacehere";
+ headers->appendNormal(line1, sizeof(line1) - 1);
+
+ static char line2[] = " next : \t\tlinejunk \t";
+ static char line2v[] = "linejunk \t";
+ headers->appendNormal(line2, sizeof(line2) - 1);
+
+ static char line3[] = "FancY-PANTs::plop:-neuf-=vleem=";
+ static char line3v[] = ":plop:-neuf-=vleem=";
+ headers->appendNormal(line3, sizeof(line3) - 1);
+
+ static char line4[] = "all-talk-no-walk:";
+ static char line4v[] = "";
+ headers->appendNormal(line4, sizeof(line4) - 1);
+
+ static char line5[] = ":all-talk-no-walk";
+ static char line5v[] = "all-talk-no-walk";
+ headers->appendNormal(line5, sizeof(line5) - 1);
+
+ static char line6[] = " :";
+ static char line6v[] = "";
+ headers->appendNormal(line6, sizeof(line6) - 1);
+
+ ensure("All entries accounted for", 6 == headers->size());
+
+ static char * values[] = {
+ line1v,
+ line2v,
+ line3v,
+ line4v,
+ line5v,
+ line6v
+ };
+
+ int i(0);
+ HttpHeaders::const_iterator cend(headers->end());
+ for (HttpHeaders::const_iterator it(headers->begin());
+ cend != it;
+ ++it, ++i)
+ {
+ std::ostringstream str;
+ str << "Const Iterator value # " << i << " was " << values[i];
+ ensure(str.str(), (*it).second == values[i]);
+ }
+
+ // Rewind, do non-consts
+ i = 0;
+ HttpHeaders::iterator end(headers->end());
+ for (HttpHeaders::iterator it(headers->begin());
+ end != it;
+ ++it, ++i)
+ {
+ std::ostringstream str;
+ str << "Const Iterator value # " << i << " was " << values[i];
+ ensure(str.str(), (*it).second == values[i]);
+ }
+ }
+
+ // release the implicit reference, causing the object to be released
+ headers->release();
+
+ // make sure we didn't leak any memory
+ ensure(mMemTotal == GetMemTotal());
+}
+
+// Reverse iterators find everything as expected
+template <> template <>
+void HttpHeadersTestObjectType::test<6>()
+{
+ set_test_name("HttpHeaders reverse iterator tests");
+
+ // record the total amount of dynamically allocated memory
+ mMemTotal = GetMemTotal();
+
+ // create a new ref counted object with an implicit reference
+ HttpHeaders * headers = new HttpHeaders();
+
+ HttpHeaders::reverse_iterator rend(headers->rend()), rbegin(headers->rbegin());
+ ensure("Empty container has equal rbegin/rend const iterators", rend == rbegin);
+ HttpHeaders::const_reverse_iterator crend(headers->rend()), crbegin(headers->rbegin());
+ ensure("Empty container has equal rbegin/rend const iterators", crend == crbegin);
+
+ {
+ static char line1[] = " AcCePT : image/yourfacehere";
+ static char line1v[] = "image/yourfacehere";
+ headers->appendNormal(line1, sizeof(line1) - 1);
+
+ static char line2[] = " next : \t\tlinejunk \t";
+ static char line2v[] = "linejunk \t";
+ headers->appendNormal(line2, sizeof(line2) - 1);
+
+ static char line3[] = "FancY-PANTs::plop:-neuf-=vleem=";
+ static char line3v[] = ":plop:-neuf-=vleem=";
+ headers->appendNormal(line3, sizeof(line3) - 1);
+
+ static char line4[] = "all-talk-no-walk:";
+ static char line4v[] = "";
+ headers->appendNormal(line4, sizeof(line4) - 1);
+
+ static char line5[] = ":all-talk-no-walk";
+ static char line5v[] = "all-talk-no-walk";
+ headers->appendNormal(line5, sizeof(line5) - 1);
+
+ static char line6[] = " :";
+ static char line6v[] = "";
+ headers->appendNormal(line6, sizeof(line6) - 1);
+
+ ensure("All entries accounted for", 6 == headers->size());
+
+ static char * values[] = {
+ line6v,
+ line5v,
+ line4v,
+ line3v,
+ line2v,
+ line1v
+ };
+
+ int i(0);
+ HttpHeaders::const_reverse_iterator cend(headers->rend());
+ for (HttpHeaders::const_reverse_iterator it(headers->rbegin());
+ cend != it;
+ ++it, ++i)
+ {
+ std::ostringstream str;
+ str << "Const Iterator value # " << i << " was " << values[i];
+ ensure(str.str(), (*it).second == values[i]);
+ }
+
+ // Rewind, do non-consts
+ i = 0;
+ HttpHeaders::reverse_iterator end(headers->rend());
+ for (HttpHeaders::reverse_iterator it(headers->rbegin());
+ end != it;
+ ++it, ++i)
+ {
+ std::ostringstream str;
+ str << "Iterator value # " << i << " was " << values[i];
+ ensure(str.str(), (*it).second == values[i]);
+ }
}
// release the implicit reference, causing the object to be released
diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp
index 900a699887..43f7e36da5 100755
--- a/indra/llcorehttp/tests/test_httprequest.hpp
+++ b/indra/llcorehttp/tests/test_httprequest.hpp
@@ -69,6 +69,8 @@ void usleep(unsigned long usec);
namespace tut
{
+typedef std::vector<std::pair<boost::regex, boost::regex> > regex_container_t;
+
struct HttpRequestTestData
{
// the test objects inherit from this so the member functions and variables
@@ -118,11 +120,17 @@ public:
for (int i(0); i < mHeadersRequired.size(); ++i)
{
bool found = false;
- for (HttpHeaders::container_t::const_iterator iter(header->mHeaders.begin());
- header->mHeaders.end() != iter;
+ for (HttpHeaders::const_iterator iter(header->begin());
+ header->end() != iter;
++iter)
{
- if (boost::regex_match(*iter, mHeadersRequired[i]))
+ // std::cerr << "Header: " << (*iter).first
+ // << ": " << (*iter).second << std::endl;
+
+ if (boost::regex_match((*iter).first,
+ mHeadersRequired[i].first) &&
+ boost::regex_match((*iter).second,
+ mHeadersRequired[i].second))
{
found = true;
break;
@@ -138,11 +146,14 @@ public:
{
for (int i(0); i < mHeadersDisallowed.size(); ++i)
{
- for (HttpHeaders::container_t::const_iterator iter(header->mHeaders.begin());
- header->mHeaders.end() != iter;
+ for (HttpHeaders::const_iterator iter(header->begin());
+ header->end() != iter;
++iter)
{
- if (boost::regex_match(*iter, mHeadersDisallowed[i]))
+ if (boost::regex_match((*iter).first,
+ mHeadersDisallowed[i].first) &&
+ boost::regex_match((*iter).second,
+ mHeadersDisallowed[i].second))
{
std::ostringstream str;
str << "Disallowed header # " << i << " not found in response";
@@ -168,8 +179,8 @@ public:
std::string mName;
HttpHandle mExpectHandle;
std::string mCheckContentType;
- std::vector<boost::regex> mHeadersRequired;
- std::vector<boost::regex> mHeadersDisallowed;
+ regex_container_t mHeadersRequired;
+ regex_container_t mHeadersDisallowed;
};
typedef test_group<HttpRequestTestData> HttpRequestTestGroupType;
@@ -1211,7 +1222,7 @@ void HttpRequestTestObjectType::test<12>()
HttpRequest::createService();
// Enable tracing
- HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, 2);
+ HttpRequest::setStaticPolicyOption(HttpRequest::PO_TRACE, HttpRequest::DEFAULT_POLICY_ID, 2, NULL);
// Start threading early so that thread memory is invariant
// over the test.
@@ -1329,7 +1340,7 @@ void HttpRequestTestObjectType::test<13>()
HttpRequest::createService();
// Enable tracing
- HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, 2);
+ HttpRequest::setStaticPolicyOption(HttpRequest::PO_TRACE, HttpRequest::DEFAULT_POLICY_ID, 2, NULL);
// Start threading early so that thread memory is invariant
// over the test.
@@ -1344,7 +1355,9 @@ void HttpRequestTestObjectType::test<13>()
// Issue a GET that succeeds
mStatus = HttpStatus(200);
- handler.mHeadersRequired.push_back(boost::regex("\\W*X-LL-Special:.*", boost::regex::icase));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(boost::regex("X-LL-Special", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base,
@@ -1711,18 +1724,54 @@ void HttpRequestTestObjectType::test<16>()
// Issue a GET that *can* connect
mStatus = HttpStatus(200);
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-connection", boost::regex::icase),
+ boost::regex("keep-alive", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept", boost::regex::icase),
+ boost::regex("\\*/\\*", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept-encoding", boost::regex::icase),
+ boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-keep-alive", boost::regex::icase),
+ boost::regex("\\d+", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-host", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-cache-control", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-pragma", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-range", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-transfer-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-referer", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-type", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
@@ -1744,23 +1793,60 @@ void HttpRequestTestObjectType::test<16>()
// Do a texture-style fetch
headers = new HttpHeaders;
- headers->mHeaders.push_back("Accept: image/x-j2c");
+ headers->append("Accept", "image/x-j2c");
mStatus = HttpStatus(200);
handler.mHeadersRequired.clear();
handler.mHeadersDisallowed.clear();
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*image/x-j2c", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("\\W*X-Reflect-range:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-connection", boost::regex::icase),
+ boost::regex("keep-alive", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept", boost::regex::icase),
+ boost::regex("image/x-j2c", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept-encoding", boost::regex::icase),
+ boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-keep-alive", boost::regex::icase),
+ boost::regex("\\d+", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-host", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("\\W*X-Reflect-range", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-cache-control", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-pragma", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-transfer-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-referer", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-type", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
@@ -1901,20 +1987,63 @@ void HttpRequestTestObjectType::test<17>()
// Issue a default POST
mStatus = HttpStatus(200);
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/x-www-form-urlencoded", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-connection", boost::regex::icase),
+ boost::regex("keep-alive", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept", boost::regex::icase),
+ boost::regex("\\*/\\*", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept-encoding", boost::regex::icase),
+ boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-keep-alive", boost::regex::icase),
+ boost::regex("\\d+", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-host", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-length", boost::regex::icase),
+ boost::regex("\\d+", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-type", boost::regex::icase),
+ boost::regex("application/x-www-form-urlencoded", boost::regex::icase)));
+
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-cache-control", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-pragma", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-range", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-referer", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-expect", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-transfer_encoding", boost::regex::icase),
+ boost::regex(".*chunked.*", boost::regex::icase)));
HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
@@ -2061,20 +2190,64 @@ void HttpRequestTestObjectType::test<18>()
// Issue a default PUT
mStatus = HttpStatus(200);
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:.*", boost::regex::icase));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-connection", boost::regex::icase),
+ boost::regex("keep-alive", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept", boost::regex::icase),
+ boost::regex("\\*/\\*", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept-encoding", boost::regex::icase),
+ boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-keep-alive", boost::regex::icase),
+ boost::regex("\\d+", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-host", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-length", boost::regex::icase),
+ boost::regex("\\d+", boost::regex::icase)));
+
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-cache-control", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-pragma", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-range", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-referer", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-expect", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-transfer-encoding", boost::regex::icase),
+ boost::regex(".*chunked.*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-type", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+
HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
@@ -2215,27 +2388,73 @@ void HttpRequestTestObjectType::test<19>()
// headers
headers = new HttpHeaders;
- headers->mHeaders.push_back("Keep-Alive: 120");
- headers->mHeaders.push_back("Accept-encoding: deflate");
- headers->mHeaders.push_back("Accept: text/plain");
+ headers->append("Keep-Alive", "120");
+ headers->append("Accept-encoding", "deflate");
+ headers->append("Accept", "text/plain");
// Issue a GET with modified headers
mStatus = HttpStatus(200);
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*text/plain", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*deflate", boost::regex::icase)); // close enough
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*120", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
- handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-keep-alive:\\s*300", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-connection", boost::regex::icase),
+ boost::regex("keep-alive", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept", boost::regex::icase),
+ boost::regex("text/plain", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept-encoding", boost::regex::icase),
+ boost::regex("deflate", boost::regex::icase))); // close enough
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-keep-alive", boost::regex::icase),
+ boost::regex("120", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-host", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept-encoding", boost::regex::icase),
+ boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-keep-alive", boost::regex::icase),
+ boost::regex("300", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept", boost::regex::icase),
+ boost::regex("\\*/\\*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-cache-control", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-pragma", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-range", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-transfer-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-referer", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-type", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
@@ -2368,10 +2587,10 @@ void HttpRequestTestObjectType::test<20>()
// headers
headers = new HttpHeaders();
- headers->mHeaders.push_back("keep-Alive: 120");
- headers->mHeaders.push_back("Accept: text/html");
- headers->mHeaders.push_back("content-type: application/llsd+xml");
- headers->mHeaders.push_back("cache-control: no-store");
+ headers->append("keep-Alive", "120");
+ headers->append("Accept", "text/html");
+ headers->append("content-type", "application/llsd+xml");
+ headers->append("cache-control", "no-store");
// And a buffer array
const char * msg("<xml><llsd><string>It was the best of times, it was the worst of times.</string></llsd></xml>");
@@ -2380,23 +2599,76 @@ void HttpRequestTestObjectType::test<20>()
// Issue a default POST
mStatus = HttpStatus(200);
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*text/html", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*120", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/llsd\\+xml", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("\\s*X-Reflect-cache-control:\\s*no-store", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*application/x-www-form-urlencoded", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-keep-alive:\\s*300", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-connection", boost::regex::icase),
+ boost::regex("keep-alive", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept", boost::regex::icase),
+ boost::regex("text/html", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept-encoding", boost::regex::icase),
+ boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-keep-alive", boost::regex::icase),
+ boost::regex("120", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-host", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-length", boost::regex::icase),
+ boost::regex("\\d+", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-type", boost::regex::icase),
+ boost::regex("application/llsd\\+xml", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-cache-control", boost::regex::icase),
+ boost::regex("no-store", boost::regex::icase)));
+
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-type", boost::regex::icase),
+ boost::regex("application/x-www-form-urlencoded", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept", boost::regex::icase),
+ boost::regex("\\*/\\*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-keep-alive", boost::regex::icase),
+ boost::regex("300", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-pragma", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-range", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-referer", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-expect", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-transfer-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+
HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
@@ -2538,9 +2810,9 @@ void HttpRequestTestObjectType::test<21>()
// headers
headers = new HttpHeaders;
- headers->mHeaders.push_back("content-type: text/plain");
- headers->mHeaders.push_back("content-type: text/html");
- headers->mHeaders.push_back("content-type: application/llsd+xml");
+ headers->append("content-type", "text/plain");
+ headers->append("content-type", "text/html");
+ headers->append("content-type", "application/llsd+xml");
// And a buffer array
const char * msg("<xml><llsd><string>It was the best of times, it was the worst of times.</string></llsd></xml>");
@@ -2549,22 +2821,71 @@ void HttpRequestTestObjectType::test<21>()
// Issue a default PUT
mStatus = HttpStatus(200);
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase));
- handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/llsd\\+xml", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*text/plain", boost::regex::icase));
- handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*text/html", boost::regex::icase));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-connection", boost::regex::icase),
+ boost::regex("keep-alive", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept", boost::regex::icase),
+ boost::regex("\\*/\\*", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-accept-encoding", boost::regex::icase),
+ boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-keep-alive", boost::regex::icase),
+ boost::regex("\\d+", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-host", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-length", boost::regex::icase),
+ boost::regex("\\d+", boost::regex::icase)));
+ handler.mHeadersRequired.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-type", boost::regex::icase),
+ boost::regex("application/llsd\\+xml", boost::regex::icase)));
+
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-cache-control", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-pragma", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-range", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-referer", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-expect", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-transfer-encoding", boost::regex::icase),
+ boost::regex(".*", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-type", boost::regex::icase),
+ boost::regex("text/plain", boost::regex::icase)));
+ handler.mHeadersDisallowed.push_back(
+ regex_container_t::value_type(
+ boost::regex("X-Reflect-content-type", boost::regex::icase),
+ boost::regex("text/html", boost::regex::icase)));
HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
@@ -2854,6 +3175,142 @@ void HttpRequestTestObjectType::test<22>()
}
}
+template <> template <>
+void HttpRequestTestObjectType::test<23>()
+{
+ ScopedCurlInit ready;
+
+ set_test_name("HttpRequest GET 503s with 'Retry-After'");
+
+ // This tests mainly that the code doesn't fall over if
+ // various well- and mis-formed Retry-After headers are
+ // sent along with the response. Direct inspection of
+ // the parsing result isn't supported.
+
+ // Handler can be stack-allocated *if* there are no dangling
+ // references to it after completion of this method.
+ // Create before memory record as the string copy will bump numbers.
+ TestHandler2 handler(this, "handler");
+ std::string url_base(get_base_url() + "/503/"); // path to 503 generators
+
+ // record the total amount of dynamically allocated memory
+ mMemTotal = GetMemTotal();
+ mHandlerCalls = 0;
+
+ HttpRequest * req = NULL;
+ HttpOptions * opts = NULL;
+
+ try
+ {
+ // Get singletons created
+ HttpRequest::createService();
+
+ // Start threading early so that thread memory is invariant
+ // over the test.
+ HttpRequest::startThread();
+
+ // create a new ref counted object with an implicit reference
+ req = new HttpRequest();
+ ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
+
+ opts = new HttpOptions();
+ opts->setRetries(1); // Retry once only
+ opts->setUseRetryAfter(true); // Try to parse the retry-after header
+
+ // Issue a GET that 503s with valid retry-after
+ mStatus = HttpStatus(503);
+ int url_limit(6);
+ for (int i(0); i < url_limit; ++i)
+ {
+ std::ostringstream url;
+ url << url_base << i << "/";
+ HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
+ 0U,
+ url.str(),
+ 0,
+ 0,
+ opts,
+ NULL,
+ &handler);
+
+ std::ostringstream testtag;
+ testtag << "Valid handle returned for 503 request #" << i;
+ ensure(testtag.str(), handle != LLCORE_HTTP_HANDLE_INVALID);
+ }
+
+
+ // Run the notification pump.
+ int count(0);
+ int limit(LOOP_COUNT_LONG);
+ while (count++ < limit && mHandlerCalls < url_limit)
+ {
+ req->update(0);
+ usleep(LOOP_SLEEP_INTERVAL);
+ }
+ ensure("Request executed in reasonable time", count < limit);
+ ensure("One handler invocation for request", mHandlerCalls == url_limit);
+
+ // Okay, request a shutdown of the servicing thread
+ mStatus = HttpStatus();
+ mHandlerCalls = 0;
+ HttpHandle handle = req->requestStopThread(&handler);
+ ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID);
+
+ // Run the notification pump again
+ count = 0;
+ limit = LOOP_COUNT_LONG;
+ while (count++ < limit && mHandlerCalls < 1)
+ {
+ req->update(1000000);
+ usleep(LOOP_SLEEP_INTERVAL);
+ }
+ ensure("Second request executed in reasonable time", count < limit);
+ ensure("Second handler invocation", mHandlerCalls == 1);
+
+ // See that we actually shutdown the thread
+ count = 0;
+ limit = LOOP_COUNT_SHORT;
+ while (count++ < limit && ! HttpService::isStopped())
+ {
+ usleep(LOOP_SLEEP_INTERVAL);
+ }
+ ensure("Thread actually stopped running", HttpService::isStopped());
+
+ // release options
+ opts->release();
+ opts = NULL;
+
+ // release the request object
+ delete req;
+ req = NULL;
+
+ // Shut down service
+ HttpRequest::destroyService();
+
+#if defined(WIN32)
+ // Can only do this memory test on Windows. On other platforms,
+ // the LL logging system holds on to memory and produces what looks
+ // like memory leaks...
+
+ // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
+ ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
+#endif
+ }
+ catch (...)
+ {
+ stop_thread(req);
+ if (opts)
+ {
+ opts->release();
+ opts = NULL;
+ }
+ delete req;
+ HttpRequest::destroyService();
+ throw;
+ }
+}
+
+
} // end namespace tut
namespace
diff --git a/indra/llcorehttp/tests/test_httpstatus.hpp b/indra/llcorehttp/tests/test_httpstatus.hpp
index b5538528c5..0b379836c9 100755
--- a/indra/llcorehttp/tests/test_httpstatus.hpp
+++ b/indra/llcorehttp/tests/test_httpstatus.hpp
@@ -259,6 +259,65 @@ void HttpStatusTestObjectType::test<7>()
ensure(msg == "Unknown error");
}
+
+template <> template <>
+void HttpStatusTestObjectType::test<8>()
+{
+ set_test_name("HttpStatus toHex() nominal function");
+
+ HttpStatus status(404);
+ std::string msg = status.toHex();
+ // std::cout << "Result: " << msg << std::endl;
+ ensure(msg == "01940001");
+}
+
+
+template <> template <>
+void HttpStatusTestObjectType::test<9>()
+{
+ set_test_name("HttpStatus toTerseString() nominal function");
+
+ HttpStatus status(404);
+ std::string msg = status.toTerseString();
+ // std::cout << "Result: " << msg << std::endl;
+ ensure("Normal HTTP 404", msg == "Http_404");
+
+ status = HttpStatus(200);
+ msg = status.toTerseString();
+ // std::cout << "Result: " << msg << std::endl;
+ ensure("Normal HTTP 200", msg == "Http_200");
+
+ status = HttpStatus(200, HE_REPLY_ERROR);
+ msg = status.toTerseString();
+ // std::cout << "Result: " << msg << std::endl;
+ ensure("Unsuccessful HTTP 200", msg == "Http_200"); // No distinction for error
+
+ status = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT);
+ msg = status.toTerseString();
+ // std::cout << "Result: " << msg << std::endl;
+ ensure("Easy couldn't connect error", msg == "Easy_7");
+
+ status = HttpStatus(HttpStatus::EXT_CURL_MULTI, CURLM_OUT_OF_MEMORY);
+ msg = status.toTerseString();
+ // std::cout << "Result: " << msg << std::endl;
+ ensure("Multi out-of-memory error", msg == "Multi_3");
+
+ status = HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_SET);
+ msg = status.toTerseString();
+ // std::cout << "Result: " << msg << std::endl;
+ ensure("Core option not set error", msg == "Core_7");
+
+ status = HttpStatus(22000, 1);
+ msg = status.toTerseString();
+ // std::cout << "Result: " << msg << std::endl;
+ ensure("Undecodable error", msg == "Unknown_1");
+
+ status = HttpStatus(22000, -1);
+ msg = status.toTerseString();
+ // std::cout << "Result: " << msg << std::endl;
+ ensure("Undecodable error 65535", msg == "Unknown_65535");
+}
+
} // end namespace tut
#endif // TEST_HTTP_STATUS_H
diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py
index 3c3af8dc75..04cde651c4 100755
--- a/indra/llcorehttp/tests/test_llcorehttp_peer.py
+++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py
@@ -69,6 +69,15 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):
"Content-Range: bytes 0-75/2983",
"Content-Length: 76"
-- '/bug2295/inv_cont_range/0/' Generates HE_INVALID_CONTENT_RANGE error in llcorehttp.
+ - '/503/' Generate 503 responses with various kinds
+ of 'retry-after' headers
+ -- '/503/0/' "Retry-After: 2"
+ -- '/503/1/' "Retry-After: Thu, 31 Dec 2043 23:59:59 GMT"
+ -- '/503/2/' "Retry-After: Fri, 31 Dec 1999 23:59:59 GMT"
+ -- '/503/3/' "Retry-After: "
+ -- '/503/4/' "Retry-After: (*#*(@*(@(")"
+ -- '/503/5/' "Retry-After: aklsjflajfaklsfaklfasfklasdfklasdgahsdhgasdiogaioshdgo"
+ -- '/503/6/' "Retry-After: 1 2 3 4 5 6 7 8 9 10"
Some combinations make no sense, there's no effort to protect
you from that.
@@ -143,22 +152,40 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):
if "/sleep/" in self.path:
time.sleep(30)
- if "fail" in self.path:
- status = data.get("status", 500)
- # self.responses maps an int status to a (short, long) pair of
- # strings. We want the longer string. That's why we pass a string
- # pair to get(): the [1] will select the second string, whether it
- # came from self.responses or from our default pair.
- reason = data.get("reason",
- self.responses.get(status,
- ("fail requested",
- "Your request specified failure status %s "
- "without providing a reason" % status))[1])
- debug("fail requested: %s: %r", status, reason)
- self.send_error(status, reason)
+ if "/503/" in self.path:
+ # Tests for various kinds of 'Retry-After' header parsing
+ body = None
+ if "/503/0/" in self.path:
+ self.send_response(503)
+ self.send_header("retry-after", "2")
+ elif "/503/1/" in self.path:
+ self.send_response(503)
+ self.send_header("retry-after", "Thu, 31 Dec 2043 23:59:59 GMT")
+ elif "/503/2/" in self.path:
+ self.send_response(503)
+ self.send_header("retry-after", "Fri, 31 Dec 1999 23:59:59 GMT")
+ elif "/503/3/" in self.path:
+ self.send_response(503)
+ self.send_header("retry-after", "")
+ elif "/503/4/" in self.path:
+ self.send_response(503)
+ self.send_header("retry-after", "(*#*(@*(@(")
+ elif "/503/5/" in self.path:
+ self.send_response(503)
+ self.send_header("retry-after", "aklsjflajfaklsfaklfasfklasdfklasdgahsdhgasdiogaioshdgo")
+ elif "/503/6/" in self.path:
+ self.send_response(503)
+ self.send_header("retry-after", "1 2 3 4 5 6 7 8 9 10")
+ else:
+ # Unknown request
+ self.send_response(400)
+ body = "Unknown /503/ path in server"
if "/reflect/" in self.path:
self.reflect_headers()
+ self.send_header("Content-type", "text/plain")
self.end_headers()
+ if body:
+ self.wfile.write(body)
elif "/bug2295/" in self.path:
# Test for https://jira.secondlife.com/browse/BUG-2295
#
@@ -194,8 +221,7 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):
self.end_headers()
if body:
self.wfile.write(body)
- else:
- # Normal response path
+ elif "fail" not in self.path:
data = data.copy() # we're going to modify
# Ensure there's a "reply" key in data, even if there wasn't before
data["reply"] = data.get("reply", llsd.LLSD("success"))
@@ -210,6 +236,22 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):
self.end_headers()
if withdata:
self.wfile.write(response)
+ else: # fail requested
+ status = data.get("status", 500)
+ # self.responses maps an int status to a (short, long) pair of
+ # strings. We want the longer string. That's why we pass a string
+ # pair to get(): the [1] will select the second string, whether it
+ # came from self.responses or from our default pair.
+ reason = data.get("reason",
+ self.responses.get(status,
+ ("fail requested",
+ "Your request specified failure status %s "
+ "without providing a reason" % status))[1])
+ debug("fail requested: %s: %r", status, reason)
+ self.send_error(status, reason)
+ if "/reflect/" in self.path:
+ self.reflect_headers()
+ self.end_headers()
def reflect_headers(self):
for name in self.headers.keys():
diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp
index f74c934b21..cc5742ff7a 100755
--- a/indra/llmath/llvolume.cpp
+++ b/indra/llmath/llvolume.cpp
@@ -6747,7 +6747,7 @@ BOOL LLVolumeFace::createSide(LLVolume* volume, BOOL partial_build)
return TRUE;
}
-//adapted from Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software 3D Graphics Library, 2001. http://www.terathon.com/code/tangent.html
+//adapted from Lengyel, Eric. "Computing Tangent Space Basis Vectors for an Arbitrary Mesh". Terathon Software 3D Graphics Library, 2001. http://www.terathon.com/code/tangent.html
void CalculateTangentArray(U32 vertexCount, const LLVector4a *vertex, const LLVector4a *normal,
const LLVector2 *texcoord, U32 triangleCount, const U16* index_array, LLVector4a *tangent)
{
diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp
index 081f070866..25e175f2cc 100755
--- a/indra/llmessage/llcurl.cpp
+++ b/indra/llmessage/llcurl.cpp
@@ -6,7 +6,7 @@
*
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2010-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -294,9 +294,12 @@ LLCurl::Easy* LLCurl::Easy::getEasy()
return NULL;
}
- // set no DNS caching as default for all easy handles. This prevents them adopting a
- // multi handles cache if they are added to one.
- CURLcode result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0);
+ // Enable a brief cache period for now. This was zero for the longest time
+ // which caused some routers grief and generated unneeded traffic. For the
+ // threaded resolver, we're using system resolution libraries and non-zero values
+ // are preferred. The c-ares resolver is another matter and it might not
+ // track server changes as well.
+ CURLcode result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_DNS_CACHE_TIMEOUT, 15);
check_curl_code(result);
result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
check_curl_code(result);
diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp
index 35620bb656..1c50a51d02 100755
--- a/indra/llrender/llglslshader.cpp
+++ b/indra/llrender/llglslshader.cpp
@@ -717,7 +717,14 @@ BOOL LLGLSLShader::mapUniforms(const vector<LLStaticHashedString> * uniforms)
BOOL LLGLSLShader::link(BOOL suppress_errors)
{
- return LLShaderMgr::instance()->linkProgramObject(mProgramObject, suppress_errors);
+ BOOL success = LLShaderMgr::instance()->linkProgramObject(mProgramObject, suppress_errors);
+
+ if (!suppress_errors)
+ {
+ LLShaderMgr::instance()->dumpObjectLog(mProgramObject, !success, mName);
+ }
+
+ return success;
}
void LLGLSLShader::bind()
diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp
index d230574752..6e04fc82df 100755
--- a/indra/llrender/llshadermgr.cpp
+++ b/indra/llrender/llshadermgr.cpp
@@ -505,9 +505,25 @@ static std::string get_object_log(GLhandleARB ret)
return res;
}
-void LLShaderMgr::dumpObjectLog(GLhandleARB ret, BOOL warns)
+void LLShaderMgr::dumpObjectLog(GLhandleARB ret, BOOL warns, const std::string& filename)
{
std::string log = get_object_log(ret);
+
+ if (log.length() > 0 || warns)
+ {
+ if (!filename.empty())
+ {
+ if (warns)
+ {
+ LL_WARNS("ShaderLoading") << "From " << filename << ":" << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS("ShaderLoading") << "From " << filename << ":" << LL_ENDL;
+ }
+ }
+ }
+
if ( log.length() > 0 )
{
if (warns)
@@ -558,7 +574,7 @@ GLhandleARB LLShaderMgr::loadShaderFile(const std::string& filename, S32 & shade
file = LLFile::fopen(fname.str(), "r"); /* Flawfinder: ignore */
if (file)
{
- LL_INFOS("ShaderLoading") << "Loading file: shaders/class" << gpu_class << "/" << filename << " (Want class " << gpu_class << ")" << LL_ENDL;
+ LL_DEBUGS("ShaderLoading") << "Loading file: shaders/class" << gpu_class << "/" << filename << " (Want class " << gpu_class << ")" << LL_ENDL;
break; // done
}
}
@@ -812,8 +828,8 @@ GLhandleARB LLShaderMgr::loadShaderFile(const std::string& filename, S32 & shade
if (error != GL_NO_ERROR || success == GL_FALSE)
{
//an error occured, print log
- LL_WARNS("ShaderLoading") << "GLSL Compilation Error: (" << error << ") in " << filename << LL_ENDL;
- dumpObjectLog(ret);
+ LL_WARNS("ShaderLoading") << "GLSL Compilation Error:" << LL_ENDL;
+ dumpObjectLog(ret, TRUE, filename);
#if LL_WINDOWS
std::stringstream ostr;
//dump shader source for debugging
@@ -938,11 +954,6 @@ BOOL LLShaderMgr::linkProgramObject(GLhandleARB obj, BOOL suppress_errors)
suppress_errors = FALSE;
}
#endif
- if (!suppress_errors)
- {
- dumpObjectLog(obj, !success);
- }
-
return success;
}
@@ -1146,6 +1157,7 @@ void LLShaderMgr::initAttribsAndUniforms()
mReservedUniforms.push_back("env_intensity");
mReservedUniforms.push_back("matrixPalette");
+ mReservedUniforms.push_back("translationPalette");
mReservedUniforms.push_back("screenTex");
mReservedUniforms.push_back("screenDepth");
diff --git a/indra/llrender/llshadermgr.h b/indra/llrender/llshadermgr.h
index 51c27fc8b6..394b38f832 100755
--- a/indra/llrender/llshadermgr.h
+++ b/indra/llrender/llshadermgr.h
@@ -176,6 +176,7 @@ public:
ENVIRONMENT_INTENSITY,
AVATAR_MATRIX,
+ AVATAR_TRANSLATION,
WATER_SCREENTEX,
WATER_SCREENDEPTH,
@@ -224,7 +225,7 @@ DISPLAY_GAMMA,
virtual void initAttribsAndUniforms(void);
BOOL attachShaderFeatures(LLGLSLShader * shader);
- void dumpObjectLog(GLhandleARB ret, BOOL warns = TRUE);
+ void dumpObjectLog(GLhandleARB ret, BOOL warns = TRUE, const std::string& filename = "");
BOOL linkProgramObject(GLhandleARB obj, BOOL suppress_errors = FALSE);
BOOL validateProgramObject(GLhandleARB obj);
GLhandleARB loadShaderFile(const std::string& filename, S32 & shader_level, GLenum type, boost::unordered_map<std::string, std::string>* defines = NULL, S32 texture_index_channels = -1);
diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp
index 50ac511d18..3cfe5ac57f 100755
--- a/indra/llui/llbutton.cpp
+++ b/indra/llui/llbutton.cpp
@@ -641,7 +641,7 @@ void LLButton::draw()
bool use_glow_effect = FALSE;
LLColor4 highlighting_color = LLColor4::white;
- LLColor4 glow_color;
+ LLColor4 glow_color = LLColor4::white;
LLRender::eBlendType glow_type = LLRender::BT_ADD_WITH_ALPHA;
LLUIImage* imagep = NULL;
diff --git a/indra/llui/llcommandmanager.cpp b/indra/llui/llcommandmanager.cpp
index 49cfb2255e..625fb8e870 100755
--- a/indra/llui/llcommandmanager.cpp
+++ b/indra/llui/llcommandmanager.cpp
@@ -50,8 +50,6 @@ const LLCommandId LLCommandId::null = LLCommandId("null command");
LLCommand::Params::Params()
: available_in_toybox("available_in_toybox", false)
, icon("icon")
- , hover_icon_unselected("hover_icon_unselected")
- , hover_icon_selected("hover_icon_selected")
, label_ref("label_ref")
, name("name")
, tooltip_ref("tooltip_ref")
@@ -73,8 +71,6 @@ LLCommand::LLCommand(const LLCommand::Params& p)
: mIdentifier(p.name)
, mAvailableInToybox(p.available_in_toybox)
, mIcon(p.icon)
- , mHoverIconUnselected(p.hover_icon_unselected)
- , mHoverIconSelected(p.hover_icon_selected)
, mLabelRef(p.label_ref)
, mName(p.name)
, mTooltipRef(p.tooltip_ref)
diff --git a/indra/llui/llcommandmanager.h b/indra/llui/llcommandmanager.h
index 9f276f712d..ff5a8a3257 100755
--- a/indra/llui/llcommandmanager.h
+++ b/indra/llui/llcommandmanager.h
@@ -96,9 +96,6 @@ public:
Mandatory<std::string> name;
Mandatory<std::string> tooltip_ref;
- Optional<std::string> hover_icon_selected;
- Optional<std::string> hover_icon_unselected;
-
Mandatory<std::string> execute_function;
Optional<LLSD> execute_parameters;
@@ -127,8 +124,6 @@ public:
const std::string& labelRef() const { return mLabelRef; }
const std::string& name() const { return mName; }
const std::string& tooltipRef() const { return mTooltipRef; }
- const std::string& hoverIconUnselected() const {return mHoverIconUnselected; }
- const std::string& hoverIconSelected() const {return mHoverIconSelected; }
const std::string& executeFunctionName() const { return mExecuteFunction; }
const LLSD& executeParameters() const { return mExecuteParameters; }
@@ -155,8 +150,6 @@ private:
std::string mLabelRef;
std::string mName;
std::string mTooltipRef;
- std::string mHoverIconUnselected;
- std::string mHoverIconSelected;
std::string mExecuteFunction;
LLSD mExecuteParameters;
diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp
index 6fd2bb1b36..76ba53ec32 100755
--- a/indra/llui/lltabcontainer.cpp
+++ b/indra/llui/lltabcontainer.cpp
@@ -193,15 +193,12 @@ LLTabContainer::TabParams::TabParams()
: tab_top_image_unselected("tab_top_image_unselected"),
tab_top_image_selected("tab_top_image_selected"),
tab_top_image_flash("tab_top_image_flash"),
- tab_top_image_hovered("tab_top_image_hovered"),
tab_bottom_image_unselected("tab_bottom_image_unselected"),
tab_bottom_image_selected("tab_bottom_image_selected"),
tab_bottom_image_flash("tab_bottom_image_flash"),
- tab_bottom_image_hovered("tab_bottom_image_hovered"),
tab_left_image_unselected("tab_left_image_unselected"),
tab_left_image_selected("tab_left_image_selected"),
- tab_left_image_flash("tab_left_image_flash"),
- tab_left_image_hovered("tab_left_image_hovered")
+ tab_left_image_flash("tab_left_image_flash")
{}
LLTabContainer::Params::Params()
@@ -221,8 +218,7 @@ LLTabContainer::Params::Params()
open_tabs_on_drag_and_drop("open_tabs_on_drag_and_drop", false),
tab_icon_ctrl_pad("tab_icon_ctrl_pad", 0),
use_ellipses("use_ellipses"),
- font_halign("halign"),
- use_highlighting_on_hover("use_highlighting_on_hover",false)
+ font_halign("halign")
{}
LLTabContainer::LLTabContainer(const LLTabContainer::Params& p)
@@ -258,8 +254,7 @@ LLTabContainer::LLTabContainer(const LLTabContainer::Params& p)
mCustomIconCtrlUsed(p.use_custom_icon_ctrl),
mOpenTabsOnDragAndDrop(p.open_tabs_on_drag_and_drop),
mTabIconCtrlPad(p.tab_icon_ctrl_pad),
- mUseTabEllipses(p.use_ellipses),
- mUseHighlightingOnHover(p.use_highlighting_on_hover)
+ mUseTabEllipses(p.use_ellipses)
{
static LLUICachedControl<S32> tabcntr_vert_tab_min_width ("UITabCntrVertTabMinWidth", 0);
@@ -908,30 +903,18 @@ void LLTabContainer::update_images(LLTabTuple* tuple, TabParams params, LLTabCon
tuple->mButton->setImageUnselected(static_cast<LLUIImage*>(params.tab_top_image_unselected));
tuple->mButton->setImageSelected(static_cast<LLUIImage*>(params.tab_top_image_selected));
tuple->mButton->setImageFlash(static_cast<LLUIImage*>(params.tab_top_image_flash));
- if(mUseHighlightingOnHover)
- {
- tuple->mButton->setImageHoverUnselected(static_cast<LLUIImage*>(params.tab_top_image_hovered));
- }
}
else if (pos == LLTabContainer::BOTTOM)
{
tuple->mButton->setImageUnselected(static_cast<LLUIImage*>(params.tab_bottom_image_unselected));
tuple->mButton->setImageSelected(static_cast<LLUIImage*>(params.tab_bottom_image_selected));
tuple->mButton->setImageFlash(static_cast<LLUIImage*>(params.tab_bottom_image_flash));
- if(mUseHighlightingOnHover)
- {
- tuple->mButton->setImageHoverUnselected(static_cast<LLUIImage*>(params.tab_bottom_image_hovered));
- }
}
else if (pos == LLTabContainer::LEFT)
{
tuple->mButton->setImageUnselected(static_cast<LLUIImage*>(params.tab_left_image_unselected));
tuple->mButton->setImageSelected(static_cast<LLUIImage*>(params.tab_left_image_selected));
tuple->mButton->setImageFlash(static_cast<LLUIImage*>(params.tab_left_image_flash));
- if(mUseHighlightingOnHover)
- {
- tuple->mButton->setImageHoverUnselected(static_cast<LLUIImage*>(params.tab_left_image_hovered));
- }
}
}
}
diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h
index 7e7d4ac6e6..57862fc626 100755
--- a/indra/llui/lltabcontainer.h
+++ b/indra/llui/lltabcontainer.h
@@ -62,15 +62,12 @@ public:
Optional<LLUIImage*> tab_top_image_unselected,
tab_top_image_selected,
tab_top_image_flash,
- tab_top_image_hovered,
tab_bottom_image_unselected,
tab_bottom_image_selected,
tab_bottom_image_flash,
- tab_bottom_image_hovered,
tab_left_image_unselected,
tab_left_image_selected,
- tab_left_image_flash,
- tab_left_image_hovered;
+ tab_left_image_flash;
TabParams();
};
@@ -117,11 +114,6 @@ public:
*/
Optional<S32> tab_icon_ctrl_pad;
- /**
- * This variable is used to found out should we highlight tab button on hover
- */
- Optional<bool> use_highlighting_on_hover;
-
Params();
};
@@ -315,7 +307,6 @@ private:
bool mOpenTabsOnDragAndDrop;
S32 mTabIconCtrlPad;
bool mUseTabEllipses;
- bool mUseHighlightingOnHover;
};
#endif // LL_TABCONTAINER_H
diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp
index 6bfe113933..928e82cb8c 100755
--- a/indra/llui/lltoolbar.cpp
+++ b/indra/llui/lltoolbar.cpp
@@ -928,8 +928,6 @@ LLToolBarButton* LLToolBar::createButton(const LLCommandId& id)
button_p.label = LLTrans::getString(commandp->labelRef());
button_p.tool_tip = LLTrans::getString(commandp->tooltipRef());
button_p.image_overlay = LLUI::getUIImage(commandp->icon());
- button_p.image_hover_unselected = LLUI::getUIImage(commandp->hoverIconUnselected());
- button_p.image_hover_selected = LLUI::getUIImage(commandp->hoverIconSelected());
button_p.button_flash_enable = commandp->isFlashingAllowed();
button_p.overwriteFrom(mButtonParams[mButtonType]);
LLToolBarButton* button = LLUICtrlFactory::create<LLToolBarButton>(button_p);
diff --git a/indra/llwindow/llopenglview-objc.mm b/indra/llwindow/llopenglview-objc.mm
index 7415c9d8dc..b393a3796d 100644
--- a/indra/llwindow/llopenglview-objc.mm
+++ b/indra/llwindow/llopenglview-objc.mm
@@ -376,13 +376,6 @@ attributedStringInfo getSegments(NSAttributedString *str)
[[self inputContext] handleEvent:theEvent];
}
- if ([[theEvent charactersIgnoringModifiers] characterAtIndex:0] == NSCarriageReturnCharacter ||
- [[theEvent charactersIgnoringModifiers] characterAtIndex:0] == NSEnterCharacter)
- {
- // callKeyDown won't return the value we expect for enter or return. Handle them as a separate case.
- [[self inputContext] handleEvent:theEvent];
- }
-
// OS X intentionally does not send us key-up information on cmd-key combinations.
// This behaviour is not a bug, and only applies to cmd-combinations (no others).
// Since SL assumes we receive those, we fake it here.
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index c5e1cde4e6..17e340d136 100755
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -267,6 +267,7 @@ set(viewer_SOURCE_FILES
llfloaterregiondebugconsole.cpp
llfloaterregioninfo.cpp
llfloaterreporter.cpp
+ llfloaterregionrestarting.cpp
llfloaterscriptdebug.cpp
llfloaterscriptlimits.cpp
llfloatersearch.cpp
@@ -855,6 +856,7 @@ set(viewer_HEADER_FILES
llfloaterregiondebugconsole.h
llfloaterregioninfo.h
llfloaterreporter.h
+ llfloaterregionrestarting.h
llfloaterscriptdebug.h
llfloaterscriptlimits.h
llfloatersearch.h
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index f5c2a4050b..c1e43e6d45 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-3.6.14
+3.7.3
diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml
index ce878f156b..60c942094a 100755
--- a/indra/newview/app_settings/commands.xml
+++ b/indra/newview/app_settings/commands.xml
@@ -3,8 +3,6 @@
<command name="aboutland"
available_in_toybox="true"
icon="Command_AboutLand_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_AboutLand_Label"
tooltip_ref="Command_AboutLand_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -15,8 +13,6 @@
<command name="appearance"
available_in_toybox="true"
icon="Command_Appearance_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Appearance_Label"
tooltip_ref="Command_Appearance_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -27,8 +23,6 @@
<command name="avatar"
available_in_toybox="true"
icon="Command_Avatar_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Avatar_Label"
tooltip_ref="Command_Avatar_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -39,8 +33,6 @@
<command name="build"
available_in_toybox="true"
icon="Command_Build_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Build_Label"
tooltip_ref="Command_Build_Tooltip"
execute_function="Build.Toggle"
@@ -54,8 +46,6 @@
available_in_toybox="true"
is_flashing_allowed="true"
icon="Command_Chat_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Chat_Label"
tooltip_ref="Command_Conversations_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -66,8 +56,6 @@
<command name="compass"
available_in_toybox="false"
icon="Command_Compass_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Compass_Label"
tooltip_ref="Command_Compass_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -78,8 +66,6 @@
<command name="destinations"
available_in_toybox="true"
icon="Command_Destinations_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Destinations_Label"
tooltip_ref="Command_Destinations_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -90,8 +76,6 @@
<command name="gestures"
available_in_toybox="true"
icon="Command_Gestures_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Gestures_Label"
tooltip_ref="Command_Gestures_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -102,8 +86,6 @@
<command name="howto"
available_in_toybox="true"
icon="Command_HowTo_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_HowTo_Label"
tooltip_ref="Command_HowTo_Tooltip"
execute_function="Help.ToggleHowTo"
@@ -112,8 +94,6 @@
<command name="inventory"
available_in_toybox="true"
icon="Command_Inventory_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Inventory_Label"
tooltip_ref="Command_Inventory_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -124,8 +104,6 @@
<command name="map"
available_in_toybox="true"
icon="Command_Map_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Map_Label"
tooltip_ref="Command_Map_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -136,8 +114,6 @@
<command name="marketplace"
available_in_toybox="false"
icon="Command_Marketplace_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Marketplace_Label"
tooltip_ref="Command_Marketplace_Tooltip"
execute_function="Avatar.OpenMarketplace"
@@ -145,8 +121,6 @@
<command name="minimap"
available_in_toybox="true"
icon="Command_MiniMap_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_MiniMap_Label"
tooltip_ref="Command_MiniMap_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -157,8 +131,6 @@
<command name="move"
available_in_toybox="true"
icon="Command_Move_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Move_Label"
tooltip_ref="Command_Move_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -169,8 +141,6 @@
<command name="outbox"
available_in_toybox="false"
icon="Command_Outbox_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Outbox_Label"
tooltip_ref="Command_Outbox_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -181,8 +151,6 @@
<command name="people"
available_in_toybox="true"
icon="Command_People_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_People_Label"
tooltip_ref="Command_People_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -193,8 +161,6 @@
<command name="picks"
available_in_toybox="true"
icon="Command_Picks_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Picks_Label"
tooltip_ref="Command_Picks_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -205,8 +171,6 @@
<command name="places"
available_in_toybox="true"
icon="Command_Places_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Places_Label"
tooltip_ref="Command_Places_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -217,8 +181,6 @@
<command name="preferences"
available_in_toybox="true"
icon="Command_Preferences_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Preferences_Label"
tooltip_ref="Command_Preferences_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -229,8 +191,6 @@
<command name="profile"
available_in_toybox="true"
icon="Command_Profile_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Profile_Label"
tooltip_ref="Command_Profile_Tooltip"
execute_function="Avatar.ToggleMyProfile"
@@ -239,8 +199,6 @@
<command name="search"
available_in_toybox="true"
icon="Command_Search_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Search_Label"
tooltip_ref="Command_Search_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -251,8 +209,6 @@
<command name="snapshot"
available_in_toybox="true"
icon="Command_Snapshot_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Snapshot_Label"
tooltip_ref="Command_Snapshot_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
@@ -273,8 +229,6 @@
<command name="speak"
available_in_toybox="true"
icon="Command_Speak_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_Speak_Label"
tooltip_ref="Command_Speak_Tooltip"
execute_function="Agent.PressMicrophone"
@@ -289,8 +243,6 @@
<command name="view"
available_in_toybox="true"
icon="Command_View_Icon"
- hover_icon_unselected="Command_Highlighting_Icon"
- hover_icon_selected="Command_Highlighting_Selected_Icon"
label_ref="Command_View_Label"
tooltip_ref="Command_View_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index d9093c2a6d..4c7b192ae5 100755
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -10068,11 +10068,21 @@
<key>Value</key>
<real>16</real>
</map>
-
+ <key>Mesh2MaxConcurrentRequests</key>
+ <map>
+ <key>Comment</key>
+ <string>Number of connections to use for loading meshes.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>8</integer>
+ </map>
<key>MeshMaxConcurrentRequests</key>
<map>
<key>Comment</key>
- <string>Number of threads to use for loading meshes.</string>
+ <string>Number of connections to use for loading meshes (legacy system).</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
@@ -10080,6 +10090,28 @@
<key>Value</key>
<integer>32</integer>
</map>
+ <key>MeshUseHttpRetryAfter</key>
+ <map>
+ <key>Comment</key>
+ <string>If TRUE, use Retry-After response headers when rescheduling a mesh request that fails with an HTTP 503 status. Static.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>MeshUseGetMesh1</key>
+ <map>
+ <key>Comment</key>
+ <string>If TRUE, use the legacy GetMesh capability for mesh download requests. Semi-dynamic (read at region crossings).</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
<key>RunMultipleThreads</key>
<map>
<key>Comment</key>
@@ -12632,6 +12664,17 @@
<key>Value</key>
<string>00000000-0000-0000-0000-000000000000</string>
</map>
+ <key>UISndRestart</key>
+ <map>
+ <key>Comment</key>
+ <string>Sound file for region restarting (uuid for sound asset)</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>b92a0f64-7709-8811-40c5-16afd624a45f</string>
+ </map>
<key>UISndSnapshot</key>
<map>
<key>Comment</key>
@@ -14871,17 +14914,6 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>DisablePrecacheDelayAfterTeleporting</key>
- <map>
- <key>Comment</key>
- <string>Disables the artificial delay in the viewer that precaches some incoming assets</string>
- <key>Persist</key>
- <integer>0</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>0</integer>
- </map>
<key>FMODExProfilerEnable</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/app_settings/shaders/class1/avatar/objectSkinV.glsl b/indra/newview/app_settings/shaders/class1/avatar/objectSkinV.glsl
index 39632d0cef..57129c3bd1 100755
--- a/indra/newview/app_settings/shaders/class1/avatar/objectSkinV.glsl
+++ b/indra/newview/app_settings/shaders/class1/avatar/objectSkinV.glsl
@@ -22,30 +22,46 @@
* $/LicenseInfo$
*/
-
-
ATTRIBUTE vec4 weight4;
-uniform mat4 matrixPalette[32];
+uniform mat3 matrixPalette[52];
+uniform vec3 translationPalette[52];
mat4 getObjectSkinnedTransform()
{
- int i;
+ int i;
vec4 w = fract(weight4);
vec4 index = floor(weight4);
- index = min(index, vec4(31.0));
+ index = min(index, vec4(51.0));
index = max(index, vec4( 0.0));
float scale = 1.0/(w.x+w.y+w.z+w.w);
w *= scale;
- mat4 mat = matrixPalette[int(index.x)]*w.x;
- mat += matrixPalette[int(index.y)]*w.y;
- mat += matrixPalette[int(index.z)]*w.z;
- mat += matrixPalette[int(index.w)]*w.w;
+ int i1 = int(index.x);
+ int i2 = int(index.y);
+ int i3 = int(index.z);
+ int i4 = int(index.w);
- return mat;
+ mat3 mat = matrixPalette[i1]*w.x;
+ mat += matrixPalette[i2]*w.y;
+ mat += matrixPalette[i3]*w.z;
+ mat += matrixPalette[i4]*w.w;
+
+ vec3 trans = translationPalette[i1]*w.x;
+ trans += translationPalette[i2]*w.y;
+ trans += translationPalette[i3]*w.z;
+ trans += translationPalette[i4]*w.w;
+
+ mat4 ret;
+
+ ret[0] = vec4(mat[0], 0);
+ ret[1] = vec4(mat[1], 0);
+ ret[2] = vec4(mat[2], 0);
+ ret[3] = vec4(trans, 1.0);
+
+ return ret;
}
diff --git a/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl b/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl
index e5f7366b70..2b5f001873 100755
--- a/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl
@@ -534,6 +534,7 @@ void main()
#ifdef FOR_IMPOSTOR
vec4 color;
color.rgb = diff.rgb;
+ color.a = 1.0;
#ifdef USE_VERTEX_COLOR
float final_alpha = diff.a * vertex_color.a;
diff --git a/indra/newview/character/avatar_lad.xml b/indra/newview/character/avatar_lad.xml
index e5b385f4aa..5268498d56 100755
--- a/indra/newview/character/avatar_lad.xml
+++ b/indra/newview/character/avatar_lad.xml
@@ -3825,7 +3825,11 @@
<volume_morph
name="BELLY"
scale="0.075 0.04 0.03"
- pos="0.07 0 -0.07"/>
+ pos="0.07 0 -0.02"/>
+ <volume_morph
+ name="PELVIS"
+ scale="0.075 0.04 0.03"
+ pos="0.07 0 -0.02"/>
</param_morph>
</param>
@@ -3844,7 +3848,16 @@
camera_elevation=".1"
camera_distance="1"
camera_angle="15">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="LEFT_PEC"
+ scale="0.0273 0.0273 0.0273"
+ pos="0.038 0.024 -0.016"/>
+ <volume_morph
+ name="RIGHT_PEC"
+ scale="0.0273 0.0273 0.0273"
+ pos="0.038 -0.024 -0.016"/>
+ </param_morph>
</param>
<param
@@ -3861,7 +3874,16 @@
value_max="1"
camera_elevation="0"
camera_distance=".28">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="LEFT_PEC"
+ scale="-0.05 0.0 0.0"
+ pos="-0.01 -0.01 -0.02"/>
+ <volume_morph
+ name="RIGHT_PEC"
+ scale="-0.05 0.0 0.0"
+ pos="-0.01 -0.01 -0.02"/>
+ </param_morph>
</param>
<param
@@ -3878,7 +3900,16 @@
value_max="1"
camera_elevation="0"
camera_distance=".28">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="LEFT_PEC"
+ scale="-0.051 0.0 0.0"
+ pos="-0.02 -0.01 -0.03"/>
+ <volume_morph
+ name="RIGHT_PEC"
+ scale="-0.051 0.0 0.0"
+ pos="-0.02 -0.01 -0.03"/>
+ </param_morph>
</param>
<param
@@ -3944,6 +3975,10 @@
scale="0.0 -0.01 0.0"
pos="0.0 0.0 0"/>
<volume_morph
+ name="UPPER_BACK"
+ scale="-0.01 -0.01 0.0"
+ pos="0.0 0.0 0"/>
+ <volume_morph
name="CHEST"
scale="-0.01 -0.01 0.0"
pos="0.01 0.0 0"/>
@@ -3994,6 +4029,10 @@
scale="-0.01 -0.01 0.0"
pos="0.01 0.0 0"/>
<volume_morph
+ name="UPPER_BACK"
+ scale="-0.01 -0.01 0.0"
+ pos="0.0 0.0 0"/>
+ <volume_morph
name="CHEST"
scale="-0.02 -0.02 0.0"
pos="0.01 0.0 0"/>
@@ -4042,6 +4081,32 @@
scale="0.02 0.03 0.03"
pos="0 0 -0.03"/>
<volume_morph
+ name="PELVIS"
+ scale="0.02 0.03 0.03"
+ pos="0 0 -0.03"/>
+ <volume_morph
+ name="UPPER_BACK"
+ scale="0.01 0.03 0.0"
+ pos="-0.03 0 0"/>
+ <volume_morph
+ name="LOWER_BACK"
+ scale="0.04 0.06 0.0"
+ pos="-0.06 0 0"/>
+ <volume_morph
+ name="LEFT_HANDLE"
+ pos="0.0 0.08 0.0"/>
+ <volume_morph
+ name="RIGHT_HANDLE"
+ pos="0.0 -0.08 0.0"/>
+ <volume_morph
+ name="LEFT_PEC"
+ scale="0.0367 0.0367 0.016"
+ pos="0.00 -0.005 -0.013"/>
+ <volume_morph
+ name="RIGHT_PEC"
+ scale="0.0367 0.0367 0.016"
+ pos="0.00 0.005 -0.013"/>
+ <volume_morph
name="BELLY"
scale="0.09 0.08 0.07"
pos="0 0 -0.05"/>
@@ -4093,7 +4158,16 @@
value_max="2"
camera_elevation=".3"
camera_distance=".8">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="LEFT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.004 0.0 -0.01"/>
+ <volume_morph
+ name="RIGHT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.004 0.0 -0.01"/>
+ </param_morph>
</param>
<param
@@ -4143,6 +4217,15 @@
<volume_morph
name="BELLY"
scale="0.0 0.02 0.0"/>
+ <volume_morph
+ name="LOWER_BACK"
+ scale="0.0 0.02 0.0"/>
+ <volume_morph
+ name="LEFT_HANDLE"
+ pos="0.0 0.025 0.0"/>
+ <volume_morph
+ name="RIGHT_HANDLE"
+ pos="0.0 -0.025 0.0"/>
</param_morph>
</param>
@@ -4162,7 +4245,16 @@
value_max="1.3"
camera_elevation=".3"
camera_distance=".8">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="LEFT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.0 -0.026 0.0"/>
+ <volume_morph
+ name="RIGHT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.0 0.026 0.0"/>
+ </param_morph>
</param>
<param
@@ -4177,11 +4269,20 @@
label_min="Big Pectorals"
label_max="Sunken Chest"
value_default="0"
- value_min="-.5"
+ value_min="-1.0"
value_max="1.1"
camera_elevation=".3"
camera_distance="1.2">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="LEFT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="-0.03 -0.024 -0.01"/>
+ <volume_morph
+ name="RIGHT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="-0.03 0.024 -0.01"/>
+ </param_morph>
</param>
<!-- ############# #
@@ -4206,6 +4307,14 @@
scale="0.03 0.03 0.0"
pos="-0.03 0 0.02"/>
<volume_morph
+ name="LEFT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.008 -0.03 0.01"/>
+ <volume_morph
+ name="RIGHT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.008 0.03 0.01"/>
+ <volume_morph
name="L_CLAVICLE"
scale="0.02 0.0 0.01"
pos="-0.02 0 0"/>
@@ -4376,7 +4485,16 @@
value_default="0"
value_min="-3"
value_max="3">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="LEFT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.0 0.0 -0.01"/>
+ <volume_morph
+ name="RIGHT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.0 0.0 -0.01"/>
+ </param_morph>
</param>
<param
@@ -4389,7 +4507,16 @@
value_default="0"
value_min="-1.25"
value_max="1.25">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="LEFT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.0 -0.026 0.0"/>
+ <volume_morph
+ name="RIGHT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.0 0.026 -0.0"/>
+ </param_morph>
</param>
<param
@@ -4402,7 +4529,12 @@
value_default="0"
value_min="-1"
value_max="1">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="BELLY"
+ scale="0.0 0.0 0.0"
+ pos="0.0 0.0 0.05"/>
+ </param_morph>
</param>
<param
@@ -4415,7 +4547,16 @@
value_default="0"
value_min="-2"
value_max="2">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="LEFT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.0 0.03 0.0"/>
+ <volume_morph
+ name="RIGHT_PEC"
+ scale="0.0 0.0 0.0"
+ pos="0.0 0.03 0.0"/>
+ </param_morph>
</param>
<!--
@@ -4518,6 +4659,10 @@
name="PELVIS"
scale="-0.01 0.0 0.0"
pos="0.01 0 0.0"/>
+ <volume_morph
+ name="BUTT"
+ scale="0.0 0.0886 0.0"
+ pos="0.03 0 0.0"/>
</param_morph>
</param>
@@ -4949,7 +5094,11 @@
value_default="0"
value_min="-1"
value_max="1">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="BUTT"
+ pos="0.0 0.0 0.05"/>
+ </param_morph>
</param>
<param
@@ -4962,7 +5111,11 @@
value_default="0"
value_min="-1"
value_max="1">
- <param_morph />
+ <param_morph>
+ <volume_morph
+ name="BUTT"
+ pos="0.0 0.05 0.0"/>
+ </param_morph>
</param>
<!--
diff --git a/indra/newview/character/avatar_skeleton.xml b/indra/newview/character/avatar_skeleton.xml
index 5e73804f2d..6b07bbc1d3 100755
--- a/indra/newview/character/avatar_skeleton.xml
+++ b/indra/newview/character/avatar_skeleton.xml
@@ -1,11 +1,18 @@
<?xml version="1.0" encoding="US-ASCII" standalone="yes"?>
-<linden_skeleton version="1.0" num_bones="46" num_collision_volumes="19">
+<linden_skeleton version="1.0" num_bones="53" num_collision_volumes="26">
<bone name="mPelvis" pos="0.000 0.000 1.067" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" pivot="0.000000 0.000000 1.067015">
<collision_volume name="PELVIS" pos = "-0.01 0 -0.02" rot="0.000000 8.00000 0.000000" scale="0.12 0.16 0.17"/>
+ <collision_volume name="BUTT" pos = "-0.06 0 -0.1" rot="0.000000 0.00000 0.000000" scale="0.1 0.1 0.1"/>
<bone name="mTorso" pos="0.000 0.000 0.084" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" pivot="0.000000 0.000000 0.084073">
<collision_volume name="BELLY" pos = "0.028 0 0.04" rot="0.000000 8.00000 0.000000" scale="0.09 0.13 0.15"/>
+ <collision_volume name="LOWER_BACK" pos = "0.0 0.0 0.023" rot="0.000000 0.00000 0.000000" scale="0.09 0.13 0.15"/>
+ <collision_volume name="LEFT_HANDLE" pos = "0.0 0.10 0.058" rot="0.000000 0.00000 0.000000" scale="0.05 0.05 0.05"/>
+ <collision_volume name="RIGHT_HANDLE" pos = "0.0 -0.10 0.058" rot="0.000000 0.00000 0.000000" scale="0.05 0.05 0.05"/>
<bone name="mChest" pos="-0.015 0.000 0.205" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" pivot="-0.015368 0.000000 0.204877">
<collision_volume name="CHEST" pos = "0.028 0 0.07" rot="0.000000 -10.00000 0.000000" scale="0.11 0.15 0.2"/>
+ <collision_volume name="UPPER_BACK" pos = "0.0 0.0 0.017" rot="0.000000 0.00000 0.000000" scale="0.09 0.13 0.15"/>
+ <collision_volume name="LEFT_PEC" pos = "0.119 0.082 0.042" rot="0.000000 4.29000 0.000000" scale="0.05 0.05 0.05"/>
+ <collision_volume name="RIGHT_PEC" pos = "0.119 -0.082 0.042" rot="0.000000 4.29000 0.000000" scale="0.05 0.05 0.05"/>
<bone name="mNeck" pos="-0.010 0.000 0.251" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" pivot="-0.009507 0.000000 0.251108">
<collision_volume name="NECK" pos = "0.0 0 0.02" rot="0.000000 0.000000 0.000000" scale="0.05 0.06 0.08"/>
<bone name="mHead" pos="0.000 -0.000 0.076" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" pivot="0.000000 -0.000000 0.075630">
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index 79da9e5873..f150ceda67 100755
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -259,11 +259,9 @@ bool handleSlowMotionAnimation(const LLSD& newvalue)
return true;
}
-// static
-void LLAgent::parcelChangedCallback()
+void LLAgent::setCanEditParcel() // called via mParcelChangedSignal
{
bool can_edit = LLToolMgr::getInstance()->canEdit();
-
gAgent.mCanEditParcel = can_edit;
}
@@ -425,6 +423,8 @@ LLAgent::LLAgent() :
mListener.reset(new LLAgentListener(*this));
+ addParcelChangedCallback(&setCanEditParcel);
+
mMoveTimer.stop();
}
@@ -451,8 +451,6 @@ void LLAgent::init()
mLastKnownRequestMaturity = mLastKnownResponseMaturity;
mIsDoSendMaturityPreferenceToServer = true;
- LLViewerParcelMgr::getInstance()->addAgentParcelChangedCallback(boost::bind(&LLAgent::parcelChangedCallback));
-
if (!mTeleportFinishedSlot.connected())
{
mTeleportFinishedSlot = LLViewerParcelMgr::getInstance()->setTeleportFinishedCallback(boost::bind(&LLAgent::handleTeleportFinished, this));
@@ -835,22 +833,33 @@ void LLAgent::handleServerBakeRegionTransition(const LLUUID& region_id)
}
}
+void LLAgent::changeParcels()
+{
+ LL_DEBUGS("AgentLocation") << "Calling ParcelChanged callbacks" << LL_ENDL;
+ // Notify anything that wants to know about parcel changes
+ mParcelChangedSignal();
+}
+
+boost::signals2::connection LLAgent::addParcelChangedCallback(parcel_changed_callback_t cb)
+{
+ return mParcelChangedSignal.connect(cb);
+}
+
//-----------------------------------------------------------------------------
// setRegion()
//-----------------------------------------------------------------------------
void LLAgent::setRegion(LLViewerRegion *regionp)
{
- bool teleport = true;
-
+ bool notifyRegionChange;
+
llassert(regionp);
if (mRegionp != regionp)
{
- // std::string host_name;
- // host_name = regionp->getHost().getHostName();
-
+ notifyRegionChange = true;
+
std::string ip = regionp->getHost().getString();
- llinfos << "Moving agent into region: " << regionp->getName()
- << " located at " << ip << llendl;
+ LL_INFOS("AgentLocation") << "Moving agent into region: " << regionp->getName()
+ << " located at " << ip << LL_ENDL;
if (mRegionp)
{
// We've changed regions, we're now going to change our agent coordinate frame.
@@ -878,9 +887,6 @@ void LLAgent::setRegion(LLViewerRegion *regionp)
{
gSky.mVOGroundp->setRegion(regionp);
}
-
- // Notify windlight managers
- teleport = (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE);
}
else
{
@@ -902,8 +908,14 @@ void LLAgent::setRegion(LLViewerRegion *regionp)
// Pass new region along to metrics components that care about this level of detail.
LLAppViewer::metricsUpdateRegion(regionp->getHandle());
}
+ else
+ {
+ notifyRegionChange = false;
+ }
mRegionp = regionp;
+ // TODO - most of what follows probably should be moved into callbacks
+
// Pass the region host to LLUrlEntryParcel to resolve parcel name
// with a server request.
LLUrlEntryParcel::setRegionHost(getRegionHost());
@@ -922,15 +934,6 @@ void LLAgent::setRegion(LLViewerRegion *regionp)
LLFloaterMove::sUpdateFlyingStatus();
- if (teleport)
- {
- LLEnvManagerNew::instance().onTeleport();
- }
- else
- {
- LLEnvManagerNew::instance().onRegionCrossing();
- }
-
// If the newly entered region is using server bakes, and our
// current appearance is non-baked, request appearance update from
// server.
@@ -943,6 +946,12 @@ void LLAgent::setRegion(LLViewerRegion *regionp)
// Need to handle via callback after caps arrive.
mRegionp->setCapabilitiesReceivedCallback(boost::bind(&LLAgent::handleServerBakeRegionTransition,this,_1));
}
+
+ if (notifyRegionChange)
+ {
+ LL_DEBUGS("AgentLocation") << "Calling RegionChanged callbacks" << LL_ENDL;
+ mRegionChangedSignal();
+ }
}
@@ -967,6 +976,16 @@ LLHost LLAgent::getRegionHost() const
}
}
+boost::signals2::connection LLAgent::addRegionChangedCallback(const region_changed_signal_t::slot_type& cb)
+{
+ return mRegionChangedSignal.connect(cb);
+}
+
+void LLAgent::removeRegionChangedCallback(boost::signals2::connection callback)
+{
+ mRegionChangedSignal.disconnect(callback);
+}
+
//-----------------------------------------------------------------------------
// inPrelude()
//-----------------------------------------------------------------------------
diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h
index 7fac17d098..0766407494 100755
--- a/indra/newview/llagent.h
+++ b/indra/newview/llagent.h
@@ -231,15 +231,54 @@ private:
LLVector3 mHomePosRegion;
//--------------------------------------------------------------------
- // Region
+ // Parcel
//--------------------------------------------------------------------
public:
+ void changeParcels(); // called by LLViewerParcelMgr when we cross a parcel boundary
+
+ // Register a boost callback to be called when the agent changes parcels
+ typedef boost::function<void()> parcel_changed_callback_t;
+ boost::signals2::connection addParcelChangedCallback(parcel_changed_callback_t);
+
+private:
+ typedef boost::signals2::signal<void()> parcel_changed_signal_t;
+ parcel_changed_signal_t mParcelChangedSignal;
+
+ //--------------------------------------------------------------------
+ // Region
+ //--------------------------------------------------------------------
+ public:
void setRegion(LLViewerRegion *regionp);
LLViewerRegion *getRegion() const;
LLHost getRegionHost() const;
BOOL inPrelude();
-private:
+
+ /**
+ * Register a boost callback to be called when the agent changes regions
+ * Note that if you need to access a capability for the region, you may need to wait
+ * for the capabilities to be received, since in some cases your region changed
+ * callback will be called before the capabilities have been received. Your callback
+ * may need to look something like:
+ *
+ * LLViewerRegion* region = gAgent.getRegion();
+ * if (region->capabilitiesReceived())
+ * {
+ * useCapability(region);
+ * }
+ * else // Need to handle via callback after caps arrive.
+ * {
+ * region->setCapabilitiesReceivedCallback(boost::bind(&useCapability,region,_1));
+ * // you may or may not want to remove that callback
+ * }
+ */
+ typedef boost::signals2::signal<void()> region_changed_signal_t;
+
+ boost::signals2::connection addRegionChangedCallback(const region_changed_signal_t::slot_type& cb);
+ void removeRegionChangedCallback(boost::signals2::connection callback);
+
+ private:
LLViewerRegion *mRegionp;
+ region_changed_signal_t mRegionChangedSignal;
//--------------------------------------------------------------------
// History
@@ -640,9 +679,10 @@ private:
public:
bool canEditParcel() const { return mCanEditParcel; }
private:
+ static void setCanEditParcel();
bool mCanEditParcel;
- static void parcelChangedCallback();
+
/********************************************************************************
** **
diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp
index 0d7d41304d..70dcffefb2 100755
--- a/indra/newview/llappcorehttp.cpp
+++ b/indra/newview/llappcorehttp.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -28,18 +28,81 @@
#include "llappcorehttp.h"
+#include "llappviewer.h"
#include "llviewercontrol.h"
+// Here is where we begin to get our connection usage under control.
+// This establishes llcorehttp policy classes that, among other
+// things, limit the maximum number of connections to outside
+// services. Each of the entries below maps to a policy class and
+// has a limit, sometimes configurable, of how many connections can
+// be open at a time.
+
const F64 LLAppCoreHttp::MAX_THREAD_WAIT_TIME(10.0);
+static const struct
+{
+ LLAppCoreHttp::EAppPolicy mPolicy;
+ U32 mDefault;
+ U32 mMin;
+ U32 mMax;
+ U32 mRate;
+ std::string mKey;
+ const char * mUsage;
+} init_data[] = // Default and dynamic values for classes
+{
+ {
+ LLAppCoreHttp::AP_DEFAULT, 8, 8, 8, 0,
+ "",
+ "other"
+ },
+ {
+ LLAppCoreHttp::AP_TEXTURE, 8, 1, 12, 0,
+ "TextureFetchConcurrency",
+ "texture fetch"
+ },
+ {
+ LLAppCoreHttp::AP_MESH1, 32, 1, 128, 100,
+ "MeshMaxConcurrentRequests",
+ "mesh fetch"
+ },
+ {
+ LLAppCoreHttp::AP_MESH2, 8, 1, 32, 100,
+ "Mesh2MaxConcurrentRequests",
+ "mesh2 fetch"
+ },
+ {
+ LLAppCoreHttp::AP_LARGE_MESH, 2, 1, 8, 0,
+ "",
+ "large mesh fetch"
+ },
+ {
+ LLAppCoreHttp::AP_UPLOADS, 2, 1, 8, 0,
+ "",
+ "asset upload"
+ },
+ {
+ LLAppCoreHttp::AP_LONG_POLL, 32, 32, 32, 0,
+ "",
+ "long poll"
+ }
+};
+
+static void setting_changed();
+
LLAppCoreHttp::LLAppCoreHttp()
: mRequest(NULL),
mStopHandle(LLCORE_HTTP_HANDLE_INVALID),
mStopRequested(0.0),
- mStopped(false),
- mPolicyDefault(-1)
-{}
+ mStopped(false)
+{
+ for (int i(0); i < LL_ARRAY_SIZE(mPolicies); ++i)
+ {
+ mPolicies[i] = LLCore::HttpRequest::DEFAULT_POLICY_ID;
+ mSettings[i] = 0U;
+ }
+}
LLAppCoreHttp::~LLAppCoreHttp()
@@ -54,30 +117,28 @@ void LLAppCoreHttp::init()
LLCore::HttpStatus status = LLCore::HttpRequest::createService();
if (! status)
{
- LL_ERRS("Init") << "Failed to initialize HTTP services. Reason: "
- << status.toString()
+ LL_ERRS("Init") << "Failed to initialize HTTP services. Reason: " << status.toString()
<< LL_ENDL;
}
// Point to our certs or SSH/https: will fail on connect
- status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_CA_FILE,
- gDirUtilp->getCAFile());
+ status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_CA_FILE,
+ LLCore::HttpRequest::GLOBAL_POLICY_ID,
+ gDirUtilp->getCAFile(), NULL);
if (! status)
{
- LL_ERRS("Init") << "Failed to set CA File for HTTP services. Reason: "
- << status.toString()
+ LL_ERRS("Init") << "Failed to set CA File for HTTP services. Reason: " << status.toString()
<< LL_ENDL;
}
- // Establish HTTP Proxy. "LLProxy" is a special string which directs
- // the code to use LLProxy::applyProxySettings() to establish any
- // HTTP or SOCKS proxy for http operations.
- status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_LLPROXY, 1);
+ // Establish HTTP Proxy, if desired.
+ status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_LLPROXY,
+ LLCore::HttpRequest::GLOBAL_POLICY_ID,
+ 1, NULL);
if (! status)
{
- LL_ERRS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: "
- << status.toString()
- << LL_ENDL;
+ LL_WARNS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: " << status.toString()
+ << LL_ENDL;
}
// Tracing levels for library & libcurl (note that 2 & 3 are beyond spammy):
@@ -90,47 +151,74 @@ void LLAppCoreHttp::init()
{
long trace_level(0L);
trace_level = long(gSavedSettings.getU32(http_trace));
- status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, trace_level);
+ status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_TRACE,
+ LLCore::HttpRequest::GLOBAL_POLICY_ID,
+ trace_level, NULL);
}
// Setup default policy and constrain if directed to
- mPolicyDefault = LLCore::HttpRequest::DEFAULT_POLICY_ID;
- static const std::string texture_concur("TextureFetchConcurrency");
- if (gSavedSettings.controlExists(texture_concur))
+ mPolicies[AP_DEFAULT] = LLCore::HttpRequest::DEFAULT_POLICY_ID;
+
+ // Setup additional policies based on table and some special rules
+ for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i)
{
- U32 concur(llmin(gSavedSettings.getU32(texture_concur), U32(12)));
+ const EAppPolicy policy(init_data[i].mPolicy);
- if (concur > 0)
+ if (AP_DEFAULT == policy)
{
- LLCore::HttpStatus status;
- status = LLCore::HttpRequest::setPolicyClassOption(mPolicyDefault,
- LLCore::HttpRequest::CP_CONNECTION_LIMIT,
- concur);
- if (! status)
- {
- LL_WARNS("Init") << "Unable to set texture fetch concurrency. Reason: "
- << status.toString()
- << LL_ENDL;
- }
- else
- {
- LL_INFOS("Init") << "Application settings overriding default texture fetch concurrency. New value: "
- << concur
- << LL_ENDL;
- }
+ // Pre-created
+ continue;
+ }
+
+ mPolicies[policy] = LLCore::HttpRequest::createPolicyClass();
+ if (! mPolicies[policy])
+ {
+ // Use default policy (but don't accidentally modify default)
+ LL_WARNS("Init") << "Failed to create HTTP policy class for " << init_data[i].mUsage
+ << ". Using default policy."
+ << LL_ENDL;
+ mPolicies[policy] = mPolicies[AP_DEFAULT];
+ continue;
}
}
+
+ // Need a request object to handle dynamic options before setting them
+ mRequest = new LLCore::HttpRequest;
+
+ // Apply initial settings
+ refreshSettings(true);
// Kick the thread
status = LLCore::HttpRequest::startThread();
if (! status)
{
- LL_ERRS("Init") << "Failed to start HTTP servicing thread. Reason: "
- << status.toString()
+ LL_ERRS("Init") << "Failed to start HTTP servicing thread. Reason: " << status.toString()
<< LL_ENDL;
}
- mRequest = new LLCore::HttpRequest;
+ // Register signals for settings and state changes
+ for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i)
+ {
+ if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey))
+ {
+ LLPointer<LLControlVariable> cntrl_ptr = gSavedSettings.getControl(init_data[i].mKey);
+ if (cntrl_ptr.isNull())
+ {
+ LL_WARNS("Init") << "Unable to set signal on global setting '" << init_data[i].mKey
+ << "'" << LL_ENDL;
+ }
+ else
+ {
+ mSettingsSignal[i] = cntrl_ptr->getCommitSignal()->connect(boost::bind(&setting_changed));
+ }
+ }
+ }
+}
+
+
+void setting_changed()
+{
+ LLAppViewer::instance()->getAppCoreHttp().refreshSettings(false);
}
@@ -173,6 +261,11 @@ void LLAppCoreHttp::cleanup()
}
}
+ for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i)
+ {
+ mSettingsSignal[i].disconnect();
+ }
+
delete mRequest;
mRequest = NULL;
@@ -185,6 +278,78 @@ void LLAppCoreHttp::cleanup()
}
}
+void LLAppCoreHttp::refreshSettings(bool initial)
+{
+ LLCore::HttpStatus status;
+
+ for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i)
+ {
+ const EAppPolicy policy(init_data[i].mPolicy);
+
+ // Set any desired throttle
+ if (initial && init_data[i].mRate)
+ {
+ // Init-time only, can use the static setters here
+ status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_THROTTLE_RATE,
+ mPolicies[policy],
+ init_data[i].mRate,
+ NULL);
+ if (! status)
+ {
+ LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage
+ << " throttle rate. Reason: " << status.toString()
+ << LL_ENDL;
+ }
+ }
+
+ // Get target connection concurrency value
+ U32 setting(init_data[i].mDefault);
+ if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey))
+ {
+ U32 new_setting(gSavedSettings.getU32(init_data[i].mKey));
+ if (new_setting)
+ {
+ // Treat zero settings as an ask for default
+ setting = llclamp(new_setting, init_data[i].mMin, init_data[i].mMax);
+ }
+ }
+
+ if (! initial && setting == mSettings[policy])
+ {
+ // Unchanged, try next setting
+ continue;
+ }
+
+ // Set it and report
+ // *TODO: These are intended to be per-host limits when we can
+ // support that in llcorehttp/libcurl.
+ LLCore::HttpHandle handle;
+ handle = mRequest->setPolicyOption(LLCore::HttpRequest::PO_CONNECTION_LIMIT,
+ mPolicies[policy],
+ setting, NULL);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ status = mRequest->getStatus();
+ LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage
+ << " concurrency. Reason: " << status.toString()
+ << LL_ENDL;
+ }
+ else
+ {
+ LL_DEBUGS("Init") << "Changed " << init_data[i].mUsage
+ << " concurrency. New value: " << setting
+ << LL_ENDL;
+ mSettings[policy] = setting;
+ if (initial && setting != init_data[i].mDefault)
+ {
+ LL_INFOS("Init") << "Application settings overriding default " << init_data[i].mUsage
+ << " concurrency. New value: " << setting
+ << LL_ENDL;
+ }
+ }
+ }
+}
+
void LLAppCoreHttp::onCompleted(LLCore::HttpHandle, LLCore::HttpResponse *)
{
diff --git a/indra/newview/llappcorehttp.h b/indra/newview/llappcorehttp.h
index 241d73ad52..40e3042b84 100755
--- a/indra/newview/llappcorehttp.h
+++ b/indra/newview/llappcorehttp.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -41,6 +41,117 @@
class LLAppCoreHttp : public LLCore::HttpHandler
{
public:
+ typedef LLCore::HttpRequest::policy_t policy_t;
+
+ enum EAppPolicy
+ {
+ /// Catchall policy class. Not used yet
+ /// but will have a generous concurrency
+ /// limit. Deep queueing possible by having
+ /// a chatty HTTP user.
+ ///
+ /// Destination: anywhere
+ /// Protocol: http: or https:
+ /// Transfer size: KB-MB
+ /// Long poll: no
+ /// Concurrency: high
+ /// Request rate: unknown
+ /// Pipelined: no
+ AP_DEFAULT,
+
+ /// Texture fetching policy class. Used to
+ /// download textures via capability or SSA
+ /// baking service. Deep queueing of requests.
+ /// Do not share.
+ ///
+ /// Destination: simhost:12046 & bake-texture:80
+ /// Protocol: http:
+ /// Transfer size: KB-MB
+ /// Long poll: no
+ /// Concurrency: high
+ /// Request rate: high
+ /// Pipelined: soon
+ AP_TEXTURE,
+
+ /// Legacy mesh fetching policy class. Used to
+ /// download textures via 'GetMesh' capability.
+ /// To be deprecated. Do not share.
+ ///
+ /// Destination: simhost:12046
+ /// Protocol: http:
+ /// Transfer size: KB-MB
+ /// Long poll: no
+ /// Concurrency: dangerously high
+ /// Request rate: high
+ /// Pipelined: no
+ AP_MESH1,
+
+ /// New mesh fetching policy class. Used to
+ /// download textures via 'GetMesh2' capability.
+ /// Used when fetch request (typically one LOD)
+ /// is 'small', currently defined as 2MB.
+ /// Very deeply queued. Do not share.
+ ///
+ /// Destination: simhost:12046
+ /// Protocol: http:
+ /// Transfer size: KB-MB
+ /// Long poll: no
+ /// Concurrency: high
+ /// Request rate: high
+ /// Pipelined: soon
+ AP_MESH2,
+
+ /// Large mesh fetching policy class. Used to
+ /// download textures via 'GetMesh' or 'GetMesh2'
+ /// capability. Used when fetch request
+ /// is not small to avoid head-of-line problem
+ /// when large requests block a sequence of small,
+ /// fast requests. Can be shared with similar
+ /// traffic that can wait for longish stalls
+ /// (default timeout 600S).
+ ///
+ /// Destination: simhost:12046
+ /// Protocol: http:
+ /// Transfer size: MB
+ /// Long poll: no
+ /// Concurrency: low
+ /// Request rate: low
+ /// Pipelined: soon
+ AP_LARGE_MESH,
+
+ /// Asset upload policy class. Used to store
+ /// assets (mesh only at the moment) via
+ /// changeable URL. Responses may take some
+ /// time (default timeout 240S).
+ ///
+ /// Destination: simhost:12043
+ /// Protocol: https:
+ /// Transfer size: KB-MB
+ /// Long poll: no
+ /// Concurrency: low
+ /// Request rate: low
+ /// Pipelined: no
+ AP_UPLOADS,
+
+ /// Long-poll-type HTTP requests. Not
+ /// bound by a connection limit. Requests
+ /// will typically hang around for a long
+ /// time (~30S). Only shareable with other
+ /// long-poll requests.
+ ///
+ /// Destination: simhost:12043
+ /// Protocol: https:
+ /// Transfer size: KB
+ /// Long poll: yes
+ /// Concurrency: unlimited but low in practice
+ /// Request rate: low
+ /// Pipelined: no
+ AP_LONG_POLL,
+
+ AP_COUNT // Must be last
+ };
+
+public:
LLAppCoreHttp();
~LLAppCoreHttp();
@@ -65,21 +176,27 @@ public:
// Notification when the stop request is complete.
virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response);
- // Retrieve the policy class for default operations.
- int getPolicyDefault() const
+ // Retrieve a policy class identifier for desired
+ // application function.
+ policy_t getPolicy(EAppPolicy policy) const
{
- return mPolicyDefault;
+ return mPolicies[policy];
}
+
+ // Apply initial or new settings from the environment.
+ void refreshSettings(bool initial);
private:
static const F64 MAX_THREAD_WAIT_TIME;
private:
- LLCore::HttpRequest * mRequest;
+ LLCore::HttpRequest * mRequest; // Request queue to issue shutdowns
LLCore::HttpHandle mStopHandle;
F64 mStopRequested;
bool mStopped;
- int mPolicyDefault;
+ policy_t mPolicies[AP_COUNT]; // Policy class id for each connection set
+ U32 mSettings[AP_COUNT];
+ boost::signals2::connection mSettingsSignal[AP_COUNT]; // Signals to global settings that affect us
};
diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp
index f622d5a63a..24f467f954 100755
--- a/indra/newview/lldrawpoolavatar.cpp
+++ b/indra/newview/lldrawpoolavatar.cpp
@@ -55,6 +55,7 @@ static U32 sDataMask = LLDrawPoolAvatar::VERTEX_DATA_MASK;
static U32 sBufferUsage = GL_STREAM_DRAW_ARB;
static U32 sShaderLevel = 0;
+#define JOINT_COUNT 52
LLGLSLShader* LLDrawPoolAvatar::sVertexProgram = NULL;
BOOL LLDrawPoolAvatar::sSkipOpaque = FALSE;
@@ -1582,10 +1583,11 @@ void LLDrawPoolAvatar::updateRiggedFaceVertexBuffer(LLVOAvatar* avatar, LLFace*
LLVector4a* norm = has_normal ? (LLVector4a*) normal.get() : NULL;
//build matrix palette
- LLMatrix4a mp[64];
+ LLMatrix4a mp[JOINT_COUNT];
LLMatrix4* mat = (LLMatrix4*) mp;
- for (U32 j = 0; j < skin->mJointNames.size(); ++j)
+ U32 count = llmin((U32) skin->mJointNames.size(), (U32) JOINT_COUNT);
+ for (U32 j = 0; j < count; ++j)
{
LLJoint* joint = avatar->getJoint(skin->mJointNames[j]);
if (joint)
@@ -1642,6 +1644,7 @@ void LLDrawPoolAvatar::updateRiggedFaceVertexBuffer(LLVOAvatar* avatar, LLFace*
LLVector4a& n = vol_face.mNormals[j];
bind_shape_matrix.rotate(n, t);
final_mat.rotate(t, dst);
+ dst.normalize3fast();
norm[j] = dst;
}
}
@@ -1708,9 +1711,9 @@ void LLDrawPoolAvatar::renderRigged(LLVOAvatar* avatar, U32 type, bool glow)
{
if (sShaderLevel > 0)
{ //upload matrix palette to shader
- LLMatrix4 mat[32];
+ LLMatrix4 mat[JOINT_COUNT];
- U32 count = llmin((U32) skin->mJointNames.size(), (U32) 32);
+ U32 count = llmin((U32) skin->mJointNames.size(), (U32) JOINT_COUNT);
for (U32 i = 0; i < count; ++i)
{
@@ -1724,10 +1727,42 @@ void LLDrawPoolAvatar::renderRigged(LLVOAvatar* avatar, U32 type, bool glow)
stop_glerror();
- LLDrawPoolAvatar::sVertexProgram->uniformMatrix4fv(LLViewerShaderMgr::AVATAR_MATRIX,
+ F32 mp[JOINT_COUNT*9];
+
+ F32 transp[JOINT_COUNT*3];
+
+ for (U32 i = 0; i < count; ++i)
+ {
+ F32* m = (F32*) mat[i].mMatrix;
+
+ U32 idx = i*9;
+
+ mp[idx+0] = m[0];
+ mp[idx+1] = m[1];
+ mp[idx+2] = m[2];
+
+ mp[idx+3] = m[4];
+ mp[idx+4] = m[5];
+ mp[idx+5] = m[6];
+
+ mp[idx+6] = m[8];
+ mp[idx+7] = m[9];
+ mp[idx+8] = m[10];
+
+ idx = i*3;
+
+ transp[idx+0] = m[12];
+ transp[idx+1] = m[13];
+ transp[idx+2] = m[14];
+ }
+
+ LLDrawPoolAvatar::sVertexProgram->uniformMatrix3fv(LLViewerShaderMgr::AVATAR_MATRIX,
count,
FALSE,
- (GLfloat*) mat[0].mMatrix);
+ (GLfloat*) mp);
+
+ LLDrawPoolAvatar::sVertexProgram->uniform3fv(LLShaderMgr::AVATAR_TRANSLATION, count, transp);
+
stop_glerror();
}
diff --git a/indra/newview/llenvmanager.cpp b/indra/newview/llenvmanager.cpp
index 86fe6754dc..589cf28615 100755
--- a/indra/newview/llenvmanager.cpp
+++ b/indra/newview/llenvmanager.cpp
@@ -92,9 +92,11 @@ void LLEnvPrefs::setUseDayCycle(const std::string& name)
}
//=============================================================================
-LLEnvManagerNew::LLEnvManagerNew()
+LLEnvManagerNew::LLEnvManagerNew():
+ mInterpNextChangeMessage(true),
+ mCurRegionUUID(LLUUID::null),
+ mLastReceivedID(LLUUID::null)
{
- mInterpNextChangeMessage = true;
// Set default environment settings.
mUserPrefs.mUseRegionSettings = true;
@@ -102,6 +104,9 @@ LLEnvManagerNew::LLEnvManagerNew()
mUserPrefs.mWaterPresetName = "Default";
mUserPrefs.mSkyPresetName = "Default";
mUserPrefs.mDayCycleName = "Default";
+
+ LL_DEBUGS("Windlight")<<LL_ENDL;
+ gAgent.addRegionChangedCallback(boost::bind(&LLEnvManagerNew::onRegionChange, this));
}
bool LLEnvManagerNew::getUseRegionSettings() const
@@ -300,6 +305,11 @@ void LLEnvManagerNew::loadUserPrefs()
mUserPrefs.mUseRegionSettings = gSavedSettings.getBOOL("UseEnvironmentFromRegion");
mUserPrefs.mUseDayCycle = gSavedSettings.getBOOL("UseDayCycle");
+
+ if (mUserPrefs.mUseRegionSettings)
+ {
+ requestRegionSettings();
+ }
}
void LLEnvManagerNew::saveUserPrefs()
@@ -398,6 +408,7 @@ void LLEnvManagerNew::dumpPresets()
void LLEnvManagerNew::requestRegionSettings()
{
+ LL_DEBUGS("Windlight") << LL_ENDL;
LLEnvironmentRequest::initiate();
}
@@ -422,11 +433,6 @@ boost::signals2::connection LLEnvManagerNew::setRegionSettingsChangeCallback(con
return mRegionSettingsChangeSignal.connect(cb);
}
-boost::signals2::connection LLEnvManagerNew::setRegionChangeCallback(const region_change_signal_t::slot_type& cb)
-{
- return mRegionChangeSignal.connect(cb);
-}
-
boost::signals2::connection LLEnvManagerNew::setRegionSettingsAppliedCallback(const region_settings_applied_signal_t::slot_type& cb)
{
return mRegionSettingsAppliedSignal.connect(cb);
@@ -457,25 +463,13 @@ const std::string LLEnvManagerNew::getScopeString(LLEnvKey::EScope scope)
}
}
-void LLEnvManagerNew::onRegionCrossing()
-{
- LL_DEBUGS("Windlight") << "Crossed region" << LL_ENDL;
- onRegionChange(true);
-}
-
-void LLEnvManagerNew::onTeleport()
-{
- LL_DEBUGS("Windlight") << "Teleported" << LL_ENDL;
- onRegionChange(false);
-}
-
void LLEnvManagerNew::onRegionSettingsResponse(const LLSD& content)
{
// If the message was valid, grab the UUID from it and save it for next outbound update message.
mLastReceivedID = content[0]["messageID"].asUUID();
// Refresh cached region settings.
- LL_DEBUGS("Windlight") << "Caching region environment settings: " << content << LL_ENDL;
+ LL_DEBUGS("Windlight") << "Received region environment settings: " << content << LL_ENDL;
F32 sun_hour = 0; // *TODO
LLEnvironmentSettings new_settings(content[1], content[2], content[3], sun_hour);
mCachedRegionPrefs = new_settings;
@@ -594,6 +588,7 @@ void LLEnvManagerNew::updateWaterFromPrefs(bool interpolate)
void LLEnvManagerNew::updateManagersFromPrefs(bool interpolate)
{
+ LL_DEBUGS("Windlight")<<LL_ENDL;
// Apply water settings.
updateWaterFromPrefs(interpolate);
@@ -651,28 +646,35 @@ bool LLEnvManagerNew::useDefaultWater()
}
-void LLEnvManagerNew::onRegionChange(bool interpolate)
+void LLEnvManagerNew::onRegionChange()
{
// Avoid duplicating region setting requests
// by checking whether the region is actually changing.
LLViewerRegion* regionp = gAgent.getRegion();
LLUUID region_uuid = regionp ? regionp->getRegionID() : LLUUID::null;
- if (region_uuid == mCurRegionUUID)
+ if (region_uuid != mCurRegionUUID)
{
- return;
+ // Clear locally modified region settings.
+ mNewRegionPrefs.clear();
+
+ // *TODO: clear environment settings of the previous region?
+
+ // Request environment settings of the new region.
+ mCurRegionUUID = region_uuid;
+ // for region crossings, interpolate the change; for teleports, don't
+ mInterpNextChangeMessage = (gAgent.getTeleportState() == LLAgent::TELEPORT_NONE);
+ LL_DEBUGS("Windlight") << (mInterpNextChangeMessage ? "Crossed" : "Teleported")
+ << " to new region: " << region_uuid
+ << LL_ENDL;
+ requestRegionSettings();
+ }
+ else
+ {
+ LL_DEBUGS("Windlight") << "disregarding region change; interp: "
+ << (mInterpNextChangeMessage ? "true" : "false")
+ << " regionp: " << regionp
+ << " old: " << mCurRegionUUID
+ << " new: " << region_uuid
+ << LL_ENDL;
}
-
- // Clear locally modified region settings.
- mNewRegionPrefs.clear();
-
- // *TODO: clear environment settings of the previous region?
-
- // Request environment settings of the new region.
- LL_DEBUGS("Windlight") << "New viewer region: " << region_uuid << LL_ENDL;
- mCurRegionUUID = region_uuid;
- mInterpNextChangeMessage = interpolate;
- requestRegionSettings();
-
- // Let interested parties know agent region has been changed.
- mRegionChangeSignal();
}
diff --git a/indra/newview/llenvmanager.h b/indra/newview/llenvmanager.h
index ad56761bc7..c7877303fc 100755
--- a/indra/newview/llenvmanager.h
+++ b/indra/newview/llenvmanager.h
@@ -166,7 +166,6 @@ class LLEnvManagerNew : public LLSingleton<LLEnvManagerNew>
public:
typedef boost::signals2::signal<void()> prefs_change_signal_t;
typedef boost::signals2::signal<void()> region_settings_change_signal_t;
- typedef boost::signals2::signal<void()> region_change_signal_t;
typedef boost::signals2::signal<void(bool)> region_settings_applied_signal_t;
LLEnvManagerNew();
@@ -222,15 +221,12 @@ public:
bool sendRegionSettings(const LLEnvironmentSettings& new_settings);
boost::signals2::connection setPreferencesChangeCallback(const prefs_change_signal_t::slot_type& cb);
boost::signals2::connection setRegionSettingsChangeCallback(const region_settings_change_signal_t::slot_type& cb);
- boost::signals2::connection setRegionChangeCallback(const region_change_signal_t::slot_type& cb);
boost::signals2::connection setRegionSettingsAppliedCallback(const region_settings_applied_signal_t::slot_type& cb);
static bool canEditRegionSettings(); /// @return true if we have access to editing region environment
static const std::string getScopeString(LLEnvKey::EScope scope);
// Public callbacks.
- void onRegionCrossing();
- void onTeleport();
void onRegionSettingsResponse(const LLSD& content);
void onRegionSettingsApplyResponse(bool ok);
@@ -251,7 +247,7 @@ private:
bool useDefaultSky();
bool useDefaultWater();
- void onRegionChange(bool interpolate);
+ void onRegionChange();
/// Emitted when user environment preferences change.
prefs_change_signal_t mUsePrefsChangeSignal;
@@ -260,9 +256,6 @@ private:
region_settings_change_signal_t mRegionSettingsChangeSignal;
/// Emitted when agent region changes. Move to LLAgent?
- region_change_signal_t mRegionChangeSignal;
-
- /// Emitted when agent region changes. Move to LLAgent?
region_settings_applied_signal_t mRegionSettingsAppliedSignal;
LLEnvPrefs mUserPrefs; /// User environment preferences.
diff --git a/indra/newview/llfloatereditdaycycle.cpp b/indra/newview/llfloatereditdaycycle.cpp
index b63677b258..78e20e3bf0 100755
--- a/indra/newview/llfloatereditdaycycle.cpp
+++ b/indra/newview/llfloatereditdaycycle.cpp
@@ -145,7 +145,7 @@ void LLFloaterEditDayCycle::initCallbacks(void)
// Connect to env manager events.
LLEnvManagerNew& env_mgr = LLEnvManagerNew::instance();
env_mgr.setRegionSettingsChangeCallback(boost::bind(&LLFloaterEditDayCycle::onRegionSettingsChange, this));
- env_mgr.setRegionChangeCallback(boost::bind(&LLFloaterEditDayCycle::onRegionChange, this));
+ gAgent.addRegionChangedCallback(boost::bind(&LLFloaterEditDayCycle::onRegionChange, this));
env_mgr.setRegionSettingsAppliedCallback(boost::bind(&LLFloaterEditDayCycle::onRegionSettingsApplied, this, _1));
// Connect to day cycle manager events.
diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp
index 14e1a486d3..84921849d0 100755
--- a/indra/newview/llfloaterimsession.cpp
+++ b/indra/newview/llfloaterimsession.cpp
@@ -61,6 +61,9 @@
#include "llnotificationmanager.h"
#include "llautoreplace.h"
+const F32 ME_TYPING_TIMEOUT = 4.0f;
+const F32 OTHER_TYPING_TIMEOUT = 9.0f;
+
floater_showed_signal_t LLFloaterIMSession::sIMFloaterShowedSignal;
LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id)
@@ -75,7 +78,10 @@ LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id)
mTypingTimer(),
mTypingTimeoutTimer(),
mPositioned(false),
- mSessionInitialized(false)
+ mSessionInitialized(false),
+ mMeTypingTimer(),
+ mOtherTypingTimer(),
+ mImInfo()
{
mIsNearbyChat = false;
@@ -96,13 +102,31 @@ LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id)
void LLFloaterIMSession::refresh()
{
if (mMeTyping)
-{
+ {
+ // Send an additional Start Typing packet every ME_TYPING_TIMEOUT seconds
+ if (mMeTypingTimer.getElapsedTimeF32() > ME_TYPING_TIMEOUT && false == mShouldSendTypingState)
+ {
+ LL_DEBUGS("TypingMsgs") << "Send additional Start Typing packet" << LL_ENDL;
+ LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, TRUE);
+ mMeTypingTimer.reset();
+ }
+
// Time out if user hasn't typed for a while.
if (mTypingTimeoutTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS)
{
- setTyping(false);
+ setTyping(false);
+ LL_DEBUGS("TypingMsgs") << "Send stop typing due to timeout" << LL_ENDL;
}
}
+
+ // Clear <name is typing> message if no data received for OTHER_TYPING_TIMEOUT seconds
+ if (mOtherTyping && mOtherTypingTimer.getElapsedTimeF32() > OTHER_TYPING_TIMEOUT)
+ {
+ LL_DEBUGS("TypingMsgs") << "Received: is typing cleared due to timeout" << LL_ENDL;
+ removeTypingIndicator(mImInfo);
+ mOtherTyping = false;
+ }
+
}
// virtual
@@ -953,13 +977,21 @@ void LLFloaterIMSession::setTyping(bool typing)
// much network traffic. Only send in person-to-person IMs.
if ( mShouldSendTypingState && mDialog == IM_NOTHING_SPECIAL )
{
- // Still typing, send 'start typing' notification or
- // send 'stop typing' notification immediately
- if (!mMeTyping || mTypingTimer.getElapsedTimeF32() > 1.f)
+ if ( mMeTyping )
{
- LLIMModel::instance().sendTypingState(mSessionID,
- mOtherParticipantUUID, mMeTyping);
- mShouldSendTypingState = false;
+ if ( mTypingTimer.getElapsedTimeF32() > 1.f )
+ {
+ // Still typing, send 'start typing' notification
+ LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, TRUE);
+ mShouldSendTypingState = false;
+ mMeTypingTimer.reset();
+ }
+ }
+ else
+ {
+ // Send 'stop typing' notification immediately
+ LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, FALSE);
+ mShouldSendTypingState = false;
}
}
@@ -975,10 +1007,12 @@ void LLFloaterIMSession::setTyping(bool typing)
void LLFloaterIMSession::processIMTyping(const LLIMInfo* im_info, BOOL typing)
{
+ LL_DEBUGS("TypingMsgs") << "typing=" << typing << LL_ENDL;
if ( typing )
{
// other user started typing
addTypingIndicator(im_info);
+ mOtherTypingTimer.reset();
}
else
{
@@ -1202,10 +1236,40 @@ BOOL LLFloaterIMSession::inviteToSession(const uuid_vec_t& ids)
void LLFloaterIMSession::addTypingIndicator(const LLIMInfo* im_info)
{
+/* Operation of "<name> is typing" state machine:
+Not Typing state:
+
+ User types in P2P IM chat ... Send Start Typing, save Started time,
+ start Idle Timer (N seconds) go to Typing state
+
+Typing State:
+
+ User enters a non-return character: if Now - Started > ME_TYPING_TIMEOUT, send
+ Start Typing, restart Idle Timer
+ User enters a return character: stop Idle Timer, send IM and Stop
+ Typing, go to Not Typing state
+ Idle Timer expires: send Stop Typing, go to Not Typing state
+
+The recipient has a complementary state machine in which a Start Typing
+that is not followed by either an IM or another Start Typing within OTHER_TYPING_TIMEOUT
+seconds switches the sender out of typing state.
+
+This has the nice quality of being self-healing for lost start/stop
+messages while adding messages only for the (relatively rare) case of a
+user who types a very long message (one that takes more than ME_TYPING_TIMEOUT seconds
+to type).
+
+Note: OTHER_TYPING_TIMEOUT must be > ME_TYPING_TIMEOUT for proper operation of the state machine
+
+*/
+
// We may have lost a "stop-typing" packet, don't add it twice
if (im_info && !mOtherTyping)
{
mOtherTyping = true;
+ mOtherTypingTimer.reset();
+ // Save im_info so that removeTypingIndicator can be properly called because a timeout has occurred
+ mImInfo = im_info;
// Update speaker
LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
diff --git a/indra/newview/llfloaterimsession.h b/indra/newview/llfloaterimsession.h
index d6718843ca..2b9d06e744 100755
--- a/indra/newview/llfloaterimsession.h
+++ b/indra/newview/llfloaterimsession.h
@@ -187,6 +187,8 @@ private:
LLFrameTimer mTypingTimer;
LLFrameTimer mTypingTimeoutTimer;
bool mSessionNameUpdatedForTyping;
+ LLFrameTimer mMeTypingTimer;
+ LLFrameTimer mOtherTypingTimer;
bool mSessionInitialized;
LLSD mQueuedMsgsForInit;
@@ -196,6 +198,8 @@ private:
// connection to voice channel state change signal
boost::signals2::connection mVoiceChannelStateChangeConnection;
+
+ const LLIMInfo* mImInfo;
};
#endif // LL_FLOATERIMSESSION_H
diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp
index 6c8e81e563..b16ef6dd79 100755
--- a/indra/newview/llfloaterland.cpp
+++ b/indra/newview/llfloaterland.cpp
@@ -1791,10 +1791,15 @@ void LLPanelLandObjects::onCommitClean(LLUICtrl *caller, void* user_data)
LLParcel* parcel = lop->mParcel->getParcel();
if (parcel)
{
- lop->mOtherTime = atoi(lop->mCleanOtherObjectsTime->getText().c_str());
+ S32 return_time = atoi(lop->mCleanOtherObjectsTime->getText().c_str());
+ // Only send return time if it has changed
+ if (return_time != lop->mOtherTime)
+ {
+ lop->mOtherTime = return_time;
- parcel->setCleanOtherTime(lop->mOtherTime);
- send_other_clean_time_message(parcel->getLocalID(), lop->mOtherTime);
+ parcel->setCleanOtherTime(lop->mOtherTime);
+ send_other_clean_time_message(parcel->getLocalID(), lop->mOtherTime);
+ }
}
}
diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp
index 19cec55837..855836af7a 100755
--- a/indra/newview/llfloatermodelpreview.cpp
+++ b/indra/newview/llfloatermodelpreview.cpp
@@ -535,9 +535,16 @@ BOOL LLFloaterModelPreview::postBuild()
mUploadBtn = getChild<LLButton>("ok_btn");
mCalculateBtn = getChild<LLButton>("calculate_btn");
- mCalculateBtn->setClickedCallback(boost::bind(&LLFloaterModelPreview::onClickCalculateBtn, this));
+ if (LLConvexDecomposition::getInstance() != NULL)
+ {
+ mCalculateBtn->setClickedCallback(boost::bind(&LLFloaterModelPreview::onClickCalculateBtn, this));
- toggleCalculateButton(true);
+ toggleCalculateButton(true);
+ }
+ else
+ {
+ mCalculateBtn->setEnabled(false);
+ }
return TRUE;
}
diff --git a/indra/newview/llfloaterpathfindingconsole.cpp b/indra/newview/llfloaterpathfindingconsole.cpp
index 298454724b..161259d049 100755
--- a/indra/newview/llfloaterpathfindingconsole.cpp
+++ b/indra/newview/llfloaterpathfindingconsole.cpp
@@ -34,11 +34,11 @@
#include <boost/signals2.hpp>
+#include "llagent.h"
#include "llbutton.h"
#include "llcheckboxctrl.h"
#include "llcombobox.h"
#include "llcontrol.h"
-#include "llenvmanager.h"
#include "llfloaterpathfindingcharacters.h"
#include "llfloaterpathfindinglinksets.h"
#include "llfloaterreg.h"
@@ -224,7 +224,7 @@ void LLFloaterPathfindingConsole::onOpen(const LLSD& pKey)
if (!mRegionBoundarySlot.connected())
{
- mRegionBoundarySlot = LLEnvManagerNew::instance().setRegionChangeCallback(boost::bind(&LLFloaterPathfindingConsole::onRegionBoundaryCross, this));
+ mRegionBoundarySlot = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterPathfindingConsole::onRegionBoundaryCross, this));
}
if (!mTeleportFailedSlot.connected())
diff --git a/indra/newview/llfloaterpathfindingobjects.cpp b/indra/newview/llfloaterpathfindingobjects.cpp
index 20c1215bcb..d72ee073e1 100755
--- a/indra/newview/llfloaterpathfindingobjects.cpp
+++ b/indra/newview/llfloaterpathfindingobjects.cpp
@@ -41,7 +41,6 @@
#include "llavatarnamecache.h"
#include "llbutton.h"
#include "llcheckboxctrl.h"
-#include "llenvmanager.h"
#include "llfloater.h"
#include "llfontgl.h"
#include "llnotifications.h"
@@ -85,7 +84,7 @@ void LLFloaterPathfindingObjects::onOpen(const LLSD &pKey)
if (!mRegionBoundaryCrossingSlot.connected())
{
- mRegionBoundaryCrossingSlot = LLEnvManagerNew::getInstance()->setRegionChangeCallback(boost::bind(&LLFloaterPathfindingObjects::onRegionBoundaryCrossed, this));
+ mRegionBoundaryCrossingSlot = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterPathfindingObjects::onRegionBoundaryCrossed, this));
}
if (!mGodLevelChangeSlot.connected())
diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp
index 66bf49331b..73c0963a1d 100755
--- a/indra/newview/llfloaterregioninfo.cpp
+++ b/indra/newview/llfloaterregioninfo.cpp
@@ -91,6 +91,7 @@
#include "lltrans.h"
#include "llagentui.h"
#include "llmeshrepository.h"
+#include "llfloaterregionrestarting.h"
const S32 TERRAIN_TEXTURE_COUNT = 4;
const S32 CORNER_COUNT = 4;
@@ -219,7 +220,7 @@ BOOL LLFloaterRegionInfo::postBuild()
&processEstateOwnerRequest);
// Request region info when agent region changes.
- LLEnvManagerNew::instance().setRegionChangeCallback(boost::bind(&LLFloaterRegionInfo::requestRegionInfo, this));
+ gAgent.addRegionChangedCallback(boost::bind(&LLFloaterRegionInfo::requestRegionInfo, this));
return TRUE;
}
diff --git a/indra/newview/llfloaterregionrestarting.cpp b/indra/newview/llfloaterregionrestarting.cpp
new file mode 100644
index 0000000000..95d4265bb4
--- /dev/null
+++ b/indra/newview/llfloaterregionrestarting.cpp
@@ -0,0 +1,176 @@
+/**
+ * @file llfloaterregionrestarting.cpp
+ * @brief Shows countdown timer during region restart
+ *
+ * $LicenseInfo:firstyear=2006&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 "llfloaterregionrestarting.h"
+
+#include "llfloaterreg.h"
+#include "lluictrl.h"
+#include "llagent.h"
+#include "llagentcamera.h"
+#include "llviewerwindow.h"
+
+static S32 sSeconds;
+static U32 sShakeState;
+
+LLFloaterRegionRestarting::LLFloaterRegionRestarting(const LLSD& key) :
+ LLFloater(key),
+ LLEventTimer(1)
+{
+ mName = (std::string)key["NAME"];
+ sSeconds = (LLSD::Integer)key["SECONDS"];
+}
+
+LLFloaterRegionRestarting::~LLFloaterRegionRestarting()
+{
+ mRegionChangedConnection.disconnect();
+}
+
+BOOL LLFloaterRegionRestarting::postBuild()
+{
+ mRegionChangedConnection = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterRegionRestarting::regionChange, this));
+
+ LLStringUtil::format_map_t args;
+ std::string text;
+
+ args["[NAME]"] = mName;
+ text = getString("RegionName", args);
+ LLTextBox* textbox = getChild<LLTextBox>("region_name");
+ textbox->setValue(text);
+
+ sShakeState = SHAKE_START;
+
+ refresh();
+
+ return TRUE;
+}
+
+void LLFloaterRegionRestarting::regionChange()
+{
+ close();
+}
+
+BOOL LLFloaterRegionRestarting::tick()
+{
+ refresh();
+
+ return FALSE;
+}
+
+void LLFloaterRegionRestarting::refresh()
+{
+ LLStringUtil::format_map_t args;
+ std::string text;
+
+ args["[SECONDS]"] = llformat("%d", sSeconds);
+ getChild<LLTextBox>("restart_seconds")->setValue(getString("RestartSeconds", args));
+
+ sSeconds = sSeconds - 1;
+ if(sSeconds < 0.0)
+ {
+ sSeconds = 0;
+ }
+}
+
+void LLFloaterRegionRestarting::draw()
+{
+ LLFloater::draw();
+
+ const F32 SHAKE_INTERVAL = 0.025;
+ const F32 SHAKE_TOTAL_DURATION = 1.8; // the length of the default alert tone for this
+ const F32 SHAKE_INITIAL_MAGNITUDE = 1.5;
+ const F32 SHAKE_HORIZONTAL_BIAS = 0.25;
+ F32 time_shaking;
+
+ if(SHAKE_START == sShakeState)
+ {
+ mShakeTimer.setTimerExpirySec(SHAKE_INTERVAL);
+ sShakeState = SHAKE_LEFT;
+ mShakeIterations = 0;
+ mShakeMagnitude = SHAKE_INITIAL_MAGNITUDE;
+ }
+
+ if(SHAKE_DONE != sShakeState && mShakeTimer.hasExpired())
+ {
+ gAgentCamera.unlockView();
+
+ switch(sShakeState)
+ {
+ case SHAKE_LEFT:
+ gAgentCamera.setPanLeftKey(mShakeMagnitude * SHAKE_HORIZONTAL_BIAS);
+ sShakeState = SHAKE_UP;
+ break;
+
+ case SHAKE_UP:
+ gAgentCamera.setPanUpKey(mShakeMagnitude);
+ sShakeState = SHAKE_RIGHT;
+ break;
+
+ case SHAKE_RIGHT:
+ gAgentCamera.setPanRightKey(mShakeMagnitude * SHAKE_HORIZONTAL_BIAS);
+ sShakeState = SHAKE_DOWN;
+ break;
+
+ case SHAKE_DOWN:
+ gAgentCamera.setPanDownKey(mShakeMagnitude);
+ mShakeIterations++;
+ time_shaking = SHAKE_INTERVAL * (mShakeIterations * 4 /* left, up, right, down */);
+ if(SHAKE_TOTAL_DURATION <= time_shaking)
+ {
+ sShakeState = SHAKE_DONE;
+ mShakeMagnitude = 0.0;
+ }
+ else
+ {
+ sShakeState = SHAKE_LEFT;
+ F32 percent_done_shaking = (SHAKE_TOTAL_DURATION - time_shaking) / SHAKE_TOTAL_DURATION;
+ mShakeMagnitude = SHAKE_INITIAL_MAGNITUDE * (percent_done_shaking * percent_done_shaking); // exponential decay
+ }
+ break;
+
+ default:
+ break;
+ }
+ mShakeTimer.setTimerExpirySec(SHAKE_INTERVAL);
+ }
+}
+
+void LLFloaterRegionRestarting::close()
+{
+ LLFloaterRegionRestarting* floaterp = LLFloaterReg::findTypedInstance<LLFloaterRegionRestarting>("region_restarting");
+
+ if (floaterp)
+ {
+ floaterp->closeFloater();
+ }
+}
+
+void LLFloaterRegionRestarting::updateTime(S32 time)
+{
+ sSeconds = time;
+ sShakeState = SHAKE_START;
+}
diff --git a/indra/newview/llfloaterregionrestarting.h b/indra/newview/llfloaterregionrestarting.h
new file mode 100644
index 0000000000..46416db2c8
--- /dev/null
+++ b/indra/newview/llfloaterregionrestarting.h
@@ -0,0 +1,69 @@
+/**
+ * @file llfloaterregionrestarting.h
+ * @brief Shows countdown timer during region restart
+ *
+ * $LicenseInfo:firstyear=2006&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$
+ */
+
+#ifndef LL_LLFLOATERREGIONRESTARTING_H
+#define LL_LLFLOATERREGIONRESTARTING_H
+
+#include "llfloater.h"
+#include "lltextbox.h"
+#include "lleventtimer.h"
+
+class LLFloaterRegionRestarting : public LLFloater, public LLEventTimer
+{
+ friend class LLFloaterReg;
+
+public:
+ static void close();
+ static void updateTime(S32 time);
+
+private:
+ LLFloaterRegionRestarting(const LLSD& key);
+ virtual ~LLFloaterRegionRestarting();
+ virtual BOOL postBuild();
+ virtual BOOL tick();
+ virtual void refresh();
+ virtual void draw();
+ virtual void regionChange();
+
+ std::string mName;
+ U32 mShakeIterations;
+ F32 mShakeMagnitude;
+ LLTimer mShakeTimer;
+
+ boost::signals2::connection mRegionChangedConnection;
+
+ enum
+ {
+ SHAKE_START,
+ SHAKE_LEFT,
+ SHAKE_UP,
+ SHAKE_RIGHT,
+ SHAKE_DOWN,
+ SHAKE_DONE
+ };
+};
+
+#endif // LL_LLFLOATERREGIONRESTARTING_H
diff --git a/indra/newview/llfloaterwebcontent.cpp b/indra/newview/llfloaterwebcontent.cpp
index 76b73fcf29..68dbb5ae33 100755
--- a/indra/newview/llfloaterwebcontent.cpp
+++ b/indra/newview/llfloaterwebcontent.cpp
@@ -372,7 +372,10 @@ void LLFloaterWebContent::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent
}
else if(event == MEDIA_EVENT_GEOMETRY_CHANGE)
{
- geometryChanged(self->getGeometryX(), self->getGeometryY(), self->getGeometryWidth(), self->getGeometryHeight());
+ if (mCurrentURL.find("facebook.com/dialog/oauth") == std::string::npos) // HACK to fix ACME-1317 - Cho
+ {
+ geometryChanged(self->getGeometryX(), self->getGeometryY(), self->getGeometryWidth(), self->getGeometryHeight());
+ }
}
else if(event == MEDIA_EVENT_STATUS_TEXT_CHANGED )
{
diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp
index 137b5446cf..cb637c7162 100755
--- a/indra/newview/llfloaterworldmap.cpp
+++ b/indra/newview/llfloaterworldmap.cpp
@@ -627,8 +627,8 @@ void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global)
if (!sim_info)
{
// We haven't found a region for that point yet, leave the tracking to the world map
- LLWorldMap::getInstance()->setTracking(pos_global);
LLTracker::stopTracking(NULL);
+ LLWorldMap::getInstance()->setTracking(pos_global);
S32 world_x = S32(pos_global.mdV[0] / 256);
S32 world_y = S32(pos_global.mdV[1] / 256);
LLWorldMapMessage::getInstance()->sendMapBlockRequest(world_x, world_y, world_x, world_y, true);
@@ -643,9 +643,9 @@ void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global)
{
// Down region. Show the blue circle of death!
// i.e. let the world map that this and tell it it's invalid
+ LLTracker::stopTracking(NULL);
LLWorldMap::getInstance()->setTracking(pos_global);
LLWorldMap::getInstance()->setTrackingInvalid();
- LLTracker::stopTracking(NULL);
setDefaultBtn("");
// clicked on a down region - turn off coord display
@@ -665,8 +665,8 @@ void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global)
std::string tooltip("");
mTrackedStatus = LLTracker::TRACKING_LOCATION;
- LLTracker::trackLocation(pos_global, full_name, tooltip);
LLWorldMap::getInstance()->cancelTracking(); // The floater is taking over the tracking
+ LLTracker::trackLocation(pos_global, full_name, tooltip);
LLVector3d coord_pos = LLTracker::getTrackedPositionGlobal();
updateTeleportCoordsDisplay( coord_pos );
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index 80ef506272..44943d8722 100755
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -74,6 +74,7 @@
#include "llvoavatarself.h"
#include "llwearablelist.h"
#include "lllandmarkactions.h"
+#include "llpanellandmarks.h"
void copy_slurl_to_clipboard_callback_inv(const std::string& slurl);
@@ -1449,6 +1450,38 @@ void LLItemBridge::performAction(LLInventoryModel* model, std::string action)
}
}
}
+ else if ("show_on_map" == action)
+ {
+ doActionOnCurSelectedLandmark(boost::bind(&LLItemBridge::doShowOnMap, this, _1));
+ }
+}
+
+void LLItemBridge::doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb)
+{
+ LLViewerInventoryItem* cur_item = getItem();
+ if(cur_item && cur_item->getInventoryType() == LLInventoryType::IT_LANDMARK)
+ {
+ LLLandmark* landmark = LLLandmarkActions::getLandmark(cur_item->getUUID(), cb);
+ if (landmark)
+ {
+ cb(landmark);
+ }
+ }
+}
+
+void LLItemBridge::doShowOnMap(LLLandmark* landmark)
+{
+ LLVector3d landmark_global_pos;
+ // landmark has already been tested for NULL by calling routine
+ if (landmark->getGlobalPos(landmark_global_pos))
+ {
+ LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance();
+ if (!landmark_global_pos.isExactlyZero() && worldmap_instance)
+ {
+ worldmap_instance->trackLocation(landmark_global_pos);
+ LLFloaterReg::showInstance("world_map", "center");
+ }
+ }
}
void copy_slurl_to_clipboard_callback_inv(const std::string& slurl)
@@ -4580,6 +4613,7 @@ void LLLandmarkBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
items.push_back(std::string("Landmark Separator"));
items.push_back(std::string("url_copy"));
items.push_back(std::string("About Landmark"));
+ items.push_back(std::string("show_on_map"));
}
// Disable "About Landmark" menu item for
diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h
index 517153e171..bc875e8f37 100755
--- a/indra/newview/llinventorybridge.h
+++ b/indra/newview/llinventorybridge.h
@@ -36,6 +36,7 @@
#include "llviewercontrol.h"
#include "llviewerwearable.h"
#include "lltooldraganddrop.h"
+#include "lllandmarklist.h"
class LLInventoryFilter;
class LLInventoryPanel;
@@ -239,7 +240,10 @@ protected:
BOOL confirmRemoveItem(const LLSD& notification, const LLSD& response);
virtual BOOL isItemPermissive() const;
virtual void buildDisplayName() const;
+ void doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb);
+private:
+ void doShowOnMap(LLLandmark* landmark);
};
class LLFolderBridge : public LLInvFVBridge
diff --git a/indra/newview/lllocationinputctrl.cpp b/indra/newview/lllocationinputctrl.cpp
index 5022dba934..dbdff11f11 100755
--- a/indra/newview/lllocationinputctrl.cpp
+++ b/indra/newview/lllocationinputctrl.cpp
@@ -407,14 +407,14 @@ LLLocationInputCtrl::LLLocationInputCtrl(const LLLocationInputCtrl::Params& p)
// - Make the "Add landmark" button updated when either current parcel gets changed
// or a landmark gets created or removed from the inventory.
// - Update the location string on parcel change.
- mParcelMgrConnection = LLViewerParcelMgr::getInstance()->addAgentParcelChangedCallback(
+ mParcelMgrConnection = gAgent.addParcelChangedCallback(
boost::bind(&LLLocationInputCtrl::onAgentParcelChange, this));
// LLLocationHistory instance is being created before the location input control, so we have to update initial state of button manually.
mButton->setEnabled(LLLocationHistory::instance().getItemCount() > 0);
mLocationHistoryConnection = LLLocationHistory::getInstance()->setChangedCallback(
boost::bind(&LLLocationInputCtrl::onLocationHistoryChanged, this,_1));
- mRegionCrossingSlot = LLEnvManagerNew::getInstance()->setRegionChangeCallback(boost::bind(&LLLocationInputCtrl::onRegionBoundaryCrossed, this));
+ mRegionCrossingSlot = gAgent.addRegionChangedCallback(boost::bind(&LLLocationInputCtrl::onRegionBoundaryCrossed, this));
createNavMeshStatusListenerForCurrentRegion();
mRemoveLandmarkObserver = new LLRemoveLandmarkObserver(this);
diff --git a/indra/newview/llmenuoptionpathfindingrebakenavmesh.cpp b/indra/newview/llmenuoptionpathfindingrebakenavmesh.cpp
index a567d1217a..8879cfd7fb 100755
--- a/indra/newview/llmenuoptionpathfindingrebakenavmesh.cpp
+++ b/indra/newview/llmenuoptionpathfindingrebakenavmesh.cpp
@@ -79,7 +79,7 @@ void LLMenuOptionPathfindingRebakeNavmesh::initialize()
if ( !mRegionCrossingSlot.connected() )
{
- mRegionCrossingSlot = LLEnvManagerNew::getInstance()->setRegionChangeCallback(boost::bind(&LLMenuOptionPathfindingRebakeNavmesh::handleRegionBoundaryCrossed, this));
+ mRegionCrossingSlot = gAgent.addRegionChangedCallback(boost::bind(&LLMenuOptionPathfindingRebakeNavmesh::handleRegionBoundaryCrossed, this));
}
if (!mAgentStateSlot.connected())
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index 2e02805c02..5afd2cb329 100755
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -5,7 +5,7 @@
*
* $LicenseInfo:firstyear=2005&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2010-2014, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -38,11 +38,13 @@
#include "llcallbacklist.h"
#include "llcurl.h"
#include "lldatapacker.h"
+#include "lldeadmantimer.h"
#include "llfloatermodelpreview.h"
#include "llfloaterperms.h"
#include "lleconomy.h"
#include "llimagej2c.h"
#include "llhost.h"
+#include "llmath.h"
#include "llnotificationsutil.h"
#include "llsd.h"
#include "llsdutil_math.h"
@@ -52,6 +54,7 @@
#include "llviewercontrol.h"
#include "llviewerinventory.h"
#include "llviewermenufile.h"
+#include "llviewermessage.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "llviewertexturelist.h"
@@ -65,6 +68,9 @@
#include "llfoldertype.h"
#include "llviewerparcelmgr.h"
#include "lluploadfloaterobservers.h"
+#include "bufferarray.h"
+#include "bufferstream.h"
+#include "llfasttimer.h"
#include "boost/lexical_cast.hpp"
@@ -72,11 +78,296 @@
#include "netdb.h"
#endif
-#include <queue>
+
+// Purpose
+//
+// The purpose of this module is to provide access between the viewer
+// and the asset system as regards to mesh objects.
+//
+// * High-throughput download of mesh assets from servers while
+// following best industry practices for network profile.
+// * Reliable expensing and upload of new mesh assets.
+// * Recovery and retry from errors when appropriate.
+// * Decomposition of mesh assets for preview and uploads.
+// * And most important: all of the above without exposing the
+// main thread to stalls due to deep processing or thread
+// locking actions. In particular, the following operations
+// on LLMeshRepository are very averse to any stalls:
+// * loadMesh
+// * getMeshHeader (For structural details, see:
+// http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format)
+// * notifyLoadedMeshes
+// * getSkinInfo
+//
+// Threads
+//
+// main Main rendering thread, very sensitive to locking and other stalls
+// repo Overseeing worker thread associated with the LLMeshRepoThread class
+// decom Worker thread for mesh decomposition requests
+// core HTTP worker thread: does the work but doesn't intrude here
+// uploadN 0-N temporary mesh upload threads (0-1 in practice)
+//
+// Sequence of Operations
+//
+// What follows is a description of the retrieval of one LOD for
+// a new mesh object. Work is performed by a series of short, quick
+// actions distributed over a number of threads. Each is meant
+// to proceed without stalling and the whole forms a deep request
+// pipeline to achieve throughput. Ellipsis indicates a return
+// or break in processing which is resumed elsewhere.
+//
+// main thread repo thread (run() method)
+//
+// loadMesh() invoked to request LOD
+// append LODRequest to mPendingRequests
+// ...
+// other mesh requests may be made
+// ...
+// notifyLoadedMeshes() invoked to stage work
+// append HeaderRequest to mHeaderReqQ
+// ...
+// scan mHeaderReqQ
+// issue 4096-byte GET for header
+// ...
+// onCompleted() invoked for GET
+// data copied
+// headerReceived() invoked
+// LLSD parsed
+// mMeshHeader, mMeshHeaderSize updated
+// scan mPendingLOD for LOD request
+// push LODRequest to mLODReqQ
+// ...
+// scan mLODReqQ
+// fetchMeshLOD() invoked
+// issue Byte-Range GET for LOD
+// ...
+// onCompleted() invoked for GET
+// data copied
+// lodReceived() invoked
+// unpack data into LLVolume
+// append LoadedMesh to mLoadedQ
+// ...
+// notifyLoadedMeshes() invoked again
+// scan mLoadedQ
+// notifyMeshLoaded() for LOD
+// setMeshAssetLoaded() invoked for system volume
+// notifyMeshLoaded() invoked for each interested object
+// ...
+//
+// Mutexes
+//
+// LLMeshRepository::mMeshMutex
+// LLMeshRepoThread::mMutex
+// LLMeshRepoThread::mHeaderMutex
+// LLMeshRepoThread::mSignal (LLCondition)
+// LLPhysicsDecomp::mSignal (LLCondition)
+// LLPhysicsDecomp::mMutex
+// LLMeshUploadThread::mMutex
+//
+// Mutex Order Rules
+//
+// 1. LLMeshRepoThread::mMutex before LLMeshRepoThread::mHeaderMutex
+// 2. LLMeshRepository::mMeshMutex before LLMeshRepoThread::mMutex
+// (There are more rules, haven't been extracted.)
+//
+// Data Member Access/Locking
+//
+// Description of how shared access to static and instance data
+// members is performed. Each member is followed by the name of
+// the mutex, if any, covering the data and then a list of data
+// access models each of which is a triplet of the following form:
+//
+// {ro, wo, rw}.{main, repo, any}.{mutex, none}
+// Type of access: read-only, write-only, read-write.
+// Accessing thread or 'any'
+// Relevant mutex held during access (several may be held) or 'none'
+//
+// A careful eye will notice some unsafe operations. Many of these
+// have an alibi of some form. Several types of alibi are identified
+// and listed here:
+//
+// [0] No alibi. Probably unsafe.
+// [1] Single-writer, self-consistent readers. Old data must
+// be tolerated by any reader but data will come true eventually.
+// [2] Like [1] but provides a hint about thread state. These
+// may be unsafe.
+// [3] empty() check outside of lock. Can me made safish when
+// done in double-check lock style. But this depends on
+// std:: implementation and memory model.
+// [4] Appears to be covered by a mutex but doesn't need one.
+// [5] Read of a double-checked lock.
+//
+// So, in addition to documentation, take this as a to-do/review
+// list and see if you can improve things. For porters to non-x86
+// architectures, the weaker memory models will make these platforms
+// probabilistically more susceptible to hitting race conditions.
+// True here and in other multi-thread code such as texture fetching.
+// (Strong memory models make weak programmers. Weak memory models
+// make strong programmers. Ref: arm, ppc, mips, alpha)
+//
+// LLMeshRepository:
+//
+// sBytesReceived none rw.repo.none, ro.main.none [1]
+// sMeshRequestCount "
+// sHTTPRequestCount "
+// sHTTPLargeRequestCount "
+// sHTTPRetryCount "
+// sHTTPErrorCount "
+// sLODPending mMeshMutex [4] rw.main.mMeshMutex
+// sLODProcessing Repo::mMutex rw.any.Repo::mMutex
+// sCacheBytesRead none rw.repo.none, ro.main.none [1]
+// sCacheBytesWritten "
+// sCacheReads "
+// sCacheWrites "
+// mLoadingMeshes mMeshMutex [4] rw.main.none, rw.any.mMeshMutex
+// mSkinMap none rw.main.none
+// mDecompositionMap none rw.main.none
+// mPendingRequests mMeshMutex [4] rw.main.mMeshMutex
+// mLoadingSkins mMeshMutex [4] rw.main.mMeshMutex
+// mPendingSkinRequests mMeshMutex [4] rw.main.mMeshMutex
+// mLoadingDecompositions mMeshMutex [4] rw.main.mMeshMutex
+// mPendingDecompositionRequests mMeshMutex [4] rw.main.mMeshMutex
+// mLoadingPhysicsShapes mMeshMutex [4] rw.main.mMeshMutex
+// mPendingPhysicsShapeRequests mMeshMutex [4] rw.main.mMeshMutex
+// mUploads none rw.main.none (upload thread accessing objects)
+// mUploadWaitList none rw.main.none (upload thread accessing objects)
+// mInventoryQ mMeshMutex [4] rw.main.mMeshMutex, ro.main.none [5]
+// mUploadErrorQ mMeshMutex rw.main.mMeshMutex, rw.any.mMeshMutex
+// mGetMeshVersion none rw.main.none
+//
+// LLMeshRepoThread:
+//
+// sActiveHeaderRequests mMutex rw.any.mMutex, ro.repo.none [1]
+// sActiveLODRequests mMutex rw.any.mMutex, ro.repo.none [1]
+// sMaxConcurrentRequests mMutex wo.main.none, ro.repo.none, ro.main.mMutex
+// mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0]
+// mMeshHeaderSize mHeaderMutex rw.repo.mHeaderMutex
+// mSkinRequests mMutex rw.repo.mMutex, ro.repo.none [5]
+// mSkinInfoQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0])
+// mDecompositionRequests mMutex rw.repo.mMutex, ro.repo.none [5]
+// mPhysicsShapeRequests mMutex rw.repo.mMutex, ro.repo.none [5]
+// mDecompositionQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0])
+// mHeaderReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex
+// mLODReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex
+// mUnavailableQ mMutex rw.repo.none [0], ro.main.none [5], rw.main.mMutex
+// mLoadedQ mMutex rw.repo.mMutex, ro.main.none [5], rw.main.mMutex
+// mPendingLOD mMutex rw.repo.mMutex, rw.any.mMutex
+// mGetMeshCapability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0])
+// mGetMesh2Capability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0])
+// mGetMeshVersion mMutex rw.main.mMutex, ro.repo.mMutex
+// mHttp* none rw.repo.none
+//
+// LLMeshUploadThread:
+//
+// mDiscarded mMutex rw.main.mMutex, ro.uploadN.none [1]
+// ... more ...
+//
+// QA/Development Testing
+//
+// Debug variable 'MeshUploadFakeErrors' takes a mask of bits that will
+// simulate an error on fee query or upload. Defined bits are:
+//
+// 0x01 Simulate application error on fee check reading
+// response body from file "fake_upload_error.xml"
+// 0x02 Same as 0x01 but for actual upload attempt.
+// 0x04 Simulate a transport problem on fee check with a
+// locally-generated 500 status.
+// 0x08 As with 0x04 but for the upload operation.
+//
+// For major changes, see the LL_MESH_FASTTIMER_ENABLE below and
+// instructions for looking for frame stalls using fast timers.
+//
+// *TODO: Work list for followup actions:
+// * Review anything marked as unsafe above, verify if there are real issues.
+// * See if we can put ::run() into a hard sleep. May not actually perform better
+// than the current scheme so be prepared for disappointment. You'll likely
+// need to introduce a condition variable class that references a mutex in
+// methods rather than derives from mutex which isn't correct.
+// * On upload failures, make more information available to the alerting
+// dialog. Get the structured information going into the log into a
+// tree there.
+// * Header parse failures come without much explanation. Elaborate.
+// * Work queue for uploads? Any need for this or is the current scheme good
+// enough?
+// * Various temp buffers used in VFS I/O might be allocated once or even
+// statically. Look for some wins here.
+// * Move data structures holding mesh data used by main thread into main-
+// thread-only access so that no locking is needed. May require duplication
+// of some data so that worker thread has a minimal data set to guide
+// operations.
+//
+// --------------------------------------------------------------------------
+// Development/Debug/QA Tools
+//
+// Enable here or in build environment to get fasttimer data on mesh fetches.
+//
+// Typically, this is used to perform A/B testing using the
+// fasttimer console (shift-ctrl-9). This is done by looking
+// for stalls due to lock contention between the main thread
+// and the repository and HTTP code. In a release viewer,
+// these appear as ping-time or worse spikes in frame time.
+// With this instrumentation enabled, a stall will appear
+// under the 'Mesh Fetch' timer which will be either top-level
+// or under 'Render' time.
+
+#ifndef LL_MESH_FASTTIMER_ENABLE
+#define LL_MESH_FASTTIMER_ENABLE 1
+#endif
+#if LL_MESH_FASTTIMER_ENABLE
+static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch");
+
+#define MESH_FASTTIMER_DEFBLOCK LLFastTimer meshtimer(FTM_MESH_FETCH)
+#else
+#define MESH_FASTTIMER_DEFBLOCK
+#endif // LL_MESH_FASTTIMER_ENABLE
+
+
+// Random failure testing for development/QA.
+//
+// Set the MESH_*_FAILED macros to either 'false' or to
+// an invocation of MESH_RANDOM_NTH_TRUE() with some
+// suitable number. In production, all must be false.
+//
+// Example:
+// #define MESH_HTTP_RESPONSE_FAILED MESH_RANDOM_NTH_TRUE(9)
+
+// 1-in-N calls will test true
+#define MESH_RANDOM_NTH_TRUE(_N) ( ll_rand(S32(_N)) == 0 )
+
+#define MESH_HTTP_RESPONSE_FAILED false
+#define MESH_HEADER_PROCESS_FAILED false
+#define MESH_LOD_PROCESS_FAILED false
+#define MESH_SKIN_INFO_PROCESS_FAILED false
+#define MESH_DECOMP_PROCESS_FAILED false
+#define MESH_PHYS_SHAPE_PROCESS_FAILED false
+
+// --------------------------------------------------------------------------
+
LLMeshRepository gMeshRepo;
-const U32 MAX_MESH_REQUESTS_PER_SECOND = 100;
+const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space
+const S32 REQUEST_HIGH_WATER_MIN = 32; // Limits for GetMesh regions
+const S32 REQUEST_HIGH_WATER_MAX = 150; // Should remain under 2X throttle
+const S32 REQUEST_LOW_WATER_MIN = 16;
+const S32 REQUEST_LOW_WATER_MAX = 75;
+const S32 REQUEST2_HIGH_WATER_MIN = 32; // Limits for GetMesh2 regions
+const S32 REQUEST2_HIGH_WATER_MAX = 80;
+const S32 REQUEST2_LOW_WATER_MIN = 16;
+const S32 REQUEST2_LOW_WATER_MAX = 40;
+const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue
+const long SMALL_MESH_XFER_TIMEOUT = 120L; // Seconds to complete xfer, small mesh downloads
+const long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large downloads
+
+// Would normally like to retry on uploads as some
+// retryable failures would be recoverable. Unfortunately,
+// the mesh service is using 500 (retryable) rather than
+// 400/bad request (permanent) for a bad payload and
+// retrying that just leads to revocation of the one-shot
+// cap which then produces a 404 on retry destroying some
+// (occasionally) useful error information. We'll leave
+// upload retries to the user as in the past. SH-4667.
+const long UPLOAD_RETRY_LIMIT = 0L;
// Maximum mesh version to support. Three least significant digits are reserved for the minor version,
// with major version changes indicating a format change that is not backwards compatible and should not
@@ -87,35 +378,45 @@ const U32 MAX_MESH_REQUESTS_PER_SECOND = 100;
const S32 MAX_MESH_VERSION = 999;
U32 LLMeshRepository::sBytesReceived = 0;
+U32 LLMeshRepository::sMeshRequestCount = 0;
U32 LLMeshRepository::sHTTPRequestCount = 0;
+U32 LLMeshRepository::sHTTPLargeRequestCount = 0;
U32 LLMeshRepository::sHTTPRetryCount = 0;
+U32 LLMeshRepository::sHTTPErrorCount = 0;
U32 LLMeshRepository::sLODProcessing = 0;
U32 LLMeshRepository::sLODPending = 0;
U32 LLMeshRepository::sCacheBytesRead = 0;
U32 LLMeshRepository::sCacheBytesWritten = 0;
-U32 LLMeshRepository::sPeakKbps = 0;
-
+U32 LLMeshRepository::sCacheReads = 0;
+U32 LLMeshRepository::sCacheWrites = 0;
+U32 LLMeshRepository::sMaxLockHoldoffs = 0;
-const U32 MAX_TEXTURE_UPLOAD_RETRIES = 5;
+LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, false); // true -> gather cpu metrics
+
static S32 dump_num = 0;
std::string make_dump_name(std::string prefix, S32 num)
{
return prefix + boost::lexical_cast<std::string>(num) + std::string(".xml");
-
}
void dump_llsd_to_file(const LLSD& content, std::string filename);
LLSD llsd_from_file(std::string filename);
-std::string header_lod[] =
+const std::string header_lod[] =
{
"lowest_lod",
"low_lod",
"medium_lod",
"high_lod"
};
+const char * const LOG_MESH = "Mesh";
+// Static data and functions to measure mesh load
+// time metrics for a new region scene.
+static unsigned int metrics_teleport_start_count = 0;
+boost::signals2::connection metrics_teleport_started_signal;
+static void teleport_started();
//get the number of bytes resident in memory for given volume
U32 get_volume_memory_size(const LLVolume* volume)
@@ -197,200 +498,228 @@ void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res,
}
}
-S32 LLMeshRepoThread::sActiveHeaderRequests = 0;
-S32 LLMeshRepoThread::sActiveLODRequests = 0;
+volatile S32 LLMeshRepoThread::sActiveHeaderRequests = 0;
+volatile S32 LLMeshRepoThread::sActiveLODRequests = 0;
U32 LLMeshRepoThread::sMaxConcurrentRequests = 1;
-
-class LLMeshHeaderResponder : public LLCurl::Responder
+S32 LLMeshRepoThread::sRequestLowWater = REQUEST2_LOW_WATER_MIN;
+S32 LLMeshRepoThread::sRequestHighWater = REQUEST2_HIGH_WATER_MIN;
+S32 LLMeshRepoThread::sRequestWaterLevel = 0;
+
+// Base handler class for all mesh users of llcorehttp.
+// This is roughly equivalent to a Responder class in
+// traditional LL code. The base is going to perform
+// common response/data handling in the inherited
+// onCompleted() method. Derived classes, one for each
+// type of HTTP action, define processData() and
+// processFailure() methods to customize handling and
+// error messages.
+//
+// LLCore::HttpHandler
+// LLMeshHandlerBase
+// LLMeshHeaderHandler
+// LLMeshLODHandler
+// LLMeshSkinInfoHandler
+// LLMeshDecompositionHandler
+// LLMeshPhysicsShapeHandler
+// LLMeshUploadThread
+
+class LLMeshHandlerBase : public LLCore::HttpHandler
{
public:
- LLVolumeParams mMeshParams;
- bool mProcessed;
+ LLMeshHandlerBase()
+ : LLCore::HttpHandler(),
+ mMeshParams(),
+ mProcessed(false),
+ mHttpHandle(LLCORE_HTTP_HANDLE_INVALID)
+ {}
+
+ virtual ~LLMeshHandlerBase()
+ {}
+
+protected:
+ LLMeshHandlerBase(const LLMeshHandlerBase &); // Not defined
+ void operator=(const LLMeshHandlerBase &); // Not defined
+
+public:
+ virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response);
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size) = 0;
+ virtual void processFailure(LLCore::HttpStatus status) = 0;
+
+public:
+ LLVolumeParams mMeshParams;
+ bool mProcessed;
+ LLCore::HttpHandle mHttpHandle;
+};
- LLMeshHeaderResponder(const LLVolumeParams& mesh_params)
- : mMeshParams(mesh_params)
- {
- LLMeshRepoThread::incActiveHeaderRequests();
- mProcessed = false;
- }
- ~LLMeshHeaderResponder()
+// Subclass for header fetches.
+//
+// Thread: repo
+class LLMeshHeaderHandler : public LLMeshHandlerBase
+{
+public:
+ LLMeshHeaderHandler(const LLVolumeParams & mesh_params)
+ : LLMeshHandlerBase()
{
- if (!LLApp::isQuitting())
- {
- if (!mProcessed)
- { //something went wrong, retry
- llwarns << "Timeout or service unavailable, retrying." << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- LLMeshRepoThread::HeaderRequest req(mMeshParams);
- LLMutexLock lock(gMeshRepo.mThread->mMutex);
- gMeshRepo.mThread->mHeaderReqQ.push(req);
- }
-
- LLMeshRepoThread::decActiveHeaderRequests();
- }
+ mMeshParams = mesh_params;
+ LLMeshRepoThread::incActiveHeaderRequests();
}
+ virtual ~LLMeshHeaderHandler();
- virtual void completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer);
-
+protected:
+ LLMeshHeaderHandler(const LLMeshHeaderHandler &); // Not defined
+ void operator=(const LLMeshHeaderHandler &); // Not defined
+
+public:
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size);
+ virtual void processFailure(LLCore::HttpStatus status);
};
-class LLMeshLODResponder : public LLCurl::Responder
+
+// Subclass for LOD fetches.
+//
+// Thread: repo
+class LLMeshLODHandler : public LLMeshHandlerBase
{
public:
- LLVolumeParams mMeshParams;
- S32 mLOD;
- U32 mRequestedBytes;
- U32 mOffset;
- bool mProcessed;
-
- LLMeshLODResponder(const LLVolumeParams& mesh_params, S32 lod, U32 offset, U32 requested_bytes)
- : mMeshParams(mesh_params), mLOD(lod), mOffset(offset), mRequestedBytes(requested_bytes)
+ LLMeshLODHandler(const LLVolumeParams & mesh_params, S32 lod, U32 offset, U32 requested_bytes)
+ : LLMeshHandlerBase(),
+ mLOD(lod),
+ mRequestedBytes(requested_bytes),
+ mOffset(offset)
{
+ mMeshParams = mesh_params;
LLMeshRepoThread::incActiveLODRequests();
- mProcessed = false;
}
+ virtual ~LLMeshLODHandler();
+
+protected:
+ LLMeshLODHandler(const LLMeshLODHandler &); // Not defined
+ void operator=(const LLMeshLODHandler &); // Not defined
+
+public:
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size);
+ virtual void processFailure(LLCore::HttpStatus status);
- ~LLMeshLODResponder()
- {
- if (!LLApp::isQuitting())
- {
- if (!mProcessed)
- {
- llwarns << "Killed without being processed, retrying." << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD);
- }
- LLMeshRepoThread::decActiveLODRequests();
- }
- }
-
- virtual void completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer);
-
-};
-
-class LLMeshSkinInfoResponder : public LLCurl::Responder
-{
public:
- LLUUID mMeshID;
+ S32 mLOD;
U32 mRequestedBytes;
U32 mOffset;
- bool mProcessed;
-
- LLMeshSkinInfoResponder(const LLUUID& id, U32 offset, U32 size)
- : mMeshID(id), mRequestedBytes(size), mOffset(offset)
- {
- mProcessed = false;
- }
-
- ~LLMeshSkinInfoResponder()
- {
- if (!LLApp::isQuitting() &&
- !mProcessed &&
- mMeshID.notNull())
- { // Something went wrong, retry
- llwarns << "Timeout or service unavailable, retrying loadMeshSkinInfo() for " << mMeshID << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->loadMeshSkinInfo(mMeshID);
- }
- }
-
- virtual void completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer);
-
};
-class LLMeshDecompositionResponder : public LLCurl::Responder
+
+// Subclass for skin info fetches.
+//
+// Thread: repo
+class LLMeshSkinInfoHandler : public LLMeshHandlerBase
{
public:
+ LLMeshSkinInfoHandler(const LLUUID& id, U32 offset, U32 size)
+ : LLMeshHandlerBase(),
+ mMeshID(id),
+ mRequestedBytes(size),
+ mOffset(offset)
+ {}
+ virtual ~LLMeshSkinInfoHandler();
+
+protected:
+ LLMeshSkinInfoHandler(const LLMeshSkinInfoHandler &); // Not defined
+ void operator=(const LLMeshSkinInfoHandler &); // Not defined
+
+public:
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size);
+ virtual void processFailure(LLCore::HttpStatus status);
+
+public:
LLUUID mMeshID;
U32 mRequestedBytes;
U32 mOffset;
- bool mProcessed;
-
- LLMeshDecompositionResponder(const LLUUID& id, U32 offset, U32 size)
- : mMeshID(id), mRequestedBytes(size), mOffset(offset)
- {
- mProcessed = false;
- }
-
- ~LLMeshDecompositionResponder()
- {
- if (!LLApp::isQuitting() &&
- !mProcessed &&
- mMeshID.notNull())
- { // Something went wrong, retry
- llwarns << "Timeout or service unavailable, retrying loadMeshDecomposition() for " << mMeshID << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->loadMeshDecomposition(mMeshID);
- }
- }
-
- virtual void completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer);
-
};
-class LLMeshPhysicsShapeResponder : public LLCurl::Responder
+
+// Subclass for decomposition fetches.
+//
+// Thread: repo
+class LLMeshDecompositionHandler : public LLMeshHandlerBase
{
public:
+ LLMeshDecompositionHandler(const LLUUID& id, U32 offset, U32 size)
+ : LLMeshHandlerBase(),
+ mMeshID(id),
+ mRequestedBytes(size),
+ mOffset(offset)
+ {}
+ virtual ~LLMeshDecompositionHandler();
+
+protected:
+ LLMeshDecompositionHandler(const LLMeshDecompositionHandler &); // Not defined
+ void operator=(const LLMeshDecompositionHandler &); // Not defined
+
+public:
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size);
+ virtual void processFailure(LLCore::HttpStatus status);
+
+public:
LLUUID mMeshID;
U32 mRequestedBytes;
U32 mOffset;
- bool mProcessed;
+};
- LLMeshPhysicsShapeResponder(const LLUUID& id, U32 offset, U32 size)
- : mMeshID(id), mRequestedBytes(size), mOffset(offset)
- {
- mProcessed = false;
- }
- ~LLMeshPhysicsShapeResponder()
- {
- if (!LLApp::isQuitting() &&
- !mProcessed &&
- mMeshID.notNull())
- { // Something went wrong, retry
- llwarns << "Timeout or service unavailable, retrying loadMeshPhysicsShape() for " << mMeshID << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID);
- }
- }
-
- virtual void completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer);
+// Subclass for physics shape fetches.
+//
+// Thread: repo
+class LLMeshPhysicsShapeHandler : public LLMeshHandlerBase
+{
+public:
+ LLMeshPhysicsShapeHandler(const LLUUID& id, U32 offset, U32 size)
+ : LLMeshHandlerBase(),
+ mMeshID(id),
+ mRequestedBytes(size),
+ mOffset(offset)
+ {}
+ virtual ~LLMeshPhysicsShapeHandler();
+
+protected:
+ LLMeshPhysicsShapeHandler(const LLMeshPhysicsShapeHandler &); // Not defined
+ void operator=(const LLMeshPhysicsShapeHandler &); // Not defined
+
+public:
+ virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size);
+ virtual void processFailure(LLCore::HttpStatus status);
+public:
+ LLUUID mMeshID;
+ U32 mRequestedBytes;
+ U32 mOffset;
};
-void log_upload_error(S32 status, const LLSD& content, std::string stage, std::string model_name)
+
+void log_upload_error(LLCore::HttpStatus status, const LLSD& content,
+ const char * const stage, const std::string & model_name)
{
// Add notification popup.
LLSD args;
- std::string message = content["error"]["message"];
- std::string identifier = content["error"]["identifier"];
+ std::string message = content["error"]["message"].asString();
+ std::string identifier = content["error"]["identifier"].asString();
args["MESSAGE"] = message;
args["IDENTIFIER"] = identifier;
args["LABEL"] = model_name;
gMeshRepo.uploadError(args);
// Log details.
- llwarns << "stage: " << stage << " http status: " << status << llendl;
+ LL_WARNS(LOG_MESH) << "Error in stage: " << stage
+ << ", Reason: " << status.toString()
+ << " (" << status.toTerseString() << ")" << LL_ENDL;
if (content.has("error"))
{
const LLSD& err = content["error"];
- llwarns << "err: " << err << llendl;
- llwarns << "mesh upload failed, stage '" << stage
- << "' error '" << err["error"].asString()
- << "', message '" << err["message"].asString()
- << "', id '" << err["identifier"].asString()
- << "'" << llendl;
+ LL_WARNS(LOG_MESH) << "error: " << err << LL_ENDL;
+ LL_WARNS(LOG_MESH) << " mesh upload failed, stage '" << stage
+ << "', error '" << err["error"].asString()
+ << "', message '" << err["message"].asString()
+ << "', id '" << err["identifier"].asString()
+ << "'" << LL_ENDL;
if (err.has("errors"))
{
S32 error_num = 0;
@@ -400,13 +729,13 @@ void log_upload_error(S32 status, const LLSD& content, std::string stage, std::s
++it)
{
const LLSD& err_entry = *it;
- llwarns << "error[" << error_num << "]:" << llendl;
+ LL_WARNS(LOG_MESH) << " error[" << error_num << "]:" << LL_ENDL;
for (LLSD::map_const_iterator map_it = err_entry.beginMap();
map_it != err_entry.endMap();
++map_it)
{
- llwarns << "\t" << map_it->first << ": "
- << map_it->second << llendl;
+ LL_WARNS(LOG_MESH) << " " << map_it->first << ": "
+ << map_it->second << LL_ENDL;
}
error_num++;
}
@@ -414,153 +743,72 @@ void log_upload_error(S32 status, const LLSD& content, std::string stage, std::s
}
else
{
- llwarns << "bad mesh, no error information available" << llendl;
+ LL_WARNS(LOG_MESH) << "Bad response to mesh request, no additional error information available." << LL_ENDL;
}
}
-class LLWholeModelFeeResponder: public LLCurl::Responder
-{
- LLMeshUploadThread* mThread;
- LLSD mModelData;
- LLHandle<LLWholeModelFeeObserver> mObserverHandle;
-public:
- LLWholeModelFeeResponder(LLMeshUploadThread* thread, LLSD& model_data, LLHandle<LLWholeModelFeeObserver> observer_handle):
- mThread(thread),
- mModelData(model_data),
- mObserverHandle(observer_handle)
- {
- if (mThread)
- {
- mThread->startRequest();
- }
- }
-
- ~LLWholeModelFeeResponder()
- {
- if (mThread)
- {
- mThread->stopRequest();
- }
- }
-
- virtual void completed(U32 status,
- const std::string& reason,
- const LLSD& content)
- {
- LLSD cc = content;
- if (gSavedSettings.getS32("MeshUploadFakeErrors")&1)
- {
- cc = llsd_from_file("fake_upload_error.xml");
- }
-
- dump_llsd_to_file(cc,make_dump_name("whole_model_fee_response_",dump_num));
- LLWholeModelFeeObserver* observer = mObserverHandle.get();
+LLMeshRepoThread::LLMeshRepoThread()
+: LLThread("mesh repo"),
+ mHttpRequest(NULL),
+ mHttpOptions(NULL),
+ mHttpLargeOptions(NULL),
+ mHttpHeaders(NULL),
+ mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID),
+ mHttpLegacyPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID),
+ mHttpLargePolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID),
+ mHttpPriority(0),
+ mGetMeshVersion(2)
+{
+ mMutex = new LLMutex(NULL);
+ mHeaderMutex = new LLMutex(NULL);
+ mSignal = new LLCondition(NULL);
+ mHttpRequest = new LLCore::HttpRequest;
+ mHttpOptions = new LLCore::HttpOptions;
+ mHttpOptions->setTransferTimeout(SMALL_MESH_XFER_TIMEOUT);
+ mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter"));
+ mHttpLargeOptions = new LLCore::HttpOptions;
+ mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT);
+ mHttpLargeOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter"));
+ mHttpHeaders = new LLCore::HttpHeaders;
+ mHttpHeaders->append("Accept", "application/vnd.ll.mesh");
+ mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH2);
+ mHttpLegacyPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH1);
+ mHttpLargePolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_LARGE_MESH);
+}
- if (isGoodStatus(status) &&
- cc["state"].asString() == "upload")
- {
- mThread->mWholeModelUploadURL = cc["uploader"].asString();
- if (observer)
- {
- cc["data"]["upload_price"] = cc["upload_price"];
- observer->onModelPhysicsFeeReceived(cc["data"], mThread->mWholeModelUploadURL);
- }
- }
- else
- {
- llwarns << "fee request failed" << llendl;
- log_upload_error(status,cc,"fee",mModelData["name"]);
- mThread->mWholeModelUploadURL = "";
+LLMeshRepoThread::~LLMeshRepoThread()
+{
+ LL_INFOS(LOG_MESH) << "Small GETs issued: " << LLMeshRepository::sHTTPRequestCount
+ << ", Large GETs issued: " << LLMeshRepository::sHTTPLargeRequestCount
+ << ", Max Lock Holdoffs: " << LLMeshRepository::sMaxLockHoldoffs
+ << LL_ENDL;
- if (observer)
- {
- observer->setModelPhysicsFeeErrorStatus(status, reason);
- }
- }
+ for (http_request_set::iterator iter(mHttpRequestSet.begin());
+ iter != mHttpRequestSet.end();
+ ++iter)
+ {
+ delete *iter;
}
-
-};
-
-class LLWholeModelUploadResponder: public LLCurl::Responder
-{
- LLMeshUploadThread* mThread;
- LLSD mModelData;
- LLHandle<LLWholeModelUploadObserver> mObserverHandle;
-
-public:
- LLWholeModelUploadResponder(LLMeshUploadThread* thread, LLSD& model_data, LLHandle<LLWholeModelUploadObserver> observer_handle):
- mThread(thread),
- mModelData(model_data),
- mObserverHandle(observer_handle)
+ mHttpRequestSet.clear();
+ if (mHttpHeaders)
{
- if (mThread)
- {
- mThread->startRequest();
- }
+ mHttpHeaders->release();
+ mHttpHeaders = NULL;
}
-
- ~LLWholeModelUploadResponder()
+ if (mHttpOptions)
{
- if (mThread)
- {
- mThread->stopRequest();
- }
+ mHttpOptions->release();
+ mHttpOptions = NULL;
}
-
- virtual void completed(U32 status,
- const std::string& reason,
- const LLSD& content)
+ if (mHttpLargeOptions)
{
- LLSD cc = content;
- if (gSavedSettings.getS32("MeshUploadFakeErrors")&2)
- {
- cc = llsd_from_file("fake_upload_error.xml");
- }
-
- dump_llsd_to_file(cc,make_dump_name("whole_model_upload_response_",dump_num));
-
- LLWholeModelUploadObserver* observer = mObserverHandle.get();
-
- // requested "mesh" asset type isn't actually the type
- // of the resultant object, fix it up here.
- if (isGoodStatus(status) &&
- cc["state"].asString() == "complete")
- {
- mModelData["asset_type"] = "object";
- gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData,cc));
-
- if (observer)
- {
- doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer));
- }
- }
- else
- {
- llwarns << "upload failed" << llendl;
- std::string model_name = mModelData["name"].asString();
- log_upload_error(status,cc,"upload",model_name);
-
- if (observer)
- {
- doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer));
- }
- }
+ mHttpLargeOptions->release();
+ mHttpLargeOptions = NULL;
}
-};
-
-LLMeshRepoThread::LLMeshRepoThread()
-: LLThread("mesh repo")
-{
- mWaiting = false;
- mMutex = new LLMutex(NULL);
- mHeaderMutex = new LLMutex(NULL);
- mSignal = new LLCondition(NULL);
-}
-
-LLMeshRepoThread::~LLMeshRepoThread()
-{
+ delete mHttpRequest;
+ mHttpRequest = NULL;
delete mMutex;
mMutex = NULL;
delete mHeaderMutex;
@@ -571,109 +819,180 @@ LLMeshRepoThread::~LLMeshRepoThread()
void LLMeshRepoThread::run()
{
- mCurlRequest = new LLCurlRequest();
LLCDResult res = LLConvexDecomposition::initThread();
if (res != LLCD_OK)
{
- llwarns << "convex decomposition unable to be loaded" << llendl;
+ LL_WARNS(LOG_MESH) << "Convex decomposition unable to be loaded. Expect severe problems." << LL_ENDL;
}
while (!LLApp::isQuitting())
{
- mWaiting = true;
+ // *TODO: Revise sleep/wake strategy and try to move away
+ // from polling operations in this thread. We can sleep
+ // this thread hard when:
+ // * All Http requests are serviced
+ // * LOD request queue empty
+ // * Header request queue empty
+ // * Skin info request queue empty
+ // * Decomposition request queue empty
+ // * Physics shape request queue empty
+ // We wake the thread when any of the above become untrue.
+ // Will likely need a correctly-implemented condition variable to do this.
+ // On the other hand, this may actually be an effective and efficient scheme...
+
mSignal->wait();
- mWaiting = false;
- if (!LLApp::isQuitting())
+ if (LLApp::isQuitting())
{
- static U32 count = 0;
+ break;
+ }
+
+ if (! mHttpRequestSet.empty())
+ {
+ // Dispatch all HttpHandler notifications
+ mHttpRequest->update(0L);
+ }
+ sRequestWaterLevel = mHttpRequestSet.size(); // Stats data update
+
+ // NOTE: order of queue processing intentionally favors LOD requests over header requests
- static F32 last_hundred = gFrameTimeSeconds;
+ while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ {
+ if (! mMutex)
+ {
+ break;
+ }
+ mMutex->lock();
+ LODRequest req = mLODReqQ.front();
+ mLODReqQ.pop();
+ LLMeshRepository::sLODProcessing--;
+ mMutex->unlock();
+ if (!fetchMeshLOD(req.mMeshParams, req.mLOD)) // failed, resubmit
+ {
+ mMutex->lock();
+ mLODReqQ.push(req) ;
+ ++LLMeshRepository::sLODProcessing;
+ mMutex->unlock();
+ }
+ }
- if (gFrameTimeSeconds - last_hundred > 1.f)
- { //a second has gone by, clear count
- last_hundred = gFrameTimeSeconds;
- count = 0;
+ while (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ {
+ if (! mMutex)
+ {
+ break;
+ }
+ mMutex->lock();
+ HeaderRequest req = mHeaderReqQ.front();
+ mHeaderReqQ.pop();
+ mMutex->unlock();
+ if (!fetchMeshHeader(req.mMeshParams))//failed, resubmit
+ {
+ mMutex->lock();
+ mHeaderReqQ.push(req) ;
+ mMutex->unlock();
}
+ }
- // NOTE: throttling intentionally favors LOD requests over header requests
-
- while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveLODRequests < sMaxConcurrentRequests)
+ // For the final three request lists, similar goal to above but
+ // slightly different queue structures. Stay off the mutex when
+ // performing long-duration actions.
+
+ if (mHttpRequestSet.size() < sRequestHighWater
+ && (! mSkinRequests.empty()
+ || ! mDecompositionRequests.empty()
+ || ! mPhysicsShapeRequests.empty()))
+ {
+ // Something to do probably, lock and double-check. We don't want
+ // to hold the lock long here. That will stall main thread activities
+ // so we bounce it.
+
+ mMutex->lock();
+ if (! mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater)
{
- if (mMutex)
+ std::set<LLUUID> incomplete;
+ std::set<LLUUID>::iterator iter(mSkinRequests.begin());
+ while (iter != mSkinRequests.end() && mHttpRequestSet.size() < sRequestHighWater)
{
- mMutex->lock();
- LODRequest req = mLODReqQ.front();
- mLODReqQ.pop();
- LLMeshRepository::sLODProcessing--;
+ LLUUID mesh_id = *iter;
+ mSkinRequests.erase(iter);
mMutex->unlock();
- if (!fetchMeshLOD(req.mMeshParams, req.mLOD, count))//failed, resubmit
+
+ if (! fetchMeshSkinInfo(mesh_id))
{
- mMutex->lock();
- mLODReqQ.push(req);
- mMutex->unlock();
+ incomplete.insert(mesh_id);
}
+
+ mMutex->lock();
+ iter = mSkinRequests.begin();
}
- }
- while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveHeaderRequests < sMaxConcurrentRequests)
- {
- if (mMutex)
+ if (! incomplete.empty())
{
- mMutex->lock();
- HeaderRequest req = mHeaderReqQ.front();
- mHeaderReqQ.pop();
- mMutex->unlock();
- if (!fetchMeshHeader(req.mMeshParams, count))//failed, resubmit
- {
- mMutex->lock();
- mHeaderReqQ.push(req) ;
- mMutex->unlock();
- }
+ mSkinRequests.insert(incomplete.begin(), incomplete.end());
}
}
- { //mSkinRequests is protected by mSignal
+ // holding lock, try next list
+ // *TODO: For UI/debug-oriented lists, we might drop the fine-
+ // grained locking as there's a lowered expectation of smoothness
+ // in these cases.
+ if (! mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ {
std::set<LLUUID> incomplete;
- for (std::set<LLUUID>::iterator iter = mSkinRequests.begin(); iter != mSkinRequests.end(); ++iter)
+ std::set<LLUUID>::iterator iter(mDecompositionRequests.begin());
+ while (iter != mDecompositionRequests.end() && mHttpRequestSet.size() < sRequestHighWater)
{
LLUUID mesh_id = *iter;
- if (!fetchMeshSkinInfo(mesh_id))
+ mDecompositionRequests.erase(iter);
+ mMutex->unlock();
+
+ if (! fetchMeshDecomposition(mesh_id))
{
incomplete.insert(mesh_id);
}
+
+ mMutex->lock();
+ iter = mDecompositionRequests.begin();
}
- mSkinRequests = incomplete;
- }
- { //mDecompositionRequests is protected by mSignal
- std::set<LLUUID> incomplete;
- for (std::set<LLUUID>::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter)
+ if (! incomplete.empty())
{
- LLUUID mesh_id = *iter;
- if (!fetchMeshDecomposition(mesh_id))
- {
- incomplete.insert(mesh_id);
- }
+ mDecompositionRequests.insert(incomplete.begin(), incomplete.end());
}
- mDecompositionRequests = incomplete;
}
- { //mPhysicsShapeRequests is protected by mSignal
+ // holding lock, final list
+ if (! mPhysicsShapeRequests.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ {
std::set<LLUUID> incomplete;
- for (std::set<LLUUID>::iterator iter = mPhysicsShapeRequests.begin(); iter != mPhysicsShapeRequests.end(); ++iter)
+ std::set<LLUUID>::iterator iter(mPhysicsShapeRequests.begin());
+ while (iter != mPhysicsShapeRequests.end() && mHttpRequestSet.size() < sRequestHighWater)
{
LLUUID mesh_id = *iter;
- if (!fetchMeshPhysicsShape(mesh_id))
+ mPhysicsShapeRequests.erase(iter);
+ mMutex->unlock();
+
+ if (! fetchMeshPhysicsShape(mesh_id))
{
incomplete.insert(mesh_id);
}
+
+ mMutex->lock();
+ iter = mPhysicsShapeRequests.begin();
}
- mPhysicsShapeRequests = incomplete;
- }
- mCurlRequest->process();
+ if (! incomplete.empty())
+ {
+ mPhysicsShapeRequests.insert(incomplete.begin(), incomplete.end());
+ }
+ }
+ mMutex->unlock();
}
+
+ // For dev purposes only. A dynamic change could make this false
+ // and that shouldn't assert.
+ // llassert_always(mHttpRequestSet.size() <= sRequestHighWater);
}
if (mSignal->isLocked())
@@ -684,25 +1003,25 @@ void LLMeshRepoThread::run()
res = LLConvexDecomposition::quitThread();
if (res != LLCD_OK)
{
- llwarns << "convex decomposition unable to be quit" << llendl;
+ LL_WARNS(LOG_MESH) << "Convex decomposition unable to be quit." << LL_ENDL;
}
-
- delete mCurlRequest;
- mCurlRequest = NULL;
}
+// Mutex: LLMeshRepoThread::mMutex must be held on entry
void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id)
-{ //protected by mSignal, no locking needed here
+{
mSkinRequests.insert(mesh_id);
}
+// Mutex: LLMeshRepoThread::mMutex must be held on entry
void LLMeshRepoThread::loadMeshDecomposition(const LLUUID& mesh_id)
-{ //protected by mSignal, no locking needed here
+{
mDecompositionRequests.insert(mesh_id);
}
+// Mutex: LLMeshRepoThread::mMutex must be held on entry
void LLMeshRepoThread::loadMeshPhysicsShape(const LLUUID& mesh_id)
-{ //protected by mSignal, no locking needed here
+{
mPhysicsShapeRequests.insert(mesh_id);
}
@@ -715,7 +1034,6 @@ void LLMeshRepoThread::lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32
}
-
void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
{ //could be called from any thread
LLMutexLock lock(mMutex);
@@ -747,31 +1065,122 @@ void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
}
}
-//static
-std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id)
+// Mutex: must be holding mMutex when called
+void LLMeshRepoThread::setGetMeshCaps(const std::string & get_mesh1,
+ const std::string & get_mesh2,
+ int pref_version)
{
- std::string http_url;
+ mGetMeshCapability = get_mesh1;
+ mGetMesh2Capability = get_mesh2;
+ mGetMeshVersion = pref_version;
+}
+
+
+// Constructs a Cap URL for the mesh. Prefers a GetMesh2 cap
+// over a GetMesh cap.
+//
+// Mutex: acquires mMutex
+void LLMeshRepoThread::constructUrl(LLUUID mesh_id, std::string * url, int * version)
+{
+ std::string res_url;
+ int res_version(2);
if (gAgent.getRegion())
{
- http_url = gMeshRepo.mGetMeshCapability;
+ LLMutexLock lock(mMutex);
+
+ // Get a consistent pair of (cap string, version). The
+ // locking could be eliminated here without loss of safety
+ // by using a set of staging values in setGetMeshCaps().
+
+ if (! mGetMesh2Capability.empty() && mGetMeshVersion > 1)
+ {
+ res_url = mGetMesh2Capability;
+ res_version = 2;
+ }
+ else
+ {
+ res_url = mGetMeshCapability;
+ res_version = 1;
+ }
}
- if (!http_url.empty())
+ if (! res_url.empty())
{
- http_url += "/?mesh_id=";
- http_url += mesh_id.asString().c_str();
+ res_url += "/?mesh_id=";
+ res_url += mesh_id.asString().c_str();
}
else
{
- llwarns << "Current region does not have GetMesh capability! Cannot load " << mesh_id << ".mesh" << llendl;
+ LL_WARNS_ONCE(LOG_MESH) << "Current region does not have GetMesh capability! Cannot load "
+ << mesh_id << ".mesh" << LL_ENDL;
}
- return http_url;
+ *url = res_url;
+ *version = res_version;
+}
+
+// Issue an HTTP GET request with byte range using the right
+// policy class. Large requests go to the large request class.
+// If the current region supports GetMesh2, we prefer that for
+// smaller requests otherwise we try to use the traditional
+// GetMesh capability and connection concurrency.
+//
+// @return Valid handle or LLCORE_HTTP_HANDLE_INVALID.
+// If the latter, actual status is found in
+// mHttpStatus member which is valid until the
+// next call to this method.
+//
+// Thread: repo
+LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, int cap_version,
+ size_t offset, size_t len,
+ LLCore::HttpHandler * handler)
+{
+ LLCore::HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID);
+
+ if (len < LARGE_MESH_FETCH_THRESHOLD)
+ {
+ handle = mHttpRequest->requestGetByteRange((2 == cap_version
+ ? mHttpPolicyClass
+ : mHttpLegacyPolicyClass),
+ mHttpPriority,
+ url,
+ offset,
+ len,
+ mHttpOptions,
+ mHttpHeaders,
+ handler);
+ if (LLCORE_HTTP_HANDLE_INVALID != handle)
+ {
+ ++LLMeshRepository::sHTTPRequestCount;
+ }
+ }
+ else
+ {
+ handle = mHttpRequest->requestGetByteRange(mHttpLargePolicyClass,
+ mHttpPriority,
+ url,
+ offset,
+ len,
+ mHttpLargeOptions,
+ mHttpHeaders,
+ handler);
+ if (LLCORE_HTTP_HANDLE_INVALID != handle)
+ {
+ ++LLMeshRepository::sHTTPLargeRequestCount;
+ }
+ }
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ // Something went wrong, capture the error code for caller.
+ mHttpStatus = mHttpRequest->getStatus();
+ }
+ return handle;
}
+
bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
-{ //protected by mMutex
+{
if (!mHeaderMutex)
{
@@ -786,7 +1195,8 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
return false;
}
- bool ret = true ;
+ ++LLMeshRepository::sMeshRequestCount;
+ bool ret = true;
U32 header_size = mMeshHeaderSize[mesh_id];
if (header_size > 0)
@@ -804,6 +1214,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
if (file.getSize() >= offset+size)
{
LLMeshRepository::sCacheBytesRead += size;
+ ++LLMeshRepository::sCacheReads;
file.seek(offset);
U8* buffer = new U8[size];
file.read(buffer, size);
@@ -828,17 +1239,28 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
}
//reading from VFS failed for whatever reason, fetch from sim
- std::vector<std::string> headers;
- headers.push_back("Accept: application/octet-stream");
+ int cap_version(2);
+ std::string http_url;
+ constructUrl(mesh_id, &http_url, &cap_version);
- std::string http_url = constructUrl(mesh_id);
if (!http_url.empty())
- {
- ret = mCurlRequest->getByteRange(http_url, headers, offset, size,
- new LLMeshSkinInfoResponder(mesh_id, offset, size));
- if(ret)
+ {
+ LLMeshSkinInfoHandler * handler = new LLMeshSkinInfoHandler(mesh_id, offset, size);
+ LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ LL_WARNS(LOG_MESH) << "HTTP GET request failed for skin info on mesh " << mID
+ << ". Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toTerseString() << ")"
+ << LL_ENDL;
+ delete handler;
+ ret = false;
+
+ }
+ else
{
- LLMeshRepository::sHTTPRequestCount++;
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
}
}
}
@@ -853,7 +1275,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
}
bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
-{ //protected by mMutex
+{
if (!mHeaderMutex)
{
return false;
@@ -867,8 +1289,9 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
return false;
}
+ ++LLMeshRepository::sMeshRequestCount;
U32 header_size = mMeshHeaderSize[mesh_id];
- bool ret = true ;
+ bool ret = true;
if (header_size > 0)
{
@@ -885,6 +1308,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
if (file.getSize() >= offset+size)
{
LLMeshRepository::sCacheBytesRead += size;
+ ++LLMeshRepository::sCacheReads;
file.seek(offset);
U8* buffer = new U8[size];
@@ -910,17 +1334,27 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
}
//reading from VFS failed for whatever reason, fetch from sim
- std::vector<std::string> headers;
- headers.push_back("Accept: application/octet-stream");
-
- std::string http_url = constructUrl(mesh_id);
+ int cap_version(2);
+ std::string http_url;
+ constructUrl(mesh_id, &http_url, &cap_version);
+
if (!http_url.empty())
- {
- ret = mCurlRequest->getByteRange(http_url, headers, offset, size,
- new LLMeshDecompositionResponder(mesh_id, offset, size));
- if(ret)
+ {
+ LLMeshDecompositionHandler * handler = new LLMeshDecompositionHandler(mesh_id, offset, size);
+ LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ LL_WARNS(LOG_MESH) << "HTTP GET request failed for decomposition mesh " << mID
+ << ". Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toTerseString() << ")"
+ << LL_ENDL;
+ delete handler;
+ ret = false;
+ }
+ else
{
- LLMeshRepository::sHTTPRequestCount++;
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
}
}
}
@@ -935,7 +1369,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
}
bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
-{ //protected by mMutex
+{
if (!mHeaderMutex)
{
return false;
@@ -949,8 +1383,9 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
return false;
}
+ ++LLMeshRepository::sMeshRequestCount;
U32 header_size = mMeshHeaderSize[mesh_id];
- bool ret = true ;
+ bool ret = true;
if (header_size > 0)
{
@@ -967,6 +1402,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
if (file.getSize() >= offset+size)
{
LLMeshRepository::sCacheBytesRead += size;
+ ++LLMeshRepository::sCacheReads;
file.seek(offset);
U8* buffer = new U8[size];
file.read(buffer, size);
@@ -991,18 +1427,27 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
}
//reading from VFS failed for whatever reason, fetch from sim
- std::vector<std::string> headers;
- headers.push_back("Accept: application/octet-stream");
-
- std::string http_url = constructUrl(mesh_id);
+ int cap_version(2);
+ std::string http_url;
+ constructUrl(mesh_id, &http_url, &cap_version);
+
if (!http_url.empty())
- {
- ret = mCurlRequest->getByteRange(http_url, headers, offset, size,
- new LLMeshPhysicsShapeResponder(mesh_id, offset, size));
-
- if(ret)
+ {
+ LLMeshPhysicsShapeHandler * handler = new LLMeshPhysicsShapeHandler(mesh_id, offset, size);
+ LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
{
- LLMeshRepository::sHTTPRequestCount++;
+ LL_WARNS(LOG_MESH) << "HTTP GET request failed for physics shape on mesh " << mID
+ << ". Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toTerseString() << ")"
+ << LL_ENDL;
+ delete handler;
+ ret = false;
+ }
+ else
+ {
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
}
}
}
@@ -1049,8 +1494,10 @@ void LLMeshRepoThread::decActiveHeaderRequests()
}
//return false if failed to get header
-bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& count)
+bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params)
{
+ ++LLMeshRepository::sMeshRequestCount;
+
{
//look for mesh in asset in vfs
LLVFile file(gVFS, mesh_params.getSculptID(), LLAssetType::AT_MESH);
@@ -1058,43 +1505,57 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c
S32 size = file.getSize();
if (size > 0)
- { //NOTE -- if the header size is ever more than 4KB, this will break
- U8 buffer[4096];
- S32 bytes = llmin(size, 4096);
+ {
+ // *NOTE: if the header size is ever more than 4KB, this will break
+ U8 buffer[MESH_HEADER_SIZE];
+ S32 bytes = llmin(size, MESH_HEADER_SIZE);
LLMeshRepository::sCacheBytesRead += bytes;
+ ++LLMeshRepository::sCacheReads;
file.read(buffer, bytes);
if (headerReceived(mesh_params, buffer, bytes))
- { //did not do an HTTP request, return false
+ {
+ // Found mesh in VFS cache
return true;
}
}
}
//either cache entry doesn't exist or is corrupt, request header from simulator
- bool retval = true ;
- std::vector<std::string> headers;
- headers.push_back("Accept: application/octet-stream");
-
- std::string http_url = constructUrl(mesh_params.getSculptID());
+ bool retval = true;
+ int cap_version(2);
+ std::string http_url;
+ constructUrl(mesh_params.getSculptID(), &http_url, &cap_version);
+
if (!http_url.empty())
{
//grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits
//within the first 4KB
//NOTE -- this will break of headers ever exceed 4KB
- retval = mCurlRequest->getByteRange(http_url, headers, 0, 4096, new LLMeshHeaderResponder(mesh_params));
- if(retval)
+
+ LLMeshHeaderHandler * handler = new LLMeshHeaderHandler(mesh_params);
+ LLCore::HttpHandle handle = getByteRange(http_url, cap_version, 0, MESH_HEADER_SIZE, handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ LL_WARNS(LOG_MESH) << "HTTP GET request failed for mesh header " << mID
+ << ". Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toTerseString() << ")"
+ << LL_ENDL;
+ delete handler;
+ retval = false;
+ }
+ else
{
- LLMeshRepository::sHTTPRequestCount++;
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
}
- count++;
}
return retval;
}
//return false if failed to get mesh lod.
-bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count)
-{ //protected by mMutex
+bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
+{
if (!mHeaderMutex)
{
return false;
@@ -1102,6 +1563,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
mHeaderMutex->lock();
+ ++LLMeshRepository::sMeshRequestCount;
bool retval = true;
LLUUID mesh_id = mesh_params.getSculptID();
@@ -1123,6 +1585,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
if (file.getSize() >= offset+size)
{
LLMeshRepository::sCacheBytesRead += size;
+ ++LLMeshRepository::sCacheReads;
file.seek(offset);
U8* buffer = new U8[size];
file.read(buffer, size);
@@ -1147,20 +1610,29 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
}
//reading from VFS failed for whatever reason, fetch from sim
- std::vector<std::string> headers;
- headers.push_back("Accept: application/octet-stream");
-
- std::string http_url = constructUrl(mesh_id);
+ int cap_version(2);
+ std::string http_url;
+ constructUrl(mesh_id, &http_url, &cap_version);
+
if (!http_url.empty())
- {
- retval = mCurlRequest->getByteRange(constructUrl(mesh_id), headers, offset, size,
- new LLMeshLODResponder(mesh_params, lod, offset, size));
-
- if(retval)
+ {
+ LLMeshLODHandler * handler = new LLMeshLODHandler(mesh_params, lod, offset, size);
+ LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ LL_WARNS(LOG_MESH) << "HTTP GET request failed for LOD on mesh " << mID
+ << ". Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toTerseString() << ")"
+ << LL_ENDL;
+ delete handler;
+ retval = false;
+ }
+ else
{
- LLMeshRepository::sHTTPRequestCount++;
+ handler->mHttpHandle = handle;
+ mHttpRequestSet.insert(handler);
+ // *NOTE: Allowing a re-request, not marking as unavailable. Is that correct?
}
- count++;
}
else
{
@@ -1182,6 +1654,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size)
{
+ const LLUUID mesh_id = mesh_params.getSculptID();
LLSD header;
U32 header_size = 0;
@@ -1202,7 +1675,8 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat
if (!LLSDSerialize::fromBinary(header, stream, data_size))
{
- llwarns << "Mesh header parse error. Not a valid mesh asset!" << llendl;
+ LL_WARNS(LOG_MESH) << "Mesh header parse error. Not a valid mesh asset! ID: " << mesh_id
+ << LL_ENDL;
return false;
}
@@ -1210,13 +1684,12 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat
}
else
{
- llinfos
- << "Marking header as non-existent, will not retry." << llendl;
+ LL_INFOS(LOG_MESH) << "Non-positive data size. Marking header as non-existent, will not retry. ID: " << mesh_id
+ << LL_ENDL;
header["404"] = 1;
}
{
- LLUUID mesh_id = mesh_params.getSculptID();
{
LLMutexLock lock(mHeaderMutex);
@@ -1224,6 +1697,7 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat
mMeshHeader[mesh_id] = header;
}
+
LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time.
//check for pending requests
@@ -1277,7 +1751,8 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat
if (!unzip_llsd(skin, stream, data_size))
{
- llwarns << "Mesh skin info parse error. Not a valid mesh asset!" << llendl;
+ LL_WARNS(LOG_MESH) << "Mesh skin info parse error. Not a valid mesh asset! ID: " << mesh_id
+ << LL_ENDL;
return false;
}
}
@@ -1286,8 +1761,11 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat
LLMeshSkinInfo info(skin);
info.mMeshID = mesh_id;
- //llinfos<<"info pelvis offset"<<info.mPelvisOffset<<llendl;
- mSkinInfoQ.push(info);
+ // LL_DEBUGS(LOG_MESH) << "info pelvis offset" << info.mPelvisOffset << LL_ENDL;
+ {
+ LLMutexLock lock(mMutex);
+ mSkinInfoQ.push_back(info);
+ }
}
return true;
@@ -1305,7 +1783,8 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3
if (!unzip_llsd(decomp, stream, data_size))
{
- llwarns << "Mesh decomposition parse error. Not a valid mesh asset!" << llendl;
+ LL_WARNS(LOG_MESH) << "Mesh decomposition parse error. Not a valid mesh asset! ID: " << mesh_id
+ << LL_ENDL;
return false;
}
}
@@ -1313,7 +1792,10 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3
{
LLModel::Decomposition* d = new LLModel::Decomposition(decomp);
d->mMeshID = mesh_id;
- mDecompositionQ.push(d);
+ {
+ LLMutexLock lock(mMutex);
+ mDecompositionQ.push_back(d);
+ }
}
return true;
@@ -1372,15 +1854,20 @@ bool LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32
}
}
- mDecompositionQ.push(d);
+ {
+ LLMutexLock lock(mMutex);
+ mDecompositionQ.push_back(d);
+ }
return true;
}
LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures,
- bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload,
- LLHandle<LLWholeModelFeeObserver> fee_observer, LLHandle<LLWholeModelUploadObserver> upload_observer)
-: LLThread("mesh upload"),
- mDiscarded(FALSE),
+ bool upload_skin, bool upload_joints, const std::string & upload_url, bool do_upload,
+ LLHandle<LLWholeModelFeeObserver> fee_observer,
+ LLHandle<LLWholeModelUploadObserver> upload_observer)
+ : LLThread("mesh upload"),
+ LLCore::HttpHandler(),
+ mDiscarded(false),
mDoUpload(do_upload),
mWholeModelUploadURL(upload_url),
mFeeObserverHandle(fee_observer),
@@ -1391,7 +1878,6 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data,
mUploadSkin = upload_skin;
mUploadJoints = upload_joints;
mMutex = new LLMutex(NULL);
- mCurlRequest = NULL;
mPendingUploads = 0;
mFinished = false;
mOrigin = gAgent.getPositionAgent();
@@ -1401,12 +1887,33 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data,
mOrigin += gAgent.getAtAxis() * scale.magVec();
- mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut") ;
+ mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut");
+
+ mHttpRequest = new LLCore::HttpRequest;
+ mHttpOptions = new LLCore::HttpOptions;
+ mHttpOptions->setTransferTimeout(mMeshUploadTimeOut);
+ mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter"));
+ mHttpOptions->setRetries(UPLOAD_RETRY_LIMIT);
+ mHttpHeaders = new LLCore::HttpHeaders;
+ mHttpHeaders->append("Content-Type", "application/llsd+xml");
+ mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_UPLOADS);
+ mHttpPriority = 0;
}
LLMeshUploadThread::~LLMeshUploadThread()
{
-
+ if (mHttpHeaders)
+ {
+ mHttpHeaders->release();
+ mHttpHeaders = NULL;
+ }
+ if (mHttpOptions)
+ {
+ mHttpOptions->release();
+ mHttpOptions = NULL;
+ }
+ delete mHttpRequest;
+ mHttpRequest = NULL;
}
LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread)
@@ -1448,14 +1955,14 @@ void LLMeshUploadThread::preStart()
void LLMeshUploadThread::discard()
{
- LLMutexLock lock(mMutex) ;
- mDiscarded = TRUE ;
+ LLMutexLock lock(mMutex);
+ mDiscarded = true;
}
-BOOL LLMeshUploadThread::isDiscarded()
+bool LLMeshUploadThread::isDiscarded() const
{
- LLMutexLock lock(mMutex) ;
- return mDiscarded ;
+ LLMutexLock lock(mMutex);
+ return mDiscarded;
}
void LLMeshUploadThread::run()
@@ -1706,9 +2213,15 @@ void LLMeshUploadThread::generateHulls()
}
}
- if(has_valid_requests)
- {
- while (!mPhysicsComplete)
+ if (has_valid_requests)
+ {
+ // *NOTE: Interesting livelock condition on shutdown. If there
+ // is an upload request in generateHulls() when shutdown starts,
+ // the main thread isn't available to manage communication between
+ // the decomposition thread and the upload thread and this loop
+ // wouldn't complete in turn stalling the main thread. The check
+ // on isDiscarded() prevents that.
+ while (! mPhysicsComplete && ! isDiscarded())
{
apr_sleep(100);
}
@@ -1717,86 +2230,266 @@ void LLMeshUploadThread::generateHulls()
void LLMeshUploadThread::doWholeModelUpload()
{
- mCurlRequest = new LLCurlRequest();
+ LL_DEBUGS(LOG_MESH) << "Starting model upload. Instances: " << mInstance.size() << LL_ENDL;
if (mWholeModelUploadURL.empty())
{
- llinfos << "unable to upload, fee request failed" << llendl;
+ LL_WARNS(LOG_MESH) << "Missing mesh upload capability, unable to upload, fee request failed."
+ << LL_ENDL;
}
else
{
generateHulls();
-
- LLSD full_model_data;
- wholeModelToLLSD(full_model_data, true);
- LLSD body = full_model_data["asset_resources"];
- dump_llsd_to_file(body,make_dump_name("whole_model_body_",dump_num));
- LLCurlRequest::headers_t headers;
-
+ LL_DEBUGS(LOG_MESH) << "Hull generation completed." << LL_ENDL;
+
+ mModelData = LLSD::emptyMap();
+ wholeModelToLLSD(mModelData, true);
+ LLSD body = mModelData["asset_resources"];
+ dump_llsd_to_file(body, make_dump_name("whole_model_body_", dump_num));
+
+ LLCore::BufferArray * ba = new LLCore::BufferArray;
+ LLCore::BufferArrayStream bas(ba);
+ LLSDSerialize::toXML(body, bas);
+ // LLSDSerialize::toXML(mModelData, bas); // <- Enabling this will generate a convenient upload error
+ LLCore::HttpHandle handle = mHttpRequest->requestPost(mHttpPolicyClass,
+ mHttpPriority,
+ mWholeModelUploadURL,
+ ba,
+ mHttpOptions,
+ mHttpHeaders,
+ this);
+ ba->release();
+
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ mHttpStatus = mHttpRequest->getStatus();
+
+ LL_WARNS(LOG_MESH) << "Couldn't issue request for full model upload. Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toTerseString() << ")"
+ << LL_ENDL;
+ }
+ else
{
- LLCurl::ResponderPtr responder = new LLWholeModelUploadResponder(this, full_model_data, mUploadObserverHandle) ;
+ U32 sleep_time(10);
+
+ LL_DEBUGS(LOG_MESH) << "POST request issued." << LL_ENDL;
+
+ mHttpRequest->update(0);
+ while (! LLApp::isQuitting() && ! finished() && ! isDiscarded())
+ {
+ ms_sleep(sleep_time);
+ sleep_time = llmin(250U, sleep_time + sleep_time);
+ mHttpRequest->update(0);
+ }
- while(!mCurlRequest->post(mWholeModelUploadURL, headers, body, responder, mMeshUploadTimeOut))
+ if (isDiscarded())
+ {
+ LL_DEBUGS(LOG_MESH) << "Mesh upload operation discarded." << LL_ENDL;
+ }
+ else
{
- //sleep for 10ms to prevent eating a whole core
- apr_sleep(10000);
+ LL_DEBUGS(LOG_MESH) << "Mesh upload operation completed." << LL_ENDL;
}
}
-
- do
- {
- mCurlRequest->process();
- //sleep for 10ms to prevent eating a whole core
- apr_sleep(10000);
- } while (!LLAppViewer::isQuitting() && mPendingUploads > 0);
}
-
- delete mCurlRequest;
- mCurlRequest = NULL;
-
- // Currently a no-op.
- mFinished = true;
}
void LLMeshUploadThread::requestWholeModelFee()
{
dump_num++;
- mCurlRequest = new LLCurlRequest();
-
generateHulls();
- LLSD model_data;
- wholeModelToLLSD(model_data,false);
- dump_llsd_to_file(model_data,make_dump_name("whole_model_fee_request_",dump_num));
-
- LLCurlRequest::headers_t headers;
+ mModelData = LLSD::emptyMap();
+ wholeModelToLLSD(mModelData, false);
+ dump_llsd_to_file(mModelData, make_dump_name("whole_model_fee_request_", dump_num));
+ LLCore::BufferArray * ba = new LLCore::BufferArray;
+ LLCore::BufferArrayStream bas(ba);
+ LLSDSerialize::toXML(mModelData, bas);
+
+ LLCore::HttpHandle handle = mHttpRequest->requestPost(mHttpPolicyClass,
+ mHttpPriority,
+ mWholeModelFeeCapability,
+ ba,
+ mHttpOptions,
+ mHttpHeaders,
+ this);
+ ba->release();
+ if (LLCORE_HTTP_HANDLE_INVALID == handle)
+ {
+ mHttpStatus = mHttpRequest->getStatus();
+
+ LL_WARNS(LOG_MESH) << "Couldn't issue request for model fee. Reason: " << mHttpStatus.toString()
+ << " (" << mHttpStatus.toTerseString() << ")"
+ << LL_ENDL;
+ }
+ else
{
- LLCurl::ResponderPtr responder = new LLWholeModelFeeResponder(this,model_data, mFeeObserverHandle) ;
- while(!mCurlRequest->post(mWholeModelFeeCapability, headers, model_data, responder, mMeshUploadTimeOut))
+ U32 sleep_time(10);
+
+ mHttpRequest->update(0);
+ while (! LLApp::isQuitting() && ! finished() && ! isDiscarded())
+ {
+ ms_sleep(sleep_time);
+ sleep_time = llmin(250U, sleep_time + sleep_time);
+ mHttpRequest->update(0);
+ }
+ if (isDiscarded())
{
- //sleep for 10ms to prevent eating a whole core
- apr_sleep(10000);
+ LL_DEBUGS(LOG_MESH) << "Mesh fee query operation discarded." << LL_ENDL;
}
}
+}
- do
- {
- mCurlRequest->process();
- //sleep for 10ms to prevent eating a whole core
- apr_sleep(10000);
- } while (!LLApp::isQuitting() && mPendingUploads > 0);
- delete mCurlRequest;
- mCurlRequest = NULL;
+// Does completion duty for both fee queries and actual uploads.
+void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response)
+{
+ // QA/Devel: 0x2 to enable fake error import on upload, 0x1 on fee check
+ const S32 fake_error(gSavedSettings.getS32("MeshUploadFakeErrors") & (mDoUpload ? 0xa : 0x5));
+ LLCore::HttpStatus status(response->getStatus());
+ if (fake_error)
+ {
+ status = (fake_error & 0x0c) ? LLCore::HttpStatus(500) : LLCore::HttpStatus(200);
+ }
+ std::string reason(status.toString());
+ LLSD body;
- // Currently a no-op.
mFinished = true;
+
+ if (mDoUpload)
+ {
+ // model upload case
+ LLWholeModelUploadObserver * observer(mUploadObserverHandle.get());
+
+ if (! status)
+ {
+ LL_WARNS(LOG_MESH) << "Upload failed. Reason: " << reason
+ << " (" << status.toTerseString() << ")"
+ << LL_ENDL;
+
+ // Build a fake body for the alert generator
+ body["error"] = LLSD::emptyMap();
+ body["error"]["message"] = reason;
+ body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py
+ log_upload_error(status, body, "upload", mModelData["name"].asString());
+
+ if (observer)
+ {
+ doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer));
+ }
+ }
+ else
+ {
+ if (fake_error & 0x2)
+ {
+ body = llsd_from_file("fake_upload_error.xml");
+ }
+ else
+ {
+ LLCore::BufferArray * ba(response->getBody());
+ if (ba && ba->size())
+ {
+ LLCore::BufferArrayStream bas(ba);
+ LLSDSerialize::fromXML(body, bas);
+ }
+ }
+ dump_llsd_to_file(body, make_dump_name("whole_model_upload_response_", dump_num));
+
+ if (body["state"].asString() == "complete")
+ {
+ // requested "mesh" asset type isn't actually the type
+ // of the resultant object, fix it up here.
+ mModelData["asset_type"] = "object";
+ gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData, body));
+
+ if (observer)
+ {
+ doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer));
+ }
+ }
+ else
+ {
+ LL_WARNS(LOG_MESH) << "Upload failed. Not in expected 'complete' state." << LL_ENDL;
+ log_upload_error(status, body, "upload", mModelData["name"].asString());
+
+ if (observer)
+ {
+ doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer));
+ }
+ }
+ }
+ }
+ else
+ {
+ // model fee case
+ LLWholeModelFeeObserver* observer(mFeeObserverHandle.get());
+ mWholeModelUploadURL.clear();
+
+ if (! status)
+ {
+ LL_WARNS(LOG_MESH) << "Fee request failed. Reason: " << reason
+ << " (" << status.toTerseString() << ")"
+ << LL_ENDL;
+
+ // Build a fake body for the alert generator
+ body["error"] = LLSD::emptyMap();
+ body["error"]["message"] = reason;
+ body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py
+ log_upload_error(status, body, "fee", mModelData["name"].asString());
+
+ if (observer)
+ {
+ observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason);
+ }
+ }
+ else
+ {
+ if (fake_error & 0x1)
+ {
+ body = llsd_from_file("fake_upload_error.xml");
+ }
+ else
+ {
+ LLCore::BufferArray * ba(response->getBody());
+ if (ba && ba->size())
+ {
+ LLCore::BufferArrayStream bas(ba);
+ LLSDSerialize::fromXML(body, bas);
+ }
+ }
+ dump_llsd_to_file(body, make_dump_name("whole_model_fee_response_", dump_num));
+
+ if (body["state"].asString() == "upload")
+ {
+ mWholeModelUploadURL = body["uploader"].asString();
+
+ if (observer)
+ {
+ body["data"]["upload_price"] = body["upload_price"];
+ observer->onModelPhysicsFeeReceived(body["data"], mWholeModelUploadURL);
+ }
+ }
+ else
+ {
+ LL_WARNS(LOG_MESH) << "Fee request failed. Not in expected 'upload' state." << LL_ENDL;
+ log_upload_error(status, body, "fee", mModelData["name"].asString());
+
+ if (observer)
+ {
+ observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason);
+ }
+ }
+ }
+ }
}
+
void LLMeshRepoThread::notifyLoadedMeshes()
{
+ bool update_metrics(false);
+
if (!mMutex)
{
return;
@@ -1805,10 +2498,16 @@ void LLMeshRepoThread::notifyLoadedMeshes()
while (!mLoadedQ.empty())
{
mMutex->lock();
+ if (mLoadedQ.empty())
+ {
+ mMutex->unlock();
+ break;
+ }
LoadedMesh mesh = mLoadedQ.front();
mLoadedQ.pop();
mMutex->unlock();
+ update_metrics = true;
if (mesh.mVolume && mesh.mVolume->getNumVolumeFaces() > 0)
{
gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume);
@@ -1823,24 +2522,59 @@ void LLMeshRepoThread::notifyLoadedMeshes()
while (!mUnavailableQ.empty())
{
mMutex->lock();
+ if (mUnavailableQ.empty())
+ {
+ mMutex->unlock();
+ break;
+ }
+
LODRequest req = mUnavailableQ.front();
mUnavailableQ.pop();
mMutex->unlock();
-
+
+ update_metrics = true;
gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD);
}
- while (!mSkinInfoQ.empty())
+ if (! mSkinInfoQ.empty() || ! mDecompositionQ.empty())
{
- gMeshRepo.notifySkinInfoReceived(mSkinInfoQ.front());
- mSkinInfoQ.pop();
+ if (mMutex->trylock())
+ {
+ std::list<LLMeshSkinInfo> skin_info_q;
+ std::list<LLModel::Decomposition*> decomp_q;
+
+ if (! mSkinInfoQ.empty())
+ {
+ skin_info_q.swap(mSkinInfoQ);
+ }
+ if (! mDecompositionQ.empty())
+ {
+ decomp_q.swap(mDecompositionQ);
+ }
+
+ mMutex->unlock();
+
+ // Process the elements free of the lock
+ while (! skin_info_q.empty())
+ {
+ gMeshRepo.notifySkinInfoReceived(skin_info_q.front());
+ skin_info_q.pop_front();
+ }
+
+ while (! decomp_q.empty())
+ {
+ gMeshRepo.notifyDecompositionReceived(decomp_q.front());
+ decomp_q.pop_front();
+ }
+ }
}
- while (!mDecompositionQ.empty())
+ if (update_metrics)
{
- gMeshRepo.notifyDecompositionReceived(mDecompositionQ.front());
- mDecompositionQ.pop();
+ // Ping time-to-load metrics for mesh download operations.
+ LLMeshRepository::metricsProgress(0);
}
+
}
S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
@@ -1919,245 +2653,250 @@ void LLMeshRepository::cacheOutgoingMesh(LLMeshUploadData& data, LLSD& header)
}
-void LLMeshLODResponder::completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
+void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response)
{
mProcessed = true;
-
- // thread could have already be destroyed during logout
- if( !gMeshRepo.mThread )
- {
- return;
- }
-
- S32 data_size = buffer->countAfter(channels.in(), NULL);
- if (status < 200 || status > 400)
+ unsigned int retries(0U);
+ response->getRetries(NULL, &retries);
+ LLMeshRepository::sHTTPRetryCount += retries;
+
+ LLCore::HttpStatus status(response->getStatus());
+ if (! status || MESH_HTTP_RESPONSE_FAILED)
{
- llwarns << status << ": " << reason << llendl;
+ processFailure(status);
+ ++LLMeshRepository::sHTTPErrorCount;
}
-
- if (data_size < mRequestedBytes)
+ else
{
- if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE)
- { //timeout or service unavailable, try again
- llwarns << "Timeout or service unavailable, retrying." << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD);
- }
- else
+ // From texture fetch code and may apply here:
+ //
+ // A warning about partial (HTTP 206) data. Some grid services
+ // do *not* return a 'Content-Range' header in the response to
+ // Range requests with a 206 status. We're forced to assume
+ // we get what we asked for in these cases until we can fix
+ // the services.
+ //
+ // May also need to deal with 200 status (full asset returned
+ // rather than partial) and 416 (request completely unsatisfyable).
+ // Always been exposed to these but are less likely here where
+ // speculative loads aren't done.
+ static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT);
+
+ if (par_status != status)
{
- llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint
- llwarns << "Unhandled status " << status << llendl;
+ LL_WARNS_ONCE(LOG_MESH) << "Non-206 successful status received for fetch: "
+ << status.toTerseString() << LL_ENDL;
}
- return;
- }
+
+ LLCore::BufferArray * body(response->getBody());
+ S32 data_size(body ? body->size() : 0);
+ U8 * data(NULL);
- LLMeshRepository::sBytesReceived += mRequestedBytes;
+ if (data_size > 0)
+ {
+ // *TODO: Try to get rid of data copying and add interfaces
+ // that support BufferArray directly. Introduce a two-phase
+ // handler, optional first that takes a body, fallback second
+ // that requires a temporary allocation and data copy.
+ data = new U8[data_size];
+ body->read(0, (char *) data, data_size);
+ LLMeshRepository::sBytesReceived += data_size;
+ }
- U8* data = NULL;
+ processData(body, data, data_size);
- if (data_size > 0)
- {
- data = new U8[data_size];
- buffer->readAfter(channels.in(), NULL, data, data_size);
+ delete [] data;
}
- if (gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size))
- {
- //good fetch from sim, write to VFS for caching
- LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE);
+ // Release handler
+ gMeshRepo.mThread->mHttpRequestSet.erase(this);
+ delete this; // Must be last statement
+}
- S32 offset = mOffset;
- S32 size = mRequestedBytes;
- if (file.getSize() >= offset+size)
+LLMeshHeaderHandler::~LLMeshHeaderHandler()
+{
+ if (!LLApp::isQuitting())
+ {
+ if (! mProcessed)
{
- file.seek(offset);
- file.write(data, size);
- LLMeshRepository::sCacheBytesWritten += size;
+ // something went wrong, retry
+ LL_WARNS(LOG_MESH) << "Mesh header fetch canceled unexpectedly, retrying." << LL_ENDL;
+ LLMeshRepoThread::HeaderRequest req(mMeshParams);
+ LLMutexLock lock(gMeshRepo.mThread->mMutex);
+ gMeshRepo.mThread->mHeaderReqQ.push(req);
}
+ LLMeshRepoThread::decActiveHeaderRequests();
}
-
- delete [] data;
}
-void LLMeshSkinInfoResponder::completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
+void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status)
{
- mProcessed = true;
+ LL_WARNS(LOG_MESH) << "Error during mesh header handling. ID: " << mMeshParams.getSculptID()
+ << ", Reason: " << status.toString()
+ << " (" << status.toTerseString() << "). Not retrying."
+ << LL_ENDL;
- // thread could have already be destroyed during logout
- if( !gMeshRepo.mThread )
+ // Can't get the header so none of the LODs will be available
+ LLMutexLock lock(gMeshRepo.mThread->mMutex);
+ for (int i(0); i < 4; ++i)
{
- return;
+ gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, i));
}
+}
- S32 data_size = buffer->countAfter(channels.in(), NULL);
-
- if (status < 200 || status > 400)
+void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size)
+{
+ LLUUID mesh_id = mMeshParams.getSculptID();
+ bool success = (! MESH_HEADER_PROCESS_FAILED) && gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size);
+ llassert(success);
+ if (! success)
{
- llwarns << status << ": " << reason << llendl;
- }
+ // *TODO: Get real reason for parse failure here. Might we want to retry?
+ LL_WARNS(LOG_MESH) << "Unable to parse mesh header. ID: " << mesh_id
+ << ", Unknown reason. Not retrying."
+ << LL_ENDL;
- if (data_size < mRequestedBytes)
- {
- if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE)
- { //timeout or service unavailable, try again
- llwarns << "Timeout or service unavailable, retrying loadMeshSkinInfo() for " << mMeshID << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->loadMeshSkinInfo(mMeshID);
- }
- else
+ // Can't get the header so none of the LODs will be available
+ LLMutexLock lock(gMeshRepo.mThread->mMutex);
+ for (int i(0); i < 4; ++i)
{
- llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint
- llwarns << "Unhandled status " << status << llendl;
+ gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, i));
}
- return;
}
+ else if (data && data_size > 0)
+ {
+ // header was successfully retrieved from sim, cache in vfs
+ LLSD header = gMeshRepo.mThread->mMeshHeader[mesh_id];
- LLMeshRepository::sBytesReceived += mRequestedBytes;
+ S32 version = header["version"].asInteger();
- U8* data = NULL;
+ if (version <= MAX_MESH_VERSION)
+ {
+ std::stringstream str;
- if (data_size > 0)
- {
- data = new U8[data_size];
- buffer->readAfter(channels.in(), NULL, data, data_size);
- }
+ S32 lod_bytes = 0;
- if (gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size))
- {
- //good fetch from sim, write to VFS for caching
- LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
+ for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i)
+ {
+ // figure out how many bytes we'll need to reserve in the file
+ const std::string & lod_name = header_lod[i];
+ lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger());
+ }
+
+ // just in case skin info or decomposition is at the end of the file (which it shouldn't be)
+ lod_bytes = llmax(lod_bytes, header["skin"]["offset"].asInteger() + header["skin"]["size"].asInteger());
+ lod_bytes = llmax(lod_bytes, header["physics_convex"]["offset"].asInteger() + header["physics_convex"]["size"].asInteger());
- S32 offset = mOffset;
- S32 size = mRequestedBytes;
+ S32 header_bytes = (S32) gMeshRepo.mThread->mMeshHeaderSize[mesh_id];
+ S32 bytes = lod_bytes + header_bytes;
- if (file.getSize() >= offset+size)
- {
- LLMeshRepository::sCacheBytesWritten += size;
- file.seek(offset);
- file.write(data, size);
- }
- }
+
+ // It's possible for the remote asset to have more data than is needed for the local cache
+ // only allocate as much space in the VFS as is needed for the local cache
+ data_size = llmin(data_size, bytes);
- delete [] data;
-}
+ LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH, LLVFile::WRITE);
+ if (file.getMaxSize() >= bytes || file.setMaxSize(bytes))
+ {
+ LLMeshRepository::sCacheBytesWritten += data_size;
+ ++LLMeshRepository::sCacheWrites;
-void LLMeshDecompositionResponder::completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
-{
- mProcessed = true;
-
- if( !gMeshRepo.mThread )
- {
- return;
- }
+ file.write(data, data_size);
+
+ // zero out the rest of the file
+ U8 block[MESH_HEADER_SIZE];
+ memset(block, 0, sizeof(block));
- S32 data_size = buffer->countAfter(channels.in(), NULL);
+ while (bytes-file.tell() > sizeof(block))
+ {
+ file.write(block, sizeof(block));
+ }
- if (status < 200 || status > 400)
- {
- llwarns << status << ": " << reason << llendl;
+ S32 remaining = bytes-file.tell();
+ if (remaining > 0)
+ {
+ file.write(block, remaining);
+ }
+ }
+ }
}
+}
- if (data_size < mRequestedBytes)
+LLMeshLODHandler::~LLMeshLODHandler()
+{
+ if (! LLApp::isQuitting())
{
- if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE)
- { //timeout or service unavailable, try again
- llwarns << "Timeout or service unavailable, retrying loadMeshDecomposition() for " << mMeshID << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->loadMeshDecomposition(mMeshID);
- }
- else
+ if (! mProcessed)
{
- llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint
- llwarns << "Unhandled status " << status << llendl;
+ LL_WARNS(LOG_MESH) << "Mesh LOD fetch canceled unexpectedly, retrying." << LL_ENDL;
+ gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD);
}
- return;
+ LLMeshRepoThread::decActiveLODRequests();
}
+}
- LLMeshRepository::sBytesReceived += mRequestedBytes;
-
- U8* data = NULL;
+void LLMeshLODHandler::processFailure(LLCore::HttpStatus status)
+{
+ LL_WARNS(LOG_MESH) << "Error during mesh LOD handling. ID: " << mMeshParams.getSculptID()
+ << ", Reason: " << status.toString()
+ << " (" << status.toTerseString() << "). Not retrying."
+ << LL_ENDL;
- if (data_size > 0)
- {
- data = new U8[data_size];
- buffer->readAfter(channels.in(), NULL, data, data_size);
- }
+ LLMutexLock lock(gMeshRepo.mThread->mMutex);
+ gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, mLOD));
+}
- if (gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size))
+void LLMeshLODHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size)
+{
+ if ((! MESH_LOD_PROCESS_FAILED) && gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size))
{
- //good fetch from sim, write to VFS for caching
- LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
+ // good fetch from sim, write to VFS for caching
+ LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE);
S32 offset = mOffset;
S32 size = mRequestedBytes;
if (file.getSize() >= offset+size)
{
- LLMeshRepository::sCacheBytesWritten += size;
file.seek(offset);
file.write(data, size);
+ LLMeshRepository::sCacheBytesWritten += size;
+ ++LLMeshRepository::sCacheWrites;
}
}
-
- delete [] data;
-}
-
-void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
-{
- mProcessed = true;
-
- // thread could have already be destroyed during logout
- if( !gMeshRepo.mThread )
- {
- return;
- }
-
- S32 data_size = buffer->countAfter(channels.in(), NULL);
-
- if (status < 200 || status > 400)
- {
- llwarns << status << ": " << reason << llendl;
- }
-
- if (data_size < mRequestedBytes)
+ else
{
- if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE)
- { //timeout or service unavailable, try again
- llwarns << "Timeout or service unavailable, retrying loadMeshPhysicsShape() for " << mMeshID << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID);
- }
- else
- {
- llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint
- llwarns << "Unhandled status " << status << llendl;
- }
- return;
+ LL_WARNS(LOG_MESH) << "Error during mesh LOD processing. ID: " << mMeshParams.getSculptID()
+ << ", Unknown reason. Not retrying."
+ << LL_ENDL;
+ LLMutexLock lock(gMeshRepo.mThread->mMutex);
+ gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, mLOD));
}
+}
- LLMeshRepository::sBytesReceived += mRequestedBytes;
+LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler()
+{
+ llassert(mProcessed);
+}
- U8* data = NULL;
+void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status)
+{
+ LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. ID: " << mMeshID
+ << ", Reason: " << status.toString()
+ << " (" << status.toTerseString() << "). Not retrying."
+ << LL_ENDL;
- if (data_size > 0)
- {
- data = new U8[data_size];
- buffer->readAfter(channels.in(), NULL, data, data_size);
- }
+ // *TODO: Mark mesh unavailable on error. For now, simply leave
+ // request unfulfilled rather than retry forever.
+}
- if (gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size))
+void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size)
+{
+ if ((! MESH_SKIN_INFO_PROCESS_FAILED) && gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size))
{
- //good fetch from sim, write to VFS for caching
+ // good fetch from sim, write to VFS for caching
LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
S32 offset = mOffset;
@@ -2166,145 +2905,108 @@ void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& re
if (file.getSize() >= offset+size)
{
LLMeshRepository::sCacheBytesWritten += size;
+ ++LLMeshRepository::sCacheWrites;
file.seek(offset);
file.write(data, size);
}
}
-
- delete [] data;
+ else
+ {
+ LL_WARNS(LOG_MESH) << "Error during mesh skin info processing. ID: " << mMeshID
+ << ", Unknown reason. Not retrying."
+ << LL_ENDL;
+ // *TODO: Mark mesh unavailable on error
+ }
}
-void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
+LLMeshDecompositionHandler::~LLMeshDecompositionHandler()
{
- mProcessed = true;
+ llassert(mProcessed);
+}
- // thread could have already be destroyed during logout
- if( !gMeshRepo.mThread )
- {
- return;
- }
+void LLMeshDecompositionHandler::processFailure(LLCore::HttpStatus status)
+{
+ LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. ID: " << mMeshID
+ << ", Reason: " << status.toString()
+ << " (" << status.toTerseString() << "). Not retrying."
+ << LL_ENDL;
+ // *TODO: Mark mesh unavailable on error. For now, simply leave
+ // request unfulfilled rather than retry forever.
+}
- if (status < 200 || status > 400)
+void LLMeshDecompositionHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size)
+{
+ if ((! MESH_DECOMP_PROCESS_FAILED) && gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size))
{
- //llwarns
- // << "Header responder failed with status: "
- // << status << ": " << reason << llendl;
-
- // 503 (service unavailable) or 499 (internal Linden-generated error)
- // can be due to server load and can be retried
-
- // TODO*: Add maximum retry logic, exponential backoff
- // and (somewhat more optional than the others) retries
- // again after some set period of time
-
- llassert(status == HTTP_NOT_FOUND || status == HTTP_SERVICE_UNAVAILABLE || status == HTTP_REQUEST_TIME_OUT || status == HTTP_INTERNAL_ERROR);
+ // good fetch from sim, write to VFS for caching
+ LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
- if (status == HTTP_SERVICE_UNAVAILABLE || status == HTTP_REQUEST_TIME_OUT || status == HTTP_INTERNAL_ERROR)
- { //retry
- llwarns << "Timeout or service unavailable, retrying." << llendl;
- LLMeshRepository::sHTTPRetryCount++;
- LLMeshRepoThread::HeaderRequest req(mMeshParams);
- LLMutexLock lock(gMeshRepo.mThread->mMutex);
- gMeshRepo.mThread->mHeaderReqQ.push(req);
+ S32 offset = mOffset;
+ S32 size = mRequestedBytes;
- return;
- }
- else
+ if (file.getSize() >= offset+size)
{
- llwarns << "Unhandled status: " << status << llendl;
+ LLMeshRepository::sCacheBytesWritten += size;
+ ++LLMeshRepository::sCacheWrites;
+ file.seek(offset);
+ file.write(data, size);
}
}
-
- S32 data_size = buffer->countAfter(channels.in(), NULL);
-
- U8* data = NULL;
-
- if (data_size > 0)
+ else
{
- data = new U8[data_size];
- buffer->readAfter(channels.in(), NULL, data, data_size);
+ LL_WARNS(LOG_MESH) << "Error during mesh decomposition processing. ID: " << mMeshID
+ << ", Unknown reason. Not retrying."
+ << LL_ENDL;
+ // *TODO: Mark mesh unavailable on error
}
+}
- LLMeshRepository::sBytesReceived += llmin(data_size, 4096);
+LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler()
+{
+ llassert(mProcessed);
+}
- bool success = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size);
-
- llassert(success);
+void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status)
+{
+ LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. ID: " << mMeshID
+ << ", Reason: " << status.toString()
+ << " (" << status.toTerseString() << "). Not retrying."
+ << LL_ENDL;
+ // *TODO: Mark mesh unavailable on error
+}
- if (!success)
- {
- llwarns
- << "Unable to parse mesh header: "
- << status << ": " << reason << llendl;
- }
- else if (data && data_size > 0)
+void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size)
+{
+ if ((! MESH_PHYS_SHAPE_PROCESS_FAILED) && gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size))
{
- //header was successfully retrieved from sim, cache in vfs
- LLUUID mesh_id = mMeshParams.getSculptID();
- LLSD header = gMeshRepo.mThread->mMeshHeader[mesh_id];
+ // good fetch from sim, write to VFS for caching
+ LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
- S32 version = header["version"].asInteger();
+ S32 offset = mOffset;
+ S32 size = mRequestedBytes;
- if (version <= MAX_MESH_VERSION)
+ if (file.getSize() >= offset+size)
{
- std::stringstream str;
-
- S32 lod_bytes = 0;
-
- for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i)
- { //figure out how many bytes we'll need to reserve in the file
- std::string lod_name = header_lod[i];
- lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger());
- }
-
- //just in case skin info or decomposition is at the end of the file (which it shouldn't be)
- lod_bytes = llmax(lod_bytes, header["skin"]["offset"].asInteger() + header["skin"]["size"].asInteger());
- lod_bytes = llmax(lod_bytes, header["physics_convex"]["offset"].asInteger() + header["physics_convex"]["size"].asInteger());
-
- S32 header_bytes = (S32) gMeshRepo.mThread->mMeshHeaderSize[mesh_id];
- S32 bytes = lod_bytes + header_bytes;
-
-
- //it's possible for the remote asset to have more data than is needed for the local cache
- //only allocate as much space in the VFS as is needed for the local cache
- data_size = llmin(data_size, bytes);
-
- LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH, LLVFile::WRITE);
- if (file.getMaxSize() >= bytes || file.setMaxSize(bytes))
- {
- LLMeshRepository::sCacheBytesWritten += data_size;
-
- file.write((const U8*) data, data_size);
-
- //zero out the rest of the file
- U8 block[4096];
- memset(block, 0, 4096);
-
- while (bytes-file.tell() > 4096)
- {
- file.write(block, 4096);
- }
-
- S32 remaining = bytes-file.tell();
-
- if (remaining > 0)
- {
- file.write(block, remaining);
- }
- }
+ LLMeshRepository::sCacheBytesWritten += size;
+ ++LLMeshRepository::sCacheWrites;
+ file.seek(offset);
+ file.write(data, size);
}
}
-
- delete [] data;
+ else
+ {
+ LL_WARNS(LOG_MESH) << "Error during mesh physics shape processing. ID: " << mMeshID
+ << ", Unknown reason. Not retrying."
+ << LL_ENDL;
+ // *TODO: Mark mesh unavailable on error
+ }
}
-
LLMeshRepository::LLMeshRepository()
: mMeshMutex(NULL),
mMeshThreadCount(0),
- mThread(NULL)
+ mThread(NULL),
+ mGetMeshVersion(2)
{
}
@@ -2323,7 +3025,7 @@ void LLMeshRepository::init()
apr_sleep(100);
}
-
+ metrics_teleport_started_signal = LLViewerMessage::getInstance()->setTeleportStartedCallback(teleport_started);
mThread = new LLMeshRepoThread();
mThread->start();
@@ -2331,11 +3033,13 @@ void LLMeshRepository::init()
void LLMeshRepository::shutdown()
{
- llinfos << "Shutting down mesh repository." << llendl;
+ LL_INFOS(LOG_MESH) << "Shutting down mesh repository." << LL_ENDL;
+
+ metrics_teleport_started_signal.disconnect();
for (U32 i = 0; i < mUploads.size(); ++i)
{
- llinfos << "Discard the pending mesh uploads " << llendl;
+ LL_INFOS(LOG_MESH) << "Discard the pending mesh uploads." << LL_ENDL;
mUploads[i]->discard() ; //discard the uploading requests.
}
@@ -2350,7 +3054,7 @@ void LLMeshRepository::shutdown()
for (U32 i = 0; i < mUploads.size(); ++i)
{
- llinfos << "Waiting for pending mesh upload " << i << "/" << mUploads.size() << llendl;
+ LL_INFOS(LOG_MESH) << "Waiting for pending mesh upload " << (i + 1) << "/" << mUploads.size() << LL_ENDL;
while (!mUploads[i]->isStopped())
{
apr_sleep(10);
@@ -2363,7 +3067,7 @@ void LLMeshRepository::shutdown()
delete mMeshMutex;
mMeshMutex = NULL;
- llinfos << "Shutting down decomposition system." << llendl;
+ LL_INFOS(LOG_MESH) << "Shutting down decomposition system." << LL_ENDL;
if (mDecompThread)
{
@@ -2378,6 +3082,9 @@ void LLMeshRepository::shutdown()
//called in the main thread.
S32 LLMeshRepository::update()
{
+ // Conditionally log a mesh metrics event
+ metricsUpdate();
+
if(mUploadWaitList.empty())
{
return 0 ;
@@ -2397,7 +3104,12 @@ S32 LLMeshRepository::update()
S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod)
{
- if (detail < 0 || detail > 4)
+ MESH_FASTTIMER_DEFBLOCK;
+
+ // Manage time-to-load metrics for mesh download operations.
+ metricsProgress(1);
+
+ if (detail < 0 || detail >= 4)
{
return detail;
}
@@ -2475,9 +3187,32 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para
void LLMeshRepository::notifyLoadedMeshes()
{ //called from main thread
+ MESH_FASTTIMER_DEFBLOCK;
- LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests");
-
+ if (1 == mGetMeshVersion)
+ {
+ // Legacy GetMesh operation with high connection concurrency
+ LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests");
+ LLMeshRepoThread::sRequestHighWater = llclamp(2 * S32(LLMeshRepoThread::sMaxConcurrentRequests),
+ REQUEST_HIGH_WATER_MIN,
+ REQUEST_HIGH_WATER_MAX);
+ LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2,
+ REQUEST_LOW_WATER_MIN,
+ REQUEST_LOW_WATER_MAX);
+ }
+ else
+ {
+ // GetMesh2 operation with keepalives, etc. With pipelining,
+ // we'll increase this.
+ LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("Mesh2MaxConcurrentRequests");
+ LLMeshRepoThread::sRequestHighWater = llclamp(5 * S32(LLMeshRepoThread::sMaxConcurrentRequests),
+ REQUEST2_HIGH_WATER_MIN,
+ REQUEST2_HIGH_WATER_MAX);
+ LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2,
+ REQUEST2_LOW_WATER_MIN,
+ REQUEST2_LOW_WATER_MAX);
+ }
+
//clean up completed upload threads
for (std::vector<LLMeshUploadThread*>::iterator iter = mUploads.begin(); iter != mUploads.end(); )
{
@@ -2554,26 +3289,46 @@ void LLMeshRepository::notifyLoadedMeshes()
//call completed callbacks on finished decompositions
mDecompThread->notifyCompleted();
-
- if (!mThread->mWaiting)
- { //curl thread is churning, wait for it to go idle
- return;
- }
- static std::string region_name("never name a region this");
+ // For major operations, attempt to get the required locks
+ // without blocking and punt if they're not available. The
+ // longest run of holdoffs is kept in sMaxLockHoldoffs just
+ // to collect the data. In testing, I've never seen a value
+ // greater than 2 (written to log on exit).
+ {
+ LLMutexTrylock lock1(mMeshMutex);
+ LLMutexTrylock lock2(mThread->mMutex);
- if (gAgent.getRegion())
- { //update capability url
- if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived())
+ static U32 hold_offs(0);
+ if (! lock1.isLocked() || ! lock2.isLocked())
{
- region_name = gAgent.getRegion()->getName();
- mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh");
+ // If we can't get the locks, skip and pick this up later.
+ ++hold_offs;
+ sMaxLockHoldoffs = llmax(sMaxLockHoldoffs, hold_offs);
+ return;
+ }
+ hold_offs = 0;
+
+ if (gAgent.getRegion())
+ {
+ // Update capability urls
+ static std::string region_name("never name a region this");
+
+ if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived())
+ {
+ region_name = gAgent.getRegion()->getName();
+ const bool use_v1(gSavedSettings.getBOOL("MeshUseGetMesh1"));
+ const std::string mesh1(gAgent.getRegion()->getCapability("GetMesh"));
+ const std::string mesh2(gAgent.getRegion()->getCapability("GetMesh2"));
+ mGetMeshVersion = (mesh2.empty() || use_v1) ? 1 : 2;
+ mThread->setGetMeshCaps(mesh1, mesh2, mGetMeshVersion);
+ LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name
+ << "', GetMesh2: " << mesh2
+ << ", GetMesh: " << mesh1
+ << ", using version: " << mGetMeshVersion
+ << LL_ENDL;
+ }
}
- }
-
- {
- LLMutexLock lock1(mMeshMutex);
- LLMutexLock lock2(mThread->mMutex);
//popup queued error messages from background threads
while (!mUploadErrorQ.empty())
@@ -2582,47 +3337,55 @@ void LLMeshRepository::notifyLoadedMeshes()
mUploadErrorQ.pop();
}
- S32 push_count = LLMeshRepoThread::sMaxConcurrentRequests-(LLMeshRepoThread::sActiveHeaderRequests+LLMeshRepoThread::sActiveLODRequests);
-
- if (push_count > 0)
+ S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests;
+ if (active_count < LLMeshRepoThread::sRequestLowWater)
{
- //calculate "score" for pending requests
-
- //create score map
- std::map<LLUUID, F32> score_map;
+ S32 push_count = LLMeshRepoThread::sRequestHighWater - active_count;
- for (U32 i = 0; i < 4; ++i)
+ if (mPendingRequests.size() > push_count)
{
- for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter)
+ // More requests than the high-water limit allows so
+ // sort and forward the most important.
+
+ //calculate "score" for pending requests
+
+ //create score map
+ std::map<LLUUID, F32> score_map;
+
+ for (U32 i = 0; i < 4; ++i)
{
- F32 max_score = 0.f;
- for (std::set<LLUUID>::iterator obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter)
+ for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter)
{
- LLViewerObject* object = gObjectList.findObject(*obj_iter);
-
- if (object)
+ F32 max_score = 0.f;
+ for (std::set<LLUUID>::iterator obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter)
{
- LLDrawable* drawable = object->mDrawable;
- if (drawable)
+ LLViewerObject* object = gObjectList.findObject(*obj_iter);
+
+ if (object)
{
- F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f);
- max_score = llmax(max_score, cur_score);
+ LLDrawable* drawable = object->mDrawable;
+ if (drawable)
+ {
+ F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f);
+ max_score = llmax(max_score, cur_score);
+ }
}
}
- }
- score_map[iter->first.getSculptID()] = max_score;
+ score_map[iter->first.getSculptID()] = max_score;
+ }
}
- }
- //set "score" for pending requests
- for (std::vector<LLMeshRepoThread::LODRequest>::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter)
- {
- iter->mScore = score_map[iter->mMeshParams.getSculptID()];
- }
+ //set "score" for pending requests
+ for (std::vector<LLMeshRepoThread::LODRequest>::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter)
+ {
+ iter->mScore = score_map[iter->mMeshParams.getSculptID()];
+ }
- //sort by "score"
- std::sort(mPendingRequests.begin(), mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater());
+ //sort by "score"
+ std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count,
+ mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater());
+ }
while (!mPendingRequests.empty() && push_count > 0)
{
@@ -2676,9 +3439,8 @@ void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo& info)
vobj->notifyMeshLoaded();
}
}
+ mLoadingSkins.erase(info.mMeshID);
}
-
- mLoadingSkins.erase(info.mMeshID);
}
void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decomp)
@@ -2687,14 +3449,14 @@ void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decom
if (iter == mDecompositionMap.end())
{ //just insert decomp into map
mDecompositionMap[decomp->mMeshID] = decomp;
+ mLoadingDecompositions.erase(decomp->mMeshID);
}
else
{ //merge decomp with existing entry
iter->second->merge(decomp);
+ mLoadingDecompositions.erase(decomp->mMeshID);
delete decomp;
}
-
- mLoadingDecompositions.erase(decomp->mMeshID);
}
void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume)
@@ -2709,7 +3471,8 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol
//make sure target volume is still valid
if (volume->getNumVolumeFaces() <= 0)
{
- llwarns << "Mesh loading returned empty volume." << llendl;
+ LL_WARNS(LOG_MESH) << "Mesh loading returned empty volume. ID: " << mesh_params.getSculptID()
+ << LL_ENDL;
}
{ //update system volume
@@ -2722,7 +3485,8 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol
}
else
{
- llwarns << "Couldn't find system volume for given mesh." << llendl;
+ LL_WARNS(LOG_MESH) << "Couldn't find system volume for mesh " << mesh_params.getSculptID()
+ << LL_ENDL;
}
}
@@ -2776,6 +3540,8 @@ S32 LLMeshRepository::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lo
const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, const LLVOVolume* requesting_obj)
{
+ MESH_FASTTIMER_DEFBLOCK;
+
if (mesh_id.notNull())
{
skin_map::iterator iter = mSkinMap.find(mesh_id);
@@ -2802,6 +3568,8 @@ const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, const
void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id)
{
+ MESH_FASTTIMER_DEFBLOCK;
+
if (mesh_id.notNull())
{
LLModel::Decomposition* decomp = NULL;
@@ -2819,6 +3587,7 @@ void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id)
std::set<LLUUID>::iterator iter = mLoadingPhysicsShapes.find(mesh_id);
if (iter == mLoadingPhysicsShapes.end())
{ //no request pending for this skin info
+ // *FIXME: Nothing ever deletes entries, can't be right
mLoadingPhysicsShapes.insert(mesh_id);
mPendingPhysicsShapeRequests.push(mesh_id);
}
@@ -2829,6 +3598,8 @@ void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id)
LLModel::Decomposition* LLMeshRepository::getDecomposition(const LLUUID& mesh_id)
{
+ MESH_FASTTIMER_DEFBLOCK;
+
LLModel::Decomposition* ret = NULL;
if (mesh_id.notNull())
@@ -2891,6 +3662,8 @@ bool LLMeshRepository::hasPhysicsShape(const LLUUID& mesh_id)
LLSD& LLMeshRepository::getMeshHeader(const LLUUID& mesh_id)
{
+ MESH_FASTTIMER_DEFBLOCK;
+
return mThread->getMeshHeader(mesh_id);
}
@@ -2912,7 +3685,7 @@ LLSD& LLMeshRepoThread::getMeshHeader(const LLUUID& mesh_id)
void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, LLVector3& scale, bool upload_textures,
- bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload,
+ bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload,
LLHandle<LLWholeModelFeeObserver> fee_observer, LLHandle<LLWholeModelUploadObserver> upload_observer)
{
LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures, upload_skin, upload_joints, upload_url,
@@ -2941,7 +3714,6 @@ S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod)
}
return -1;
-
}
void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation,
@@ -3206,7 +3978,7 @@ void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh, bool vertex_based)
if (ret)
{
- llerrs << "Convex Decomposition thread valid but could not set mesh data" << llendl;
+ LL_ERRS(LOG_MESH) << "Convex Decomposition thread valid but could not set mesh data." << LL_ENDL;
}
}
}
@@ -3282,7 +4054,8 @@ void LLPhysicsDecomp::doDecomposition()
if (ret)
{
- llwarns << "Convex Decomposition thread valid but could not execute stage " << stage << llendl;
+ LL_WARNS(LOG_MESH) << "Convex Decomposition thread valid but could not execute stage " << stage << "."
+ << LL_ENDL;
LLMutexLock lock(mMutex);
mCurRequest->mHull.clear();
@@ -3411,9 +4184,9 @@ void LLPhysicsDecomp::doDecompositionSingleHull()
setMeshData(mesh, true);
LLCDResult ret = decomp->buildSingleHull() ;
- if(ret)
+ if (ret)
{
- llwarns << "Could not execute decomposition stage when attempting to create single hull." << llendl;
+ LL_WARNS(LOG_MESH) << "Could not execute decomposition stage when attempting to create single hull." << LL_ENDL;
make_box(mCurRequest);
}
else
@@ -3718,3 +4491,63 @@ bool LLMeshRepository::meshRezEnabled()
}
return false;
}
+
+// Threading: main thread only
+// static
+void LLMeshRepository::metricsStart()
+{
+ ++metrics_teleport_start_count;
+ sQuiescentTimer.start(0);
+}
+
+// Threading: main thread only
+// static
+void LLMeshRepository::metricsStop()
+{
+ sQuiescentTimer.stop(0);
+}
+
+// Threading: main thread only
+// static
+void LLMeshRepository::metricsProgress(unsigned int this_count)
+{
+ static bool first_start(true);
+
+ if (first_start)
+ {
+ metricsStart();
+ first_start = false;
+ }
+ sQuiescentTimer.ringBell(0, this_count);
+}
+
+// Threading: main thread only
+// static
+void LLMeshRepository::metricsUpdate()
+{
+ F64 started, stopped;
+ U64 total_count(U64L(0)), user_cpu(U64L(0)), sys_cpu(U64L(0));
+
+ if (sQuiescentTimer.isExpired(0, started, stopped, total_count, user_cpu, sys_cpu))
+ {
+ LLSD metrics;
+
+ metrics["reason"] = "Mesh Download Quiescent";
+ metrics["scope"] = metrics_teleport_start_count > 1 ? "Teleport" : "Login";
+ metrics["start"] = started;
+ metrics["stop"] = stopped;
+ metrics["fetches"] = LLSD::Integer(total_count);
+ metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count);
+ metrics["user_cpu"] = double(user_cpu) / 1.0e6;
+ metrics["sys_cpu"] = double(sys_cpu) / 1.0e6;
+ LL_INFOS(LOG_MESH) << "EventMarker " << metrics << LL_ENDL;
+ }
+}
+
+// Threading: main thread only
+// static
+void teleport_started()
+{
+ LLMeshRepository::metricsStart();
+}
+
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index 8eaf691d6f..39280bea3a 100755
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2010-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -32,6 +32,12 @@
#include "lluuid.h"
#include "llviewertexture.h"
#include "llvolume.h"
+#include "lldeadmantimer.h"
+#include "httpcommon.h"
+#include "httprequest.h"
+#include "httpoptions.h"
+#include "httpheaders.h"
+#include "httphandler.h"
#define LLCONVEXDECOMPINTER_STATIC 1
@@ -39,8 +45,6 @@
#include "lluploadfloaterobservers.h"
class LLVOVolume;
-class LLMeshResponder;
-class LLCurlRequest;
class LLMutex;
class LLCondition;
class LLVFS;
@@ -215,17 +219,17 @@ class LLMeshRepoThread : public LLThread
{
public:
- static S32 sActiveHeaderRequests;
- static S32 sActiveLODRequests;
+ volatile static S32 sActiveHeaderRequests;
+ volatile static S32 sActiveLODRequests;
static U32 sMaxConcurrentRequests;
+ static S32 sRequestLowWater;
+ static S32 sRequestHighWater;
+ static S32 sRequestWaterLevel; // Stats-use only, may read outside of thread
- LLCurlRequest* mCurlRequest;
LLMutex* mMutex;
LLMutex* mHeaderMutex;
LLCondition* mSignal;
- bool mWaiting;
-
//map of known mesh headers
typedef std::map<LLUUID, LLSD> mesh_header_map;
mesh_header_map mMeshHeader;
@@ -287,8 +291,8 @@ public:
//set of requested skin info
std::set<LLUUID> mSkinRequests;
- //queue of completed skin info requests
- std::queue<LLMeshSkinInfo> mSkinInfoQ;
+ // list of completed skin info requests
+ std::list<LLMeshSkinInfo> mSkinInfoQ;
//set of requested decompositions
std::set<LLUUID> mDecompositionRequests;
@@ -296,8 +300,8 @@ public:
//set of requested physics shapes
std::set<LLUUID> mPhysicsShapeRequests;
- //queue of completed Decomposition info requests
- std::queue<LLModel::Decomposition*> mDecompositionQ;
+ // list of completed Decomposition info requests
+ std::list<LLModel::Decomposition*> mDecompositionQ;
//queue of requested headers
std::queue<HeaderRequest> mHeaderReqQ;
@@ -315,7 +319,23 @@ public:
typedef std::map<LLVolumeParams, std::vector<S32> > pending_lod_map;
pending_lod_map mPendingLOD;
- static std::string constructUrl(LLUUID mesh_id);
+ // llcorehttp library interface objects.
+ LLCore::HttpStatus mHttpStatus;
+ LLCore::HttpRequest * mHttpRequest;
+ LLCore::HttpOptions * mHttpOptions;
+ LLCore::HttpOptions * mHttpLargeOptions;
+ LLCore::HttpHeaders * mHttpHeaders;
+ LLCore::HttpRequest::policy_t mHttpPolicyClass;
+ LLCore::HttpRequest::policy_t mHttpLegacyPolicyClass;
+ LLCore::HttpRequest::policy_t mHttpLargePolicyClass;
+ LLCore::HttpRequest::priority_t mHttpPriority;
+
+ typedef std::set<LLCore::HttpHandler *> http_request_set;
+ http_request_set mHttpRequestSet; // Outstanding HTTP requests
+
+ std::string mGetMeshCapability;
+ std::string mGetMesh2Capability;
+ int mGetMeshVersion;
LLMeshRepoThread();
~LLMeshRepoThread();
@@ -325,8 +345,8 @@ public:
void lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod);
void loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod);
- bool fetchMeshHeader(const LLVolumeParams& mesh_params, U32& count);
- bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count);
+ bool fetchMeshHeader(const LLVolumeParams& mesh_params);
+ bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod);
bool headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size);
bool lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size);
bool skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size);
@@ -358,9 +378,37 @@ public:
static void incActiveHeaderRequests();
static void decActiveHeaderRequests();
+ // Set the caps strings and preferred version for constructing
+ // mesh fetch URLs.
+ //
+ // Mutex: must be holding mMutex when called
+ void setGetMeshCaps(const std::string & get_mesh1,
+ const std::string & get_mesh2,
+ int pref_version);
+
+ // Mutex: acquires mMutex
+ void constructUrl(LLUUID mesh_id, std::string * url, int * version);
+
+private:
+ // Issue a GET request to a URL with 'Range' header using
+ // the correct policy class and other attributes. If an invalid
+ // handle is returned, the request failed and caller must retry
+ // or dispose of handler.
+ //
+ // Threads: Repo thread only
+ LLCore::HttpHandle getByteRange(const std::string & url, int cap_version,
+ size_t offset, size_t len,
+ LLCore::HttpHandler * handler);
};
-class LLMeshUploadThread : public LLThread
+
+// Class whose instances represent a single upload-type request for
+// meshes: one fee query or one actual upload attempt. Yes, it creates
+// a unique thread for that single request. As it is 1:1, it can also
+// trivially serve as the HttpHandler object for request completion
+// notifications.
+
+class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler
{
private:
S32 mMeshUploadTimeOut ; //maximum time in seconds to execute an uploading request.
@@ -381,44 +429,41 @@ public:
};
LLPointer<DecompRequest> mFinalDecomp;
- bool mPhysicsComplete;
+ volatile bool mPhysicsComplete;
typedef std::map<LLPointer<LLModel>, std::vector<LLVector3> > hull_map;
- hull_map mHullMap;
+ hull_map mHullMap;
typedef std::vector<LLModelInstance> instance_list;
- instance_list mInstanceList;
+ instance_list mInstanceList;
typedef std::map<LLPointer<LLModel>, instance_list> instance_map;
- instance_map mInstance;
+ instance_map mInstance;
- LLMutex* mMutex;
- LLCurlRequest* mCurlRequest;
+ LLMutex* mMutex;
S32 mPendingUploads;
LLVector3 mOrigin;
bool mFinished;
bool mUploadTextures;
bool mUploadSkin;
bool mUploadJoints;
- BOOL mDiscarded ;
+ volatile bool mDiscarded;
LLHost mHost;
std::string mWholeModelFeeCapability;
std::string mWholeModelUploadURL;
LLMeshUploadThread(instance_list& data, LLVector3& scale, bool upload_textures,
- bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload = true,
- LLHandle<LLWholeModelFeeObserver> fee_observer= (LLHandle<LLWholeModelFeeObserver>()), LLHandle<LLWholeModelUploadObserver> upload_observer = (LLHandle<LLWholeModelUploadObserver>()));
+ bool upload_skin, bool upload_joints, const std::string & upload_url, bool do_upload = true,
+ LLHandle<LLWholeModelFeeObserver> fee_observer = (LLHandle<LLWholeModelFeeObserver>()),
+ LLHandle<LLWholeModelUploadObserver> upload_observer = (LLHandle<LLWholeModelUploadObserver>()));
~LLMeshUploadThread();
- void startRequest() { ++mPendingUploads; }
- void stopRequest() { --mPendingUploads; }
-
- bool finished() { return mFinished; }
+ bool finished() const { return mFinished; }
virtual void run();
void preStart();
void discard() ;
- BOOL isDiscarded();
+ bool isDiscarded() const;
void generateHulls();
@@ -435,11 +480,23 @@ public:
void setFeeObserverHandle(LLHandle<LLWholeModelFeeObserver> observer_handle) { mFeeObserverHandle = observer_handle; }
void setUploadObserverHandle(LLHandle<LLWholeModelUploadObserver> observer_handle) { mUploadObserverHandle = observer_handle; }
+ // Inherited from LLCore::HttpHandler
+ virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response);
+
private:
LLHandle<LLWholeModelFeeObserver> mFeeObserverHandle;
LLHandle<LLWholeModelUploadObserver> mUploadObserverHandle;
bool mDoUpload; // if FALSE only model data will be requested, otherwise the model will be uploaded
+ LLSD mModelData;
+
+ // llcorehttp library interface objects.
+ LLCore::HttpStatus mHttpStatus;
+ LLCore::HttpRequest * mHttpRequest;
+ LLCore::HttpOptions * mHttpOptions;
+ LLCore::HttpHeaders * mHttpHeaders;
+ LLCore::HttpRequest::policy_t mHttpPolicyClass;
+ LLCore::HttpRequest::priority_t mHttpPriority;
};
class LLMeshRepository
@@ -448,21 +505,28 @@ public:
//metrics
static U32 sBytesReceived;
- static U32 sHTTPRequestCount;
- static U32 sHTTPRetryCount;
+ static U32 sMeshRequestCount; // Total request count, http or cached, all component types
+ static U32 sHTTPRequestCount; // Http GETs issued (not large)
+ static U32 sHTTPLargeRequestCount; // Http GETs issued for large requests
+ static U32 sHTTPRetryCount; // Total request retries whether successful or failed
+ static U32 sHTTPErrorCount; // Requests ending in error
static U32 sLODPending;
static U32 sLODProcessing;
static U32 sCacheBytesRead;
static U32 sCacheBytesWritten;
- static U32 sPeakKbps;
+ static U32 sCacheReads;
+ static U32 sCacheWrites;
+ static U32 sMaxLockHoldoffs; // Maximum sequential locking failures
+ static LLDeadmanTimer sQuiescentTimer; // Time-to-complete-mesh-downloads after significant events
+
static F32 getStreamingCost(LLSD& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL);
LLMeshRepository();
void init();
void shutdown();
- S32 update() ;
+ S32 update();
//mesh management functions
S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 detail = 0, S32 last_lod = -1);
@@ -495,6 +559,12 @@ public:
S32 getMeshSize(const LLUUID& mesh_id, S32 lod);
+ // Quiescent timer management, main thread only.
+ static void metricsStart();
+ static void metricsStop();
+ static void metricsProgress(unsigned int count);
+ static void metricsUpdate();
+
typedef std::map<LLVolumeParams, std::set<LLUUID> > mesh_load_map;
mesh_load_map mLoadingMeshes[4];
@@ -556,8 +626,7 @@ public:
void uploadError(LLSD& args);
void updateInventory(inventory_data data);
- std::string mGetMeshCapability;
-
+ int mGetMeshVersion; // Shadows value in LLMeshRepoThread
};
extern LLMeshRepository gMeshRepo;
diff --git a/indra/newview/llmoveview.cpp b/indra/newview/llmoveview.cpp
index eb6591eb39..32b168b8c5 100755
--- a/indra/newview/llmoveview.cpp
+++ b/indra/newview/llmoveview.cpp
@@ -140,7 +140,7 @@ BOOL LLFloaterMove::postBuild()
initMovementMode();
- LLViewerParcelMgr::getInstance()->addAgentParcelChangedCallback(LLFloaterMove::sUpdateFlyingStatus);
+ gAgent.addParcelChangedCallback(LLFloaterMove::sUpdateFlyingStatus);
return TRUE;
}
diff --git a/indra/newview/llpanelplaces.cpp b/indra/newview/llpanelplaces.cpp
index 6c2a01fc82..8bb3ace2d9 100755
--- a/indra/newview/llpanelplaces.cpp
+++ b/indra/newview/llpanelplaces.cpp
@@ -251,7 +251,7 @@ LLPanelPlaces::LLPanelPlaces()
gInventory.addObserver(mInventoryObserver);
- mAgentParcelChangedConnection = LLViewerParcelMgr::getInstance()->addAgentParcelChangedCallback(
+ mAgentParcelChangedConnection = gAgent.addParcelChangedCallback(
boost::bind(&LLPanelPlaces::updateVerbs, this));
//buildFromFile( "panel_places.xml"); // Called from LLRegisterPanelClass::defaultPanelClassBuilder()
diff --git a/indra/newview/llpanelteleporthistory.cpp b/indra/newview/llpanelteleporthistory.cpp
index 0756faf5c0..9c380f63bd 100755
--- a/indra/newview/llpanelteleporthistory.cpp
+++ b/indra/newview/llpanelteleporthistory.cpp
@@ -359,6 +359,11 @@ void LLTeleportHistoryPanel::ContextMenu::onInfo()
void LLTeleportHistoryPanel::ContextMenu::gotSLURLCallback(const std::string& slurl)
{
LLClipboard::instance().copyToClipboard(utf8str_to_wstring(slurl),0,slurl.size());
+
+ LLSD args;
+ args["SLURL"] = slurl;
+
+ LLNotificationsUtil::add("CopySLURL", args);
}
void LLTeleportHistoryPanel::ContextMenu::onCopyToClipboard()
diff --git a/indra/newview/llpaneltopinfobar.cpp b/indra/newview/llpaneltopinfobar.cpp
index 9dd665198f..0d09f0bbfc 100755
--- a/indra/newview/llpaneltopinfobar.cpp
+++ b/indra/newview/llpaneltopinfobar.cpp
@@ -166,7 +166,7 @@ BOOL LLPanelTopInfoBar::postBuild()
mShowCoordsCtrlConnection = ctrl->getSignal()->connect(boost::bind(&LLPanelTopInfoBar::onNavBarShowParcelPropertiesCtrlChanged, this));
}
- mParcelMgrConnection = LLViewerParcelMgr::getInstance()->addAgentParcelChangedCallback(
+ mParcelMgrConnection = gAgent.addParcelChangedCallback(
boost::bind(&LLPanelTopInfoBar::onAgentParcelChange, this));
setVisibleCallback(boost::bind(&LLPanelTopInfoBar::onVisibilityChange, this, _2));
diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp
index def26b3885..e5f2ca7e5c 100644
--- a/indra/newview/lltexturefetch.cpp
+++ b/indra/newview/lltexturefetch.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -1552,7 +1552,7 @@ bool LLTextureFetchWorker::doWork(S32 param)
else
{
llinfos << "HTTP GET failed for: " << mUrl
- << " Status: " << mGetStatus.toHex()
+ << " Status: " << mGetStatus.toTerseString()
<< " Reason: '" << mGetReason << "'"
<< llendl;
}
@@ -1896,7 +1896,7 @@ void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRe
LLCore::HttpStatus status(response->getStatus());
LL_DEBUGS("Texture") << "HTTP COMPLETE: " << mID
- << " status: " << status.toHex()
+ << " status: " << status.toTerseString()
<< " '" << status.toString() << "'"
<< llendl;
// unsigned int offset(0), length(0), full_length(0);
@@ -1912,7 +1912,7 @@ void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRe
success = false;
std::string reason(status.toString());
setGetStatus(status, reason);
- llwarns << "CURL GET FAILED, status: " << status.toHex()
+ llwarns << "CURL GET FAILED, status: " << status.toTerseString()
<< " reason: " << reason << llendl;
}
else
@@ -2376,6 +2376,7 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image
mQAMode(qa_mode),
mHttpRequest(NULL),
mHttpOptions(NULL),
+ mHttpOptionsWithHeaders(NULL),
mHttpHeaders(NULL),
mHttpMetricsHeaders(NULL),
mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID),
@@ -2406,11 +2407,13 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image
mHttpRequest = new LLCore::HttpRequest;
mHttpOptions = new LLCore::HttpOptions;
+ mHttpOptionsWithHeaders = new LLCore::HttpOptions;
+ mHttpOptionsWithHeaders->setWantHeaders(true);
mHttpHeaders = new LLCore::HttpHeaders;
- mHttpHeaders->mHeaders.push_back("Accept: image/x-j2c");
+ mHttpHeaders->append("Accept", "image/x-j2c");
mHttpMetricsHeaders = new LLCore::HttpHeaders;
- mHttpMetricsHeaders->mHeaders.push_back("Content-Type: application/llsd+xml");
- mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicyDefault();
+ mHttpMetricsHeaders->append("Content-Type", "application/llsd+xml");
+ mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_TEXTURE);
}
LLTextureFetch::~LLTextureFetch()
@@ -2430,6 +2433,12 @@ LLTextureFetch::~LLTextureFetch()
mHttpOptions = NULL;
}
+ if (mHttpOptionsWithHeaders)
+ {
+ mHttpOptionsWithHeaders->release();
+ mHttpOptionsWithHeaders = NULL;
+ }
+
if (mHttpHeaders)
{
mHttpHeaders->release();
@@ -3772,7 +3781,7 @@ public:
else
{
LL_WARNS("Texture") << "Error delivering asset metrics to grid. Status: "
- << status.toHex()
+ << status.toTerseString()
<< ", Reason: " << status.toString() << LL_ENDL;
}
}
@@ -4041,7 +4050,7 @@ void LLTextureFetchDebugger::init()
if (! mHttpHeaders)
{
mHttpHeaders = new LLCore::HttpHeaders;
- mHttpHeaders->mHeaders.push_back("Accept: image/x-j2c");
+ mHttpHeaders->append("Accept", "image/x-j2c");
}
}
@@ -4461,7 +4470,7 @@ S32 LLTextureFetchDebugger::fillCurlQueue()
LL_WARNS("Texture") << "Couldn't issue HTTP request in debugger for texture "
<< mFetchingHistory[i].mID
- << ", status: " << status.toHex()
+ << ", status: " << status.toTerseString()
<< " reason: " << status.toString()
<< LL_ENDL;
mFetchingHistory[i].mCurlState = FetchEntry::CURL_DONE;
@@ -4854,7 +4863,7 @@ void LLTextureFetchDebugger::callbackHTTP(FetchEntry & fetch, LLCore::HttpRespon
else //failed
{
llinfos << "Fetch Debugger : CURL GET FAILED, ID = " << fetch.mID
- << ", status: " << status.toHex()
+ << ", status: " << status.toTerseString()
<< " reason: " << status.toString() << llendl;
}
}
diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h
index 902a3d7a25..3c79a5a24d 100755
--- a/indra/newview/lltexturefetch.h
+++ b/indra/newview/lltexturefetch.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -351,6 +351,7 @@ private:
// LLCurl interfaces used in the past.
LLCore::HttpRequest * mHttpRequest; // Ttf
LLCore::HttpOptions * mHttpOptions; // Ttf
+ LLCore::HttpOptions * mHttpOptionsWithHeaders; // Ttf
LLCore::HttpHeaders * mHttpHeaders; // Ttf
LLCore::HttpHeaders * mHttpMetricsHeaders; // Ttf
LLCore::HttpRequest::policy_t mHttpPolicyClass; // T*
diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp
index e80136b286..50edbb61a8 100755
--- a/indra/newview/lltextureview.cpp
+++ b/indra/newview/lltextureview.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -49,6 +49,7 @@
#include "llviewertexturelist.h"
#include "llvovolume.h"
#include "llviewerstats.h"
+#include "llmeshrepository.h"
// For avatar texture view
#include "llvoavatarself.h"
@@ -517,6 +518,8 @@ void LLGLTexMemBar::draw()
F32 total_texture_downloaded = (F32)gTotalTextureBytes / (1024 * 1024);
F32 total_object_downloaded = (F32)gTotalObjectBytes / (1024 * 1024);
U32 total_http_requests = LLAppViewer::getTextureFetch()->getTotalNumHTTPRequests();
+ F32 x_right = 0.0;
+
//----------------------------------------------------------------------------
LLGLSUIDefault gls_ui;
LLColor4 text_color(1.f, 1.f, 1.f, 0.75f);
@@ -543,7 +546,7 @@ void LLGLTexMemBar::draw()
cache_max_usage);
//, cache_entries, cache_max_entries
- LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*4,
+ LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*5,
text_color, LLFontGL::LEFT, LLFontGL::TOP);
U32 cache_read(0U), cache_write(0U), res_wait(0U);
@@ -557,13 +560,12 @@ void LLGLTexMemBar::draw()
cache_write,
res_wait);
- LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3,
+ LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*4,
text_color, LLFontGL::LEFT, LLFontGL::TOP);
- S32 left = 0 ;
//----------------------------------------------------------------------------
- text = llformat("Textures: %d Fetch: %d(%d) Pkts:%d(%d) Cache R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d",
+ text = llformat("Textures: %d Fetch: %d(%d) Pkts:%d(%d) Cache R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d ",
gTextureList.getNumImages(),
LLAppViewer::getTextureFetch()->getNumRequests(), LLAppViewer::getTextureFetch()->getNumDeletes(),
LLAppViewer::getTextureFetch()->mPacketCount, LLAppViewer::getTextureFetch()->mBadPacketCount,
@@ -574,19 +576,30 @@ void LLGLTexMemBar::draw()
LLAppViewer::getImageDecodeThread()->getPending(),
gTextureList.mCreateTextureList.size());
- LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*2,
- text_color, LLFontGL::LEFT, LLFontGL::TOP);
-
+ x_right = 550.0;
+ LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3,
+ text_color, LLFontGL::LEFT, LLFontGL::TOP,
+ LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX,
+ &x_right, FALSE);
- left = 550;
F32 bandwidth = LLAppViewer::getTextureFetch()->getTextureBandwidth();
F32 max_bandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS");
- color = bandwidth > max_bandwidth ? LLColor4::red : bandwidth > max_bandwidth*.75f ? LLColor4::yellow : text_color;
+ color = bandwidth > max_bandwidth ? LLColor4::red : bandwidth > max_bandwidth * .75f ? LLColor4::yellow : text_color;
color[VALPHA] = text_color[VALPHA];
- text = llformat("BW:%.0f/%.0f",bandwidth, max_bandwidth);
- LLFontGL::getFontMonospace()->renderUTF8(text, 0, left, v_offset + line_height*2,
+ text = llformat("BW:%.0f/%.0f", bandwidth, max_bandwidth);
+ LLFontGL::getFontMonospace()->renderUTF8(text, 0, x_right, v_offset + line_height*3,
color, LLFontGL::LEFT, LLFontGL::TOP);
-
+
+ // Mesh status line
+ text = llformat("Mesh: Reqs(Tot/Htp/Big): %u/%u/%u Rtr/Err: %u/%u Cread/Cwrite: %u/%u Low/At/High: %d/%d/%d",
+ LLMeshRepository::sMeshRequestCount, LLMeshRepository::sHTTPRequestCount, LLMeshRepository::sHTTPLargeRequestCount,
+ LLMeshRepository::sHTTPRetryCount, LLMeshRepository::sHTTPErrorCount,
+ LLMeshRepository::sCacheReads, LLMeshRepository::sCacheWrites,
+ LLMeshRepoThread::sRequestLowWater, LLMeshRepoThread::sRequestWaterLevel, LLMeshRepoThread::sRequestHighWater);
+ LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*2,
+ text_color, LLFontGL::LEFT, LLFontGL::TOP);
+
+ // Header for texture table columns
S32 dx1 = 0;
if (LLAppViewer::getTextureFetch()->mDebugPause)
{
@@ -629,7 +642,7 @@ BOOL LLGLTexMemBar::handleMouseDown(S32 x, S32 y, MASK mask)
LLRect LLGLTexMemBar::getRequiredRect()
{
LLRect rect;
- rect.mTop = 50; //LLFontGL::getFontMonospace()->getLineHeight() * 6;
+ rect.mTop = 68; //LLFontGL::getFontMonospace()->getLineHeight() * 6;
return rect;
}
diff --git a/indra/newview/lltracker.cpp b/indra/newview/lltracker.cpp
index cbd16e873d..73ceb783b5 100755
--- a/indra/newview/lltracker.cpp
+++ b/indra/newview/lltracker.cpp
@@ -167,6 +167,7 @@ void LLTracker::render3D()
}
static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white);
+ static LLUIColor map_track_color_under = LLUIColorTable::instance().getColor("MapTrackColorUnder", LLColor4::white);
// Arbitary location beacon
if( instance()->mIsTrackingLocation )
@@ -187,7 +188,7 @@ void LLTracker::render3D()
}
else
{
- renderBeacon( instance()->mTrackedPositionGlobal, map_track_color,
+ renderBeacon( instance()->mTrackedPositionGlobal, map_track_color, map_track_color_under,
instance()->mBeaconText, instance()->mTrackedLocationName );
}
}
@@ -229,7 +230,7 @@ void LLTracker::render3D()
// and back again
instance()->mHasReachedLandmark = FALSE;
}
- renderBeacon( instance()->mTrackedPositionGlobal, map_track_color,
+ renderBeacon( instance()->mTrackedPositionGlobal, map_track_color, map_track_color_under,
instance()->mBeaconText, instance()->mTrackedLandmarkName );
}
}
@@ -258,7 +259,7 @@ void LLTracker::render3D()
}
else
{
- renderBeacon( av_tracker.getGlobalPos(), map_track_color,
+ renderBeacon( av_tracker.getGlobalPos(), map_track_color, map_track_color_under,
instance()->mBeaconText, av_tracker.getName() );
}
}
@@ -412,7 +413,7 @@ const std::string& LLTracker::getTrackedLocationName()
return instance()->mTrackedLocationName;
}
-F32 pulse_func(F32 t, F32 z)
+F32 pulse_func(F32 t, F32 z, bool tracking_avatar, std::string direction)
{
if (!LLTracker::sCheesyBeacon)
{
@@ -420,8 +421,15 @@ F32 pulse_func(F32 t, F32 z)
}
t *= F_PI;
- z -= t*64.f - 256.f;
-
+ if ("DOWN" == direction)
+ {
+ z += t*64.f - 256.f;
+ }
+ else
+ {
+ z -= t*64.f - 256.f;
+ }
+
F32 a = cosf(z*F_PI/512.f)*10.0f;
a = llmax(a, 9.9f);
a -= 9.9f;
@@ -474,10 +482,78 @@ void draw_shockwave(F32 center_z, F32 t, S32 steps, LLColor4 color)
gGL.end();
}
+void LLTracker::drawBeacon(LLVector3 pos_agent, std::string direction, LLColor4 fogged_color, F32 dist)
+{
+ const U32 BEACON_VERTS = 256;
+ F32 step;
+
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+ gGL.pushMatrix();
+
+ if ("DOWN" == direction)
+ {
+ gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], pos_agent.mV[2]);
+ draw_shockwave(1024.f, gRenderStartTime.getElapsedTimeF32(), 32, fogged_color);
+ step = (5020.0f - pos_agent.mV[2]) / BEACON_VERTS;
+ }
+ else
+ {
+ gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], 0);
+ step = pos_agent.mV[2] / BEACON_VERTS;
+ }
+
+ gGL.color4fv(fogged_color.mV);
+
+ LLVector3 x_axis = LLViewerCamera::getInstance()->getLeftAxis();
+ F32 t = gRenderStartTime.getElapsedTimeF32();
+
+ for (U32 i = 0; i < BEACON_VERTS; i++)
+ {
+ F32 x = x_axis.mV[0];
+ F32 y = x_axis.mV[1];
+
+ F32 z = i * step;
+ F32 z_next = (i+1)*step;
+
+ bool tracking_avatar = getTrackingStatus() == TRACKING_AVATAR;
+ F32 a = pulse_func(t, z, tracking_avatar, direction);
+ F32 an = pulse_func(t, z_next, tracking_avatar, direction);
+
+ LLColor4 c_col = fogged_color + LLColor4(a,a,a,a);
+ LLColor4 col_next = fogged_color + LLColor4(an,an,an,an);
+ LLColor4 col_edge = fogged_color * LLColor4(a,a,a,0.0f);
+ LLColor4 col_edge_next = fogged_color * LLColor4(an,an,an,0.0f);
+
+ a *= 2.f;
+ a += 1.0f;
+
+ an *= 2.f;
+ an += 1.0f;
+
+ gGL.begin(LLRender::TRIANGLE_STRIP);
+ gGL.color4fv(col_edge.mV);
+ gGL.vertex3f(-x*a, -y*a, z);
+ gGL.color4fv(col_edge_next.mV);
+ gGL.vertex3f(-x*an, -y*an, z_next);
+
+ gGL.color4fv(c_col.mV);
+ gGL.vertex3f(0, 0, z);
+ gGL.color4fv(col_next.mV);
+ gGL.vertex3f(0, 0, z_next);
+
+ gGL.color4fv(col_edge.mV);
+ gGL.vertex3f(x*a,y*a,z);
+ gGL.color4fv(col_edge_next.mV);
+ gGL.vertex3f(x*an,y*an,z_next);
+ gGL.end();
+ }
+ gGL.popMatrix();
+}
// static
void LLTracker::renderBeacon(LLVector3d pos_global,
- const LLColor4& color,
+ const LLColor4& color,
+ const LLColor4& color_under,
LLHUDText* hud_textp,
const std::string& label )
{
@@ -497,9 +573,11 @@ void LLTracker::renderBeacon(LLVector3d pos_global,
}
LLColor4 fogged_color = color_frac * color + (1 - color_frac)*gSky.getFogColor();
+ LLColor4 fogged_color_under = color_frac * color_under + (1 - color_frac) * gSky.getFogColor();
F32 FADE_DIST = 3.f;
fogged_color.mV[3] = llmax(0.2f, llmin(0.5f,(dist-FADE_DIST)/FADE_DIST));
+ fogged_color_under.mV[3] = llmax(0.2f, llmin(0.5f,(dist-FADE_DIST)/FADE_DIST));
LLVector3 pos_agent = gAgent.getPosAgentFromGlobal(pos_global);
@@ -508,64 +586,8 @@ void LLTracker::renderBeacon(LLVector3d pos_global,
LLGLDisable cull_face(GL_CULL_FACE);
LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE);
-
- gGL.matrixMode(LLRender::MM_MODELVIEW);
- gGL.pushMatrix();
- {
- gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], pos_agent.mV[2]);
-
- draw_shockwave(1024.f, gRenderStartTime.getElapsedTimeF32(), 32, fogged_color);
-
- gGL.color4fv(fogged_color.mV);
- const U32 BEACON_VERTS = 256;
- const F32 step = 1024.0f/BEACON_VERTS;
-
- LLVector3 x_axis = LLViewerCamera::getInstance()->getLeftAxis();
- F32 t = gRenderStartTime.getElapsedTimeF32();
- F32 dr = dist/LLViewerCamera::getInstance()->getFar();
-
- for (U32 i = 0; i < BEACON_VERTS; i++)
- {
- F32 x = x_axis.mV[0];
- F32 y = x_axis.mV[1];
-
- F32 z = i * step;
- F32 z_next = (i+1)*step;
-
- F32 a = pulse_func(t, z);
- F32 an = pulse_func(t, z_next);
-
- LLColor4 c_col = fogged_color + LLColor4(a,a,a,a);
- LLColor4 col_next = fogged_color + LLColor4(an,an,an,an);
- LLColor4 col_edge = fogged_color * LLColor4(a,a,a,0.0f);
- LLColor4 col_edge_next = fogged_color * LLColor4(an,an,an,0.0f);
-
- a *= 2.f;
- a += 1.0f+dr;
-
- an *= 2.f;
- an += 1.0f+dr;
-
- gGL.begin(LLRender::TRIANGLE_STRIP);
- gGL.color4fv(col_edge.mV);
- gGL.vertex3f(-x*a, -y*a, z);
- gGL.color4fv(col_edge_next.mV);
- gGL.vertex3f(-x*an, -y*an, z_next);
-
- gGL.color4fv(c_col.mV);
- gGL.vertex3f(0, 0, z);
- gGL.color4fv(col_next.mV);
- gGL.vertex3f(0, 0, z_next);
-
- gGL.color4fv(col_edge.mV);
- gGL.vertex3f(x*a,y*a,z);
- gGL.color4fv(col_edge_next.mV);
- gGL.vertex3f(x*an,y*an,z_next);
-
- gGL.end();
- }
- }
- gGL.popMatrix();
+ LLTracker::drawBeacon(pos_agent, "DOWN", fogged_color, dist);
+ LLTracker::drawBeacon(pos_agent, "UP", fogged_color_under, dist);
std::string text;
text = llformat( "%.0f m", to_vec.magVec());
diff --git a/indra/newview/lltracker.h b/indra/newview/lltracker.h
index 8e916af315..d8d5803787 100755
--- a/indra/newview/lltracker.h
+++ b/indra/newview/lltracker.h
@@ -108,8 +108,10 @@ protected:
LLTracker();
~LLTracker();
+ static void drawBeacon(LLVector3 pos_agent, std::string direction, LLColor4 fogged_color, F32 dist);
static void renderBeacon( LLVector3d pos_global,
const LLColor4& color,
+ const LLColor4& color_under,
LLHUDText* hud_textp,
const std::string& label );
diff --git a/indra/newview/llvieweraudio.cpp b/indra/newview/llvieweraudio.cpp
index 3da934b148..826d296117 100755
--- a/indra/newview/llvieweraudio.cpp
+++ b/indra/newview/llvieweraudio.cpp
@@ -368,6 +368,7 @@ void init_audio()
gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTyping")));
gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowClose")));
gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowOpen")));
+ gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndRestart")));
}
audio_update_volume(true);
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 4ce049df03..a8eeddb798 100755
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -95,6 +95,7 @@
#include "llfloaterproperties.h"
#include "llfloaterregiondebugconsole.h"
#include "llfloaterregioninfo.h"
+#include "llfloaterregionrestarting.h"
#include "llfloaterreporter.h"
#include "llfloaterscriptdebug.h"
#include "llfloaterscriptlimits.h"
@@ -296,6 +297,7 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("reset_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterResetQueue>);
LLFloaterReg::add("region_debug_console", "floater_region_debug_console.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionDebugConsole>);
LLFloaterReg::add("region_info", "floater_region_info.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionInfo>);
+ LLFloaterReg::add("region_restarting", "floater_region_restarting.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionRestarting>);
LLFloaterReg::add("script_debug", "floater_script_debug.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterScriptDebug>);
LLFloaterReg::add("script_debug_output", "floater_script_debug_panel.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterScriptDebugOutput>);
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index ac2940fcfc..fb07ab8fbe 100755
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -1025,6 +1025,10 @@ U32 info_display_from_string(std::string info_display)
{
return LLPipeline::RENDER_DEBUG_AVATAR_VOLUME;
}
+ else if ("joints" == info_display)
+ {
+ return LLPipeline::RENDER_DEBUG_AVATAR_JOINTS;
+ }
else if ("raycast" == info_display)
{
return LLPipeline::RENDER_DEBUG_RAYCAST;
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index cd0d56152e..c7589a8523 100755
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -111,6 +111,7 @@
#include "llpanelblockedlist.h"
#include "llpanelplaceprofile.h"
#include "llviewerregion.h"
+#include "llfloaterregionrestarting.h"
#include <boost/algorithm/string/split.hpp> //
#include <boost/regex.hpp>
@@ -5742,7 +5743,6 @@ bool handle_special_notification(std::string notificationID, LLSD& llsdBlock)
std::string regionMaturity = LLViewerRegion::accessToString(regionAccess);
LLStringUtil::toLower(regionMaturity);
llsdBlock["REGIONMATURITY"] = regionMaturity;
-
bool returnValue = false;
LLNotificationPtr maturityLevelNotification;
std::string notifySuffix = "_Notify";
@@ -5912,6 +5912,7 @@ bool attempt_standard_notification(LLMessageSystem* msgsystem)
(notificationID == "RegionEntryAccessBlocked") ||
(notificationID == "LandClaimAccessBlocked") ||
(notificationID == "LandBuyAccessBlocked")
+
)
{
/*---------------------------------------------------------------------
@@ -5953,7 +5954,41 @@ bool attempt_standard_notification(LLMessageSystem* msgsystem)
snap_filename += SCREEN_HOME_FILENAME;
gViewerWindow->saveSnapshot(snap_filename, gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw(), FALSE, FALSE);
}
-
+
+ if (notificationID == "RegionRestartMinutes" ||
+ notificationID == "RegionRestartSeconds")
+ {
+ S32 seconds;
+ if (notificationID == "RegionRestartMinutes")
+ {
+ seconds = 60 * static_cast<S32>(llsdBlock["MINUTES"].asInteger());
+ }
+ else
+ {
+ seconds = static_cast<S32>(llsdBlock["SECONDS"].asInteger());
+ }
+
+ LLFloaterRegionRestarting* floaterp = LLFloaterReg::findTypedInstance<LLFloaterRegionRestarting>("region_restarting");
+
+ if (floaterp)
+ {
+ LLFloaterRegionRestarting::updateTime(seconds);
+ }
+ else
+ {
+ LLSD params;
+ params["NAME"] = llsdBlock["NAME"];
+ params["SECONDS"] = (LLSD::Integer)seconds;
+ LLFloaterRegionRestarting* restarting_floater = dynamic_cast<LLFloaterRegionRestarting*>(LLFloaterReg::showInstance("region_restarting", params));
+ if(restarting_floater)
+ {
+ restarting_floater->center();
+ }
+ }
+
+ send_sound_trigger(LLUUID(gSavedSettings.getString("UISndRestart")), 1.0f);
+ }
+
LLNotificationsUtil::add(notificationID, llsdBlock);
return true;
}
@@ -6013,7 +6048,6 @@ void process_alert_message(LLMessageSystem *msgsystem, void **user_data)
std::string message;
msgsystem->getStringFast(_PREHASH_AlertData, _PREHASH_Message, message);
-
process_special_alert_messages(message);
if (!attempt_standard_notification(msgsystem))
@@ -6037,7 +6071,6 @@ bool handle_not_age_verified_alert(const std::string &pAlertName)
bool handle_special_alerts(const std::string &pAlertName)
{
bool isHandled = false;
-
if (LLStringUtil::compareStrings(pAlertName, "NotAgeVerified") == 0)
{
@@ -6073,26 +6106,17 @@ void process_alert_core(const std::string& message, BOOL modal)
// System message is important, show in upper-right box not tip
std::string text(message.substr(1));
LLSD args;
- if (text.substr(0,17) == "RESTART_X_MINUTES")
- {
- S32 mins = 0;
- LLStringUtil::convertToS32(text.substr(18), mins);
- args["MINUTES"] = llformat("%d",mins);
- LLNotificationsUtil::add("RegionRestartMinutes", args);
- }
- else if (text.substr(0,17) == "RESTART_X_SECONDS")
- {
- S32 secs = 0;
- LLStringUtil::convertToS32(text.substr(18), secs);
- args["SECONDS"] = llformat("%d",secs);
- LLNotificationsUtil::add("RegionRestartSeconds", args);
- }
- else
+
+ // *NOTE: If the text from the server ever changes this line will need to be adjusted.
+ std::string restart_cancelled = "Region restart cancelled.";
+ if (text.substr(0, restart_cancelled.length()) == restart_cancelled)
{
- std::string new_msg =LLNotifications::instance().getGlobalString(text);
- args["MESSAGE"] = new_msg;
- LLNotificationsUtil::add("SystemMessage", args);
+ LLFloaterRegionRestarting::close();
}
+
+ std::string new_msg =LLNotifications::instance().getGlobalString(text);
+ args["MESSAGE"] = new_msg;
+ LLNotificationsUtil::add("SystemMessage", args);
}
else if (modal)
{
diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp
index 4cdb568d17..e361fad9de 100755
--- a/indra/newview/llviewerparcelmgr.cpp
+++ b/indra/newview/llviewerparcelmgr.cpp
@@ -1580,7 +1580,8 @@ void LLViewerParcelMgr::processParcelProperties(LLMessageSystem *msg, void **use
// Let interesting parties know about agent parcel change.
LLViewerParcelMgr* instance = LLViewerParcelMgr::getInstance();
- instance->mAgentParcelChangedSignal();
+ // Notify anything that wants to know when the agent changes parcels
+ gAgent.changeParcels();
if (instance->mTeleportInProgress)
{
@@ -2458,10 +2459,6 @@ LLViewerTexture* LLViewerParcelMgr::getPassImage() const
return sPassImage;
}
-boost::signals2::connection LLViewerParcelMgr::addAgentParcelChangedCallback(parcel_changed_callback_t cb)
-{
- return mAgentParcelChangedSignal.connect(cb);
-}
/*
* Set finish teleport callback. You can use it to observe all teleport events.
* NOTE:
@@ -2475,7 +2472,7 @@ boost::signals2::connection LLViewerParcelMgr::setTeleportFinishedCallback(telep
return mTeleportFinishedSignal.connect(cb);
}
-boost::signals2::connection LLViewerParcelMgr::setTeleportFailedCallback(parcel_changed_callback_t cb)
+boost::signals2::connection LLViewerParcelMgr::setTeleportFailedCallback(teleport_failed_callback_t cb)
{
return mTeleportFailedSignal.connect(cb);
}
diff --git a/indra/newview/llviewerparcelmgr.h b/indra/newview/llviewerparcelmgr.h
index 6183b7e90e..9da49bb3f3 100755
--- a/indra/newview/llviewerparcelmgr.h
+++ b/indra/newview/llviewerparcelmgr.h
@@ -80,8 +80,8 @@ class LLViewerParcelMgr : public LLSingleton<LLViewerParcelMgr>
public:
typedef boost::function<void (const LLVector3d&, const bool& local)> teleport_finished_callback_t;
typedef boost::signals2::signal<void (const LLVector3d&, const bool&)> teleport_finished_signal_t;
- typedef boost::function<void()> parcel_changed_callback_t;
- typedef boost::signals2::signal<void()> parcel_changed_signal_t;
+ typedef boost::function<void()> teleport_failed_callback_t;
+ typedef boost::signals2::signal<void()> teleport_failed_signal_t;
LLViewerParcelMgr();
~LLViewerParcelMgr();
@@ -283,9 +283,8 @@ public:
// the agent is banned or not in the allowed group
BOOL isCollisionBanned();
- boost::signals2::connection addAgentParcelChangedCallback(parcel_changed_callback_t cb);
boost::signals2::connection setTeleportFinishedCallback(teleport_finished_callback_t cb);
- boost::signals2::connection setTeleportFailedCallback(parcel_changed_callback_t cb);
+ boost::signals2::connection setTeleportFailedCallback(teleport_failed_callback_t cb);
void onTeleportFinished(bool local, const LLVector3d& new_pos);
void onTeleportFailed();
@@ -338,8 +337,7 @@ private:
BOOL mTeleportInProgress;
teleport_finished_signal_t mTeleportFinishedSignal;
- parcel_changed_signal_t mTeleportFailedSignal;
- parcel_changed_signal_t mAgentParcelChangedSignal;
+ teleport_failed_signal_t mTeleportFailedSignal;
// Array of pieces of parcel edges to potentially draw
// Has (parcels_per_edge + 1) * (parcels_per_edge + 1) elements so
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index 6018c2d250..c6ae7d7fa0 100755
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2010-2013, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -1601,6 +1601,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
capabilityNames.append("GetDisplayNames");
capabilityNames.append("GetMesh");
+ capabilityNames.append("GetMesh2");
capabilityNames.append("GetObjectCost");
capabilityNames.append("GetObjectPhysicsData");
capabilityNames.append("GetTexture");
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index c3c1edb0a3..1e7d1644b2 100755
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -1392,9 +1392,11 @@ void LLVOAvatar::getSpatialExtents(LLVector4a& newMin, LLVector4a& newMax)
//-----------------------------------------------------------------------------
void LLVOAvatar::renderCollisionVolumes()
{
+ std::ostringstream ostr;
for (S32 i = 0; i < mNumCollisionVolumes; i++)
{
mCollisionVolumes[i].renderCollision();
+ ostr << mCollisionVolumes[i].getName() << ", ";
}
if (mNameText.notNull())
@@ -1403,6 +1405,96 @@ void LLVOAvatar::renderCollisionVolumes()
mNameText->lineSegmentIntersect(unused, unused, unused, TRUE);
}
+
+ mDebugText.clear();
+ addDebugText(ostr.str());
+}
+
+void LLVOAvatar::renderJoints()
+{
+ std::ostringstream ostr;
+ std::ostringstream nullstr;
+
+ for (joint_map_t::iterator iter = mJointMap.begin(); iter != mJointMap.end(); ++iter)
+ {
+ LLJoint* jointp = iter->second;
+ if (!jointp)
+ {
+ nullstr << iter->first << " is NULL" << std::endl;
+ continue;
+ }
+
+ ostr << jointp->getName() << ", ";
+
+ jointp->updateWorldMatrix();
+
+ gGL.pushMatrix();
+ gGL.multMatrix( &jointp->getXform()->getWorldMatrix().mMatrix[0][0] );
+
+ gGL.diffuseColor3f( 1.f, 0.f, 1.f );
+
+ gGL.begin(LLRender::LINES);
+
+ LLVector3 v[] =
+ {
+ LLVector3(1,0,0),
+ LLVector3(-1,0,0),
+ LLVector3(0,1,0),
+ LLVector3(0,-1,0),
+
+ LLVector3(0,0,-1),
+ LLVector3(0,0,1),
+ };
+
+ //sides
+ gGL.vertex3fv(v[0].mV);
+ gGL.vertex3fv(v[2].mV);
+
+ gGL.vertex3fv(v[0].mV);
+ gGL.vertex3fv(v[3].mV);
+
+ gGL.vertex3fv(v[1].mV);
+ gGL.vertex3fv(v[2].mV);
+
+ gGL.vertex3fv(v[1].mV);
+ gGL.vertex3fv(v[3].mV);
+
+
+ //top
+ gGL.vertex3fv(v[0].mV);
+ gGL.vertex3fv(v[4].mV);
+
+ gGL.vertex3fv(v[1].mV);
+ gGL.vertex3fv(v[4].mV);
+
+ gGL.vertex3fv(v[2].mV);
+ gGL.vertex3fv(v[4].mV);
+
+ gGL.vertex3fv(v[3].mV);
+ gGL.vertex3fv(v[4].mV);
+
+
+ //bottom
+ gGL.vertex3fv(v[0].mV);
+ gGL.vertex3fv(v[5].mV);
+
+ gGL.vertex3fv(v[1].mV);
+ gGL.vertex3fv(v[5].mV);
+
+ gGL.vertex3fv(v[2].mV);
+ gGL.vertex3fv(v[5].mV);
+
+ gGL.vertex3fv(v[3].mV);
+ gGL.vertex3fv(v[5].mV);
+
+ gGL.end();
+
+ gGL.popMatrix();
+ }
+
+ mDebugText.clear();
+ addDebugText(ostr.str());
+ addDebugText(nullstr.str());
}
BOOL LLVOAvatar::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
@@ -3077,9 +3169,6 @@ void LLVOAvatar::forceUpdateVisualMuteSettings()
//------------------------------------------------------------------------
BOOL LLVOAvatar::updateCharacter(LLAgent &agent)
{
- // clear debug text
- mDebugText.clear();
-
if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage"))
{
S32 central_bake_version = -1;
@@ -3588,6 +3677,7 @@ BOOL LLVOAvatar::updateCharacter(LLAgent &agent)
{
setDebugText(mDebugText);
}
+ mDebugText.clear();
//mesh vertices need to be reskinned
mNeedsSkin = TRUE;
diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h
index 9d45a74ecc..0e4121f1c4 100755
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -406,6 +406,7 @@ public:
F32 getLastSkinTime() { return mLastSkinTime; }
U32 renderTransparent(BOOL first_pass);
void renderCollisionVolumes();
+ void renderJoints();
static void deleteCachedImages(bool clearAll=true);
static void destroyGL();
static void restoreGL();
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index 9aeb2d4978..1c7154d413 100755
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -535,15 +535,16 @@ public:
RENDER_DEBUG_SHADOW_FRUSTA = 0x00040000,
RENDER_DEBUG_SCULPTED = 0x00080000,
RENDER_DEBUG_AVATAR_VOLUME = 0x00100000,
- RENDER_DEBUG_BUILD_QUEUE = 0x00200000,
- RENDER_DEBUG_AGENT_TARGET = 0x00400000,
- RENDER_DEBUG_UPDATE_TYPE = 0x00800000,
- RENDER_DEBUG_PHYSICS_SHAPES = 0x01000000,
- RENDER_DEBUG_NORMALS = 0x02000000,
- RENDER_DEBUG_LOD_INFO = 0x04000000,
- RENDER_DEBUG_RENDER_COMPLEXITY = 0x08000000,
- RENDER_DEBUG_ATTACHMENT_BYTES = 0x10000000,
- RENDER_DEBUG_TEXEL_DENSITY = 0x20000000
+ RENDER_DEBUG_AVATAR_JOINTS = 0x00200000,
+ RENDER_DEBUG_BUILD_QUEUE = 0x00400000,
+ RENDER_DEBUG_AGENT_TARGET = 0x00800000,
+ RENDER_DEBUG_UPDATE_TYPE = 0x01000000,
+ RENDER_DEBUG_PHYSICS_SHAPES = 0x02000000,
+ RENDER_DEBUG_NORMALS = 0x04000000,
+ RENDER_DEBUG_LOD_INFO = 0x08000000,
+ RENDER_DEBUG_RENDER_COMPLEXITY = 0x10000000,
+ RENDER_DEBUG_ATTACHMENT_BYTES = 0x20000000,
+ RENDER_DEBUG_TEXEL_DENSITY = 0x40000000
};
public:
diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml
index f53995732f..3ebb64e3fa 100755
--- a/indra/newview/skins/default/colors.xml
+++ b/indra/newview/skins/default/colors.xml
@@ -122,6 +122,9 @@
<color
name="Blue_80"
value="0 0 1 0.8" />
+ <color
+ name="Orange"
+ value="1 .82 .46 1" />
<!-- This color name makes potentially unused colors show up bright purple.
Leave this here until all Unused? are removed below, otherwise
@@ -511,6 +514,9 @@
name="MapTrackColor"
reference="Red" />
<color
+ name="MapTrackColorUnder"
+ reference="Blue" />
+ <color
name="MapTrackDisabledColor"
value="0.5 0 0 1" />
<color
diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml
index bb891996c9..94c187e21a 100755
--- a/indra/newview/skins/default/textures/textures.xml
+++ b/indra/newview/skins/default/textures/textures.xml
@@ -153,8 +153,6 @@ with the same filename but different name
<texture name="Command_Speak_Icon" file_name="toolbar_icons/speak.png" preload="true" />
<texture name="Command_View_Icon" file_name="toolbar_icons/view.png" preload="true" />
<texture name="Command_Voice_Icon" file_name="toolbar_icons/nearbyvoice.png" preload="true" />
- <texture name="Command_Highlighting_Icon" file_name="toolbar_icons/highlighting.png" preload="true" scale.left="4" scale.top="19" scale.right="28" scale.bottom="4" />
- <texture name="Command_Highlighting_Selected_Icon" file_name="toolbar_icons/highlighting_selected.png" preload="true" scale.left="4" scale.top="19" scale.right="28" scale.bottom="4" />
<texture name="Caret_Bottom_Icon" file_name="toolbar_icons/caret_bottom.png" preload="true" scale.left="1" scale.top="23" scale.right="15" scale.bottom="1" />
<texture name="Caret_Right_Icon" file_name="toolbar_icons/caret_right.png" preload="true" scale.left="5" scale.top="15" scale.right="28" scale.bottom="1" />
<texture name="Caret_Left_Icon" file_name="toolbar_icons/caret_left.png" preload="true" scale.left="1" scale.top="15" scale.right="23" scale.bottom="1" />
@@ -165,7 +163,6 @@ with the same filename but different name
<texture name="ComboButton_On" file_name="widgets/ComboButton_On.png" preload="true" scale.left="2" scale.top="19" scale.right="18" scale.bottom="2" />
<texture name="ComboButton_Off" file_name="widgets/ComboButton_Off.png" preload="true" scale.left="2" scale.top="19" scale.right="18" scale.bottom="2" />
<texture name="ComboButton_UpOff" file_name="widgets/ComboButton_UpOff.png" preload="true" scale.left="2" scale.top="19" scale.right="18" scale.bottom="2" />
- <texture name="ComboButton_Hovered" file_name="widgets/ComboButton_Hover.png" preload="true" scale.left="2" scale.top="19" scale.right="18" scale.bottom="2" />
<texture name="Container" file_name="containers/Container.png" preload="false" />
diff --git a/indra/newview/skins/default/xui/en/floater_im_session.xml b/indra/newview/skins/default/xui/en/floater_im_session.xml
index 43d0f2fb18..7076de55e3 100755
--- a/indra/newview/skins/default/xui/en/floater_im_session.xml
+++ b/indra/newview/skins/default/xui/en/floater_im_session.xml
@@ -210,7 +210,7 @@
default_tab_group="3"
tab_group="2"
name="right_part_holder"
- min_width="172">
+ min_width="230">
<layout_stack
animate="true"
default_tab_group="2"
diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml
index 5e92a12251..9fa416012c 100755
--- a/indra/newview/skins/default/xui/en/floater_model_preview.xml
+++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml
@@ -212,11 +212,11 @@
follows="top|left"
height="20"
layout="topleft"
- left="215"
+ left="222"
name="lod_mode_high"
top_delta="0"
visible="false"
- width="135">
+ width="130">
<item
name="Triangle Limit"
value="Triangle Limit" />
@@ -230,7 +230,7 @@
height="20"
increment="10"
layout="topleft"
- left_pad="5"
+ left_pad="3"
name="lod_triangle_limit_high"
visible="false"
width="55" />
@@ -342,10 +342,10 @@
follows="top|left"
height="20"
layout="topleft"
- left="215"
+ left="222"
name="lod_mode_medium"
top_delta="0"
- width="135">
+ width="130">
<item
name="Triangle Limit"
value="Triangle Limit" />
@@ -359,7 +359,7 @@
height="20"
increment="10"
layout="topleft"
- left_pad="5"
+ left_pad="3"
name="lod_triangle_limit_medium"
width="55" />
<spinner
@@ -470,10 +470,10 @@
follows="top|left"
height="20"
layout="topleft"
- left="215"
+ left="222"
name="lod_mode_low"
top_delta="0"
- width="135">
+ width="130">
<item
name="Triangle Limit"
value="Triangle Limit" />
@@ -487,7 +487,7 @@
height="20"
increment="10"
layout="topleft"
- left_pad="5"
+ left_pad="3"
name="lod_triangle_limit_low"
width="55" />
<spinner
@@ -598,10 +598,10 @@
follows="top|left"
height="20"
layout="topleft"
- left="215"
+ left="222"
name="lod_mode_lowest"
top_delta="0"
- width="135">
+ width="130">
<item
name="Triangle Limit"
value="Triangle Limit" />
@@ -615,7 +615,7 @@
height="20"
increment="10"
layout="topleft"
- left_pad="5"
+ left_pad="3"
name="lod_triangle_limit_lowest"
width="55" />
<spinner
diff --git a/indra/newview/skins/default/xui/en/floater_region_restarting.xml b/indra/newview/skins/default/xui/en/floater_region_restarting.xml
new file mode 100644
index 0000000000..2fe4d0190a
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_region_restarting.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ height="150"
+ width="290"
+ layout="topleft"
+ name="region_restarting"
+ help_topic="floater_region_restarting"
+ single_instance="true"
+ reuse_instance="false"
+ title="REGION RESTARTING">
+ <string name="RegionName">
+ The region you are in now ([NAME]) is about to restart.
+
+If you stay in this region you will be logged out.
+ </string>
+ <string name="RestartSeconds">
+ Seconds until restart
+[SECONDS]
+ </string>
+ <panel
+ name="layout_panel_1"
+ height="150"
+ width="290"
+ follows="right|top"
+ top="0"
+ left="0"
+ background_visible="true"
+ bg_opaque_color="Orange"
+ bg_alpha_color="Orange">
+
+ <icon color="1.0 1.0 1.0 1.0"
+ tab_stop="false"
+ mouse_opaque="false"
+ name="icon"
+ width="32"
+ height="32"
+ image_name="notify_caution_icon.tga"
+ follows="left|top">
+ </icon>
+
+ <text
+ type="string"
+ length="1"
+ follows="top|left"
+ layout="topleft"
+ name="region_name"
+ text_color="Black"
+ font="SansSerifBold"
+ word_wrap="true"
+ height="100"
+ top="5"
+ left="40"
+ width="230">
+ The region you are in now (-The longest region name-) is about to restart.
+
+If you stay in this region you will be logged out.
+ </text>
+ <text
+ type="string"
+ length="1"
+ follows="top|left"
+ layout="topleft"
+ name="restart_seconds"
+ text_color="Black"
+ font="SansSerifLargeBold"
+ height="40"
+ top="110"
+ left="0"
+ halign="center"
+ width="290">
+ Seconds until restart
+ 32767
+ </text>
+ </panel>
+ </floater>
diff --git a/indra/newview/skins/default/xui/en/menu_inventory.xml b/indra/newview/skins/default/xui/en/menu_inventory.xml
index 512205ba43..6fa45d7d66 100755
--- a/indra/newview/skins/default/xui/en/menu_inventory.xml
+++ b/indra/newview/skins/default/xui/en/menu_inventory.xml
@@ -552,6 +552,14 @@
function="Inventory.DoToSelected"
parameter="about" />
</menu_item_call>
+ <menu_item_call
+ label="Show on Map"
+ layout="topleft"
+ name="show_on_map">
+ <menu_item_call.on_click
+ function="Inventory.DoToSelected"
+ parameter="show_on_map" />
+ </menu_item_call>
<menu_item_separator
layout="topleft"
name="Animation Separator" />
diff --git a/indra/newview/skins/default/xui/en/menu_teleport_history_item.xml b/indra/newview/skins/default/xui/en/menu_teleport_history_item.xml
index 0160d52b17..f939c3996d 100755
--- a/indra/newview/skins/default/xui/en/menu_teleport_history_item.xml
+++ b/indra/newview/skins/default/xui/en/menu_teleport_history_item.xml
@@ -17,7 +17,7 @@
function="TeleportHistory.MoreInformation" />
</menu_item_call>
<menu_item_call
- label="Copy to Clipboard"
+ label="Copy SLurl"
layout="topleft"
name="CopyToClipboard">
<menu_item_call.on_click
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index e5e2bd4c11..64de010eb5 100755
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -2564,6 +2564,16 @@
parameter="collision skeleton" />
</menu_item_check>
<menu_item_check
+ label="Joints"
+ name="Joints">
+ <menu_item_check.on_check
+ function="Advanced.CheckInfoDisplay"
+ parameter="joints" />
+ <menu_item_check.on_click
+ function="Advanced.ToggleInfoDisplay"
+ parameter="joints" />
+ </menu_item_check>
+ <menu_item_check
label="Raycast"
name="Raycast">
<menu_item_check.on_check
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 35f2e7b31e..a93601f5fd 100755
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -6893,8 +6893,8 @@ This will add a bookmark in your inventory so you can quickly IM this Resident.
<notification
icon="notify.tga"
name="RegionRestartMinutes"
+ show_toast="false"
priority="high"
- sound="UISndAlert"
type="notify">
The region "[NAME]" will restart in [MINUTES] minutes.
If you stay in this region you will be logged out.
@@ -6903,8 +6903,8 @@ If you stay in this region you will be logged out.
<notification
icon="notify.tga"
name="RegionRestartSeconds"
+ show_toast="false"
priority="high"
- sound="UISndAlert"
type="notify">
The region "[NAME]" will restart in [SECONDS] seconds.
If you stay in this region you will be logged out.
diff --git a/indra/newview/skins/default/xui/en/panel_people.xml b/indra/newview/skins/default/xui/en/panel_people.xml
index 3caf2b3d7e..d2caf63052 100755
--- a/indra/newview/skins/default/xui/en/panel_people.xml
+++ b/indra/newview/skins/default/xui/en/panel_people.xml
@@ -66,8 +66,7 @@ Looking for people to hang out with? Try the [secondlife:///app/worldmap World M
tab_position="top"
top="0"
halign="center"
- right="-5"
- use_highlighting_on_hover="true">
+ right="-5">
<!-- ================================= NEARBY tab =========================== -->
@@ -507,6 +506,7 @@ Looking for people to hang out with? Try the [secondlife:///app/worldmap World M
right="-10"
top_pad="4"
left="3"
+ use_ellipses="true"
name="groupcount">
You belong to [COUNT] groups, and can join [REMAINING] more.
</text>
diff --git a/indra/newview/skins/default/xui/en/widgets/location_input.xml b/indra/newview/skins/default/xui/en/widgets/location_input.xml
index 4ea1aa6efb..61ec046649 100755
--- a/indra/newview/skins/default/xui/en/widgets/location_input.xml
+++ b/indra/newview/skins/default/xui/en/widgets/location_input.xml
@@ -150,7 +150,6 @@
<combo_button
name="Location History"
label=""
- image_hover_unselected="ComboButton_Hovered"
pad_right="0"/>
<combo_list
bg_writeable_color="MenuDefaultBgColor"
diff --git a/indra/newview/skins/default/xui/en/widgets/tab_container.xml b/indra/newview/skins/default/xui/en/widgets/tab_container.xml
index 9559be214a..0586119681 100755
--- a/indra/newview/skins/default/xui/en/widgets/tab_container.xml
+++ b/indra/newview/skins/default/xui/en/widgets/tab_container.xml
@@ -24,26 +24,17 @@ label_pad_left - padding to the left of tab button labels
tab_bottom_image_unselected="Toolbar_Left_Off"
tab_bottom_image_selected="Toolbar_Left_Selected"
tab_left_image_unselected="SegmentedBtn_Left_Disabled"
- tab_left_image_selected="SegmentedBtn_Left_Selected_Over"
- tab_top_image_hovered="TabTop_Left_Selected"
- tab_button_image_hovered="Toolbar_Left_Selected"
- tab_left_image_hovered="SegmentedBtn_Left_Selected_Over"/>
+ tab_left_image_selected="SegmentedBtn_Left_Selected_Over"/>
<middle_tab tab_top_image_unselected="TabTop_Middle_Off"
tab_top_image_selected="TabTop_Middle_Selected"
tab_bottom_image_unselected="Toolbar_Middle_Off"
tab_bottom_image_selected="Toolbar_Middle_Selected"
tab_left_image_unselected="SegmentedBtn_Left_Disabled"
- tab_left_image_selected="SegmentedBtn_Left_Selected_Over"
- tab_top_image_hovered="TabTop_Middle_Selected"
- tab_button_image_hovered="Toolbar_Middle_Selected"
- tab_left_image_hovered="SegmentedBtn_Left_Selected_Over"/>
+ tab_left_image_selected="SegmentedBtn_Left_Selected_Over"/>
<last_tab tab_top_image_unselected="TabTop_Right_Off"
tab_top_image_selected="TabTop_Right_Selected"
tab_bottom_image_unselected="Toolbar_Right_Off"
tab_bottom_image_selected="Toolbar_Right_Selected"
tab_left_image_unselected="SegmentedBtn_Left_Disabled"
- tab_left_image_selected="SegmentedBtn_Left_Selected_Over"
- tab_top_image_hovered="TabTop_Right_Selected"
- tab_button_image_hovered="Toolbar_Right_Selected"
- tab_left_image_hovered="SegmentedBtn_Left_Selected_Over"/>
+ tab_left_image_selected="SegmentedBtn_Left_Selected_Over"/>
</tab_container>