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