From 74d59e7128bb02a4b49af99e44f437a736a3f62b Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 7 May 2012 15:16:31 -0400 Subject: Build llcorehttp as part of a viewer dependency with unit tests. This required boost::thread and the easiest path to that was to go with the 1.48 Boost release in the 3P tree (eliminating a fork for a modified 1.45 packaging). One unit test, the most important one, is failing in test_httprequest but that can be attended to later. This test issues a GET to http://localhost:2/ and that is hitting the wire but the libcurl plumbing isn't delivering the failure, only the eventual timeout. An unexpected change in behavior. --- indra/llcommon/linden_common.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/linden_common.h b/indra/llcommon/linden_common.h index bdcc98e402..50db03cb6a 100644 --- a/indra/llcommon/linden_common.h +++ b/indra/llcommon/linden_common.h @@ -82,4 +82,8 @@ #include "llfile.h" #include "llformat.h" +// Boost 1.45 had version 2 as the default for the filesystem library, +// 1.48 has version 3 as the default. Keep compatibility for now. +#define BOOST_FILESYSTEM_VERSION 2 + #endif -- cgit v1.3 From fb5a29c069d27611b6328fbc313382ef0914ffe9 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 1 Jun 2012 23:06:24 +0000 Subject: Platform fixups Linux: unused variables, make error strings constant. --- indra/llcommon/llsdserialize.cpp | 5 ++++- indra/llcommon/tests/bitpack_test.cpp | 1 + indra/llcommon/tests/reflection_test.cpp | 2 +- indra/llcorehttp/httpcommon.cpp | 2 +- indra/llmessage/llhttpassetstorage.cpp | 2 +- indra/newview/lltexturefetch.cpp | 10 +++++----- 6 files changed, 13 insertions(+), 9 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index bf62600514..f29c96f17c 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -1447,9 +1447,12 @@ S32 LLSDBinaryFormatter::format(const LLSD& data, std::ostream& ostr, U32 option } case LLSD::TypeUUID: + { ostr.put('u'); - ostr.write((const char*)(&(data.asUUID().mData)), UUID_BYTES); + LLSD::UUID value = data.asUUID(); + ostr.write((const char*)(&value.mData), UUID_BYTES); break; + } case LLSD::TypeString: ostr.put('s'); diff --git a/indra/llcommon/tests/bitpack_test.cpp b/indra/llcommon/tests/bitpack_test.cpp index 05289881d0..4c3bc674af 100644 --- a/indra/llcommon/tests/bitpack_test.cpp +++ b/indra/llcommon/tests/bitpack_test.cpp @@ -95,6 +95,7 @@ namespace tut ensure("bitPack: individual unpack: 5", unpackbuffer[0] == (U8) str[5]); unpack_bufsize = bitunpack.bitUnpack(unpackbuffer, 8*4); // Life ensure_memory_matches("bitPack: 4 bytes unpack:", unpackbuffer, 4, str+6, 4); + ensure("keep compiler quiet", unpack_bufsize == unpack_bufsize); } // U32 packing diff --git a/indra/llcommon/tests/reflection_test.cpp b/indra/llcommon/tests/reflection_test.cpp index 59491cd1fe..8980ebb1f1 100644 --- a/indra/llcommon/tests/reflection_test.cpp +++ b/indra/llcommon/tests/reflection_test.cpp @@ -207,7 +207,7 @@ namespace tut const LLReflective* reflective = property->get(aggregated_data); // Wrong reflective type, should throw exception. // useless op to get rid of compiler warning. - reflective = NULL; + reflective = reflective; } catch(...) { diff --git a/indra/llcorehttp/httpcommon.cpp b/indra/llcorehttp/httpcommon.cpp index 273acae132..a01182cf23 100644 --- a/indra/llcorehttp/httpcommon.cpp +++ b/indra/llcorehttp/httpcommon.cpp @@ -73,7 +73,7 @@ std::string HttpStatus::toString() const static const struct { type_enum_t mCode; - char * mText; + const char * mText; } http_errors[] = { diff --git a/indra/llmessage/llhttpassetstorage.cpp b/indra/llmessage/llhttpassetstorage.cpp index 612d765969..d6ed08055e 100644 --- a/indra/llmessage/llhttpassetstorage.cpp +++ b/indra/llmessage/llhttpassetstorage.cpp @@ -749,7 +749,7 @@ LLAssetRequest* LLHTTPAssetStorage::findNextRequest(LLAssetStorage::request_list request_list_t::iterator pending_iter = pending.begin(); request_list_t::iterator pending_end = pending.end(); // Loop over all pending requests until we miss finding it in the running list. - for (; pending_iter != pending.end(); ++pending_iter) + for (; pending_iter != pending_end; ++pending_iter) { LLAssetRequest* req = *pending_iter; // Look for this pending request in the running list. diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 381364b5c3..daad463e0d 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -1065,13 +1065,13 @@ bool LLTextureFetchWorker::doWork(S32 param) //1, not openning too many file descriptors at the same time; //2, control the traffic of http so udp gets bandwidth. // - static const S32 MAX_NUM_OF_HTTP_REQUESTS_IN_QUEUE = 8; + // static const S32 MAX_NUM_OF_HTTP_REQUESTS_IN_QUEUE = 8; // *FIXME: For the moment, allow everything to transition into HTTP // and have the new library order and throttle. - //if(mFetcher->getNumHTTPRequests() > MAX_NUM_OF_HTTP_REQUESTS_IN_QUEUE) - //{ - //return false ; //wait. - //} + // if(mFetcher->getNumHTTPRequests() > MAX_NUM_OF_HTTP_REQUESTS_IN_QUEUE) + // { + // return false ; //wait. + // } mFetcher->removeFromNetworkQueue(this, false); -- cgit v1.3 From 24e16e1632974057013b86300bb60954ea6f5684 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Tue, 12 Jun 2012 12:41:09 -0400 Subject: Convert _refcounted.h over to using LLAtomic32<>. Beware of bad documentation. operator--(int) does not return what the header claimed it did. --- indra/llcommon/llapr.h | 2 +- indra/llcorehttp/_refcounted.h | 52 ++++++++++++++-------------------------- indra/llcorehttp/bufferarray.cpp | 6 ++--- 3 files changed, 22 insertions(+), 38 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index af33ce666f..034546c3f3 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -168,7 +168,7 @@ public: void operator -=(Type x) { apr_atomic_sub32(&mData, apr_uint32_t(x)); } void operator +=(Type x) { apr_atomic_add32(&mData, apr_uint32_t(x)); } Type operator ++(int) { return apr_atomic_inc32(&mData); } // Type++ - Type operator --(int) { return apr_atomic_dec32(&mData); } // Type-- + Type operator --(int) { return apr_atomic_dec32(&mData); } // approximately --Type (0 if final is 0, non-zero otherwise) private: apr_uint32_t mData; diff --git a/indra/llcorehttp/_refcounted.h b/indra/llcorehttp/_refcounted.h index 72cef6b342..a96c65fb6b 100644 --- a/indra/llcorehttp/_refcounted.h +++ b/indra/llcorehttp/_refcounted.h @@ -28,9 +28,11 @@ #define LLCOREINT__REFCOUNTED_H_ +#include "linden_common.h" + #include -#include "linden_common.h" +#include "llapr.h" namespace LLCoreInt @@ -44,7 +46,7 @@ private: void operator=(const RefCounted &); // Not defined public: - explicit RefCounted(bool const implicit) + explicit RefCounted(bool const implicit) : mRefCount(implicit) {} @@ -52,42 +54,34 @@ public: void addRef() const; void release() const; bool isLastRef() const; - int getRefCount() const; + S32 getRefCount() const; void noRef() const; - static const int NOT_REF_COUNTED = -1; + static const S32 NOT_REF_COUNTED = -1; protected: virtual ~RefCounted(); virtual void destroySelf(); private: - mutable int mRefCount; - mutable boost::mutex mRefLock; + mutable LLAtomicS32 mRefCount; }; // end class RefCounted inline void RefCounted::addRef() const { - boost::mutex::scoped_lock lock(mRefLock); - llassert_always(mRefCount >= 0); - ++mRefCount; + S32 count(mRefCount++); + llassert_always(count >= 0); } inline void RefCounted::release() const { - int count(0); - { - // CRITICAL SECTION - boost::mutex::scoped_lock lock(mRefLock); - llassert_always(mRefCount != NOT_REF_COUNTED); - llassert_always(mRefCount > 0); - count = --mRefCount; - // CRITICAL SECTION - } - + S32 count(mRefCount); + llassert_always(count != NOT_REF_COUNTED); + llassert_always(count > 0); + count = mRefCount--; // clean ourselves up if that was the last reference if (0 == count) @@ -99,32 +93,22 @@ inline void RefCounted::release() const inline bool RefCounted::isLastRef() const { - int count(0); - { - // CRITICAL SECTION - boost::mutex::scoped_lock lock(mRefLock); - - llassert_always(mRefCount != NOT_REF_COUNTED); - llassert_always(mRefCount >= 1); - count = mRefCount; - // CRITICAL SECTION - } - + const S32 count(mRefCount); + llassert_always(count != NOT_REF_COUNTED); + llassert_always(count >= 1); return (1 == count); } -inline int RefCounted::getRefCount() const +inline S32 RefCounted::getRefCount() const { - boost::mutex::scoped_lock lock(mRefLock); - const int result(mRefCount); + const S32 result(mRefCount); return result; } inline void RefCounted::noRef() const { - boost::mutex::scoped_lock lock(mRefLock); llassert_always(mRefCount <= 1); mRefCount = NOT_REF_COUNTED; } diff --git a/indra/llcorehttp/bufferarray.cpp b/indra/llcorehttp/bufferarray.cpp index 6d5309a043..ae92057df0 100644 --- a/indra/llcorehttp/bufferarray.cpp +++ b/indra/llcorehttp/bufferarray.cpp @@ -175,7 +175,7 @@ size_t BufferArray::read(size_t pos, void * dst, size_t len) if (pos >= mLen) return 0; size_t len_limit(mLen - pos); - len = std::min(len, len_limit); + len = (std::min)(len, len_limit); if (0 == len) return 0; @@ -189,7 +189,7 @@ size_t BufferArray::read(size_t pos, void * dst, size_t len) { Block & block(*mBlocks[block_start]); size_t block_limit(block.mUsed - offset); - size_t block_len(std::min(block_limit, len)); + size_t block_len((std::min)(block_limit, len)); memcpy(c_dst, &block.mData[offset], block_len); result += block_len; @@ -223,7 +223,7 @@ size_t BufferArray::write(size_t pos, const void * src, size_t len) { Block & block(*mBlocks[block_start]); size_t block_limit(block.mUsed - offset); - size_t block_len(std::min(block_limit, len)); + size_t block_len((std::min)(block_limit, len)); memcpy(&block.mData[offset], c_src, block_len); result += block_len; -- cgit v1.3 From b08125a5874a89ce5210f8fb2c961ae17fb80fde Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 14 Jun 2012 16:31:48 -0400 Subject: LLMutex recursive lock, global & per-request tracing, simple GET request, LLProxy support, HttpOptions starting to work, HTTP resource waiting fixed. Non-LLThread-based threads need to do some registration or LLMutex locks taken out in these threads will not work as expected (SH-3154). We'll get a better solution later, this fixes some things for now. Tracing of operations now supported. Global and per-request (via HttpOptions) tracing levels of [0..3]. The 2 and 3 levels use libcurl's VERBOSE mode combined with CURLOPT_DEBUGFUNCTION to stream high levels of detail into the log. *Very* laggy but useful. Simple GET request supported (no Range: header). Really just a degenrate case of a ranged get but supplied an API anyway. Global option to use the LLProxy interface to setup CURL handles for either socks5 or http proxy usage. This isn't really the most encapsulated way to do this but a better solution will have to come later. The wantHeaders and tracing options are now supported in HttpOptions giving per-request controls. Big refactoring of the HTTP resource waiter in lltexturefetch. What I was doing before wasn't correct. Instead, I'm implementing the resource wait after the Semaphore model (though not using system semaphores). So instead of having a sequence like: SEND_HTTP_REQ -> WAIT_HTTP_RESOURCE -> SEND_HTTP_REQ, we now do WAIT_HTTP_RESOURCE -> WAIT_HTTP_RESOURCE2 (actual wait) -> SEND_HTTP_REQ. Works well but the prioritized filling of the corehttp library needs some performance work later. --- indra/llcommon/llthread.cpp | 7 + indra/llcommon/llthread.h | 5 + indra/llcorehttp/_httplibcurl.cpp | 24 ++- indra/llcorehttp/_httpoperation.cpp | 15 +- indra/llcorehttp/_httpoperation.h | 4 + indra/llcorehttp/_httpoprequest.cpp | 271 +++++++++++++++++++++------- indra/llcorehttp/_httpoprequest.h | 16 +- indra/llcorehttp/_httpopsetget.cpp | 4 +- indra/llcorehttp/_httppolicy.cpp | 21 ++- indra/llcorehttp/_httppolicy.h | 5 +- indra/llcorehttp/_httppolicyglobal.cpp | 52 ++++-- indra/llcorehttp/_httppolicyglobal.h | 6 +- indra/llcorehttp/_httpservice.cpp | 18 +- indra/llcorehttp/httpoptions.cpp | 18 +- indra/llcorehttp/httpoptions.h | 14 +- indra/llcorehttp/httprequest.cpp | 27 +++ indra/llcorehttp/httprequest.h | 75 +++++++- indra/llcorehttp/tests/test_httprequest.hpp | 228 ++++++++++++++++++++++- indra/newview/llappviewer.cpp | 20 +- indra/newview/lltexturefetch.cpp | 120 +++++++----- indra/newview/lltexturefetch.h | 9 + indra/newview/lltextureview.cpp | 5 +- 22 files changed, 803 insertions(+), 161 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index a6ad6b125c..b27b64b26f 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -71,6 +71,13 @@ LL_COMMON_API void assert_main_thread() } } +void LLThread::registerThreadID() +{ +#if !LL_DARWIN + sThreadID = ++sIDIter; +#endif +} + // // Handed to the APR thread creation function // diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index b52e70ab2e..54af41ec59 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -88,6 +88,11 @@ public: U32 getID() const { return mID; } + // Called by threads *not* created via LLThread to register some + // internal state used by LLMutex. You must call this once early + // in the running thread to prevent collisions with the main thread. + static void registerThreadID(); + private: BOOL mPaused; diff --git a/indra/llcorehttp/_httplibcurl.cpp b/indra/llcorehttp/_httplibcurl.cpp index e134a28401..a176dd5b2a 100644 --- a/indra/llcorehttp/_httplibcurl.cpp +++ b/indra/llcorehttp/_httplibcurl.cpp @@ -159,6 +159,17 @@ void HttpLibcurl::addOp(HttpOpRequest * op) curl_multi_add_handle(mMultiHandles[op->mReqPolicy], op->mCurlHandle); op->mCurlActive = true; + if (op->mTracing > 0) + { + HttpPolicy & policy(mService->getPolicy()); + + LL_INFOS("CoreHttp") << "TRACE, ToActiveQueue, Handle: " + << static_cast(op) + << ", Actives: " << mActiveOps.size() + << ", Readies: " << policy.getReadyCount(op->mReqPolicy) + << LL_ENDL; + } + // On success, make operation active mActiveOps.insert(op); } @@ -190,10 +201,9 @@ bool HttpLibcurl::completeRequest(CURLM * multi_handle, CURL * handle, CURLcode // Deactivate request op->mCurlActive = false; - // Set final status of request + // Set final status of request if it hasn't failed by other mechanisms yet if (op->mStatus) { - // Only set if it hasn't failed by other mechanisms yet op->mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, status); } if (op->mStatus) @@ -209,6 +219,16 @@ bool HttpLibcurl::completeRequest(CURLM * multi_handle, CURL * handle, CURLcode curl_easy_cleanup(handle); op->mCurlHandle = NULL; + // Tracing + if (op->mTracing > 0) + { + LL_INFOS("CoreHttp") << "TRACE, RequestComplete, Handle: " + << static_cast(op) + << ", Status: " << op->mStatus.toHex() + << LL_ENDL; + } + + // Dispatch to next stage HttpPolicy & policy(mService->getPolicy()); bool still_active(policy.stageAfterCompletion(op)); diff --git a/indra/llcorehttp/_httpoperation.cpp b/indra/llcorehttp/_httpoperation.cpp index b5c58013d4..5a31bf90e7 100644 --- a/indra/llcorehttp/_httpoperation.cpp +++ b/indra/llcorehttp/_httpoperation.cpp @@ -34,6 +34,8 @@ #include "_httpreplyqueue.h" #include "_httpservice.h" +#include "lltimer.h" + namespace LLCore { @@ -49,8 +51,10 @@ HttpOperation::HttpOperation() mReplyQueue(NULL), mUserHandler(NULL), mReqPolicy(HttpRequest::DEFAULT_POLICY_ID), - mReqPriority(0U) + mReqPriority(0U), + mTracing(0) { + mMetricCreated = totalTime(); } @@ -113,7 +117,7 @@ void HttpOperation::stageFromActive(HttpService *) llassert_always(false); } - + void HttpOperation::visitNotifier(HttpRequest *) { if (mUserHandler) @@ -138,6 +142,13 @@ HttpStatus HttpOperation::cancel() void HttpOperation::addAsReply() { + if (mTracing > 0) + { + LL_INFOS("CoreHttp") << "TRACE, ToReplyQueue, Handle: " + << static_cast(this) + << LL_ENDL; + } + if (mReplyQueue) { addRef(); diff --git a/indra/llcorehttp/_httpoperation.h b/indra/llcorehttp/_httpoperation.h index c93aa2def9..de4939a0ac 100644 --- a/indra/llcorehttp/_httpoperation.h +++ b/indra/llcorehttp/_httpoperation.h @@ -110,6 +110,10 @@ public: // Reply Data HttpStatus mStatus; + + // Tracing, debug and metrics + HttpTime mMetricCreated; + int mTracing; }; // end class HttpOperation diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index e2550d057e..f78971d8f2 100644 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -63,6 +63,16 @@ int parse_content_range_header(char * buffer, unsigned int * last, unsigned int * length); + +// Take data from libcurl's CURLOPT_DEBUGFUNCTION callback and +// escape and format it for a tracing line in logging. Absolutely +// anything including NULs can be in the data. If @scrub is true, +// non-printing or non-ascii characters are replaced with spaces +// otherwise a %XX form of escaping is used. +void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, + std::string & safe_line); + + #if defined(WIN32) // Not available on windows where the legacy strtok interface @@ -78,11 +88,6 @@ namespace LLCore { -// ================================== -// HttpOpRequest -// ================================== - - HttpOpRequest::HttpOpRequest() : HttpOperation(), mProcFlags(0U), @@ -237,6 +242,19 @@ HttpStatus HttpOpRequest::cancel() } +HttpStatus HttpOpRequest::setupGet(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + HttpOptions * options, + HttpHeaders * headers) +{ + setupCommon(policy_id, priority, url, NULL, options, headers); + mReqMethod = HOR_GET; + + return HttpStatus(); +} + + HttpStatus HttpOpRequest::setupGetByteRange(HttpRequest::policy_t policy_id, HttpRequest::priority_t priority, const std::string & url, @@ -245,30 +263,16 @@ HttpStatus HttpOpRequest::setupGetByteRange(HttpRequest::policy_t policy_id, HttpOptions * options, HttpHeaders * headers) { - HttpStatus status; - - mProcFlags = 0; - mReqPolicy = policy_id; - mReqPriority = priority; + setupCommon(policy_id, priority, url, NULL, options, headers); mReqMethod = HOR_GET; - mReqURL = url; mReqOffset = offset; mReqLength = len; if (offset || len) { mProcFlags |= PF_SCAN_RANGE_HEADER; } - if (headers && ! mReqHeaders) - { - headers->addRef(); - mReqHeaders = headers; - } - if (options && ! mReqOptions) - { - mReqOptions = new HttpOptions(*options); - } - return status; + return HttpStatus(); } @@ -279,29 +283,10 @@ HttpStatus HttpOpRequest::setupPost(HttpRequest::policy_t policy_id, HttpOptions * options, HttpHeaders * headers) { - HttpStatus status; - - mProcFlags = 0; - mReqPolicy = policy_id; - mReqPriority = priority; + setupCommon(policy_id, priority, url, body, options, headers); mReqMethod = HOR_POST; - mReqURL = url; - if (body) - { - body->addRef(); - mReqBody = body; - } - if (headers && ! mReqHeaders) - { - headers->addRef(); - mReqHeaders = headers; - } - if (options && ! mReqOptions) - { - mReqOptions = new HttpOptions(*options); - } - return status; + return HttpStatus(); } @@ -312,12 +297,23 @@ HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id, HttpOptions * options, HttpHeaders * headers) { - HttpStatus status; + setupCommon(policy_id, priority, url, body, options, headers); + mReqMethod = HOR_PUT; + + return HttpStatus(); +} + +void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers) +{ mProcFlags = 0; mReqPolicy = policy_id; mReqPriority = priority; - mReqMethod = HOR_PUT; mReqURL = url; if (body) { @@ -331,10 +327,14 @@ HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id, } if (options && ! mReqOptions) { - mReqOptions = new HttpOptions(*options); + options->addRef(); + mReqOptions = options; + if (options->getWantHeaders()) + { + mProcFlags |= PF_SAVE_HEADERS; + } + mTracing = (std::max)(mTracing, llclamp(options->getTrace(), 0, 3)); } - - return status; } @@ -394,30 +394,29 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0); const std::string * opt_value(NULL); - if (policy.get(HttpRequest::GP_CA_PATH, opt_value)) + long opt_long(0L); + policy.get(HttpRequest::GP_LLPROXY, &opt_long); + if (opt_long) { - curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, opt_value->c_str()); + // 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); } - if (policy.get(HttpRequest::GP_CA_FILE, opt_value)) + else if (policy.get(HttpRequest::GP_HTTP_PROXY, &opt_value)) { - curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, opt_value->c_str()); + // *TODO: This is fine for now but get fuller socks/ + // authentication thing going later.... + curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value->c_str()); + curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); } - if (policy.get(HttpRequest::GP_HTTP_PROXY, opt_value)) + if (policy.get(HttpRequest::GP_CA_PATH, &opt_value)) { - if (*opt_value == "LLProxy") - { - // 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 - { - // *TODO: This is fine for now but get fuller socks/ - // authentication thing going later.... - curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value->c_str()); - curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); - } + curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, opt_value->c_str()); + } + if (policy.get(HttpRequest::GP_CA_FILE, &opt_value)) + { + curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, opt_value->c_str()); } switch (mReqMethod) @@ -463,6 +462,14 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) // *FIXME: fail out here break; } + + // Tracing + if (mTracing > 1) + { + curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1); + curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGDATA, mCurlHandle); + curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGFUNCTION, debugCallback); + } // There's a CURLOPT for this now... if ((mReqOffset || mReqLength) && HOR_GET == mReqMethod) @@ -621,6 +628,101 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi return hdr_size; } + +int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffer, size_t len, void * userdata) +{ + HttpOpRequest * op(NULL); + curl_easy_getinfo(handle, CURLINFO_PRIVATE, &op); + // *FIXME: check the pointer + + std::string safe_line; + std::string tag; + bool logit(false); + len = (std::min)(len, size_t(256)); // Keep things reasonable in all cases + + switch (info) + { + case CURLINFO_TEXT: + if (op->mTracing > 1) + { + tag = "TEXT"; + escape_libcurl_debug_data(buffer, len, true, safe_line); + logit = true; + } + break; + + case CURLINFO_HEADER_IN: + if (op->mTracing > 1) + { + tag = "HEADERIN"; + escape_libcurl_debug_data(buffer, len, true, safe_line); + logit = true; + } + break; + + case CURLINFO_HEADER_OUT: + if (op->mTracing > 1) + { + tag = "HEADEROUT"; + escape_libcurl_debug_data(buffer, len, true, safe_line); + logit = true; + } + break; + + case CURLINFO_DATA_IN: + if (op->mTracing > 1) + { + tag = "DATAIN"; + logit = true; + if (op->mTracing > 2) + { + escape_libcurl_debug_data(buffer, len, false, safe_line); + } + else + { + std::ostringstream out; + out << len << " Bytes"; + safe_line = out.str(); + } + } + break; + + case CURLINFO_DATA_OUT: + if (op->mTracing > 1) + { + tag = "DATAOUT"; + logit = true; + if (op->mTracing > 2) + { + escape_libcurl_debug_data(buffer, len, false, safe_line); + } + else + { + std::ostringstream out; + out << len << " Bytes"; + safe_line = out.str(); + } + } + break; + + default: + logit = false; + break; + } + + if (logit) + { + LL_INFOS("CoreHttp") << "TRACE, LibcurlDebug, Handle: " + << static_cast(op) + << ", Type: " << tag + << ", Data: " << safe_line + << LL_ENDL; + } + + return 0; +} + + } // end namespace LLCore @@ -694,6 +796,43 @@ char *strtok_r(char *str, const char *delim, char ** savestate) #endif + +void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::string & safe_line) +{ + std::string out; + len = (std::min)(len, size_t(200)); + out.reserve(3 * len); + for (int i(0); i < len; ++i) + { + unsigned char uc(static_cast(buffer[i])); + + if (uc < 32 || uc > 126) + { + if (scrub) + { + out.append(1, ' '); + } + else + { + static const char hex[] = "0123456789ABCDEF"; + char convert[4]; + + convert[0] = '%'; + convert[1] = hex[(uc >> 4) % 16]; + convert[2] = hex[uc % 16]; + convert[3] = '\0'; + out.append(convert); + } + } + else + { + out.append(1, buffer[i]); + } + } + safe_line.swap(out); +} + + } // end anonymous namespace diff --git a/indra/llcorehttp/_httpoprequest.h b/indra/llcorehttp/_httpoprequest.h index 80893beb40..fc2301057c 100644 --- a/indra/llcorehttp/_httpoprequest.h +++ b/indra/llcorehttp/_httpoprequest.h @@ -76,6 +76,12 @@ public: public: // Setup Methods + HttpStatus setupGet(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + HttpOptions * options, + HttpHeaders * headers); + HttpStatus setupGetByteRange(HttpRequest::policy_t policy_id, HttpRequest::priority_t priority, const std::string & url, @@ -103,15 +109,23 @@ public: virtual HttpStatus cancel(); protected: + void setupCommon(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers); + static size_t writeCallback(void * data, size_t size, size_t nmemb, void * userdata); static size_t readCallback(void * data, size_t size, size_t nmemb, void * userdata); static size_t headerCallback(void * data, size_t size, size_t nmemb, void * userdata); + static int debugCallback(CURL *, curl_infotype info, char * buffer, size_t len, void * userdata); protected: unsigned int mProcFlags; static const unsigned int PF_SCAN_RANGE_HEADER = 0x00000001U; static const unsigned int PF_SAVE_HEADERS = 0x00000002U; - + public: // Request data EMethod mReqMethod; diff --git a/indra/llcorehttp/_httpopsetget.cpp b/indra/llcorehttp/_httpopsetget.cpp index 21e058b2be..c1357f9ae5 100644 --- a/indra/llcorehttp/_httpopsetget.cpp +++ b/indra/llcorehttp/_httpopsetget.cpp @@ -89,8 +89,8 @@ void HttpOpSetGet::stageFromRequest(HttpService * service) } if (mStatus) { - const std::string * value; - if ((mStatus = pol_opt.get(setting, value))) + const std::string * value(NULL); + if ((mStatus = pol_opt.get(setting, &value))) { mStrValue = *value; } diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp index 8ee3f88658..0e08d88276 100644 --- a/indra/llcorehttp/_httppolicy.cpp +++ b/indra/llcorehttp/_httppolicy.cpp @@ -107,6 +107,12 @@ void HttpPolicy::retryOp(HttpOpRequest * op) LL_WARNS("CoreHttp") << "URL op retry #" << op->mPolicyRetries << " being scheduled for " << delta << " uSecs from now." << LL_ENDL; + if (op->mTracing > 0) + { + LL_INFOS("CoreHttp") << "TRACE, ToRetryQueue, Handle: " + << static_cast(op) + << LL_ENDL; + } mState[policy_class].mRetryQueue.push(op); } @@ -224,8 +230,8 @@ bool HttpPolicy::stageAfterCompletion(HttpOpRequest * op) } else if (op->mPolicyRetries) { - LL_WARNS("CoreHttp") << "URL op succeeded after " << op->mPolicyRetries << " retries." - << LL_ENDL; + LL_DEBUGS("CoreHttp") << "URL op succeeded after " << op->mPolicyRetries << " retries." + << LL_ENDL; } op->stageFromActive(mService); @@ -234,4 +240,15 @@ bool HttpPolicy::stageAfterCompletion(HttpOpRequest * op) } +int HttpPolicy::getReadyCount(HttpRequest::policy_t policy_class) +{ + if (policy_class < HttpRequest::POLICY_CLASS_LIMIT) + { + return (mState[policy_class].mReadyQueue.size() + + mState[policy_class].mRetryQueue.size()); + } + return 0; +} + + } // end namespace LLCore diff --git a/indra/llcorehttp/_httppolicy.h b/indra/llcorehttp/_httppolicy.h index 73c22bab78..4114f64848 100644 --- a/indra/llcorehttp/_httppolicy.h +++ b/indra/llcorehttp/_httppolicy.h @@ -97,7 +97,10 @@ public: } void setPolicies(const HttpPolicyGlobal & global); - + + // Get ready counts for a particular class + int getReadyCount(HttpRequest::policy_t policy_class); + protected: struct State { diff --git a/indra/llcorehttp/_httppolicyglobal.cpp b/indra/llcorehttp/_httppolicyglobal.cpp index d95d73cfba..6b1de38fd6 100644 --- a/indra/llcorehttp/_httppolicyglobal.cpp +++ b/indra/llcorehttp/_httppolicyglobal.cpp @@ -33,7 +33,9 @@ namespace LLCore HttpPolicyGlobal::HttpPolicyGlobal() : mSetMask(0UL), - mConnectionLimit(32L) + mConnectionLimit(32L), + mTrace(0), + mUseLLProxy(0) {} @@ -50,6 +52,8 @@ HttpPolicyGlobal & HttpPolicyGlobal::operator=(const HttpPolicyGlobal & other) mCAPath = other.mCAPath; mCAFile = other.mCAFile; mHttpProxy = other.mHttpProxy; + mTrace = other.mTrace; + mUseLLProxy = other.mUseLLProxy; } return *this; } @@ -63,6 +67,14 @@ HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, long value) mConnectionLimit = value; break; + case HttpRequest::GP_TRACE: + mTrace = llclamp(value, 0L, 3L); + break; + + case HttpRequest::GP_LLPROXY: + mUseLLProxy = llclamp(value, 0L, 1L); + break; + default: return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); } @@ -97,54 +109,64 @@ HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, const std::stri } -HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, long & value) +HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, long * value) { static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET); + long * src(NULL); switch (opt) { case HttpRequest::GP_CONNECTION_LIMIT: - if (! (mSetMask & (1UL << int(opt)))) - return not_set; - value = mConnectionLimit; + src = &mConnectionLimit; + break; + + case HttpRequest::GP_TRACE: + src = &mTrace; + break; + + case HttpRequest::GP_LLPROXY: + src = &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::EGlobalPolicy opt, const std::string ** value) { static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET); + const std::string * src(NULL); switch (opt) { case HttpRequest::GP_CA_PATH: - if (! (mSetMask & (1UL << int(opt)))) - return not_set; - value = &mCAPath; + src = &mCAPath; break; case HttpRequest::GP_CA_FILE: - if (! (mSetMask & (1UL << int(opt)))) - return not_set; - value = &mCAFile; + src = &mCAFile; break; case HttpRequest::GP_HTTP_PROXY: - if (! (mSetMask & (1UL << int(opt)))) - return not_set; - value = &mHttpProxy; + src = &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 f4bb4d4b25..a50d0e4188 100644 --- a/indra/llcorehttp/_httppolicyglobal.h +++ b/indra/llcorehttp/_httppolicyglobal.h @@ -48,8 +48,8 @@ private: 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 get(HttpRequest::EGlobalPolicy opt, long * value); + HttpStatus get(HttpRequest::EGlobalPolicy opt, const std::string ** value); public: unsigned long mSetMask; @@ -57,6 +57,8 @@ public: std::string mCAPath; std::string mCAFile; std::string mHttpProxy; + long mTrace; + long mUseLLProxy; }; // end class HttpPolicyGlobal } // end namespace LLCore diff --git a/indra/llcorehttp/_httpservice.cpp b/indra/llcorehttp/_httpservice.cpp index 920a3f3b6d..beba8f08f4 100644 --- a/indra/llcorehttp/_httpservice.cpp +++ b/indra/llcorehttp/_httpservice.cpp @@ -36,6 +36,7 @@ #include "_thread.h" #include "lltimer.h" +#include "llthread.h" // Tuning parameters @@ -186,8 +187,10 @@ void HttpService::shutdown() void HttpService::threadRun(LLCoreInt::HttpThread * thread) { boost::this_thread::disable_interruption di; - ELoopSpeed loop(REQUEST_SLEEP); + + LLThread::registerThreadID(); + ELoopSpeed loop(REQUEST_SLEEP); while (! mExitRequested) { loop = processRequestQueue(loop); @@ -226,6 +229,19 @@ HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop) // Process operation if (! mExitRequested) { + // Setup for subsequent tracing + long tracing(0); + mPolicy->getGlobalOptions().get(HttpRequest::GP_TRACE, &tracing); + op->mTracing = (std::max)(op->mTracing, int(tracing)); + + if (op->mTracing > 0) + { + LL_INFOS("CoreHttp") << "TRACE, FromRequestQueue, Handle: " + << static_cast(op) + << LL_ENDL; + } + + // Stage op->stageFromRequest(this); } diff --git a/indra/llcorehttp/httpoptions.cpp b/indra/llcorehttp/httpoptions.cpp index 15b505f5bf..155fbda7f1 100644 --- a/indra/llcorehttp/httpoptions.cpp +++ b/indra/llcorehttp/httpoptions.cpp @@ -33,13 +33,15 @@ namespace LLCore HttpOptions::HttpOptions() : RefCounted(true), - mWantHeaders(false) + mWantHeaders(false), + mTracing(0) {} HttpOptions::HttpOptions(const HttpOptions & rhs) : RefCounted(true), - mWantHeaders(rhs.mWantHeaders) + mWantHeaders(rhs.mWantHeaders), + mTracing(rhs.mTracing) {} @@ -47,4 +49,16 @@ HttpOptions::~HttpOptions() {} +void HttpOptions::setWantHeaders() +{ + mWantHeaders = true; +} + + +void HttpOptions::setTrace(long level) +{ + mTracing = int(level); +} + + } // end namespace LLCore diff --git a/indra/llcorehttp/httpoptions.h b/indra/llcorehttp/httpoptions.h index 267a982dd5..0b9dfdc1de 100644 --- a/indra/llcorehttp/httpoptions.h +++ b/indra/llcorehttp/httpoptions.h @@ -67,11 +67,21 @@ protected: void operator=(const HttpOptions &); // Not defined public: + void setWantHeaders(); + bool getWantHeaders() const + { + return mWantHeaders; + } + + void setTrace(long level); + int getTrace() const + { + return mTracing; + } protected: - // *TODO: add some options bool mWantHeaders; - + long int mTracing; }; // end class HttpOptions diff --git a/indra/llcorehttp/httprequest.cpp b/indra/llcorehttp/httprequest.cpp index 089eee76f3..2036ecfd1c 100644 --- a/indra/llcorehttp/httprequest.cpp +++ b/indra/llcorehttp/httprequest.cpp @@ -135,6 +135,33 @@ HttpStatus HttpRequest::getStatus() const } +HttpHandle HttpRequest::requestGet(policy_t policy_id, + priority_t priority, + const std::string & url, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * user_handler) +{ + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpRequest * op = new HttpOpRequest(); + if (! (status = op->setupGet(policy_id, priority, url, options, headers))) + { + op->release(); + mLastReqStatus = status; + return handle; + } + op->setReplyPath(mReplyQueue, user_handler); + mRequestQueue->addOp(op); // transfers refcount + + mLastReqStatus = status; + handle = static_cast(op); + + return handle; +} + + HttpHandle HttpRequest::requestGetByteRange(policy_t policy_id, priority_t priority, const std::string & url, diff --git a/indra/llcorehttp/httprequest.h b/indra/llcorehttp/httprequest.h index a953aa28d0..4e78ed3719 100644 --- a/indra/llcorehttp/httprequest.h +++ b/indra/llcorehttp/httprequest.h @@ -111,10 +111,43 @@ public: /// Maximum number of connections the library will use to /// perform operations. This is somewhat soft as the underlying /// transport will cache some connections (up to 5). - GP_CONNECTION_LIMIT, ///< Takes long giving number of connections - GP_CA_PATH, ///< System path/directory where SSL certs are stored. - GP_CA_FILE, ///< System path/file containing certs. - GP_HTTP_PROXY ///< String giving host/port to use for HTTP proxy + + /// A long value setting the maximum number of connections + /// allowed over all policy classes. Note that this will be + /// a somewhat soft value. There may be an additional five + /// connections per policy class depending upon runtime + /// behavior. + GP_CONNECTION_LIMIT, + + /// String containing a system-appropriate directory name + /// where SSL certs are stored. + GP_CA_PATH, + + /// String giving a full path to a file containing SSL certs. + GP_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, + + /// 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, + + /// Long value setting the logging trace level for the + /// library. Possible values are: + /// 0 - No tracing (default) + /// 1 - Basic tracing of request start, stop and major events. + /// 2 - Connection, header and payload size information from + /// HTTP transactions. + /// 3 - Partial logging of payload itself. + /// + /// 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 @@ -133,7 +166,7 @@ public: /// the class in other methods. If -1, an error /// occurred and @see getStatus() may provide more /// detail on the reason. - policy_t createPolicyClass(); + static policy_t createPolicyClass(); enum EClassPolicy { @@ -157,7 +190,7 @@ public: /// @param opt Enum of option to be set. /// @param value Desired value of option. /// @return Standard status code. - HttpStatus setPolicyClassOption(policy_t policy_id, EClassPolicy opt, long value); + static HttpStatus setPolicyClassOption(policy_t policy_id, EClassPolicy opt, long value); /// @} @@ -176,6 +209,36 @@ public: /// HttpStatus getStatus() const; + /// Queue a full HTTP GET request to be issued for entire entity. + /// The request is queued and serviced by the working thread and + /// notification of completion delivered to the optional HttpHandler + /// argument during @see update() calls. + /// + /// With a valid handle returned, it can be used to reference the + /// request in other requests (like cancellation) and will be an + /// argument when any HttpHandler object is invoked. + /// + /// @param policy_id Default or user-defined policy class under + /// which this request is to be serviced. + /// @param priority Standard priority scheme inherited from + /// Indra code base (U32-type scheme). + /// @param url + /// @param options (optional) + /// @param headers (optional) + /// @param handler (optional) + /// @return The handle of the request if successfully + /// queued or LLCORE_HTTP_HANDLE_INVALID if the + /// request could not be queued. In the latter + /// case, @see getStatus() will return more info. + /// + HttpHandle requestGet(policy_t policy_id, + priority_t priority, + const std::string & url, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * handler); + + /// Queue a full HTTP GET request to be issued with a 'Range' header. /// The request is queued and serviced by the working thread and /// notification of completion delivered to the optional HttpHandler diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index 61698f34d8..5b04796c8a 100644 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -395,7 +395,7 @@ void HttpRequestTestObjectType::test<5>() { ScopedCurlInit ready; - set_test_name("HttpRequest GET + Stop execution"); + set_test_name("HttpRequest GET to dead port + Stop execution"); // Handler can be stack-allocated *if* there are no dangling // references to it after completion of this method. @@ -496,6 +496,7 @@ void HttpRequestTestObjectType::test<5>() } } + template <> template <> void HttpRequestTestObjectType::test<6>() { @@ -517,6 +518,114 @@ void HttpRequestTestObjectType::test<6>() HttpRequest * req = 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()); + + // Issue a GET that *can* connect + mStatus = HttpStatus(200); + HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + NULL, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#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); + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +template <> template <> +void HttpRequestTestObjectType::test<7>() +{ + ScopedCurlInit ready; + + std::string url_base(get_base_url()); + std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest GET with Range: header to real service"); + + // 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"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + try { // Get singletons created @@ -605,8 +714,9 @@ void HttpRequestTestObjectType::test<6>() } } + template <> template <> -void HttpRequestTestObjectType::test<7>() +void HttpRequestTestObjectType::test<8>() { ScopedCurlInit ready; @@ -725,7 +835,7 @@ void HttpRequestTestObjectType::test<7>() } template <> template <> -void HttpRequestTestObjectType::test<8>() +void HttpRequestTestObjectType::test<9>() { ScopedCurlInit ready; @@ -843,6 +953,118 @@ void HttpRequestTestObjectType::test<8>() } } +template <> template <> +void HttpRequestTestObjectType::test<10>() +{ + ScopedCurlInit ready; + + std::string url_base(get_base_url()); + std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest GET with some tracing"); + + // 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"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Enable tracing + HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, 2); + + // 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()); + + // Issue a GET that *can* connect + mStatus = HttpStatus(200); + HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + 0, + 0, + NULL, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#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); + delete req; + HttpRequest::destroyService(); + throw; + } +} + } // end namespace tut namespace diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index e2c13e77e3..430dd89c3e 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -5356,11 +5356,9 @@ void CoreHttp::init() << LL_ENDL; } - mRequest = new LLCore::HttpRequest; - // Point to our certs or SSH/https: will fail on connect - status = mRequest->setPolicyGlobalOption(LLCore::HttpRequest::GP_CA_FILE, - gDirUtilp->getCAFile()); + status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_CA_FILE, + gDirUtilp->getCAFile()); if (! status) { LL_ERRS("Init") << "Failed to set CA File for HTTP services. Reason: " @@ -5371,15 +5369,22 @@ void CoreHttp::init() // 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 = mRequest->setPolicyGlobalOption(LLCore::HttpRequest::GP_HTTP_PROXY, - std::string("LLProxy")); + status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_LLPROXY, 1); if (! status) { LL_ERRS("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): + // 0 - None + // 1 - Basic start, stop simple transitions + // 2 - libcurl CURLOPT_VERBOSE mode with brief lines + // 3 - with partial data content + status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, 0); + + // Kick the thread status = LLCore::HttpRequest::startThread(); if (! status) { @@ -5388,6 +5393,7 @@ void CoreHttp::init() << LL_ENDL; } + mRequest = new LLCore::HttpRequest; } diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index f5e7540e85..664af02f78 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -431,6 +431,22 @@ private: void lockWorkMutex() { mWorkMutex.lock(); } void unlockWorkMutex() { mWorkMutex.unlock(); } + // Locks: Mw + void acquireHttpSemaphore() + { + llassert(! mHttpHasResource); + mHttpHasResource = true; + --mFetcher->mHttpSemaphore; + } + + // Locks: Mw + void releaseHttpSemaphore() + { + llassert(mHttpHasResource); + mHttpHasResource = false; + ++mFetcher->mHttpSemaphore; + } + private: enum e_state // mState { @@ -444,8 +460,9 @@ private: CACHE_POST, LOAD_FROM_NETWORK, LOAD_FROM_SIMULATOR, - SEND_HTTP_REQ, // Commit to sending as HTTP WAIT_HTTP_RESOURCE, // Waiting for HTTP resources + WAIT_HTTP_RESOURCE2, // Waiting for HTTP resources + SEND_HTTP_REQ, // Commit to sending as HTTP WAIT_HTTP_REQ, // Request sent, wait for completion DECODE_IMAGE, DECODE_IMAGE_UPDATE, @@ -532,7 +549,7 @@ private: bool mHttpActive; // Active request to http library unsigned int mHttpReplySize; unsigned int mHttpReplyOffset; - bool mHttpReleased; // Has been released from resource wait once + bool mHttpHasResource; // Counts against Fetcher's mHttpSemaphore }; ////////////////////////////////////////////////////////////////////////////// @@ -768,8 +785,9 @@ const char* LLTextureFetchWorker::sStateDescs[] = { "CACHE_POST", "LOAD_FROM_NETWORK", "LOAD_FROM_SIMULATOR", - "SEND_HTTP_REQ", "WAIT_HTTP_RESOURCE", + "WAIT_HTTP_RESOURCE2", + "SEND_HTTP_REQ", "WAIT_HTTP_REQ", "DECODE_IMAGE", "DECODE_IMAGE_UPDATE", @@ -836,7 +854,7 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher, mHttpActive(false), mHttpReplySize(0U), mHttpReplyOffset(0U), - mHttpReleased(true) + mHttpHasResource(false) { mCanUseNET = mUrl.empty() ; @@ -860,6 +878,10 @@ LLTextureFetchWorker::~LLTextureFetchWorker() llassert_always(!haveWork()); lockWorkMutex(); // +Mw (should be useless) + if (mHttpHasResource) + { + releaseHttpSemaphore(); + } if (mHttpActive) { // Issue a cancel on a live request... @@ -1126,7 +1148,7 @@ bool LLTextureFetchWorker::doWork(S32 param) llwarns << "Unknown URL Type: " << mUrl << llendl; } setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); - mState = SEND_HTTP_REQ; + mState = WAIT_HTTP_RESOURCE; } else { @@ -1223,7 +1245,7 @@ bool LLTextureFetchWorker::doWork(S32 param) } if (mCanUseHTTP && !mUrl.empty()) { - mState = SEND_HTTP_REQ; + mState = WAIT_HTTP_RESOURCE; setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); if(mWriteToCacheState != NOT_WRITE) { @@ -1287,31 +1309,38 @@ bool LLTextureFetchWorker::doWork(S32 param) return false; } - if (mState == SEND_HTTP_REQ) + if (mState == WAIT_HTTP_RESOURCE) { - if (! mCanUseHTTP) - { - return true; // abort - } - // NOTE: // control the number of the http requests issued for: // 1, not openning too many file descriptors at the same time; // 2, control the traffic of http so udp gets bandwidth. // - if (! mHttpReleased) + // If it looks like we're busy, keep this request here. + // Otherwise, advance into the HTTP states. + if (mFetcher->mHttpSemaphore <= 0 || mFetcher->getHttpWaitersCount()) { - // If this request hasn't been released before and it looks like - // we're busy, put this request into resource wait and allow something - // else to come to the front. - if (mFetcher->getNumHTTPRequests() >= HTTP_REQUESTS_IN_QUEUE_HIGH_WATER || - mFetcher->getHttpWaitersCount()) - { - mState = WAIT_HTTP_RESOURCE; - setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); - mFetcher->addHttpWaiter(this->mID); - return false; - } + mState = WAIT_HTTP_RESOURCE2; + setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); + mFetcher->addHttpWaiter(this->mID); + return false; + } + mState = SEND_HTTP_REQ; + acquireHttpSemaphore(); + } + + if (mState == WAIT_HTTP_RESOURCE2) + { + // Just idle it if we make it to the head... + return false; + } + + if (mState == SEND_HTTP_REQ) + { + if (! mCanUseHTTP) + { + releaseHttpSemaphore(); + return true; // abort } mFetcher->removeFromNetworkQueue(this, false); @@ -1327,10 +1356,12 @@ bool LLTextureFetchWorker::doWork(S32 param) // We already have all the data, just decode it mLoadedDiscard = mFormattedImage->getDiscardLevel(); mState = DECODE_IMAGE; + releaseHttpSemaphore(); return false; } else { + releaseHttpSemaphore(); return true; // abort. } } @@ -1365,6 +1396,7 @@ bool LLTextureFetchWorker::doWork(S32 param) { llwarns << "HTTP GET request failed for " << mID << llendl; resetFormattedData(); + releaseHttpSemaphore(); return true; // failed } @@ -1377,13 +1409,6 @@ bool LLTextureFetchWorker::doWork(S32 param) // fall through } - if (mState == WAIT_HTTP_RESOURCE) - { - // Nothing to do until releaseHttpWaiters() puts us back - // into the flow... - return false; - } - if (mState == WAIT_HTTP_REQ) { if (mLoaded) @@ -1401,6 +1426,7 @@ bool LLTextureFetchWorker::doWork(S32 param) mState = INIT; mCanUseHTTP = false; setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); + releaseHttpSemaphore(); return false; } } @@ -1423,12 +1449,14 @@ bool LLTextureFetchWorker::doWork(S32 param) // Use available data mLoadedDiscard = mFormattedImage->getDiscardLevel(); mState = DECODE_IMAGE; + releaseHttpSemaphore(); return false; } // Fail harder resetFormattedData(); mState = DONE; + releaseHttpSemaphore(); return true; // failed } @@ -1443,6 +1471,7 @@ bool LLTextureFetchWorker::doWork(S32 param) // abort. mState = DONE; + releaseHttpSemaphore(); return true; } @@ -1491,6 +1520,7 @@ bool LLTextureFetchWorker::doWork(S32 param) mWriteToCacheState = SHOULD_WRITE ; } setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); + releaseHttpSemaphore(); return false; } else @@ -2137,7 +2167,8 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image mQAMode(qa_mode), mHttpRequest(NULL), mHttpOptions(NULL), - mHttpHeaders(NULL) + mHttpHeaders(NULL), + mHttpSemaphore(HTTP_REQUESTS_IN_QUEUE_HIGH_WATER) { mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS"); mTextureInfo.setUpLogging(gSavedSettings.getBOOL("LogTextureDownloadsToViewerLog"), gSavedSettings.getBOOL("LogTextureDownloadsToSimulator"), gSavedSettings.getU32("TextureLoggingThreshold")); @@ -3155,12 +3186,11 @@ void LLTextureFetch::removeHttpWaiter(const LLUUID & tid) // Locks: -Mw (must not hold any worker when called) void LLTextureFetch::releaseHttpWaiters() { - if (HTTP_REQUESTS_IN_QUEUE_LOW_WATER < getNumHTTPRequests()) + if (mHttpSemaphore < HTTP_REQUESTS_IN_QUEUE_LOW_WATER) return; // Quickly make a copy of all the LLUIDs. Get off the // mutex as early as possible. - typedef std::vector uuid_vec_t; uuid_vec_t tids; @@ -3171,13 +3201,12 @@ void LLTextureFetch::releaseHttpWaiters() return; const size_t limit(mHttpWaitResource.size()); - tids.resize(limit); - wait_http_res_queue_t::iterator iter(mHttpWaitResource.begin()); - for (int i(0); - i < limit && mHttpWaitResource.end() != iter; - ++i, ++iter) + tids.reserve(limit); + for (wait_http_res_queue_t::iterator iter(mHttpWaitResource.begin()); + mHttpWaitResource.end() != iter; + ++iter) { - tids[i] = *iter; + tids.push_back(*iter); } } // -Mfnq @@ -3196,28 +3225,29 @@ void LLTextureFetch::releaseHttpWaiters() tids2.insert(worker); } } + tids.clear(); // Release workers up to the high water mark. Since we aren't // holding any locks at this point, we can be in competition // with other callers. Do defensive things like getting // refreshed counts of requests and checking if someone else // has moved any worker state around.... - tids.clear(); for (worker_set_t::iterator iter2(tids2.begin()); - tids2.end() != iter2 && 0 < (HTTP_REQUESTS_IN_QUEUE_HIGH_WATER - getNumHTTPRequests()); + tids2.end() != iter2 && mHttpSemaphore > 0; ++iter2) { LLTextureFetchWorker * worker(* iter2); worker->lockWorkMutex(); // +Mw - if (LLTextureFetchWorker::WAIT_HTTP_RESOURCE != worker->mState) + if (LLTextureFetchWorker::WAIT_HTTP_RESOURCE2 != worker->mState) { worker->unlockWorkMutex(); // -Mw continue; } - worker->mHttpReleased = true; + worker->mState = LLTextureFetchWorker::SEND_HTTP_REQ; worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority); + worker->acquireHttpSemaphore(); worker->unlockWorkMutex(); // -Mw removeHttpWaiter(worker->mID); @@ -3456,7 +3486,7 @@ TFReqSendMetrics::doWork(LLTextureFetch * fetcher) } // In QA mode, Metrics submode, log the result for ease of testing - if (fetcher->isQAMode() || true) + if (fetcher->isQAMode()) { LL_INFOS("Textures") << merged_llsd << LL_ENDL; } diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index 4ee13d171e..50e3181623 100644 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -312,6 +312,15 @@ private: LLCore::HttpOptions * mHttpOptions; // Ttf LLCore::HttpHeaders * mHttpHeaders; // Ttf + // We use a resource semaphore to keep HTTP requests in + // WAIT_HTTP_RESOURCE2 if there aren't sufficient slots in the + // transport. This keeps them near where they can be cheaply + // reprioritized rather than dumping them all across a thread + // where it's more expensive to get at them. Requests in either + // SEND_HTTP_REQ or WAIT_HTTP_REQ charge against the semaphore + // and tracking state transitions is critical to liveness. + int mHttpSemaphore; // Ttf + typedef std::set wait_http_res_queue_t; wait_http_res_queue_t mHttpWaitResource; // Mfnq diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index 5f1d7829ed..bb1535d23d 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -234,15 +234,16 @@ void LLTextureBar::draw() { "DSK", LLColor4::blue }, // CACHE_POST { "NET", LLColor4::green }, // LOAD_FROM_NETWORK { "SIM", LLColor4::green }, // LOAD_FROM_SIMULATOR + { "HTW", LLColor4::green }, // WAIT_HTTP_RESOURCE + { "HTW", LLColor4::green }, // WAIT_HTTP_RESOURCE2 { "REQ", LLColor4::yellow },// SEND_HTTP_REQ - { "HTW", LLColor4::green }, // WAIT_HTTP_RES { "HTP", LLColor4::green }, // WAIT_HTTP_REQ { "DEC", LLColor4::yellow },// DECODE_IMAGE { "DEC", LLColor4::green }, // DECODE_IMAGE_UPDATE { "WRT", LLColor4::purple },// WRITE_TO_CACHE { "WRT", LLColor4::orange },// WAIT_ON_WRITE { "END", LLColor4::red }, // DONE -#define LAST_STATE 13 +#define LAST_STATE 14 { "CRE", LLColor4::magenta }, // LAST_STATE+1 { "FUL", LLColor4::green }, // LAST_STATE+2 { "BAD", LLColor4::red }, // LAST_STATE+3 -- cgit v1.3