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