diff --git a/indra/aistatemachine/aistatemachine.cpp b/indra/aistatemachine/aistatemachine.cpp index 065724961..cdaf2e3d4 100644 --- a/indra/aistatemachine/aistatemachine.cpp +++ b/indra/aistatemachine/aistatemachine.cpp @@ -592,7 +592,7 @@ void AIStateMachine::flush(void) AIStateMachine& statemachine(iter->statemachine()); if (statemachine.abortable()) { - // We can't safely call abort() here for non-running (run() was called, but they we're initialized yet) statemachines, + // We can't safely call abort() here for non-running (run() was called, but they weren't initialized yet) statemachines, // because that might call kill() which in some cases is undesirable (ie, when it is owned by a partent that will // also call abort() on it when it is aborted itself). if (statemachine.running()) @@ -619,12 +619,10 @@ void AIStateMachine::flush(void) AIReadAccess csme_r(sContinuedStateMachinesAndMainloopEnabled); add_continued_statemachines(csme_r); } - // Kill all state machines. - for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter) - { - AIStateMachine& statemachine(iter->statemachine()); - if (statemachine.running()) - statemachine.kill(); - } } + // At this point all statemachines should be idle. + AIReadAccess csme_r(sContinuedStateMachinesAndMainloopEnabled); + llinfos << "Current number of continued statemachines: " << csme_r->continued_statemachines.size() << llendl; + llinfos << "Current number of active statemachines: " << active_statemachines.size() << llendl; + llassert(csme_r->continued_statemachines.empty() && active_statemachines.empty()); } diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index d16b87cd7..997da6a79 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -36,26 +36,39 @@ #include "llapr.h" #include "llscopedvolatileaprpool.h" +LLFastTimer::DeclareTimer FT_WAIT_FOR_SCOPEDLOCK("LLScopedLock"); + //--------------------------------------------------------------------- // // LLScopedLock // LLScopedLock::LLScopedLock(apr_thread_mutex_t* mutex) : mMutex(mutex) { - if(mutex) + mLocked = !!mutex; + if (LL_LIKELY(mutex)) { - if(ll_apr_warn_status(apr_thread_mutex_lock(mMutex))) + apr_status_t status = apr_thread_mutex_trylock(mMutex); + while (LL_UNLIKELY(status != APR_SUCCESS)) { - mLocked = false; + if (APR_STATUS_IS_EBUSY(status)) + { + if (AIThreadID::in_main_thread_inline()) + { + LLFastTimer ft1(FT_WAIT_FOR_SCOPEDLOCK); + status = apr_thread_mutex_lock(mMutex); + } + else + { + status = apr_thread_mutex_lock(mMutex); + } + } + else + { + ll_apr_warn_status(status); + mLocked = false; + return; + } } - else - { - mLocked = true; - } - } - else - { - mLocked = false; } } diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 421ad4513..ac97d37de 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -382,9 +382,19 @@ LLCondition::~LLCondition() mAPRCondp = NULL; } +LLFastTimer::DeclareTimer FT_WAIT_FOR_CONDITION("LLCondition::wait()"); + void LLCondition::wait() { - apr_thread_cond_wait(mAPRCondp, mAPRMutexp); + if (AIThreadID::in_main_thread_inline()) + { + LLFastTimer ft1(FT_WAIT_FOR_CONDITION); + apr_thread_cond_wait(mAPRCondp, mAPRMutexp); + } + else + { + apr_thread_cond_wait(mAPRCondp, mAPRMutexp); + } } void LLCondition::signal() @@ -409,6 +419,8 @@ bool LLMutexBase::isSelfLocked() const return mLockingThread.equals_current_thread_inline(); } +LLFastTimer::DeclareTimer FT_WAIT_FOR_MUTEX("LLMutexBase::lock()"); + void LLMutexBase::lock() { if (mLockingThread.equals_current_thread_inline()) @@ -417,7 +429,18 @@ void LLMutexBase::lock() return; } - apr_thread_mutex_lock(mAPRMutexp); + if (APR_STATUS_IS_EBUSY(apr_thread_mutex_trylock(mAPRMutexp))) + { + if (AIThreadID::in_main_thread_inline()) + { + LLFastTimer ft1(FT_WAIT_FOR_MUTEX); + apr_thread_mutex_lock(mAPRMutexp); + } + else + { + apr_thread_mutex_lock(mAPRMutexp); + } + } mLockingThread.reset_inline(); } diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index f1406dc7b..8bbb150c4 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -24,6 +24,7 @@ include_directories( set(llmessage_SOURCE_FILES aicurl.cpp aicurleasyrequeststatemachine.cpp + aicurlperhost.cpp aicurlthread.cpp aihttpheaders.cpp aihttptimeoutpolicy.cpp @@ -110,6 +111,7 @@ set(llmessage_HEADER_FILES aicurl.h aicurleasyrequeststatemachine.h aicurlprivate.h + aicurlperhost.h aicurlthread.h aihttpheaders.h aihttptimeoutpolicy.h diff --git a/indra/llmessage/aicurl.cpp b/indra/llmessage/aicurl.cpp index 427721dbd..3aacd0ecd 100644 --- a/indra/llmessage/aicurl.cpp +++ b/indra/llmessage/aicurl.cpp @@ -58,6 +58,7 @@ #include "aihttpheaders.h" #include "aihttptimeoutpolicy.h" #include "aicurleasyrequeststatemachine.h" +#include "aicurlperhost.h" //================================================================================== // Debug Settings @@ -90,7 +91,6 @@ enum gSSLlib_type { // No locking needed: initialized before threads are created, and subsequently only read. gSSLlib_type gSSLlib; bool gSetoptParamsNeedDup; -void (*statemachines_flush_hook)(void); } // namespace @@ -290,10 +290,39 @@ static unsigned int encoded_version(int major, int minor, int patch) namespace AICurlInterface { -// MAIN-THREAD -void initCurl(void (*flush_hook)()) +//static +LLAtomicU32 Stats::easy_calls; +LLAtomicU32 Stats::easy_errors; +LLAtomicU32 Stats::easy_init_calls; +LLAtomicU32 Stats::easy_init_errors; +LLAtomicU32 Stats::easy_cleanup_calls; +LLAtomicU32 Stats::multi_calls; +LLAtomicU32 Stats::multi_errors; +LLAtomicU32 Stats::AICurlEasyRequest_count; +LLAtomicU32 Stats::AICurlEasyRequestStateMachine_count; +LLAtomicU32 Stats::BufferedCurlEasyRequest_count; +LLAtomicU32 Stats::ResponderBase_count; +LLAtomicU32 Stats::ThreadSafeBufferedCurlEasyRequest_count; +LLAtomicU32 Stats::status_count[100]; +LLAtomicU32 Stats::llsd_body_count; +LLAtomicU32 Stats::llsd_body_parse_error; +LLAtomicU32 Stats::raw_body_count; + +U32 Stats::status2index(U32 status) { - DoutEntering(dc::curl, "AICurlInterface::initCurl(" << (void*)flush_hook << ")"); + llassert_always(status >= 100 && status < 600 && (status % 100) < 20); // Max value 519. + return (status - 100) / 100 * 20 + status % 100; // Returns 0..99 (for status 100..519). +} + +U32 Stats::index2status(U32 index) +{ + return 100 + (index / 20) * 100 + index % 20; +} + +// MAIN-THREAD +void initCurl(void) +{ + DoutEntering(dc::curl, "AICurlInterface::initCurl()"); llassert(LLThread::getRunning() == 0); // We must not call curl_global_init unless we are the only thread. CURLcode res = curl_global_init(CURL_GLOBAL_ALL); @@ -376,9 +405,6 @@ void initCurl(void (*flush_hook)()) } llassert_always(!gSetoptParamsNeedDup); // Might add support later. } - - // Called in cleanupCurl. - statemachines_flush_hook = flush_hook; } // MAIN-THREAD @@ -391,8 +417,7 @@ void cleanupCurl(void) stopCurlThread(); if (CurlMultiHandle::getTotalMultiHandles() != 0) llwarns << "Not all CurlMultiHandle objects were destroyed!" << llendl; - if (statemachines_flush_hook) - (*statemachines_flush_hook)(); + AIStateMachine::flush(); Stats::print(); ssl_cleanup(); @@ -422,6 +447,58 @@ void setCAPath(std::string const& path) CertificateAuthority_w->path = path; } +//static +void Stats::print(void) +{ + int const easy_handles = easy_init_calls - easy_init_errors - easy_cleanup_calls; + llinfos_nf << "============ CURL STATS ============" << llendl; + llinfos_nf << " Curl multi errors/calls : " << std::dec << multi_errors << "/" << multi_calls << llendl; + llinfos_nf << " Curl easy errors/calls : " << std::dec << easy_errors << "/" << easy_calls << llendl; + llinfos_nf << " curl_easy_init() errors/calls : " << std::dec << easy_init_errors << "/" << easy_init_calls << llendl; + llinfos_nf << " Current number of curl easy handles: " << std::dec << easy_handles << llendl; +#ifdef DEBUG_CURLIO + llinfos_nf << " Current number of BufferedCurlEasyRequest objects: " << BufferedCurlEasyRequest_count << llendl; + llinfos_nf << " Current number of ThreadSafeBufferedCurlEasyRequest objects: " << ThreadSafeBufferedCurlEasyRequest_count << llendl; + llinfos_nf << " Current number of AICurlEasyRequest objects: " << AICurlEasyRequest_count << llendl; + llinfos_nf << " Current number of AICurlEasyRequestStateMachine objects: " << AICurlEasyRequestStateMachine_count << llendl; +#endif + llinfos_nf << " Current number of Responders: " << ResponderBase_count << llendl; + llinfos_nf << " Received HTTP bodies LLSD / LLSD parse errors / non-LLSD: " << llsd_body_count << "/" << llsd_body_parse_error << "/" << raw_body_count << llendl; + llinfos_nf << " Received HTTP status codes: status (count) [...]: "; + bool first = true; + for (U32 index = 0; index < 100; ++index) + { + if (status_count[index] > 0) + { + if (!first) + { + llcont << ", "; + } + else + { + first = false; + } + llcont << index2status(index) << " (" << status_count[index] << ')'; + } + } + llcont << llendl; + llinfos_nf << "========= END OF CURL STATS =========" << llendl; + // Leak tests. + // There is one easy handle per CurlEasyHandle, and BufferedCurlEasyRequest is derived from that. + // It is not allowed to create CurlEasyHandle (or CurlEasyRequest) directly, only by creating a BufferedCurlEasyRequest, + // therefore the number of existing easy handles must equal the number of BufferedCurlEasyRequest objects. + llassert(easy_handles == BufferedCurlEasyRequest_count); + // Even more strict, BufferedCurlEasyRequest may not be created directly either, only as + // base class of ThreadSafeBufferedCurlEasyRequest. + llassert(BufferedCurlEasyRequest_count == ThreadSafeBufferedCurlEasyRequest_count); + // Each AICurlEasyRequestStateMachine is responsible for exactly one easy handle. + llassert(easy_handles >= AICurlEasyRequest_count); + // Each AICurlEasyRequestStateMachine has one AICurlEasyRequest member. + llassert(AICurlEasyRequest_count >= AICurlEasyRequestStateMachine_count); + // AIFIXME: is this really always the case? And why? + llassert(easy_handles <= ResponderBase_count); +} + } // namespace AICurlInterface //================================================================================== @@ -431,31 +508,13 @@ void setCAPath(std::string const& path) namespace AICurlPrivate { +using AICurlInterface::Stats; + #if defined(CWDEBUG) || defined(DEBUG_CURLIO) // CURLOPT_DEBUGFUNCTION function. extern int debug_callback(CURL*, curl_infotype infotype, char* buf, size_t size, void* user_ptr); #endif -//static -LLAtomicU32 Stats::easy_calls; -LLAtomicU32 Stats::easy_errors; -LLAtomicU32 Stats::easy_init_calls; -LLAtomicU32 Stats::easy_init_errors; -LLAtomicU32 Stats::easy_cleanup_calls; -LLAtomicU32 Stats::multi_calls; -LLAtomicU32 Stats::multi_errors; - -//static -void Stats::print(void) -{ - llinfos_nf << "============ CURL STATS ============" << llendl; - llinfos_nf << " Curl multi errors/calls : " << std::dec << multi_errors << "/" << multi_calls << llendl; - llinfos_nf << " Curl easy errors/calls : " << std::dec << easy_errors << "/" << easy_calls << llendl; - llinfos_nf << " curl_easy_init() errors/calls : " << std::dec << easy_init_errors << "/" << easy_init_calls << llendl; - llinfos_nf << " Current number of curl easy handles: " << std::dec << (easy_init_calls - easy_init_errors - easy_cleanup_calls) << llendl; - llinfos_nf << "========= END OF CURL STATS =========" << llendl; -} - // THREAD-SAFE void handle_multi_error(CURLMcode code) { @@ -856,6 +915,10 @@ CurlEasyRequest::~CurlEasyRequest() // be available anymore. send_handle_events_to(NULL); revokeCallbacks(); + if (mPerHostPtr) + { + PerHostRequestQueue::release(mPerHostPtr); + } // This wasn't freed yet if the request never finished. curl_slist_free_all(mHeaders); } @@ -1054,6 +1117,7 @@ void CurlEasyRequest::finalizeRequest(std::string const& url, AIHTTPTimeoutPolic #endif setopt(CURLOPT_HTTPHEADER, mHeaders); setoptString(CURLOPT_URL, url); + llassert(!mPerHostPtr); mLowercaseHostname = extract_canonical_hostname(url); mTimeoutPolicy = &policy; state_machine->setTotalDelayTimeout(policy.getTotalDelay()); @@ -1171,6 +1235,24 @@ void CurlEasyRequest::print_diagnostics(CURLcode code) } } +PerHostRequestQueuePtr CurlEasyRequest::getPerHostPtr(void) +{ + if (!mPerHostPtr) + { + // mPerHostPtr is really just a speed-up cache. + // The reason we can cache it is because mLowercaseHostname is only set + // in finalizeRequest which may only be called once: it never changes. + mPerHostPtr = PerHostRequestQueue::instance(mLowercaseHostname); + } + return mPerHostPtr; +} + +bool CurlEasyRequest::removeFromPerHostQueue(AICurlEasyRequest const& easy_request) const +{ + // Note that easy_request (must) represent(s) this object; it's just passed for convenience. + return mPerHostPtr && PerHostRequestQueue_wat(*mPerHostPtr)->cancel(easy_request); +} + //----------------------------------------------------------------------------- // BufferedCurlEasyRequest @@ -1180,6 +1262,7 @@ LLChannelDescriptors const BufferedCurlEasyRequest::sChannels; BufferedCurlEasyRequest::BufferedCurlEasyRequest() : mRequestTransferedBytes(0), mResponseTransferedBytes(0), mBufferEventsTarget(NULL) { + AICurlInterface::Stats::BufferedCurlEasyRequest_count++; } #define llmaybeerrs lllog(LLApp::isRunning() ? LLError::LEVEL_ERROR : LLError::LEVEL_WARN, NULL, NULL, false, true) @@ -1207,6 +1290,7 @@ BufferedCurlEasyRequest::~BufferedCurlEasyRequest() timed_out(); } } + --AICurlInterface::Stats::BufferedCurlEasyRequest_count; } void BufferedCurlEasyRequest::timed_out(void) diff --git a/indra/llmessage/aicurl.h b/indra/llmessage/aicurl.h index c5e27db74..b7599c817 100644 --- a/indra/llmessage/aicurl.h +++ b/indra/llmessage/aicurl.h @@ -127,19 +127,43 @@ class AICurlNoBody : public AICurlError { // Things defined in this namespace are called from elsewhere in the viewer code. namespace AICurlInterface { +struct Stats { + static LLAtomicU32 easy_calls; + static LLAtomicU32 easy_errors; + static LLAtomicU32 easy_init_calls; + static LLAtomicU32 easy_init_errors; + static LLAtomicU32 easy_cleanup_calls; + static LLAtomicU32 multi_calls; + static LLAtomicU32 multi_errors; + static LLAtomicU32 AICurlEasyRequest_count; + static LLAtomicU32 AICurlEasyRequestStateMachine_count; + static LLAtomicU32 BufferedCurlEasyRequest_count; + static LLAtomicU32 ResponderBase_count; + static LLAtomicU32 ThreadSafeBufferedCurlEasyRequest_count; + static LLAtomicU32 status_count[100]; + static LLAtomicU32 llsd_body_count; + static LLAtomicU32 llsd_body_parse_error; + static LLAtomicU32 raw_body_count; + + static void print(void); + static U32 status2index(U32 status); + static U32 index2status(U32 index); +}; + //----------------------------------------------------------------------------- // Global functions. // Called to handle changes in Debug Settings. -bool handleCurlConcurrentConnections(LLSD const& newvalue); +bool handleCurlMaxTotalConcurrentConnections(LLSD const& newvalue); +bool handleCurlConcurrentConnectionsPerHost(LLSD const& newvalue); bool handleNoVerifySSLCert(LLSD const& newvalue); // Called once at start of application (from newview/llappviewer.cpp by main thread (before threads are created)), // with main purpose to initialize curl. -void initCurl(void (*)(void) = NULL); +void initCurl(void); // Called once at start of application (from LLAppViewer::initThreads), starts AICurlThread. -void startCurlThread(U32 CurlConcurrentConnections, bool NoVerifySSLCert); +void startCurlThread(U32 CurlMaxTotalConcurrentConnections, U32 CurlConcurrentConnectionsPerHost, bool NoVerifySSLCert); // Called once at end of application (from newview/llappviewer.cpp by main thread), // with purpose to stop curl threads, free curl resources and deinitialize curl. @@ -221,11 +245,14 @@ class AICurlEasyRequest { // 'new' never returned however and neither the constructor nor destructor of mBufferedCurlEasyRequest is called in this case. // This might throw AICurlNoEasyHandle. AICurlEasyRequest(void) : - mBufferedCurlEasyRequest(new AICurlPrivate::ThreadSafeBufferedCurlEasyRequest) { } + mBufferedCurlEasyRequest(new AICurlPrivate::ThreadSafeBufferedCurlEasyRequest) { AICurlInterface::Stats::AICurlEasyRequest_count++; } public: + // Update stats. + ~AICurlEasyRequest() { --AICurlInterface::Stats::AICurlEasyRequest_count; } + // Used for storing this object in a standard container (see MultiHandle::add_easy_request). - AICurlEasyRequest(AICurlEasyRequest const& orig) : mBufferedCurlEasyRequest(orig.mBufferedCurlEasyRequest) { } + AICurlEasyRequest(AICurlEasyRequest const& orig) : mBufferedCurlEasyRequest(orig.mBufferedCurlEasyRequest) { AICurlInterface::Stats::AICurlEasyRequest_count++; } // For the rest, only allow read operations. AIThreadSafeSimple& operator*(void) const { llassert(mBufferedCurlEasyRequest.get()); return *mBufferedCurlEasyRequest; } @@ -271,11 +298,11 @@ class AICurlEasyRequest { // then it's OK to construct a AICurlEasyRequest from it. // Note that the external AICurlPrivate::BufferedCurlEasyRequestPtr needs its own locking, because // it's not thread-safe in itself. - AICurlEasyRequest(AICurlPrivate::BufferedCurlEasyRequestPtr const& ptr) : mBufferedCurlEasyRequest(ptr) { } + AICurlEasyRequest(AICurlPrivate::BufferedCurlEasyRequestPtr const& ptr) : mBufferedCurlEasyRequest(ptr) { AICurlInterface::Stats::AICurlEasyRequest_count++; } // This one is obviously dangerous. It's for use only in MultiHandle::check_msg_queue. // See also the long comment in BufferedCurlEasyRequest::finalizeRequest with regard to CURLOPT_PRIVATE. - explicit AICurlEasyRequest(AICurlPrivate::ThreadSafeBufferedCurlEasyRequest* ptr) : mBufferedCurlEasyRequest(ptr) { } + explicit AICurlEasyRequest(AICurlPrivate::ThreadSafeBufferedCurlEasyRequest* ptr) : mBufferedCurlEasyRequest(ptr) { AICurlInterface::Stats::AICurlEasyRequest_count++; } }; #define AICurlPrivate DONTUSE_AICurlPrivate diff --git a/indra/llmessage/aicurleasyrequeststatemachine.cpp b/indra/llmessage/aicurleasyrequeststatemachine.cpp index a5c9f9d44..d7babe6f3 100644 --- a/indra/llmessage/aicurleasyrequeststatemachine.cpp +++ b/indra/llmessage/aicurleasyrequeststatemachine.cpp @@ -249,6 +249,7 @@ AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(void) : mTimer(NULL), mTotalDelayTimeout(AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout().getTotalDelay()) { Dout(dc::statemachine, "Calling AICurlEasyRequestStateMachine(void) [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); + AICurlInterface::Stats::AICurlEasyRequestStateMachine_count++; } void AICurlEasyRequestStateMachine::setTotalDelayTimeout(F32 totalDelayTimeout) @@ -259,5 +260,6 @@ void AICurlEasyRequestStateMachine::setTotalDelayTimeout(F32 totalDelayTimeout) AICurlEasyRequestStateMachine::~AICurlEasyRequestStateMachine() { Dout(dc::statemachine, "Calling ~AICurlEasyRequestStateMachine() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); + --AICurlInterface::Stats::AICurlEasyRequestStateMachine_count; } diff --git a/indra/llmessage/aicurlperhost.cpp b/indra/llmessage/aicurlperhost.cpp new file mode 100644 index 000000000..d2494283b --- /dev/null +++ b/indra/llmessage/aicurlperhost.cpp @@ -0,0 +1,171 @@ +/** + * @file aiperhost.cpp + * @brief Implementation of PerHostRequestQueue + * + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 04/11/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#include "sys.h" +#include "aicurlperhost.h" +#include "aicurlthread.h" + +#undef AICurlPrivate + +namespace AICurlPrivate { + +PerHostRequestQueue::threadsafe_instance_map_type PerHostRequestQueue::sInstanceMap; +U32 curl_concurrent_connections_per_host; + +//static +PerHostRequestQueuePtr PerHostRequestQueue::instance(std::string const& hostname) +{ + llassert(!hostname.empty()); + instance_map_wat instance_map_w(sInstanceMap); + PerHostRequestQueue::iterator iter = instance_map_w->find(hostname); + if (iter == instance_map_w->end()) + { + iter = instance_map_w->insert(instance_map_type::value_type(hostname, new RefCountedThreadSafePerHostRequestQueue)).first; + } + // Note: the creation of PerHostRequestQueuePtr MUST be protected by the lock on sInstanceMap (see release()). + return iter->second; +} + +//static +void PerHostRequestQueue::release(PerHostRequestQueuePtr& instance) +{ + if (instance->lastone()) + { + instance_map_wat instance_map_w(sInstanceMap); + // It is possible that 'lastone' is not up to date anymore. + // Therefore, recheck the condition now that we have locked sInstanceMap. + if (!instance->lastone()) + { + // Some other thread added this host in the meantime. + return; + } + // The reference in the map is the last one; that means there can't be any curl easy requests queued for this host. + llassert(PerHostRequestQueue_wat(*instance)->mQueuedRequests.empty()); + // Find the host and erase it from the map. + iterator const end = instance_map_w->end(); + for(iterator iter = instance_map_w->begin(); iter != end; ++iter) + { + if (instance == iter->second) + { + instance_map_w->erase(iter); + instance.reset(); + return; + } + } + // We should always find the host. + llassert(false); + } + instance.reset(); +} + +bool PerHostRequestQueue::throttled() const +{ + llassert(mAdded <= curl_concurrent_connections_per_host); + return mAdded == curl_concurrent_connections_per_host; +} + +void PerHostRequestQueue::added_to_multi_handle(void) +{ + llassert(mAdded < curl_concurrent_connections_per_host); + ++mAdded; +} + +void PerHostRequestQueue::removed_from_multi_handle(void) +{ + --mAdded; + llassert(mAdded >= 0); +} + +void PerHostRequestQueue::queue(AICurlEasyRequest const& easy_request) +{ + mQueuedRequests.push_back(easy_request); +} + +bool PerHostRequestQueue::cancel(AICurlEasyRequest const& easy_request) +{ + std::deque::iterator const end = mQueuedRequests.end(); + std::deque::iterator cur = std::find(mQueuedRequests.begin(), end, easy_request); + + if (cur == end) + return false; // Not found. + + // We can't use erase because that uses assignment to move elements, which is + // private because it isn't thread-safe for AICurlEasyRequest. Therefore, move + // the element that we found to the back with swap (could just swap with the + // end immediately, but I don't want to break the order in which requests where + // added). Swap is also not thread-safe, but OK here because it only touches the + // AICurlEasyRequest objects in the deque, and the deque is protected by the + // lock on the PerHostRequestQueue object. + std::deque::iterator prev = cur; + while (++cur != end) + { + prev->swap(*cur); // This is safe, + prev = cur; + } + mQueuedRequests.pop_back(); // if this is safe. + return true; +} + +void PerHostRequestQueue::add_queued_to(curlthread::MultiHandle* multi_handle) +{ + if (!mQueuedRequests.empty()) + { + multi_handle->add_easy_request(mQueuedRequests.front()); + mQueuedRequests.pop_front(); + } +} + +//static +void PerHostRequestQueue::purge(void) +{ + instance_map_wat instance_map_w(sInstanceMap); + for (iterator host = instance_map_w->begin(); host != instance_map_w->end(); ++host) + { + Dout(dc::curl, "Purging queue of host \"" << host->first << "\"."); + PerHostRequestQueue_wat(*host->second)->mQueuedRequests.clear(); + } +} + +// Friend functions of RefCountedThreadSafePerHostRequestQueue + +void intrusive_ptr_add_ref(RefCountedThreadSafePerHostRequestQueue* per_host) +{ + per_host->mReferenceCount++; +} + +void intrusive_ptr_release(RefCountedThreadSafePerHostRequestQueue* per_host) +{ + if (--per_host->mReferenceCount == 0) + { + delete per_host; + } +} + +} // namespace AICurlPrivate diff --git a/indra/llmessage/aicurlperhost.h b/indra/llmessage/aicurlperhost.h new file mode 100644 index 000000000..196f46ea9 --- /dev/null +++ b/indra/llmessage/aicurlperhost.h @@ -0,0 +1,131 @@ +/** + * @file aicurlperhost.h + * @brief Definition of class PerHostRequestQueue + * + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 04/11/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AICURLPERHOST_H +#define AICURLPERHOST_H + +#include "llerror.h" // llassert +#include +#include +#include +#include +#include "aithreadsafe.h" + +class AICurlEasyRequest; + +namespace AICurlPrivate { +namespace curlthread { class MultiHandle; } + +class PerHostRequestQueue; +class RefCountedThreadSafePerHostRequestQueue; + +// PerHostRequestQueue objects are created by the curl thread and destructed by the main thread. +// We need locking. +typedef AIThreadSafeSimpleDC threadsafe_PerHostRequestQueue; +typedef AIAccessConst PerHostRequestQueue_crat; +typedef AIAccess PerHostRequestQueue_rat; +typedef AIAccess PerHostRequestQueue_wat; + +// We can't put threadsafe_PerHostRequestQueue in a std::map because you can't copy a mutex. +// Therefore, use an intrusive pointer for the threadsafe type. +typedef boost::intrusive_ptr PerHostRequestQueuePtr; + +//----------------------------------------------------------------------------- +// PerHostRequestQueue + +// This class provides a static interface to create and maintain instances +// of PerHostRequestQueue objects, so that at any moment there is at most +// one instance per hostname. Those instances then are used to queue curl +// requests when the maximum number of connections for that host already +// have been reached. +class PerHostRequestQueue { + private: + typedef std::map instance_map_type; + typedef AIThreadSafeSimpleDC threadsafe_instance_map_type; + typedef AIAccess instance_map_rat; + typedef AIAccess instance_map_wat; + + static threadsafe_instance_map_type sInstanceMap; // Map of PerHostRequestQueue instances with the hostname as key. + + friend class AIThreadSafeSimpleDC; //threadsafe_PerHostRequestQueue + PerHostRequestQueue(void) : mAdded(0) { } + + public: + typedef instance_map_type::iterator iterator; + typedef instance_map_type::const_iterator const_iterator; + + // Return (possibly create) a unique instance for the given hostname. + static PerHostRequestQueuePtr instance(std::string const& hostname); + + // Release instance (object will be deleted if this was the last instance). + static void release(PerHostRequestQueuePtr& instance); + + // Remove everything. Called upon viewer exit. + static void purge(void); + + private: + typedef std::deque queued_request_type; + + int mAdded; // Number of active easy handles with this host. + queued_request_type mQueuedRequests; // Waiting (throttled) requests. + + public: + void added_to_multi_handle(void); // Called when an easy handle for this host has been added to the multi handle. + void removed_from_multi_handle(void); // Called when an easy handle for this host is removed again from the multi handle. + bool throttled(void) const; // Returns true if the maximum number of allowed requests for this host have been added to the multi handle. + + void queue(AICurlEasyRequest const& easy_request); // Add easy_request to the queue. + bool cancel(AICurlEasyRequest const& easy_request); // Remove easy_request from the queue (if it's there). + + void add_queued_to(curlthread::MultiHandle* mh); // Add queued easy handle (if any) to the multi handle. The request is removed from the queue, + // followed by either a call to added_to_multi_handle() or to queue() to add it back. + private: + // Disallow copying. + PerHostRequestQueue(PerHostRequestQueue const&) { } +}; + +class RefCountedThreadSafePerHostRequestQueue : public threadsafe_PerHostRequestQueue { + public: + RefCountedThreadSafePerHostRequestQueue(void) : mReferenceCount(0) { } + bool lastone(void) const { llassert(mReferenceCount >= 2); return mReferenceCount == 2; } + + private: + // Used by PerHostRequestQueuePtr. Object is deleted when reference count reaches zero. + LLAtomicU32 mReferenceCount; + + friend void intrusive_ptr_add_ref(RefCountedThreadSafePerHostRequestQueue* p); + friend void intrusive_ptr_release(RefCountedThreadSafePerHostRequestQueue* p); +}; + +extern U32 curl_concurrent_connections_per_host; + +} // namespace AICurlPrivate + +#endif // AICURLPERHOST_H diff --git a/indra/llmessage/aicurlprivate.h b/indra/llmessage/aicurlprivate.h index 8c8bc3621..d6bc5ee9c 100644 --- a/indra/llmessage/aicurlprivate.h +++ b/indra/llmessage/aicurlprivate.h @@ -34,9 +34,11 @@ #include #include "llatomic.h" #include "llrefcount.h" +#include "aicurlperhost.h" class AIHTTPHeaders; class AIHTTPTimeoutPolicy; +class AICurlEasyRequest; class AICurlEasyRequestStateMachine; namespace AICurlPrivate { @@ -94,7 +96,7 @@ class HTTPTimeout : public LLRefCount { // Accessor. bool has_stalled(void) const { return mStalled < sClockCount; } - // Called from CurlResponderBuffer::processOutput if a timeout occurred. + // Called from BufferedCurlEasyRequest::processOutput if a timeout occurred. void print_diagnostics(CurlEasyRequest const* curl_easy_request); #if defined(CWDEBUG) || defined(DEBUG_CURLIO) @@ -111,20 +113,8 @@ class HTTPTimeout : public LLRefCount { } // namespace curlthread -struct Stats { - static LLAtomicU32 easy_calls; - static LLAtomicU32 easy_errors; - static LLAtomicU32 easy_init_calls; - static LLAtomicU32 easy_init_errors; - static LLAtomicU32 easy_cleanup_calls; - static LLAtomicU32 multi_calls; - static LLAtomicU32 multi_errors; - - static void print(void); -}; - void handle_multi_error(CURLMcode code); -inline CURLMcode check_multi_code(CURLMcode code) { Stats::multi_calls++; if (code != CURLM_OK) handle_multi_error(code); return code; } +inline CURLMcode check_multi_code(CURLMcode code) { AICurlInterface::Stats::multi_calls++; if (code != CURLM_OK) handle_multi_error(code); return code; } bool curlThreadIsRunning(void); void wakeUpCurlThread(void); @@ -248,7 +238,7 @@ class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEven // Always first call setErrorBuffer()! static inline CURLcode check_easy_code(CURLcode code) { - Stats::easy_calls++; + AICurlInterface::Stats::easy_calls++; if (code != CURLE_OK) handle_easy_error(code); return code; @@ -373,6 +363,7 @@ class CurlEasyRequest : public CurlEasyHandle { AIHTTPTimeoutPolicy const* mTimeoutPolicy; std::string mLowercaseHostname; // Lowercase hostname (canonicalized) extracted from the url. + PerHostRequestQueuePtr mPerHostPtr; // Pointer to the corresponding PerHostRequestQueue. LLPointer mTimeout;// Timeout administration object associated with last created CurlSocketInfo. bool mTimeoutIsOrphan; // Set to true when mTimeout is not (yet) associated with a CurlSocketInfo. #if defined(CWDEBUG) || defined(DEBUG_CURLIO) @@ -414,6 +405,11 @@ class CurlEasyRequest : public CurlEasyHandle { inline ThreadSafeBufferedCurlEasyRequest* get_lockobj(void); inline ThreadSafeBufferedCurlEasyRequest const* get_lockobj(void) const; + // PerHost API. + PerHostRequestQueuePtr getPerHostPtr(void); // (Optionally create and) return a pointer to the unique + // PerHostRequestQueue corresponding to mLowercaseHostname. + bool removeFromPerHostQueue(AICurlEasyRequest const&) const; // Remove this request from the per-host queue, if queued at all. + // Returns true if it was queued. protected: // Pass events to parent. /*virtual*/ void added_to_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w); @@ -444,7 +440,7 @@ class BufferedCurlEasyRequest : public CurlEasyRequest { //void setBodyLimit(U32 size) { mBodyLimit = size; } // Post-initialization, set the parent to pass the events to. - void send_buffer_events_to(AICurlResponderBufferEvents* target) { mBufferEventsTarget = target; } + void send_buffer_events_to(AIBufferedCurlEasyRequestEvents* target) { mBufferEventsTarget = target; } protected: // Events from this class. @@ -462,7 +458,7 @@ class BufferedCurlEasyRequest : public CurlEasyRequest { std::string mReason; // The "reason" from the same header line. S32 mRequestTransferedBytes; S32 mResponseTransferedBytes; - AICurlResponderBufferEvents* mBufferEventsTarget; + AIBufferedCurlEasyRequestEvents* mBufferEventsTarget; public: static LLChannelDescriptors const sChannels; // Channel object for mInput (channel out()) and mOutput (channel in()). @@ -511,9 +507,11 @@ class ThreadSafeBufferedCurlEasyRequest : public AIThreadSafeSimplecheck_msg_queue(); } + // Clear the queued requests. + PerHostRequestQueue::purge(); } AICurlMultiHandle::destroyInstance(); } @@ -1527,31 +1530,39 @@ CURLMsg const* MultiHandle::info_read(int* msgs_in_queue) const // never increment Stats::multi_errors. However, lets just increment multi_calls // when it certainly wasn't an error... if (ret) - Stats::multi_calls++; + AICurlInterface::Stats::multi_calls++; return ret; } -static U32 curl_concurrent_connections = 8; // Initialized on start up by startCurlThread(). +static U32 curl_max_total_concurrent_connections = 32; // Initialized on start up by startCurlThread(). void MultiHandle::add_easy_request(AICurlEasyRequest const& easy_request) { - if (mAddedEasyRequests.size() < curl_concurrent_connections) // Not throttled? + bool throttled = true; // Default. + PerHostRequestQueuePtr per_host; { - CURLMcode ret; + AICurlEasyRequest_wat curl_easy_request_w(*easy_request); + per_host = curl_easy_request_w->getPerHostPtr(); + PerHostRequestQueue_wat per_host_w(*per_host); + if (mAddedEasyRequests.size() < curl_max_total_concurrent_connections && !per_host_w->throttled()) { - AICurlEasyRequest_wat curl_easy_request_w(*easy_request); curl_easy_request_w->set_timeout_opts(); - ret = curl_easy_request_w->add_handle_to_multi(curl_easy_request_w, mMultiHandle); - } - if (ret == CURLM_OK) - { - std::pair res = mAddedEasyRequests.insert(easy_request); - llassert(res.second); // May not have been added before. - Dout(dc::curl, "MultiHandle::add_easy_request: Added AICurlEasyRequest " << (void*)easy_request.get_ptr().get() << "; now processing " << mAddedEasyRequests.size() << " easy handles."); - return; + if (curl_easy_request_w->add_handle_to_multi(curl_easy_request_w, mMultiHandle) == CURLM_OK) + { + per_host_w->added_to_multi_handle(); // (About to be) added to mAddedEasyRequests. + throttled = false; // Fall through... + } } + } // Release the lock on easy_request. + if (!throttled) + { // ... to here. + std::pair res = mAddedEasyRequests.insert(easy_request); + llassert(res.second); // May not have been added before. + Dout(dc::curl, "MultiHandle::add_easy_request: Added AICurlEasyRequest " << (void*)easy_request.get_ptr().get() << "; now processing " << mAddedEasyRequests.size() << " easy handles."); + return; } - mQueuedRequests.push_back(easy_request); + // The request could not be added, we have to queue it. + PerHostRequestQueue_wat(*per_host)->queue(easy_request); #ifdef SHOW_ASSERT // Not active yet, but it's no longer an error if next we try to remove the request. AICurlEasyRequest_wat(*easy_request)->mRemovedPerCommand = false; @@ -1560,31 +1571,22 @@ void MultiHandle::add_easy_request(AICurlEasyRequest const& easy_request) CURLMcode MultiHandle::remove_easy_request(AICurlEasyRequest const& easy_request, bool as_per_command) { + AICurlEasyRequest_wat easy_request_w(*easy_request); addedEasyRequests_type::iterator iter = mAddedEasyRequests.find(easy_request); if (iter == mAddedEasyRequests.end()) { // The request could be queued. - std::deque::iterator const end = mQueuedRequests.end(); - std::deque::iterator cur = std::find(mQueuedRequests.begin(), end, easy_request); - if (cur != end) - { - // We can't use erase because that uses assignment to move elements, which is private because it isn't thread-safe for AICurlEasyRequest. - // Therefore, move the element that we found to the back with swap (could just swap with the end immediately, - // but I don't want to break the order in which requests where added). Swap is also not thread-safe, but OK here - // because it only touches the AICurlEasyRequest objects in the deque, and the deque is protected by the - // lock on MultiHandle. - std::deque::iterator prev = cur; - while (++cur != end) - { - prev->swap(*cur); - prev = cur; - } #ifdef SHOW_ASSERT - // Now a second remove command would be an error again. - AICurlEasyRequest_wat(**prev)->mRemovedPerCommand = true; + bool removed = #endif - mQueuedRequests.pop_back(); + easy_request_w->removeFromPerHostQueue(easy_request); +#ifdef SHOW_ASSERT + if (removed) + { + // Now a second remove command would be an error again. + AICurlEasyRequest_wat(*easy_request)->mRemovedPerCommand = true; } +#endif return (CURLMcode)-2; // Was already removed before, or never added (queued). } return remove_easy_request(iter, as_per_command); @@ -1593,9 +1595,12 @@ CURLMcode MultiHandle::remove_easy_request(AICurlEasyRequest const& easy_request CURLMcode MultiHandle::remove_easy_request(addedEasyRequests_type::iterator const& iter, bool as_per_command) { CURLMcode res; + PerHostRequestQueuePtr per_host; { AICurlEasyRequest_wat curl_easy_request_w(**iter); res = curl_easy_request_w->remove_handle_from_multi(curl_easy_request_w, mMultiHandle); + per_host = curl_easy_request_w->getPerHostPtr(); + PerHostRequestQueue_wat(*per_host)->removed_from_multi_handle(); // (About to be) removed from mAddedEasyRequests. #ifdef SHOW_ASSERT curl_easy_request_w->mRemovedPerCommand = as_per_command; #endif @@ -1607,12 +1612,7 @@ CURLMcode MultiHandle::remove_easy_request(addedEasyRequests_type::iterator cons Dout(dc::curl, "MultiHandle::remove_easy_request: Removed AICurlEasyRequest " << (void*)lockobj << "; now processing " << mAddedEasyRequests.size() << " easy handles."); // Attempt to add a queued request, if any. - if (!mQueuedRequests.empty()) - { - add_easy_request(mQueuedRequests.front()); - mQueuedRequests.pop_front(); - } - + PerHostRequestQueue_wat(*per_host)->add_queued_to(this); return res; } @@ -2043,6 +2043,7 @@ void BufferedCurlEasyRequest::setStatusAndReason(U32 status, std::string const& { mStatus = status; mReason = reason; + AICurlInterface::Stats::status_count[AICurlInterface::Stats::status2index(mStatus)]++; } void BufferedCurlEasyRequest::processOutput(void) @@ -2455,23 +2456,37 @@ void AICurlEasyRequest::removeRequest(void) namespace AICurlInterface { -void startCurlThread(U32 CurlConcurrentConnections, bool NoVerifySSLCert) +void startCurlThread(U32 CurlMaxTotalConcurrentConnections, U32 CurlConcurrentConnectionsPerHost, bool NoVerifySSLCert) { + using namespace AICurlPrivate; using namespace AICurlPrivate::curlthread; llassert(is_main_thread()); - curl_concurrent_connections = CurlConcurrentConnections; // Debug Setting. - gNoVerifySSLCert = NoVerifySSLCert; // Debug Setting. + + // Cache Debug Settings. + curl_max_total_concurrent_connections = CurlMaxTotalConcurrentConnections; + curl_concurrent_connections_per_host = CurlConcurrentConnectionsPerHost; + gNoVerifySSLCert = NoVerifySSLCert; + AICurlThread::sInstance = new AICurlThread; AICurlThread::sInstance->start(); } -bool handleCurlConcurrentConnections(LLSD const& newvalue) +bool handleCurlMaxTotalConcurrentConnections(LLSD const& newvalue) { using namespace AICurlPrivate::curlthread; - curl_concurrent_connections = newvalue.asInteger(); - llinfos << "CurlConcurrentConnections set to " << curl_concurrent_connections << llendl; + curl_max_total_concurrent_connections = newvalue.asInteger(); + llinfos << "CurlMaxTotalConcurrentConnections set to " << curl_max_total_concurrent_connections << llendl; + return true; +} + +bool handleCurlConcurrentConnectionsPerHost(LLSD const& newvalue) +{ + using namespace AICurlPrivate; + + curl_concurrent_connections_per_host = newvalue.asInteger(); + llinfos << "CurlConcurrentConnectionsPerHost set to " << curl_concurrent_connections_per_host << llendl; return true; } diff --git a/indra/llmessage/aicurlthread.h b/indra/llmessage/aicurlthread.h index 32ebad685..fc29ac1fc 100644 --- a/indra/llmessage/aicurlthread.h +++ b/indra/llmessage/aicurlthread.h @@ -33,7 +33,6 @@ #include "aicurl.h" #include -#include #undef AICurlPrivate @@ -81,7 +80,7 @@ class MultiHandle : public CurlMultiHandle // Store result and trigger events for easy request. void finish_easy_request(AICurlEasyRequest const& easy_request, CURLcode result); // Remove easy request at iter (must exist). - // Note that it's possible that a new request from mQueuedRequests is inserted before iter. + // Note that it's possible that a new request from a PerHostRequestQueue::mQueuedRequests is inserted before iter. CURLMcode remove_easy_request(addedEasyRequests_type::iterator const& iter, bool as_per_command); static int socket_callback(CURL* easy, curl_socket_t s, int action, void* userp, void* socketp); @@ -103,10 +102,6 @@ class MultiHandle : public CurlMultiHandle PollSet* mReadPollSet; PollSet* mWritePollSet; - - private: - // Temporary throttling hack. - std::deque mQueuedRequests; // Waiting (throttled) requests. }; } // namespace curlthread diff --git a/indra/llmessage/llavatarnamecache.cpp b/indra/llmessage/llavatarnamecache.cpp index 4eac136d5..ae04a8105 100644 --- a/indra/llmessage/llavatarnamecache.cpp +++ b/indra/llmessage/llavatarnamecache.cpp @@ -182,7 +182,7 @@ private: std::vector mAgentIDs; // Need the headers to look up Expires: and Retry-After: - AIHTTPReceivedHeaders mHeaders; + virtual bool needsHeaders(void) const { return true; } public: virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return avatarNameResponder_timeout; } @@ -191,17 +191,10 @@ public: : mAgentIDs(agent_ids) { } - /*virtual*/ bool needsHeaders(void) const { return true; } - - /*virtual*/ void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers) - { - mHeaders = headers; - } - /*virtual*/ void result(const LLSD& content) { // Pull expiration out of headers if available - F64 expires = LLAvatarNameCache::nameExpirationFromHeaders(mHeaders); + F64 expires = LLAvatarNameCache::nameExpirationFromHeaders(mReceivedHeaders); F64 now = LLFrameTimer::getTotalSeconds(); LLSD agents = content["agents"]; diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index 97fce1373..b2bf549ab 100644 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -264,65 +264,76 @@ void LLHTTPClient::get(std::string const& url, LLSD const& query, ResponderPtr r LLHTTPClient::ResponderBase::ResponderBase(void) : mReferenceCount(0), mCode(CURLE_FAILED_INIT), mFinished(false) { - DoutEntering(dc::curl, "AICurlInterface::Responder() with this = " << (void*)this); + DoutEntering(dc::curl, "AICurlInterface::Responder() with this = " << (void*)this); + AICurlInterface::Stats::ResponderBase_count++; } LLHTTPClient::ResponderBase::~ResponderBase() { - DoutEntering(dc::curl, "AICurlInterface::ResponderBase::~ResponderBase() with this = " << (void*)this << "; mReferenceCount = " << mReferenceCount); - llassert(mReferenceCount == 0); + DoutEntering(dc::curl, "AICurlInterface::ResponderBase::~ResponderBase() with this = " << (void*)this << "; mReferenceCount = " << mReferenceCount); + llassert(mReferenceCount == 0); + --AICurlInterface::Stats::ResponderBase_count; } void LLHTTPClient::ResponderBase::setURL(std::string const& url) { - // setURL is called from llhttpclient.cpp (request()), before calling any of the below (of course). - // We don't need locking here therefore; it's a case of initializing before use. - mURL = url; + // setURL is called from llhttpclient.cpp (request()), before calling any of the below (of course). + // We don't need locking here therefore; it's a case of initializing before use. + mURL = url; } AIHTTPTimeoutPolicy const& LLHTTPClient::ResponderBase::getHTTPTimeoutPolicy(void) const { - return AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout(); + return AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout(); } void LLHTTPClient::ResponderBase::decode_llsd_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, LLSD& content) { - // If the status indicates success (and we get here) then we expect the body to be LLSD. - bool const should_be_llsd = (200 <= status && status < 300); - if (should_be_llsd) - { - LLBufferStream istr(channels, buffer.get()); - if (LLSDSerialize::fromXML(content, istr) == LLSDParser::PARSE_FAILURE) + AICurlInterface::Stats::llsd_body_count++; + // If the status indicates success (and we get here) then we expect the body to be LLSD. + bool const should_be_llsd = (200 <= status && status < 300); + if (should_be_llsd) { - // Unfortunately we can't show the body of the message... I think this is a pretty serious error - // though, so if this ever happens it has to be investigated by making a copy of the buffer - // before serializing it, as is done below. - llwarns << "Failed to deserialize LLSD. " << mURL << " [" << status << "]: " << reason << llendl; + LLBufferStream istr(channels, buffer.get()); + if (LLSDSerialize::fromXML(content, istr) == LLSDParser::PARSE_FAILURE) + { + // Unfortunately we can't show the body of the message... I think this is a pretty serious error + // though, so if this ever happens it has to be investigated by making a copy of the buffer + // before serializing it, as is done below. + llwarns << "Failed to deserialize LLSD. " << mURL << " [" << status << "]: " << reason << llendl; + AICurlInterface::Stats::llsd_body_parse_error++; + } + // LLSDSerialize::fromXML destructed buffer, we can't initialize content now. + return; } - // LLSDSerialize::fromXML destructed buffer, we can't initialize content now. - return; - } - // Put the body in content as-is. - std::stringstream ss; - buffer->writeChannelTo(ss, channels.in()); - content = ss.str(); + // Put the body in content as-is. + std::stringstream ss; + buffer->writeChannelTo(ss, channels.in()); + content = ss.str(); #ifdef SHOW_ASSERT - if (!should_be_llsd) - { - // Make sure that the server indeed never returns LLSD as body when the http status is an error. - LLSD dummy; - bool server_sent_llsd_with_http_error = LLSDSerialize::fromXML(dummy, ss) > 0; - if (server_sent_llsd_with_http_error) + if (!should_be_llsd) { - llwarns << "The server sent us a response with http status " << status << " and LLSD(!) body: \"" << ss.str() << "\"!" << llendl; + char const* str = ss.str().c_str(); + // Make sure that the server indeed never returns LLSD as body when the http status is an error. + LLSD dummy; + bool server_sent_llsd_with_http_error = + strncmp(str, " 0; + if (server_sent_llsd_with_http_error) + { + llwarns << "The server sent us a response with http status " << status << " and LLSD(!) body: \"" << ss.str() << "\"!" << llendl; + } + llassert(!server_sent_llsd_with_http_error); } - llassert(!server_sent_llsd_with_http_error); - } #endif } void LLHTTPClient::ResponderBase::decode_raw_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, std::string& content) { + AICurlInterface::Stats::raw_body_count++; LLMutexLock lock(buffer->getMutex()); LLBufferArray::const_segment_iterator_t const end = buffer->endSegment(); for (LLBufferArray::const_segment_iterator_t iter = buffer->beginSegment(); iter != end; ++iter) diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h index 1c8fe9c65..fa37e8b62 100644 --- a/indra/llmessage/llhttpclient.h +++ b/indra/llmessage/llhttpclient.h @@ -59,8 +59,8 @@ struct AITransferInfo { F64 mSpeedDownload; }; -// Events generated by AICurlPrivate::CurlResponderBuffer. -struct AICurlResponderBufferEvents { +// Events generated by AICurlPrivate::BufferedCurlEasyRequest +struct AIBufferedCurlEasyRequestEvents { virtual void received_HTTP_header(void) = 0; // For example "HTTP/1.0 200 OK", the first header of a reply. virtual void received_header(std::string const& key, std::string const& value) = 0; // Subsequent headers. virtual void completed_headers(U32 status, std::string const& reason, AITransferInfo* info) = 0; // Transaction completed. @@ -79,13 +79,13 @@ public: * The life cycle of classes derived from this class is as follows: * They are allocated with new on the line where get(), getByteRange() or post() is called, * and the pointer to the allocated object is then put in a reference counting ResponderPtr. - * This ResponderPtr is passed to CurlResponderBuffer::prepRequest which stores it in its + * This ResponderPtr is passed to BufferedCurlEasyRequest::prepRequest which stores it in its * member mResponder. Hence, the life time of a Responder is never longer than its - * associated CurlResponderBuffer, however, if everything works correctly, then normally a - * responder is deleted in CurlResponderBuffer::removed_from_multi_handle by setting + * associated BufferedCurlEasyRequest, however, if everything works correctly, then normally a + * responder is deleted in BufferedCurlEasyRequest::processOutput by setting * mReponder to NULL. */ - class ResponderBase : public AICurlResponderBufferEvents { + class ResponderBase : public AIBufferedCurlEasyRequestEvents { public: typedef boost::shared_ptr buffer_ptr_t; @@ -121,7 +121,7 @@ public: std::string const& getURL(void) const { return mURL; } CURLcode result_code(void) const { return mCode; } - // Called by CurlResponderBuffer::timed_out or CurlResponderBuffer::processOutput. + // Called by BufferedCurlEasyRequest::timed_out or BufferedCurlEasyRequest::processOutput. virtual void finished(CURLcode code, U32 http_status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer) = 0; // Return true if the curl thread is done with this transaction. @@ -132,7 +132,8 @@ public: bool is_finished(void) const { return mFinished; } protected: - // AICurlResponderBufferEvents + // AIBufferedCurlEasyRequestEvents + // These three events are only actually called for classes that implement a needsHeaders() that returns true. // Called when the "HTTP/1.x " header is received. /*virtual*/ void received_HTTP_header(void) @@ -167,6 +168,7 @@ public: protected: // Derived classes can override this to get the HTML headers that were received, when the message is completed. + // Only actually called for classes that implement a needsHeaders() that returns true. virtual void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers) { // The default does nothing. diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index f54d2c761..acc842818 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -352,8 +352,11 @@ void LLLineEditor::setText(const LLStringExplicit &new_text) } setCursor(llmin((S32)mText.length(), getCursor())); - // Set current history line to end of history. - mCurrentHistoryLine = mLineHistory.end() - 1; + if (!mLineHistory.empty()) + { + // Set current history line to end of history. + mCurrentHistoryLine = mLineHistory.end() - 1; + } mPrevText = mText; } diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 35313721e..ba485dc74 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4205,15 +4205,26 @@ Value 0 - CurlConcurrentConnections + CurlMaxTotalConcurrentConnections Comment - Maximum number of simultaneous curl connections + Maximum total number of simultaneous curl connections Persist 1 Type U32 Value + 64 + + CurlConcurrentConnectionsPerHost + + Comment + Maximum number of simultaneous curl connections per host + Persist + 0 + Type + U32 + Value 16 CurlMaximumNumberOfHandles diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 9ae51fe20..5913aae9c 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -617,7 +617,7 @@ bool LLAppViewer::init() // // Curl must be initialized before any thread is running. - AICurlInterface::initCurl(&AIStateMachine::flush); + AICurlInterface::initCurl(); // Logging is initialized. Now it's safe to start the error thread. startErrorThread(); @@ -1836,7 +1836,9 @@ bool LLAppViewer::initThreads() LLWatchdog::getInstance()->init(watchdog_killer_callback); } - AICurlInterface::startCurlThread(gSavedSettings.getU32("CurlConcurrentConnections"), gSavedSettings.getBOOL("NoVerifySSLCert")); + AICurlInterface::startCurlThread(gSavedSettings.getU32("CurlMaxTotalConcurrentConnections"), + gSavedSettings.getU32("CurlConcurrentConnectionsPerHost"), + gSavedSettings.getBOOL("NoVerifySSLCert")); LLImage::initClass(); diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 7feab1d86..4e1ad8645 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -796,7 +796,8 @@ void settings_setup_listeners() gSavedSettings.getControl("AscentAvatarYModifier")->getSignal()->connect(boost::bind(&handleAscentAvatarModifier, _2)); gSavedSettings.getControl("AscentAvatarZModifier")->getSignal()->connect(boost::bind(&handleAscentAvatarModifier, _2)); - gSavedSettings.getControl("CurlConcurrentConnections")->getSignal()->connect(boost::bind(&AICurlInterface::handleCurlConcurrentConnections, _2)); + gSavedSettings.getControl("CurlMaxTotalConcurrentConnections")->getSignal()->connect(boost::bind(&AICurlInterface::handleCurlMaxTotalConcurrentConnections, _2)); + gSavedSettings.getControl("CurlConcurrentConnectionsPerHost")->getSignal()->connect(boost::bind(&AICurlInterface::handleCurlConcurrentConnectionsPerHost, _2)); gSavedSettings.getControl("NoVerifySSLCert")->getSignal()->connect(boost::bind(&AICurlInterface::handleNoVerifySSLCert, _2)); gSavedSettings.getControl("CurlTimeoutDNSLookup")->getValidateSignal()->connect(boost::bind(&validateCurlTimeoutDNSLookup, _2)); diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index b8833d302..e917727d1 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -1773,7 +1773,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, static std::map state_map; std::map::iterator it = state_map.find(getID()); - if(it == state_map.end() || (!it->second && sent_parentp)) + if(it != state_map.end() && (!it->second && sent_parentp)) { it->second = sent_parentp != NULL; LL_INFOS("Attachment") << getID() << " ("<