summaryrefslogtreecommitdiffstats
path: root/net/base/host_resolver_impl.cc
diff options
context:
space:
mode:
Diffstat (limited to 'net/base/host_resolver_impl.cc')
-rw-r--r--net/base/host_resolver_impl.cc483
1 files changed, 483 insertions, 0 deletions
diff --git a/net/base/host_resolver_impl.cc b/net/base/host_resolver_impl.cc
new file mode 100644
index 0000000..2a0eb0f
--- /dev/null
+++ b/net/base/host_resolver_impl.cc
@@ -0,0 +1,483 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/base/host_resolver_impl.h"
+
+#if defined(OS_WIN)
+#include <ws2tcpip.h>
+#include <wspiapi.h> // Needed for Win2k compat.
+#elif defined(OS_POSIX)
+#include <netdb.h>
+#include <sys/socket.h>
+#endif
+#if defined(OS_LINUX)
+#include <resolv.h>
+#endif
+
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "base/worker_pool.h"
+#include "net/base/address_list.h"
+#include "net/base/host_resolver_proc.h"
+#include "net/base/net_errors.h"
+
+#if defined(OS_WIN)
+#include "net/base/winsock_init.h"
+#endif
+
+namespace net {
+
+HostResolver* CreateSystemHostResolver() {
+ static const size_t kMaxHostCacheEntries = 100;
+ static const size_t kHostCacheExpirationMs = 60000; // 1 minute.
+ return new HostResolverImpl(
+ NULL, kMaxHostCacheEntries, kHostCacheExpirationMs);
+}
+
+static int ResolveAddrInfo(HostResolverProc* resolver_proc,
+ const std::string& host, AddressList* out) {
+ if (resolver_proc) {
+ // Use the custom procedure.
+ return resolver_proc->Resolve(host, out);
+ } else {
+ // Use the system procedure (getaddrinfo).
+ return SystemHostResolverProc(host, out);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+class HostResolverImpl::Request {
+ public:
+ Request(int id, const RequestInfo& info, CompletionCallback* callback,
+ AddressList* addresses)
+ : id_(id), info_(info), job_(NULL), callback_(callback),
+ addresses_(addresses) {}
+
+ // Mark the request as cancelled.
+ void MarkAsCancelled() {
+ job_ = NULL;
+ callback_ = NULL;
+ addresses_ = NULL;
+ }
+
+ bool was_cancelled() const {
+ return callback_ == NULL;
+ }
+
+ void set_job(Job* job) {
+ DCHECK(job != NULL);
+ // Identify which job the request is waiting on.
+ job_ = job;
+ }
+
+ void OnComplete(int error, const AddressList& addrlist) {
+ if (error == OK)
+ addresses_->SetFrom(addrlist, port());
+ callback_->Run(error);
+ }
+
+ int port() const {
+ return info_.port();
+ }
+
+ Job* job() const {
+ return job_;
+ }
+
+ int id() const {
+ return id_;
+ }
+
+ const RequestInfo& info() const {
+ return info_;
+ }
+
+ private:
+ // Unique ID for this request. Used by observers to identify requests.
+ int id_;
+
+ // The request info that started the request.
+ RequestInfo info_;
+
+ // The resolve job (running in worker pool) that this request is dependent on.
+ Job* job_;
+
+ // The user's callback to invoke when the request completes.
+ CompletionCallback* callback_;
+
+ // The address list to save result into.
+ AddressList* addresses_;
+
+ DISALLOW_COPY_AND_ASSIGN(Request);
+};
+
+//-----------------------------------------------------------------------------
+
+// This class represents a request to the worker pool for a "getaddrinfo()"
+// call.
+class HostResolverImpl::Job
+ : public base::RefCountedThreadSafe<HostResolverImpl::Job> {
+ public:
+ Job(HostResolverImpl* resolver, const std::string& host)
+ : host_(host),
+ resolver_(resolver),
+ origin_loop_(MessageLoop::current()),
+ resolver_proc_(resolver->effective_resolver_proc()),
+ error_(OK) {
+ }
+
+ ~Job() {
+ // Free the requests attached to this job.
+ STLDeleteElements(&requests_);
+ }
+
+ // Attaches a request to this job. The job takes ownership of |req| and will
+ // take care to delete it.
+ void AddRequest(Request* req) {
+ req->set_job(this);
+ requests_.push_back(req);
+ }
+
+ // Called from origin loop.
+ void Start() {
+ // Dispatch the job to a worker thread.
+ if (!WorkerPool::PostTask(FROM_HERE,
+ NewRunnableMethod(this, &Job::DoLookup), true)) {
+ NOTREACHED();
+
+ // Since we could be running within Resolve() right now, we can't just
+ // call OnLookupComplete(). Instead we must wait until Resolve() has
+ // returned (IO_PENDING).
+ error_ = ERR_UNEXPECTED;
+ MessageLoop::current()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &Job::OnLookupComplete));
+ }
+ }
+
+ // Cancels the current job. Callable from origin thread.
+ void Cancel() {
+ HostResolver* resolver = resolver_;
+ resolver_ = NULL;
+
+ // Mark the job as cancelled, so when worker thread completes it will
+ // not try to post completion to origin loop.
+ {
+ AutoLock locked(origin_loop_lock_);
+ origin_loop_ = NULL;
+ }
+
+ // We don't have to do anything further to actually cancel the requests
+ // that were attached to this job (since they are unreachable now).
+ // But we will call HostResolverImpl::CancelRequest(Request*) on each one
+ // in order to notify any observers.
+ for (RequestsList::const_iterator it = requests_.begin();
+ it != requests_.end(); ++it) {
+ HostResolverImpl::Request* req = *it;
+ if (!req->was_cancelled())
+ resolver->CancelRequest(req);
+ }
+ }
+
+ // Called from origin thread.
+ bool was_cancelled() const {
+ return resolver_ == NULL;
+ }
+
+ // Called from origin thread.
+ const std::string& host() const {
+ return host_;
+ }
+
+ // Called from origin thread.
+ const RequestsList& requests() const {
+ return requests_;
+ }
+
+ private:
+ void DoLookup() {
+ // Running on the worker thread
+ error_ = ResolveAddrInfo(resolver_proc_, host_, &results_);
+
+ Task* reply = NewRunnableMethod(this, &Job::OnLookupComplete);
+
+ // The origin loop could go away while we are trying to post to it, so we
+ // need to call its PostTask method inside a lock. See ~HostResolver.
+ {
+ AutoLock locked(origin_loop_lock_);
+ if (origin_loop_) {
+ origin_loop_->PostTask(FROM_HERE, reply);
+ reply = NULL;
+ }
+ }
+
+ // Does nothing if it got posted.
+ delete reply;
+ }
+
+ // Callback for when DoLookup() completes (runs on origin thread).
+ void OnLookupComplete() {
+ // Should be running on origin loop.
+ // TODO(eroman): this is being hit by URLRequestTest.CancelTest*,
+ // because MessageLoop::current() == NULL.
+ //DCHECK_EQ(origin_loop_, MessageLoop::current());
+ DCHECK(error_ || results_.head());
+
+ if (was_cancelled())
+ return;
+
+ DCHECK(!requests_.empty());
+
+ // Use the port number of the first request.
+ if (error_ == OK)
+ results_.SetPort(requests_[0]->port());
+
+ resolver_->OnJobComplete(this, error_, results_);
+ }
+
+ // Set on the origin thread, read on the worker thread.
+ std::string host_;
+
+ // Only used on the origin thread (where Resolve was called).
+ HostResolverImpl* resolver_;
+ RequestsList requests_; // The requests waiting on this job.
+
+ // Used to post ourselves onto the origin thread.
+ Lock origin_loop_lock_;
+ MessageLoop* origin_loop_;
+
+ // Hold an owning reference to the HostResolverProc that we are going to use.
+ // This may not be the current resolver procedure by the time we call
+ // ResolveAddrInfo, but that's OK... we'll use it anyways, and the owning
+ // reference ensures that it remains valid until we are done.
+ scoped_refptr<HostResolverProc> resolver_proc_;
+
+ // Assigned on the worker thread, read on the origin thread.
+ int error_;
+ AddressList results_;
+
+ DISALLOW_COPY_AND_ASSIGN(Job);
+};
+
+//-----------------------------------------------------------------------------
+
+HostResolverImpl::HostResolverImpl(HostResolverProc* resolver_proc,
+ int max_cache_entries,
+ int cache_duration_ms)
+ : cache_(max_cache_entries, cache_duration_ms), next_request_id_(0),
+ resolver_proc_(resolver_proc), shutdown_(false) {
+#if defined(OS_WIN)
+ EnsureWinsockInit();
+#endif
+}
+
+HostResolverImpl::~HostResolverImpl() {
+ // Cancel the outstanding jobs. Those jobs may contain several attached
+ // requests, which will also be cancelled.
+ for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
+ it->second->Cancel();
+
+ // In case we are being deleted during the processing of a callback.
+ if (cur_completing_job_)
+ cur_completing_job_->Cancel();
+}
+
+// TODO(eroman): Don't create cache entries for hostnames which are simply IP
+// address literals.
+int HostResolverImpl::Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ CompletionCallback* callback,
+ RequestHandle* out_req) {
+ if (shutdown_)
+ return ERR_UNEXPECTED;
+
+ // Choose a unique ID number for observers to see.
+ int request_id = next_request_id_++;
+
+ // Notify registered observers.
+ NotifyObserversStartRequest(request_id, info);
+
+ // If we have an unexpired cache entry, use it.
+ if (info.allow_cached_response()) {
+ const HostCache::Entry* cache_entry = cache_.Lookup(
+ info.hostname(), base::TimeTicks::Now());
+ if (cache_entry) {
+ addresses->SetFrom(cache_entry->addrlist, info.port());
+ int error = cache_entry->error;
+
+ // Notify registered observers.
+ NotifyObserversFinishRequest(request_id, info, error);
+
+ return error;
+ }
+ }
+
+ // If no callback was specified, do a synchronous resolution.
+ if (!callback) {
+ AddressList addrlist;
+ int error = ResolveAddrInfo(
+ effective_resolver_proc(), info.hostname(), &addrlist);
+ if (error == OK) {
+ addrlist.SetPort(info.port());
+ *addresses = addrlist;
+ }
+
+ // Write to cache.
+ cache_.Set(info.hostname(), error, addrlist, base::TimeTicks::Now());
+
+ // Notify registered observers.
+ NotifyObserversFinishRequest(request_id, info, error);
+
+ return error;
+ }
+
+ // Create a handle for this request, and pass it back to the user if they
+ // asked for it (out_req != NULL).
+ Request* req = new Request(request_id, info, callback, addresses);
+ if (out_req)
+ *out_req = reinterpret_cast<RequestHandle>(req);
+
+ // Next we need to attach our request to a "job". This job is responsible for
+ // calling "getaddrinfo(hostname)" on a worker thread.
+ scoped_refptr<Job> job;
+
+ // If there is already an outstanding job to resolve |info.hostname()|, use
+ // it. This prevents starting concurrent resolves for the same hostname.
+ job = FindOutstandingJob(info.hostname());
+ if (job) {
+ job->AddRequest(req);
+ } else {
+ // Create a new job for this request.
+ job = new Job(this, info.hostname());
+ job->AddRequest(req);
+ AddOutstandingJob(job);
+ // TODO(eroman): Bound the total number of concurrent jobs.
+ // http://crbug.com/9598
+ job->Start();
+ }
+
+ // Completion happens during OnJobComplete(Job*).
+ return ERR_IO_PENDING;
+}
+
+// See OnJobComplete(Job*) for why it is important not to clean out
+// cancelled requests from Job::requests_.
+void HostResolverImpl::CancelRequest(RequestHandle req_handle) {
+ Request* req = reinterpret_cast<Request*>(req_handle);
+ DCHECK(req);
+ DCHECK(req->job());
+ // NULL out the fields of req, to mark it as cancelled.
+ req->MarkAsCancelled();
+ NotifyObserversCancelRequest(req->id(), req->info());
+}
+
+void HostResolverImpl::AddObserver(Observer* observer) {
+ observers_.push_back(observer);
+}
+
+void HostResolverImpl::RemoveObserver(Observer* observer) {
+ ObserversList::iterator it =
+ std::find(observers_.begin(), observers_.end(), observer);
+
+ // Observer must exist.
+ DCHECK(it != observers_.end());
+
+ observers_.erase(it);
+}
+
+void HostResolverImpl::Shutdown() {
+ shutdown_ = true;
+
+ // Cancel the outstanding jobs.
+ for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
+ it->second->Cancel();
+ jobs_.clear();
+}
+
+void HostResolverImpl::AddOutstandingJob(Job* job) {
+ scoped_refptr<Job>& found_job = jobs_[job->host()];
+ DCHECK(!found_job);
+ found_job = job;
+}
+
+HostResolverImpl::Job* HostResolverImpl::FindOutstandingJob(
+ const std::string& hostname) {
+ JobMap::iterator it = jobs_.find(hostname);
+ if (it != jobs_.end())
+ return it->second;
+ return NULL;
+}
+
+void HostResolverImpl::RemoveOutstandingJob(Job* job) {
+ JobMap::iterator it = jobs_.find(job->host());
+ DCHECK(it != jobs_.end());
+ DCHECK_EQ(it->second.get(), job);
+ jobs_.erase(it);
+}
+
+void HostResolverImpl::OnJobComplete(Job* job,
+ int error,
+ const AddressList& addrlist) {
+ RemoveOutstandingJob(job);
+
+ // Write result to the cache.
+ cache_.Set(job->host(), error, addrlist, base::TimeTicks::Now());
+
+ // Make a note that we are executing within OnJobComplete() in case the
+ // HostResolver is deleted by a callback invocation.
+ DCHECK(!cur_completing_job_);
+ cur_completing_job_ = job;
+
+ // Complete all of the requests that were attached to the job.
+ for (RequestsList::const_iterator it = job->requests().begin();
+ it != job->requests().end(); ++it) {
+ Request* req = *it;
+ if (!req->was_cancelled()) {
+ DCHECK_EQ(job, req->job());
+
+ // Notify registered observers.
+ NotifyObserversFinishRequest(req->id(), req->info(), error);
+
+ req->OnComplete(error, addrlist);
+
+ // Check if the job was cancelled as a result of running the callback.
+ // (Meaning that |this| was deleted).
+ if (job->was_cancelled())
+ return;
+ }
+ }
+
+ cur_completing_job_ = NULL;
+}
+
+void HostResolverImpl::NotifyObserversStartRequest(int request_id,
+ const RequestInfo& info) {
+ for (ObserversList::iterator it = observers_.begin();
+ it != observers_.end(); ++it) {
+ (*it)->OnStartResolution(request_id, info);
+ }
+}
+
+void HostResolverImpl::NotifyObserversFinishRequest(int request_id,
+ const RequestInfo& info,
+ int error) {
+ bool was_resolved = error == OK;
+ for (ObserversList::iterator it = observers_.begin();
+ it != observers_.end(); ++it) {
+ (*it)->OnFinishResolutionWithStatus(request_id, was_resolved, info);
+ }
+}
+
+void HostResolverImpl::NotifyObserversCancelRequest(int request_id,
+ const RequestInfo& info) {
+ for (ObserversList::iterator it = observers_.begin();
+ it != observers_.end(); ++it) {
+ (*it)->OnCancelResolution(request_id, info);
+ }
+}
+
+} // namespace net