blob: 7c6dbfa5746c41acbe7860a075e519ee0fe47380 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/loader/resource_scheduler.h"
#include <stdint.h>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/supports_user_data.h"
#include "content/common/resource_messages.h"
#include "content/public/browser/resource_controller.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/resource_throttle.h"
#include "net/base/host_port_pair.h"
#include "net/base/load_flags.h"
#include "net/base/request_priority.h"
#include "net/http/http_server_properties.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "url/scheme_host_port.h"
namespace content {
namespace {
enum StartMode {
START_SYNC,
START_ASYNC
};
// Field trial constants
const char kRequestLimitFieldTrial[] = "OutstandingRequestLimiting";
const char kRequestLimitFieldTrialGroupPrefix[] = "Limit";
// 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;
} // namespace
// The maximum number of delayable requests to allow to be in-flight at any
// point in time (across all hosts).
static const size_t kMaxNumDelayableRequestsPerClient = 10;
// The maximum number of requests to allow be in-flight at any point in time per
// host.
static const size_t kMaxNumDelayableRequestsPerHostPerClient = 6;
// The maximum number of delayable requests to allow to be in-flight at any
// point in time while in the layout-blocking phase of loading.
static const size_t kMaxNumDelayableWhileLayoutBlockingPerClient = 1;
// The priority level above which resources are considered layout-blocking if
// the html_body has not started.
static const net::RequestPriority
kLayoutBlockingPriorityThreshold = net::MEDIUM;
// The priority level below which resources are considered to be delayable.
static const net::RequestPriority
kDelayablePriorityThreshold = net::MEDIUM;
// The number of in-flight layout-blocking requests above which all delayable
// requests should be blocked.
static const size_t kInFlightNonDelayableRequestCountPerClientThreshold = 1;
struct ResourceScheduler::RequestPriorityParams {
RequestPriorityParams()
: priority(net::DEFAULT_PRIORITY),
intra_priority(0) {
}
RequestPriorityParams(net::RequestPriority priority, int intra_priority)
: priority(priority),
intra_priority(intra_priority) {
}
bool operator==(const RequestPriorityParams& other) const {
return (priority == other.priority) &&
(intra_priority == other.intra_priority);
}
bool operator!=(const RequestPriorityParams& other) const {
return !(*this == other);
}
bool GreaterThan(const RequestPriorityParams& other) const {
if (priority != other.priority)
return priority > other.priority;
return intra_priority > other.intra_priority;
}
net::RequestPriority priority;
int intra_priority;
};
class ResourceScheduler::RequestQueue {
public:
typedef std::multiset<ScheduledResourceRequest*, ScheduledResourceSorter>
NetQueue;
RequestQueue() : fifo_ordering_ids_(0) {}
~RequestQueue() {}
// Adds |request| to the queue with given |priority|.
void Insert(ScheduledResourceRequest* request);
// Removes |request| from the queue.
void Erase(ScheduledResourceRequest* request) {
PointerMap::iterator it = pointers_.find(request);
CHECK(it != pointers_.end());
queue_.erase(it->second);
pointers_.erase(it);
}
NetQueue::iterator GetNextHighestIterator() {
return queue_.begin();
}
NetQueue::iterator End() {
return queue_.end();
}
// Returns true if |request| is queued.
bool IsQueued(ScheduledResourceRequest* request) const {
return base::ContainsKey(pointers_, request);
}
// Returns true if no requests are queued.
bool IsEmpty() const { return queue_.size() == 0; }
private:
typedef std::map<ScheduledResourceRequest*, NetQueue::iterator> PointerMap;
uint32_t MakeFifoOrderingId() {
fifo_ordering_ids_ += 1;
return fifo_ordering_ids_;
}
// Used to create an ordering ID for scheduled resources so that resources
// with same priority/intra_priority stay in fifo order.
uint32_t fifo_ordering_ids_;
NetQueue queue_;
PointerMap pointers_;
};
// This is the handle we return to the ResourceDispatcherHostImpl so it can
// interact with the request.
class ResourceScheduler::ScheduledResourceRequest : public ResourceThrottle {
public:
ScheduledResourceRequest(const ClientId& client_id,
net::URLRequest* request,
ResourceScheduler* scheduler,
const RequestPriorityParams& priority,
bool is_async)
: client_id_(client_id),
request_(request),
ready_(false),
deferred_(false),
is_async_(is_async),
attributes_(kAttributeNone),
scheduler_(scheduler),
priority_(priority),
fifo_ordering_(0),
weak_ptr_factory_(this) {
DCHECK(!request_->GetUserData(kUserDataKey));
request_->SetUserData(kUserDataKey, new UnownedPointer(this));
}
~ScheduledResourceRequest() override {
request_->RemoveUserData(kUserDataKey);
scheduler_->RemoveRequest(this);
}
static ScheduledResourceRequest* ForRequest(net::URLRequest* request) {
return static_cast<UnownedPointer*>(request->GetUserData(kUserDataKey))
->get();
}
// Starts the request. If |start_mode| is START_ASYNC, the request will not
// be started immediately.
void Start(StartMode start_mode) {
DCHECK(!ready_);
// If the request was cancelled, do nothing.
if (!request_->status().is_success())
return;
// If the request was deferred, need to start it. Otherwise, will just not
// defer starting it in the first place, and the value of |start_mode|
// makes no difference.
if (deferred_) {
// If can't start the request synchronously, post a task to start the
// request.
if (start_mode == START_ASYNC) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&ScheduledResourceRequest::Start,
weak_ptr_factory_.GetWeakPtr(),
START_SYNC));
return;
}
deferred_ = false;
controller()->Resume();
}
ready_ = true;
}
void set_request_priority_params(const RequestPriorityParams& priority) {
priority_ = priority;
}
const RequestPriorityParams& get_request_priority_params() const {
return priority_;
}
const ClientId& client_id() const { return client_id_; }
net::URLRequest* url_request() { return request_; }
const net::URLRequest* url_request() const { return request_; }
bool is_async() const { return is_async_; }
uint32_t fifo_ordering() const { return fifo_ordering_; }
void set_fifo_ordering(uint32_t fifo_ordering) {
fifo_ordering_ = fifo_ordering;
}
RequestAttributes attributes() const {
return attributes_;
}
void set_attributes(RequestAttributes attributes) {
attributes_ = attributes;
}
private:
class UnownedPointer : public base::SupportsUserData::Data {
public:
explicit UnownedPointer(ScheduledResourceRequest* pointer)
: pointer_(pointer) {}
ScheduledResourceRequest* get() const { return pointer_; }
private:
ScheduledResourceRequest* const pointer_;
DISALLOW_COPY_AND_ASSIGN(UnownedPointer);
};
static const void* const kUserDataKey;
// ResourceThrottle interface:
void WillStartRequest(bool* defer) override {
deferred_ = *defer = !ready_;
}
const char* GetNameForLogging() const override { return "ResourceScheduler"; }
const ClientId client_id_;
net::URLRequest* request_;
bool ready_;
bool deferred_;
bool is_async_;
RequestAttributes attributes_;
ResourceScheduler* scheduler_;
RequestPriorityParams priority_;
uint32_t fifo_ordering_;
base::WeakPtrFactory<ResourceScheduler::ScheduledResourceRequest>
weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest);
};
const void* const ResourceScheduler::ScheduledResourceRequest::kUserDataKey =
&ResourceScheduler::ScheduledResourceRequest::kUserDataKey;
bool ResourceScheduler::ScheduledResourceSorter::operator()(
const ScheduledResourceRequest* a,
const ScheduledResourceRequest* b) const {
// Want the set to be ordered first by decreasing priority, then by
// decreasing intra_priority.
// ie. with (priority, intra_priority)
// [(1, 0), (1, 0), (0, 100), (0, 0)]
if (a->get_request_priority_params() != b->get_request_priority_params())
return a->get_request_priority_params().GreaterThan(
b->get_request_priority_params());
// If priority/intra_priority is the same, fall back to fifo ordering.
// std::multiset doesn't guarantee this until c++11.
return a->fifo_ordering() < b->fifo_ordering();
}
void ResourceScheduler::RequestQueue::Insert(
ScheduledResourceRequest* request) {
DCHECK(!base::ContainsKey(pointers_, request));
request->set_fifo_ordering(MakeFifoOrderingId());
pointers_[request] = queue_.insert(request);
}
// Each client represents a tab.
class ResourceScheduler::Client {
public:
explicit Client(ResourceScheduler* scheduler)
: is_loaded_(false),
has_html_body_(false),
using_spdy_proxy_(false),
scheduler_(scheduler),
in_flight_delayable_count_(0),
total_layout_blocking_count_(0) {}
~Client() {}
void ScheduleRequest(net::URLRequest* url_request,
ScheduledResourceRequest* request) {
SetRequestAttributes(request, DetermineRequestAttributes(request));
if (ShouldStartRequest(request) == START_REQUEST) {
// New requests can be started synchronously without issue.
StartRequest(request, START_SYNC);
} else {
pending_requests_.Insert(request);
}
}
void RemoveRequest(ScheduledResourceRequest* request) {
if (pending_requests_.IsQueued(request)) {
pending_requests_.Erase(request);
DCHECK(!base::ContainsKey(in_flight_requests_, request));
} else {
EraseInFlightRequest(request);
// Removing this request may have freed up another to load.
LoadAnyStartablePendingRequests();
}
}
RequestSet StartAndRemoveAllRequests() {
// First start any pending requests so that they will be moved into
// in_flight_requests_. This may exceed the limits
// kDefaultMaxNumDelayableRequestsPerClient and
// kMaxNumDelayableRequestsPerHostPerClient, 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();
pending_requests_.Erase(request);
// Starting requests asynchronously ensures no side effects, and avoids
// starting a bunch of requests that may be about to be deleted.
StartRequest(request, START_ASYNC);
}
RequestSet unowned_requests;
for (RequestSet::iterator it = in_flight_requests_.begin();
it != in_flight_requests_.end(); ++it) {
unowned_requests.insert(*it);
(*it)->set_attributes(kAttributeNone);
}
ClearInFlightRequests();
return unowned_requests;
}
bool is_loaded() const { return is_loaded_; }
void OnLoadingStateChanged(bool is_loaded) {
is_loaded_ = is_loaded;
}
void OnNavigate() {
has_html_body_ = false;
is_loaded_ = false;
}
void OnWillInsertBody() {
has_html_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);
SetRequestAttributes(request, DetermineRequestAttributes(request));
if (!pending_requests_.IsQueued(request)) {
DCHECK(base::ContainsKey(in_flight_requests_, 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,
DO_NOT_START_REQUEST_AND_KEEP_SEARCHING,
START_REQUEST,
};
void InsertInFlightRequest(ScheduledResourceRequest* request) {
in_flight_requests_.insert(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.
SetRequestAttributes(request, kAttributeNone);
}
void ClearInFlightRequests() {
in_flight_requests_.clear();
in_flight_delayable_count_ = 0;
total_layout_blocking_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 (RequestAttributesAreSet((*it)->attributes(), attributes))
matching_request_count++;
}
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 (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 &&
!base::ContainsKey(in_flight_requests_, current_request) &&
!current_request_is_pending) {
if (RequestAttributesAreSet(current_request->attributes(), attributes))
matching_request_count++;
}
}
return matching_request_count;
}
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 (RequestAttributesAreSet(old_attributes,
kAttributeInFlight | kAttributeDelayable)) {
in_flight_delayable_count_--;
}
if (RequestAttributesAreSet(old_attributes, kAttributeLayoutBlocking))
total_layout_blocking_count_--;
if (RequestAttributesAreSet(attributes,
kAttributeInFlight | kAttributeDelayable)) {
in_flight_delayable_count_++;
}
if (RequestAttributesAreSet(attributes, kAttributeLayoutBlocking))
total_layout_blocking_count_++;
request->set_attributes(attributes);
DCHECK_EQ(CountRequestsWithAttributes(
kAttributeInFlight | kAttributeDelayable, request),
in_flight_delayable_count_);
DCHECK_EQ(CountRequestsWithAttributes(kAttributeLayoutBlocking, request),
total_layout_blocking_count_);
}
RequestAttributes DetermineRequestAttributes(
ScheduledResourceRequest* request) {
RequestAttributes attributes = kAttributeNone;
if (base::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() >
kLayoutBlockingPriorityThreshold) {
// 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() <
kDelayablePriorityThreshold) {
// Resources below the delayable priority threshold that are being
// requested from a server that does not support native prioritization are
// considered delayable.
url::SchemeHostPort scheme_host_port(request->url_request()->url());
net::HttpServerProperties& http_server_properties =
*request->url_request()->context()->http_server_properties();
if (!http_server_properties.SupportsRequestPriority(scheme_host_port))
attributes |= kAttributeDelayable;
}
return attributes;
}
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 >= kMaxNumDelayableRequestsPerHostPerClient)
return true;
}
}
return false;
}
void StartRequest(ScheduledResourceRequest* request,
StartMode start_mode) {
InsertInFlightRequest(request);
request->Start(start_mode);
}
// ShouldStartRequest is the main scheduling algorithm.
//
// Requests are evaluated on five attributes:
//
// 1. Non-delayable requests:
// * Synchronous requests.
// * Non-HTTP[S] requests.
//
// 2. Requests to request-priority-capable origin servers.
//
// 3. High-priority requests:
// * Higher priority requests (>= net::LOW).
//
// 4. Layout-blocking requests:
// * High-priority requests (> net::LOW) initiated before the renderer has
// a <body>.
//
// 5. Low priority requests
//
// The following rules are followed:
//
// All types of requests:
// * If an outstanding request limit is in place, only that number
// of requests may be in flight for a single client at the same time.
// * Non-delayable, High-priority and request-priority capable requests are
// issued immediately.
// * Low priority requests are delayable.
// * While kInFlightNonDelayableRequestCountPerClientThreshold
// layout-blocking requests are loading or the body tag has not yet been
// parsed, limit the number of delayable requests that may be in flight
// (to kMaxNumDelayableWhileLayoutBlockingPerClient by default, or to zero
// if there's an outstanding request limit in place).
// * If no high priority or layout-blocking requests are in flight, start
// loading delayable requests.
// * Never exceed 10 delayable requests in flight per client.
// * Never exceed 6 delayable requests for a given host.
ShouldStartReqResult ShouldStartRequest(
ScheduledResourceRequest* request) const {
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())
return START_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(url::kHttpScheme))
return START_REQUEST;
// Implementation of the kRequestLimitFieldTrial.
if (scheduler_->limit_outstanding_requests() &&
in_flight_requests_.size() >= scheduler_->outstanding_request_limit()) {
return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
}
net::HostPortPair host_port_pair =
net::HostPortPair::FromURL(url_request.url());
url::SchemeHostPort scheme_host_port(url_request.url());
net::HttpServerProperties& http_server_properties =
*url_request.context()->http_server_properties();
// 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(scheme_host_port))
return START_REQUEST;
// Non-delayable requests.
if (!RequestAttributesAreSet(request->attributes(), kAttributeDelayable))
return START_REQUEST;
if (in_flight_delayable_count_ >= kMaxNumDelayableRequestsPerClient)
return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
if (ShouldKeepSearching(host_port_pair)) {
// There may be other requests for other hosts that may be allowed,
// so keep checking.
return DO_NOT_START_REQUEST_AND_KEEP_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 (non_delayable_requests_in_flight_count >
kInFlightNonDelayableRequestCountPerClientThreshold) {
// 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_ >=
kMaxNumDelayableWhileLayoutBlockingPerClient)) {
// 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;
}
}
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, START_ASYNC);
// 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 is_loaded_;
// 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_;
ResourceScheduler* scheduler_;
// The number of delayable in-flight requests.
size_t in_flight_delayable_count_;
// The number of layout-blocking in-flight requests.
size_t total_layout_blocking_count_;
};
ResourceScheduler::ResourceScheduler()
: limit_outstanding_requests_(false),
outstanding_request_limit_(0) {
std::string outstanding_limit_trial_group =
base::FieldTrialList::FindFullName(kRequestLimitFieldTrial);
std::vector<std::string> split_group(
base::SplitString(outstanding_limit_trial_group, "=",
base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL));
int outstanding_limit = 0;
if (split_group.size() == 2 &&
split_group[0] == kRequestLimitFieldTrialGroupPrefix &&
base::StringToInt(split_group[1], &outstanding_limit) &&
outstanding_limit > 0) {
limit_outstanding_requests_ = true;
outstanding_request_limit_ = outstanding_limit;
}
}
ResourceScheduler::~ResourceScheduler() {
DCHECK(unowned_requests_.empty());
DCHECK(client_map_.empty());
}
std::unique_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest(
int child_id,
int route_id,
bool is_async,
net::URLRequest* url_request) {
DCHECK(CalledOnValidThread());
ClientId client_id = MakeClientId(child_id, route_id);
std::unique_ptr<ScheduledResourceRequest> request(
new ScheduledResourceRequest(
client_id, url_request, this,
RequestPriorityParams(url_request->priority(), 0), is_async));
ClientMap::iterator it = client_map_.find(client_id);
if (it == client_map_.end()) {
// There are several ways this could happen:
// 1. <a ping> requests don't have a route_id.
// 2. Most unittests don't send the IPCs needed to register Clients.
// 3. The tab is closed while a RequestResource IPC is in flight.
unowned_requests_.insert(request.get());
request->Start(START_SYNC);
return std::move(request);
}
Client* client = it->second;
client->ScheduleRequest(url_request, request.get());
return std::move(request);
}
void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) {
DCHECK(CalledOnValidThread());
if (base::ContainsKey(unowned_requests_, request)) {
unowned_requests_.erase(request);
return;
}
ClientMap::iterator client_it = client_map_.find(request->client_id());
if (client_it == client_map_.end()) {
return;
}
Client* client = client_it->second;
client->RemoveRequest(request);
}
void ResourceScheduler::OnClientCreated(int child_id,
int route_id) {
DCHECK(CalledOnValidThread());
ClientId client_id = MakeClientId(child_id, route_id);
DCHECK(!base::ContainsKey(client_map_, client_id));
Client* client = new Client(this);
client_map_[client_id] = client;
}
void ResourceScheduler::OnClientDeleted(int child_id, int route_id) {
DCHECK(CalledOnValidThread());
ClientId client_id = MakeClientId(child_id, route_id);
ClientMap::iterator it = client_map_.find(client_id);
DCHECK(it != client_map_.end());
Client* client = it->second;
// ResourceDispatcherHost cancels all requests except for cross-renderer
// navigations, async revalidations and detachable requests after
// OnClientDeleted() returns.
RequestSet client_unowned_requests = client->StartAndRemoveAllRequests();
for (RequestSet::iterator it = client_unowned_requests.begin();
it != client_unowned_requests.end(); ++it) {
unowned_requests_.insert(*it);
}
delete client;
client_map_.erase(it);
}
void ResourceScheduler::OnLoadingStateChanged(int child_id,
int route_id,
bool is_loaded) {
Client* client = GetClient(child_id, route_id);
DCHECK(client);
client->OnLoadingStateChanged(is_loaded);
}
void ResourceScheduler::OnNavigate(int child_id, int route_id) {
DCHECK(CalledOnValidThread());
ClientId client_id = MakeClientId(child_id, route_id);
ClientMap::iterator it = client_map_.find(client_id);
if (it == client_map_.end()) {
// The client was likely deleted shortly before we received this IPC.
return;
}
Client* client = it->second;
client->OnNavigate();
}
void ResourceScheduler::OnWillInsertBody(int child_id, int route_id) {
DCHECK(CalledOnValidThread());
ClientId client_id = MakeClientId(child_id, route_id);
ClientMap::iterator it = client_map_.find(client_id);
if (it == client_map_.end()) {
// The client was likely deleted shortly before we received this IPC.
return;
}
Client* client = it->second;
client->OnWillInsertBody();
}
void ResourceScheduler::OnReceivedSpdyProxiedHttpResponse(
int child_id,
int route_id) {
DCHECK(CalledOnValidThread());
ClientId client_id = MakeClientId(child_id, route_id);
ClientMap::iterator client_it = client_map_.find(client_id);
if (client_it == client_map_.end()) {
return;
}
Client* client = client_it->second;
client->OnReceivedSpdyProxiedHttpResponse();
}
bool ResourceScheduler::HasLoadingClients() const {
for (const auto& client : client_map_) {
if (!client.second->is_loaded())
return true;
}
return false;
}
ResourceScheduler::Client* ResourceScheduler::GetClient(int child_id,
int route_id) {
ClientId client_id = MakeClientId(child_id, route_id);
ClientMap::iterator client_it = client_map_.find(client_id);
if (client_it == client_map_.end()) {
return NULL;
}
return client_it->second;
}
void ResourceScheduler::ReprioritizeRequest(net::URLRequest* request,
net::RequestPriority new_priority,
int new_intra_priority_value) {
if (request->load_flags() & net::LOAD_IGNORE_LIMITS) {
// Requests with the IGNORE_LIMITS flag must stay at MAXIMUM_PRIORITY.
return;
}
auto* scheduled_resource_request =
ScheduledResourceRequest::ForRequest(request);
// Downloads don't use the resource scheduler.
if (!scheduled_resource_request) {
request->SetPriority(new_priority);
return;
}
RequestPriorityParams new_priority_params(new_priority,
new_intra_priority_value);
RequestPriorityParams old_priority_params =
scheduled_resource_request->get_request_priority_params();
if (old_priority_params == new_priority_params)
return;
ClientMap::iterator client_it =
client_map_.find(scheduled_resource_request->client_id());
if (client_it == client_map_.end()) {
// The client was likely deleted shortly before we received this IPC.
request->SetPriority(new_priority_params.priority);
scheduled_resource_request->set_request_priority_params(
new_priority_params);
return;
}
Client* client = client_it->second;
client->ReprioritizeRequest(scheduled_resource_request, old_priority_params,
new_priority_params);
}
ResourceScheduler::ClientId ResourceScheduler::MakeClientId(
int child_id, int route_id) {
return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id;
}
} // namespace content