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/message_loop_proxy.h" |
| 9 #include "base/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/host_resolver.h" |
| 17 #include "net/base/net_errors.h" |
| 18 #include "net/base/net_log.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/16Ij5OcVnR3s0MH4Z5XkhI9VTPo
MJdaBn9rKreAmGOdE/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 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 string16 message; |
| 120 }; |
| 121 |
| 122 ~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 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) OVERRIDE; |
| 151 virtual void Alert(const string16& message) OVERRIDE; |
| 152 virtual void OnError(int line_number, const string16& error) OVERRIDE; |
| 153 |
| 154 bool ResolveDnsBlocking(const std::string& host, |
| 155 ResolveDnsOperation op, |
| 156 std::string* output); |
| 157 |
| 158 bool ResolveDnsNonBlocking(const std::string& host, |
| 159 ResolveDnsOperation op, |
| 160 std::string* output); |
| 161 |
| 162 void DoDnsOperation(const std::string& host, ResolveDnsOperation op, |
| 163 bool* out_cache_hit); |
| 164 void OnDnsOperationComplete(int result); |
| 165 |
| 166 void ScheduleRestartWithBlockingDns(); |
| 167 |
| 168 bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op, |
| 169 std::string* output, bool* return_value); |
| 170 |
| 171 void SaveDnsToLocalCache(const std::string& host, ResolveDnsOperation op, |
| 172 int net_error, const net::AddressList& addresses); |
| 173 |
| 174 // Builds a RequestInfo to service the specified PAC DNS operation. |
| 175 static HostResolver::RequestInfo MakeDnsRequestInfo(const std::string& host, |
| 176 ResolveDnsOperation op); |
| 177 |
| 178 // Makes a key for looking up |host, op| in |dns_cache_|. Strings are used for |
| 179 // convenience, to avoid defining custom comparators. |
| 180 static std::string MakeDnsCacheKey(const std::string& host, |
| 181 ResolveDnsOperation op); |
| 182 |
| 183 void HandleAlertOrError(bool is_alert, int line_number, |
| 184 const string16& message); |
| 185 void DispatchBufferedAlertsAndErrors(); |
| 186 void DispatchAlertOrError(bool is_alert, int line_number, |
| 187 const string16& message); |
| 188 |
| 189 void LogEventToCurrentRequestAndGlobally( |
| 190 NetLog::EventType type, |
| 191 const NetLog::ParametersCallback& parameters_callback); |
| 192 |
| 193 // The thread which called into ProxyResolverV8Tracing, and on which the |
| 194 // completion callback is expected to run. |
| 195 scoped_refptr<base::MessageLoopProxy> origin_loop_; |
| 196 |
| 197 // The ProxyResolverV8Tracing which spawned this Job. |
| 198 // Initialized on origin thread and then accessed from both threads. |
| 199 ProxyResolverV8Tracing* parent_; |
| 200 |
| 201 // The callback to run (on the origin thread) when the Job finishes. |
| 202 // Should only be accessed from origin thread. |
| 203 CompletionCallback callback_; |
| 204 |
| 205 // Flag to indicate whether the request has been cancelled. |
| 206 base::CancellationFlag cancelled_; |
| 207 |
| 208 // The operation that this Job is running. |
| 209 // Initialized on origin thread and then accessed from both threads. |
| 210 Operation operation_; |
| 211 |
| 212 // The DNS mode for this Job. |
| 213 // Initialized on origin thread, mutated on worker thread, and accessed |
| 214 // by both the origin thread and worker thread. |
| 215 bool blocking_dns_; |
| 216 |
| 217 // Used to block the worker thread on a DNS operation taking place on the |
| 218 // origin thread. |
| 219 base::WaitableEvent event_; |
| 220 |
| 221 // Map of DNS operations completed so far. Written into on the origin thread |
| 222 // and read on the worker thread. |
| 223 DnsCache dns_cache_; |
| 224 |
| 225 // The job holds a reference to itself to ensure that it remains alive until |
| 226 // either completion or cancellation. |
| 227 scoped_refptr<Job> owned_self_reference_; |
| 228 |
| 229 // ------------------------------------------------------- |
| 230 // State specific to SET_PAC_SCRIPT. |
| 231 // ------------------------------------------------------- |
| 232 |
| 233 scoped_refptr<ProxyResolverScriptData> script_data_; |
| 234 |
| 235 // ------------------------------------------------------- |
| 236 // State specific to GET_PROXY_FOR_URL. |
| 237 // ------------------------------------------------------- |
| 238 |
| 239 ProxyInfo* user_results_; // Owned by caller, lives on origin thread. |
| 240 GURL url_; |
| 241 ProxyInfo results_; |
| 242 BoundNetLog bound_net_log_; |
| 243 |
| 244 // --------------------------------------------------------------------------- |
| 245 // State for ExecuteNonBlocking() |
| 246 // --------------------------------------------------------------------------- |
| 247 // These variables are used exclusively on the worker thread and are only |
| 248 // meaningful when executing inside of ExecuteNonBlocking(). |
| 249 |
| 250 // Whether this execution was abandoned due to a missing DNS dependency. |
| 251 bool abandoned_; |
| 252 |
| 253 // Number of calls made to ResolveDns() by this execution. |
| 254 int num_dns_; |
| 255 |
| 256 // Sequence of calls made to Alert() or OnError() by this execution. |
| 257 std::vector<AlertOrError> alerts_and_errors_; |
| 258 size_t alerts_and_errors_byte_cost_; // Approximate byte cost of the above. |
| 259 |
| 260 // Number of calls made to ResolveDns() by the PREVIOUS execution. |
| 261 int last_num_dns_; |
| 262 |
| 263 // Whether the current execution needs to be restarted in blocking mode. |
| 264 bool should_restart_with_blocking_dns_; |
| 265 |
| 266 // --------------------------------------------------------------------------- |
| 267 // State for pending DNS request. |
| 268 // --------------------------------------------------------------------------- |
| 269 // These variables are used exclusively on the origin thread. |
| 270 |
| 271 HostResolver::RequestHandle pending_dns_; |
| 272 // Only meaningful when |pending_dns_|: |
| 273 std::string pending_dns_host_; |
| 274 ResolveDnsOperation pending_dns_op_; |
| 275 AddressList pending_dns_addresses_; |
| 276 }; |
| 277 |
| 278 ProxyResolverV8Tracing::Job::Job(ProxyResolverV8Tracing* parent) |
| 279 : origin_loop_(base::MessageLoopProxy::current()), |
| 280 parent_(parent), |
| 281 event_(true, false), |
| 282 last_num_dns_(0), |
| 283 pending_dns_(NULL) { |
| 284 CheckIsOnOriginThread(); |
| 285 } |
| 286 |
| 287 void ProxyResolverV8Tracing::Job::StartSetPacScript( |
| 288 const scoped_refptr<ProxyResolverScriptData>& script_data, |
| 289 const CompletionCallback& callback) { |
| 290 CheckIsOnOriginThread(); |
| 291 |
| 292 script_data_ = script_data; |
| 293 |
| 294 // Script initialization uses blocking DNS since there isn't any |
| 295 // advantage to using non-blocking mode here. That is because the |
| 296 // parent ProxyService can't submit any ProxyResolve requests until |
| 297 // initialization has completed successfully! |
| 298 Start(SET_PAC_SCRIPT, true /*blocking*/, callback); |
| 299 } |
| 300 |
| 301 void ProxyResolverV8Tracing::Job::StartGetProxyForURL( |
| 302 const GURL& url, |
| 303 ProxyInfo* results, |
| 304 const BoundNetLog& net_log, |
| 305 const CompletionCallback& callback) { |
| 306 CheckIsOnOriginThread(); |
| 307 |
| 308 url_ = url; |
| 309 user_results_ = results; |
| 310 bound_net_log_ = net_log; |
| 311 |
| 312 Start(GET_PROXY_FOR_URL, false /*non-blocking*/, callback); |
| 313 } |
| 314 |
| 315 void ProxyResolverV8Tracing::Job::Cancel() { |
| 316 CheckIsOnOriginThread(); |
| 317 |
| 318 // There are several possibilities to consider for cancellation: |
| 319 // (a) The job has been posted to the worker thread, however script execution |
| 320 // has not yet started. |
| 321 // (b) The script is executing on the worker thread. |
| 322 // (c) The script is executing on the worker thread, however is blocked inside |
| 323 // of dnsResolve() waiting for a response from the origin thread. |
| 324 // (d) Nothing is running on the worker thread, however the host resolver has |
| 325 // a pending DNS request which upon completion will restart the script |
| 326 // execution. |
| 327 // (e) The worker thread has a pending task to restart execution, which was |
| 328 // posted after the DNS dependency was resolved and saved to local cache. |
| 329 // (f) The script execution completed entirely, and posted a task to the |
| 330 // origin thread to notify the caller. |
| 331 // |
| 332 // |cancelled_| is read on both the origin thread and worker thread. The |
| 333 // code that runs on the worker thread is littered with checks on |
| 334 // |cancelled_| to break out early. |
| 335 cancelled_.Set(); |
| 336 |
| 337 ReleaseCallback(); |
| 338 |
| 339 if (pending_dns_) { |
| 340 host_resolver()->CancelRequest(pending_dns_); |
| 341 pending_dns_ = NULL; |
| 342 } |
| 343 |
| 344 // The worker thread might be blocked waiting for DNS. |
| 345 event_.Signal(); |
| 346 |
| 347 owned_self_reference_ = NULL; |
| 348 } |
| 349 |
| 350 LoadState ProxyResolverV8Tracing::Job::GetLoadState() const { |
| 351 CheckIsOnOriginThread(); |
| 352 |
| 353 if (pending_dns_) |
| 354 return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT; |
| 355 |
| 356 return LOAD_STATE_RESOLVING_PROXY_FOR_URL; |
| 357 } |
| 358 |
| 359 ProxyResolverV8Tracing::Job::~Job() { |
| 360 DCHECK(!pending_dns_); |
| 361 DCHECK(callback_.is_null()); |
| 362 } |
| 363 |
| 364 void ProxyResolverV8Tracing::Job::CheckIsOnWorkerThread() const { |
| 365 DCHECK_EQ(MessageLoop::current(), parent_->thread_->message_loop()); |
| 366 } |
| 367 |
| 368 void ProxyResolverV8Tracing::Job::CheckIsOnOriginThread() const { |
| 369 DCHECK(origin_loop_->BelongsToCurrentThread()); |
| 370 } |
| 371 |
| 372 void ProxyResolverV8Tracing::Job::SetCallback( |
| 373 const CompletionCallback& callback) { |
| 374 CheckIsOnOriginThread(); |
| 375 DCHECK(callback_.is_null()); |
| 376 parent_->num_outstanding_callbacks_++; |
| 377 callback_ = callback; |
| 378 } |
| 379 |
| 380 void ProxyResolverV8Tracing::Job::ReleaseCallback() { |
| 381 CheckIsOnOriginThread(); |
| 382 DCHECK(!callback_.is_null()); |
| 383 CHECK_GT(parent_->num_outstanding_callbacks_, 0); |
| 384 parent_->num_outstanding_callbacks_--; |
| 385 callback_.Reset(); |
| 386 |
| 387 // For good measure, clear this other user-owned pointer. |
| 388 user_results_ = NULL; |
| 389 } |
| 390 |
| 391 ProxyResolverV8* ProxyResolverV8Tracing::Job::v8_resolver() { |
| 392 return parent_->v8_resolver_.get(); |
| 393 } |
| 394 |
| 395 MessageLoop* ProxyResolverV8Tracing::Job::worker_loop() { |
| 396 return parent_->thread_->message_loop(); |
| 397 } |
| 398 |
| 399 HostResolver* ProxyResolverV8Tracing::Job::host_resolver() { |
| 400 return parent_->host_resolver_; |
| 401 } |
| 402 |
| 403 ProxyResolverErrorObserver* ProxyResolverV8Tracing::Job::error_observer() { |
| 404 return parent_->error_observer_.get(); |
| 405 } |
| 406 |
| 407 NetLog* ProxyResolverV8Tracing::Job::net_log() { |
| 408 return parent_->net_log_; |
| 409 } |
| 410 |
| 411 void ProxyResolverV8Tracing::Job::NotifyCaller(int result) { |
| 412 CheckIsOnWorkerThread(); |
| 413 |
| 414 origin_loop_->PostTask( |
| 415 FROM_HERE, |
| 416 base::Bind(&Job::NotifyCallerOnOriginLoop, this, result)); |
| 417 } |
| 418 |
| 419 void ProxyResolverV8Tracing::Job::NotifyCallerOnOriginLoop(int result) { |
| 420 CheckIsOnOriginThread(); |
| 421 |
| 422 if (cancelled_.IsSet()) |
| 423 return; |
| 424 |
| 425 DCHECK(!callback_.is_null()); |
| 426 DCHECK(!pending_dns_); |
| 427 |
| 428 if (operation_ == GET_PROXY_FOR_URL) |
| 429 *user_results_ = results_; |
| 430 |
| 431 // There is only ever 1 outstanding SET_PAC_SCRIPT job. It needs to be |
| 432 // tracked to support cancellation. |
| 433 if (operation_ == SET_PAC_SCRIPT) { |
| 434 DCHECK_EQ(parent_->set_pac_script_job_.get(), this); |
| 435 parent_->set_pac_script_job_ = NULL; |
| 436 } |
| 437 |
| 438 CompletionCallback callback = callback_; |
| 439 ReleaseCallback(); |
| 440 callback.Run(result); |
| 441 |
| 442 owned_self_reference_ = NULL; |
| 443 } |
| 444 |
| 445 void ProxyResolverV8Tracing::Job::Start(Operation op, bool blocking_dns, |
| 446 const CompletionCallback& callback) { |
| 447 CheckIsOnOriginThread(); |
| 448 |
| 449 operation_ = op; |
| 450 blocking_dns_ = blocking_dns; |
| 451 SetCallback(callback); |
| 452 |
| 453 owned_self_reference_ = this; |
| 454 |
| 455 worker_loop()->PostTask(FROM_HERE, |
| 456 blocking_dns_ ? base::Bind(&Job::ExecuteBlocking, this) : |
| 457 base::Bind(&Job::ExecuteNonBlocking, this)); |
| 458 } |
| 459 |
| 460 void ProxyResolverV8Tracing::Job::ExecuteBlocking() { |
| 461 CheckIsOnWorkerThread(); |
| 462 DCHECK(blocking_dns_); |
| 463 |
| 464 if (cancelled_.IsSet()) |
| 465 return; |
| 466 |
| 467 NotifyCaller(ExecuteProxyResolver()); |
| 468 } |
| 469 |
| 470 void ProxyResolverV8Tracing::Job::ExecuteNonBlocking() { |
| 471 CheckIsOnWorkerThread(); |
| 472 DCHECK(!blocking_dns_); |
| 473 |
| 474 if (cancelled_.IsSet()) |
| 475 return; |
| 476 |
| 477 // Reset state for the current execution. |
| 478 abandoned_ = false; |
| 479 num_dns_ = 0; |
| 480 alerts_and_errors_.clear(); |
| 481 alerts_and_errors_byte_cost_ = 0; |
| 482 should_restart_with_blocking_dns_ = false; |
| 483 |
| 484 int result = ExecuteProxyResolver(); |
| 485 |
| 486 if (should_restart_with_blocking_dns_) { |
| 487 DCHECK(!blocking_dns_); |
| 488 DCHECK(abandoned_); |
| 489 blocking_dns_ = true; |
| 490 ExecuteBlocking(); |
| 491 return; |
| 492 } |
| 493 |
| 494 if (abandoned_) |
| 495 return; |
| 496 |
| 497 DispatchBufferedAlertsAndErrors(); |
| 498 NotifyCaller(result); |
| 499 } |
| 500 |
| 501 int ProxyResolverV8Tracing::Job::ExecuteProxyResolver() { |
| 502 JSBindings* prev_bindings = v8_resolver()->js_bindings(); |
| 503 v8_resolver()->set_js_bindings(this); |
| 504 |
| 505 int result = ERR_UNEXPECTED; // Initialized to silence warnings. |
| 506 |
| 507 switch (operation_) { |
| 508 case SET_PAC_SCRIPT: |
| 509 result = v8_resolver()->SetPacScript( |
| 510 script_data_, CompletionCallback()); |
| 511 break; |
| 512 case GET_PROXY_FOR_URL: |
| 513 result = v8_resolver()->GetProxyForURL( |
| 514 url_, |
| 515 // Important: Do not write directly into |user_results_|, since if the |
| 516 // request were to be cancelled from the origin thread, must guarantee |
| 517 // that |user_results_| is not accessed anymore. |
| 518 &results_, |
| 519 CompletionCallback(), |
| 520 NULL, |
| 521 bound_net_log_); |
| 522 break; |
| 523 } |
| 524 |
| 525 v8_resolver()->set_js_bindings(prev_bindings); |
| 526 return result; |
| 527 } |
| 528 |
| 529 bool ProxyResolverV8Tracing::Job::ResolveDns(const std::string& host, |
| 530 ResolveDnsOperation op, |
| 531 std::string* output) { |
| 532 if (cancelled_.IsSet()) |
| 533 return false; |
| 534 |
| 535 if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) { |
| 536 // a DNS resolve with an empty hostname is considered an error. |
| 537 return false; |
| 538 } |
| 539 |
| 540 return blocking_dns_ ? |
| 541 ResolveDnsBlocking(host, op, output) : |
| 542 ResolveDnsNonBlocking(host, op, output); |
| 543 } |
| 544 |
| 545 void ProxyResolverV8Tracing::Job::Alert(const string16& message) { |
| 546 HandleAlertOrError(true, -1, message); |
| 547 } |
| 548 |
| 549 void ProxyResolverV8Tracing::Job::OnError(int line_number, |
| 550 const string16& error) { |
| 551 HandleAlertOrError(false, line_number, error); |
| 552 } |
| 553 |
| 554 bool ProxyResolverV8Tracing::Job::ResolveDnsBlocking(const std::string& host, |
| 555 ResolveDnsOperation op, |
| 556 std::string* output) { |
| 557 CheckIsOnWorkerThread(); |
| 558 |
| 559 // Check if the DNS result for this host has already been cached. |
| 560 bool rv; |
| 561 if (GetDnsFromLocalCache(host, op, output, &rv)) { |
| 562 // Yay, cache hit! |
| 563 return rv; |
| 564 } |
| 565 |
| 566 // If the host was not in the local cache, this is a new hostname. |
| 567 |
| 568 if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) { |
| 569 // Safety net for scripts with unexpectedly many DNS calls. |
| 570 // We will continue running to completion, but will fail every |
| 571 // subsequent DNS request. |
| 572 return false; |
| 573 } |
| 574 |
| 575 bool unused; |
| 576 origin_loop_->PostTask( |
| 577 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op, &unused)); |
| 578 |
| 579 // Wait for the DNS operation to be completed by the host resolver on the |
| 580 // origin thread. After waking up, either the request was cancelled, or |
| 581 // the DNS result is now available in the cache. |
| 582 event_.Wait(); |
| 583 event_.Reset(); |
| 584 |
| 585 if (cancelled_.IsSet()) |
| 586 return false; |
| 587 |
| 588 CHECK(GetDnsFromLocalCache(host, op, output, &rv)); |
| 589 return rv; |
| 590 } |
| 591 |
| 592 bool ProxyResolverV8Tracing::Job::ResolveDnsNonBlocking(const std::string& host, |
| 593 ResolveDnsOperation op, |
| 594 std::string* output) { |
| 595 CheckIsOnWorkerThread(); |
| 596 |
| 597 if (abandoned_) { |
| 598 // If this execution was already abandoned can fail right away. Only 1 DNS |
| 599 // dependency will be traced at a time (for more predictable outcomes). |
| 600 return false; |
| 601 } |
| 602 |
| 603 num_dns_ += 1; |
| 604 |
| 605 // Check if the DNS result for this host has already been cached. |
| 606 bool rv; |
| 607 if (GetDnsFromLocalCache(host, op, output, &rv)) { |
| 608 // Yay, cache hit! |
| 609 return rv; |
| 610 } |
| 611 |
| 612 // If the host was not in the local cache, then this is a new hostname. |
| 613 |
| 614 if (num_dns_ <= last_num_dns_) { |
| 615 // The sequence of DNS operations is different from last time! |
| 616 ScheduleRestartWithBlockingDns(); |
| 617 return false; |
| 618 } |
| 619 |
| 620 if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) { |
| 621 // Safety net for scripts with unexpectedly many DNS calls. |
| 622 return false; |
| 623 } |
| 624 |
| 625 DCHECK(!should_restart_with_blocking_dns_); |
| 626 |
| 627 // Post the DNS request to the origin thread. |
| 628 bool resolver_cache_hit = false; |
| 629 origin_loop_->PostTask( |
| 630 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op, |
| 631 &resolver_cache_hit)); |
| 632 |
| 633 // As an optimization to avoid restarting too often, wait until the |
| 634 // resolver's cache has been inspected on the origin thread. |
| 635 event_.Wait(); |
| 636 event_.Reset(); |
| 637 |
| 638 if (cancelled_.IsSet()) |
| 639 return false; |
| 640 |
| 641 if (resolver_cache_hit) { |
| 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 last_num_dns_ = num_dns_; |
| 651 return false; |
| 652 } |
| 653 |
| 654 void ProxyResolverV8Tracing::Job::DoDnsOperation( |
| 655 const std::string& host, ResolveDnsOperation op, bool* out_cache_hit) { |
| 656 CheckIsOnOriginThread(); |
| 657 DCHECK(!pending_dns_); |
| 658 |
| 659 if (cancelled_.IsSet()) |
| 660 return; |
| 661 |
| 662 int result = host_resolver()->Resolve( |
| 663 MakeDnsRequestInfo(host, op), |
| 664 &pending_dns_addresses_, |
| 665 base::Bind(&Job::OnDnsOperationComplete, this), |
| 666 &pending_dns_, |
| 667 bound_net_log_); |
| 668 |
| 669 bool completed_synchronously = result != ERR_IO_PENDING; |
| 670 |
| 671 if (!blocking_dns_) { |
| 672 // Check if the DNS result can be serviced directly from the cache. |
| 673 // (The worker thread is blocked waiting for this information). |
| 674 if (completed_synchronously) { |
| 675 SaveDnsToLocalCache(host, op, result, pending_dns_addresses_); |
| 676 pending_dns_ = NULL; |
| 677 } |
| 678 |
| 679 // Important: Do not read/write |out_cache_hit| after signalling, since |
| 680 // the memory may no longer be valid. |
| 681 *out_cache_hit = completed_synchronously; |
| 682 event_.Signal(); |
| 683 |
| 684 if (completed_synchronously) |
| 685 return; |
| 686 } |
| 687 |
| 688 pending_dns_host_ = host; |
| 689 pending_dns_op_ = op; |
| 690 |
| 691 if (completed_synchronously) |
| 692 OnDnsOperationComplete(result); |
| 693 } |
| 694 |
| 695 void ProxyResolverV8Tracing::Job::OnDnsOperationComplete(int result) { |
| 696 CheckIsOnOriginThread(); |
| 697 |
| 698 DCHECK(pending_dns_); |
| 699 DCHECK(!cancelled_.IsSet()); |
| 700 |
| 701 SaveDnsToLocalCache(pending_dns_host_, pending_dns_op_, result, |
| 702 pending_dns_addresses_); |
| 703 pending_dns_ = NULL; |
| 704 |
| 705 if (!blocking_dns_) { |
| 706 // Restart. This time it should make more progress due to having |
| 707 // cached items. |
| 708 worker_loop()->PostTask(FROM_HERE, |
| 709 base::Bind(&Job::ExecuteNonBlocking, this)); |
| 710 } else { |
| 711 // Otherwise wakeup the blocked worker thread. |
| 712 event_.Signal(); |
| 713 } |
| 714 } |
| 715 |
| 716 void ProxyResolverV8Tracing::Job::ScheduleRestartWithBlockingDns() { |
| 717 CheckIsOnWorkerThread(); |
| 718 |
| 719 DCHECK(!should_restart_with_blocking_dns_); |
| 720 DCHECK(!abandoned_); |
| 721 DCHECK(!blocking_dns_); |
| 722 |
| 723 abandoned_ = true; |
| 724 |
| 725 // The restart will happen after ExecuteNonBlocking() finishes. |
| 726 should_restart_with_blocking_dns_ = true; |
| 727 } |
| 728 |
| 729 bool ProxyResolverV8Tracing::Job::GetDnsFromLocalCache( |
| 730 const std::string& host, |
| 731 ResolveDnsOperation op, |
| 732 std::string* output, |
| 733 bool* return_value) { |
| 734 CheckIsOnWorkerThread(); |
| 735 |
| 736 DnsCache::const_iterator it = dns_cache_.find(MakeDnsCacheKey(host, op)); |
| 737 if (it == dns_cache_.end()) |
| 738 return false; |
| 739 |
| 740 *output = it->second; |
| 741 *return_value = !it->second.empty(); |
| 742 return true; |
| 743 } |
| 744 |
| 745 void ProxyResolverV8Tracing::Job::SaveDnsToLocalCache( |
| 746 const std::string& host, |
| 747 ResolveDnsOperation op, |
| 748 int net_error, |
| 749 const net::AddressList& addresses) { |
| 750 CheckIsOnOriginThread(); |
| 751 |
| 752 // Serialize the result into a string to save to the cache. |
| 753 std::string cache_value; |
| 754 if (net_error != OK) { |
| 755 cache_value = std::string(); |
| 756 } else if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) { |
| 757 // dnsResolve() and myIpAddress() are expected to return a single IP |
| 758 // address. |
| 759 cache_value = addresses.front().ToStringWithoutPort(); |
| 760 } else { |
| 761 // The *Ex versions are expected to return a semi-colon separated list. |
| 762 for (AddressList::const_iterator iter = addresses.begin(); |
| 763 iter != addresses.end(); ++iter) { |
| 764 if (!cache_value.empty()) |
| 765 cache_value += ";"; |
| 766 cache_value += iter->ToStringWithoutPort(); |
| 767 } |
| 768 } |
| 769 |
| 770 dns_cache_[MakeDnsCacheKey(host, op)] = cache_value; |
| 771 } |
| 772 |
| 773 // static |
| 774 HostResolver::RequestInfo ProxyResolverV8Tracing::Job::MakeDnsRequestInfo( |
| 775 const std::string& host, ResolveDnsOperation op) { |
| 776 HostPortPair host_port = HostPortPair(host, 80); |
| 777 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) { |
| 778 host_port.set_host(GetHostName()); |
| 779 } |
| 780 |
| 781 HostResolver::RequestInfo info(host_port); |
| 782 |
| 783 // The non-ex flavors are limited to IPv4 results. |
| 784 if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) { |
| 785 info.set_address_family(ADDRESS_FAMILY_IPV4); |
| 786 } |
| 787 |
| 788 return info; |
| 789 } |
| 790 |
| 791 std::string ProxyResolverV8Tracing::Job::MakeDnsCacheKey( |
| 792 const std::string& host, ResolveDnsOperation op) { |
| 793 return StringPrintf("%d:%s", op, host.c_str()); |
| 794 } |
| 795 |
| 796 void ProxyResolverV8Tracing::Job::HandleAlertOrError(bool is_alert, |
| 797 int line_number, |
| 798 const string16& message) { |
| 799 CheckIsOnWorkerThread(); |
| 800 |
| 801 if (cancelled_.IsSet()) |
| 802 return; |
| 803 |
| 804 if (blocking_dns_) { |
| 805 // In blocking DNS mode the events can be dispatched immediately. |
| 806 DispatchAlertOrError(is_alert, line_number, message); |
| 807 return; |
| 808 } |
| 809 |
| 810 // Otherwise in nonblocking mode, buffer all the messages until |
| 811 // the end. |
| 812 |
| 813 if (abandoned_) |
| 814 return; |
| 815 |
| 816 alerts_and_errors_byte_cost_ += sizeof(AlertOrError) + message.size() * 2; |
| 817 |
| 818 // If there have been lots of messages, enqueing could be expensive on |
| 819 // memory. Consider a script which does megabytes worth of alerts(). |
| 820 // Avoid this by falling back to blocking mode. |
| 821 if (alerts_and_errors_byte_cost_ > kMaxAlertsAndErrorsBytes) { |
| 822 ScheduleRestartWithBlockingDns(); |
| 823 return; |
| 824 } |
| 825 |
| 826 AlertOrError entry = {is_alert, line_number, message}; |
| 827 alerts_and_errors_.push_back(entry); |
| 828 } |
| 829 |
| 830 void ProxyResolverV8Tracing::Job::DispatchBufferedAlertsAndErrors() { |
| 831 CheckIsOnWorkerThread(); |
| 832 DCHECK(!blocking_dns_); |
| 833 DCHECK(!abandoned_); |
| 834 |
| 835 for (size_t i = 0; i < alerts_and_errors_.size(); ++i) { |
| 836 const AlertOrError& x = alerts_and_errors_[i]; |
| 837 DispatchAlertOrError(x.is_alert, x.line_number, x.message); |
| 838 } |
| 839 } |
| 840 |
| 841 void ProxyResolverV8Tracing::Job::DispatchAlertOrError( |
| 842 bool is_alert, int line_number, const string16& message) { |
| 843 CheckIsOnWorkerThread(); |
| 844 |
| 845 // Note that the handling of cancellation is racy with regard to |
| 846 // alerts/errors. The request might get cancelled shortly after this |
| 847 // check! (There is no lock being held to guarantee otherwise). |
| 848 // |
| 849 // If this happens, then some information will get written to the NetLog |
| 850 // needlessly, however the NetLog will still be alive so it shouldn't cause |
| 851 // problems. |
| 852 if (cancelled_.IsSet()) |
| 853 return; |
| 854 |
| 855 if (is_alert) { |
| 856 // ------------------- |
| 857 // alert |
| 858 // ------------------- |
| 859 VLOG(1) << "PAC-alert: " << message; |
| 860 |
| 861 // Send to the NetLog. |
| 862 LogEventToCurrentRequestAndGlobally( |
| 863 NetLog::TYPE_PAC_JAVASCRIPT_ALERT, |
| 864 NetLog::StringCallback("message", &message)); |
| 865 } else { |
| 866 // ------------------- |
| 867 // error |
| 868 // ------------------- |
| 869 if (line_number == -1) |
| 870 VLOG(1) << "PAC-error: " << message; |
| 871 else |
| 872 VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message; |
| 873 |
| 874 // Send the error to the NetLog. |
| 875 LogEventToCurrentRequestAndGlobally( |
| 876 NetLog::TYPE_PAC_JAVASCRIPT_ERROR, |
| 877 base::Bind(&NetLogErrorCallback, line_number, &message)); |
| 878 |
| 879 if (error_observer()) |
| 880 error_observer()->OnPACScriptError(line_number, message); |
| 881 } |
| 882 } |
| 883 |
| 884 void ProxyResolverV8Tracing::Job::LogEventToCurrentRequestAndGlobally( |
| 885 NetLog::EventType type, |
| 886 const NetLog::ParametersCallback& parameters_callback) { |
| 887 CheckIsOnWorkerThread(); |
| 888 bound_net_log_.AddEvent(type, parameters_callback); |
| 889 |
| 890 // Emit to the global NetLog event stream. |
| 891 if (net_log()) |
| 892 net_log()->AddGlobalEntry(type, parameters_callback); |
| 893 } |
| 894 |
| 895 ProxyResolverV8Tracing::ProxyResolverV8Tracing( |
| 896 HostResolver* host_resolver, |
| 897 ProxyResolverErrorObserver* error_observer, |
| 898 NetLog* net_log) |
| 899 : ProxyResolver(true /*expects_pac_bytes*/), |
| 900 host_resolver_(host_resolver), |
| 901 error_observer_(error_observer), |
| 902 net_log_(net_log), |
| 903 num_outstanding_callbacks_(0) { |
| 904 DCHECK(host_resolver); |
| 905 // Start up the thread. |
| 906 thread_.reset(new base::Thread("Proxy resolver")); |
| 907 CHECK(thread_->Start()); |
| 908 |
| 909 v8_resolver_.reset(new ProxyResolverV8); |
| 910 } |
| 911 |
| 912 ProxyResolverV8Tracing::~ProxyResolverV8Tracing() { |
| 913 // Note, all requests should have been cancelled. |
| 914 CHECK(!set_pac_script_job_); |
| 915 CHECK_EQ(0, num_outstanding_callbacks_); |
| 916 |
| 917 // Join the worker thread. |
| 918 // See http://crbug.com/69710. |
| 919 base::ThreadRestrictions::ScopedAllowIO allow_io; |
| 920 thread_.reset(); |
| 921 } |
| 922 |
| 923 int ProxyResolverV8Tracing::GetProxyForURL(const GURL& url, |
| 924 ProxyInfo* results, |
| 925 const CompletionCallback& callback, |
| 926 RequestHandle* request, |
| 927 const BoundNetLog& net_log) { |
| 928 DCHECK(CalledOnValidThread()); |
| 929 DCHECK(!callback.is_null()); |
| 930 DCHECK(!set_pac_script_job_); |
| 931 |
| 932 scoped_refptr<Job> job = new Job(this); |
| 933 |
| 934 if (request) |
| 935 *request = job.get(); |
| 936 |
| 937 job->StartGetProxyForURL(url, results, net_log, callback); |
| 938 return ERR_IO_PENDING; |
| 939 } |
| 940 |
| 941 void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) { |
| 942 Job* job = reinterpret_cast<Job*>(request); |
| 943 job->Cancel(); |
| 944 } |
| 945 |
| 946 LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const { |
| 947 Job* job = reinterpret_cast<Job*>(request); |
| 948 return job->GetLoadState(); |
| 949 } |
| 950 |
| 951 void ProxyResolverV8Tracing::CancelSetPacScript() { |
| 952 DCHECK(set_pac_script_job_); |
| 953 set_pac_script_job_->Cancel(); |
| 954 set_pac_script_job_ = NULL; |
| 955 } |
| 956 |
| 957 void ProxyResolverV8Tracing::PurgeMemory() { |
| 958 thread_->message_loop()->PostTask( |
| 959 FROM_HERE, |
| 960 base::Bind(&ProxyResolverV8::PurgeMemory, |
| 961 // The use of unretained is safe, since the worker thread |
| 962 // cannot outlive |this|. |
| 963 base::Unretained(v8_resolver_.get()))); |
| 964 } |
| 965 |
| 966 int ProxyResolverV8Tracing::SetPacScript( |
| 967 const scoped_refptr<ProxyResolverScriptData>& script_data, |
| 968 const CompletionCallback& callback) { |
| 969 DCHECK(CalledOnValidThread()); |
| 970 DCHECK(!callback.is_null()); |
| 971 |
| 972 // Note that there should not be any outstanding (non-cancelled) Jobs when |
| 973 // setting the PAC script (ProxyService should guarantee this). If there are, |
| 974 // then they might complete in strange ways after the new script is set. |
| 975 DCHECK(!set_pac_script_job_); |
| 976 CHECK_EQ(0, num_outstanding_callbacks_); |
| 977 |
| 978 set_pac_script_job_ = new Job(this); |
| 979 set_pac_script_job_->StartSetPacScript(script_data, callback); |
| 980 |
| 981 return ERR_IO_PENDING; |
| 982 } |
| 983 |
| 984 } // namespace net |
OLD | NEW |