| 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() {
|
|
|