Added AICapabilityType and related.

This splits the AIPerService queue up into four queues: one for each
"capability type". On Second Life that doesn't make a difference in
itself for textures because the texture service only serves one
capability type: textures. Other services however can serve two types,
while on Avination - that currently only has one services for everything
- this really makes a difference because that single service now has
four queues.

More importantly however is that the administration of how many requests
are in the "pipeline" (from approving that a new HTTP request may be
added for given service, till curl finished it) is now per capability
type (or service/capabitity type pair actually). This means downloads of
a certain capability type (textures, inventory, mesh, other) will no
longer stall because unapproved requests cluttered the queue for a given
service.

Moreover, before when a request did finished, it would only look for a
new request in the queue of the service that just finished. This simple
algorithm worked when there were no 'PerSerice' objects, and only one
'Curl' queue: because if anything was queued that that was because there
were running requests, and when one of those running requests finished
it made sense to see if one of those queued requests could be added now.
However, after adding multiple queues, one for each service, it could
happen that service A had queued requests while only requests from
service B were actually running: only requests of B would ever finish
and the requests of A would be queued forever.

With this patch the algorithm is to look alternating first in the
texture request queue and then in the inventory request queue - or vice
versa, and if there are none of those, look for a request of a different
type. If also that cannot be found, look for a request in another
service. This is still not optimal and subject to change.
This commit is contained in:
Aleric Inglewood
2013-05-21 23:34:23 +02:00
parent 74102e4964
commit cf4c4a72c2
10 changed files with 273 additions and 133 deletions

View File

@@ -71,14 +71,24 @@ void intrusive_ptr_release(RefCountedThreadSafePerService* per_service)
using namespace AICurlPrivate;
AIPerService::AIPerService(void) :
mApprovedRequests(0), mQueuedCommands(0), mAdded(0), mQueueEmpty(false),
mQueueFull(false), mRequestStarvation(false), mHTTPBandwidth(25), // 25 = 1000 ms / 40 ms.
mHTTPBandwidth(25), // 25 = 1000 ms / 40 ms.
mConcurrectConnections(CurlConcurrentConnectionsPerService),
mTotalAdded(0),
mApprovedFirst(0),
mUnapprovedFirst(0)
{
}
AIPerService::CapabilityType::CapabilityType(void) :
mApprovedRequests(0),
mQueuedCommands(0),
mAdded(0),
mFlags(0),
mMaxPipelinedRequests(CurlConcurrentConnectionsPerService)
{
}
AIPerService::~AIPerService()
AIPerService::CapabilityType::~CapabilityType()
{
}
@@ -212,12 +222,20 @@ void AIPerService::release(AIPerServicePtr& instance)
// Therefore, recheck the condition now that we have locked sInstanceMap.
if (!instance->exactly_two_left())
{
// Some other thread added this host in the meantime.
// Some other thread added this service 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(PerService_rat(*instance)->mQueuedRequests.empty());
// Find the host and erase it from the map.
#ifdef SHOW_ASSERT
{
// The reference in the map is the last one; that means there can't be any curl easy requests queued for this service.
PerService_rat per_service_r(*instance);
for (int i = 0; i < number_of_capability_types; ++i)
{
llassert(per_service_r->mCapabilityType[i].mQueuedRequests.empty());
}
}
#endif
// Find the service and erase it from the map.
iterator const end = instance_map_w->end();
for(iterator iter = instance_map_w->begin(); iter != end; ++iter)
{
@@ -228,7 +246,7 @@ void AIPerService::release(AIPerServicePtr& instance)
return;
}
}
// We should always find the host.
// We should always find the service.
llassert(false);
}
instance.reset();
@@ -236,30 +254,32 @@ void AIPerService::release(AIPerServicePtr& instance)
bool AIPerService::throttled() const
{
return mAdded >= mConcurrectConnections;
return mTotalAdded >= mConcurrectConnections;
}
void AIPerService::added_to_multi_handle(void)
void AIPerService::added_to_multi_handle(AICapabilityType capability_type)
{
++mAdded;
++mCapabilityType[capability_type].mAdded;
++mTotalAdded;
}
void AIPerService::removed_from_multi_handle(void)
void AIPerService::removed_from_multi_handle(AICapabilityType capability_type)
{
--mAdded;
llassert(mAdded >= 0);
--mCapabilityType[capability_type].mAdded;
--mTotalAdded;
llassert(mTotalAdded >= 0 && mCapabilityType[capability_type].mAdded >= 0);
}
void AIPerService::queue(AICurlEasyRequest const& easy_request)
void AIPerService::queue(AICurlEasyRequest const& easy_request, AICapabilityType capability_type)
{
mQueuedRequests.push_back(easy_request.get_ptr());
mCapabilityType[capability_type].mQueuedRequests.push_back(easy_request.get_ptr());
TotalQueued_wat(sTotalQueued)->count++;
}
bool AIPerService::cancel(AICurlEasyRequest const& easy_request)
bool AIPerService::cancel(AICurlEasyRequest const& easy_request, AICapabilityType capability_type)
{
queued_request_type::iterator const end = mQueuedRequests.end();
queued_request_type::iterator cur = std::find(mQueuedRequests.begin(), end, easy_request.get_ptr());
CapabilityType::queued_request_type::iterator const end = mCapabilityType[capability_type].mQueuedRequests.end();
CapabilityType::queued_request_type::iterator cur = std::find(mCapabilityType[capability_type].mQueuedRequests.begin(), end, easy_request.get_ptr());
if (cur == end)
return false; // Not found.
@@ -270,62 +290,122 @@ bool AIPerService::cancel(AICurlEasyRequest const& easy_request)
// want to break the order in which requests where added). Swap is also not
// thread-safe, but OK here because it only touches the objects in the deque,
// and the deque is protected by the lock on the AIPerService object.
queued_request_type::iterator prev = cur;
CapabilityType::queued_request_type::iterator prev = cur;
while (++cur != end)
{
prev->swap(*cur); // This is safe,
prev = cur;
}
mQueuedRequests.pop_back(); // if this is safe.
mCapabilityType[capability_type].mQueuedRequests.pop_back(); // if this is safe.
TotalQueued_wat total_queued_w(sTotalQueued);
total_queued_w->count--;
llassert(total_queued_w->count >= 0);
return true;
}
void AIPerService::add_queued_to(curlthread::MultiHandle* multi_handle)
void AIPerService::add_queued_to(curlthread::MultiHandle* multi_handle, bool recursive)
{
if (!mQueuedRequests.empty())
int order[number_of_capability_types];
// The first two types are approved types, they should be the first to try.
// Try the one that has the largest queue first, if they the queues have equal size, try mApprovedFirst first.
size_t s0 = mCapabilityType[0].mQueuedRequests.size();
size_t s1 = mCapabilityType[1].mQueuedRequests.size();
if (s0 == s1)
{
if (!multi_handle->add_easy_request(mQueuedRequests.front(), true))
{
// Throttled.
return;
}
mQueuedRequests.pop_front();
if (mQueuedRequests.empty())
{
// We obtained a request from the queue, and after that there we no more request in the queue of this host.
mQueueEmpty = true;
}
else
{
// We obtained a request from the queue, and even after that there was at least one more request in the queue of this host.
mQueueFull = true;
}
TotalQueued_wat total_queued_w(sTotalQueued);
llassert(total_queued_w->count > 0);
if (!--(total_queued_w->count))
{
// We obtained a request from the queue, and after that there we no more request in any queue.
total_queued_w->empty = true;
}
else
{
// We obtained a request from the queue, and even after that there was at least one more request in some queue.
total_queued_w->full = true;
}
order[0] = mApprovedFirst;
mApprovedFirst = 1 - mApprovedFirst;
order[1] = mApprovedFirst;
}
else if (s0 > s1)
{
order[0] = 0;
order[1] = 1;
}
else
{
// We can add a new request, but there is none in the queue!
mRequestStarvation = true;
TotalQueued_wat total_queued_w(sTotalQueued);
if (total_queued_w->count == 0)
order[0] = 1;
order[1] = 0;
}
// The next two types are unapproved types. Here, try them alternating regardless of queue size.
int n = mUnapprovedFirst;
for (int i = 2; i < number_of_capability_types; ++i, n = (n + 1) % (number_of_capability_types - 2))
{
order[i] = 2 + n;
}
mUnapprovedFirst = (mUnapprovedFirst + 1) % (number_of_capability_types - 2);
for (int i = 0; i < number_of_capability_types; ++i)
{
CapabilityType& ct(mCapabilityType[order[i]]);
if (!ct.mQueuedRequests.empty())
{
// The queue of every host is empty!
total_queued_w->starvation = true;
if (!multi_handle->add_easy_request(ct.mQueuedRequests.front(), true))
{
// Throttled. If this failed then every capability type will fail: we either are using too much bandwidth, or too many total connections.
// However, it MAY be that this service was thottled for using too much bandwidth by itself. Look if other services can be added.
break;
}
// Request was added, remove it from the queue.
ct.mQueuedRequests.pop_front();
if (ct.mQueuedRequests.empty())
{
// We obtained a request from the queue, and after that there we no more request in the queue of this service.
ct.mFlags |= ctf_empty;
}
else
{
// We obtained a request from the queue, and even after that there was at least one more request in the queue of this service.
ct.mFlags |= ctf_full;
}
TotalQueued_wat total_queued_w(sTotalQueued);
llassert(total_queued_w->count > 0);
if (!--(total_queued_w->count))
{
// We obtained a request from the queue, and after that there we no more request in any queue.
total_queued_w->empty = true;
}
else
{
// We obtained a request from the queue, and even after that there was at least one more request in some queue.
total_queued_w->full = true;
}
// We added something from a queue, so we're done.
return;
}
else
{
// We could add a new request, but there is none in the queue!
// Note that if this service does not serve this capability type,
// then obviously this queue was empty; however, in that case
// this variable will never be looked at, so it's ok to set it.
ct.mFlags |= ctf_starvation;
}
if (i == number_of_capability_types - 1)
{
// Last entry also empty. All queues of this service were empty. Check total connections.
TotalQueued_wat total_queued_w(sTotalQueued);
if (total_queued_w->count == 0)
{
// The queue of every service is empty!
total_queued_w->starvation = true;
return;
}
}
}
if (recursive)
{
return;
}
// Nothing from this service could be added, try other services.
instance_map_wat instance_map_w(sInstanceMap);
for (iterator service = instance_map_w->begin(); service != instance_map_w->end(); ++service)
{
PerService_wat per_service_w(*service->second);
if (&*per_service_w == this)
{
continue;
}
per_service_w->add_queued_to(multi_handle, true);
}
}
@@ -333,15 +413,18 @@ void AIPerService::add_queued_to(curlthread::MultiHandle* multi_handle)
void AIPerService::purge(void)
{
instance_map_wat instance_map_w(sInstanceMap);
for (iterator host = instance_map_w->begin(); host != instance_map_w->end(); ++host)
for (iterator service = instance_map_w->begin(); service != instance_map_w->end(); ++service)
{
Dout(dc::curl, "Purging queue of host \"" << host->first << "\".");
PerService_wat per_service_w(*host->second);
size_t s = per_service_w->mQueuedRequests.size();
per_service_w->mQueuedRequests.clear();
Dout(dc::curl, "Purging queues of service \"" << service->first << "\".");
PerService_wat per_service_w(*service->second);
TotalQueued_wat total_queued_w(sTotalQueued);
total_queued_w->count -= s;
llassert(total_queued_w->count >= 0);
for (int i = 0; i < number_of_capability_types; ++i)
{
size_t s = per_service_w->mCapabilityType[i].mQueuedRequests.size();
per_service_w->mCapabilityType[i].mQueuedRequests.clear();
total_queued_w->count -= s;
llassert(total_queued_w->count >= 0);
}
}
}
@@ -355,7 +438,10 @@ void AIPerService::adjust_concurrent_connections(int increment)
U32 old_concurrent_connections = per_service_w->mConcurrectConnections;
per_service_w->mConcurrectConnections = llclamp(old_concurrent_connections + increment, (U32)1, CurlConcurrentConnectionsPerService);
increment = per_service_w->mConcurrectConnections - old_concurrent_connections;
per_service_w->mMaxPipelinedRequests = llmax(per_service_w->mMaxPipelinedRequests + increment, 0);
for (int i = 0; i < number_of_capability_types; ++i)
{
per_service_w->mCapabilityType[i].mMaxPipelinedRequests = llmax(per_service_w->mCapabilityType[i].mMaxPipelinedRequests + increment, (U32)0);
}
}
}
@@ -365,8 +451,8 @@ void AIPerService::Approvement::honored(void)
{
mHonored = true;
AICurlPrivate::PerService_wat per_service_w(*mPerServicePtr);
llassert(per_service_w->mApprovedRequests > 0);
per_service_w->mApprovedRequests--;
llassert(per_service_w->mCapabilityType[mCapabilityType].mApprovedRequests > 0);
per_service_w->mCapabilityType[mCapabilityType].mApprovedRequests--;
}
}