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