Index: net/proxy/proxy_resolver_v8_tracing.cc |
diff --git a/net/proxy/proxy_resolver_v8_tracing.cc b/net/proxy/proxy_resolver_v8_tracing.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..49e8af3d683a4c7f0bc0185054f4c7c6e35af130 |
--- /dev/null |
+++ b/net/proxy/proxy_resolver_v8_tracing.cc |
@@ -0,0 +1,847 @@ |
+// Copyright (c) 2013 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/proxy/proxy_resolver_v8_tracing.h" |
+ |
+#include "base/bind.h" |
+#include "base/md5.h" |
+#include "base/message_loop_proxy.h" |
+#include "base/string_number_conversions.h" |
+#include "base/string_util.h" |
+#include "base/stringprintf.h" |
+#include "base/synchronization/cancellation_flag.h" |
+#include "base/synchronization/waitable_event.h" |
+#include "base/threading/thread.h" |
+#include "base/threading/thread_restrictions.h" |
+#include "base/values.h" |
+#include "net/base/address_list.h" |
+#include "net/base/host_resolver.h" |
+#include "net/base/net_errors.h" |
+#include "net/base/net_log.h" |
+#include "net/proxy/proxy_info.h" |
+#include "net/proxy/proxy_resolver_error_observer.h" |
+#include "net/proxy/proxy_resolver_v8.h" |
+ |
+// The intent of this class is explained in the design document: |
+// https://docs.google.com/a/chromium.org/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPoMJdaBn9rKreAmGOdE/edit |
mmenke
2013/01/18 19:59:40
"Sorry, the file you have requested does not exist
eroman
2013/01/23 03:26:02
Are you sure you copied the full line exactly? (it
mmenke
2013/01/24 21:06:33
Oops...A straight copy gets the spaces inserted by
|
+// |
+// In a nutshell, PAC scripts are Javascript programs and may depend on |
+// network I/O, by calling functions like dnsResolve(). |
+// |
+// This is problematic since functions such as dnsResolve() will block the |
+// Javascript execution until the DNS result is availble, thereby stalling the |
+// PAC thread, which hurts the ability to process parallel proxy resolves. |
+// An obvious solution is to simply start more PAC threads, however this scales |
+// poorly, which hurts the ability to process parallel proxy resolves. |
+// |
+// The solution in ProxyResolverV8Tracing is to model PAC scripts as being |
+// deterministic, and depending only on the inputted URL. When the script |
+// issues a dnsResolve() for a yet unresolved hostname, the Javascript |
+// execution is "aborted", and then re-started once the DNS result is |
+// known. |
+namespace net { |
+ |
+namespace { |
+ |
+// Upper bound on how many *unique* DNS resolves a PAC script is allowed |
+// to make. This is a failsafe in case the logic to detect PAC scripts |
+// incompatible with the tracing optimization doesn't work. It is not |
+// expected for well behaved scripts to hit this. |
+const size_t kMaxUniqueResolveDnsPerExec = 20; |
+ |
+// Approximate number of bytes to use for buffering alerts() and errors. |
+// This is a failsafe in case repeated executions of the script causes |
+// too much memory bloat. It is not expected for well behaved scripts to |
+// hit this. (In fact normal scripts should not even have alerts() or errors). |
+const size_t kMaxAlertsAndErrorsBytes = 2048; |
+ |
+// Compares two MD5 digests for equality. The second digest is given as a |
+// non-finalized MD5Context. |
+bool MD5DigestEquals(const base::MD5Digest& digest1, |
+ const base::MD5Context& digest2_context) { |
+ // Finalize the context into a digest. (Make a copy first |
+ // because MD5Final() mutates the context). |
+ base::MD5Digest digest2; |
+ base::MD5Context digest2_copy; |
+ memcpy(&digest2_copy[0], &digest2_context[0], sizeof(base::MD5Context)); |
+ base::MD5Final(&digest2, &digest2_copy); |
+ |
+ return memcmp(&digest1, &digest2, sizeof(base::MD5Digest)) == 0; |
+} |
+ |
+// Returns event parameters for a PAC error message (line number + message). |
+base::Value* NetLogErrorCallback(int line_number, |
+ const string16* message, |
+ NetLog::LogLevel /* log_level */) { |
+ base::DictionaryValue* dict = new base::DictionaryValue(); |
+ dict->SetInteger("line_number", line_number); |
+ dict->SetString("message", *message); |
+ return dict; |
+} |
+ |
+} // namespace |
+ |
+// The Job class is responsible for executing GetProxyForURL() and |
+// SetPacScript(), since both of these operations share similar code. |
+// |
+// The DNS for these operations can operate in either synchronous or |
+// asynchronous mode. Synchronous mode is used as a fallback when the PAC script |
+// seems to be misbehaving under the tracing optimization. |
mmenke
2013/01/18 19:59:40
You call these blocking and non-blocking elsewhere
eroman
2013/01/23 03:26:02
Done -- renamed everything to blocking/non-blockin
|
+// |
+// Note that this class runs on both the origin thread and a worker |
+// thread. Most methods are expected to be used exclusively on one thread |
+// or the other. |
+// |
+// The lifetiime of Jobs does not exceed that of the ProxyResolverV8Tracing that |
mmenke
2013/01/18 19:59:40
nit: lifetime
eroman
2013/01/23 03:26:02
Done.
|
+// spawned it. Destruction might happen on either the origin thread or the |
+// worker thread. |
+class ProxyResolverV8Tracing::Job |
+ : public base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>, |
+ public ProxyResolverV8::JSBindings { |
+ public: |
+ // |parent| is non-owned, and references the ProxyResolverV8Tracing that |
+ // spawned this Job. |
+ explicit Job(ProxyResolverV8Tracing* parent) |
+ : origin_loop_(base::MessageLoopProxy::current()), |
+ parent_(parent), |
+ event_(true, false) { |
+ } |
+ |
+ void StartSetPacScript( |
+ const scoped_refptr<ProxyResolverScriptData>& script_data, |
+ const CompletionCallback& callback) { |
mmenke
2013/01/18 19:59:40
I recommend de-inlining all these functions. With
eroman
2013/01/23 03:26:02
Done.
|
+ CheckIsOnOriginThread(); |
+ |
+ operation_ = SET_PAC_SCRIPT; |
+ // Script initialization uses a synchronous DNS since there isn't any |
+ // advantage to using asynchronous mode here. That is because the |
+ // parent ProxyService can't submit any ProxyResolve requests until |
+ // initialization has completed successfully! |
mmenke
2013/01/18 19:59:40
Setting the PAC can call scripts?
eroman
2013/01/23 03:26:02
Setting the PAC involves "sourcing" the script. It
|
+ nonblocking_dns_ = false; |
+ |
+ script_data_ = script_data; |
+ callback_ = callback; |
+ |
+ PostStart(); |
+ } |
+ |
+ void StartGetProxyForURL(const GURL& url, |
+ ProxyInfo* results, |
+ const BoundNetLog& net_log, |
+ const CompletionCallback& callback) { |
+ CheckIsOnOriginThread(); |
+ |
+ operation_ = GET_PROXY_FOR_URL; |
+ nonblocking_dns_ = true; |
+ |
+ url_ = url; |
+ user_results_ = results; |
+ bound_net_log_ = net_log; |
+ callback_ = callback; |
+ |
+ last_num_dns_ = 0; |
+ |
+ PostStart(); |
+ } |
+ |
+ void Cancel() { |
+ CheckIsOnOriginThread(); |
+ |
+ // There are three main possibility to consider for cancellation: |
+ // (a) The job has been posted to the worker thread, but not yet started |
+ // (b) The script is executing on the worker thread |
+ // (c) The script is blocked waiting for DNS |
mmenke
2013/01/18 19:59:40
(d) The script has stopped, and there's a pending
eroman
2013/01/23 03:26:02
Done.
|
+ // |
+ // |cancelled_| is read on both the origin thread and worker thread. The |
+ // code that runs on the worker thread is littered with checks on |
+ // |cancelled_| to break out early. Breaking out early is not necessary |
+ // for correctness, however it does avoid doing unecessary work. |
mmenke
2013/01/18 19:59:40
It is necessary to in the blocking case, isn't it?
eroman
2013/01/23 03:26:02
Yes that is true, cancellation which interrupts a
|
+ // |
+ // The important check on |cancelled_| is done from the origin thread in |
+ // NotifyCallerOnOriginLoop(). |
+ cancelled_.Set(); |
+ |
+ // These two assignments are not necessary for correctness. Just defensive |
+ // programming to make sure the user-provided data is not used beyond this |
+ // point. |
+ user_results_ = NULL; |
+ callback_ = CompletionCallback(); |
+ |
+ if (pending_dns_.get()) { |
mmenke
2013/01/18 19:59:40
nit: Preferred style is not to use get().
eroman
2013/01/23 03:26:02
Done.
|
+ host_resolver()->CancelRequest(pending_dns_->handle); |
+ pending_dns_.reset(); |
+ } |
+ |
+ // The worker thread might be blocked waiting for DNS. |
+ event_.Signal(); |
+ |
+ Release(); |
+ } |
+ |
+ LoadState GetLoadState() const { |
+ CheckIsOnOriginThread(); |
+ |
+ if (pending_dns_.get()) |
mmenke
2013/01/18 19:59:40
nit: Preferred style is not to use get().
eroman
2013/01/23 03:26:02
Done.
|
+ return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT; |
+ |
+ return LOAD_STATE_RESOLVING_PROXY_FOR_URL; |
+ } |
+ |
+ private: |
+ friend class base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>; |
+ |
+ enum Operation { |
+ SET_PAC_SCRIPT, |
+ GET_PROXY_FOR_URL, |
+ }; |
+ |
+ struct AlertOrError { |
+ AlertOrError(bool is_alert, int line_number, const string16& message) |
+ : is_alert(is_alert), line_number(line_number), message(message) {} |
+ |
+ bool is_alert; |
+ int line_number; |
+ string16 message; |
mmenke
2013/01/18 19:59:40
Could you make these const? Not sure if that woul
eroman
2013/01/23 03:26:02
Yeah const makes std::vector sad, because push_bac
|
+ }; |
+ |
+ struct ExecutionState { |
+ bool was_abandoned; |
+ |
+ // The number of times ResolveDns() was called so far. |
+ int num_dns; |
+ base::MD5Context dns_digest; |
+ |
+ size_t alerts_and_errors_bytes_; |
+ std::vector<AlertOrError> alerts_and_errors_; |
+ }; |
+ |
+ struct PendingDns { |
+ PendingDns(const std::string& host, ResolveDnsOperation op) |
+ : host(host), op(op), handle(NULL) {} |
+ |
+ std::string host; |
+ ResolveDnsOperation op; |
+ |
+ HostResolver::RequestHandle handle; |
+ net::AddressList addresses; |
+ }; |
+ |
+ ~Job() { |
+ DCHECK(!pending_dns_.get()); |
+ } |
+ |
+ void CheckIsOnWorkerThread() { |
+ DCHECK_EQ(MessageLoop::current(), parent_->thread_->message_loop()); |
+ } |
+ |
+ void CheckIsOnOriginThread() const { |
+ DCHECK(origin_loop_->BelongsToCurrentThread()); |
+ } |
+ |
+ ProxyResolverV8* v8_resolver() { |
+ return parent_->v8_resolver_.get(); |
+ } |
+ |
+ MessageLoop* worker_loop() { |
+ return parent_->thread_->message_loop(); |
+ } |
+ |
+ HostResolver* host_resolver() { |
+ return parent_->host_resolver_; |
+ } |
+ |
+ ProxyResolverErrorObserver* error_observer() { |
+ return parent_->error_observer_.get(); |
+ } |
+ |
+ NetLog* net_log() { |
+ return parent_->net_log_; |
+ } |
+ |
+ void PostStart() { |
+ AddRef(); // Balanced on completion, or cancellation. |
+ worker_loop()->PostTask(FROM_HERE, base::Bind(&Job::Start, this)); |
+ } |
+ |
+ // Post a task to the origin thread, to invoke the user's callback. |
+ void NotifyCaller(int result) { |
+ CheckIsOnWorkerThread(); |
+ |
+ origin_loop_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&Job::NotifyCallerOnOriginLoop, this, result)); |
+ } |
+ |
+ void NotifyCallerOnOriginLoop(int result) { |
+ CheckIsOnOriginThread(); |
+ |
+ if (cancelled_.IsSet()) |
+ return; |
+ |
+ if (operation_ == GET_PROXY_FOR_URL) |
+ *user_results_ = results_; |
+ |
+ // There is only ever 1 outstanding SET_PAC_SCRIPT job. It needs to be |
+ // tracked to support cancellation. |
+ if (operation_ == SET_PAC_SCRIPT) { |
+ DCHECK(parent_->set_pac_script_job_); |
+ parent_->set_pac_script_job_ = NULL; |
+ } |
+ |
+ callback_.Run(result); |
+ callback_.Reset(); |
+ |
+ Release(); |
+ } |
+ |
+ void Start() { |
+ CheckIsOnWorkerThread(); |
+ |
+ if (cancelled_.IsSet()) |
+ return; |
+ |
+ if (nonblocking_dns_) { |
+ // Reset state pertaining to the current playthrough. |
+ exec_.was_abandoned = false; |
+ exec_.num_dns = 0; |
+ base::MD5Init(&exec_.dns_digest); |
+ exec_.alerts_and_errors_.clear(); |
+ exec_.alerts_and_errors_bytes_ = 0; |
+ } |
+ |
+ JSBindings* prev_bindings = v8_resolver()->js_bindings(); |
+ v8_resolver()->set_js_bindings(this); |
+ |
+ int result = ERR_UNEXPECTED; // Initialized to silence warnings. |
+ |
+ switch (operation_) { |
+ case SET_PAC_SCRIPT: |
+ result = v8_resolver()->SetPacScript( |
+ script_data_, CompletionCallback()); |
+ break; |
+ case GET_PROXY_FOR_URL: |
+ result = v8_resolver()->GetProxyForURL( |
+ url_, |
+ // Important: Do not write directly into |user_results_|, since if the |
+ // request were to be cancelled from the origin thread, must guarantee |
+ // that |user_results| is not accessed anymore must guarantee that |
+ // |user_results| is not accessed anymore. |
+ &results_, |
+ CompletionCallback(), |
+ NULL, |
+ bound_net_log_); |
+ break; |
+ } |
+ |
+ v8_resolver()->set_js_bindings(prev_bindings); |
+ |
+ if (nonblocking_dns_) { |
+ if (!exec_.was_abandoned) { |
+ // Some alerts() and errors may have been buffered, since it was not |
+ // known at the time whether this run would be discarded. Now that it is |
+ // known to be a full run, dispatch those buffered events. |
+ DispatchBufferedAlertsAndErrors(); |
+ NotifyCaller(result); |
+ } |
+ } else { |
+ NotifyCaller(result); |
+ } |
+ } |
+ |
+ // Handler for "alert(message)" |
+ virtual void Alert(const string16& message) OVERRIDE { |
+ HandleAlertOrError(true, -1, message); |
+ } |
+ |
+ // Handler for when an error is encountered. |line_number| may be -1 |
+ // if a line number is not applicable to this error. |
+ virtual void OnError(int line_number, const string16& error) OVERRIDE { |
+ HandleAlertOrError(false, line_number, error); |
+ } |
+ |
+ void HandleAlertOrError(bool is_alert, int line_number, |
+ const string16& message) { |
+ if (cancelled_.IsSet()) |
+ return; |
+ |
+ if (!nonblocking_dns_) { |
+ // In blocking DNS mode the events can be dispatched immediately. |
+ DispatchAlertOrError(is_alert, line_number, message); |
+ return; |
+ } |
+ |
+ // Otherwise in nonblocking mode, buffer all the messages until |
+ // the end. |
+ |
+ if (exec_.was_abandoned) |
+ return; |
+ |
+ exec_.alerts_and_errors_bytes_ += message.size() * 2; |
+ |
+ // If there have been lots of messages, enqueuing could be expensive on |
+ // memory. (consider a script which does megabytes worth of alerts()). |
+ // Avoid this by falling back to blocking mode. |
+ if (exec_.alerts_and_errors_bytes_ > kMaxAlertsAndErrorsBytes) { |
+ ScheduleRestartWithBlockingDns(); |
+ exec_.was_abandoned = true; |
+ return; |
+ } |
+ |
+ exec_.alerts_and_errors_.push_back( |
+ AlertOrError(is_alert, line_number, message)); |
+ } |
+ |
+ void DispatchBufferedAlertsAndErrors() { |
+ DCHECK(nonblocking_dns_); |
+ DCHECK(!exec_.was_abandoned); |
+ |
+ for (size_t i = 0; i < exec_.alerts_and_errors_.size(); ++i) { |
+ const AlertOrError& x = exec_.alerts_and_errors_[i]; |
+ DispatchAlertOrError(x.is_alert, x.line_number, x.message); |
+ } |
+ } |
+ |
+ void DispatchAlertOrError(bool is_alert, int line_number, |
+ const string16& message) { |
+ // Note that the handling of cancellation is racy with regard to |
+ // alerts/errors. The request might get cancelled shortly after this |
+ // check! (There is no lock being held to guarantee otherwise). |
+ // |
+ // If this happens, then some information will get written to the NetLog |
+ // needlessly, however the NetLog will still be alive so it shouldn't cause |
+ // problems. |
+ if (cancelled_.IsSet()) |
+ return; |
+ |
+ if (is_alert) { |
+ // ------------------- |
+ // alert |
+ // ------------------- |
+ VLOG(1) << "PAC-alert: " << message; |
+ |
+ // Send to the NetLog. |
+ LogEventToCurrentRequestAndGlobally( |
+ NetLog::TYPE_PAC_JAVASCRIPT_ALERT, |
+ NetLog::StringCallback("message", &message)); |
+ } else { |
+ // ------------------- |
+ // error |
+ // ------------------- |
+ if (line_number == -1) |
+ VLOG(1) << "PAC-error: " << message; |
+ else |
+ VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message; |
+ |
+ // Send the error to the NetLog. |
+ LogEventToCurrentRequestAndGlobally( |
+ NetLog::TYPE_PAC_JAVASCRIPT_ERROR, |
+ base::Bind(&NetLogErrorCallback, line_number, &message)); |
+ |
+ if (error_observer()) |
+ error_observer()->OnPACScriptError(line_number, message); |
+ } |
+ } |
+ |
+ void LogEventToCurrentRequestAndGlobally( |
mmenke
2013/01/18 19:59:40
nit: Fix indent.
eroman
2013/01/23 03:26:02
Done.
|
+ NetLog::EventType type, |
+ const NetLog::ParametersCallback& parameters_callback) { |
+ bound_net_log_.AddEvent(type, parameters_callback); |
+ |
+ // Emit to the global NetLog event stream. |
+ if (net_log()) |
+ net_log()->AddGlobalEntry(type, parameters_callback); |
+ } |
+ |
+ virtual bool ResolveDns(const std::string& host, |
+ ResolveDnsOperation op, |
+ std::string* output) { |
+ if (cancelled_.IsSet()) |
+ return false; |
+ |
+ if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) { |
+ // a DNS resolve with an empty hostname is considered an error. |
+ return false; |
+ } |
+ |
+ return nonblocking_dns_ ? |
+ ResolveDnsNonBlocking(host, op, output) : |
+ ResolveDnsBlocking(host, op, output); |
mmenke
2013/01/18 19:59:40
nit: 4 space indent.
eroman
2013/01/23 03:26:02
Done.
|
+ } |
+ |
+ bool ResolveDnsBlocking(const std::string& host, |
+ ResolveDnsOperation op, |
+ std::string* output) { |
+ CheckIsOnWorkerThread(); |
+ |
+ // Check if the DNS result for this host has already been cached. |
+ bool rv; |
+ if (GetDnsFromLocalCache(host, op, output, &rv)) { |
+ // Yay, cache hit! |
+ return rv; |
+ } |
+ |
+ // If the host was not in the local cache, this is a new hostname. |
+ |
+ if (dns_cache_.size() > kMaxUniqueResolveDnsPerExec) { |
+ // Safety net for scripts with unexpectedly many DNS calls. |
+ // We will continue running to completion, but will fail every |
+ // subsequent DNS request. |
+ return false; |
+ } |
+ |
+ bool unused; |
+ origin_loop_->PostTask( |
+ FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op, &unused)); |
+ |
+ // Wait for the DNS operation to be completed by the host resolver on the |
+ // origin thread. After waking up, either the request was cancelled, or |
+ // the DNS result is now available in the cache. |
+ event_.Wait(); |
+ event_.Reset(); |
+ |
+ if (cancelled_.IsSet()) |
+ return false; |
+ |
+ CHECK(GetDnsFromLocalCache(host, op, output, &rv)); |
+ return rv; |
+ } |
+ |
+ // To detect inconsistencies, the past sequence of ResovleDns operations are |
+ // saved and enforced during each replay. Instead of saving the full sequence |
+ // of hostnames, just an MD5 checksum is kept (to avoid unbounded memory |
+ // growth). |
+ bool ResolveDnsNonBlocking(const std::string& host, |
+ ResolveDnsOperation op, |
+ std::string* output) { |
+ CheckIsOnWorkerThread(); |
+ |
+ if (exec_.was_abandoned) { |
+ // If this execution was alread abandoned can fail right away. Only 1 DNS |
mmenke
2013/01/18 19:59:40
nit "already"
eroman
2013/01/23 03:26:02
Done.
|
+ // dependency will be traced at a time (for more predictable outcomes). |
+ return false; |
+ } |
+ |
+ exec_.num_dns += 1; |
+ |
+ // Keep a digest of the sequence of DNS requests this execution has seen so |
+ // far. This is cheaper than keeping a full list of the history, and good |
+ // enough for our purposes (we want to detect when things have gone wrong in |
+ // order to fallback to synchronous mode). |
+ base::MD5Update(&exec_.dns_digest, host); |
+ base::MD5Update(&exec_.dns_digest, base::IntToString(op)); |
mmenke
2013/01/18 19:59:40
Does this really get us anything we care about ove
eroman
2013/01/23 03:26:02
I chose to enforce the sequence of all DNS request
mmenke
2013/01/24 21:06:33
I was just thinking that it's not too likely that
eroman
2013/01/24 22:13:58
Thanks for discussing this issue!
Indeed, none of
|
+ |
+ // Upon reaching the point of execution from the last run, make sure |
+ // that the sequence of resolves has matched so far. |
+ if (exec_.num_dns == last_num_dns_ && |
+ !MD5DigestEquals(last_dns_digest_, exec_.dns_digest)) { |
+ // The sequence of DNS operations is different from last time! |
+ ScheduleRestartWithBlockingDns(); |
mmenke
2013/01/18 19:59:40
Restarting instead of just switching is primarily
eroman
2013/01/23 03:26:02
Correct, restarting is to keep things simple. Ther
mmenke
2013/01/24 21:06:33
I was thinking that you could get rid of "Schedule
|
+ exec_.was_abandoned = true; |
+ return false; |
+ } |
+ |
+ // Check if the DNS result for this host has already been cached. |
+ bool rv; |
+ if (GetDnsFromLocalCache(host, op, output, &rv)) { |
+ // Yay, cache hit! |
+ return rv; |
+ } |
+ |
+ // If the host was not in the local cache, then this is a new hostname. |
+ |
+ if (exec_.num_dns <= last_num_dns_) { |
+ // The sequence of DNS operations is different from last time! |
+ ScheduleRestartWithBlockingDns(); |
+ exec_.was_abandoned = true; |
+ return false; |
+ } |
+ |
+ if (dns_cache_.size() > kMaxUniqueResolveDnsPerExec) { |
+ // Safety net for scripts with unexpectedly many DNS calls. |
+ return false; |
+ } |
+ |
+ // Post the DNS request to the origin thread. |
+ bool resolver_cache_hit = false; |
+ origin_loop_->PostTask( |
+ FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op, |
+ &resolver_cache_hit)); |
+ |
+ // As an optimization to avoid restarting too often, wait until the |
+ // resolver's cache has been inspected on the origin thread. |
+ event_.Wait(); |
+ event_.Reset(); |
+ |
+ if (cancelled_.IsSet()) |
+ return false; |
+ |
+ if (resolver_cache_hit) { |
+ CHECK(GetDnsFromLocalCache(host, op, output, &rv)); |
+ return rv; |
+ } |
+ |
+ // Otherwise if the result was not in the cache, then a DNS request has |
+ // been started. Abandon this invocation of FindProxyForURL(), it will be |
+ // restarted once the DNS request completes. |
+ exec_.was_abandoned = true; |
+ last_num_dns_ = exec_.num_dns; |
+ base::MD5Final(&last_dns_digest_, &exec_.dns_digest); |
+ return false; |
+ } |
+ |
+ // Builds a RequestInfo to service the specified PAC DNS operation. |
+ static HostResolver::RequestInfo MakeRequestInfo(const std::string& host, |
+ ResolveDnsOperation op) { |
+ HostPortPair host_port = HostPortPair(host, 80); |
+ if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) { |
+ host_port.set_host(GetHostName()); |
+ } |
+ |
+ HostResolver::RequestInfo info(host_port); |
+ |
+ // The non-ex flavors are limited to IPv4 results. |
+ if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) { |
+ info.set_address_family(ADDRESS_FAMILY_IPV4); |
+ } |
+ |
+ return info; |
+ } |
+ |
+ void DoDnsOperation(const std::string& host, ResolveDnsOperation op, |
+ bool* out_cache_hit) { |
+ CheckIsOnOriginThread(); |
+ |
+ if (cancelled_.IsSet()) |
+ return; |
+ |
+ DCHECK(!pending_dns_.get()); |
+ |
+ HostResolver::RequestInfo info = MakeRequestInfo(host, op); |
+ |
+ if (nonblocking_dns_) { |
+ // Check if the DNS result can be serviced directly from the cache. |
+ // (The worker thread is blocked waiting for this information). |
+ net::AddressList addresses; |
+ int result = host_resolver()->ResolveFromCache( |
+ info, &addresses, bound_net_log_); |
+ |
+ bool cache_hit = result != ERR_DNS_CACHE_MISS; |
+ if (cache_hit) { |
+ // Cool, it was in the host resolver's cache. Copy it to the |
+ // per-request cache. |
+ SaveDnsToLocalCache(host, op, result, addresses); |
+ } |
+ |
+ // Important: Do not read/write |out_cache_hit| after signalling, since |
+ // the memory may no longer be valid. |
+ *out_cache_hit = cache_hit; |
+ event_.Signal(); |
+ |
+ if (cache_hit) |
+ return; |
mmenke
2013/01/18 19:59:40
I don't think this cache hit optimization is worth
eroman
2013/01/23 03:26:02
This cuts down quite a few restarts, and IMO is de
mmenke
2013/01/24 21:06:33
Suppose it depends on how expensive script executi
eroman
2013/01/24 23:07:15
One final comment on this issue:
Waiting for the
|
+ } |
+ |
+ pending_dns_.reset(new PendingDns(host, op)); |
+ |
+ int result = host_resolver()->Resolve( |
+ info, |
+ &pending_dns_->addresses, base::Bind(&Job::OnResolveCompletion, this), |
+ &pending_dns_->handle, bound_net_log_); |
+ |
+ if (result != ERR_IO_PENDING) |
+ OnResolveCompletion(result); |
+ } |
+ |
+ void OnResolveCompletion(int result) { |
+ CheckIsOnOriginThread(); |
+ |
+ DCHECK(!cancelled_.IsSet()); |
+ |
+ SaveDnsToLocalCache(pending_dns_->host, pending_dns_->op, result, |
+ pending_dns_->addresses); |
+ pending_dns_.reset(); |
+ |
+ if (nonblocking_dns_) { |
+ // Restart. This time it should make more progress due to having |
+ // cached items. |
+ worker_loop()->PostTask(FROM_HERE, base::Bind(&Job::Start, this)); |
+ } else { |
+ // Otherwise wakeup the blocked worker thread. |
+ event_.Signal(); |
+ } |
+ } |
+ |
+ void ScheduleRestartWithBlockingDns() { |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, base::Bind(&Job::RestartWithBlockingDns, this)); |
+ } |
+ |
+ void RestartWithBlockingDns() { |
+ DCHECK(nonblocking_dns_); |
+ nonblocking_dns_ = false; |
+ Start(); |
+ } |
+ |
+ // Make a key for looking up |host, op| in |dns_cache_|. Strings are used for |
+ // convenience, to avoid defining custom comparators. |
+ static std::string GetCacheKey(const std::string& host, |
+ ResolveDnsOperation op) { |
+ return StringPrintf("%d:%s", op, host.c_str()); |
+ } |
+ |
+ // Serialize |net_error, address_list| into something that can be saved into |
+ // |dns_cache|. |
+ static std::string MakeCacheValue(ResolveDnsOperation op, |
+ int net_error, |
+ const AddressList& address_list) { |
+ if (net_error != OK) |
+ return std::string(); |
+ |
+ // dnsResolve() and myIpAddress() are expected to return a single IP |
+ // address. Whereas the *Ex versions are expected to return a semi-colon |
+ // separated list. |
+ |
+ if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) { |
+ return address_list.front().ToStringWithoutPort(); |
+ } |
+ |
+ std::string address_list_str; |
+ for (AddressList::const_iterator iter = address_list.begin(); |
+ iter != address_list.end(); ++iter) { |
+ if (!address_list_str.empty()) |
+ address_list_str += ";"; |
+ const std::string address_string = iter->ToStringWithoutPort(); |
+ if (address_string.empty()) |
+ return std::string(); |
+ address_list_str += address_string; |
+ } |
+ |
+ return address_list_str; |
+ } |
+ |
+ bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op, |
+ std::string* output, bool* return_value) { |
+ DnsCache::const_iterator it = dns_cache_.find(GetCacheKey(host, op)); |
+ if (it == dns_cache_.end()) |
+ return false; |
+ |
+ *output = it->second; |
+ *return_value = !it->second.empty(); |
+ return true; |
+ } |
+ |
+ void SaveDnsToLocalCache(const std::string& host, ResolveDnsOperation op, |
+ int net_error, const net::AddressList& addresses) { |
+ dns_cache_[GetCacheKey(host, op)] = |
+ MakeCacheValue(op, net_error, addresses); |
+ } |
+ |
+ Operation operation_; |
+ bool nonblocking_dns_; |
mmenke
2013/01/18 19:59:40
Wonder if this should be a property of the PAC scr
mmenke
2013/01/18 19:59:40
Suggest using blocking_dns_ instead. "!non_blocki
eroman
2013/01/23 03:26:02
To make sure I understand:
Do you mean keep track
eroman
2013/01/23 03:26:02
Done.
|
+ |
+ scoped_refptr<base::MessageLoopProxy> origin_loop_; |
+ CompletionCallback callback_; |
+ ProxyResolverV8Tracing* parent_; |
+ base::CancellationFlag cancelled_; |
+ |
+ scoped_refptr<ProxyResolverScriptData> script_data_; |
mmenke
2013/01/18 19:59:40
What do you think of splitting this class into two
mmenke
2013/01/18 20:02:39
If you think that's too ugly, clearly separating o
eroman
2013/01/23 03:26:02
I couldn't get it any cleaner splitting off into s
|
+ |
+ base::WaitableEvent event_; |
+ |
+ GURL url_; |
+ ProxyInfo results_; |
+ ProxyInfo* user_results_; |
+ BoundNetLog bound_net_log_; |
+ |
+ ExecutionState exec_; |
+ |
+ int last_num_dns_; |
+ base::MD5Digest last_dns_digest_; |
+ |
+ typedef std::map<std::string, std::string> DnsCache; |
+ DnsCache dns_cache_; |
+ |
+ scoped_ptr<PendingDns> pending_dns_; |
+}; |
+ |
+ProxyResolverV8Tracing::ProxyResolverV8Tracing( |
+ HostResolver* host_resolver, |
+ ProxyResolverErrorObserver* error_observer, |
+ NetLog* net_log) |
+ : ProxyResolver(true /*expects_pac_bytes*/), |
+ host_resolver_(host_resolver), |
+ error_observer_(error_observer), |
+ net_log_(net_log) { |
+ DCHECK(host_resolver); |
+ // Start up the thread. |
+ thread_.reset(new base::Thread("Proxy resolver")); |
+ CHECK(thread_->Start()); |
+ |
+ v8_resolver_.reset(new ProxyResolverV8); |
+} |
+ |
+ProxyResolverV8Tracing::~ProxyResolverV8Tracing() { |
+ // Note, all requests should have been cancelled. |
+ DCHECK(!set_pac_script_job_); |
+ |
+ // Join the worker thread. |
+ // See http://crbug.com/69710. |
+ base::ThreadRestrictions::ScopedAllowIO allow_io; |
+ thread_.reset(); |
+} |
+ |
+int ProxyResolverV8Tracing::GetProxyForURL(const GURL& url, |
+ ProxyInfo* results, |
+ const CompletionCallback& callback, |
+ RequestHandle* request, |
+ const BoundNetLog& net_log) { |
+ DCHECK(CalledOnValidThread()); |
+ DCHECK(!callback.is_null()); |
+ |
+ scoped_refptr<Job> job = new Job(this); |
+ |
+ if (request) |
+ *request = job.get(); |
+ |
+ job->StartGetProxyForURL(url, results, net_log, callback); |
+ return ERR_IO_PENDING; |
+} |
+ |
+void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) { |
+ Job* job = reinterpret_cast<Job*>(request); |
+ job->Cancel(); |
+} |
+ |
+LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const { |
+ Job* job = reinterpret_cast<Job*>(request); |
+ return job->GetLoadState(); |
+} |
+ |
+void ProxyResolverV8Tracing::CancelSetPacScript() { |
+ set_pac_script_job_->Cancel(); |
+ set_pac_script_job_ = NULL; |
+} |
+ |
+void ProxyResolverV8Tracing::PurgeMemory() { |
+ thread_->message_loop()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&ProxyResolverV8::PurgeMemory, |
+ // The use of unretained is safe, since the worker thread |
+ // cannot outlive |this|. |
+ base::Unretained(v8_resolver_.get()))); |
+} |
+ |
+int ProxyResolverV8Tracing::SetPacScript( |
+ const scoped_refptr<ProxyResolverScriptData>& script_data, |
+ const CompletionCallback& callback) { |
+ DCHECK(CalledOnValidThread()); |
+ DCHECK(!callback.is_null()); |
+ |
+ set_pac_script_job_ = new Job(this); |
+ set_pac_script_job_->StartSetPacScript(script_data, callback); |
+ |
+ return ERR_IO_PENDING; |
+} |
+ |
+} // namespace net |