Use host:port as key for the "PerHost" request queue, instead of just the hostname.

Rationale: LL is doing all throttling per service (host:port), not per
service hostname. Also, textures and capabilities use the same host: the
sim you are connected to. Splitting the queues up on a per-service basis
will stop the textures from blocking a capability request.
This commit is contained in:
Aleric Inglewood
2013-04-09 04:32:36 +02:00
parent 3af89dd685
commit bb948ce6d5
6 changed files with 90 additions and 37 deletions

View File

@@ -1114,7 +1114,7 @@ void CurlEasyRequest::finalizeRequest(std::string const& url, AIHTTPTimeoutPolic
setopt(CURLOPT_HTTPHEADER, mHeaders);
setoptString(CURLOPT_URL, url);
llassert(!mPerHostPtr);
mLowercaseHostname = AIPerHostRequestQueue::extract_canonical_hostname(url);
mLowercaseServicename = AIPerHostRequestQueue::extract_canonical_servicename(url);
mTimeoutPolicy = &policy;
state_machine->setTotalDelayTimeout(policy.getTotalDelay());
// The following line is a bit tricky: we store a pointer to the object without increasing its reference count.
@@ -1140,7 +1140,7 @@ void CurlEasyRequest::finalizeRequest(std::string const& url, AIHTTPTimeoutPolic
// // get less connect time, while it still (also) has to wait for this DNS lookup.
void CurlEasyRequest::set_timeout_opts(void)
{
setopt(CURLOPT_CONNECTTIMEOUT, mTimeoutPolicy->getConnectTimeout(mLowercaseHostname));
setopt(CURLOPT_CONNECTTIMEOUT, mTimeoutPolicy->getConnectTimeout(getLowercaseHostname()));
setopt(CURLOPT_TIMEOUT, mTimeoutPolicy->getCurlTransaction());
}
@@ -1241,9 +1241,9 @@ AIPerHostRequestQueuePtr 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
// The reason we can cache it is because mLowercaseServicename is only set
// in finalizeRequest which may only be called once: it never changes.
mPerHostPtr = AIPerHostRequestQueue::instance(mLowercaseHostname);
mPerHostPtr = AIPerHostRequestQueue::instance(mLowercaseServicename);
}
return mPerHostPtr;
}
@@ -1254,6 +1254,11 @@ bool CurlEasyRequest::removeFromPerHostQueue(AICurlEasyRequest const& easy_reque
return mPerHostPtr && PerHostRequestQueue_wat(*mPerHostPtr)->cancel(easy_request);
}
std::string CurlEasyRequest::getLowercaseHostname(void) const
{
return mLowercaseServicename.substr(0, mLowercaseServicename.find_last_of(':'));
}
//-----------------------------------------------------------------------------
// BufferedCurlEasyRequest

View File

@@ -92,40 +92,86 @@ using namespace AICurlPrivate;
// - port does not contain a ':', and if it exists is always prepended by a ':'.
//
//static
std::string AIPerHostRequestQueue::extract_canonical_hostname(std::string const& url)
std::string AIPerHostRequestQueue::extract_canonical_servicename(std::string const& url)
{
std::string::size_type pos;
std::string::size_type authority = 0; // Default if there is no sheme.
if ((pos = url.find("://")) != url.npos && pos < url.find('/')) authority = pos + 3; // Skip the "sheme://" if any, the second find is to avoid finding a "://" as part of path-abempty.
std::string::size_type host = authority; // Default if there is no userinfo.
if ((pos = url.find('@', authority)) != url.npos) host = pos + 1; // Skip the "userinfo@" if any.
authority = url.length() - 1; // Default last character of host if there is no path-abempty.
if ((pos = url.find('/', host)) != url.npos) authority = pos - 1; // Point to last character of host.
std::string::size_type len = url.find_last_not_of(":0123456789", authority) - host + 1; // Skip trailing ":port", if any.
std::string hostname(url, host, len);
char const* p = url.data();
char const* const end = p + url.size();
char const* sheme_colon = NULL;
char const* sheme_slash = NULL;
char const* first_ampersand = NULL;
char const* port_colon = NULL;
std::string servicename;
char const* hostname = p; // Default in the case there is no "sheme://userinfo@".
while (p < end)
{
int c = *p;
if (c == ':')
{
if (!port_colon && std::isdigit(p[1]))
{
port_colon = p;
}
else if (!sheme_colon && !sheme_slash && !first_ampersand && !port_colon)
{
// Found a colon before any slash or ampersand: this has to be the colon between the sheme and the hier-part.
sheme_colon = p;
}
}
else if (c == '/')
{
if (!sheme_slash && sheme_colon && sheme_colon == p - 1 && !first_ampersand && p[1] == '/')
{
// Found the first '/' in the first occurance of the sequence "://".
sheme_slash = p;
hostname = ++p + 1; // Point hostname to the start of the authority, the default when there is no "userinfo@" part.
servicename.clear(); // Remove the sheme.
}
else
{
// Found slash that is not part of the "sheme://" string. Signals end of authority.
// We're done.
break;
}
}
else if (c == '@')
{
if (!first_ampersand)
{
first_ampersand = p;
hostname = p + 1;
servicename.clear(); // Remove the "userinfo@"
}
}
if (p >= hostname)
{
// Convert hostname to lowercase in a way that we compare two hostnames equal iff libcurl does.
#if APR_CHARSET_EBCDIC
#error Not implemented
#else
// Convert hostname to lowercase in a way that we compare two hostnames equal iff libcurl does.
for (std::string::iterator iter = hostname.begin(); iter != hostname.end(); ++iter)
{
int c = *iter;
if (c >= 'A' && c <= 'Z')
*iter = c + ('a' - 'A');
}
if (c >= 'A' && c <= 'Z')
c += ('a' - 'A');
#endif
return hostname;
servicename += c;
}
++p;
}
// Strip of any trailing ":80".
if (p - 3 == port_colon && p[-1] == '0' && p[-2] == '8')
{
return servicename.substr(0, p - hostname - 3);
}
return servicename;
}
//static
AIPerHostRequestQueuePtr AIPerHostRequestQueue::instance(std::string const& hostname)
AIPerHostRequestQueuePtr AIPerHostRequestQueue::instance(std::string const& servicename)
{
llassert(!hostname.empty());
llassert(!servicename.empty());
instance_map_wat instance_map_w(sInstanceMap);
AIPerHostRequestQueue::iterator iter = instance_map_w->find(hostname);
AIPerHostRequestQueue::iterator iter = instance_map_w->find(servicename);
if (iter == instance_map_w->end())
{
iter = instance_map_w->insert(instance_map_type::value_type(hostname, new RefCountedThreadSafePerHostRequestQueue)).first;
iter = instance_map_w->insert(instance_map_type::value_type(servicename, new RefCountedThreadSafePerHostRequestQueue)).first;
}
// Note: the creation of AIPerHostRequestQueuePtr MUST be protected by the lock on sInstanceMap (see release()).
return iter->second;

View File

@@ -91,8 +91,8 @@ class AIPerHostRequestQueue {
typedef instance_map_type::iterator iterator;
typedef instance_map_type::const_iterator const_iterator;
// Utility function; extract canonical (lowercase) hostname from url.
static std::string extract_canonical_hostname(std::string const& url);
// Utility function; extract canonical (lowercase) hostname and port from url.
static std::string extract_canonical_servicename(std::string const& url);
// Return (possibly create) a unique instance for the given hostname.
static AIPerHostRequestQueuePtr instance(std::string const& hostname);

View File

@@ -304,7 +304,7 @@ class CurlEasyRequest : public CurlEasyHandle {
CURLcode mResult; //AIFIXME: this does not belong in the request object, but belongs in the response object.
AIHTTPTimeoutPolicy const* mTimeoutPolicy;
std::string mLowercaseHostname; // Lowercase hostname (canonicalized) extracted from the url.
std::string mLowercaseServicename; // Lowercase hostname:port (canonicalized) extracted from the url.
AIPerHostRequestQueuePtr mPerHostPtr; // Pointer to the corresponding AIPerHostRequestQueue.
LLPointer<curlthread::HTTPTimeout> mTimeout;// Timeout administration object associated with last created CurlSocketInfo.
bool mTimeoutIsOrphan; // Set to true when mTimeout is not (yet) associated with a CurlSocketInfo.
@@ -316,7 +316,8 @@ class CurlEasyRequest : public CurlEasyHandle {
public:
// These two are only valid after finalizeRequest.
AIHTTPTimeoutPolicy const* getTimeoutPolicy(void) const { return mTimeoutPolicy; }
std::string const& getLowercaseHostname(void) const { return mLowercaseHostname; }
std::string const& getLowercaseServicename(void) const { return mLowercaseServicename; }
std::string getLowercaseHostname(void) const;
// Called by CurlSocketInfo to allow access to the last (after a redirect) HTTPTimeout object related to this request.
// This creates mTimeout (unless mTimeoutIsOrphan is set in which case it adopts the orphan).
LLPointer<curlthread::HTTPTimeout>& get_timeout_object(void);
@@ -349,7 +350,7 @@ class CurlEasyRequest : public CurlEasyHandle {
// PerHost API.
AIPerHostRequestQueuePtr getPerHostPtr(void); // (Optionally create and) return a pointer to the unique
// AIPerHostRequestQueue corresponding to mLowercaseHostname.
// AIPerHostRequestQueue corresponding to mLowercaseServicename.
bool removeFromPerHostQueue(AICurlEasyRequest const&) const; // Remove this request from the per-host queue, if queued at all.
// Returns true if it was queued.
protected:

View File

@@ -75,6 +75,7 @@ namespace AICurlPrivate {
class BufferedCurlEasyRequest {
public:
char const* getLowercaseHostname(void) const { return "hostname.com"; }
char const* getLowercaseServicename(void) const { return "hostname.com:12047"; }
void getinfo(const int&, double* p) { *p = 0.1; }
};
@@ -435,7 +436,7 @@ bool HTTPTimeout::maybe_upload_finished(void)
void HTTPTimeout::print_diagnostics(CurlEasyRequest const* curl_easy_request, char const* eff_url)
{
#ifndef HTTPTIMEOUT_TESTSUITE
llwarns << "Request to \"" << curl_easy_request->getLowercaseHostname() << "\" timed out for " << curl_easy_request->getTimeoutPolicy()->name() << llendl;
llwarns << "Request to \"" << curl_easy_request->getLowercaseServicename() << "\" timed out for " << curl_easy_request->getTimeoutPolicy()->name() << llendl;
llinfos << "Effective URL: \"" << eff_url << "\"." << llendl;
double namelookup_time, connect_time, appconnect_time, pretransfer_time, starttransfer_time;
curl_easy_request->getinfo(CURLINFO_NAMELOOKUP_TIME, &namelookup_time);

View File

@@ -798,12 +798,12 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher,
if (!mCanUseNET)
{
// Probably a file://, but well; in that case hostname will be empty.
std::string hostname = AIPerHostRequestQueue::extract_canonical_hostname(mUrl);
if (!hostname.empty())
// Probably a file://, but well; in that case servicename will be empty.
std::string servicename = AIPerHostRequestQueue::extract_canonical_servicename(mUrl);
if (!servicename.empty())
{
// Make sure mPerHostPtr is up to date with mUrl.
mPerHostPtr = AIPerHostRequestQueue::instance(hostname);
mPerHostPtr = AIPerHostRequestQueue::instance(servicename);
}
}
@@ -1162,7 +1162,7 @@ bool LLTextureFetchWorker::doWork(S32 param)
{
mUrl = http_url + "/?texture_id=" + mID.asString().c_str();
mWriteToCacheState = CAN_WRITE ; //because this texture has a fixed texture id.
mPerHostPtr = AIPerHostRequestQueue::instance(AIPerHostRequestQueue::extract_canonical_hostname(http_url));
mPerHostPtr = AIPerHostRequestQueue::instance(AIPerHostRequestQueue::extract_canonical_servicename(http_url));
}
else
{