Index: content/browser/loader/resource_scheduler.cc |
diff --git a/content/browser/loader/resource_scheduler.cc b/content/browser/loader/resource_scheduler.cc |
index 4b4c832b7d5ef7d416804b6ad9cef8a65ad726b1..e2a0fe21b88bf845609e9b057621b0164cb3b27d 100644 |
--- a/content/browser/loader/resource_scheduler.cc |
+++ b/content/browser/loader/resource_scheduler.cc |
@@ -126,7 +126,8 @@ class ResourceScheduler::ScheduledResourceRequest |
deferred_(false), |
scheduler_(scheduler), |
priority_(priority), |
- fifo_ordering_(0) { |
+ fifo_ordering_(0), |
+ accounted_as_delayable_request_(false) { |
TRACE_EVENT_ASYNC_BEGIN1("net", "URLRequest", request_, |
"url", request->url().spec()); |
} |
@@ -157,6 +158,12 @@ class ResourceScheduler::ScheduledResourceRequest |
void set_fifo_ordering(uint32 fifo_ordering) { |
fifo_ordering_ = fifo_ordering; |
} |
+ bool accounted_as_delayable_request() const { |
+ return accounted_as_delayable_request_; |
+ } |
+ void set_accounted_as_delayable_request(bool accounted) { |
+ accounted_as_delayable_request_ = accounted; |
+ } |
private: |
// ResourceMessageDelegate interface: |
@@ -191,6 +198,8 @@ class ResourceScheduler::ScheduledResourceRequest |
ResourceScheduler* scheduler_; |
RequestPriorityParams priority_; |
uint32 fifo_ordering_; |
+ // True if the request is delayable in |in_flight_requests_|. |
+ bool accounted_as_delayable_request_; |
DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest); |
}; |
@@ -219,14 +228,270 @@ void ResourceScheduler::RequestQueue::Insert( |
} |
// Each client represents a tab. |
-struct ResourceScheduler::Client { |
- Client() : has_body(false), using_spdy_proxy(false) {} |
+class ResourceScheduler::Client { |
+ public: |
+ Client() |
+ : has_body_(false), |
+ using_spdy_proxy_(false), |
+ total_delayable_count_(0) {} |
~Client() {} |
- bool has_body; |
- bool using_spdy_proxy; |
- RequestQueue pending_requests; |
- RequestSet in_flight_requests; |
+ void ScheduleRequest( |
+ net::URLRequest* url_request, |
+ ScheduledResourceRequest* request) { |
+ if (ShouldStartRequest(request) == START_REQUEST) { |
+ StartRequest(request); |
+ } else { |
+ pending_requests_.Insert(request); |
+ } |
+ } |
+ |
+ void RemoveRequest(ScheduledResourceRequest* request) { |
+ if (pending_requests_.IsQueued(request)) { |
+ pending_requests_.Erase(request); |
+ DCHECK(!ContainsKey(in_flight_requests_, request)); |
+ } else { |
+ EraseInFlightRequest(request); |
+ |
+ // Removing this request may have freed up another to load. |
+ LoadAnyStartablePendingRequests(); |
+ } |
+ } |
+ |
+ RequestSet RemoveAllRequests() { |
+ RequestSet unowned_requests; |
+ for (RequestSet::iterator it = in_flight_requests_.begin(); |
+ it != in_flight_requests_.end(); ++it) { |
+ unowned_requests.insert(*it); |
+ (*it)->set_accounted_as_delayable_request(false); |
+ } |
+ ClearInFlightRequests(); |
+ return unowned_requests; |
+ } |
+ |
+ void OnNavigate() { |
+ has_body_ = false; |
+ } |
+ |
+ void OnWillInsertBody() { |
+ has_body_ = true; |
+ LoadAnyStartablePendingRequests(); |
+ } |
+ |
+ void OnReceivedSpdyProxiedHttpResponse() { |
+ if (!using_spdy_proxy_) { |
+ using_spdy_proxy_ = true; |
+ LoadAnyStartablePendingRequests(); |
+ } |
+ } |
+ |
+ void ReprioritizeRequest(ScheduledResourceRequest* request, |
+ RequestPriorityParams old_priority_params, |
+ RequestPriorityParams new_priority_params) { |
+ request->url_request()->SetPriority(new_priority_params.priority); |
+ request->set_request_priority_params(new_priority_params); |
+ if (!pending_requests_.IsQueued(request)) { |
+ DCHECK(ContainsKey(in_flight_requests_, request)); |
+ // The priority and SPDY support may have changed, so update the |
+ // delayable count. |
+ SetRequestDelayable(request, IsDelayableRequest(request)); |
+ // Request has already started. |
+ return; |
+ } |
+ |
+ pending_requests_.Erase(request); |
+ pending_requests_.Insert(request); |
+ |
+ if (new_priority_params.priority > old_priority_params.priority) { |
+ // Check if this request is now able to load at its new priority. |
+ LoadAnyStartablePendingRequests(); |
+ } |
+ } |
+ |
+ private: |
+ enum ShouldStartReqResult { |
+ DO_NOT_START_REQUEST_AND_STOP_SEARCHING = -2, |
+ DO_NOT_START_REQUEST_AND_KEEP_SEARCHING = -1, |
+ START_REQUEST = 1, |
+ }; |
+ |
+ void InsertInFlightRequest(ScheduledResourceRequest* request) { |
+ in_flight_requests_.insert(request); |
+ if (IsDelayableRequest(request)) |
+ SetRequestDelayable(request, true); |
+ } |
+ |
+ void EraseInFlightRequest(ScheduledResourceRequest* request) { |
+ size_t erased = in_flight_requests_.erase(request); |
+ DCHECK_EQ(1u, erased); |
+ SetRequestDelayable(request, false); |
+ DCHECK_LE(total_delayable_count_, in_flight_requests_.size()); |
+ } |
+ |
+ void ClearInFlightRequests() { |
+ in_flight_requests_.clear(); |
+ total_delayable_count_ = 0; |
+ } |
+ |
+ bool IsDelayableRequest(ScheduledResourceRequest* request) { |
+ if (request->url_request()->priority() < net::LOW) { |
+ net::HostPortPair host_port_pair = |
+ net::HostPortPair::FromURL(request->url_request()->url()); |
+ const net::HttpServerProperties& http_server_properties = |
+ *request->url_request()->context()->http_server_properties(); |
+ if (!http_server_properties.SupportsSpdy(host_port_pair)) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ void SetRequestDelayable(ScheduledResourceRequest* request, |
+ bool delayable) { |
+ if (request->accounted_as_delayable_request() == delayable) |
+ return; |
+ if (delayable) |
+ total_delayable_count_++; |
+ else |
+ total_delayable_count_--; |
+ request->set_accounted_as_delayable_request(delayable); |
+ } |
+ |
+ bool ShouldKeepSearching( |
+ const net::HostPortPair& active_request_host) const { |
+ size_t same_host_count = 0; |
+ for (RequestSet::const_iterator it = in_flight_requests_.begin(); |
+ it != in_flight_requests_.end(); ++it) { |
+ net::HostPortPair host_port_pair = |
+ net::HostPortPair::FromURL((*it)->url_request()->url()); |
+ if (active_request_host.Equals(host_port_pair)) { |
+ same_host_count++; |
+ if (same_host_count >= kMaxNumDelayableRequestsPerHost) |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ void StartRequest(ScheduledResourceRequest* request) { |
+ InsertInFlightRequest(request); |
+ request->Start(); |
+ } |
+ |
+ // 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. |
+ // * Requests to SPDY-capable origin servers. |
+ // * Non-HTTP[S] 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. |
+ // * Never exceed 6 delayable requests for a given host. |
+ // * Prior to <body>, allow one delayable request to load at a time. |
+ ShouldStartReqResult ShouldStartRequest( |
+ ScheduledResourceRequest* request) const { |
+ const net::URLRequest& url_request = *request->url_request(); |
+ // TODO(simonjam): This may end up causing disk contention. We should |
+ // experiment with throttling if that happens. |
+ if (!url_request.url().SchemeIsHTTPOrHTTPS()) { |
+ return START_REQUEST; |
+ } |
+ |
+ if (using_spdy_proxy_ && url_request.url().SchemeIs("http")) { |
+ return START_REQUEST; |
+ } |
+ |
+ const net::HttpServerProperties& http_server_properties = |
+ *url_request.context()->http_server_properties(); |
+ |
+ if (url_request.priority() >= net::LOW || |
+ !ResourceRequestInfo::ForRequest(&url_request)->IsAsync()) { |
+ return START_REQUEST; |
+ } |
+ |
+ net::HostPortPair host_port_pair = |
+ net::HostPortPair::FromURL(url_request.url()); |
+ |
+ // TODO(willchan): We should really improve this algorithm as described in |
+ // crbug.com/164101. Also, theoretically we should not count a SPDY request |
+ // against the delayable requests limit. |
+ if (http_server_properties.SupportsSpdy(host_port_pair)) { |
+ return START_REQUEST; |
+ } |
+ |
+ size_t num_delayable_requests_in_flight = total_delayable_count_; |
+ if (num_delayable_requests_in_flight >= kMaxNumDelayableRequestsPerClient) { |
+ return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; |
+ } |
+ |
+ if (ShouldKeepSearching(host_port_pair)) { |
+ // There may be other requests for other hosts we'd allow, |
+ // so keep checking. |
+ return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING; |
+ } |
+ |
+ bool have_immediate_requests_in_flight = |
+ in_flight_requests_.size() > num_delayable_requests_in_flight; |
+ if (have_immediate_requests_in_flight && !has_body_ && |
+ num_delayable_requests_in_flight != 0) { |
+ return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; |
+ } |
+ |
+ return START_REQUEST; |
+ } |
+ |
+ void LoadAnyStartablePendingRequests() { |
+ // We iterate through all the pending requests, starting with the highest |
+ // priority one. For each entry, one of three things can happen: |
+ // 1) We start the request, remove it from the list, and keep checking. |
+ // 2) We do NOT start the request, but ShouldStartRequest() signals us that |
+ // there may be room for other requests, so we keep checking and leave |
+ // the previous request still in the list. |
+ // 3) We do not start the request, same as above, but StartRequest() tells |
+ // us there's no point in checking any further requests. |
+ RequestQueue::NetQueue::iterator request_iter = |
+ pending_requests_.GetNextHighestIterator(); |
+ |
+ while (request_iter != pending_requests_.End()) { |
+ ScheduledResourceRequest* request = *request_iter; |
+ ShouldStartReqResult query_result = ShouldStartRequest(request); |
+ |
+ if (query_result == START_REQUEST) { |
+ pending_requests_.Erase(request); |
+ StartRequest(request); |
+ |
+ // StartRequest can modify the pending list, so we (re)start evaluation |
+ // from the currently highest priority request. Avoid copying a singular |
+ // iterator, which would trigger undefined behavior. |
+ if (pending_requests_.GetNextHighestIterator() == |
+ pending_requests_.End()) |
+ break; |
+ request_iter = pending_requests_.GetNextHighestIterator(); |
+ } else if (query_result == DO_NOT_START_REQUEST_AND_KEEP_SEARCHING) { |
+ ++request_iter; |
+ continue; |
+ } else { |
+ DCHECK(query_result == DO_NOT_START_REQUEST_AND_STOP_SEARCHING); |
+ break; |
+ } |
+ } |
+ } |
+ |
+ bool has_body_; |
+ bool using_spdy_proxy_; |
+ RequestQueue pending_requests_; |
+ RequestSet in_flight_requests_; |
+ // The number of delayable in-flight requests. |
+ size_t total_delayable_count_; |
}; |
ResourceScheduler::ResourceScheduler() { |
@@ -259,11 +524,7 @@ scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest( |
} |
Client* client = it->second; |
- if (ShouldStartRequest(request.get(), client) == START_REQUEST) { |
- StartRequest(request.get(), client); |
- } else { |
- client->pending_requests.Insert(request.get()); |
- } |
+ client->ScheduleRequest(url_request, request.get()); |
return request.PassAs<ResourceThrottle>(); |
} |
@@ -280,17 +541,7 @@ void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) { |
} |
Client* client = client_it->second; |
- |
- 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); |
- |
- // Removing this request may have freed up another to load. |
- LoadAnyStartablePendingRequests(client); |
- } |
+ client->RemoveRequest(request); |
} |
void ResourceScheduler::OnClientCreated(int child_id, int route_id) { |
@@ -314,11 +565,11 @@ void ResourceScheduler::OnClientDeleted(int child_id, int route_id) { |
// FYI, ResourceDispatcherHost cancels all of the requests after this function |
// is called. It should end up canceling all of the requests except for a |
// cross-renderer navigation. |
- for (RequestSet::iterator it = client->in_flight_requests.begin(); |
- it != client->in_flight_requests.end(); ++it) { |
+ RequestSet client_unowned_requests = client->RemoveAllRequests(); |
+ for (RequestSet::iterator it = client_unowned_requests.begin(); |
+ it != client_unowned_requests.end(); ++it) { |
unowned_requests_.insert(*it); |
} |
- client->in_flight_requests.clear(); |
delete client; |
client_map_.erase(it); |
@@ -335,7 +586,7 @@ void ResourceScheduler::OnNavigate(int child_id, int route_id) { |
} |
Client* client = it->second; |
- client->has_body = false; |
+ client->OnNavigate(); |
} |
void ResourceScheduler::OnWillInsertBody(int child_id, int route_id) { |
@@ -349,8 +600,7 @@ void ResourceScheduler::OnWillInsertBody(int child_id, int route_id) { |
} |
Client* client = it->second; |
- client->has_body = true; |
- LoadAnyStartablePendingRequests(client); |
+ client->OnWillInsertBody(); |
} |
void ResourceScheduler::OnReceivedSpdyProxiedHttpResponse( |
@@ -365,17 +615,7 @@ void ResourceScheduler::OnReceivedSpdyProxiedHttpResponse( |
} |
Client* client = client_it->second; |
- |
- if (!client->using_spdy_proxy) { |
- client->using_spdy_proxy = true; |
- LoadAnyStartablePendingRequests(client); |
- } |
-} |
- |
-void ResourceScheduler::StartRequest(ScheduledResourceRequest* request, |
- Client* client) { |
- client->in_flight_requests.insert(request); |
- request->Start(); |
+ client->OnReceivedSpdyProxiedHttpResponse(); |
} |
void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request, |
@@ -387,7 +627,6 @@ void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request, |
NOTREACHED(); |
return; |
} |
- |
RequestPriorityParams new_priority_params(new_priority, |
new_intra_priority_value); |
RequestPriorityParams old_priority_params = |
@@ -395,11 +634,11 @@ void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request, |
DCHECK(old_priority_params != new_priority_params); |
- request->url_request()->SetPriority(new_priority_params.priority); |
- request->set_request_priority_params(new_priority_params); |
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. |
+ request->url_request()->SetPriority(new_priority_params.priority); |
+ request->set_request_priority_params(new_priority_params); |
return; |
} |
@@ -407,164 +646,8 @@ void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request, |
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); |
- |
- if (new_priority_params.priority > old_priority_params.priority) { |
- // Check if this request is now able to load at its new priority. |
- LoadAnyStartablePendingRequests(client); |
- } |
-} |
- |
-void ResourceScheduler::LoadAnyStartablePendingRequests(Client* client) { |
- // We iterate through all the pending requests, starting with the highest |
- // priority one. For each entry, one of three things can happen: |
- // 1) We start the request, remove it from the list, and keep checking. |
- // 2) We do NOT start the request, but ShouldStartRequest() signals us that |
- // there may be room for other requests, so we keep checking and leave |
- // the previous request still in the list. |
- // 3) We do not start the request, same as above, but StartRequest() tells |
- // us there's no point in checking any further requests. |
- RequestQueue::NetQueue::iterator request_iter = |
- client->pending_requests.GetNextHighestIterator(); |
- while (request_iter != client->pending_requests.End()) { |
- ScheduledResourceRequest* request = *request_iter; |
- ShouldStartReqResult query_result = ShouldStartRequest(request, client); |
- |
- if (query_result == START_REQUEST) { |
- client->pending_requests.Erase(request); |
- StartRequest(request, client); |
- |
- // StartRequest can modify the pending list, so we (re)start evaluation |
- // from the currently highest priority request. Avoid copying a singular |
- // iterator, which would trigger undefined behavior. |
- if (client->pending_requests.GetNextHighestIterator() == |
- client->pending_requests.End()) |
- break; |
- request_iter = client->pending_requests.GetNextHighestIterator(); |
- } else if (query_result == DO_NOT_START_REQUEST_AND_KEEP_SEARCHING) { |
- ++request_iter; |
- continue; |
- } else { |
- DCHECK(query_result == DO_NOT_START_REQUEST_AND_STOP_SEARCHING); |
- break; |
- } |
- } |
-} |
- |
-void ResourceScheduler::GetNumDelayableRequestsInFlight( |
- Client* client, |
- const net::HostPortPair& active_request_host, |
- size_t* total_delayable, |
- size_t* total_for_active_host) const { |
- DCHECK(client != NULL && total_delayable != NULL && |
- total_for_active_host != NULL); |
- |
- size_t total_delayable_count = 0; |
- size_t same_host_count = 0; |
- for (RequestSet::iterator it = client->in_flight_requests.begin(); |
- it != client->in_flight_requests.end(); ++it) { |
- net::HostPortPair host_port_pair = |
- net::HostPortPair::FromURL((*it)->url_request()->url()); |
- |
- if (active_request_host.Equals(host_port_pair)) { |
- same_host_count++; |
- } |
- |
- if ((*it)->url_request()->priority() < net::LOW) { |
- const net::HttpServerProperties& http_server_properties = |
- *(*it)->url_request()->context()->http_server_properties(); |
- |
- if (!http_server_properties.SupportsSpdy(host_port_pair)) { |
- ++total_delayable_count; |
- } |
- } |
- } |
- *total_delayable = total_delayable_count; |
- *total_for_active_host = same_host_count; |
-} |
- |
-// 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. |
-// * Requests to SPDY-capable origin servers. |
-// * Non-HTTP[S] 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. |
-// * Never exceed 6 delayable requests for a given host. |
-// * Prior to <body>, allow one delayable request to load at a time. |
-ResourceScheduler::ShouldStartReqResult ResourceScheduler::ShouldStartRequest( |
- ScheduledResourceRequest* request, |
- Client* client) const { |
- const net::URLRequest& url_request = *request->url_request(); |
- |
- // TODO(simonjam): This may end up causing disk contention. We should |
- // experiment with throttling if that happens. |
- if (!url_request.url().SchemeIsHTTPOrHTTPS()) { |
- return START_REQUEST; |
- } |
- |
- if (client->using_spdy_proxy && url_request.url().SchemeIs("http")) { |
- return START_REQUEST; |
- } |
- |
- const net::HttpServerProperties& http_server_properties = |
- *url_request.context()->http_server_properties(); |
- |
- if (url_request.priority() >= net::LOW || |
- !ResourceRequestInfo::ForRequest(&url_request)->IsAsync()) { |
- return START_REQUEST; |
- } |
- |
- net::HostPortPair host_port_pair = |
- net::HostPortPair::FromURL(url_request.url()); |
- |
- // TODO(willchan): We should really improve this algorithm as described in |
- // crbug.com/164101. Also, theoretically we should not count a SPDY request |
- // against the delayable requests limit. |
- if (http_server_properties.SupportsSpdy(host_port_pair)) { |
- return START_REQUEST; |
- } |
- |
- size_t num_delayable_requests_in_flight = 0; |
- size_t num_requests_in_flight_for_host = 0; |
- GetNumDelayableRequestsInFlight(client, host_port_pair, |
- &num_delayable_requests_in_flight, |
- &num_requests_in_flight_for_host); |
- |
- if (num_delayable_requests_in_flight >= kMaxNumDelayableRequestsPerClient) { |
- return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; |
- } |
- |
- if (num_requests_in_flight_for_host >= kMaxNumDelayableRequestsPerHost) { |
- // There may be other requests for other hosts we'd allow, so keep checking. |
- return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING; |
- } |
- |
- 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 && |
- num_delayable_requests_in_flight != 0) { |
- return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; |
- } |
- |
- return START_REQUEST; |
+ client->ReprioritizeRequest( |
+ request, old_priority_params, new_priority_params); |
} |
ResourceScheduler::ClientId ResourceScheduler::MakeClientId( |