| Index: content/browser/loader/resource_scheduler.cc
|
| diff --git a/content/browser/loader/resource_scheduler.cc b/content/browser/loader/resource_scheduler.cc
|
| index 0a171ad06923cbadc9d9dad2704a5fc84c34fb8a..145abb2b9551d41de15c0393e564c46c4aa3f134 100644
|
| --- a/content/browser/loader/resource_scheduler.cc
|
| +++ b/content/browser/loader/resource_scheduler.cc
|
| @@ -8,6 +8,7 @@
|
| #include "content/common/resource_messages.h"
|
| #include "content/browser/loader/resource_message_delegate.h"
|
| #include "content/public/browser/resource_controller.h"
|
| +#include "content/public/browser/resource_request_info.h"
|
| #include "content/public/browser/resource_throttle.h"
|
| #include "ipc/ipc_message_macros.h"
|
| #include "net/base/load_flags.h"
|
| @@ -16,6 +17,54 @@
|
|
|
| namespace content {
|
|
|
| +static const int kMaxNumDelayableRequestsPerClient = 10;
|
| +
|
| +// A thin wrapper around net::PriorityQueue that deals with
|
| +// ScheduledResourceRequests instead of PriorityQueue::Pointers.
|
| +class ResourceScheduler::RequestQueue {
|
| + public:
|
| + RequestQueue() : queue_(net::NUM_PRIORITIES) {}
|
| + ~RequestQueue() {}
|
| +
|
| + // Adds |request| to the queue with given |priority|.
|
| + void Insert(ScheduledResourceRequest* request,
|
| + net::RequestPriority priority) {
|
| + DCHECK(!ContainsKey(pointers_, request));
|
| + NetQueue::Pointer pointer = queue_.Insert(request, priority);
|
| + pointers_[request] = pointer;
|
| + }
|
| +
|
| + // Removes |request| from the queue.
|
| + void Erase(ScheduledResourceRequest* request) {
|
| + PointerMap::iterator it = pointers_.find(request);
|
| + DCHECK(it != pointers_.end());
|
| + queue_.Erase(it->second);
|
| + pointers_.erase(it);
|
| + }
|
| +
|
| + // Returns the highest priority request that's queued, or NULL if none are.
|
| + ScheduledResourceRequest* FirstMax() {
|
| + return queue_.FirstMax().value();
|
| + }
|
| +
|
| + // Returns true if |request| is queued.
|
| + bool IsQueued(ScheduledResourceRequest* request) const {
|
| + return ContainsKey(pointers_, request);
|
| + }
|
| +
|
| + // Returns true if no requests are queued.
|
| + bool IsEmpty() const { return queue_.size() == 0; }
|
| +
|
| + private:
|
| + typedef net::PriorityQueue<ScheduledResourceRequest*> NetQueue;
|
| + typedef std::map<ScheduledResourceRequest*, NetQueue::Pointer> PointerMap;
|
| +
|
| + NetQueue queue_;
|
| + PointerMap pointers_;
|
| +};
|
| +
|
| +// This is the handle we return to the ResourceDispatcherHostImpl so it can
|
| +// interact with the request.
|
| class ResourceScheduler::ScheduledResourceRequest
|
| : public ResourceMessageDelegate,
|
| public ResourceThrottle {
|
| @@ -44,7 +93,8 @@ class ResourceScheduler::ScheduledResourceRequest
|
| }
|
|
|
| const ClientId& client_id() const { return client_id_; }
|
| - const net::URLRequest& url_request() const { return *request_; }
|
| + net::URLRequest* url_request() { return request_; }
|
| + const net::URLRequest* url_request() const { return request_; }
|
|
|
| private:
|
| // ResourceMessageDelegate interface:
|
| @@ -64,11 +114,7 @@ class ResourceScheduler::ScheduledResourceRequest
|
| }
|
|
|
| void DidChangePriority(int request_id, net::RequestPriority new_priority) {
|
| - net::RequestPriority old_priority = request_->priority();
|
| - request_->set_priority(new_priority);
|
| - if (new_priority > old_priority) {
|
| - Start();
|
| - }
|
| + scheduler_->ReprioritizeRequest(this, new_priority);
|
| }
|
|
|
| ClientId client_id_;
|
| @@ -80,15 +126,20 @@ class ResourceScheduler::ScheduledResourceRequest
|
| DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest);
|
| };
|
|
|
| +// Each client represents a tab.
|
| +struct ResourceScheduler::Client {
|
| + Client() : has_body(false) {}
|
| + ~Client() {}
|
| +
|
| + bool has_body;
|
| + RequestQueue pending_requests;
|
| + RequestSet in_flight_requests;
|
| +};
|
| +
|
| ResourceScheduler::ResourceScheduler() {
|
| }
|
|
|
| ResourceScheduler::~ResourceScheduler() {
|
| - for (ClientMap::iterator it ALLOW_UNUSED = client_map_.begin();
|
| - it != client_map_.end(); ++it) {
|
| - DCHECK(it->second->pending_requests.empty());
|
| - DCHECK(it->second->in_flight_requests.empty());
|
| - }
|
| DCHECK(unowned_requests_.empty());
|
| DCHECK(client_map_.empty());
|
| }
|
| @@ -114,17 +165,10 @@ scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest(
|
| }
|
|
|
| Client* client = it->second;
|
| -
|
| - bool is_synchronous = (url_request->load_flags() & net::LOAD_IGNORE_LIMITS) ==
|
| - net::LOAD_IGNORE_LIMITS;
|
| - bool is_low_priority =
|
| - url_request->priority() < net::LOW && !is_synchronous;
|
| -
|
| - if (is_low_priority && !client->in_flight_requests.empty() &&
|
| - !client->has_body) {
|
| - client->pending_requests.push_back(request.get());
|
| - } else {
|
| + if (ShouldStartRequest(request.get(), client)) {
|
| StartRequest(request.get(), client);
|
| + } else {
|
| + client->pending_requests.Insert(request.get(), url_request->priority());
|
| }
|
| return request.PassAs<ResourceThrottle>();
|
| }
|
| @@ -142,29 +186,16 @@ void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) {
|
| }
|
|
|
| Client* client = client_it->second;
|
| - RequestSet::iterator request_it = client->in_flight_requests.find(request);
|
| - if (request_it == client->in_flight_requests.end()) {
|
| - bool removed = false;
|
| - RequestQueue::iterator queue_it;
|
| - for (queue_it = client->pending_requests.begin();
|
| - queue_it != client->pending_requests.end(); ++queue_it) {
|
| - if (*queue_it == request) {
|
| - client->pending_requests.erase(queue_it);
|
| - removed = true;
|
| - break;
|
| - }
|
| - }
|
| - DCHECK(removed);
|
| +
|
| + if (client->pending_requests.IsQueued(request)) {
|
| + client->pending_requests.Erase(request);
|
| DCHECK(!ContainsKey(client->in_flight_requests, request));
|
| } else {
|
| size_t erased = client->in_flight_requests.erase(request);
|
| DCHECK(erased);
|
| - }
|
|
|
| - if (client->in_flight_requests.empty()) {
|
| - // Since the network is now idle, we may as well load some of the low
|
| - // priority requests.
|
| - LoadPendingRequests(client);
|
| + // Removing this request may have freed up another to load.
|
| + LoadAnyStartablePendingRequests(client);
|
| }
|
| }
|
|
|
| @@ -224,7 +255,7 @@ void ResourceScheduler::OnWillInsertBody(int child_id, int route_id) {
|
| client->has_body = false;
|
| if (!client->has_body) {
|
| client->has_body = true;
|
| - LoadPendingRequests(client);
|
| + LoadAnyStartablePendingRequests(client);
|
| }
|
| }
|
|
|
| @@ -234,24 +265,97 @@ void ResourceScheduler::StartRequest(ScheduledResourceRequest* request,
|
| request->Start();
|
| }
|
|
|
| -void ResourceScheduler::LoadPendingRequests(Client* client) {
|
| - while (!client->pending_requests.empty()) {
|
| - ScheduledResourceRequest* request = client->pending_requests.front();
|
| - client->pending_requests.erase(client->pending_requests.begin());
|
| - StartRequest(request, client);
|
| +void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request,
|
| + net::RequestPriority new_priority) {
|
| + net::RequestPriority old_priority = request->url_request()->priority();
|
| + DCHECK_NE(new_priority, old_priority);
|
| + request->url_request()->set_priority(new_priority);
|
| + ClientMap::iterator client_it = client_map_.find(request->client_id());
|
| + if (client_it == client_map_.end()) {
|
| + // The client was likely deleted shortly before we received this IPC.
|
| + return;
|
| + }
|
| +
|
| + Client *client = client_it->second;
|
| + if (!client->pending_requests.IsQueued(request)) {
|
| + DCHECK(ContainsKey(client->in_flight_requests, request));
|
| + // Request has already started.
|
| + return;
|
| + }
|
| +
|
| + client->pending_requests.Erase(request);
|
| + client->pending_requests.Insert(request, request->url_request()->priority());
|
| +
|
| + if (new_priority > old_priority) {
|
| + // Check if this request is now able to load at its new priority.
|
| + LoadAnyStartablePendingRequests(client);
|
| }
|
| }
|
|
|
| -ResourceScheduler::ClientId ResourceScheduler::MakeClientId(
|
| - int child_id, int route_id) {
|
| - return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id;
|
| +void ResourceScheduler::LoadAnyStartablePendingRequests(Client* client) {
|
| + while (!client->pending_requests.IsEmpty()) {
|
| + ScheduledResourceRequest* request = client->pending_requests.FirstMax();
|
| + if (ShouldStartRequest(request, client)) {
|
| + client->pending_requests.Erase(request);
|
| + StartRequest(request, client);
|
| + } else {
|
| + break;
|
| + }
|
| + }
|
| +}
|
| +
|
| +size_t ResourceScheduler::GetNumDelayableRequestsInFlight(
|
| + Client* client) const {
|
| + size_t count = 0;
|
| + for (RequestSet::iterator it = client->in_flight_requests.begin();
|
| + it != client->in_flight_requests.end(); ++it) {
|
| + if ((*it)->url_request()->priority() < net::LOW) {
|
| + ++count;
|
| + }
|
| + }
|
| + return count;
|
| }
|
|
|
| -ResourceScheduler::Client::Client()
|
| - : has_body(false) {
|
| +// ShouldStartRequest is the main scheduling algorithm.
|
| +//
|
| +// Requests are categorized into two categories:
|
| +//
|
| +// 1. Immediately issued requests, which are:
|
| +//
|
| +// * Higher priority requests (>= net::LOW).
|
| +// * Synchronous requests.
|
| +//
|
| +// 2. The remainder are delayable requests, which follow these rules:
|
| +//
|
| +// * If no high priority requests are in flight, start loading low priority
|
| +// requests.
|
| +// * Once the renderer has a <body>, start loading delayable requests.
|
| +// * Never exceed 10 delayable requests in flight per client.
|
| +bool ResourceScheduler::ShouldStartRequest(ScheduledResourceRequest* request,
|
| + Client* client) const {
|
| + if (request->url_request()->priority() >= net::LOW ||
|
| + !ResourceRequestInfo::ForRequest(request->url_request())->IsAsync()) {
|
| + return true;
|
| + }
|
| +
|
| + size_t num_delayable_requests_in_flight =
|
| + GetNumDelayableRequestsInFlight(client);
|
| + if (num_delayable_requests_in_flight >= kMaxNumDelayableRequestsPerClient) {
|
| + return false;
|
| + }
|
| +
|
| + bool have_immediate_requests_in_flight =
|
| + client->in_flight_requests.size() > num_delayable_requests_in_flight;
|
| + if (have_immediate_requests_in_flight && !client->has_body) {
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| }
|
|
|
| -ResourceScheduler::Client::~Client() {
|
| +ResourceScheduler::ClientId ResourceScheduler::MakeClientId(
|
| + int child_id, int route_id) {
|
| + return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id;
|
| }
|
|
|
| } // namespace content
|
|
|