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/message_loop/message_loop_proxy.h" 9 #include "base/strings/stringprintf.h" 10 #include "base/synchronization/cancellation_flag.h" 11 #include "base/synchronization/waitable_event.h" 12 #include "base/threading/thread.h" 13 #include "base/threading/thread_restrictions.h" 14 #include "base/values.h" 15 #include "net/base/address_list.h" 16 #include "net/base/net_errors.h" 17 #include "net/base/net_log.h" 18 #include "net/dns/host_resolver.h" 19 #include "net/proxy/proxy_info.h" 20 #include "net/proxy/proxy_resolver_error_observer.h" 21 #include "net/proxy/proxy_resolver_v8.h" 22 23 // The intent of this class is explained in the design document: 24 // https://docs.google.com/a/chromium.org/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPoMJdaBn9rKreAmGOdE/edit 25 // 26 // In a nutshell, PAC scripts are Javascript programs and may depend on 27 // network I/O, by calling functions like dnsResolve(). 28 // 29 // This is problematic since functions such as dnsResolve() will block the 30 // Javascript execution until the DNS result is availble, thereby stalling the 31 // PAC thread, which hurts the ability to process parallel proxy resolves. 32 // An obvious solution is to simply start more PAC threads, however this scales 33 // poorly, which hurts the ability to process parallel proxy resolves. 34 // 35 // The solution in ProxyResolverV8Tracing is to model PAC scripts as being 36 // deterministic, and depending only on the inputted URL. When the script 37 // issues a dnsResolve() for a yet unresolved hostname, the Javascript 38 // execution is "aborted", and then re-started once the DNS result is 39 // known. 40 namespace net { 41 42 namespace { 43 44 // Upper bound on how many *unique* DNS resolves a PAC script is allowed 45 // to make. This is a failsafe both for scripts that do a ridiculous 46 // number of DNS resolves, as well as scripts which are misbehaving 47 // under the tracing optimization. It is not expected to hit this normally. 48 const size_t kMaxUniqueResolveDnsPerExec = 20; 49 50 // Approximate number of bytes to use for buffering alerts() and errors. 51 // This is a failsafe in case repeated executions of the script causes 52 // too much memory bloat. It is not expected for well behaved scripts to 53 // hit this. (In fact normal scripts should not even have alerts() or errors). 54 const size_t kMaxAlertsAndErrorsBytes = 2048; 55 56 // Returns event parameters for a PAC error message (line number + message). 57 base::Value* NetLogErrorCallback(int line_number, 58 const base::string16* message, 59 NetLog::LogLevel /* log_level */) { 60 base::DictionaryValue* dict = new base::DictionaryValue(); 61 dict->SetInteger("line_number", line_number); 62 dict->SetString("message", *message); 63 return dict; 64 } 65 66 } // namespace 67 68 // The Job class is responsible for executing GetProxyForURL() and 69 // SetPacScript(), since both of these operations share similar code. 70 // 71 // The DNS for these operations can operate in either blocking or 72 // non-blocking mode. Blocking mode is used as a fallback when the PAC script 73 // seems to be misbehaving under the tracing optimization. 74 // 75 // Note that this class runs on both the origin thread and a worker 76 // thread. Most methods are expected to be used exclusively on one thread 77 // or the other. 78 // 79 // The lifetime of Jobs does not exceed that of the ProxyResolverV8Tracing that 80 // spawned it. Destruction might happen on either the origin thread or the 81 // worker thread. 82 class ProxyResolverV8Tracing::Job 83 : public base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>, 84 public ProxyResolverV8::JSBindings { 85 public: 86 // |parent| is non-owned. It is the ProxyResolverV8Tracing that spawned this 87 // Job, and must oulive it. 88 explicit Job(ProxyResolverV8Tracing* parent); 89 90 // Called from origin thread. 91 void StartSetPacScript( 92 const scoped_refptr<ProxyResolverScriptData>& script_data, 93 const CompletionCallback& callback); 94 95 // Called from origin thread. 96 void StartGetProxyForURL(const GURL& url, 97 ProxyInfo* results, 98 const BoundNetLog& net_log, 99 const CompletionCallback& callback); 100 101 // Called from origin thread. 102 void Cancel(); 103 104 // Called from origin thread. 105 LoadState GetLoadState() const; 106 107 private: 108 typedef std::map<std::string, std::string> DnsCache; 109 friend class base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>; 110 111 enum Operation { 112 SET_PAC_SCRIPT, 113 GET_PROXY_FOR_URL, 114 }; 115 116 struct AlertOrError { 117 bool is_alert; 118 int line_number; 119 base::string16 message; 120 }; 121 122 virtual ~Job(); 123 124 void CheckIsOnWorkerThread() const; 125 void CheckIsOnOriginThread() const; 126 127 void SetCallback(const CompletionCallback& callback); 128 void ReleaseCallback(); 129 130 ProxyResolverV8* v8_resolver(); 131 base::MessageLoop* worker_loop(); 132 HostResolver* host_resolver(); 133 ProxyResolverErrorObserver* error_observer(); 134 NetLog* net_log(); 135 136 // Invokes the user's callback. 137 void NotifyCaller(int result); 138 void NotifyCallerOnOriginLoop(int result); 139 140 void Start(Operation op, bool blocking_dns, 141 const CompletionCallback& callback); 142 143 void ExecuteBlocking(); 144 void ExecuteNonBlocking(); 145 int ExecuteProxyResolver(); 146 147 // Implementation of ProxyResolverv8::JSBindings 148 virtual bool ResolveDns(const std::string& host, 149 ResolveDnsOperation op, 150 std::string* output, 151 bool* terminate) OVERRIDE; 152 virtual void Alert(const base::string16& message) OVERRIDE; 153 virtual void OnError(int line_number, const base::string16& error) OVERRIDE; 154 155 bool ResolveDnsBlocking(const std::string& host, 156 ResolveDnsOperation op, 157 std::string* output); 158 159 bool ResolveDnsNonBlocking(const std::string& host, 160 ResolveDnsOperation op, 161 std::string* output, 162 bool* terminate); 163 164 bool PostDnsOperationAndWait(const std::string& host, 165 ResolveDnsOperation op, 166 bool* completed_synchronously) 167 WARN_UNUSED_RESULT; 168 169 void DoDnsOperation(); 170 void OnDnsOperationComplete(int result); 171 172 void ScheduleRestartWithBlockingDns(); 173 174 bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op, 175 std::string* output, bool* return_value); 176 177 void SaveDnsToLocalCache(const std::string& host, ResolveDnsOperation op, 178 int net_error, const net::AddressList& addresses); 179 180 // Builds a RequestInfo to service the specified PAC DNS operation. 181 static HostResolver::RequestInfo MakeDnsRequestInfo(const std::string& host, 182 ResolveDnsOperation op); 183 184 // Makes a key for looking up |host, op| in |dns_cache_|. Strings are used for 185 // convenience, to avoid defining custom comparators. 186 static std::string MakeDnsCacheKey(const std::string& host, 187 ResolveDnsOperation op); 188 189 void HandleAlertOrError(bool is_alert, int line_number, 190 const base::string16& message); 191 void DispatchBufferedAlertsAndErrors(); 192 void DispatchAlertOrError(bool is_alert, int line_number, 193 const base::string16& message); 194 195 void LogEventToCurrentRequestAndGlobally( 196 NetLog::EventType type, 197 const NetLog::ParametersCallback& parameters_callback); 198 199 // The thread which called into ProxyResolverV8Tracing, and on which the 200 // completion callback is expected to run. 201 scoped_refptr<base::MessageLoopProxy> origin_loop_; 202 203 // The ProxyResolverV8Tracing which spawned this Job. 204 // Initialized on origin thread and then accessed from both threads. 205 ProxyResolverV8Tracing* parent_; 206 207 // The callback to run (on the origin thread) when the Job finishes. 208 // Should only be accessed from origin thread. 209 CompletionCallback callback_; 210 211 // Flag to indicate whether the request has been cancelled. 212 base::CancellationFlag cancelled_; 213 214 // The operation that this Job is running. 215 // Initialized on origin thread and then accessed from both threads. 216 Operation operation_; 217 218 // The DNS mode for this Job. 219 // Initialized on origin thread, mutated on worker thread, and accessed 220 // by both the origin thread and worker thread. 221 bool blocking_dns_; 222 223 // Used to block the worker thread on a DNS operation taking place on the 224 // origin thread. 225 base::WaitableEvent event_; 226 227 // Map of DNS operations completed so far. Written into on the origin thread 228 // and read on the worker thread. 229 DnsCache dns_cache_; 230 231 // The job holds a reference to itself to ensure that it remains alive until 232 // either completion or cancellation. 233 scoped_refptr<Job> owned_self_reference_; 234 235 // ------------------------------------------------------- 236 // State specific to SET_PAC_SCRIPT. 237 // ------------------------------------------------------- 238 239 scoped_refptr<ProxyResolverScriptData> script_data_; 240 241 // ------------------------------------------------------- 242 // State specific to GET_PROXY_FOR_URL. 243 // ------------------------------------------------------- 244 245 ProxyInfo* user_results_; // Owned by caller, lives on origin thread. 246 GURL url_; 247 ProxyInfo results_; 248 BoundNetLog bound_net_log_; 249 250 // --------------------------------------------------------------------------- 251 // State for ExecuteNonBlocking() 252 // --------------------------------------------------------------------------- 253 // These variables are used exclusively on the worker thread and are only 254 // meaningful when executing inside of ExecuteNonBlocking(). 255 256 // Whether this execution was abandoned due to a missing DNS dependency. 257 bool abandoned_; 258 259 // Number of calls made to ResolveDns() by this execution. 260 int num_dns_; 261 262 // Sequence of calls made to Alert() or OnError() by this execution. 263 std::vector<AlertOrError> alerts_and_errors_; 264 size_t alerts_and_errors_byte_cost_; // Approximate byte cost of the above. 265 266 // Number of calls made to ResolveDns() by the PREVIOUS execution. 267 int last_num_dns_; 268 269 // Whether the current execution needs to be restarted in blocking mode. 270 bool should_restart_with_blocking_dns_; 271 272 // --------------------------------------------------------------------------- 273 // State for pending DNS request. 274 // --------------------------------------------------------------------------- 275 276 // Handle to the outstanding request in the HostResolver, or NULL. 277 // This is mutated and used on the origin thread, however it may be read by 278 // the worker thread for some DCHECKS(). 279 HostResolver::RequestHandle pending_dns_; 280 281 // Indicates if the outstanding DNS request completed synchronously. Written 282 // on the origin thread, and read by the worker thread. 283 bool pending_dns_completed_synchronously_; 284 285 // These are the inputs to DoDnsOperation(). Written on the worker thread, 286 // read by the origin thread. 287 std::string pending_dns_host_; 288 ResolveDnsOperation pending_dns_op_; 289 290 // This contains the resolved address list that DoDnsOperation() fills in. 291 // Used exclusively on the origin thread. 292 AddressList pending_dns_addresses_; 293 }; 294 295 ProxyResolverV8Tracing::Job::Job(ProxyResolverV8Tracing* parent) 296 : origin_loop_(base::MessageLoopProxy::current()), 297 parent_(parent), 298 event_(true, false), 299 last_num_dns_(0), 300 pending_dns_(NULL) { 301 CheckIsOnOriginThread(); 302 } 303 304 void ProxyResolverV8Tracing::Job::StartSetPacScript( 305 const scoped_refptr<ProxyResolverScriptData>& script_data, 306 const CompletionCallback& callback) { 307 CheckIsOnOriginThread(); 308 309 script_data_ = script_data; 310 311 // Script initialization uses blocking DNS since there isn't any 312 // advantage to using non-blocking mode here. That is because the 313 // parent ProxyService can't submit any ProxyResolve requests until 314 // initialization has completed successfully! 315 Start(SET_PAC_SCRIPT, true /*blocking*/, callback); 316 } 317 318 void ProxyResolverV8Tracing::Job::StartGetProxyForURL( 319 const GURL& url, 320 ProxyInfo* results, 321 const BoundNetLog& net_log, 322 const CompletionCallback& callback) { 323 CheckIsOnOriginThread(); 324 325 url_ = url; 326 user_results_ = results; 327 bound_net_log_ = net_log; 328 329 Start(GET_PROXY_FOR_URL, false /*non-blocking*/, callback); 330 } 331 332 void ProxyResolverV8Tracing::Job::Cancel() { 333 CheckIsOnOriginThread(); 334 335 // There are several possibilities to consider for cancellation: 336 // (a) The job has been posted to the worker thread, however script execution 337 // has not yet started. 338 // (b) The script is executing on the worker thread. 339 // (c) The script is executing on the worker thread, however is blocked inside 340 // of dnsResolve() waiting for a response from the origin thread. 341 // (d) Nothing is running on the worker thread, however the host resolver has 342 // a pending DNS request which upon completion will restart the script 343 // execution. 344 // (e) The worker thread has a pending task to restart execution, which was 345 // posted after the DNS dependency was resolved and saved to local cache. 346 // (f) The script execution completed entirely, and posted a task to the 347 // origin thread to notify the caller. 348 // 349 // |cancelled_| is read on both the origin thread and worker thread. The 350 // code that runs on the worker thread is littered with checks on 351 // |cancelled_| to break out early. 352 cancelled_.Set(); 353 354 ReleaseCallback(); 355 356 if (pending_dns_) { 357 host_resolver()->CancelRequest(pending_dns_); 358 pending_dns_ = NULL; 359 } 360 361 // The worker thread might be blocked waiting for DNS. 362 event_.Signal(); 363 364 owned_self_reference_ = NULL; 365 } 366 367 LoadState ProxyResolverV8Tracing::Job::GetLoadState() const { 368 CheckIsOnOriginThread(); 369 370 if (pending_dns_) 371 return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT; 372 373 return LOAD_STATE_RESOLVING_PROXY_FOR_URL; 374 } 375 376 ProxyResolverV8Tracing::Job::~Job() { 377 DCHECK(!pending_dns_); 378 DCHECK(callback_.is_null()); 379 } 380 381 void ProxyResolverV8Tracing::Job::CheckIsOnWorkerThread() const { 382 DCHECK_EQ(base::MessageLoop::current(), parent_->thread_->message_loop()); 383 } 384 385 void ProxyResolverV8Tracing::Job::CheckIsOnOriginThread() const { 386 DCHECK(origin_loop_->BelongsToCurrentThread()); 387 } 388 389 void ProxyResolverV8Tracing::Job::SetCallback( 390 const CompletionCallback& callback) { 391 CheckIsOnOriginThread(); 392 DCHECK(callback_.is_null()); 393 parent_->num_outstanding_callbacks_++; 394 callback_ = callback; 395 } 396 397 void ProxyResolverV8Tracing::Job::ReleaseCallback() { 398 CheckIsOnOriginThread(); 399 DCHECK(!callback_.is_null()); 400 CHECK_GT(parent_->num_outstanding_callbacks_, 0); 401 parent_->num_outstanding_callbacks_--; 402 callback_.Reset(); 403 404 // For good measure, clear this other user-owned pointer. 405 user_results_ = NULL; 406 } 407 408 ProxyResolverV8* ProxyResolverV8Tracing::Job::v8_resolver() { 409 return parent_->v8_resolver_.get(); 410 } 411 412 base::MessageLoop* ProxyResolverV8Tracing::Job::worker_loop() { 413 return parent_->thread_->message_loop(); 414 } 415 416 HostResolver* ProxyResolverV8Tracing::Job::host_resolver() { 417 return parent_->host_resolver_; 418 } 419 420 ProxyResolverErrorObserver* ProxyResolverV8Tracing::Job::error_observer() { 421 return parent_->error_observer_.get(); 422 } 423 424 NetLog* ProxyResolverV8Tracing::Job::net_log() { 425 return parent_->net_log_; 426 } 427 428 void ProxyResolverV8Tracing::Job::NotifyCaller(int result) { 429 CheckIsOnWorkerThread(); 430 431 origin_loop_->PostTask( 432 FROM_HERE, 433 base::Bind(&Job::NotifyCallerOnOriginLoop, this, result)); 434 } 435 436 void ProxyResolverV8Tracing::Job::NotifyCallerOnOriginLoop(int result) { 437 CheckIsOnOriginThread(); 438 439 if (cancelled_.IsSet()) 440 return; 441 442 DCHECK(!callback_.is_null()); 443 DCHECK(!pending_dns_); 444 445 if (operation_ == GET_PROXY_FOR_URL) { 446 *user_results_ = results_; 447 } 448 449 // There is only ever 1 outstanding SET_PAC_SCRIPT job. It needs to be 450 // tracked to support cancellation. 451 if (operation_ == SET_PAC_SCRIPT) { 452 DCHECK_EQ(parent_->set_pac_script_job_.get(), this); 453 parent_->set_pac_script_job_ = NULL; 454 } 455 456 CompletionCallback callback = callback_; 457 ReleaseCallback(); 458 callback.Run(result); 459 460 owned_self_reference_ = NULL; 461 } 462 463 void ProxyResolverV8Tracing::Job::Start(Operation op, bool blocking_dns, 464 const CompletionCallback& callback) { 465 CheckIsOnOriginThread(); 466 467 operation_ = op; 468 blocking_dns_ = blocking_dns; 469 SetCallback(callback); 470 471 owned_self_reference_ = this; 472 473 worker_loop()->PostTask(FROM_HERE, 474 blocking_dns_ ? base::Bind(&Job::ExecuteBlocking, this) : 475 base::Bind(&Job::ExecuteNonBlocking, this)); 476 } 477 478 void ProxyResolverV8Tracing::Job::ExecuteBlocking() { 479 CheckIsOnWorkerThread(); 480 DCHECK(blocking_dns_); 481 482 if (cancelled_.IsSet()) 483 return; 484 485 NotifyCaller(ExecuteProxyResolver()); 486 } 487 488 void ProxyResolverV8Tracing::Job::ExecuteNonBlocking() { 489 CheckIsOnWorkerThread(); 490 DCHECK(!blocking_dns_); 491 492 if (cancelled_.IsSet()) 493 return; 494 495 // Reset state for the current execution. 496 abandoned_ = false; 497 num_dns_ = 0; 498 alerts_and_errors_.clear(); 499 alerts_and_errors_byte_cost_ = 0; 500 should_restart_with_blocking_dns_ = false; 501 502 int result = ExecuteProxyResolver(); 503 504 if (should_restart_with_blocking_dns_) { 505 DCHECK(!blocking_dns_); 506 DCHECK(abandoned_); 507 blocking_dns_ = true; 508 ExecuteBlocking(); 509 return; 510 } 511 512 if (abandoned_) 513 return; 514 515 DispatchBufferedAlertsAndErrors(); 516 NotifyCaller(result); 517 } 518 519 int ProxyResolverV8Tracing::Job::ExecuteProxyResolver() { 520 JSBindings* prev_bindings = v8_resolver()->js_bindings(); 521 v8_resolver()->set_js_bindings(this); 522 523 int result = ERR_UNEXPECTED; // Initialized to silence warnings. 524 525 switch (operation_) { 526 case SET_PAC_SCRIPT: 527 result = v8_resolver()->SetPacScript( 528 script_data_, CompletionCallback()); 529 break; 530 case GET_PROXY_FOR_URL: 531 result = v8_resolver()->GetProxyForURL( 532 url_, 533 // Important: Do not write directly into |user_results_|, since if the 534 // request were to be cancelled from the origin thread, must guarantee 535 // that |user_results_| is not accessed anymore. 536 &results_, 537 CompletionCallback(), 538 NULL, 539 bound_net_log_); 540 break; 541 } 542 543 v8_resolver()->set_js_bindings(prev_bindings); 544 545 return result; 546 } 547 548 bool ProxyResolverV8Tracing::Job::ResolveDns(const std::string& host, 549 ResolveDnsOperation op, 550 std::string* output, 551 bool* terminate) { 552 if (cancelled_.IsSet()) { 553 *terminate = true; 554 return false; 555 } 556 557 if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) { 558 // a DNS resolve with an empty hostname is considered an error. 559 return false; 560 } 561 562 return blocking_dns_ ? 563 ResolveDnsBlocking(host, op, output) : 564 ResolveDnsNonBlocking(host, op, output, terminate); 565 } 566 567 void ProxyResolverV8Tracing::Job::Alert(const base::string16& message) { 568 HandleAlertOrError(true, -1, message); 569 } 570 571 void ProxyResolverV8Tracing::Job::OnError(int line_number, 572 const base::string16& error) { 573 HandleAlertOrError(false, line_number, error); 574 } 575 576 bool ProxyResolverV8Tracing::Job::ResolveDnsBlocking(const std::string& host, 577 ResolveDnsOperation op, 578 std::string* output) { 579 CheckIsOnWorkerThread(); 580 581 // Check if the DNS result for this host has already been cached. 582 bool rv; 583 if (GetDnsFromLocalCache(host, op, output, &rv)) { 584 // Yay, cache hit! 585 return rv; 586 } 587 588 if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) { 589 // Safety net for scripts with unexpectedly many DNS calls. 590 // We will continue running to completion, but will fail every 591 // subsequent DNS request. 592 return false; 593 } 594 595 if (!PostDnsOperationAndWait(host, op, NULL)) 596 return false; // Was cancelled. 597 598 CHECK(GetDnsFromLocalCache(host, op, output, &rv)); 599 return rv; 600 } 601 602 bool ProxyResolverV8Tracing::Job::ResolveDnsNonBlocking(const std::string& host, 603 ResolveDnsOperation op, 604 std::string* output, 605 bool* terminate) { 606 CheckIsOnWorkerThread(); 607 608 if (abandoned_) { 609 // If this execution was already abandoned can fail right away. Only 1 DNS 610 // dependency will be traced at a time (for more predictable outcomes). 611 return false; 612 } 613 614 num_dns_ += 1; 615 616 // Check if the DNS result for this host has already been cached. 617 bool rv; 618 if (GetDnsFromLocalCache(host, op, output, &rv)) { 619 // Yay, cache hit! 620 return rv; 621 } 622 623 if (num_dns_ <= last_num_dns_) { 624 // The sequence of DNS operations is different from last time! 625 ScheduleRestartWithBlockingDns(); 626 *terminate = true; 627 return false; 628 } 629 630 if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) { 631 // Safety net for scripts with unexpectedly many DNS calls. 632 return false; 633 } 634 635 DCHECK(!should_restart_with_blocking_dns_); 636 637 bool completed_synchronously; 638 if (!PostDnsOperationAndWait(host, op, &completed_synchronously)) 639 return false; // Was cancelled. 640 641 if (completed_synchronously) { 642 CHECK(GetDnsFromLocalCache(host, op, output, &rv)); 643 return rv; 644 } 645 646 // Otherwise if the result was not in the cache, then a DNS request has 647 // been started. Abandon this invocation of FindProxyForURL(), it will be 648 // restarted once the DNS request completes. 649 abandoned_ = true; 650 *terminate = true; 651 last_num_dns_ = num_dns_; 652 return false; 653 } 654 655 bool ProxyResolverV8Tracing::Job::PostDnsOperationAndWait( 656 const std::string& host, ResolveDnsOperation op, 657 bool* completed_synchronously) { 658 659 // Post the DNS request to the origin thread. 660 DCHECK(!pending_dns_); 661 pending_dns_host_ = host; 662 pending_dns_op_ = op; 663 origin_loop_->PostTask(FROM_HERE, base::Bind(&Job::DoDnsOperation, this)); 664 665 event_.Wait(); 666 event_.Reset(); 667 668 if (cancelled_.IsSet()) 669 return false; 670 671 if (completed_synchronously) 672 *completed_synchronously = pending_dns_completed_synchronously_; 673 674 return true; 675 } 676 677 void ProxyResolverV8Tracing::Job::DoDnsOperation() { 678 CheckIsOnOriginThread(); 679 DCHECK(!pending_dns_); 680 681 if (cancelled_.IsSet()) 682 return; 683 684 HostResolver::RequestHandle dns_request = NULL; 685 int result = host_resolver()->Resolve( 686 MakeDnsRequestInfo(pending_dns_host_, pending_dns_op_), 687 DEFAULT_PRIORITY, 688 &pending_dns_addresses_, 689 base::Bind(&Job::OnDnsOperationComplete, this), 690 &dns_request, 691 bound_net_log_); 692 693 pending_dns_completed_synchronously_ = result != ERR_IO_PENDING; 694 695 // Check if the request was cancelled as a side-effect of calling into the 696 // HostResolver. This isn't the ordinary execution flow, however it is 697 // exercised by unit-tests. 698 if (cancelled_.IsSet()) { 699 if (!pending_dns_completed_synchronously_) 700 host_resolver()->CancelRequest(dns_request); 701 return; 702 } 703 704 if (pending_dns_completed_synchronously_) { 705 OnDnsOperationComplete(result); 706 } else { 707 DCHECK(dns_request); 708 pending_dns_ = dns_request; 709 // OnDnsOperationComplete() will be called by host resolver on completion. 710 } 711 712 if (!blocking_dns_) { 713 // The worker thread always blocks waiting to see if the result can be 714 // serviced from cache before restarting. 715 event_.Signal(); 716 } 717 } 718 719 void ProxyResolverV8Tracing::Job::OnDnsOperationComplete(int result) { 720 CheckIsOnOriginThread(); 721 722 DCHECK(!cancelled_.IsSet()); 723 DCHECK(pending_dns_completed_synchronously_ == (pending_dns_ == NULL)); 724 725 SaveDnsToLocalCache(pending_dns_host_, pending_dns_op_, result, 726 pending_dns_addresses_); 727 pending_dns_ = NULL; 728 729 if (blocking_dns_) { 730 event_.Signal(); 731 return; 732 } 733 734 if (!blocking_dns_ && !pending_dns_completed_synchronously_) { 735 // Restart. This time it should make more progress due to having 736 // cached items. 737 worker_loop()->PostTask(FROM_HERE, 738 base::Bind(&Job::ExecuteNonBlocking, this)); 739 } 740 } 741 742 void ProxyResolverV8Tracing::Job::ScheduleRestartWithBlockingDns() { 743 CheckIsOnWorkerThread(); 744 745 DCHECK(!should_restart_with_blocking_dns_); 746 DCHECK(!abandoned_); 747 DCHECK(!blocking_dns_); 748 749 abandoned_ = true; 750 751 // The restart will happen after ExecuteNonBlocking() finishes. 752 should_restart_with_blocking_dns_ = true; 753 } 754 755 bool ProxyResolverV8Tracing::Job::GetDnsFromLocalCache( 756 const std::string& host, 757 ResolveDnsOperation op, 758 std::string* output, 759 bool* return_value) { 760 CheckIsOnWorkerThread(); 761 762 DnsCache::const_iterator it = dns_cache_.find(MakeDnsCacheKey(host, op)); 763 if (it == dns_cache_.end()) 764 return false; 765 766 *output = it->second; 767 *return_value = !it->second.empty(); 768 return true; 769 } 770 771 void ProxyResolverV8Tracing::Job::SaveDnsToLocalCache( 772 const std::string& host, 773 ResolveDnsOperation op, 774 int net_error, 775 const net::AddressList& addresses) { 776 CheckIsOnOriginThread(); 777 778 // Serialize the result into a string to save to the cache. 779 std::string cache_value; 780 if (net_error != OK) { 781 cache_value = std::string(); 782 } else if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) { 783 // dnsResolve() and myIpAddress() are expected to return a single IP 784 // address. 785 cache_value = addresses.front().ToStringWithoutPort(); 786 } else { 787 // The *Ex versions are expected to return a semi-colon separated list. 788 for (AddressList::const_iterator iter = addresses.begin(); 789 iter != addresses.end(); ++iter) { 790 if (!cache_value.empty()) 791 cache_value += ";"; 792 cache_value += iter->ToStringWithoutPort(); 793 } 794 } 795 796 dns_cache_[MakeDnsCacheKey(host, op)] = cache_value; 797 } 798 799 // static 800 HostResolver::RequestInfo ProxyResolverV8Tracing::Job::MakeDnsRequestInfo( 801 const std::string& host, ResolveDnsOperation op) { 802 HostPortPair host_port = HostPortPair(host, 80); 803 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) { 804 host_port.set_host(GetHostName()); 805 } 806 807 HostResolver::RequestInfo info(host_port); 808 // Flag myIpAddress requests. 809 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) { 810 // TODO: Provide a RequestInfo construction mechanism that does not 811 // require a hostname and sets is_my_ip_address to true instead of this. 812 info.set_is_my_ip_address(true); 813 } 814 // The non-ex flavors are limited to IPv4 results. 815 if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) { 816 info.set_address_family(ADDRESS_FAMILY_IPV4); 817 } 818 819 return info; 820 } 821 822 std::string ProxyResolverV8Tracing::Job::MakeDnsCacheKey( 823 const std::string& host, ResolveDnsOperation op) { 824 return base::StringPrintf("%d:%s", op, host.c_str()); 825 } 826 827 void ProxyResolverV8Tracing::Job::HandleAlertOrError( 828 bool is_alert, 829 int line_number, 830 const base::string16& message) { 831 CheckIsOnWorkerThread(); 832 833 if (cancelled_.IsSet()) 834 return; 835 836 if (blocking_dns_) { 837 // In blocking DNS mode the events can be dispatched immediately. 838 DispatchAlertOrError(is_alert, line_number, message); 839 return; 840 } 841 842 // Otherwise in nonblocking mode, buffer all the messages until 843 // the end. 844 845 if (abandoned_) 846 return; 847 848 alerts_and_errors_byte_cost_ += sizeof(AlertOrError) + message.size() * 2; 849 850 // If there have been lots of messages, enqueing could be expensive on 851 // memory. Consider a script which does megabytes worth of alerts(). 852 // Avoid this by falling back to blocking mode. 853 if (alerts_and_errors_byte_cost_ > kMaxAlertsAndErrorsBytes) { 854 ScheduleRestartWithBlockingDns(); 855 return; 856 } 857 858 AlertOrError entry = {is_alert, line_number, message}; 859 alerts_and_errors_.push_back(entry); 860 } 861 862 void ProxyResolverV8Tracing::Job::DispatchBufferedAlertsAndErrors() { 863 CheckIsOnWorkerThread(); 864 DCHECK(!blocking_dns_); 865 DCHECK(!abandoned_); 866 867 for (size_t i = 0; i < alerts_and_errors_.size(); ++i) { 868 const AlertOrError& x = alerts_and_errors_[i]; 869 DispatchAlertOrError(x.is_alert, x.line_number, x.message); 870 } 871 } 872 873 void ProxyResolverV8Tracing::Job::DispatchAlertOrError( 874 bool is_alert, int line_number, const base::string16& message) { 875 CheckIsOnWorkerThread(); 876 877 // Note that the handling of cancellation is racy with regard to 878 // alerts/errors. The request might get cancelled shortly after this 879 // check! (There is no lock being held to guarantee otherwise). 880 // 881 // If this happens, then some information will get written to the NetLog 882 // needlessly, however the NetLog will still be alive so it shouldn't cause 883 // problems. 884 if (cancelled_.IsSet()) 885 return; 886 887 if (is_alert) { 888 // ------------------- 889 // alert 890 // ------------------- 891 VLOG(1) << "PAC-alert: " << message; 892 893 // Send to the NetLog. 894 LogEventToCurrentRequestAndGlobally( 895 NetLog::TYPE_PAC_JAVASCRIPT_ALERT, 896 NetLog::StringCallback("message", &message)); 897 } else { 898 // ------------------- 899 // error 900 // ------------------- 901 if (line_number == -1) 902 VLOG(1) << "PAC-error: " << message; 903 else 904 VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message; 905 906 // Send the error to the NetLog. 907 LogEventToCurrentRequestAndGlobally( 908 NetLog::TYPE_PAC_JAVASCRIPT_ERROR, 909 base::Bind(&NetLogErrorCallback, line_number, &message)); 910 911 if (error_observer()) 912 error_observer()->OnPACScriptError(line_number, message); 913 } 914 } 915 916 void ProxyResolverV8Tracing::Job::LogEventToCurrentRequestAndGlobally( 917 NetLog::EventType type, 918 const NetLog::ParametersCallback& parameters_callback) { 919 CheckIsOnWorkerThread(); 920 bound_net_log_.AddEvent(type, parameters_callback); 921 922 // Emit to the global NetLog event stream. 923 if (net_log()) 924 net_log()->AddGlobalEntry(type, parameters_callback); 925 } 926 927 ProxyResolverV8Tracing::ProxyResolverV8Tracing( 928 HostResolver* host_resolver, 929 ProxyResolverErrorObserver* error_observer, 930 NetLog* net_log) 931 : ProxyResolver(true /*expects_pac_bytes*/), 932 host_resolver_(host_resolver), 933 error_observer_(error_observer), 934 net_log_(net_log), 935 num_outstanding_callbacks_(0) { 936 DCHECK(host_resolver); 937 // Start up the thread. 938 thread_.reset(new base::Thread("Proxy resolver")); 939 base::Thread::Options options; 940 options.timer_slack = base::TIMER_SLACK_MAXIMUM; 941 CHECK(thread_->StartWithOptions(options)); 942 943 v8_resolver_.reset(new ProxyResolverV8); 944 } 945 946 ProxyResolverV8Tracing::~ProxyResolverV8Tracing() { 947 // Note, all requests should have been cancelled. 948 CHECK(!set_pac_script_job_.get()); 949 CHECK_EQ(0, num_outstanding_callbacks_); 950 951 // Join the worker thread. See http://crbug.com/69710. Note that we call 952 // Stop() here instead of simply clearing thread_ since there may be pending 953 // callbacks on the worker thread which want to dereference thread_. 954 base::ThreadRestrictions::ScopedAllowIO allow_io; 955 thread_->Stop(); 956 } 957 958 int ProxyResolverV8Tracing::GetProxyForURL(const GURL& url, 959 ProxyInfo* results, 960 const CompletionCallback& callback, 961 RequestHandle* request, 962 const BoundNetLog& net_log) { 963 DCHECK(CalledOnValidThread()); 964 DCHECK(!callback.is_null()); 965 DCHECK(!set_pac_script_job_.get()); 966 967 scoped_refptr<Job> job = new Job(this); 968 969 if (request) 970 *request = job.get(); 971 972 job->StartGetProxyForURL(url, results, net_log, callback); 973 return ERR_IO_PENDING; 974 } 975 976 void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) { 977 Job* job = reinterpret_cast<Job*>(request); 978 job->Cancel(); 979 } 980 981 LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const { 982 Job* job = reinterpret_cast<Job*>(request); 983 return job->GetLoadState(); 984 } 985 986 void ProxyResolverV8Tracing::CancelSetPacScript() { 987 DCHECK(set_pac_script_job_.get()); 988 set_pac_script_job_->Cancel(); 989 set_pac_script_job_ = NULL; 990 } 991 992 int ProxyResolverV8Tracing::SetPacScript( 993 const scoped_refptr<ProxyResolverScriptData>& script_data, 994 const CompletionCallback& callback) { 995 DCHECK(CalledOnValidThread()); 996 DCHECK(!callback.is_null()); 997 998 // Note that there should not be any outstanding (non-cancelled) Jobs when 999 // setting the PAC script (ProxyService should guarantee this). If there are, 1000 // then they might complete in strange ways after the new script is set. 1001 DCHECK(!set_pac_script_job_.get()); 1002 CHECK_EQ(0, num_outstanding_callbacks_); 1003 1004 set_pac_script_job_ = new Job(this); 1005 set_pac_script_job_->StartSetPacScript(script_data, callback); 1006 1007 return ERR_IO_PENDING; 1008 } 1009 1010 } // namespace net 1011