Chromium Code Reviews| 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 |