| Index: net/proxy/proxy_service.cc
|
| ===================================================================
|
| --- net/proxy/proxy_service.cc (revision 21630)
|
| +++ net/proxy/proxy_service.cc (working copy)
|
| @@ -8,6 +8,7 @@
|
|
|
| #include "base/compiler_specific.h"
|
| #include "base/logging.h"
|
| +#include "base/message_loop.h"
|
| #include "base/string_util.h"
|
| #include "googleurl/src/gurl.h"
|
| #include "net/base/net_errors.h"
|
| @@ -23,6 +24,7 @@
|
| #endif
|
| #include "net/proxy/proxy_resolver.h"
|
| #include "net/proxy/proxy_resolver_v8.h"
|
| +#include "net/proxy/single_threaded_proxy_resolver.h"
|
| #include "net/url_request/url_request_context.h"
|
|
|
| using base::TimeDelta;
|
| @@ -42,14 +44,22 @@
|
| // Proxy resolver that fails every time.
|
| class ProxyResolverNull : public ProxyResolver {
|
| public:
|
| - ProxyResolverNull() : ProxyResolver(true /*does_fetch*/) {}
|
| + ProxyResolverNull() : ProxyResolver(false /*expects_pac_bytes*/) {}
|
|
|
| // ProxyResolver implementation:
|
| - virtual int GetProxyForURL(const GURL& /*query_url*/,
|
| - const GURL& /*pac_url*/,
|
| - ProxyInfo* /*results*/) {
|
| + virtual int GetProxyForURL(const GURL& url,
|
| + ProxyInfo* results,
|
| + CompletionCallback* callback,
|
| + RequestHandle* request) {
|
| return ERR_NOT_IMPLEMENTED;
|
| }
|
| +
|
| + virtual void CancelRequest(RequestHandle request) {
|
| + NOTREACHED();
|
| + }
|
| +
|
| + private:
|
| + virtual void SetPacScriptByUrlInternal(const GURL& pac_url) {}
|
| };
|
|
|
| // Strip away any reference fragments and the username/password, as they
|
| @@ -64,132 +74,109 @@
|
| return url.ReplaceComponents(replacements);
|
| }
|
|
|
| -// Runs on the PAC thread to notify the proxy resolver of the fetched PAC
|
| -// script contents. This task shouldn't outlive ProxyService, since
|
| -// |resolver| is owned by ProxyService.
|
| -class NotifyFetchCompletionTask : public Task {
|
| - public:
|
| - NotifyFetchCompletionTask(ProxyResolver* resolver, const std::string& bytes)
|
| - : resolver_(resolver), bytes_(bytes) {}
|
| -
|
| - virtual void Run() {
|
| - resolver_->SetPacScript(bytes_);
|
| - }
|
| -
|
| - private:
|
| - ProxyResolver* resolver_;
|
| - std::string bytes_;
|
| -};
|
| -
|
| // ProxyService::PacRequest ---------------------------------------------------
|
|
|
| -// We rely on the fact that the origin thread (and its message loop) will not
|
| -// be destroyed until after the PAC thread is destroyed.
|
| -
|
| class ProxyService::PacRequest
|
| - : public base::RefCountedThreadSafe<ProxyService::PacRequest> {
|
| + : public base::RefCounted<ProxyService::PacRequest> {
|
| public:
|
| - // |service| -- the ProxyService that owns this request.
|
| - // |url| -- the url of the query.
|
| - // |results| -- the structure to fill with proxy resolve results.
|
| PacRequest(ProxyService* service,
|
| const GURL& url,
|
| ProxyInfo* results,
|
| - CompletionCallback* callback)
|
| + CompletionCallback* user_callback)
|
| : service_(service),
|
| - callback_(callback),
|
| + user_callback_(user_callback),
|
| + ALLOW_THIS_IN_INITIALIZER_LIST(io_callback_(
|
| + this, &PacRequest::QueryComplete)),
|
| results_(results),
|
| url_(url),
|
| - is_started_(false),
|
| - origin_loop_(MessageLoop::current()) {
|
| - DCHECK(callback);
|
| + resolve_job_(NULL),
|
| + config_id_(ProxyConfig::INVALID_ID) {
|
| + DCHECK(user_callback);
|
| }
|
|
|
| - // Start the resolve proxy request on the PAC thread.
|
| - void Query() {
|
| - is_started_ = true;
|
| - AddRef(); // balanced in QueryComplete
|
| + // Starts the resolve proxy request.
|
| + int Start() {
|
| + DCHECK(!was_cancelled());
|
| + DCHECK(!is_started());
|
|
|
| - GURL query_url = SanitizeURLForProxyResolver(url_);
|
| - const GURL& pac_url = service_->config_.pac_url;
|
| - results_->config_id_ = service_->config_.id();
|
| + config_id_ = service_->config_.id();
|
|
|
| - service_->pac_thread()->message_loop()->PostTask(FROM_HERE,
|
| - NewRunnableMethod(this, &ProxyService::PacRequest::DoQuery,
|
| - service_->resolver(), query_url, pac_url));
|
| + return resolver()->GetProxyForURL(
|
| + url_, results_, &io_callback_, &resolve_job_);
|
| }
|
|
|
| - // Run the request's callback on the current message loop.
|
| - void PostCallback(int result_code) {
|
| - AddRef(); // balanced in DoCallback
|
| - MessageLoop::current()->PostTask(FROM_HERE,
|
| - NewRunnableMethod(this, &ProxyService::PacRequest::DoCallback,
|
| - result_code));
|
| + bool is_started() const {
|
| + // Note that !! casts to bool. (VS gives a warning otherwise).
|
| + return !!resolve_job_;
|
| }
|
|
|
| + void StartAndComplete() {
|
| + int rv = Start();
|
| + if (rv != ERR_IO_PENDING)
|
| + QueryComplete(rv);
|
| + }
|
| +
|
| void Cancel() {
|
| - // Clear these to inform QueryComplete that it should not try to
|
| - // access them.
|
| + // The request may already be running in the resolver.
|
| + if (is_started()) {
|
| + resolver()->CancelRequest(resolve_job_);
|
| + resolve_job_ = NULL;
|
| + }
|
| +
|
| + // Mark as cancelled, to prevent accessing this again later.
|
| service_ = NULL;
|
| - callback_ = NULL;
|
| + user_callback_ = NULL;
|
| results_ = NULL;
|
| }
|
|
|
| // Returns true if Cancel() has been called.
|
| - bool was_cancelled() const { return callback_ == NULL; }
|
| + bool was_cancelled() const { return user_callback_ == NULL; }
|
|
|
| - private:
|
| - friend class ProxyService;
|
| + // Helper to call after ProxyResolver completion (both synchronous and
|
| + // asynchronous). Fixes up the result that is to be returned to user.
|
| + int QueryDidComplete(int result_code) {
|
| + DCHECK(!was_cancelled());
|
|
|
| - // Runs on the PAC thread.
|
| - void DoQuery(ProxyResolver* resolver,
|
| - const GURL& query_url,
|
| - const GURL& pac_url) {
|
| - int rv = resolver->GetProxyForURL(query_url, pac_url, &results_buf_);
|
| - origin_loop_->PostTask(FROM_HERE,
|
| - NewRunnableMethod(this, &PacRequest::QueryComplete, rv));
|
| - }
|
| + // Make a note in the results which configuration was in use at the
|
| + // time of the resolve.
|
| + results_->config_id_ = config_id_;
|
|
|
| - // Runs the completion callback on the origin thread.
|
| - void QueryComplete(int result_code) {
|
| - // The PacRequest may have been cancelled after it was started.
|
| - if (!was_cancelled()) {
|
| - service_->DidCompletePacRequest(results_->config_id_, result_code);
|
| + // Reset the state associated with in-progress-resolve.
|
| + resolve_job_ = NULL;
|
| + config_id_ = ProxyConfig::INVALID_ID;
|
|
|
| - if (result_code == OK) {
|
| - results_->Use(results_buf_);
|
| - results_->RemoveBadProxies(service_->proxy_retry_info_);
|
| - }
|
| - callback_->Run(result_code);
|
| + // Notify the service of the completion.
|
| + service_->DidCompletePacRequest(results_->config_id_, result_code);
|
|
|
| - // We check for cancellation once again, in case the callback deleted
|
| - // the owning ProxyService (whose destructor will in turn cancel us).
|
| - if (!was_cancelled())
|
| - service_->RemoveFrontOfRequestQueue(this);
|
| - }
|
| + // Clean up the results list.
|
| + if (result_code == OK)
|
| + results_->RemoveBadProxies(service_->proxy_retry_info_);
|
|
|
| - Release(); // balances the AddRef in Query. we may get deleted after
|
| - // we return.
|
| + return result_code;
|
| }
|
|
|
| - // Runs the completion callback on the origin thread.
|
| - void DoCallback(int result_code) {
|
| - if (!was_cancelled()) {
|
| - callback_->Run(result_code);
|
| - }
|
| - Release(); // balances the AddRef in PostCallback.
|
| + private:
|
| + // Callback for when the ProxyResolver request has completed.
|
| + void QueryComplete(int result_code) {
|
| + result_code = QueryDidComplete(result_code);
|
| +
|
| + // Remove this completed PacRequest from the service's pending list.
|
| + /// (which will probably cause deletion of |this|).
|
| + CompletionCallback* callback = user_callback_;
|
| + service_->RemovePendingRequest(this);
|
| +
|
| + callback->Run(result_code);
|
| }
|
|
|
| - // Must only be used on the "origin" thread.
|
| + ProxyResolver* resolver() const { return service_->resolver_.get(); }
|
| +
|
| ProxyService* service_;
|
| - CompletionCallback* callback_;
|
| + CompletionCallback* user_callback_;
|
| + CompletionCallbackImpl<PacRequest> io_callback_;
|
| ProxyInfo* results_;
|
| GURL url_;
|
| - bool is_started_;
|
| -
|
| - // Usable from within DoQuery on the PAC thread.
|
| - ProxyInfo results_buf_;
|
| - MessageLoop* origin_loop_;
|
| + ProxyResolver::RequestHandle resolve_job_;
|
| + ProxyConfig::ID config_id_; // The config id when the resolve was started.
|
| };
|
|
|
| // ProxyService ---------------------------------------------------------------
|
| @@ -231,10 +218,14 @@
|
| proxy_resolver = CreateNonV8ProxyResolver();
|
| }
|
|
|
| + // Wrap the (synchronous) ProxyResolver implementation in a single-threaded
|
| + // runner. This will dispatch requests to a threadpool of size 1.
|
| + proxy_resolver = new SingleThreadedProxyResolver(proxy_resolver);
|
| +
|
| ProxyService* proxy_service = new ProxyService(
|
| proxy_config_service, proxy_resolver);
|
|
|
| - if (!proxy_resolver->does_fetch()) {
|
| + if (proxy_resolver->expects_pac_bytes()) {
|
| // Configure PAC script downloads to be issued using |url_request_context|.
|
| DCHECK(url_request_context);
|
| proxy_service->SetProxyScriptFetcher(
|
| @@ -255,11 +246,13 @@
|
| return new ProxyService(new ProxyConfigServiceNull, new ProxyResolverNull);
|
| }
|
|
|
| -int ProxyService::ResolveProxy(const GURL& url, ProxyInfo* result,
|
| +int ProxyService::ResolveProxy(const GURL& raw_url, ProxyInfo* result,
|
| CompletionCallback* callback,
|
| PacRequest** pac_request) {
|
| DCHECK(callback);
|
|
|
| + GURL url = SanitizeURLForProxyResolver(raw_url);
|
| +
|
| // Check if the request can be completed right away. This is the case when
|
| // using a direct connection, or when the config is bad.
|
| UpdateConfigIfOld();
|
| @@ -267,10 +260,20 @@
|
| if (rv != ERR_IO_PENDING)
|
| return rv;
|
|
|
| - // Otherwise, push the request into the work queue.
|
| scoped_refptr<PacRequest> req = new PacRequest(this, url, result, callback);
|
| +
|
| + bool resolver_is_ready = PrepareResolverForRequests();
|
| +
|
| + if (resolver_is_ready) {
|
| + // Start the resolve request.
|
| + rv = req->Start();
|
| + if (rv != ERR_IO_PENDING)
|
| + return req->QueryDidComplete(rv);
|
| + }
|
| +
|
| + DCHECK_EQ(ERR_IO_PENDING, rv);
|
| + DCHECK(!ContainsPendingRequest(req));
|
| pending_requests_.push_back(req);
|
| - ProcessPendingRequests(req.get());
|
|
|
| // Completion will be notifed through |callback|, unless the caller cancels
|
| // the request using |pac_request|.
|
| @@ -350,50 +353,39 @@
|
| }
|
| }
|
|
|
| -void ProxyService::InitPacThread() {
|
| - if (!pac_thread_.get()) {
|
| - pac_thread_.reset(new base::Thread("pac-thread"));
|
| - pac_thread_->Start();
|
| - }
|
| -}
|
| -
|
| ProxyService::~ProxyService() {
|
| - // Cancel the inprogress request (if any), and free the rest.
|
| - for (PendingRequestsQueue::iterator it = pending_requests_.begin();
|
| + // Cancel any inprogress requests.
|
| + for (PendingRequests::iterator it = pending_requests_.begin();
|
| it != pending_requests_.end();
|
| ++it) {
|
| (*it)->Cancel();
|
| }
|
| }
|
|
|
| -void ProxyService::ProcessPendingRequests(PacRequest* recent_req) {
|
| - if (pending_requests_.empty())
|
| - return;
|
| +void ProxyService::ResumeAllPendingRequests() {
|
| + // Make a copy in case |this| is deleted during the synchronous completion
|
| + // of one of the requests. If |this| is deleted then all of the PacRequest
|
| + // instances will be Cancel()-ed.
|
| + PendingRequests pending_copy = pending_requests_;
|
|
|
| - // While the PAC script is being downloaded, requests are blocked.
|
| - if (IsFetchingPacScript())
|
| - return;
|
| -
|
| - // Get the next request to process (FIFO).
|
| - PacRequest* req = pending_requests_.front().get();
|
| - if (req->is_started_)
|
| - return;
|
| -
|
| - // The configuration may have changed since |req| was added to the
|
| - // queue. It could be this request now completes synchronously.
|
| - if (req != recent_req) {
|
| - UpdateConfigIfOld();
|
| - int rv = TryToCompleteSynchronously(req->url_, req->results_);
|
| - if (rv != ERR_IO_PENDING) {
|
| - req->PostCallback(rv);
|
| - RemoveFrontOfRequestQueue(req);
|
| - return;
|
| - }
|
| + for (PendingRequests::iterator it = pending_copy.begin();
|
| + it != pending_copy.end();
|
| + ++it) {
|
| + PacRequest* req = it->get();
|
| + if (!req->is_started() && !req->was_cancelled())
|
| + req->StartAndComplete();
|
| }
|
| +}
|
|
|
| +bool ProxyService::PrepareResolverForRequests() {
|
| + // While the PAC script is being downloaded, block requests.
|
| + if (IsFetchingPacScript())
|
| + return false;
|
| +
|
| // Check if a new PAC script needs to be downloaded.
|
| DCHECK(config_.id() != ProxyConfig::INVALID_ID);
|
| - if (!resolver_->does_fetch() && config_.id() != fetched_pac_config_id_) {
|
| + if (resolver_->expects_pac_bytes() &&
|
| + config_.id() != fetched_pac_config_id_) {
|
| // For auto-detect we use the well known WPAD url.
|
| GURL pac_url = config_.auto_detect ?
|
| GURL("http://wpad/wpad.dat") : config_.pac_url;
|
| @@ -405,26 +397,16 @@
|
|
|
| proxy_script_fetcher_->Fetch(
|
| pac_url, &in_progress_fetch_bytes_, &proxy_script_fetcher_callback_);
|
| - return;
|
| + return false;
|
| }
|
|
|
| - // The only choice left now is to actually run the ProxyResolver on
|
| - // the PAC thread.
|
| - InitPacThread();
|
| - req->Query();
|
| + // We are good to go.
|
| + return true;
|
| }
|
|
|
| -void ProxyService::RemoveFrontOfRequestQueue(PacRequest* expected_req) {
|
| - DCHECK(pending_requests_.front().get() == expected_req);
|
| - pending_requests_.pop_front();
|
| -
|
| - // Start next work item.
|
| - ProcessPendingRequests(NULL);
|
| -}
|
| -
|
| void ProxyService::OnScriptFetchCompletion(int result) {
|
| DCHECK(IsFetchingPacScript());
|
| - DCHECK(!resolver_->does_fetch());
|
| + DCHECK(resolver_->expects_pac_bytes());
|
|
|
| LOG(INFO) << "Completed PAC script fetch for config_id="
|
| << in_progress_fetch_config_id_
|
| @@ -434,18 +416,16 @@
|
|
|
| // Notify the ProxyResolver of the new script data (will be empty string if
|
| // result != OK).
|
| - InitPacThread();
|
| - pac_thread()->message_loop()->PostTask(FROM_HERE,
|
| - new NotifyFetchCompletionTask(
|
| - resolver_.get(), in_progress_fetch_bytes_));
|
| + resolver_->SetPacScriptByData(in_progress_fetch_bytes_);
|
|
|
| fetched_pac_config_id_ = in_progress_fetch_config_id_;
|
| fetched_pac_error_ = result;
|
| in_progress_fetch_config_id_ = ProxyConfig::INVALID_ID;
|
| in_progress_fetch_bytes_.clear();
|
|
|
| - // Start a pending request if any.
|
| - ProcessPendingRequests(NULL);
|
| + // Resume any requests which we had to defer until the PAC script was
|
| + // downloaded.
|
| + ResumeAllPendingRequests();
|
| }
|
|
|
| int ProxyService::ReconsiderProxyAfterError(const GURL& url,
|
| @@ -498,30 +478,23 @@
|
| return OK;
|
| }
|
|
|
| -// There are four states of the request we need to handle:
|
| -// (1) Not started (just sitting in the queue).
|
| -// (2) Executing PacRequest::DoQuery in the PAC thread.
|
| -// (3) Waiting for PacRequest::QueryComplete to be run on the origin thread.
|
| -// (4) Waiting for PacRequest::DoCallback to be run on the origin thread.
|
| void ProxyService::CancelPacRequest(PacRequest* req) {
|
| DCHECK(req);
|
| -
|
| - bool is_active_request = req->is_started_ && !pending_requests_.empty() &&
|
| - pending_requests_.front().get() == req;
|
| -
|
| req->Cancel();
|
| + RemovePendingRequest(req);
|
| +}
|
|
|
| - if (is_active_request) {
|
| - RemoveFrontOfRequestQueue(req);
|
| - return;
|
| - }
|
| +bool ProxyService::ContainsPendingRequest(PacRequest* req) {
|
| + PendingRequests::iterator it = std::find(
|
| + pending_requests_.begin(), pending_requests_.end(), req);
|
| + return pending_requests_.end() != it;
|
| +}
|
|
|
| - // Otherwise just delete the request from the queue.
|
| - PendingRequestsQueue::iterator it = std::find(
|
| +void ProxyService::RemovePendingRequest(PacRequest* req) {
|
| + DCHECK(ContainsPendingRequest(req));
|
| + PendingRequests::iterator it = std::find(
|
| pending_requests_.begin(), pending_requests_.end(), req);
|
| - if (it != pending_requests_.end()) {
|
| - pending_requests_.erase(it);
|
| - }
|
| + pending_requests_.erase(it);
|
| }
|
|
|
| void ProxyService::SetProxyScriptFetcher(
|
| @@ -621,6 +594,15 @@
|
| // Reset state associated with latest config.
|
| config_is_bad_ = false;
|
| proxy_retry_info_.clear();
|
| +
|
| + // Tell the resolver to use the new PAC URL (for resolver's that fetch the
|
| + // PAC script internally).
|
| + // TODO(eroman): this isn't quite right. See http://crbug.com/9985
|
| + if ((config_.pac_url.is_valid() || config_.auto_detect) &&
|
| + !resolver_->expects_pac_bytes()) {
|
| + const GURL pac_url = config_.auto_detect ? GURL() : config_.pac_url;
|
| + resolver_->SetPacScriptByUrl(pac_url);
|
| + }
|
| }
|
|
|
| void ProxyService::UpdateConfigIfOld() {
|
|
|