Add mechanism for global queueing and prioritization of DNS.
Currently this limit is very high (50), but it can be tuned through a variable.
BUG=9598
TEST=
HostResolverImplTest.HigherPriorityRequestsStartedFirst
HostResolverImplTest.CancelPendingRequest
HostResolverImplTest.QueueOverflow
Review URL: http://codereview.chromium.org/542086
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@37608 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/base/host_resolver_impl.cc b/net/base/host_resolver_impl.cc
index 5644223..b81b4cb 100644
--- a/net/base/host_resolver_impl.cc
+++ b/net/base/host_resolver_impl.cc
@@ -4,6 +4,10 @@
#include "net/base/host_resolver_impl.h"
+#include <cmath>
+#include <deque>
+
+#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/debug_util.h"
#include "base/message_loop.h"
@@ -39,8 +43,15 @@
} // anonymous namespace
HostResolver* CreateSystemHostResolver() {
+ // Maximum of 50 concurrent threads.
+ // TODO(eroman): Adjust this, do some A/B experiments.
+ static const size_t kMaxJobs = 50u;
+
// TODO(willchan): Pass in the NetworkChangeNotifier.
- return new HostResolverImpl(NULL, CreateDefaultCache(), NULL);
+ HostResolverImpl* resolver = new HostResolverImpl(
+ NULL, CreateDefaultCache(), NULL, kMaxJobs);
+
+ return resolver;
}
static int ResolveAddrInfo(HostResolverProc* resolver_proc,
@@ -212,6 +223,13 @@
return requests_;
}
+ // Returns the first request attached to the job.
+ const Request* initial_request() const {
+ DCHECK_EQ(origin_loop_, MessageLoop::current());
+ DCHECK(!requests_.empty());
+ return requests_[0];
+ }
+
private:
friend class base::RefCountedThreadSafe<HostResolverImpl::Job>;
@@ -289,16 +307,172 @@
//-----------------------------------------------------------------------------
+// We rely on the priority enum values being sequential having starting at 0,
+// and increasing for lower priorities.
+COMPILE_ASSERT(HIGHEST == 0u &&
+ LOWEST > HIGHEST &&
+ NUM_PRIORITIES > LOWEST,
+ priority_indexes_incompatible);
+
+// JobPool contains all the information relating to queued requests, including
+// the limits on how many jobs are allowed to be used for this category of
+// requests.
+class HostResolverImpl::JobPool {
+ public:
+ JobPool(size_t max_outstanding_jobs, size_t max_pending_requests)
+ : num_outstanding_jobs_(0u) {
+ SetConstraints(max_outstanding_jobs, max_pending_requests);
+ }
+
+ ~JobPool() {
+ // Free the pending requests.
+ for (size_t i = 0; i < arraysize(pending_requests_); ++i)
+ STLDeleteElements(&pending_requests_[i]);
+ }
+
+ // Sets the constraints for this pool. See SetPoolConstraints() for the
+ // specific meaning of these parameters.
+ void SetConstraints(size_t max_outstanding_jobs,
+ size_t max_pending_requests) {
+ CHECK(max_outstanding_jobs != 0u);
+ max_outstanding_jobs_ = max_outstanding_jobs;
+ max_pending_requests_ = max_pending_requests;
+ }
+
+ // Returns the number of pending requests enqueued to this pool.
+ // A pending request is one waiting to be attached to a job.
+ size_t GetNumPendingRequests() const {
+ size_t total = 0u;
+ for (size_t i = 0u; i < arraysize(pending_requests_); ++i)
+ total += pending_requests_[i].size();
+ return total;
+ }
+
+ bool HasPendingRequests() const {
+ return GetNumPendingRequests() > 0u;
+ }
+
+ // Enqueues a request to this pool. As a result of enqueing this request,
+ // the queue may have reached its maximum size. In this case, a request is
+ // evicted from the queue, and returned. Otherwise returns NULL. The caller
+ // is responsible for freeing the evicted request.
+ Request* InsertPendingRequest(Request* req) {
+ PendingRequestsQueue& q = pending_requests_[req->info().priority()];
+ q.push_back(req);
+
+ // If the queue is too big, kick out the lowest priority oldest request.
+ if (GetNumPendingRequests() > max_pending_requests_) {
+ // Iterate over the queues from lowest priority to highest priority.
+ for (int i = static_cast<int>(arraysize(pending_requests_)) - 1;
+ i >= 0; --i) {
+ PendingRequestsQueue& q = pending_requests_[i];
+ if (!q.empty()) {
+ Request* req = q.front();
+ q.pop_front();
+ return req;
+ }
+ }
+ }
+
+ return NULL;
+ }
+
+ // Erases |req| from this container. Caller is responsible for freeing
+ // |req| afterwards.
+ void RemovePendingRequest(Request* req) {
+ PendingRequestsQueue& q = pending_requests_[req->info().priority()];
+ PendingRequestsQueue::iterator it = std::find(q.begin(), q.end(), req);
+ DCHECK(it != q.end());
+ q.erase(it);
+ }
+
+ // Removes and returns the highest priority pending request.
+ Request* RemoveTopPendingRequest() {
+ DCHECK(HasPendingRequests());
+
+ for (size_t i = 0u; i < arraysize(pending_requests_); ++i) {
+ PendingRequestsQueue& q = pending_requests_[i];
+ if (!q.empty()) {
+ Request* req = q.front();
+ q.pop_front();
+ return req;
+ }
+ }
+
+ NOTREACHED();
+ return NULL;
+ }
+
+ // Keeps track of a job that was just added/removed, and belongs to this pool.
+ void AdjustNumOutstandingJobs(int offset) {
+ DCHECK(offset == 1 || (offset == -1 && num_outstanding_jobs_ > 0u));
+ num_outstanding_jobs_ += offset;
+ }
+
+ // Returns true if a new job can be created for this pool.
+ bool CanCreateJob() const {
+ return num_outstanding_jobs_ + 1u <= max_outstanding_jobs_;
+ }
+
+ // Removes any pending requests from the queue which are for the
+ // same hostname/address-family as |job|, and attaches them to |job|.
+ void MoveRequestsToJob(Job* job) {
+ for (size_t i = 0u; i < arraysize(pending_requests_); ++i) {
+ PendingRequestsQueue& q = pending_requests_[i];
+ PendingRequestsQueue::iterator req_it = q.begin();
+ while (req_it != q.end()) {
+ Request* req = *req_it;
+ Key req_key(req->info().hostname(), req->info().address_family());
+ if (req_key == job->key()) {
+ // Job takes ownership of |req|.
+ job->AddRequest(req);
+ req_it = q.erase(req_it);
+ } else {
+ ++req_it;
+ }
+ }
+ }
+ }
+
+ private:
+ typedef std::deque<Request*> PendingRequestsQueue;
+
+ // Maximum number of concurrent jobs allowed to be started for requests
+ // belonging to this pool.
+ size_t max_outstanding_jobs_;
+
+ // The current number of running jobs that were started for requests
+ // belonging to this pool.
+ size_t num_outstanding_jobs_;
+
+ // The maximum number of requests we allow to be waiting on a job,
+ // for this pool.
+ size_t max_pending_requests_;
+
+ // The requests which are waiting to be started for this pool.
+ PendingRequestsQueue pending_requests_[NUM_PRIORITIES];
+};
+
+//-----------------------------------------------------------------------------
+
HostResolverImpl::HostResolverImpl(
HostResolverProc* resolver_proc,
HostCache* cache,
- const scoped_refptr<NetworkChangeNotifier>& network_change_notifier)
+ const scoped_refptr<NetworkChangeNotifier>& network_change_notifier,
+ size_t max_jobs)
: cache_(cache),
+ max_jobs_(max_jobs),
next_request_id_(0),
resolver_proc_(resolver_proc),
default_address_family_(ADDRESS_FAMILY_UNSPECIFIED),
shutdown_(false),
network_change_notifier_(network_change_notifier) {
+ DCHECK_GT(max_jobs, 0u);
+
+ // It is cumbersome to expose all of the constraints in the constructor,
+ // so we choose some defaults, which users can override later.
+ job_pools_[POOL_NORMAL] = new JobPool(max_jobs, 100u * max_jobs);
+
#if defined(OS_WIN)
EnsureWinsockInit();
#endif
@@ -318,6 +492,10 @@
if (network_change_notifier_)
network_change_notifier_->RemoveObserver(this);
+
+ // Delete the job pools.
+ for (size_t i = 0u; i < arraysize(job_pools_); ++i)
+ delete job_pools_[i];
}
// TODO(eroman): Don't create cache entries for hostnames which are simply IP
@@ -394,13 +572,12 @@
if (job) {
job->AddRequest(req);
} else {
- // Create a new job for this request.
- job = new Job(this, key);
- job->AddRequest(req);
- AddOutstandingJob(job);
- // TODO(eroman): Bound the total number of concurrent jobs.
- // http://crbug.com/9598
- job->Start();
+ JobPool* pool = GetPoolForRequest(req);
+ if (CanCreateJobForPool(*pool)) {
+ CreateAndStartJob(req);
+ } else {
+ return EnqueueRequest(pool, req);
+ }
}
// Completion happens during OnJobComplete(Job*).
@@ -420,7 +597,19 @@
}
Request* req = reinterpret_cast<Request*>(req_handle);
DCHECK(req);
- DCHECK(req->job());
+
+ scoped_ptr<Request> request_deleter; // Frees at end of function.
+
+ if (!req->job()) {
+ // If the request was not attached to a job yet, it must have been
+ // enqueued into a pool. Remove it from that pool's queue.
+ // Otherwise if it was attached to a job, the job is responsible for
+ // deleting it.
+ JobPool* pool = GetPoolForRequest(req);
+ pool->RemovePendingRequest(req);
+ request_deleter.reset(req);
+ }
+
// NULL out the fields of req, to mark it as cancelled.
req->MarkAsCancelled();
OnCancelRequest(req->load_log(), req->id(), req->info());
@@ -453,10 +642,23 @@
jobs_.clear();
}
+void HostResolverImpl::SetPoolConstraints(JobPoolIndex pool_index,
+ size_t max_outstanding_jobs,
+ size_t max_pending_requests) {
+ CHECK(pool_index >= 0);
+ CHECK(pool_index < POOL_COUNT);
+ CHECK(jobs_.empty()) << "Can only set constraints during setup";
+ JobPool* pool = job_pools_[pool_index];
+ pool->SetConstraints(max_outstanding_jobs, max_pending_requests);
+}
+
void HostResolverImpl::AddOutstandingJob(Job* job) {
scoped_refptr<Job>& found_job = jobs_[job->key()];
DCHECK(!found_job);
found_job = job;
+
+ JobPool* pool = GetPoolForRequest(job->initial_request());
+ pool->AdjustNumOutstandingJobs(1);
}
HostResolverImpl::Job* HostResolverImpl::FindOutstandingJob(const Key& key) {
@@ -471,6 +673,9 @@
DCHECK(it != jobs_.end());
DCHECK_EQ(it->second.get(), job);
jobs_.erase(it);
+
+ JobPool* pool = GetPoolForRequest(job->initial_request());
+ pool->AdjustNumOutstandingJobs(-1);
}
void HostResolverImpl::OnJobComplete(Job* job,
@@ -487,6 +692,9 @@
DCHECK(!cur_completing_job_);
cur_completing_job_ = job;
+ // Try to start any queued requests now that a job-slot has freed up.
+ ProcessQueuedRequests();
+
// Complete all of the requests that were attached to the job.
for (RequestsList::const_iterator it = job->requests().begin();
it != job->requests().end(); ++it) {
@@ -578,4 +786,74 @@
cache_->clear();
}
+// static
+HostResolverImpl::JobPoolIndex HostResolverImpl::GetJobPoolIndexForRequest(
+ const Request* req) {
+ return POOL_NORMAL;
+}
+
+bool HostResolverImpl::CanCreateJobForPool(const JobPool& pool) const {
+ DCHECK_LE(jobs_.size(), max_jobs_);
+
+ // We can't create another job if it would exceed the global total.
+ if (jobs_.size() + 1 > max_jobs_)
+ return false;
+
+ // Check whether the pool's constraints are met.
+ return pool.CanCreateJob();
+}
+
+void HostResolverImpl::ProcessQueuedRequests() {
+ // Find the highest priority request that can be scheduled.
+ Request* top_req = NULL;
+ for (size_t i = 0; i < arraysize(job_pools_); ++i) {
+ JobPool* pool = job_pools_[i];
+ if (pool->HasPendingRequests() && CanCreateJobForPool(*pool)) {
+ top_req = pool->RemoveTopPendingRequest();
+ break;
+ }
+ }
+
+ if (!top_req)
+ return;
+
+ scoped_refptr<Job> job = CreateAndStartJob(top_req);
+
+ // Search for any other pending request which can piggy-back off this job.
+ for (size_t pool_i = 0; pool_i < POOL_COUNT; ++pool_i) {
+ JobPool* pool = job_pools_[pool_i];
+ pool->MoveRequestsToJob(job);
+ }
+}
+
+HostResolverImpl::Job* HostResolverImpl::CreateAndStartJob(Request* req) {
+ DCHECK(CanCreateJobForPool(*GetPoolForRequest(req)));
+ Key key(req->info().hostname(), req->info().address_family());
+ scoped_refptr<Job> job = new Job(this, key);
+ job->AddRequest(req);
+ AddOutstandingJob(job);
+ job->Start();
+ return job.get();
+}
+
+int HostResolverImpl::EnqueueRequest(JobPool* pool, Request* req) {
+ scoped_ptr<Request> req_evicted_from_queue(
+ pool->InsertPendingRequest(req));
+
+ // If the queue has become too large, we need to kick something out.
+ if (req_evicted_from_queue.get()) {
+ Request* r = req_evicted_from_queue.get();
+ int error = ERR_HOST_RESOLVER_QUEUE_TOO_LARGE;
+
+ OnFinishRequest(r->load_log(), r->id(), r->info(), error);
+
+ if (r == req)
+ return error;
+
+ r->OnComplete(error, AddressList());
+ }
+
+ return ERR_IO_PENDING;
+}
+
} // namespace net