Index: content/browser/loader/resource_scheduler.cc |
diff --git a/content/browser/loader/resource_scheduler.cc b/content/browser/loader/resource_scheduler.cc |
index e222143f31c3be16a3fd5d376f4f8e3faf1991e0..e1098ebf4aa1cddb69069d50dfe31537a234f0dd 100644 |
--- a/content/browser/loader/resource_scheduler.cc |
+++ b/content/browser/loader/resource_scheduler.cc |
@@ -2,10 +2,11 @@ |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
-#include <set> |
- |
#include "content/browser/loader/resource_scheduler.h" |
+#include <stdint.h> |
+#include <set> |
+ |
#include "base/metrics/field_trial.h" |
#include "base/metrics/histogram.h" |
#include "base/stl_util.h" |
@@ -36,6 +37,16 @@ const char kThrottleCoalesceFieldTrialCoalesce[] = "Coalesce"; |
const char kRequestLimitFieldTrial[] = "OutstandingRequestLimiting"; |
const char kRequestLimitFieldTrialGroupPrefix[] = "Limit"; |
+const char kResourcePrioritiesFieldTrial[] = "ResourcePriorities"; |
+ |
+// Flags identifying various attributes of the request that are used |
+// when making scheduling decisions. |
+using RequestAttributes = uint8_t; |
+const RequestAttributes kAttributeNone = 0x00; |
+const RequestAttributes kAttributeInFlight = 0x01; |
+const RequestAttributes kAttributeDelayable = 0x02; |
+const RequestAttributes kAttributeLayoutBlocking = 0x04; |
+ |
// Post ResourceScheduler histograms of the following forms: |
// If |histogram_suffix| is NULL or the empty string: |
// ResourceScheduler.base_name.histogram_name |
@@ -73,9 +84,12 @@ const char* GetNumClientsString(size_t num_clients) { |
} // namespace |
static const size_t kCoalescedTimerPeriod = 5000; |
-static const size_t kMaxNumDelayableRequestsPerClient = 10; |
+static const size_t kDefaultMaxNumDelayableRequestsPerClient = 10; |
static const size_t kMaxNumDelayableRequestsPerHost = 6; |
static const size_t kMaxNumThrottledRequestsPerClient = 1; |
+static const size_t kDefaultMaxNumDelayableWhileLayoutBlocking = 1; |
+static const net::RequestPriority |
+ kDefaultLayoutBlockingPriorityThreshold = net::LOW; |
struct ResourceScheduler::RequestPriorityParams { |
RequestPriorityParams() |
@@ -175,7 +189,7 @@ class ResourceScheduler::ScheduledResourceRequest : public ResourceThrottle { |
ready_(false), |
deferred_(false), |
is_async_(is_async), |
- classification_(NORMAL_REQUEST), |
+ attributes_(kAttributeNone), |
scheduler_(scheduler), |
priority_(priority), |
fifo_ordering_(0) { |
@@ -239,11 +253,11 @@ class ResourceScheduler::ScheduledResourceRequest : public ResourceThrottle { |
void set_fifo_ordering(uint32 fifo_ordering) { |
fifo_ordering_ = fifo_ordering; |
} |
- RequestClassification classification() const { |
- return classification_; |
+ RequestAttributes attributes() const { |
+ return attributes_; |
} |
- void set_classification(RequestClassification classification) { |
- classification_ = classification; |
+ void set_attributes(RequestAttributes attributes) { |
+ attributes_ = attributes; |
} |
private: |
@@ -276,7 +290,7 @@ class ResourceScheduler::ScheduledResourceRequest : public ResourceThrottle { |
bool ready_; |
bool deferred_; |
bool is_async_; |
- RequestClassification classification_; |
+ RequestAttributes attributes_; |
ResourceScheduler* scheduler_; |
RequestPriorityParams priority_; |
uint32 fifo_ordering_; |
@@ -321,7 +335,7 @@ class ResourceScheduler::Client { |
is_visible_(is_visible), |
is_loaded_(false), |
is_paused_(false), |
- has_body_(false), |
+ has_html_body_(false), |
using_spdy_proxy_(false), |
load_started_time_(base::TimeTicks::Now()), |
scheduler_(scheduler), |
@@ -340,11 +354,11 @@ class ResourceScheduler::Client { |
void ScheduleRequest(net::URLRequest* url_request, |
ScheduledResourceRequest* request) { |
+ SetRequestAttributes(request, DetermineRequestAttributes(request)); |
if (ShouldStartRequest(request) == START_REQUEST) |
StartRequest(request); |
else |
pending_requests_.Insert(request); |
- SetRequestClassification(request, ClassifyRequest(request)); |
} |
void RemoveRequest(ScheduledResourceRequest* request) { |
@@ -362,10 +376,10 @@ class ResourceScheduler::Client { |
RequestSet StartAndRemoveAllRequests() { |
// First start any pending requests so that they will be moved into |
// in_flight_requests_. This may exceed the limits |
- // kMaxNumDelayableRequestsPerClient, kMaxNumDelayableRequestsPerHost and |
- // kMaxNumThrottledRequestsPerClient, so this method must not do anything |
- // that depends on those limits before calling ClearInFlightRequests() |
- // below. |
+ // kDefaultMaxNumDelayableRequestsPerClient, kMaxNumDelayableRequestsPerHost |
+ // and kMaxNumThrottledRequestsPerClient, so this method must not do |
+ // anything that depends on those limits before calling |
+ // ClearInFlightRequests() below. |
while (!pending_requests_.IsEmpty()) { |
ScheduledResourceRequest* request = |
*pending_requests_.GetNextHighestIterator(); |
@@ -377,7 +391,7 @@ class ResourceScheduler::Client { |
for (RequestSet::iterator it = in_flight_requests_.begin(); |
it != in_flight_requests_.end(); ++it) { |
unowned_requests.insert(*it); |
- (*it)->set_classification(NORMAL_REQUEST); |
+ (*it)->set_attributes(kAttributeNone); |
} |
ClearInFlightRequests(); |
return unowned_requests; |
@@ -482,12 +496,12 @@ class ResourceScheduler::Client { |
} |
void OnNavigate() { |
- has_body_ = false; |
+ has_html_body_ = false; |
is_loaded_ = false; |
} |
void OnWillInsertBody() { |
- has_body_ = true; |
+ has_html_body_ = true; |
LoadAnyStartablePendingRequests(); |
} |
@@ -503,11 +517,9 @@ class ResourceScheduler::Client { |
RequestPriorityParams new_priority_params) { |
request->url_request()->SetPriority(new_priority_params.priority); |
request->set_request_priority_params(new_priority_params); |
+ SetRequestAttributes(request, DetermineRequestAttributes(request)); |
if (!pending_requests_.IsQueued(request)) { |
DCHECK(ContainsKey(in_flight_requests_, request)); |
- // The priority of the request and priority support of the server may |
- // have changed, so update the delayable count. |
- SetRequestClassification(request, ClassifyRequest(request)); |
// Request has already started. |
return; |
} |
@@ -574,14 +586,14 @@ class ResourceScheduler::Client { |
void InsertInFlightRequest(ScheduledResourceRequest* request) { |
in_flight_requests_.insert(request); |
- SetRequestClassification(request, ClassifyRequest(request)); |
+ SetRequestAttributes(request, DetermineRequestAttributes(request)); |
} |
void EraseInFlightRequest(ScheduledResourceRequest* request) { |
size_t erased = in_flight_requests_.erase(request); |
DCHECK_EQ(1u, erased); |
// Clear any special state that we were tracking for this request. |
- SetRequestClassification(request, NORMAL_REQUEST); |
+ SetRequestAttributes(request, kAttributeNone); |
} |
void ClearInFlightRequests() { |
@@ -590,71 +602,101 @@ class ResourceScheduler::Client { |
total_layout_blocking_count_ = 0; |
} |
- size_t CountRequestsWithClassification( |
- const RequestClassification classification, const bool include_pending) { |
- size_t classification_request_count = 0; |
+ size_t CountRequestsWithAttributes( |
+ const RequestAttributes attributes, |
+ ScheduledResourceRequest* current_request) { |
+ size_t matching_request_count = 0; |
for (RequestSet::const_iterator it = in_flight_requests_.begin(); |
it != in_flight_requests_.end(); ++it) { |
- if ((*it)->classification() == classification) |
- classification_request_count++; |
+ if (RequestAttributesAreSet((*it)->attributes(), attributes)) |
+ matching_request_count++; |
} |
- if (include_pending) { |
+ if (!RequestAttributesAreSet(attributes, kAttributeInFlight)) { |
+ bool current_request_is_pending = false; |
for (RequestQueue::NetQueue::const_iterator |
it = pending_requests_.GetNextHighestIterator(); |
it != pending_requests_.End(); ++it) { |
- if ((*it)->classification() == classification) |
- classification_request_count++; |
+ if (RequestAttributesAreSet((*it)->attributes(), attributes)) |
+ matching_request_count++; |
+ if (*it == current_request) |
+ current_request_is_pending = true; |
+ } |
+ // Account for the current request if it is not in one of the lists yet. |
+ if (current_request && |
+ !ContainsKey(in_flight_requests_, current_request) && |
+ !current_request_is_pending) { |
+ if (RequestAttributesAreSet(current_request->attributes(), attributes)) |
+ matching_request_count++; |
} |
} |
- return classification_request_count; |
+ return matching_request_count; |
} |
- void SetRequestClassification(ScheduledResourceRequest* request, |
- RequestClassification classification) { |
- RequestClassification old_classification = request->classification(); |
- if (old_classification == classification) |
+ bool RequestAttributesAreSet(RequestAttributes request_attributes, |
+ RequestAttributes matching_attributes) const { |
+ return (request_attributes & matching_attributes) == matching_attributes; |
+ } |
+ |
+ void SetRequestAttributes(ScheduledResourceRequest* request, |
+ RequestAttributes attributes) { |
+ RequestAttributes old_attributes = request->attributes(); |
+ if (old_attributes == attributes) |
return; |
- if (old_classification == IN_FLIGHT_DELAYABLE_REQUEST) |
+ if (RequestAttributesAreSet(old_attributes, |
+ kAttributeInFlight | kAttributeDelayable)) { |
in_flight_delayable_count_--; |
- if (old_classification == LAYOUT_BLOCKING_REQUEST) |
+ } |
+ if (RequestAttributesAreSet(old_attributes, kAttributeLayoutBlocking)) |
total_layout_blocking_count_--; |
- if (classification == IN_FLIGHT_DELAYABLE_REQUEST) |
+ if (RequestAttributesAreSet(attributes, |
+ kAttributeInFlight | kAttributeDelayable)) { |
in_flight_delayable_count_++; |
- if (classification == LAYOUT_BLOCKING_REQUEST) |
+ } |
+ if (RequestAttributesAreSet(attributes, kAttributeLayoutBlocking)) |
total_layout_blocking_count_++; |
- request->set_classification(classification); |
- DCHECK_EQ( |
- CountRequestsWithClassification(IN_FLIGHT_DELAYABLE_REQUEST, false), |
+ request->set_attributes(attributes); |
+ DCHECK_EQ(CountRequestsWithAttributes( |
+ kAttributeInFlight | kAttributeDelayable, request), |
in_flight_delayable_count_); |
- DCHECK_EQ(CountRequestsWithClassification(LAYOUT_BLOCKING_REQUEST, true), |
+ DCHECK_EQ(CountRequestsWithAttributes(kAttributeLayoutBlocking, request), |
total_layout_blocking_count_); |
} |
- RequestClassification ClassifyRequest(ScheduledResourceRequest* request) { |
- // If a request is already marked as layout-blocking make sure to keep the |
- // classification across redirects unless the priority was lowered. |
- if (request->classification() == LAYOUT_BLOCKING_REQUEST && |
- request->url_request()->priority() > net::LOW) { |
- return LAYOUT_BLOCKING_REQUEST; |
- } |
- |
- if (!has_body_ && request->url_request()->priority() > net::LOW) |
- return LAYOUT_BLOCKING_REQUEST; |
- |
- if (request->url_request()->priority() < net::LOW) { |
+ RequestAttributes DetermineRequestAttributes( |
+ ScheduledResourceRequest* request) { |
+ RequestAttributes attributes = kAttributeNone; |
+ |
+ if (ContainsKey(in_flight_requests_, request)) |
+ attributes |= kAttributeInFlight; |
+ |
+ if (RequestAttributesAreSet(request->attributes(), |
+ kAttributeLayoutBlocking)) { |
+ // If a request is already marked as layout-blocking make sure to keep the |
+ // attribute across redirects. |
+ attributes |= kAttributeLayoutBlocking; |
+ } else if (!has_html_body_ && |
+ request->url_request()->priority() > |
+ scheduler_->non_delayable_threshold()) { |
+ // Requests that are above the non_delayable threshold before the HTML |
+ // body has been parsed are inferred to be layout-blocking. |
+ attributes |= kAttributeLayoutBlocking; |
+ } else if (request->url_request()->priority() < |
+ scheduler_->non_delayable_threshold()) { |
+ // Resources below the non_delayable_threshold that are being requested |
+ // from a server that does not support native prioritization are |
+ // considered delayable. |
net::HostPortPair host_port_pair = |
net::HostPortPair::FromURL(request->url_request()->url()); |
net::HttpServerProperties& http_server_properties = |
*request->url_request()->context()->http_server_properties(); |
- if (!http_server_properties.SupportsRequestPriority(host_port_pair) && |
- ContainsKey(in_flight_requests_, request)) { |
- return IN_FLIGHT_DELAYABLE_REQUEST; |
- } |
+ if (!http_server_properties.SupportsRequestPriority(host_port_pair)) |
+ attributes |= kAttributeDelayable; |
} |
- return NORMAL_REQUEST; |
+ |
+ return attributes; |
} |
bool ShouldKeepSearching( |
@@ -735,25 +777,21 @@ class ResourceScheduler::Client { |
const net::URLRequest& url_request = *request->url_request(); |
// Syncronous requests could block the entire render, which could impact |
// user-observable Clients. |
- if (!request->is_async()) { |
+ if (!request->is_async()) |
return START_REQUEST; |
- } |
// TODO(simonjam): This may end up causing disk contention. We should |
// experiment with throttling if that happens. |
// TODO(aiolos): We probably want to Coalesce these as well to avoid |
// waking the disk. |
- if (!url_request.url().SchemeIsHTTPOrHTTPS()) { |
+ if (!url_request.url().SchemeIsHTTPOrHTTPS()) |
return START_REQUEST; |
- } |
- if (throttle_state_ == COALESCED) { |
+ if (throttle_state_ == COALESCED) |
return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; |
- } |
- if (using_spdy_proxy_ && url_request.url().SchemeIs(url::kHttpScheme)) { |
+ if (using_spdy_proxy_ && url_request.url().SchemeIs(url::kHttpScheme)) |
return START_REQUEST; |
- } |
// Implementation of the kRequestLimitFieldTrial. |
if (scheduler_->limit_outstanding_requests() && |
@@ -769,9 +807,8 @@ class ResourceScheduler::Client { |
// TODO(willchan): We should really improve this algorithm as described in |
// crbug.com/164101. Also, theoretically we should not count a |
// request-priority capable request against the delayable requests limit. |
- if (http_server_properties.SupportsRequestPriority(host_port_pair)) { |
+ if (http_server_properties.SupportsRequestPriority(host_port_pair)) |
return START_REQUEST; |
- } |
if (throttle_state_ == THROTTLED && |
in_flight_requests_.size() >= kMaxNumThrottledRequestsPerClient) { |
@@ -780,30 +817,55 @@ class ResourceScheduler::Client { |
return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING; |
} |
- // High-priority and layout-blocking requests. |
- if (url_request.priority() >= net::LOW) { |
+ // Non-delayable requests. |
+ if (!RequestAttributesAreSet(request->attributes(), kAttributeDelayable)) |
return START_REQUEST; |
- } |
- if (in_flight_delayable_count_ >= kMaxNumDelayableRequestsPerClient) { |
+ if (in_flight_delayable_count_ >= |
+ scheduler_->max_num_delayable_requests()) { |
return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; |
} |
if (ShouldKeepSearching(host_port_pair)) { |
- // There may be other requests for other hosts we'd allow, |
+ // There may be other requests for other hosts that may be allowed, |
// so keep checking. |
return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING; |
} |
- bool have_immediate_requests_in_flight = |
- in_flight_requests_.size() > in_flight_delayable_count_; |
- if (have_immediate_requests_in_flight && |
- (!has_body_ || total_layout_blocking_count_ != 0) && |
- // Do not allow a low priority request through in parallel if |
- // we are in a limit field trial. |
- (scheduler_->limit_outstanding_requests() || |
- in_flight_delayable_count_ != 0)) { |
- return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; |
+ // The in-flight requests consist of layout-blocking requests, |
+ // normal requests and delayable requests. Everything except for |
+ // delayable requests is handled above here so this is deciding what to |
+ // do with a delayable request while we are in the layout-blocking phase |
+ // of loading. |
+ if (!has_html_body_ || total_layout_blocking_count_ != 0) { |
+ size_t non_delayable_requests_in_flight_count = |
+ in_flight_requests_.size() - in_flight_delayable_count_; |
+ if (scheduler_->enable_in_flight_non_delayable_threshold()) { |
+ if (non_delayable_requests_in_flight_count > |
+ scheduler_->in_flight_non_delayable_threshold()) { |
+ // Too many higher priority in-flight requests to allow lower priority |
+ // requests through. |
+ return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; |
+ } |
+ if (in_flight_requests_.size() > 0 && |
+ (scheduler_->limit_outstanding_requests() || |
+ in_flight_delayable_count_ >= |
+ scheduler_->max_num_delayable_while_layout_blocking())) { |
+ // Block the request if at least one request is in flight and the |
+ // number of in-flight delayable requests has hit the configured |
+ // limit. |
+ return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; |
+ } |
+ } else if (non_delayable_requests_in_flight_count > 0 && |
+ (scheduler_->limit_outstanding_requests() || |
+ in_flight_delayable_count_ >= |
+ scheduler_->max_num_delayable_while_layout_blocking())) { |
+ // If there are no high-priority requests in flight the floodgates open. |
+ // If there are high-priority requests in-flight then limit the number |
+ // of lower-priority requests (or zero if a limit field trial is |
+ // active). |
+ return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; |
+ } |
} |
return START_REQUEST; |
@@ -850,7 +912,9 @@ class ResourceScheduler::Client { |
bool is_visible_; |
bool is_loaded_; |
bool is_paused_; |
- bool has_body_; |
+ // Tracks if the main HTML parser has reached the body which marks the end of |
+ // layout-blocking resources. |
+ bool has_html_body_; |
bool using_spdy_proxy_; |
RequestQueue pending_requests_; |
RequestSet in_flight_requests_; |
@@ -872,6 +936,13 @@ ResourceScheduler::ResourceScheduler() |
coalesced_clients_(0), |
limit_outstanding_requests_(false), |
outstanding_request_limit_(0), |
+ non_delayable_threshold_( |
+ kDefaultLayoutBlockingPriorityThreshold), |
+ enable_in_flight_non_delayable_threshold_(false), |
+ in_flight_non_delayable_threshold_(0), |
+ max_num_delayable_while_layout_blocking_( |
+ kDefaultMaxNumDelayableWhileLayoutBlocking), |
+ max_num_delayable_requests_(kDefaultMaxNumDelayableRequestsPerClient), |
coalescing_timer_(new base::Timer(true /* retain_user_task */, |
true /* is_repeating */)) { |
std::string throttling_trial_group = |
@@ -896,6 +967,42 @@ ResourceScheduler::ResourceScheduler() |
limit_outstanding_requests_ = true; |
outstanding_request_limit_ = outstanding_limit; |
} |
+ |
+ // Set up the ResourceScheduling field trial options. |
+ // The field trial parameters are also encoded into the group name since |
+ // the variations component is not available from here and plumbing the |
+ // options through the code is overkill for a short experiment. |
+ // |
+ // The group name encoding looks like this: |
+ // <descriptiveName>_ABCDE_E2_F_G |
+ // A - fetchDeferLateScripts (1 for true, 0 for false) |
+ // B - fetchIncreaseFontPriority (1 for true, 0 for false) |
+ // C - fetchIncreaseAsyncScriptPriority (1 for true, 0 for false) |
+ // D - fetchIncreasePriorities (1 for true, 0 for false) |
+ // E - fetchEnableLayoutBlockingThreshold (1 for true, 0 for false) |
+ // E2 - fetchLayoutBlockingThreshold (Numeric) |
+ // F - fetchMaxNumDelayableWhileLayoutBlocking (Numeric) |
+ // G - fetchMaxNumDelayableRequests (Numeric) |
+ std::string resource_priorities_trial_group = |
+ base::FieldTrialList::FindFullName(kResourcePrioritiesFieldTrial); |
+ std::vector<std::string> resource_priorities_split_group( |
+ base::SplitString(resource_priorities_trial_group, "_", |
+ base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)); |
+ if (resource_priorities_split_group.size() == 5 && |
+ resource_priorities_split_group[1].length() == 5) { |
+ // fetchIncreasePriorities |
+ if (resource_priorities_split_group[1].at(3) == '1') |
+ non_delayable_threshold_ = net::MEDIUM; |
+ enable_in_flight_non_delayable_threshold_ = |
+ resource_priorities_split_group[1].at(4) == '1'; |
+ size_t numeric_value = 0; |
+ if (base::StringToSizeT(resource_priorities_split_group[2], &numeric_value)) |
+ in_flight_non_delayable_threshold_ = numeric_value; |
+ if (base::StringToSizeT(resource_priorities_split_group[3], &numeric_value)) |
+ max_num_delayable_while_layout_blocking_ = numeric_value; |
+ if (base::StringToSizeT(resource_priorities_split_group[4], &numeric_value)) |
+ max_num_delayable_requests_ = numeric_value; |
+ } |
} |
ResourceScheduler::~ResourceScheduler() { |