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