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 |