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 | |
mmenke
2013/01/18 19:59:40
"Sorry, the file you have requested does not exist
eroman
2013/01/23 03:26:02
Are you sure you copied the full line exactly? (it
mmenke
2013/01/24 21:06:33
Oops...A straight copy gets the spaces inserted by
| |
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 synchronous or | |
89 // asynchronous mode. Synchronous mode is used as a fallback when the PAC script | |
90 // seems to be misbehaving under the tracing optimization. | |
mmenke
2013/01/18 19:59:40
You call these blocking and non-blocking elsewhere
eroman
2013/01/23 03:26:02
Done -- renamed everything to blocking/non-blockin
| |
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 lifetiime of Jobs does not exceed that of the ProxyResolverV8Tracing that | |
mmenke
2013/01/18 19:59:40
nit: lifetime
eroman
2013/01/23 03:26:02
Done.
| |
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. | |
105 explicit Job(ProxyResolverV8Tracing* parent) | |
106 : origin_loop_(base::MessageLoopProxy::current()), | |
107 parent_(parent), | |
108 event_(true, false) { | |
109 } | |
110 | |
111 void StartSetPacScript( | |
112 const scoped_refptr<ProxyResolverScriptData>& script_data, | |
113 const CompletionCallback& callback) { | |
mmenke
2013/01/18 19:59:40
I recommend de-inlining all these functions. With
eroman
2013/01/23 03:26:02
Done.
| |
114 CheckIsOnOriginThread(); | |
115 | |
116 operation_ = SET_PAC_SCRIPT; | |
117 // Script initialization uses a synchronous DNS since there isn't any | |
118 // advantage to using asynchronous mode here. That is because the | |
119 // parent ProxyService can't submit any ProxyResolve requests until | |
120 // initialization has completed successfully! | |
mmenke
2013/01/18 19:59:40
Setting the PAC can call scripts?
eroman
2013/01/23 03:26:02
Setting the PAC involves "sourcing" the script. It
| |
121 nonblocking_dns_ = false; | |
122 | |
123 script_data_ = script_data; | |
124 callback_ = callback; | |
125 | |
126 PostStart(); | |
127 } | |
128 | |
129 void StartGetProxyForURL(const GURL& url, | |
130 ProxyInfo* results, | |
131 const BoundNetLog& net_log, | |
132 const CompletionCallback& callback) { | |
133 CheckIsOnOriginThread(); | |
134 | |
135 operation_ = GET_PROXY_FOR_URL; | |
136 nonblocking_dns_ = true; | |
137 | |
138 url_ = url; | |
139 user_results_ = results; | |
140 bound_net_log_ = net_log; | |
141 callback_ = callback; | |
142 | |
143 last_num_dns_ = 0; | |
144 | |
145 PostStart(); | |
146 } | |
147 | |
148 void Cancel() { | |
149 CheckIsOnOriginThread(); | |
150 | |
151 // There are three main possibility to consider for cancellation: | |
152 // (a) The job has been posted to the worker thread, but not yet started | |
153 // (b) The script is executing on the worker thread | |
154 // (c) The script is blocked waiting for DNS | |
mmenke
2013/01/18 19:59:40
(d) The script has stopped, and there's a pending
eroman
2013/01/23 03:26:02
Done.
| |
155 // | |
156 // |cancelled_| is read on both the origin thread and worker thread. The | |
157 // code that runs on the worker thread is littered with checks on | |
158 // |cancelled_| to break out early. Breaking out early is not necessary | |
159 // for correctness, however it does avoid doing unecessary work. | |
mmenke
2013/01/18 19:59:40
It is necessary to in the blocking case, isn't it?
eroman
2013/01/23 03:26:02
Yes that is true, cancellation which interrupts a
| |
160 // | |
161 // The important check on |cancelled_| is done from the origin thread in | |
162 // NotifyCallerOnOriginLoop(). | |
163 cancelled_.Set(); | |
164 | |
165 // These two assignments are not necessary for correctness. Just defensive | |
166 // programming to make sure the user-provided data is not used beyond this | |
167 // point. | |
168 user_results_ = NULL; | |
169 callback_ = CompletionCallback(); | |
170 | |
171 if (pending_dns_.get()) { | |
mmenke
2013/01/18 19:59:40
nit: Preferred style is not to use get().
eroman
2013/01/23 03:26:02
Done.
| |
172 host_resolver()->CancelRequest(pending_dns_->handle); | |
173 pending_dns_.reset(); | |
174 } | |
175 | |
176 // The worker thread might be blocked waiting for DNS. | |
177 event_.Signal(); | |
178 | |
179 Release(); | |
180 } | |
181 | |
182 LoadState GetLoadState() const { | |
183 CheckIsOnOriginThread(); | |
184 | |
185 if (pending_dns_.get()) | |
mmenke
2013/01/18 19:59:40
nit: Preferred style is not to use get().
eroman
2013/01/23 03:26:02
Done.
| |
186 return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT; | |
187 | |
188 return LOAD_STATE_RESOLVING_PROXY_FOR_URL; | |
189 } | |
190 | |
191 private: | |
192 friend class base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>; | |
193 | |
194 enum Operation { | |
195 SET_PAC_SCRIPT, | |
196 GET_PROXY_FOR_URL, | |
197 }; | |
198 | |
199 struct AlertOrError { | |
200 AlertOrError(bool is_alert, int line_number, const string16& message) | |
201 : is_alert(is_alert), line_number(line_number), message(message) {} | |
202 | |
203 bool is_alert; | |
204 int line_number; | |
205 string16 message; | |
mmenke
2013/01/18 19:59:40
Could you make these const? Not sure if that woul
eroman
2013/01/23 03:26:02
Yeah const makes std::vector sad, because push_bac
| |
206 }; | |
207 | |
208 struct ExecutionState { | |
209 bool was_abandoned; | |
210 | |
211 // The number of times ResolveDns() was called so far. | |
212 int num_dns; | |
213 base::MD5Context dns_digest; | |
214 | |
215 size_t alerts_and_errors_bytes_; | |
216 std::vector<AlertOrError> alerts_and_errors_; | |
217 }; | |
218 | |
219 struct PendingDns { | |
220 PendingDns(const std::string& host, ResolveDnsOperation op) | |
221 : host(host), op(op), handle(NULL) {} | |
222 | |
223 std::string host; | |
224 ResolveDnsOperation op; | |
225 | |
226 HostResolver::RequestHandle handle; | |
227 net::AddressList addresses; | |
228 }; | |
229 | |
230 ~Job() { | |
231 DCHECK(!pending_dns_.get()); | |
232 } | |
233 | |
234 void CheckIsOnWorkerThread() { | |
235 DCHECK_EQ(MessageLoop::current(), parent_->thread_->message_loop()); | |
236 } | |
237 | |
238 void CheckIsOnOriginThread() const { | |
239 DCHECK(origin_loop_->BelongsToCurrentThread()); | |
240 } | |
241 | |
242 ProxyResolverV8* v8_resolver() { | |
243 return parent_->v8_resolver_.get(); | |
244 } | |
245 | |
246 MessageLoop* worker_loop() { | |
247 return parent_->thread_->message_loop(); | |
248 } | |
249 | |
250 HostResolver* host_resolver() { | |
251 return parent_->host_resolver_; | |
252 } | |
253 | |
254 ProxyResolverErrorObserver* error_observer() { | |
255 return parent_->error_observer_.get(); | |
256 } | |
257 | |
258 NetLog* net_log() { | |
259 return parent_->net_log_; | |
260 } | |
261 | |
262 void PostStart() { | |
263 AddRef(); // Balanced on completion, or cancellation. | |
264 worker_loop()->PostTask(FROM_HERE, base::Bind(&Job::Start, this)); | |
265 } | |
266 | |
267 // Post a task to the origin thread, to invoke the user's callback. | |
268 void NotifyCaller(int result) { | |
269 CheckIsOnWorkerThread(); | |
270 | |
271 origin_loop_->PostTask( | |
272 FROM_HERE, | |
273 base::Bind(&Job::NotifyCallerOnOriginLoop, this, result)); | |
274 } | |
275 | |
276 void NotifyCallerOnOriginLoop(int result) { | |
277 CheckIsOnOriginThread(); | |
278 | |
279 if (cancelled_.IsSet()) | |
280 return; | |
281 | |
282 if (operation_ == GET_PROXY_FOR_URL) | |
283 *user_results_ = results_; | |
284 | |
285 // There is only ever 1 outstanding SET_PAC_SCRIPT job. It needs to be | |
286 // tracked to support cancellation. | |
287 if (operation_ == SET_PAC_SCRIPT) { | |
288 DCHECK(parent_->set_pac_script_job_); | |
289 parent_->set_pac_script_job_ = NULL; | |
290 } | |
291 | |
292 callback_.Run(result); | |
293 callback_.Reset(); | |
294 | |
295 Release(); | |
296 } | |
297 | |
298 void Start() { | |
299 CheckIsOnWorkerThread(); | |
300 | |
301 if (cancelled_.IsSet()) | |
302 return; | |
303 | |
304 if (nonblocking_dns_) { | |
305 // Reset state pertaining to the current playthrough. | |
306 exec_.was_abandoned = false; | |
307 exec_.num_dns = 0; | |
308 base::MD5Init(&exec_.dns_digest); | |
309 exec_.alerts_and_errors_.clear(); | |
310 exec_.alerts_and_errors_bytes_ = 0; | |
311 } | |
312 | |
313 JSBindings* prev_bindings = v8_resolver()->js_bindings(); | |
314 v8_resolver()->set_js_bindings(this); | |
315 | |
316 int result = ERR_UNEXPECTED; // Initialized to silence warnings. | |
317 | |
318 switch (operation_) { | |
319 case SET_PAC_SCRIPT: | |
320 result = v8_resolver()->SetPacScript( | |
321 script_data_, CompletionCallback()); | |
322 break; | |
323 case GET_PROXY_FOR_URL: | |
324 result = v8_resolver()->GetProxyForURL( | |
325 url_, | |
326 // Important: Do not write directly into |user_results_|, since if the | |
327 // request were to be cancelled from the origin thread, must guarantee | |
328 // that |user_results| is not accessed anymore must guarantee that | |
329 // |user_results| is not accessed anymore. | |
330 &results_, | |
331 CompletionCallback(), | |
332 NULL, | |
333 bound_net_log_); | |
334 break; | |
335 } | |
336 | |
337 v8_resolver()->set_js_bindings(prev_bindings); | |
338 | |
339 if (nonblocking_dns_) { | |
340 if (!exec_.was_abandoned) { | |
341 // Some alerts() and errors may have been buffered, since it was not | |
342 // known at the time whether this run would be discarded. Now that it is | |
343 // known to be a full run, dispatch those buffered events. | |
344 DispatchBufferedAlertsAndErrors(); | |
345 NotifyCaller(result); | |
346 } | |
347 } else { | |
348 NotifyCaller(result); | |
349 } | |
350 } | |
351 | |
352 // Handler for "alert(message)" | |
353 virtual void Alert(const string16& message) OVERRIDE { | |
354 HandleAlertOrError(true, -1, message); | |
355 } | |
356 | |
357 // Handler for when an error is encountered. |line_number| may be -1 | |
358 // if a line number is not applicable to this error. | |
359 virtual void OnError(int line_number, const string16& error) OVERRIDE { | |
360 HandleAlertOrError(false, line_number, error); | |
361 } | |
362 | |
363 void HandleAlertOrError(bool is_alert, int line_number, | |
364 const string16& message) { | |
365 if (cancelled_.IsSet()) | |
366 return; | |
367 | |
368 if (!nonblocking_dns_) { | |
369 // In blocking DNS mode the events can be dispatched immediately. | |
370 DispatchAlertOrError(is_alert, line_number, message); | |
371 return; | |
372 } | |
373 | |
374 // Otherwise in nonblocking mode, buffer all the messages until | |
375 // the end. | |
376 | |
377 if (exec_.was_abandoned) | |
378 return; | |
379 | |
380 exec_.alerts_and_errors_bytes_ += message.size() * 2; | |
381 | |
382 // If there have been lots of messages, enqueuing could be expensive on | |
383 // memory. (consider a script which does megabytes worth of alerts()). | |
384 // Avoid this by falling back to blocking mode. | |
385 if (exec_.alerts_and_errors_bytes_ > kMaxAlertsAndErrorsBytes) { | |
386 ScheduleRestartWithBlockingDns(); | |
387 exec_.was_abandoned = true; | |
388 return; | |
389 } | |
390 | |
391 exec_.alerts_and_errors_.push_back( | |
392 AlertOrError(is_alert, line_number, message)); | |
393 } | |
394 | |
395 void DispatchBufferedAlertsAndErrors() { | |
396 DCHECK(nonblocking_dns_); | |
397 DCHECK(!exec_.was_abandoned); | |
398 | |
399 for (size_t i = 0; i < exec_.alerts_and_errors_.size(); ++i) { | |
400 const AlertOrError& x = exec_.alerts_and_errors_[i]; | |
401 DispatchAlertOrError(x.is_alert, x.line_number, x.message); | |
402 } | |
403 } | |
404 | |
405 void DispatchAlertOrError(bool is_alert, int line_number, | |
406 const string16& message) { | |
407 // Note that the handling of cancellation is racy with regard to | |
408 // alerts/errors. The request might get cancelled shortly after this | |
409 // check! (There is no lock being held to guarantee otherwise). | |
410 // | |
411 // If this happens, then some information will get written to the NetLog | |
412 // needlessly, however the NetLog will still be alive so it shouldn't cause | |
413 // problems. | |
414 if (cancelled_.IsSet()) | |
415 return; | |
416 | |
417 if (is_alert) { | |
418 // ------------------- | |
419 // alert | |
420 // ------------------- | |
421 VLOG(1) << "PAC-alert: " << message; | |
422 | |
423 // Send to the NetLog. | |
424 LogEventToCurrentRequestAndGlobally( | |
425 NetLog::TYPE_PAC_JAVASCRIPT_ALERT, | |
426 NetLog::StringCallback("message", &message)); | |
427 } else { | |
428 // ------------------- | |
429 // error | |
430 // ------------------- | |
431 if (line_number == -1) | |
432 VLOG(1) << "PAC-error: " << message; | |
433 else | |
434 VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message; | |
435 | |
436 // Send the error to the NetLog. | |
437 LogEventToCurrentRequestAndGlobally( | |
438 NetLog::TYPE_PAC_JAVASCRIPT_ERROR, | |
439 base::Bind(&NetLogErrorCallback, line_number, &message)); | |
440 | |
441 if (error_observer()) | |
442 error_observer()->OnPACScriptError(line_number, message); | |
443 } | |
444 } | |
445 | |
446 void LogEventToCurrentRequestAndGlobally( | |
mmenke
2013/01/18 19:59:40
nit: Fix indent.
eroman
2013/01/23 03:26:02
Done.
| |
447 NetLog::EventType type, | |
448 const NetLog::ParametersCallback& parameters_callback) { | |
449 bound_net_log_.AddEvent(type, parameters_callback); | |
450 | |
451 // Emit to the global NetLog event stream. | |
452 if (net_log()) | |
453 net_log()->AddGlobalEntry(type, parameters_callback); | |
454 } | |
455 | |
456 virtual bool ResolveDns(const std::string& host, | |
457 ResolveDnsOperation op, | |
458 std::string* output) { | |
459 if (cancelled_.IsSet()) | |
460 return false; | |
461 | |
462 if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) { | |
463 // a DNS resolve with an empty hostname is considered an error. | |
464 return false; | |
465 } | |
466 | |
467 return nonblocking_dns_ ? | |
468 ResolveDnsNonBlocking(host, op, output) : | |
469 ResolveDnsBlocking(host, op, output); | |
mmenke
2013/01/18 19:59:40
nit: 4 space indent.
eroman
2013/01/23 03:26:02
Done.
| |
470 } | |
471 | |
472 bool ResolveDnsBlocking(const std::string& host, | |
473 ResolveDnsOperation op, | |
474 std::string* output) { | |
475 CheckIsOnWorkerThread(); | |
476 | |
477 // Check if the DNS result for this host has already been cached. | |
478 bool rv; | |
479 if (GetDnsFromLocalCache(host, op, output, &rv)) { | |
480 // Yay, cache hit! | |
481 return rv; | |
482 } | |
483 | |
484 // If the host was not in the local cache, this is a new hostname. | |
485 | |
486 if (dns_cache_.size() > kMaxUniqueResolveDnsPerExec) { | |
487 // Safety net for scripts with unexpectedly many DNS calls. | |
488 // We will continue running to completion, but will fail every | |
489 // subsequent DNS request. | |
490 return false; | |
491 } | |
492 | |
493 bool unused; | |
494 origin_loop_->PostTask( | |
495 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op, &unused)); | |
496 | |
497 // Wait for the DNS operation to be completed by the host resolver on the | |
498 // origin thread. After waking up, either the request was cancelled, or | |
499 // the DNS result is now available in the cache. | |
500 event_.Wait(); | |
501 event_.Reset(); | |
502 | |
503 if (cancelled_.IsSet()) | |
504 return false; | |
505 | |
506 CHECK(GetDnsFromLocalCache(host, op, output, &rv)); | |
507 return rv; | |
508 } | |
509 | |
510 // To detect inconsistencies, the past sequence of ResovleDns operations are | |
511 // saved and enforced during each replay. Instead of saving the full sequence | |
512 // of hostnames, just an MD5 checksum is kept (to avoid unbounded memory | |
513 // growth). | |
514 bool ResolveDnsNonBlocking(const std::string& host, | |
515 ResolveDnsOperation op, | |
516 std::string* output) { | |
517 CheckIsOnWorkerThread(); | |
518 | |
519 if (exec_.was_abandoned) { | |
520 // If this execution was alread abandoned can fail right away. Only 1 DNS | |
mmenke
2013/01/18 19:59:40
nit "already"
eroman
2013/01/23 03:26:02
Done.
| |
521 // dependency will be traced at a time (for more predictable outcomes). | |
522 return false; | |
523 } | |
524 | |
525 exec_.num_dns += 1; | |
526 | |
527 // Keep a digest of the sequence of DNS requests this execution has seen so | |
528 // far. This is cheaper than keeping a full list of the history, and good | |
529 // enough for our purposes (we want to detect when things have gone wrong in | |
530 // order to fallback to synchronous mode). | |
531 base::MD5Update(&exec_.dns_digest, host); | |
532 base::MD5Update(&exec_.dns_digest, base::IntToString(op)); | |
mmenke
2013/01/18 19:59:40
Does this really get us anything we care about ove
eroman
2013/01/23 03:26:02
I chose to enforce the sequence of all DNS request
mmenke
2013/01/24 21:06:33
I was just thinking that it's not too likely that
eroman
2013/01/24 22:13:58
Thanks for discussing this issue!
Indeed, none of
| |
533 | |
534 // Upon reaching the point of execution from the last run, make sure | |
535 // that the sequence of resolves has matched so far. | |
536 if (exec_.num_dns == last_num_dns_ && | |
537 !MD5DigestEquals(last_dns_digest_, exec_.dns_digest)) { | |
538 // The sequence of DNS operations is different from last time! | |
539 ScheduleRestartWithBlockingDns(); | |
mmenke
2013/01/18 19:59:40
Restarting instead of just switching is primarily
eroman
2013/01/23 03:26:02
Correct, restarting is to keep things simple. Ther
mmenke
2013/01/24 21:06:33
I was thinking that you could get rid of "Schedule
| |
540 exec_.was_abandoned = true; | |
541 return false; | |
542 } | |
543 | |
544 // Check if the DNS result for this host has already been cached. | |
545 bool rv; | |
546 if (GetDnsFromLocalCache(host, op, output, &rv)) { | |
547 // Yay, cache hit! | |
548 return rv; | |
549 } | |
550 | |
551 // If the host was not in the local cache, then this is a new hostname. | |
552 | |
553 if (exec_.num_dns <= last_num_dns_) { | |
554 // The sequence of DNS operations is different from last time! | |
555 ScheduleRestartWithBlockingDns(); | |
556 exec_.was_abandoned = true; | |
557 return false; | |
558 } | |
559 | |
560 if (dns_cache_.size() > kMaxUniqueResolveDnsPerExec) { | |
561 // Safety net for scripts with unexpectedly many DNS calls. | |
562 return false; | |
563 } | |
564 | |
565 // Post the DNS request to the origin thread. | |
566 bool resolver_cache_hit = false; | |
567 origin_loop_->PostTask( | |
568 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op, | |
569 &resolver_cache_hit)); | |
570 | |
571 // As an optimization to avoid restarting too often, wait until the | |
572 // resolver's cache has been inspected on the origin thread. | |
573 event_.Wait(); | |
574 event_.Reset(); | |
575 | |
576 if (cancelled_.IsSet()) | |
577 return false; | |
578 | |
579 if (resolver_cache_hit) { | |
580 CHECK(GetDnsFromLocalCache(host, op, output, &rv)); | |
581 return rv; | |
582 } | |
583 | |
584 // Otherwise if the result was not in the cache, then a DNS request has | |
585 // been started. Abandon this invocation of FindProxyForURL(), it will be | |
586 // restarted once the DNS request completes. | |
587 exec_.was_abandoned = true; | |
588 last_num_dns_ = exec_.num_dns; | |
589 base::MD5Final(&last_dns_digest_, &exec_.dns_digest); | |
590 return false; | |
591 } | |
592 | |
593 // Builds a RequestInfo to service the specified PAC DNS operation. | |
594 static HostResolver::RequestInfo MakeRequestInfo(const std::string& host, | |
595 ResolveDnsOperation op) { | |
596 HostPortPair host_port = HostPortPair(host, 80); | |
597 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) { | |
598 host_port.set_host(GetHostName()); | |
599 } | |
600 | |
601 HostResolver::RequestInfo info(host_port); | |
602 | |
603 // The non-ex flavors are limited to IPv4 results. | |
604 if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) { | |
605 info.set_address_family(ADDRESS_FAMILY_IPV4); | |
606 } | |
607 | |
608 return info; | |
609 } | |
610 | |
611 void DoDnsOperation(const std::string& host, ResolveDnsOperation op, | |
612 bool* out_cache_hit) { | |
613 CheckIsOnOriginThread(); | |
614 | |
615 if (cancelled_.IsSet()) | |
616 return; | |
617 | |
618 DCHECK(!pending_dns_.get()); | |
619 | |
620 HostResolver::RequestInfo info = MakeRequestInfo(host, op); | |
621 | |
622 if (nonblocking_dns_) { | |
623 // Check if the DNS result can be serviced directly from the cache. | |
624 // (The worker thread is blocked waiting for this information). | |
625 net::AddressList addresses; | |
626 int result = host_resolver()->ResolveFromCache( | |
627 info, &addresses, bound_net_log_); | |
628 | |
629 bool cache_hit = result != ERR_DNS_CACHE_MISS; | |
630 if (cache_hit) { | |
631 // Cool, it was in the host resolver's cache. Copy it to the | |
632 // per-request cache. | |
633 SaveDnsToLocalCache(host, op, result, addresses); | |
634 } | |
635 | |
636 // Important: Do not read/write |out_cache_hit| after signalling, since | |
637 // the memory may no longer be valid. | |
638 *out_cache_hit = cache_hit; | |
639 event_.Signal(); | |
640 | |
641 if (cache_hit) | |
642 return; | |
mmenke
2013/01/18 19:59:40
I don't think this cache hit optimization is worth
eroman
2013/01/23 03:26:02
This cuts down quite a few restarts, and IMO is de
mmenke
2013/01/24 21:06:33
Suppose it depends on how expensive script executi
eroman
2013/01/24 23:07:15
One final comment on this issue:
Waiting for the
| |
643 } | |
644 | |
645 pending_dns_.reset(new PendingDns(host, op)); | |
646 | |
647 int result = host_resolver()->Resolve( | |
648 info, | |
649 &pending_dns_->addresses, base::Bind(&Job::OnResolveCompletion, this), | |
650 &pending_dns_->handle, bound_net_log_); | |
651 | |
652 if (result != ERR_IO_PENDING) | |
653 OnResolveCompletion(result); | |
654 } | |
655 | |
656 void OnResolveCompletion(int result) { | |
657 CheckIsOnOriginThread(); | |
658 | |
659 DCHECK(!cancelled_.IsSet()); | |
660 | |
661 SaveDnsToLocalCache(pending_dns_->host, pending_dns_->op, result, | |
662 pending_dns_->addresses); | |
663 pending_dns_.reset(); | |
664 | |
665 if (nonblocking_dns_) { | |
666 // Restart. This time it should make more progress due to having | |
667 // cached items. | |
668 worker_loop()->PostTask(FROM_HERE, base::Bind(&Job::Start, this)); | |
669 } else { | |
670 // Otherwise wakeup the blocked worker thread. | |
671 event_.Signal(); | |
672 } | |
673 } | |
674 | |
675 void ScheduleRestartWithBlockingDns() { | |
676 MessageLoop::current()->PostTask( | |
677 FROM_HERE, base::Bind(&Job::RestartWithBlockingDns, this)); | |
678 } | |
679 | |
680 void RestartWithBlockingDns() { | |
681 DCHECK(nonblocking_dns_); | |
682 nonblocking_dns_ = false; | |
683 Start(); | |
684 } | |
685 | |
686 // Make a key for looking up |host, op| in |dns_cache_|. Strings are used for | |
687 // convenience, to avoid defining custom comparators. | |
688 static std::string GetCacheKey(const std::string& host, | |
689 ResolveDnsOperation op) { | |
690 return StringPrintf("%d:%s", op, host.c_str()); | |
691 } | |
692 | |
693 // Serialize |net_error, address_list| into something that can be saved into | |
694 // |dns_cache|. | |
695 static std::string MakeCacheValue(ResolveDnsOperation op, | |
696 int net_error, | |
697 const AddressList& address_list) { | |
698 if (net_error != OK) | |
699 return std::string(); | |
700 | |
701 // dnsResolve() and myIpAddress() are expected to return a single IP | |
702 // address. Whereas the *Ex versions are expected to return a semi-colon | |
703 // separated list. | |
704 | |
705 if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) { | |
706 return address_list.front().ToStringWithoutPort(); | |
707 } | |
708 | |
709 std::string address_list_str; | |
710 for (AddressList::const_iterator iter = address_list.begin(); | |
711 iter != address_list.end(); ++iter) { | |
712 if (!address_list_str.empty()) | |
713 address_list_str += ";"; | |
714 const std::string address_string = iter->ToStringWithoutPort(); | |
715 if (address_string.empty()) | |
716 return std::string(); | |
717 address_list_str += address_string; | |
718 } | |
719 | |
720 return address_list_str; | |
721 } | |
722 | |
723 bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op, | |
724 std::string* output, bool* return_value) { | |
725 DnsCache::const_iterator it = dns_cache_.find(GetCacheKey(host, op)); | |
726 if (it == dns_cache_.end()) | |
727 return false; | |
728 | |
729 *output = it->second; | |
730 *return_value = !it->second.empty(); | |
731 return true; | |
732 } | |
733 | |
734 void SaveDnsToLocalCache(const std::string& host, ResolveDnsOperation op, | |
735 int net_error, const net::AddressList& addresses) { | |
736 dns_cache_[GetCacheKey(host, op)] = | |
737 MakeCacheValue(op, net_error, addresses); | |
738 } | |
739 | |
740 Operation operation_; | |
741 bool nonblocking_dns_; | |
mmenke
2013/01/18 19:59:40
Wonder if this should be a property of the PAC scr
mmenke
2013/01/18 19:59:40
Suggest using blocking_dns_ instead. "!non_blocki
eroman
2013/01/23 03:26:02
To make sure I understand:
Do you mean keep track
eroman
2013/01/23 03:26:02
Done.
| |
742 | |
743 scoped_refptr<base::MessageLoopProxy> origin_loop_; | |
744 CompletionCallback callback_; | |
745 ProxyResolverV8Tracing* parent_; | |
746 base::CancellationFlag cancelled_; | |
747 | |
748 scoped_refptr<ProxyResolverScriptData> script_data_; | |
mmenke
2013/01/18 19:59:40
What do you think of splitting this class into two
mmenke
2013/01/18 20:02:39
If you think that's too ugly, clearly separating o
eroman
2013/01/23 03:26:02
I couldn't get it any cleaner splitting off into s
| |
749 | |
750 base::WaitableEvent event_; | |
751 | |
752 GURL url_; | |
753 ProxyInfo results_; | |
754 ProxyInfo* user_results_; | |
755 BoundNetLog bound_net_log_; | |
756 | |
757 ExecutionState exec_; | |
758 | |
759 int last_num_dns_; | |
760 base::MD5Digest last_dns_digest_; | |
761 | |
762 typedef std::map<std::string, std::string> DnsCache; | |
763 DnsCache dns_cache_; | |
764 | |
765 scoped_ptr<PendingDns> pending_dns_; | |
766 }; | |
767 | |
768 ProxyResolverV8Tracing::ProxyResolverV8Tracing( | |
769 HostResolver* host_resolver, | |
770 ProxyResolverErrorObserver* error_observer, | |
771 NetLog* net_log) | |
772 : ProxyResolver(true /*expects_pac_bytes*/), | |
773 host_resolver_(host_resolver), | |
774 error_observer_(error_observer), | |
775 net_log_(net_log) { | |
776 DCHECK(host_resolver); | |
777 // Start up the thread. | |
778 thread_.reset(new base::Thread("Proxy resolver")); | |
779 CHECK(thread_->Start()); | |
780 | |
781 v8_resolver_.reset(new ProxyResolverV8); | |
782 } | |
783 | |
784 ProxyResolverV8Tracing::~ProxyResolverV8Tracing() { | |
785 // Note, all requests should have been cancelled. | |
786 DCHECK(!set_pac_script_job_); | |
787 | |
788 // Join the worker thread. | |
789 // See http://crbug.com/69710. | |
790 base::ThreadRestrictions::ScopedAllowIO allow_io; | |
791 thread_.reset(); | |
792 } | |
793 | |
794 int ProxyResolverV8Tracing::GetProxyForURL(const GURL& url, | |
795 ProxyInfo* results, | |
796 const CompletionCallback& callback, | |
797 RequestHandle* request, | |
798 const BoundNetLog& net_log) { | |
799 DCHECK(CalledOnValidThread()); | |
800 DCHECK(!callback.is_null()); | |
801 | |
802 scoped_refptr<Job> job = new Job(this); | |
803 | |
804 if (request) | |
805 *request = job.get(); | |
806 | |
807 job->StartGetProxyForURL(url, results, net_log, callback); | |
808 return ERR_IO_PENDING; | |
809 } | |
810 | |
811 void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) { | |
812 Job* job = reinterpret_cast<Job*>(request); | |
813 job->Cancel(); | |
814 } | |
815 | |
816 LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const { | |
817 Job* job = reinterpret_cast<Job*>(request); | |
818 return job->GetLoadState(); | |
819 } | |
820 | |
821 void ProxyResolverV8Tracing::CancelSetPacScript() { | |
822 set_pac_script_job_->Cancel(); | |
823 set_pac_script_job_ = NULL; | |
824 } | |
825 | |
826 void ProxyResolverV8Tracing::PurgeMemory() { | |
827 thread_->message_loop()->PostTask( | |
828 FROM_HERE, | |
829 base::Bind(&ProxyResolverV8::PurgeMemory, | |
830 // The use of unretained is safe, since the worker thread | |
831 // cannot outlive |this|. | |
832 base::Unretained(v8_resolver_.get()))); | |
833 } | |
834 | |
835 int ProxyResolverV8Tracing::SetPacScript( | |
836 const scoped_refptr<ProxyResolverScriptData>& script_data, | |
837 const CompletionCallback& callback) { | |
838 DCHECK(CalledOnValidThread()); | |
839 DCHECK(!callback.is_null()); | |
840 | |
841 set_pac_script_job_ = new Job(this); | |
842 set_pac_script_job_->StartSetPacScript(script_data, callback); | |
843 | |
844 return ERR_IO_PENDING; | |
845 } | |
846 | |
847 } // namespace net | |
OLD | NEW |