summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/base/host_resolver.h10
-rw-r--r--net/base/host_resolver_impl.cc298
-rw-r--r--net/base/host_resolver_impl.h65
-rw-r--r--net/base/host_resolver_impl_unittest.cc277
-rw-r--r--net/base/mock_host_resolver.cc2
-rw-r--r--net/base/net_error_list.h4
-rw-r--r--net/base/request_priority.h3
-rw-r--r--net/http/http_network_transaction.cc3
-rw-r--r--net/tools/hresolv/hresolv.cc2
9 files changed, 634 insertions, 30 deletions
diff --git a/net/base/host_resolver.h b/net/base/host_resolver.h
index 8a88402..0bcbbe8 100644
--- a/net/base/host_resolver.h
+++ b/net/base/host_resolver.h
@@ -11,6 +11,7 @@
#include "googleurl/src/gurl.h"
#include "net/base/address_family.h"
#include "net/base/completion_callback.h"
+#include "net/base/request_priority.h"
class MessageLoop;
@@ -40,7 +41,8 @@ class HostResolver : public base::RefCountedThreadSafe<HostResolver> {
address_family_(ADDRESS_FAMILY_UNSPECIFIED),
port_(port),
allow_cached_response_(true),
- is_speculative_(false) {}
+ is_speculative_(false),
+ priority_(MEDIUM) {}
const int port() const { return port_; }
const std::string& hostname() const { return hostname_; }
@@ -56,6 +58,9 @@ class HostResolver : public base::RefCountedThreadSafe<HostResolver> {
bool is_speculative() const { return is_speculative_; }
void set_is_speculative(bool b) { is_speculative_ = b; }
+ RequestPriority priority() const { return priority_; }
+ void set_priority(RequestPriority priority) { priority_ = priority; }
+
const GURL& referrer() const { return referrer_; }
void set_referrer(const GURL& referrer) { referrer_ = referrer; }
@@ -75,6 +80,9 @@ class HostResolver : public base::RefCountedThreadSafe<HostResolver> {
// Whether this request was started by the DNS prefetcher.
bool is_speculative_;
+ // The priority for the request.
+ RequestPriority priority_;
+
// Optional data for consumption by observers. This is the URL of the
// page that lead us to the navigation, for DNS prefetcher's benefit.
GURL referrer_;
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 @@ HostCache* CreateDefaultCache() {
} // 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 @@ class HostResolverImpl::Job
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 @@ class HostResolverImpl::Job
//-----------------------------------------------------------------------------
+// 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 @@ HostResolverImpl::~HostResolverImpl() {
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 @@ int HostResolverImpl::Resolve(const RequestInfo& info,
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 @@ void HostResolverImpl::CancelRequest(RequestHandle req_handle) {
}
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 @@ void HostResolverImpl::Shutdown() {
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 @@ void HostResolverImpl::RemoveOutstandingJob(Job* job) {
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 @@ void HostResolverImpl::OnJobComplete(Job* job,
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 @@ void HostResolverImpl::OnIPAddressChanged() {
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
diff --git a/net/base/host_resolver_impl.h b/net/base/host_resolver_impl.h
index 6cabcf9..77f9020 100644
--- a/net/base/host_resolver_impl.h
+++ b/net/base/host_resolver_impl.h
@@ -41,9 +41,24 @@ namespace net {
// Thread safety: This class is not threadsafe, and must only be called
// from one thread!
//
+// The HostResolverImpl enforces |max_jobs_| as the maximum number of concurrent
+// threads.
+//
+// Requests are ordered in the queue based on their priority.
+
class HostResolverImpl : public HostResolver,
public NetworkChangeNotifier::Observer {
public:
+ // The index into |job_pools_| for the various job pools. Pools with a higher
+ // index have lower priority.
+ //
+ // Note: This is currently unused, since there is a single pool
+ // for all requests.
+ enum JobPoolIndex {
+ POOL_NORMAL = 0,
+ POOL_COUNT,
+ };
+
// Creates a HostResolver that first uses the local cache |cache|, and then
// falls back to |resolver_proc|.
//
@@ -54,9 +69,13 @@ class HostResolverImpl : public HostResolver,
// thread-safe since it is run from multiple worker threads. If
// |resolver_proc| is NULL then the default host resolver procedure is
// used (which is SystemHostResolverProc except if overridden).
+ //
+ // |max_jobs| specifies the maximum number of threads that the host resolver
+ // will use. Use SetPoolConstraints() to specify finer-grain settings.
HostResolverImpl(HostResolverProc* resolver_proc,
HostCache* cache,
- const scoped_refptr<NetworkChangeNotifier>& notifier);
+ const scoped_refptr<NetworkChangeNotifier>& notifier,
+ size_t max_jobs);
// HostResolver methods:
virtual int Resolve(const RequestInfo& info,
@@ -76,8 +95,24 @@ class HostResolverImpl : public HostResolver,
default_address_family_ = address_family;
}
+ // Applies a set of constraints for requests that belong to the specified
+ // pool. NOTE: Don't call this after requests have been already been started.
+ //
+ // |pool_index| -- Specifies which pool these constraints should be applied
+ // to.
+ // |max_outstanding_jobs| -- How many concurrent jobs are allowed for this
+ // pool.
+ // |max_pending_requests| -- How many requests can be enqueued for this pool
+ // before we start dropping requests. Dropped
+ // requests fail with
+ // ERR_HOST_RESOLVER_QUEUE_TOO_LARGE.
+ void SetPoolConstraints(JobPoolIndex pool_index,
+ size_t max_outstanding_jobs,
+ size_t max_pending_requests);
+
private:
class Job;
+ class JobPool;
class Request;
typedef std::vector<Request*> RequestsList;
typedef HostCache::Key Key;
@@ -126,12 +161,40 @@ class HostResolverImpl : public HostResolver,
// NetworkChangeNotifier::Observer methods:
virtual void OnIPAddressChanged();
+ // Returns true if the constraints for |pool| are met, and a new job can be
+ // created for this pool.
+ bool CanCreateJobForPool(const JobPool& pool) const;
+
+ // Returns the index of the pool that request |req| maps to.
+ static JobPoolIndex GetJobPoolIndexForRequest(const Request* req);
+
+ JobPool* GetPoolForRequest(const Request* req) {
+ return job_pools_[GetJobPoolIndexForRequest(req)];
+ }
+
+ // Starts up to 1 job given the current pool constraints. This job
+ // may have multiple requests attached to it.
+ void ProcessQueuedRequests();
+
+ // Attaches |req| to a new job, and starts it. Returns that job.
+ Job* CreateAndStartJob(Request* req);
+
+ // Adds a pending request |req| to |pool|.
+ int EnqueueRequest(JobPool* pool, Request* req);
+
// Cache of host resolution results.
scoped_ptr<HostCache> cache_;
// Map from hostname to outstanding job.
JobMap jobs_;
+ // Maximum number of concurrent jobs allowed, across all pools.
+ size_t max_jobs_;
+
+ // The information to track pending requests for a JobPool, as well as
+ // how many outstanding jobs the pool already has, and its constraints.
+ JobPool* job_pools_[POOL_COUNT];
+
// The job that OnJobComplete() is currently processing (needed in case
// HostResolver gets deleted from within the callback).
scoped_refptr<Job> cur_completing_job_;
diff --git a/net/base/host_resolver_impl_unittest.cc b/net/base/host_resolver_impl_unittest.cc
index c2cd1b5..f3d350c 100644
--- a/net/base/host_resolver_impl_unittest.cc
+++ b/net/base/host_resolver_impl_unittest.cc
@@ -24,6 +24,7 @@
// cache while an async is already pending).
namespace net {
+
namespace {
HostCache* CreateDefaultCache() {
@@ -33,6 +34,25 @@ HostCache* CreateDefaultCache() {
base::TimeDelta::FromSeconds(0));
}
+static const size_t kMaxJobs = 10u;
+
+HostResolverImpl* CreateHostResolverImpl(HostResolverProc* resolver_proc) {
+ return new HostResolverImpl(
+ resolver_proc,
+ CreateDefaultCache(),
+ NULL, // network_change_notifier
+ kMaxJobs);
+}
+
+// Helper to create a HostResolver::RequestInfo.
+HostResolver::RequestInfo CreateResolverRequest(
+ const std::string& hostname,
+ RequestPriority priority) {
+ HostResolver::RequestInfo info(hostname, 80);
+ info.set_priority(priority);
+ return info;
+}
+
// A variant of WaitingHostResolverProc that pushes each host mapped into a
// list.
// (and uses a manual-reset event rather than auto-reset).
@@ -186,7 +206,7 @@ TEST_F(HostResolverImplTest, SynchronousLookup) {
resolver_proc->AddRule("just.testing", "192.168.1.42");
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(resolver_proc));
HostResolver::RequestInfo info("just.testing", kPortnum);
scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
@@ -216,7 +236,7 @@ TEST_F(HostResolverImplTest, AsynchronousLookup) {
resolver_proc->AddRule("just.testing", "192.168.1.42");
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(resolver_proc));
HostResolver::RequestInfo info("just.testing", kPortnum);
scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
@@ -251,7 +271,7 @@ TEST_F(HostResolverImplTest, CanceledAsynchronousLookup) {
scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded));
{
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(resolver_proc));
AddressList adrlist;
const int kPortnum = 80;
@@ -285,7 +305,7 @@ TEST_F(HostResolverImplTest, NumericIPv4Address) {
resolver_proc->AllowDirectLookup("*");
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(resolver_proc));
AddressList adrlist;
const int kPortnum = 5555;
HostResolver::RequestInfo info("127.1.2.3", kPortnum);
@@ -310,7 +330,7 @@ TEST_F(HostResolverImplTest, NumericIPv6Address) {
// Resolve a plain IPv6 address. Don't worry about [brackets], because
// the caller should have removed them.
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(resolver_proc));
AddressList adrlist;
const int kPortnum = 5555;
HostResolver::RequestInfo info("2001:db8::1", kPortnum);
@@ -345,7 +365,7 @@ TEST_F(HostResolverImplTest, EmptyHost) {
resolver_proc->AllowDirectLookup("*");
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(resolver_proc));
AddressList adrlist;
const int kPortnum = 5555;
HostResolver::RequestInfo info("", kPortnum);
@@ -406,7 +426,7 @@ TEST_F(HostResolverImplTest, DeDupeRequests) {
new CapturingHostResolverProc(NULL);
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(resolver_proc));
// The class will receive callbacks for when each resolve completes. It
// checks that the right things happened.
@@ -457,7 +477,7 @@ TEST_F(HostResolverImplTest, CancelMultipleRequests) {
new CapturingHostResolverProc(NULL);
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(resolver_proc));
// The class will receive callbacks for when each resolve completes. It
// checks that the right things happened.
@@ -544,7 +564,7 @@ TEST_F(HostResolverImplTest, CancelWithinCallback) {
new CapturingHostResolverProc(NULL);
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(resolver_proc));
// The class will receive callbacks for when each resolve completes. It
// checks that the right things happened.
@@ -605,7 +625,7 @@ TEST_F(HostResolverImplTest, DeleteWithinCallback) {
// checks that the right things happened. Note that the verifier holds the
// only reference to |host_resolver|, so it can delete it within callback.
HostResolver* host_resolver =
- new HostResolverImpl(resolver_proc, CreateDefaultCache(), NULL);
+ CreateHostResolverImpl(resolver_proc);
DeleteWithinCallbackVerifier verifier(host_resolver);
// Start 4 requests, duplicating hosts "a". Since the resolver_proc is
@@ -658,7 +678,7 @@ TEST_F(HostResolverImplTest, StartWithinCallback) {
// Turn off caching for this host resolver.
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(resolver_proc, NULL, NULL));
+ new HostResolverImpl(resolver_proc, NULL, NULL, kMaxJobs));
// The class will receive callbacks for when each resolve completes. It
// checks that the right things happened.
@@ -723,7 +743,7 @@ class BypassCacheVerifier : public ResolveRequest::Delegate {
TEST_F(HostResolverImplTest, BypassCache) {
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(NULL, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(NULL));
// The class will receive callbacks for when each resolve completes. It
// checks that the right things happened.
@@ -741,6 +761,7 @@ bool operator==(const HostResolver::RequestInfo& a,
return a.hostname() == b.hostname() &&
a.port() == b.port() &&
a.allow_cached_response() == b.allow_cached_response() &&
+ a.priority() == b.priority() &&
a.is_speculative() == b.is_speculative() &&
a.referrer() == b.referrer();
}
@@ -807,7 +828,7 @@ class CapturingObserver : public HostResolver::Observer {
// synchronous.
TEST_F(HostResolverImplTest, Observers) {
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(NULL, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(NULL));
CapturingObserver observer;
@@ -892,7 +913,7 @@ TEST_F(HostResolverImplTest, CancellationObserver) {
{
// Create a host resolver and attach an observer.
scoped_refptr<HostResolver> host_resolver(
- new HostResolverImpl(NULL, CreateDefaultCache(), NULL));
+ CreateHostResolverImpl(NULL));
host_resolver->AddObserver(&observer);
TestCompletionCallback callback;
@@ -961,7 +982,8 @@ TEST_F(HostResolverImplTest, FlushCacheOnIPAddressChange) {
new MockNetworkChangeNotifier);
scoped_refptr<HostResolver> host_resolver(
new HostResolverImpl(NULL, CreateDefaultCache(),
- mock_network_change_notifier));
+ mock_network_change_notifier,
+ kMaxJobs));
AddressList addrlist;
@@ -987,5 +1009,230 @@ TEST_F(HostResolverImplTest, FlushCacheOnIPAddressChange) {
EXPECT_EQ(OK, callback.WaitForResult());
}
+// Tests that when the maximum threads is set to 1, requests are dequeued
+// in order of priority.
+TEST_F(HostResolverImplTest, HigherPriorityRequestsStartedFirst) {
+ scoped_refptr<CapturingHostResolverProc> resolver_proc =
+ new CapturingHostResolverProc(NULL);
+
+ // This HostResolverImpl will only allow 1 outstanding resolve at a time.
+ size_t kMaxJobs = 1u;
+ scoped_refptr<HostResolver> host_resolver(
+ new HostResolverImpl(resolver_proc, CreateDefaultCache(),
+ NULL, kMaxJobs));
+
+ CapturingObserver observer;
+ host_resolver->AddObserver(&observer);
+
+ // Note that at this point the CapturingHostResolverProc is blocked, so any
+ // requests we make will not complete.
+
+ HostResolver::RequestInfo req[] = {
+ CreateResolverRequest("req0", LOW),
+ CreateResolverRequest("req1", MEDIUM),
+ CreateResolverRequest("req2", MEDIUM),
+ CreateResolverRequest("req3", LOW),
+ CreateResolverRequest("req4", HIGHEST),
+ CreateResolverRequest("req5", LOW),
+ CreateResolverRequest("req6", LOW),
+ CreateResolverRequest("req5", HIGHEST),
+ };
+
+ TestCompletionCallback callback[arraysize(req)];
+ AddressList addrlist[arraysize(req)];
+
+ // Start all of the requests.
+ for (size_t i = 0; i < arraysize(req); ++i) {
+ int rv = host_resolver->Resolve(req[i], &addrlist[i],
+ &callback[i], NULL, NULL);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ }
+
+ // Unblock the resolver thread so the requests can run.
+ resolver_proc->Signal();
+
+ // Wait for all the requests to complete succesfully.
+ for (size_t i = 0; i < arraysize(req); ++i) {
+ EXPECT_EQ(OK, callback[i].WaitForResult()) << "i=" << i;
+ }
+
+ host_resolver->RemoveObserver(&observer);
+
+ // Since we have restricted to a single concurrent thread in the jobpool,
+ // the requests should complete in order of priority (with the exception
+ // of the first request, which gets started right away, since there is
+ // nothing outstanding).
+ std::vector<std::string> capture_list = resolver_proc->GetCaptureList();
+ ASSERT_EQ(7u, capture_list.size());
+
+ EXPECT_EQ("req0", capture_list[0]);
+ EXPECT_EQ("req4", capture_list[1]);
+ EXPECT_EQ("req5", capture_list[2]);
+ EXPECT_EQ("req1", capture_list[3]);
+ EXPECT_EQ("req2", capture_list[4]);
+ EXPECT_EQ("req3", capture_list[5]);
+ EXPECT_EQ("req6", capture_list[6]);
+
+ // Also check using the observer's trace.
+ EXPECT_EQ(8U, observer.start_log.size());
+ EXPECT_EQ(8U, observer.finish_log.size());
+ EXPECT_EQ(0U, observer.cancel_log.size());
+
+ EXPECT_EQ("req0", observer.finish_log[0].info.hostname());
+ EXPECT_EQ("req4", observer.finish_log[1].info.hostname());
+
+ // There were two requests for "req5". The highest priority
+ // one should have been dispatched earlier.
+ EXPECT_EQ("req5", observer.finish_log[2].info.hostname());
+ EXPECT_EQ("req5", observer.finish_log[3].info.hostname());
+ EXPECT_EQ(HIGHEST, observer.finish_log[2].info.priority());
+ EXPECT_EQ(LOW, observer.finish_log[3].info.priority());
+
+ EXPECT_EQ("req1", observer.finish_log[4].info.hostname());
+ EXPECT_EQ("req2", observer.finish_log[5].info.hostname());
+ EXPECT_EQ("req3", observer.finish_log[6].info.hostname());
+ EXPECT_EQ("req6", observer.finish_log[7].info.hostname());
+}
+
+// Try cancelling a request which has not been attached to a job yet.
+TEST_F(HostResolverImplTest, CancelPendingRequest) {
+ scoped_refptr<CapturingHostResolverProc> resolver_proc =
+ new CapturingHostResolverProc(NULL);
+
+ // This HostResolverImpl will only allow 1 outstanding resolve at a time.
+ const size_t kMaxJobs = 1u;
+ scoped_refptr<HostResolver> host_resolver(
+ new HostResolverImpl(resolver_proc, CreateDefaultCache(),
+ NULL, kMaxJobs));
+
+ // Note that at this point the CapturingHostResolverProc is blocked, so any
+ // requests we make will not complete.
+
+ HostResolver::RequestInfo req[] = {
+ CreateResolverRequest("req0", LOWEST),
+ CreateResolverRequest("req1", HIGHEST), // Will cancel.
+ CreateResolverRequest("req2", MEDIUM),
+ CreateResolverRequest("req3", LOW),
+ CreateResolverRequest("req4", HIGHEST), // Will cancel.
+ CreateResolverRequest("req5", LOWEST), // Will cancel.
+ CreateResolverRequest("req6", MEDIUM),
+ };
+
+ TestCompletionCallback callback[arraysize(req)];
+ AddressList addrlist[arraysize(req)];
+ HostResolver::RequestHandle handle[arraysize(req)];
+
+ // Start all of the requests.
+ for (size_t i = 0; i < arraysize(req); ++i) {
+ int rv = host_resolver->Resolve(req[i], &addrlist[i],
+ &callback[i], &handle[i], NULL);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ }
+
+ // Cancel some requests
+ host_resolver->CancelRequest(handle[1]);
+ host_resolver->CancelRequest(handle[4]);
+ host_resolver->CancelRequest(handle[5]);
+ handle[1] = handle[4] = handle[5] = NULL;
+
+ // Unblock the resolver thread so the requests can run.
+ resolver_proc->Signal();
+
+ // Wait for all the requests to complete succesfully.
+ for (size_t i = 0; i < arraysize(req); ++i) {
+ if (!handle[i])
+ continue; // Don't wait for the requests we cancelled.
+ EXPECT_EQ(OK, callback[i].WaitForResult());
+ }
+
+ // Verify that they called out the the resolver proc (which runs on the
+ // resolver thread) in the expected order.
+ std::vector<std::string> capture_list = resolver_proc->GetCaptureList();
+ ASSERT_EQ(4u, capture_list.size());
+
+ EXPECT_EQ("req0", capture_list[0]);
+ EXPECT_EQ("req2", capture_list[1]);
+ EXPECT_EQ("req6", capture_list[2]);
+ EXPECT_EQ("req3", capture_list[3]);
+}
+
+// Test that when too many requests are enqueued, old ones start to be aborted.
+TEST_F(HostResolverImplTest, QueueOverflow) {
+ scoped_refptr<CapturingHostResolverProc> resolver_proc =
+ new CapturingHostResolverProc(NULL);
+
+ // This HostResolverImpl will only allow 1 outstanding resolve at a time.
+ const size_t kMaxOutstandingJobs = 1u;
+ scoped_refptr<HostResolverImpl> host_resolver(
+ new HostResolverImpl(resolver_proc, CreateDefaultCache(),
+ NULL, kMaxOutstandingJobs));
+
+ // Only allow up to 3 requests to be enqueued at a time.
+ const size_t kMaxPendingRequests = 3u;
+ host_resolver->SetPoolConstraints(HostResolverImpl::POOL_NORMAL,
+ kMaxOutstandingJobs,
+ kMaxPendingRequests);
+
+ // Note that at this point the CapturingHostResolverProc is blocked, so any
+ // requests we make will not complete.
+
+ HostResolver::RequestInfo req[] = {
+ CreateResolverRequest("req0", LOWEST),
+ CreateResolverRequest("req1", HIGHEST),
+ CreateResolverRequest("req2", MEDIUM),
+ CreateResolverRequest("req3", MEDIUM),
+
+ // At this point, there are 3 enqueued requests.
+ // Insertion of subsequent requests will cause evictions
+ // based on priority.
+
+ CreateResolverRequest("req4", LOW), // Evicts itself!
+ CreateResolverRequest("req5", MEDIUM), // Evicts req3
+ CreateResolverRequest("req6", HIGHEST), // Evicts req5.
+ CreateResolverRequest("req7", MEDIUM), // Evicts req2.
+ };
+
+ TestCompletionCallback callback[arraysize(req)];
+ AddressList addrlist[arraysize(req)];
+ HostResolver::RequestHandle handle[arraysize(req)];
+
+ // Start all of the requests.
+ for (size_t i = 0; i < arraysize(req); ++i) {
+ int rv = host_resolver->Resolve(req[i], &addrlist[i],
+ &callback[i], &handle[i], NULL);
+ if (i == 4u)
+ EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE, rv);
+ else
+ EXPECT_EQ(ERR_IO_PENDING, rv) << i;
+ }
+
+ // Unblock the resolver thread so the requests can run.
+ resolver_proc->Signal();
+
+ // Requests 3, 5, 2 will have been evicted due to queue overflow.
+ size_t reqs_expected_to_fail[] = { 2, 3, 5 };
+ for (size_t i = 0; i < arraysize(reqs_expected_to_fail); ++i) {
+ EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE,
+ callback[reqs_expected_to_fail[i]].WaitForResult());
+ }
+
+ // The rest should succeed.
+ size_t reqs_expected_to_succeed[] = { 0, 1, 6, 7 };
+ for (size_t i = 0; i < arraysize(reqs_expected_to_succeed); ++i) {
+ EXPECT_EQ(OK, callback[reqs_expected_to_succeed[i]].WaitForResult());
+ }
+
+ // Verify that they called out the the resolver proc (which runs on the
+ // resolver thread) in the expected order.
+ std::vector<std::string> capture_list = resolver_proc->GetCaptureList();
+ ASSERT_EQ(4u, capture_list.size());
+
+ EXPECT_EQ("req0", capture_list[0]);
+ EXPECT_EQ("req1", capture_list[1]);
+ EXPECT_EQ("req6", capture_list[2]);
+ EXPECT_EQ("req7", capture_list[3]);
+}
+
} // namespace
+
} // namespace net
diff --git a/net/base/mock_host_resolver.cc b/net/base/mock_host_resolver.cc
index 0bac1ff..6f555d3 100644
--- a/net/base/mock_host_resolver.cc
+++ b/net/base/mock_host_resolver.cc
@@ -102,7 +102,7 @@ void MockHostResolverBase::Reset(HostResolverProc* interceptor) {
base::TimeDelta::FromSeconds(0));
}
- impl_ = new HostResolverImpl(proc, cache, NULL);
+ impl_ = new HostResolverImpl(proc, cache, NULL, 50u);
}
//-----------------------------------------------------------------------------
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 81ca271..bff55d5 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -121,6 +121,10 @@ NET_ERROR(BAD_SSL_CLIENT_AUTH_CERT, -117)
// A connection attempt timed out.
NET_ERROR(CONNECTION_TIMED_OUT, -118)
+// There are too many pending DNS resolves, so a request in the queue was
+// aborted.
+NET_ERROR(HOST_RESOLVER_QUEUE_TOO_LARGE, -119)
+
// Certificate error codes
//
// The values of certificate error codes must be consecutive.
diff --git a/net/base/request_priority.h b/net/base/request_priority.h
index b618ef2..1347001 100644
--- a/net/base/request_priority.h
+++ b/net/base/request_priority.h
@@ -13,7 +13,8 @@ enum RequestPriority {
HIGHEST = 0, // 0 must be the highest priority.
MEDIUM,
LOW,
- LOWEST
+ LOWEST,
+ NUM_PRIORITIES,
};
} // namespace net
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 8bbc883..4be555f 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -677,6 +677,7 @@ int HttpNetworkTransaction::DoInitConnection() {
DCHECK(!connection_group.empty());
HostResolver::RequestInfo resolve_info(host, port);
+ resolve_info.set_priority(request_->priority);
// The referrer is used by the DNS prefetch system to correlate resolutions
// with the page that triggered them. It doesn't impact the actual addresses
@@ -751,6 +752,7 @@ int HttpNetworkTransaction::DoSOCKSConnect() {
HostResolver::RequestInfo req_info(request_->url.HostNoBrackets(),
request_->url.EffectiveIntPort());
req_info.set_referrer(request_->referrer);
+ req_info.set_priority(request_->priority);
if (proxy_info_.proxy_server().scheme() == ProxyServer::SCHEME_SOCKS5)
s = new SOCKS5ClientSocket(s, req_info);
@@ -1134,6 +1136,7 @@ int HttpNetworkTransaction::DoSpdySendRequest() {
HostResolver::RequestInfo req_info(request_->url.HostNoBrackets(),
request_->url.EffectiveIntPort());
+ req_info.set_priority(request_->priority);
const scoped_refptr<FlipSessionPool> spdy_pool =
session_->flip_session_pool();
scoped_refptr<FlipSession> spdy_session;
diff --git a/net/tools/hresolv/hresolv.cc b/net/tools/hresolv/hresolv.cc
index f865a53..f092703 100644
--- a/net/tools/hresolv/hresolv.cc
+++ b/net/tools/hresolv/hresolv.cc
@@ -446,7 +446,7 @@ int main(int argc, char** argv) {
base::TimeDelta::FromSeconds(0));
scoped_refptr<net::HostResolver> host_resolver(
- new net::HostResolverImpl(NULL, cache, NULL));
+ new net::HostResolverImpl(NULL, cache, NULL, 100u));
ResolverInvoker invoker(host_resolver.get());
invoker.ResolveAll(hosts_and_times, options.async);