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