OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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/single_threaded_proxy_resolver.h" | |
6 | |
7 #include "base/message_loop.h" | |
8 #include "base/thread.h" | |
9 #include "net/base/capturing_net_log.h" | |
10 #include "net/base/net_errors.h" | |
11 #include "net/proxy/proxy_info.h" | |
12 | |
13 namespace net { | |
14 | |
15 namespace { | |
16 | |
17 class PurgeMemoryTask : public base::RefCountedThreadSafe<PurgeMemoryTask> { | |
18 public: | |
19 explicit PurgeMemoryTask(ProxyResolver* resolver) : resolver_(resolver) {} | |
20 void PurgeMemory() { resolver_->PurgeMemory(); } | |
21 private: | |
22 friend class base::RefCountedThreadSafe<PurgeMemoryTask>; | |
23 ~PurgeMemoryTask() {} | |
24 ProxyResolver* resolver_; | |
25 }; | |
26 | |
27 } // namespace | |
28 | |
29 // SingleThreadedProxyResolver::SetPacScriptTask ------------------------------ | |
30 | |
31 // Runs on the worker thread to call ProxyResolver::SetPacScript. | |
32 class SingleThreadedProxyResolver::SetPacScriptTask | |
33 : public base::RefCountedThreadSafe< | |
34 SingleThreadedProxyResolver::SetPacScriptTask> { | |
35 public: | |
36 SetPacScriptTask(SingleThreadedProxyResolver* coordinator, | |
37 const GURL& pac_url, | |
38 const string16& pac_script, | |
39 CompletionCallback* callback) | |
40 : coordinator_(coordinator), | |
41 callback_(callback), | |
42 pac_script_(pac_script), | |
43 pac_url_(pac_url), | |
44 origin_loop_(MessageLoop::current()) { | |
45 DCHECK(callback); | |
46 } | |
47 | |
48 // Start the SetPacScript request on the worker thread. | |
49 void Start() { | |
50 coordinator_->thread()->message_loop()->PostTask( | |
51 FROM_HERE, NewRunnableMethod(this, &SetPacScriptTask::DoRequest, | |
52 coordinator_->resolver_.get())); | |
53 } | |
54 | |
55 void Cancel() { | |
56 // Clear these to inform RequestComplete that it should not try to | |
57 // access them. | |
58 coordinator_ = NULL; | |
59 callback_ = NULL; | |
60 } | |
61 | |
62 // Returns true if Cancel() has been called. | |
63 bool was_cancelled() const { return callback_ == NULL; } | |
64 | |
65 private: | |
66 friend class base::RefCountedThreadSafe< | |
67 SingleThreadedProxyResolver::SetPacScriptTask>; | |
68 | |
69 ~SetPacScriptTask() {} | |
70 | |
71 // Runs on the worker thread. | |
72 void DoRequest(ProxyResolver* resolver) { | |
73 int rv = resolver->expects_pac_bytes() ? | |
74 resolver->SetPacScriptByData(pac_script_, NULL) : | |
75 resolver->SetPacScriptByUrl(pac_url_, NULL); | |
76 | |
77 DCHECK_NE(rv, ERR_IO_PENDING); | |
78 origin_loop_->PostTask(FROM_HERE, | |
79 NewRunnableMethod(this, &SetPacScriptTask::RequestComplete, rv)); | |
80 } | |
81 | |
82 // Runs the completion callback on the origin thread. | |
83 void RequestComplete(int result_code) { | |
84 // The task may have been cancelled after it was started. | |
85 if (!was_cancelled()) { | |
86 CompletionCallback* callback = callback_; | |
87 coordinator_->RemoveOutstandingSetPacScriptTask(this); | |
88 callback->Run(result_code); | |
89 } | |
90 } | |
91 | |
92 // Must only be used on the "origin" thread. | |
93 SingleThreadedProxyResolver* coordinator_; | |
94 CompletionCallback* callback_; | |
95 string16 pac_script_; | |
96 GURL pac_url_; | |
97 | |
98 // Usable from within DoQuery on the worker thread. | |
99 MessageLoop* origin_loop_; | |
100 }; | |
101 | |
102 // SingleThreadedProxyResolver::Job ------------------------------------------- | |
103 | |
104 class SingleThreadedProxyResolver::Job | |
105 : public base::RefCountedThreadSafe<SingleThreadedProxyResolver::Job> { | |
106 public: | |
107 // |coordinator| -- the SingleThreadedProxyResolver that owns this job. | |
108 // |url| -- the URL of the query. | |
109 // |results| -- the structure to fill with proxy resolve results. | |
110 Job(SingleThreadedProxyResolver* coordinator, | |
111 const GURL& url, | |
112 ProxyInfo* results, | |
113 CompletionCallback* callback, | |
114 const BoundNetLog& net_log) | |
115 : coordinator_(coordinator), | |
116 callback_(callback), | |
117 results_(results), | |
118 net_log_(net_log), | |
119 url_(url), | |
120 is_started_(false), | |
121 origin_loop_(MessageLoop::current()) { | |
122 DCHECK(callback); | |
123 } | |
124 | |
125 // Start the resolve proxy request on the worker thread. | |
126 void Start() { | |
127 is_started_ = true; | |
128 | |
129 size_t load_log_bound = 100; | |
130 | |
131 coordinator_->thread()->message_loop()->PostTask( | |
132 FROM_HERE, NewRunnableMethod(this, &Job::DoQuery, | |
133 coordinator_->resolver_.get(), | |
134 load_log_bound)); | |
135 } | |
136 | |
137 bool is_started() const { return is_started_; } | |
138 | |
139 void Cancel() { | |
140 // Clear these to inform QueryComplete that it should not try to | |
141 // access them. | |
142 coordinator_ = NULL; | |
143 callback_ = NULL; | |
144 results_ = NULL; | |
145 } | |
146 | |
147 // Returns true if Cancel() has been called. | |
148 bool was_cancelled() const { return callback_ == NULL; } | |
149 | |
150 BoundNetLog* net_log() { return &net_log_; } | |
151 | |
152 private: | |
153 friend class base::RefCountedThreadSafe<SingleThreadedProxyResolver::Job>; | |
154 | |
155 ~Job() {} | |
156 | |
157 // Runs on the worker thread. | |
158 void DoQuery(ProxyResolver* resolver, size_t load_log_bound) { | |
159 worker_log_.reset(new CapturingNetLog(load_log_bound)); | |
160 BoundNetLog bound_worker_log(NetLog::Source(), worker_log_.get()); | |
161 | |
162 int rv = resolver->GetProxyForURL(url_, &results_buf_, NULL, NULL, | |
163 bound_worker_log); | |
164 DCHECK_NE(rv, ERR_IO_PENDING); | |
165 | |
166 origin_loop_->PostTask(FROM_HERE, | |
167 NewRunnableMethod(this, &Job::QueryComplete, rv)); | |
168 } | |
169 | |
170 // Runs the completion callback on the origin thread. | |
171 void QueryComplete(int result_code) { | |
172 // Merge the load log that was generated on the worker thread, into the | |
173 // main log. | |
174 CapturingBoundNetLog bound_worker_log(NetLog::Source(), | |
175 worker_log_.release()); | |
176 bound_worker_log.AppendTo(net_log_); | |
177 | |
178 // The Job may have been cancelled after it was started. | |
179 if (!was_cancelled()) { | |
180 if (result_code >= OK) { // Note: unit-tests use values > 0. | |
181 results_->Use(results_buf_); | |
182 } | |
183 callback_->Run(result_code); | |
184 | |
185 // We check for cancellation once again, in case the callback deleted | |
186 // the owning ProxyService (whose destructor will in turn cancel us). | |
187 if (!was_cancelled()) | |
188 coordinator_->RemoveFrontOfJobsQueueAndStartNext(this); | |
189 } | |
190 } | |
191 | |
192 // Must only be used on the "origin" thread. | |
193 SingleThreadedProxyResolver* coordinator_; | |
194 CompletionCallback* callback_; | |
195 ProxyInfo* results_; | |
196 BoundNetLog net_log_; | |
197 GURL url_; | |
198 bool is_started_; | |
199 | |
200 // Usable from within DoQuery on the worker thread. | |
201 ProxyInfo results_buf_; | |
202 MessageLoop* origin_loop_; | |
203 | |
204 // Used to pass the captured events between DoQuery [worker thread] and | |
205 // QueryComplete [origin thread]. | |
206 scoped_ptr<CapturingNetLog> worker_log_; | |
207 }; | |
208 | |
209 // SingleThreadedProxyResolver ------------------------------------------------ | |
210 | |
211 SingleThreadedProxyResolver::SingleThreadedProxyResolver( | |
212 ProxyResolver* resolver) | |
213 : ProxyResolver(resolver->expects_pac_bytes()), | |
214 resolver_(resolver) { | |
215 } | |
216 | |
217 SingleThreadedProxyResolver::~SingleThreadedProxyResolver() { | |
218 // Cancel the inprogress job (if any), and free the rest. | |
219 for (PendingJobsQueue::iterator it = pending_jobs_.begin(); | |
220 it != pending_jobs_.end(); | |
221 ++it) { | |
222 (*it)->Cancel(); | |
223 } | |
224 | |
225 if (outstanding_set_pac_script_task_) | |
226 outstanding_set_pac_script_task_->Cancel(); | |
227 | |
228 // Note that |thread_| is destroyed before |resolver_|. This is important | |
229 // since |resolver_| could be running on |thread_|. | |
230 } | |
231 | |
232 int SingleThreadedProxyResolver::GetProxyForURL(const GURL& url, | |
233 ProxyInfo* results, | |
234 CompletionCallback* callback, | |
235 RequestHandle* request, | |
236 const BoundNetLog& net_log) { | |
237 DCHECK(callback); | |
238 | |
239 scoped_refptr<Job> job = new Job(this, url, results, callback, net_log); | |
240 bool is_first_job = pending_jobs_.empty(); | |
241 pending_jobs_.push_back(job); // Jobs can never finish synchronously. | |
242 | |
243 if (is_first_job) { | |
244 // If there is nothing already running, start the job now. | |
245 EnsureThreadStarted(); | |
246 job->Start(); | |
247 } else { | |
248 // Otherwise the job will get started eventually by ProcessPendingJobs(). | |
249 job->net_log()->BeginEvent( | |
250 NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD, NULL); | |
251 } | |
252 | |
253 // Completion will be notified through |callback|, unless the caller cancels | |
254 // the request using |request|. | |
255 if (request) | |
256 *request = reinterpret_cast<RequestHandle>(job.get()); | |
257 | |
258 return ERR_IO_PENDING; | |
259 } | |
260 | |
261 // There are three states of the request we need to handle: | |
262 // (1) Not started (just sitting in the queue). | |
263 // (2) Executing Job::DoQuery in the worker thread. | |
264 // (3) Waiting for Job::QueryComplete to be run on the origin thread. | |
265 void SingleThreadedProxyResolver::CancelRequest(RequestHandle req) { | |
266 DCHECK(req); | |
267 | |
268 Job* job = reinterpret_cast<Job*>(req); | |
269 | |
270 bool is_active_job = job->is_started() && !pending_jobs_.empty() && | |
271 pending_jobs_.front().get() == job; | |
272 | |
273 job->Cancel(); | |
274 | |
275 if (is_active_job) { | |
276 RemoveFrontOfJobsQueueAndStartNext(job); | |
277 return; | |
278 } | |
279 | |
280 // Otherwise just delete the job from the queue. | |
281 PendingJobsQueue::iterator it = std::find( | |
282 pending_jobs_.begin(), pending_jobs_.end(), job); | |
283 DCHECK(it != pending_jobs_.end()); | |
284 pending_jobs_.erase(it); | |
285 } | |
286 | |
287 void SingleThreadedProxyResolver::CancelSetPacScript() { | |
288 DCHECK(outstanding_set_pac_script_task_); | |
289 outstanding_set_pac_script_task_->Cancel(); | |
290 outstanding_set_pac_script_task_ = NULL; | |
291 } | |
292 | |
293 void SingleThreadedProxyResolver::PurgeMemory() { | |
294 if (thread_.get()) { | |
295 scoped_refptr<PurgeMemoryTask> helper(new PurgeMemoryTask(resolver_.get())); | |
296 thread_->message_loop()->PostTask(FROM_HERE, | |
297 NewRunnableMethod(helper.get(), &PurgeMemoryTask::PurgeMemory)); | |
298 } | |
299 } | |
300 | |
301 int SingleThreadedProxyResolver::SetPacScript( | |
302 const GURL& pac_url, | |
303 const string16& pac_script, | |
304 CompletionCallback* callback) { | |
305 EnsureThreadStarted(); | |
306 DCHECK(!outstanding_set_pac_script_task_); | |
307 | |
308 SetPacScriptTask* task = new SetPacScriptTask( | |
309 this, pac_url, pac_script, callback); | |
310 outstanding_set_pac_script_task_ = task; | |
311 task->Start(); | |
312 return ERR_IO_PENDING; | |
313 } | |
314 | |
315 void SingleThreadedProxyResolver::EnsureThreadStarted() { | |
316 if (!thread_.get()) { | |
317 thread_.reset(new base::Thread("pac-thread")); | |
318 thread_->Start(); | |
319 } | |
320 } | |
321 | |
322 void SingleThreadedProxyResolver::ProcessPendingJobs() { | |
323 if (pending_jobs_.empty()) | |
324 return; | |
325 | |
326 // Get the next job to process (FIFO). | |
327 Job* job = pending_jobs_.front().get(); | |
328 if (job->is_started()) | |
329 return; | |
330 | |
331 job->net_log()->EndEvent( | |
332 NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD, NULL); | |
333 | |
334 EnsureThreadStarted(); | |
335 job->Start(); | |
336 } | |
337 | |
338 void SingleThreadedProxyResolver::RemoveFrontOfJobsQueueAndStartNext( | |
339 Job* expected_job) { | |
340 DCHECK_EQ(expected_job, pending_jobs_.front().get()); | |
341 pending_jobs_.pop_front(); | |
342 | |
343 // Start next work item. | |
344 ProcessPendingJobs(); | |
345 } | |
346 | |
347 void SingleThreadedProxyResolver::RemoveOutstandingSetPacScriptTask( | |
348 SetPacScriptTask* task) { | |
349 DCHECK_EQ(outstanding_set_pac_script_task_.get(), task); | |
350 outstanding_set_pac_script_task_ = NULL; | |
351 } | |
352 | |
353 } // namespace net | |
OLD | NEW |