| Index: content/browser/loader/resource_scheduler.cc
|
| diff --git a/content/browser/loader/resource_scheduler.cc b/content/browser/loader/resource_scheduler.cc
|
| index 680ddfee3c5944aa913af4162c872a47736a095c..f21dd4badfadafc10f5b15e2dce73e9e003200d3 100644
|
| --- a/content/browser/loader/resource_scheduler.cc
|
| +++ b/content/browser/loader/resource_scheduler.cc
|
| @@ -24,7 +24,7 @@ namespace content {
|
|
|
| static const size_t kMaxNumDelayableRequestsPerClient = 10;
|
| static const size_t kMaxNumDelayableRequestsPerHost = 6;
|
| -
|
| +static const size_t kMaxNumThrottledRequestsPerClient = 1;
|
|
|
| struct ResourceScheduler::RequestPriorityParams {
|
| RequestPriorityParams()
|
| @@ -229,10 +229,16 @@ void ResourceScheduler::RequestQueue::Insert(
|
| // Each client represents a tab.
|
| class ResourceScheduler::Client {
|
| public:
|
| - Client()
|
| - : has_body_(false),
|
| + explicit Client(ResourceScheduler* scheduler)
|
| + : is_audible_(false),
|
| + is_visible_(false),
|
| + is_loaded_(false),
|
| + has_body_(false),
|
| using_spdy_proxy_(false),
|
| - total_delayable_count_(0) {}
|
| + total_delayable_count_(0),
|
| + throttle_state_(ResourceScheduler::THROTTLED) {
|
| + scheduler_ = scheduler;
|
| + }
|
| ~Client() {}
|
|
|
| void ScheduleRequest(
|
| @@ -268,8 +274,60 @@ class ResourceScheduler::Client {
|
| return unowned_requests;
|
| }
|
|
|
| + bool is_active() const { return is_visible_ || is_audible_; }
|
| +
|
| + bool is_loaded() const { return is_loaded_; }
|
| +
|
| + void OnAudibilityChanged(bool is_audible) {
|
| + if (is_audible == is_audible_) {
|
| + return;
|
| + }
|
| + is_audible_ = is_audible;
|
| + UpdateThrottleState();
|
| + }
|
| +
|
| + void OnVisibilityChanged(bool is_visible) {
|
| + if (is_visible == is_visible_) {
|
| + return;
|
| + }
|
| + is_visible_ = is_visible;
|
| + UpdateThrottleState();
|
| + }
|
| +
|
| + void OnLoadingStateChanged(bool is_loaded) {
|
| + if (is_loaded == is_loaded_) {
|
| + return;
|
| + }
|
| + is_loaded_ = is_loaded;
|
| + UpdateThrottleState();
|
| + }
|
| +
|
| + void UpdateThrottleState() {
|
| + ClientThrottleState old_throttle_state = throttle_state_;
|
| + if (is_active() && !is_loaded_) {
|
| + SetThrottleState(ACTIVE_AND_LOADING);
|
| + } else if (is_active()) {
|
| + SetThrottleState(UNTHROTTLED);
|
| + } else if (!scheduler_->active_clients_loaded()) {
|
| + SetThrottleState(THROTTLED);
|
| + } else if (is_loaded_ && scheduler_->should_coalesce()) {
|
| + SetThrottleState(COALESCED);
|
| + } else if (!is_active()) {
|
| + SetThrottleState(UNTHROTTLED);
|
| + }
|
| + if (throttle_state_ == old_throttle_state) {
|
| + return;
|
| + }
|
| + if (throttle_state_ == ACTIVE_AND_LOADING) {
|
| + scheduler_->IncrementActiveClientsLoading();
|
| + } else if (old_throttle_state == ACTIVE_AND_LOADING) {
|
| + scheduler_->DecrementActiveClientsLoading();
|
| + }
|
| + }
|
| +
|
| void OnNavigate() {
|
| has_body_ = false;
|
| + is_loaded_ = false;
|
| }
|
|
|
| void OnWillInsertBody() {
|
| @@ -307,6 +365,47 @@ class ResourceScheduler::Client {
|
| }
|
| }
|
|
|
| + // Called on Client creation, when a Client changes user observability,
|
| + // possibly when all observable Clients have finished loading, and
|
| + // possibly when this Client has finished loading.
|
| + // State changes:
|
| + // Client became observable.
|
| + // any state -> UNTHROTTLED
|
| + // Client is unobservable, but all observable clients finished loading.
|
| + // THROTTLED -> UNTHROTTLED
|
| + // Non-observable client finished loading.
|
| + // THROTTLED || UNTHROTTLED -> COALESCED
|
| + // Non-observable client, an observable client starts loading.
|
| + // COALESCED -> THROTTLED
|
| + // A COALESCED client will transition into UNTHROTTLED when the network is
|
| + // woken up by a heartbeat and then transition back into COALESCED.
|
| + void SetThrottleState(ResourceScheduler::ClientThrottleState throttle_state) {
|
| + if (throttle_state == throttle_state_) {
|
| + return;
|
| + }
|
| + throttle_state_ = throttle_state;
|
| + LoadAnyStartablePendingRequests();
|
| + // TODO(aiolos): Stop any started but not inflght requests when
|
| + // switching to stricter throttle state?
|
| + }
|
| +
|
| + ResourceScheduler::ClientThrottleState throttle_state() const {
|
| + return throttle_state_;
|
| + }
|
| +
|
| + void LoadCoalescedRequests() {
|
| + if (throttle_state_ != COALESCED) {
|
| + return;
|
| + }
|
| + if (scheduler_->active_clients_loaded()) {
|
| + SetThrottleState(UNTHROTTLED);
|
| + } else {
|
| + SetThrottleState(THROTTLED);
|
| + }
|
| + LoadAnyStartablePendingRequests();
|
| + SetThrottleState(COALESCED);
|
| + }
|
| +
|
| private:
|
| enum ShouldStartReqResult {
|
| DO_NOT_START_REQUEST_AND_STOP_SEARCHING,
|
| @@ -379,46 +478,73 @@ class ResourceScheduler::Client {
|
|
|
| // ShouldStartRequest is the main scheduling algorithm.
|
| //
|
| - // Requests are categorized into two categories:
|
| - //
|
| - // 1. Immediately issued requests, which are:
|
| + // Requests are categorized into three categories:
|
| //
|
| - // * Higher priority requests (>= net::LOW).
|
| + // 1. Non-delayable requests:
|
| // * Synchronous requests.
|
| - // * Requests to SPDY-capable origin servers.
|
| // * Non-HTTP[S] requests.
|
| //
|
| - // 2. The remainder are delayable requests, which follow these rules:
|
| + // 2. Requests to SPDY-capable origin servers.
|
| //
|
| + // 3. High-priority requests:
|
| + // * Higher priority requests (>= net::LOW).
|
| + //
|
| + // 4. Low priority requests
|
| + //
|
| + // The following rules are followed:
|
| + //
|
| + // ACTIVE_AND_LOADING and UNTHROTTLED Clients follow these rules:
|
| + // * Non-delayable, High-priority and SDPY capable requests are issued
|
| + // immediately
|
| // * If no high priority requests are in flight, start loading low priority
|
| - // requests.
|
| + // requests.
|
| + // * Low priority requests are delayable.
|
| // * 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.
|
| + //
|
| + // THROTTLED Clients follow these rules:
|
| + // * Non-delayable and SPDY-capable requests are issued immediately.
|
| + // * At most one non-SPDY request will be issued per THROTTLED Client
|
| + // * If no high priority requests are in flight, start loading low priority
|
| + // requests.
|
| + //
|
| + // COALESCED Clients never load requests, with the following exceptions:
|
| + // * Non-delayable requests are issued imediately.
|
| + // * On a (currently 5 second) heart beat, they load all requests as an
|
| + // UNTHROTTLED Client, and then return to the COALESCED state.
|
| + // * When an active Client makes a request, they are THROTTLED until the
|
| + // active Client finishes loading.
|
| 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 (!ResourceRequestInfo::ForRequest(&url_request)->IsAsync()) {
|
| + 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()) {
|
| return START_REQUEST;
|
| }
|
|
|
| - if (using_spdy_proxy_ && url_request.url().SchemeIs("http")) {
|
| - return START_REQUEST;
|
| + if (throttle_state_ == COALESCED) {
|
| + return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
|
| }
|
|
|
| - net::HttpServerProperties& http_server_properties =
|
| - *url_request.context()->http_server_properties();
|
| -
|
| - if (url_request.priority() >= net::LOW ||
|
| - !ResourceRequestInfo::ForRequest(&url_request)->IsAsync()) {
|
| + if (using_spdy_proxy_ && url_request.url().SchemeIs("http")) {
|
| return START_REQUEST;
|
| }
|
|
|
| net::HostPortPair host_port_pair =
|
| net::HostPortPair::FromURL(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 SPDY request
|
| @@ -427,6 +553,16 @@ class ResourceScheduler::Client {
|
| return START_REQUEST;
|
| }
|
|
|
| + if (throttle_state_ == THROTTLED &&
|
| + in_flight_requests_.size() >= kMaxNumThrottledRequestsPerClient) {
|
| + // There may still be SPDY-capable requests that should be issued.
|
| + return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING;
|
| + }
|
| +
|
| + if (url_request.priority() >= net::LOW) {
|
| + 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;
|
| @@ -485,15 +621,20 @@ class ResourceScheduler::Client {
|
| }
|
| }
|
|
|
| + bool is_audible_;
|
| + bool is_visible_;
|
| + bool is_loaded_;
|
| bool has_body_;
|
| bool using_spdy_proxy_;
|
| RequestQueue pending_requests_;
|
| RequestSet in_flight_requests_;
|
| + ResourceScheduler* scheduler_;
|
| // The number of delayable in-flight requests.
|
| size_t total_delayable_count_;
|
| + ResourceScheduler::ClientThrottleState throttle_state_;
|
| };
|
|
|
| -ResourceScheduler::ResourceScheduler() {
|
| +ResourceScheduler::ResourceScheduler() : active_clients_loading_(0) {
|
| }
|
|
|
| ResourceScheduler::~ResourceScheduler() {
|
| @@ -501,6 +642,20 @@ ResourceScheduler::~ResourceScheduler() {
|
| DCHECK(client_map_.empty());
|
| }
|
|
|
| +void ResourceScheduler::SetThrottleOptionsForTesting(bool should_throttle,
|
| + bool should_coalesce) {
|
| + should_coalesce_ = should_coalesce;
|
| + should_throttle_ = should_throttle;
|
| + OnLoadingActiveClientsStateChanged();
|
| +}
|
| +
|
| +ResourceScheduler::ClientThrottleState
|
| +ResourceScheduler::GetClientStateForTesting(int child_id, int route_id) {
|
| + Client* client = GetClient(child_id, route_id);
|
| + DCHECK(client);
|
| + return client->throttle_state();
|
| +}
|
| +
|
| scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest(
|
| int child_id,
|
| int route_id,
|
| @@ -548,7 +703,12 @@ void ResourceScheduler::OnClientCreated(int child_id, int route_id) {
|
| ClientId client_id = MakeClientId(child_id, route_id);
|
| DCHECK(!ContainsKey(client_map_, client_id));
|
|
|
| - client_map_[client_id] = new Client;
|
| + Client* client = new Client(this);
|
| + client_map_[client_id] = client;
|
| +
|
| + // TODO(aiolos): set Client visibility/audibility when signals are added
|
| + // this will UNTHROTTLE Clients as needed
|
| + client->UpdateThrottleState();
|
| }
|
|
|
| void ResourceScheduler::OnClientDeleted(int child_id, int route_id) {
|
| @@ -560,7 +720,7 @@ void ResourceScheduler::OnClientDeleted(int child_id, int route_id) {
|
| return;
|
|
|
| Client* client = it->second;
|
| -
|
| + client->OnLoadingStateChanged(true);
|
| // 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.
|
| @@ -617,6 +777,79 @@ void ResourceScheduler::OnReceivedSpdyProxiedHttpResponse(
|
| client->OnReceivedSpdyProxiedHttpResponse();
|
| }
|
|
|
| +void ResourceScheduler::OnAudibilityChanged(int child_id,
|
| + int route_id,
|
| + bool is_audible) {
|
| + Client* client = GetClient(child_id, route_id);
|
| + DCHECK(client);
|
| + client->OnAudibilityChanged(is_audible);
|
| +}
|
| +
|
| +void ResourceScheduler::OnVisibilityChanged(int child_id,
|
| + int route_id,
|
| + bool is_visible) {
|
| + Client* client = GetClient(child_id, route_id);
|
| + DCHECK(client);
|
| + client->OnVisibilityChanged(is_visible);
|
| +}
|
| +
|
| +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);
|
| +}
|
| +
|
| +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::DecrementActiveClientsLoading() {
|
| + DCHECK_NE(0u, active_clients_loading_);
|
| + --active_clients_loading_;
|
| + DCHECK_EQ(active_clients_loading_, CountActiveClientsLoading());
|
| + if (active_clients_loading_ == 0) {
|
| + OnLoadingActiveClientsStateChanged();
|
| + }
|
| +}
|
| +
|
| +void ResourceScheduler::IncrementActiveClientsLoading() {
|
| + ++active_clients_loading_;
|
| + DCHECK_EQ(active_clients_loading_, CountActiveClientsLoading());
|
| + if (active_clients_loading_ == 1) {
|
| + OnLoadingActiveClientsStateChanged();
|
| + }
|
| +}
|
| +
|
| +void ResourceScheduler::OnLoadingActiveClientsStateChanged() {
|
| + ClientMap::iterator client_it = client_map_.begin();
|
| + while (client_it != client_map_.end()) {
|
| + Client* client = client_it->second;
|
| + client->UpdateThrottleState();
|
| + ++client_it;
|
| + }
|
| +}
|
| +
|
| +size_t ResourceScheduler::CountActiveClientsLoading() {
|
| + size_t active_and_loading = 0;
|
| + ClientMap::iterator client_it = client_map_.begin();
|
| + while (client_it != client_map_.end()) {
|
| + Client* client = client_it->second;
|
| + if (client->throttle_state() == ACTIVE_AND_LOADING) {
|
| + ++active_and_loading;
|
| + }
|
| + ++client_it;
|
| + }
|
| + return active_and_loading;
|
| +}
|
| +
|
| void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request,
|
| net::RequestPriority new_priority,
|
| int new_intra_priority_value) {
|
|
|