OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/browser/loader/resource_scheduler.h" | 5 #include "content/browser/loader/resource_scheduler.h" |
6 | 6 |
7 #include "base/stl_util.h" | 7 #include "base/stl_util.h" |
8 #include "content/common/resource_messages.h" | 8 #include "content/common/resource_messages.h" |
9 #include "content/browser/loader/resource_message_delegate.h" | 9 #include "content/browser/loader/resource_message_delegate.h" |
10 #include "content/public/browser/resource_controller.h" | 10 #include "content/public/browser/resource_controller.h" |
11 #include "content/public/browser/resource_request_info.h" | 11 #include "content/public/browser/resource_request_info.h" |
12 #include "content/public/browser/resource_throttle.h" | 12 #include "content/public/browser/resource_throttle.h" |
13 #include "ipc/ipc_message_macros.h" | 13 #include "ipc/ipc_message_macros.h" |
14 #include "net/base/host_port_pair.h" | 14 #include "net/base/host_port_pair.h" |
15 #include "net/base/load_flags.h" | 15 #include "net/base/load_flags.h" |
16 #include "net/base/request_priority.h" | 16 #include "net/base/request_priority.h" |
17 #include "net/http/http_server_properties.h" | 17 #include "net/http/http_server_properties.h" |
18 #include "net/url_request/url_request.h" | 18 #include "net/url_request/url_request.h" |
19 #include "net/url_request/url_request_context.h" | 19 #include "net/url_request/url_request_context.h" |
20 | 20 |
21 namespace content { | 21 namespace content { |
22 | 22 |
23 static const size_t kMaxNumDelayableRequestsPerClient = 10; | 23 static const size_t kMaxNumDelayableRequestsPerClient = 10; |
24 static const size_t kMaxNumDelayableRequestsPerHost = 6; | |
24 | 25 |
25 // A thin wrapper around net::PriorityQueue that deals with | 26 // A thin wrapper around net::PriorityQueue that deals with |
26 // ScheduledResourceRequests instead of PriorityQueue::Pointers. | 27 // ScheduledResourceRequests instead of PriorityQueue::Pointers. |
27 class ResourceScheduler::RequestQueue { | 28 class ResourceScheduler::RequestQueue { |
29 private: | |
30 typedef net::PriorityQueue<ScheduledResourceRequest*> NetQueue; | |
31 | |
28 public: | 32 public: |
33 class Iterator { | |
34 public: | |
35 Iterator(NetQueue* queue) : queue_(queue) { | |
36 DCHECK(queue != NULL); | |
37 current_pointer_ = queue_->FirstMax(); | |
38 } | |
39 | |
40 Iterator& operator++() { | |
41 current_pointer_ = queue_->NextHighest(current_pointer_); | |
42 return *this; | |
43 } | |
44 | |
45 Iterator operator++(int) { | |
46 Iterator result(*this); | |
47 ++(*this); | |
48 return result; | |
49 } | |
50 | |
51 ScheduledResourceRequest* value() { | |
52 return current_pointer_.value(); | |
53 } | |
54 | |
55 bool is_null() { | |
56 return current_pointer_.is_null(); | |
57 } | |
58 | |
59 private: | |
60 NetQueue* queue_; | |
61 NetQueue::Pointer current_pointer_; | |
62 }; | |
63 | |
29 RequestQueue() : queue_(net::NUM_PRIORITIES) {} | 64 RequestQueue() : queue_(net::NUM_PRIORITIES) {} |
30 ~RequestQueue() {} | 65 ~RequestQueue() {} |
31 | 66 |
32 // Adds |request| to the queue with given |priority|. | 67 // Adds |request| to the queue with given |priority|. |
33 void Insert(ScheduledResourceRequest* request, | 68 void Insert(ScheduledResourceRequest* request, |
34 net::RequestPriority priority) { | 69 net::RequestPriority priority) { |
35 DCHECK(!ContainsKey(pointers_, request)); | 70 DCHECK(!ContainsKey(pointers_, request)); |
36 NetQueue::Pointer pointer = queue_.Insert(request, priority); | 71 NetQueue::Pointer pointer = queue_.Insert(request, priority); |
37 pointers_[request] = pointer; | 72 pointers_[request] = pointer; |
38 } | 73 } |
39 | 74 |
40 // Removes |request| from the queue. | 75 // Removes |request| from the queue. |
41 void Erase(ScheduledResourceRequest* request) { | 76 void Erase(ScheduledResourceRequest* request) { |
42 PointerMap::iterator it = pointers_.find(request); | 77 PointerMap::iterator it = pointers_.find(request); |
43 DCHECK(it != pointers_.end()); | 78 DCHECK(it != pointers_.end()); |
44 queue_.Erase(it->second); | 79 queue_.Erase(it->second); |
45 pointers_.erase(it); | 80 pointers_.erase(it); |
46 } | 81 } |
47 | 82 |
48 // Returns the highest priority request that's queued, or NULL if none are. | 83 // Returns the highest priority request that's queued, or NULL if none are. |
49 ScheduledResourceRequest* FirstMax() { | 84 ScheduledResourceRequest* FirstMax() { |
50 return queue_.FirstMax().value(); | 85 return queue_.FirstMax().value(); |
51 } | 86 } |
52 | 87 |
88 Iterator GetNextHighestIterator() { | |
89 return Iterator(&queue_); | |
90 } | |
91 | |
53 // Returns true if |request| is queued. | 92 // Returns true if |request| is queued. |
54 bool IsQueued(ScheduledResourceRequest* request) const { | 93 bool IsQueued(ScheduledResourceRequest* request) const { |
55 return ContainsKey(pointers_, request); | 94 return ContainsKey(pointers_, request); |
56 } | 95 } |
57 | 96 |
58 // Returns true if no requests are queued. | 97 // Returns true if no requests are queued. |
59 bool IsEmpty() const { return queue_.size() == 0; } | 98 bool IsEmpty() const { return queue_.size() == 0; } |
60 | 99 |
61 private: | 100 private: |
62 typedef net::PriorityQueue<ScheduledResourceRequest*> NetQueue; | |
63 typedef std::map<ScheduledResourceRequest*, NetQueue::Pointer> PointerMap; | 101 typedef std::map<ScheduledResourceRequest*, NetQueue::Pointer> PointerMap; |
64 | 102 |
65 NetQueue queue_; | 103 NetQueue queue_; |
66 PointerMap pointers_; | 104 PointerMap pointers_; |
67 }; | 105 }; |
68 | 106 |
69 // This is the handle we return to the ResourceDispatcherHostImpl so it can | 107 // This is the handle we return to the ResourceDispatcherHostImpl so it can |
70 // interact with the request. | 108 // interact with the request. |
71 class ResourceScheduler::ScheduledResourceRequest | 109 class ResourceScheduler::ScheduledResourceRequest |
72 : public ResourceMessageDelegate, | 110 : public ResourceMessageDelegate, |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
164 // There are several ways this could happen: | 202 // There are several ways this could happen: |
165 // 1. <a ping> requests don't have a route_id. | 203 // 1. <a ping> requests don't have a route_id. |
166 // 2. Most unittests don't send the IPCs needed to register Clients. | 204 // 2. Most unittests don't send the IPCs needed to register Clients. |
167 // 3. The tab is closed while a RequestResource IPC is in flight. | 205 // 3. The tab is closed while a RequestResource IPC is in flight. |
168 unowned_requests_.insert(request.get()); | 206 unowned_requests_.insert(request.get()); |
169 request->Start(); | 207 request->Start(); |
170 return request.PassAs<ResourceThrottle>(); | 208 return request.PassAs<ResourceThrottle>(); |
171 } | 209 } |
172 | 210 |
173 Client* client = it->second; | 211 Client* client = it->second; |
174 if (ShouldStartRequest(request.get(), client)) { | 212 if (ShouldStartRequest(request.get(), client) == kStartRequest) { |
175 StartRequest(request.get(), client); | 213 StartRequest(request.get(), client); |
176 } else { | 214 } else { |
177 client->pending_requests.Insert(request.get(), url_request->priority()); | 215 client->pending_requests.Insert(request.get(), url_request->priority()); |
178 } | 216 } |
179 return request.PassAs<ResourceThrottle>(); | 217 return request.PassAs<ResourceThrottle>(); |
180 } | 218 } |
181 | 219 |
182 void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) { | 220 void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) { |
183 DCHECK(CalledOnValidThread()); | 221 DCHECK(CalledOnValidThread()); |
184 if (ContainsKey(unowned_requests_, request)) { | 222 if (ContainsKey(unowned_requests_, request)) { |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
292 client->pending_requests.Erase(request); | 330 client->pending_requests.Erase(request); |
293 client->pending_requests.Insert(request, request->url_request()->priority()); | 331 client->pending_requests.Insert(request, request->url_request()->priority()); |
294 | 332 |
295 if (new_priority > old_priority) { | 333 if (new_priority > old_priority) { |
296 // Check if this request is now able to load at its new priority. | 334 // Check if this request is now able to load at its new priority. |
297 LoadAnyStartablePendingRequests(client); | 335 LoadAnyStartablePendingRequests(client); |
298 } | 336 } |
299 } | 337 } |
300 | 338 |
301 void ResourceScheduler::LoadAnyStartablePendingRequests(Client* client) { | 339 void ResourceScheduler::LoadAnyStartablePendingRequests(Client* client) { |
302 while (!client->pending_requests.IsEmpty()) { | 340 // We iterate through all the pending requests, starting with the highest |
303 ScheduledResourceRequest* request = client->pending_requests.FirstMax(); | 341 // priority one. For each entry, one of three things can happen: |
304 if (ShouldStartRequest(request, client)) { | 342 // 1) We start the request, remove it from the list, and keep checking. |
343 // 2) We do NOT start the request, but ShouldStartRequest() signals us that | |
344 // there may be room for other requests, so we keep checking and leave | |
345 // the previous request still in the list. | |
346 // 3) We do not start the request, same as above, but StartRequest() tells | |
347 // us there's no point in checking any further requests. | |
348 | |
349 RequestQueue::Iterator request_iter = | |
350 client->pending_requests.GetNextHighestIterator(); | |
351 | |
352 while (!request_iter.is_null()) { | |
353 ScheduledResourceRequest* request = request_iter.value(); | |
354 ShouldStartReqResult query_result = ShouldStartRequest(request, client); | |
355 | |
356 if (query_result == kStartRequest) { | |
305 client->pending_requests.Erase(request); | 357 client->pending_requests.Erase(request); |
306 StartRequest(request, client); | 358 StartRequest(request, client); |
359 | |
360 // StartRequest can modify the pending list, so we (re)start evaluation | |
361 // from the currently highest priority request. | |
362 request_iter = client->pending_requests.GetNextHighestIterator(); | |
363 } else if (query_result == kDoNotStartRequest_KeepSearching) { | |
364 ++request_iter; | |
365 continue; | |
307 } else { | 366 } else { |
367 DCHECK(query_result == kDoNotStartRequest_StopSearching); | |
James Simonsen
2013/11/13 01:26:23
I'd prefer it be a switch instead of if/else. Let
oystein (OOO til 10th of July)
2013/11/14 00:19:07
The reason I didn't go with a switch is to avoid n
| |
308 break; | 368 break; |
309 } | 369 } |
310 } | 370 } |
311 } | 371 } |
312 | 372 |
313 size_t ResourceScheduler::GetNumDelayableRequestsInFlight( | 373 void ResourceScheduler::GetNumDelayableRequestsInFlight( |
James Simonsen
2013/11/13 01:26:23
It feels like we're doing a lot of work here now.
oystein (OOO til 10th of July)
2013/11/14 00:19:07
I looked into this a bit now, I think the main pro
James Simonsen
2013/11/14 19:22:43
There has to be an easier way to keep track of all
oystein (OOO til 10th of July)
2013/11/14 19:51:49
I agree, but I suggest we wait until the next chan
| |
314 Client* client) const { | 374 Client* client, |
315 size_t count = 0; | 375 net::HostPortPair* active_request_host, |
James Simonsen
2013/11/13 01:26:23
This should be a const reference. It shouldn't eve
oystein (OOO til 10th of July)
2013/11/14 19:51:49
Done.
| |
376 size_t* total_delayable, | |
377 size_t* total_for_active_host) const { | |
378 DCHECK(client != NULL && active_request_host != NULL && | |
379 total_delayable != NULL && total_for_active_host != NULL); | |
380 | |
381 size_t total_delayable_count = 0; | |
382 size_t same_host_count = 0; | |
316 for (RequestSet::iterator it = client->in_flight_requests.begin(); | 383 for (RequestSet::iterator it = client->in_flight_requests.begin(); |
317 it != client->in_flight_requests.end(); ++it) { | 384 it != client->in_flight_requests.end(); ++it) { |
385 net::HostPortPair host_port_pair = | |
386 net::HostPortPair::FromURL((*it)->url_request()->url()); | |
James Simonsen
2013/11/13 01:26:23
Yeah, the multimap or whatever would be nice here.
| |
387 | |
388 if (active_request_host->Equals(host_port_pair)) { | |
389 same_host_count++; | |
390 } | |
391 | |
318 if ((*it)->url_request()->priority() < net::LOW) { | 392 if ((*it)->url_request()->priority() < net::LOW) { |
319 const net::HttpServerProperties& http_server_properties = | 393 const net::HttpServerProperties& http_server_properties = |
320 *(*it)->url_request()->context()->http_server_properties(); | 394 *(*it)->url_request()->context()->http_server_properties(); |
321 if (!http_server_properties.SupportsSpdy( | 395 |
322 net::HostPortPair::FromURL((*it)->url_request()->url()))) { | 396 if (!http_server_properties.SupportsSpdy(host_port_pair)) { |
James Simonsen
2013/11/13 01:26:23
Maybe we should keep these in a separate set.
| |
323 ++count; | 397 ++total_delayable_count; |
324 } | 398 } |
325 } | 399 } |
326 } | 400 } |
327 return count; | 401 *total_delayable = total_delayable_count; |
402 *total_for_active_host = same_host_count; | |
328 } | 403 } |
329 | 404 |
330 // ShouldStartRequest is the main scheduling algorithm. | 405 // ShouldStartRequest is the main scheduling algorithm. |
331 // | 406 // |
332 // Requests are categorized into two categories: | 407 // Requests are categorized into two categories: |
333 // | 408 // |
334 // 1. Immediately issued requests, which are: | 409 // 1. Immediately issued requests, which are: |
335 // | 410 // |
336 // * Higher priority requests (>= net::LOW). | 411 // * Higher priority requests (>= net::LOW). |
337 // * Synchronous requests. | 412 // * Synchronous requests. |
338 // * Requests to SPDY-capable origin servers. | 413 // * Requests to SPDY-capable origin servers. |
339 // * Non-HTTP[S] requests. | 414 // * Non-HTTP[S] requests. |
340 // | 415 // |
341 // 2. The remainder are delayable requests, which follow these rules: | 416 // 2. The remainder are delayable requests, which follow these rules: |
342 // | 417 // |
343 // * If no high priority requests are in flight, start loading low priority | 418 // * If no high priority requests are in flight, start loading low priority |
344 // requests. | 419 // requests. |
345 // * Once the renderer has a <body>, start loading delayable requests. | 420 // * Once the renderer has a <body>, start loading delayable requests. |
346 // * Never exceed 10 delayable requests in flight per client. | 421 // * Never exceed 10 delayable requests in flight per client. |
422 // * Never exceed 6 delayable requests for a given host. | |
347 // * Prior to <body>, allow one delayable request to load at a time. | 423 // * Prior to <body>, allow one delayable request to load at a time. |
348 bool ResourceScheduler::ShouldStartRequest(ScheduledResourceRequest* request, | 424 ResourceScheduler::ShouldStartReqResult ResourceScheduler::ShouldStartRequest( |
349 Client* client) const { | 425 ScheduledResourceRequest* request, |
426 Client* client) const { | |
350 const net::URLRequest& url_request = *request->url_request(); | 427 const net::URLRequest& url_request = *request->url_request(); |
351 | 428 |
352 // TODO(simonjam): This may end up causing disk contention. We should | 429 // TODO(simonjam): This may end up causing disk contention. We should |
353 // experiment with throttling if that happens. | 430 // experiment with throttling if that happens. |
354 if (!url_request.url().SchemeIsHTTPOrHTTPS()) { | 431 if (!url_request.url().SchemeIsHTTPOrHTTPS()) { |
355 return true; | 432 return kStartRequest; |
356 } | 433 } |
357 | 434 |
358 const net::HttpServerProperties& http_server_properties = | 435 const net::HttpServerProperties& http_server_properties = |
359 *url_request.context()->http_server_properties(); | 436 *url_request.context()->http_server_properties(); |
360 | 437 |
438 if (url_request.priority() >= net::LOW || | |
439 !ResourceRequestInfo::ForRequest(&url_request)->IsAsync()) { | |
440 return kStartRequest; | |
441 } | |
442 | |
443 net::HostPortPair host_port_pair = | |
444 net::HostPortPair::FromURL(url_request.url()); | |
445 | |
361 // TODO(willchan): We should really improve this algorithm as described in | 446 // TODO(willchan): We should really improve this algorithm as described in |
362 // crbug.com/164101. Also, theoretically we should not count a SPDY request | 447 // crbug.com/164101. Also, theoretically we should not count a SPDY request |
363 // against the delayable requests limit. | 448 // against the delayable requests limit. |
364 bool origin_supports_spdy = http_server_properties.SupportsSpdy( | 449 if (http_server_properties.SupportsSpdy(host_port_pair)) { |
365 net::HostPortPair::FromURL(url_request.url())); | 450 return kStartRequest; |
366 | |
367 if (url_request.priority() >= net::LOW || | |
368 !ResourceRequestInfo::ForRequest(&url_request)->IsAsync() || | |
369 origin_supports_spdy) { | |
370 return true; | |
371 } | 451 } |
372 | 452 |
373 size_t num_delayable_requests_in_flight = | 453 size_t num_delayable_requests_in_flight = 0; |
374 GetNumDelayableRequestsInFlight(client); | 454 size_t num_requests_in_flight_for_host = 0; |
455 GetNumDelayableRequestsInFlight(client, &host_port_pair, | |
456 &num_delayable_requests_in_flight, | |
457 &num_requests_in_flight_for_host); | |
458 | |
375 if (num_delayable_requests_in_flight >= kMaxNumDelayableRequestsPerClient) { | 459 if (num_delayable_requests_in_flight >= kMaxNumDelayableRequestsPerClient) { |
376 return false; | 460 return kDoNotStartRequest_StopSearching; |
461 } | |
462 | |
463 if (num_requests_in_flight_for_host >= kMaxNumDelayableRequestsPerHost) { | |
464 // There may be other requests for other hosts we'd allow, so keep checking. | |
465 return kDoNotStartRequest_KeepSearching; | |
377 } | 466 } |
378 | 467 |
379 bool have_immediate_requests_in_flight = | 468 bool have_immediate_requests_in_flight = |
380 client->in_flight_requests.size() > num_delayable_requests_in_flight; | 469 client->in_flight_requests.size() > num_delayable_requests_in_flight; |
381 if (have_immediate_requests_in_flight && !client->has_body && | 470 if (have_immediate_requests_in_flight && !client->has_body && |
382 num_delayable_requests_in_flight != 0) { | 471 num_delayable_requests_in_flight != 0) { |
383 return false; | 472 return kDoNotStartRequest_StopSearching; |
384 } | 473 } |
385 | 474 |
386 return true; | 475 return kStartRequest; |
387 } | 476 } |
388 | 477 |
389 ResourceScheduler::ClientId ResourceScheduler::MakeClientId( | 478 ResourceScheduler::ClientId ResourceScheduler::MakeClientId( |
390 int child_id, int route_id) { | 479 int child_id, int route_id) { |
391 return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id; | 480 return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id; |
392 } | 481 } |
393 | 482 |
394 } // namespace content | 483 } // namespace content |
OLD | NEW |