Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "net/proxy/proxy_resolver_v8_tracing.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/md5.h" | |
| 9 #include "base/message_loop_proxy.h" | |
| 10 #include "base/string_number_conversions.h" | |
| 11 #include "base/string_util.h" | |
| 12 #include "base/stringprintf.h" | |
| 13 #include "base/synchronization/cancellation_flag.h" | |
| 14 #include "base/synchronization/waitable_event.h" | |
| 15 #include "base/threading/thread.h" | |
| 16 #include "base/threading/thread_restrictions.h" | |
| 17 #include "base/values.h" | |
| 18 #include "net/base/address_list.h" | |
| 19 #include "net/base/host_resolver.h" | |
| 20 #include "net/base/net_errors.h" | |
| 21 #include "net/base/net_log.h" | |
| 22 #include "net/proxy/proxy_info.h" | |
| 23 #include "net/proxy/proxy_resolver_error_observer.h" | |
| 24 #include "net/proxy/proxy_resolver_v8.h" | |
| 25 | |
| 26 // The intent of this class is explained in the design document: | |
| 27 // https://docs.google.com/a/chromium.org/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPo MJdaBn9rKreAmGOdE/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
| |
| 28 // | |
| 29 // In a nutshell, PAC scripts are Javascript programs and may depend on | |
| 30 // network I/O, by calling functions like dnsResolve(). | |
| 31 // | |
| 32 // This is problematic since functions such as dnsResolve() will block the | |
| 33 // Javascript execution until the DNS result is availble, thereby stalling the | |
| 34 // PAC thread, which hurts the ability to process parallel proxy resolves. | |
| 35 // An obvious solution is to simply start more PAC threads, however this scales | |
| 36 // poorly, which hurts the ability to process parallel proxy resolves. | |
| 37 // | |
| 38 // The solution in ProxyResolverV8Tracing is to model PAC scripts as being | |
| 39 // deterministic, and depending only on the inputted URL. When the script | |
| 40 // issues a dnsResolve() for a yet unresolved hostname, the Javascript | |
| 41 // execution is "aborted", and then re-started once the DNS result is | |
| 42 // known. | |
| 43 namespace net { | |
| 44 | |
| 45 namespace { | |
| 46 | |
| 47 // Upper bound on how many *unique* DNS resolves a PAC script is allowed | |
| 48 // to make. This is a failsafe in case the logic to detect PAC scripts | |
| 49 // incompatible with the tracing optimization doesn't work. It is not | |
| 50 // expected for well behaved scripts to hit this. | |
| 51 const size_t kMaxUniqueResolveDnsPerExec = 20; | |
| 52 | |
| 53 // Approximate number of bytes to use for buffering alerts() and errors. | |
| 54 // This is a failsafe in case repeated executions of the script causes | |
| 55 // too much memory bloat. It is not expected for well behaved scripts to | |
| 56 // hit this. (In fact normal scripts should not even have alerts() or errors). | |
| 57 const size_t kMaxAlertsAndErrorsBytes = 2048; | |
| 58 | |
| 59 // Compares two MD5 digests for equality. The second digest is given as a | |
| 60 // non-finalized MD5Context. | |
| 61 bool MD5DigestEquals(const base::MD5Digest& digest1, | |
| 62 const base::MD5Context& digest2_context) { | |
| 63 // Finalize the context into a digest. (Make a copy first | |
| 64 // because MD5Final() mutates the context). | |
| 65 base::MD5Digest digest2; | |
| 66 base::MD5Context digest2_copy; | |
| 67 memcpy(&digest2_copy[0], &digest2_context[0], sizeof(base::MD5Context)); | |
| 68 base::MD5Final(&digest2, &digest2_copy); | |
| 69 | |
| 70 return memcmp(&digest1, &digest2, sizeof(base::MD5Digest)) == 0; | |
| 71 } | |
| 72 | |
| 73 // Returns event parameters for a PAC error message (line number + message). | |
| 74 base::Value* NetLogErrorCallback(int line_number, | |
| 75 const string16* message, | |
| 76 NetLog::LogLevel /* log_level */) { | |
| 77 base::DictionaryValue* dict = new base::DictionaryValue(); | |
| 78 dict->SetInteger("line_number", line_number); | |
| 79 dict->SetString("message", *message); | |
| 80 return dict; | |
| 81 } | |
| 82 | |
| 83 } // namespace | |
| 84 | |
| 85 // The Job class is responsible for executing GetProxyForURL() and | |
| 86 // SetPacScript(), since both of these operations share similar code. | |
| 87 // | |
| 88 // The DNS for these operations can operate in either synchronous or | |
| 89 // asynchronous mode. Synchronous mode is used as a fallback when the PAC script | |
| 90 // 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
| |
| 91 // | |
| 92 // Note that this class runs on both the origin thread and a worker | |
| 93 // thread. Most methods are expected to be used exclusively on one thread | |
| 94 // or the other. | |
| 95 // | |
| 96 // 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.
| |
| 97 // spawned it. Destruction might happen on either the origin thread or the | |
| 98 // worker thread. | |
| 99 class ProxyResolverV8Tracing::Job | |
| 100 : public base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>, | |
| 101 public ProxyResolverV8::JSBindings { | |
| 102 public: | |
| 103 // |parent| is non-owned, and references the ProxyResolverV8Tracing that | |
| 104 // spawned this Job. | |
| 105 explicit Job(ProxyResolverV8Tracing* parent) | |
| 106 : origin_loop_(base::MessageLoopProxy::current()), | |
| 107 parent_(parent), | |
| 108 event_(true, false) { | |
| 109 } | |
| 110 | |
| 111 void StartSetPacScript( | |
| 112 const scoped_refptr<ProxyResolverScriptData>& script_data, | |
| 113 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.
| |
| 114 CheckIsOnOriginThread(); | |
| 115 | |
| 116 operation_ = SET_PAC_SCRIPT; | |
| 117 // Script initialization uses a synchronous DNS since there isn't any | |
| 118 // advantage to using asynchronous mode here. That is because the | |
| 119 // parent ProxyService can't submit any ProxyResolve requests until | |
| 120 // 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
| |
| 121 nonblocking_dns_ = false; | |
| 122 | |
| 123 script_data_ = script_data; | |
| 124 callback_ = callback; | |
| 125 | |
| 126 PostStart(); | |
| 127 } | |
| 128 | |
| 129 void StartGetProxyForURL(const GURL& url, | |
| 130 ProxyInfo* results, | |
| 131 const BoundNetLog& net_log, | |
| 132 const CompletionCallback& callback) { | |
| 133 CheckIsOnOriginThread(); | |
| 134 | |
| 135 operation_ = GET_PROXY_FOR_URL; | |
| 136 nonblocking_dns_ = true; | |
| 137 | |
| 138 url_ = url; | |
| 139 user_results_ = results; | |
| 140 bound_net_log_ = net_log; | |
| 141 callback_ = callback; | |
| 142 | |
| 143 last_num_dns_ = 0; | |
| 144 | |
| 145 PostStart(); | |
| 146 } | |
| 147 | |
| 148 void Cancel() { | |
| 149 CheckIsOnOriginThread(); | |
| 150 | |
| 151 // There are three main possibility to consider for cancellation: | |
| 152 // (a) The job has been posted to the worker thread, but not yet started | |
| 153 // (b) The script is executing on the worker thread | |
| 154 // (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.
| |
| 155 // | |
| 156 // |cancelled_| is read on both the origin thread and worker thread. The | |
| 157 // code that runs on the worker thread is littered with checks on | |
| 158 // |cancelled_| to break out early. Breaking out early is not necessary | |
| 159 // 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
| |
| 160 // | |
| 161 // The important check on |cancelled_| is done from the origin thread in | |
| 162 // NotifyCallerOnOriginLoop(). | |
| 163 cancelled_.Set(); | |
| 164 | |
| 165 // These two assignments are not necessary for correctness. Just defensive | |
| 166 // programming to make sure the user-provided data is not used beyond this | |
| 167 // point. | |
| 168 user_results_ = NULL; | |
| 169 callback_ = CompletionCallback(); | |
| 170 | |
| 171 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.
| |
| 172 host_resolver()->CancelRequest(pending_dns_->handle); | |
| 173 pending_dns_.reset(); | |
| 174 } | |
| 175 | |
| 176 // The worker thread might be blocked waiting for DNS. | |
| 177 event_.Signal(); | |
| 178 | |
| 179 Release(); | |
| 180 } | |
| 181 | |
| 182 LoadState GetLoadState() const { | |
| 183 CheckIsOnOriginThread(); | |
| 184 | |
| 185 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.
| |
| 186 return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT; | |
| 187 | |
| 188 return LOAD_STATE_RESOLVING_PROXY_FOR_URL; | |
| 189 } | |
| 190 | |
| 191 private: | |
| 192 friend class base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>; | |
| 193 | |
| 194 enum Operation { | |
| 195 SET_PAC_SCRIPT, | |
| 196 GET_PROXY_FOR_URL, | |
| 197 }; | |
| 198 | |
| 199 struct AlertOrError { | |
| 200 AlertOrError(bool is_alert, int line_number, const string16& message) | |
| 201 : is_alert(is_alert), line_number(line_number), message(message) {} | |
| 202 | |
| 203 bool is_alert; | |
| 204 int line_number; | |
| 205 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
| |
| 206 }; | |
| 207 | |
| 208 struct ExecutionState { | |
| 209 bool was_abandoned; | |
| 210 | |
| 211 // The number of times ResolveDns() was called so far. | |
| 212 int num_dns; | |
| 213 base::MD5Context dns_digest; | |
| 214 | |
| 215 size_t alerts_and_errors_bytes_; | |
| 216 std::vector<AlertOrError> alerts_and_errors_; | |
| 217 }; | |
| 218 | |
| 219 struct PendingDns { | |
| 220 PendingDns(const std::string& host, ResolveDnsOperation op) | |
| 221 : host(host), op(op), handle(NULL) {} | |
| 222 | |
| 223 std::string host; | |
| 224 ResolveDnsOperation op; | |
| 225 | |
| 226 HostResolver::RequestHandle handle; | |
| 227 net::AddressList addresses; | |
| 228 }; | |
| 229 | |
| 230 ~Job() { | |
| 231 DCHECK(!pending_dns_.get()); | |
| 232 } | |
| 233 | |
| 234 void CheckIsOnWorkerThread() { | |
| 235 DCHECK_EQ(MessageLoop::current(), parent_->thread_->message_loop()); | |
| 236 } | |
| 237 | |
| 238 void CheckIsOnOriginThread() const { | |
| 239 DCHECK(origin_loop_->BelongsToCurrentThread()); | |
| 240 } | |
| 241 | |
| 242 ProxyResolverV8* v8_resolver() { | |
| 243 return parent_->v8_resolver_.get(); | |
| 244 } | |
| 245 | |
| 246 MessageLoop* worker_loop() { | |
| 247 return parent_->thread_->message_loop(); | |
| 248 } | |
| 249 | |
| 250 HostResolver* host_resolver() { | |
| 251 return parent_->host_resolver_; | |
| 252 } | |
| 253 | |
| 254 ProxyResolverErrorObserver* error_observer() { | |
| 255 return parent_->error_observer_.get(); | |
| 256 } | |
| 257 | |
| 258 NetLog* net_log() { | |
| 259 return parent_->net_log_; | |
| 260 } | |
| 261 | |
| 262 void PostStart() { | |
| 263 AddRef(); // Balanced on completion, or cancellation. | |
| 264 worker_loop()->PostTask(FROM_HERE, base::Bind(&Job::Start, this)); | |
| 265 } | |
| 266 | |
| 267 // Post a task to the origin thread, to invoke the user's callback. | |
| 268 void NotifyCaller(int result) { | |
| 269 CheckIsOnWorkerThread(); | |
| 270 | |
| 271 origin_loop_->PostTask( | |
| 272 FROM_HERE, | |
| 273 base::Bind(&Job::NotifyCallerOnOriginLoop, this, result)); | |
| 274 } | |
| 275 | |
| 276 void NotifyCallerOnOriginLoop(int result) { | |
| 277 CheckIsOnOriginThread(); | |
| 278 | |
| 279 if (cancelled_.IsSet()) | |
| 280 return; | |
| 281 | |
| 282 if (operation_ == GET_PROXY_FOR_URL) | |
| 283 *user_results_ = results_; | |
| 284 | |
| 285 // There is only ever 1 outstanding SET_PAC_SCRIPT job. It needs to be | |
| 286 // tracked to support cancellation. | |
| 287 if (operation_ == SET_PAC_SCRIPT) { | |
| 288 DCHECK(parent_->set_pac_script_job_); | |
| 289 parent_->set_pac_script_job_ = NULL; | |
| 290 } | |
| 291 | |
| 292 callback_.Run(result); | |
| 293 callback_.Reset(); | |
| 294 | |
| 295 Release(); | |
| 296 } | |
| 297 | |
| 298 void Start() { | |
| 299 CheckIsOnWorkerThread(); | |
| 300 | |
| 301 if (cancelled_.IsSet()) | |
| 302 return; | |
| 303 | |
| 304 if (nonblocking_dns_) { | |
| 305 // Reset state pertaining to the current playthrough. | |
| 306 exec_.was_abandoned = false; | |
| 307 exec_.num_dns = 0; | |
| 308 base::MD5Init(&exec_.dns_digest); | |
| 309 exec_.alerts_and_errors_.clear(); | |
| 310 exec_.alerts_and_errors_bytes_ = 0; | |
| 311 } | |
| 312 | |
| 313 JSBindings* prev_bindings = v8_resolver()->js_bindings(); | |
| 314 v8_resolver()->set_js_bindings(this); | |
| 315 | |
| 316 int result = ERR_UNEXPECTED; // Initialized to silence warnings. | |
| 317 | |
| 318 switch (operation_) { | |
| 319 case SET_PAC_SCRIPT: | |
| 320 result = v8_resolver()->SetPacScript( | |
| 321 script_data_, CompletionCallback()); | |
| 322 break; | |
| 323 case GET_PROXY_FOR_URL: | |
| 324 result = v8_resolver()->GetProxyForURL( | |
| 325 url_, | |
| 326 // Important: Do not write directly into |user_results_|, since if the | |
| 327 // request were to be cancelled from the origin thread, must guarantee | |
| 328 // that |user_results| is not accessed anymore must guarantee that | |
| 329 // |user_results| is not accessed anymore. | |
| 330 &results_, | |
| 331 CompletionCallback(), | |
| 332 NULL, | |
| 333 bound_net_log_); | |
| 334 break; | |
| 335 } | |
| 336 | |
| 337 v8_resolver()->set_js_bindings(prev_bindings); | |
| 338 | |
| 339 if (nonblocking_dns_) { | |
| 340 if (!exec_.was_abandoned) { | |
| 341 // Some alerts() and errors may have been buffered, since it was not | |
| 342 // known at the time whether this run would be discarded. Now that it is | |
| 343 // known to be a full run, dispatch those buffered events. | |
| 344 DispatchBufferedAlertsAndErrors(); | |
| 345 NotifyCaller(result); | |
| 346 } | |
| 347 } else { | |
| 348 NotifyCaller(result); | |
| 349 } | |
| 350 } | |
| 351 | |
| 352 // Handler for "alert(message)" | |
| 353 virtual void Alert(const string16& message) OVERRIDE { | |
| 354 HandleAlertOrError(true, -1, message); | |
| 355 } | |
| 356 | |
| 357 // Handler for when an error is encountered. |line_number| may be -1 | |
| 358 // if a line number is not applicable to this error. | |
| 359 virtual void OnError(int line_number, const string16& error) OVERRIDE { | |
| 360 HandleAlertOrError(false, line_number, error); | |
| 361 } | |
| 362 | |
| 363 void HandleAlertOrError(bool is_alert, int line_number, | |
| 364 const string16& message) { | |
| 365 if (cancelled_.IsSet()) | |
| 366 return; | |
| 367 | |
| 368 if (!nonblocking_dns_) { | |
| 369 // In blocking DNS mode the events can be dispatched immediately. | |
| 370 DispatchAlertOrError(is_alert, line_number, message); | |
| 371 return; | |
| 372 } | |
| 373 | |
| 374 // Otherwise in nonblocking mode, buffer all the messages until | |
| 375 // the end. | |
| 376 | |
| 377 if (exec_.was_abandoned) | |
| 378 return; | |
| 379 | |
| 380 exec_.alerts_and_errors_bytes_ += message.size() * 2; | |
| 381 | |
| 382 // If there have been lots of messages, enqueuing could be expensive on | |
| 383 // memory. (consider a script which does megabytes worth of alerts()). | |
| 384 // Avoid this by falling back to blocking mode. | |
| 385 if (exec_.alerts_and_errors_bytes_ > kMaxAlertsAndErrorsBytes) { | |
| 386 ScheduleRestartWithBlockingDns(); | |
| 387 exec_.was_abandoned = true; | |
| 388 return; | |
| 389 } | |
| 390 | |
| 391 exec_.alerts_and_errors_.push_back( | |
| 392 AlertOrError(is_alert, line_number, message)); | |
| 393 } | |
| 394 | |
| 395 void DispatchBufferedAlertsAndErrors() { | |
| 396 DCHECK(nonblocking_dns_); | |
| 397 DCHECK(!exec_.was_abandoned); | |
| 398 | |
| 399 for (size_t i = 0; i < exec_.alerts_and_errors_.size(); ++i) { | |
| 400 const AlertOrError& x = exec_.alerts_and_errors_[i]; | |
| 401 DispatchAlertOrError(x.is_alert, x.line_number, x.message); | |
| 402 } | |
| 403 } | |
| 404 | |
| 405 void DispatchAlertOrError(bool is_alert, int line_number, | |
| 406 const string16& message) { | |
| 407 // Note that the handling of cancellation is racy with regard to | |
| 408 // alerts/errors. The request might get cancelled shortly after this | |
| 409 // check! (There is no lock being held to guarantee otherwise). | |
| 410 // | |
| 411 // If this happens, then some information will get written to the NetLog | |
| 412 // needlessly, however the NetLog will still be alive so it shouldn't cause | |
| 413 // problems. | |
| 414 if (cancelled_.IsSet()) | |
| 415 return; | |
| 416 | |
| 417 if (is_alert) { | |
| 418 // ------------------- | |
| 419 // alert | |
| 420 // ------------------- | |
| 421 VLOG(1) << "PAC-alert: " << message; | |
| 422 | |
| 423 // Send to the NetLog. | |
| 424 LogEventToCurrentRequestAndGlobally( | |
| 425 NetLog::TYPE_PAC_JAVASCRIPT_ALERT, | |
| 426 NetLog::StringCallback("message", &message)); | |
| 427 } else { | |
| 428 // ------------------- | |
| 429 // error | |
| 430 // ------------------- | |
| 431 if (line_number == -1) | |
| 432 VLOG(1) << "PAC-error: " << message; | |
| 433 else | |
| 434 VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message; | |
| 435 | |
| 436 // Send the error to the NetLog. | |
| 437 LogEventToCurrentRequestAndGlobally( | |
| 438 NetLog::TYPE_PAC_JAVASCRIPT_ERROR, | |
| 439 base::Bind(&NetLogErrorCallback, line_number, &message)); | |
| 440 | |
| 441 if (error_observer()) | |
| 442 error_observer()->OnPACScriptError(line_number, message); | |
| 443 } | |
| 444 } | |
| 445 | |
| 446 void LogEventToCurrentRequestAndGlobally( | |
|
mmenke
2013/01/18 19:59:40
nit: Fix indent.
eroman
2013/01/23 03:26:02
Done.
| |
| 447 NetLog::EventType type, | |
| 448 const NetLog::ParametersCallback& parameters_callback) { | |
| 449 bound_net_log_.AddEvent(type, parameters_callback); | |
| 450 | |
| 451 // Emit to the global NetLog event stream. | |
| 452 if (net_log()) | |
| 453 net_log()->AddGlobalEntry(type, parameters_callback); | |
| 454 } | |
| 455 | |
| 456 virtual bool ResolveDns(const std::string& host, | |
| 457 ResolveDnsOperation op, | |
| 458 std::string* output) { | |
| 459 if (cancelled_.IsSet()) | |
| 460 return false; | |
| 461 | |
| 462 if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) { | |
| 463 // a DNS resolve with an empty hostname is considered an error. | |
| 464 return false; | |
| 465 } | |
| 466 | |
| 467 return nonblocking_dns_ ? | |
| 468 ResolveDnsNonBlocking(host, op, output) : | |
| 469 ResolveDnsBlocking(host, op, output); | |
|
mmenke
2013/01/18 19:59:40
nit: 4 space indent.
eroman
2013/01/23 03:26:02
Done.
| |
| 470 } | |
| 471 | |
| 472 bool ResolveDnsBlocking(const std::string& host, | |
| 473 ResolveDnsOperation op, | |
| 474 std::string* output) { | |
| 475 CheckIsOnWorkerThread(); | |
| 476 | |
| 477 // Check if the DNS result for this host has already been cached. | |
| 478 bool rv; | |
| 479 if (GetDnsFromLocalCache(host, op, output, &rv)) { | |
| 480 // Yay, cache hit! | |
| 481 return rv; | |
| 482 } | |
| 483 | |
| 484 // If the host was not in the local cache, this is a new hostname. | |
| 485 | |
| 486 if (dns_cache_.size() > kMaxUniqueResolveDnsPerExec) { | |
| 487 // Safety net for scripts with unexpectedly many DNS calls. | |
| 488 // We will continue running to completion, but will fail every | |
| 489 // subsequent DNS request. | |
| 490 return false; | |
| 491 } | |
| 492 | |
| 493 bool unused; | |
| 494 origin_loop_->PostTask( | |
| 495 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op, &unused)); | |
| 496 | |
| 497 // Wait for the DNS operation to be completed by the host resolver on the | |
| 498 // origin thread. After waking up, either the request was cancelled, or | |
| 499 // the DNS result is now available in the cache. | |
| 500 event_.Wait(); | |
| 501 event_.Reset(); | |
| 502 | |
| 503 if (cancelled_.IsSet()) | |
| 504 return false; | |
| 505 | |
| 506 CHECK(GetDnsFromLocalCache(host, op, output, &rv)); | |
| 507 return rv; | |
| 508 } | |
| 509 | |
| 510 // To detect inconsistencies, the past sequence of ResovleDns operations are | |
| 511 // saved and enforced during each replay. Instead of saving the full sequence | |
| 512 // of hostnames, just an MD5 checksum is kept (to avoid unbounded memory | |
| 513 // growth). | |
| 514 bool ResolveDnsNonBlocking(const std::string& host, | |
| 515 ResolveDnsOperation op, | |
| 516 std::string* output) { | |
| 517 CheckIsOnWorkerThread(); | |
| 518 | |
| 519 if (exec_.was_abandoned) { | |
| 520 // 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.
| |
| 521 // dependency will be traced at a time (for more predictable outcomes). | |
| 522 return false; | |
| 523 } | |
| 524 | |
| 525 exec_.num_dns += 1; | |
| 526 | |
| 527 // Keep a digest of the sequence of DNS requests this execution has seen so | |
| 528 // far. This is cheaper than keeping a full list of the history, and good | |
| 529 // enough for our purposes (we want to detect when things have gone wrong in | |
| 530 // order to fallback to synchronous mode). | |
| 531 base::MD5Update(&exec_.dns_digest, host); | |
| 532 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
| |
| 533 | |
| 534 // Upon reaching the point of execution from the last run, make sure | |
| 535 // that the sequence of resolves has matched so far. | |
| 536 if (exec_.num_dns == last_num_dns_ && | |
| 537 !MD5DigestEquals(last_dns_digest_, exec_.dns_digest)) { | |
| 538 // The sequence of DNS operations is different from last time! | |
| 539 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
| |
| 540 exec_.was_abandoned = true; | |
| 541 return false; | |
| 542 } | |
| 543 | |
| 544 // Check if the DNS result for this host has already been cached. | |
| 545 bool rv; | |
| 546 if (GetDnsFromLocalCache(host, op, output, &rv)) { | |
| 547 // Yay, cache hit! | |
| 548 return rv; | |
| 549 } | |
| 550 | |
| 551 // If the host was not in the local cache, then this is a new hostname. | |
| 552 | |
| 553 if (exec_.num_dns <= last_num_dns_) { | |
| 554 // The sequence of DNS operations is different from last time! | |
| 555 ScheduleRestartWithBlockingDns(); | |
| 556 exec_.was_abandoned = true; | |
| 557 return false; | |
| 558 } | |
| 559 | |
| 560 if (dns_cache_.size() > kMaxUniqueResolveDnsPerExec) { | |
| 561 // Safety net for scripts with unexpectedly many DNS calls. | |
| 562 return false; | |
| 563 } | |
| 564 | |
| 565 // Post the DNS request to the origin thread. | |
| 566 bool resolver_cache_hit = false; | |
| 567 origin_loop_->PostTask( | |
| 568 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op, | |
| 569 &resolver_cache_hit)); | |
| 570 | |
| 571 // As an optimization to avoid restarting too often, wait until the | |
| 572 // resolver's cache has been inspected on the origin thread. | |
| 573 event_.Wait(); | |
| 574 event_.Reset(); | |
| 575 | |
| 576 if (cancelled_.IsSet()) | |
| 577 return false; | |
| 578 | |
| 579 if (resolver_cache_hit) { | |
| 580 CHECK(GetDnsFromLocalCache(host, op, output, &rv)); | |
| 581 return rv; | |
| 582 } | |
| 583 | |
| 584 // Otherwise if the result was not in the cache, then a DNS request has | |
| 585 // been started. Abandon this invocation of FindProxyForURL(), it will be | |
| 586 // restarted once the DNS request completes. | |
| 587 exec_.was_abandoned = true; | |
| 588 last_num_dns_ = exec_.num_dns; | |
| 589 base::MD5Final(&last_dns_digest_, &exec_.dns_digest); | |
| 590 return false; | |
| 591 } | |
| 592 | |
| 593 // Builds a RequestInfo to service the specified PAC DNS operation. | |
| 594 static HostResolver::RequestInfo MakeRequestInfo(const std::string& host, | |
| 595 ResolveDnsOperation op) { | |
| 596 HostPortPair host_port = HostPortPair(host, 80); | |
| 597 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) { | |
| 598 host_port.set_host(GetHostName()); | |
| 599 } | |
| 600 | |
| 601 HostResolver::RequestInfo info(host_port); | |
| 602 | |
| 603 // The non-ex flavors are limited to IPv4 results. | |
| 604 if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) { | |
| 605 info.set_address_family(ADDRESS_FAMILY_IPV4); | |
| 606 } | |
| 607 | |
| 608 return info; | |
| 609 } | |
| 610 | |
| 611 void DoDnsOperation(const std::string& host, ResolveDnsOperation op, | |
| 612 bool* out_cache_hit) { | |
| 613 CheckIsOnOriginThread(); | |
| 614 | |
| 615 if (cancelled_.IsSet()) | |
| 616 return; | |
| 617 | |
| 618 DCHECK(!pending_dns_.get()); | |
| 619 | |
| 620 HostResolver::RequestInfo info = MakeRequestInfo(host, op); | |
| 621 | |
| 622 if (nonblocking_dns_) { | |
| 623 // Check if the DNS result can be serviced directly from the cache. | |
| 624 // (The worker thread is blocked waiting for this information). | |
| 625 net::AddressList addresses; | |
| 626 int result = host_resolver()->ResolveFromCache( | |
| 627 info, &addresses, bound_net_log_); | |
| 628 | |
| 629 bool cache_hit = result != ERR_DNS_CACHE_MISS; | |
| 630 if (cache_hit) { | |
| 631 // Cool, it was in the host resolver's cache. Copy it to the | |
| 632 // per-request cache. | |
| 633 SaveDnsToLocalCache(host, op, result, addresses); | |
| 634 } | |
| 635 | |
| 636 // Important: Do not read/write |out_cache_hit| after signalling, since | |
| 637 // the memory may no longer be valid. | |
| 638 *out_cache_hit = cache_hit; | |
| 639 event_.Signal(); | |
| 640 | |
| 641 if (cache_hit) | |
| 642 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
| |
| 643 } | |
| 644 | |
| 645 pending_dns_.reset(new PendingDns(host, op)); | |
| 646 | |
| 647 int result = host_resolver()->Resolve( | |
| 648 info, | |
| 649 &pending_dns_->addresses, base::Bind(&Job::OnResolveCompletion, this), | |
| 650 &pending_dns_->handle, bound_net_log_); | |
| 651 | |
| 652 if (result != ERR_IO_PENDING) | |
| 653 OnResolveCompletion(result); | |
| 654 } | |
| 655 | |
| 656 void OnResolveCompletion(int result) { | |
| 657 CheckIsOnOriginThread(); | |
| 658 | |
| 659 DCHECK(!cancelled_.IsSet()); | |
| 660 | |
| 661 SaveDnsToLocalCache(pending_dns_->host, pending_dns_->op, result, | |
| 662 pending_dns_->addresses); | |
| 663 pending_dns_.reset(); | |
| 664 | |
| 665 if (nonblocking_dns_) { | |
| 666 // Restart. This time it should make more progress due to having | |
| 667 // cached items. | |
| 668 worker_loop()->PostTask(FROM_HERE, base::Bind(&Job::Start, this)); | |
| 669 } else { | |
| 670 // Otherwise wakeup the blocked worker thread. | |
| 671 event_.Signal(); | |
| 672 } | |
| 673 } | |
| 674 | |
| 675 void ScheduleRestartWithBlockingDns() { | |
| 676 MessageLoop::current()->PostTask( | |
| 677 FROM_HERE, base::Bind(&Job::RestartWithBlockingDns, this)); | |
| 678 } | |
| 679 | |
| 680 void RestartWithBlockingDns() { | |
| 681 DCHECK(nonblocking_dns_); | |
| 682 nonblocking_dns_ = false; | |
| 683 Start(); | |
| 684 } | |
| 685 | |
| 686 // Make a key for looking up |host, op| in |dns_cache_|. Strings are used for | |
| 687 // convenience, to avoid defining custom comparators. | |
| 688 static std::string GetCacheKey(const std::string& host, | |
| 689 ResolveDnsOperation op) { | |
| 690 return StringPrintf("%d:%s", op, host.c_str()); | |
| 691 } | |
| 692 | |
| 693 // Serialize |net_error, address_list| into something that can be saved into | |
| 694 // |dns_cache|. | |
| 695 static std::string MakeCacheValue(ResolveDnsOperation op, | |
| 696 int net_error, | |
| 697 const AddressList& address_list) { | |
| 698 if (net_error != OK) | |
| 699 return std::string(); | |
| 700 | |
| 701 // dnsResolve() and myIpAddress() are expected to return a single IP | |
| 702 // address. Whereas the *Ex versions are expected to return a semi-colon | |
| 703 // separated list. | |
| 704 | |
| 705 if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) { | |
| 706 return address_list.front().ToStringWithoutPort(); | |
| 707 } | |
| 708 | |
| 709 std::string address_list_str; | |
| 710 for (AddressList::const_iterator iter = address_list.begin(); | |
| 711 iter != address_list.end(); ++iter) { | |
| 712 if (!address_list_str.empty()) | |
| 713 address_list_str += ";"; | |
| 714 const std::string address_string = iter->ToStringWithoutPort(); | |
| 715 if (address_string.empty()) | |
| 716 return std::string(); | |
| 717 address_list_str += address_string; | |
| 718 } | |
| 719 | |
| 720 return address_list_str; | |
| 721 } | |
| 722 | |
| 723 bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op, | |
| 724 std::string* output, bool* return_value) { | |
| 725 DnsCache::const_iterator it = dns_cache_.find(GetCacheKey(host, op)); | |
| 726 if (it == dns_cache_.end()) | |
| 727 return false; | |
| 728 | |
| 729 *output = it->second; | |
| 730 *return_value = !it->second.empty(); | |
| 731 return true; | |
| 732 } | |
| 733 | |
| 734 void SaveDnsToLocalCache(const std::string& host, ResolveDnsOperation op, | |
| 735 int net_error, const net::AddressList& addresses) { | |
| 736 dns_cache_[GetCacheKey(host, op)] = | |
| 737 MakeCacheValue(op, net_error, addresses); | |
| 738 } | |
| 739 | |
| 740 Operation operation_; | |
| 741 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.
| |
| 742 | |
| 743 scoped_refptr<base::MessageLoopProxy> origin_loop_; | |
| 744 CompletionCallback callback_; | |
| 745 ProxyResolverV8Tracing* parent_; | |
| 746 base::CancellationFlag cancelled_; | |
| 747 | |
| 748 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
| |
| 749 | |
| 750 base::WaitableEvent event_; | |
| 751 | |
| 752 GURL url_; | |
| 753 ProxyInfo results_; | |
| 754 ProxyInfo* user_results_; | |
| 755 BoundNetLog bound_net_log_; | |
| 756 | |
| 757 ExecutionState exec_; | |
| 758 | |
| 759 int last_num_dns_; | |
| 760 base::MD5Digest last_dns_digest_; | |
| 761 | |
| 762 typedef std::map<std::string, std::string> DnsCache; | |
| 763 DnsCache dns_cache_; | |
| 764 | |
| 765 scoped_ptr<PendingDns> pending_dns_; | |
| 766 }; | |
| 767 | |
| 768 ProxyResolverV8Tracing::ProxyResolverV8Tracing( | |
| 769 HostResolver* host_resolver, | |
| 770 ProxyResolverErrorObserver* error_observer, | |
| 771 NetLog* net_log) | |
| 772 : ProxyResolver(true /*expects_pac_bytes*/), | |
| 773 host_resolver_(host_resolver), | |
| 774 error_observer_(error_observer), | |
| 775 net_log_(net_log) { | |
| 776 DCHECK(host_resolver); | |
| 777 // Start up the thread. | |
| 778 thread_.reset(new base::Thread("Proxy resolver")); | |
| 779 CHECK(thread_->Start()); | |
| 780 | |
| 781 v8_resolver_.reset(new ProxyResolverV8); | |
| 782 } | |
| 783 | |
| 784 ProxyResolverV8Tracing::~ProxyResolverV8Tracing() { | |
| 785 // Note, all requests should have been cancelled. | |
| 786 DCHECK(!set_pac_script_job_); | |
| 787 | |
| 788 // Join the worker thread. | |
| 789 // See http://crbug.com/69710. | |
| 790 base::ThreadRestrictions::ScopedAllowIO allow_io; | |
| 791 thread_.reset(); | |
| 792 } | |
| 793 | |
| 794 int ProxyResolverV8Tracing::GetProxyForURL(const GURL& url, | |
| 795 ProxyInfo* results, | |
| 796 const CompletionCallback& callback, | |
| 797 RequestHandle* request, | |
| 798 const BoundNetLog& net_log) { | |
| 799 DCHECK(CalledOnValidThread()); | |
| 800 DCHECK(!callback.is_null()); | |
| 801 | |
| 802 scoped_refptr<Job> job = new Job(this); | |
| 803 | |
| 804 if (request) | |
| 805 *request = job.get(); | |
| 806 | |
| 807 job->StartGetProxyForURL(url, results, net_log, callback); | |
| 808 return ERR_IO_PENDING; | |
| 809 } | |
| 810 | |
| 811 void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) { | |
| 812 Job* job = reinterpret_cast<Job*>(request); | |
| 813 job->Cancel(); | |
| 814 } | |
| 815 | |
| 816 LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const { | |
| 817 Job* job = reinterpret_cast<Job*>(request); | |
| 818 return job->GetLoadState(); | |
| 819 } | |
| 820 | |
| 821 void ProxyResolverV8Tracing::CancelSetPacScript() { | |
| 822 set_pac_script_job_->Cancel(); | |
| 823 set_pac_script_job_ = NULL; | |
| 824 } | |
| 825 | |
| 826 void ProxyResolverV8Tracing::PurgeMemory() { | |
| 827 thread_->message_loop()->PostTask( | |
| 828 FROM_HERE, | |
| 829 base::Bind(&ProxyResolverV8::PurgeMemory, | |
| 830 // The use of unretained is safe, since the worker thread | |
| 831 // cannot outlive |this|. | |
| 832 base::Unretained(v8_resolver_.get()))); | |
| 833 } | |
| 834 | |
| 835 int ProxyResolverV8Tracing::SetPacScript( | |
| 836 const scoped_refptr<ProxyResolverScriptData>& script_data, | |
| 837 const CompletionCallback& callback) { | |
| 838 DCHECK(CalledOnValidThread()); | |
| 839 DCHECK(!callback.is_null()); | |
| 840 | |
| 841 set_pac_script_job_ = new Job(this); | |
| 842 set_pac_script_job_->StartSetPacScript(script_data, callback); | |
| 843 | |
| 844 return ERR_IO_PENDING; | |
| 845 } | |
| 846 | |
| 847 } // namespace net | |
| OLD | NEW |