namespace content {
+static const size_t kCoalescedTimerPeriod = 5000;
static const size_t kMaxNumDelayableRequestsPerClient = 10;
static const size_t kMaxNumDelayableRequestsPerHost = 6;
-
+static const size_t kMaxNumThrottledRequestsPerClient = 1;
struct ResourceScheduler::RequestPriorityParams {
RequestPriorityParams()
// 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),
+ is_paused_(false),
+ has_body_(false),
using_spdy_proxy_(false),
- total_delayable_count_(0) {}
- ~Client() {}
+ total_delayable_count_(0),
+ throttle_state_(ResourceScheduler::THROTTLED) {
+ scheduler_ = scheduler;
+ }
+
+ ~Client() {
+ // Update to default state and pause to ensure the scheduler has a
+ // correct count of relevant types of clients.
+ is_visible_ = false;
+ is_audible_ = false;
+ is_paused_ = true;
+ UpdateThrottleState();
+ }
void ScheduleRequest(
net::URLRequest* url_request,
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 SetPaused() {
+ is_paused_ = true;
+ 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 (is_paused_) {
+ SetThrottleState(PAUSED);
+ } 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();
+ }
+ if (throttle_state_ == COALESCED) {
+ scheduler_->IncrementCoalescedClients();
+ } else if (old_throttle_state == COALESCED) {
+ scheduler_->DecrementCoalescedClients();
+ }
+ }
+
void OnNavigate() {
has_body_ = false;
+ is_loaded_ = false;
}
void OnWillInsertBody() {
}
}
+ // 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;
+ if (throttle_state_ != PAUSED) {
+ is_paused_ = false;
+ }
+ 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 = -2,
- DO_NOT_START_REQUEST_AND_KEEP_SEARCHING = -1,
- START_REQUEST = 1,
+ DO_NOT_START_REQUEST_AND_STOP_SEARCHING,
+ DO_NOT_START_REQUEST_AND_KEEP_SEARCHING,
+ START_REQUEST,
};
void InsertInFlightRequest(ScheduledResourceRequest* request) {
// ShouldStartRequest is the main scheduling algorithm.
//
- // Requests are categorized into two categories:
+ // Requests are categorized into three categories:
//
- // 1. Immediately issued requests, which are:
- //
- // * 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
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;
}
}
+ bool is_audible_;
+ bool is_visible_;
+ bool is_loaded_;
+ bool is_paused_;
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()
+ : should_coalesce_(false),
+ should_throttle_(false),
+ active_clients_loading_(0),
+ coalesced_clients_(0),
+ coalescing_timer_(new base::Timer(true /* retain_user_task */,
+ true /* is_repeating */)) {
}
ResourceScheduler::~ResourceScheduler() {
DCHECK(client_map_.empty());
}
+void ResourceScheduler::SetThrottleOptionsForTesting(bool should_throttle,
+ bool should_coalesce) {
+ should_coalesce_ = should_coalesce;
+ should_throttle_ = should_throttle;
+ OnLoadingActiveClientsStateChangedForAllClients();
+}
+
+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,
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) {
return;
Client* client = it->second;
-
// 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.
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) {
+ OnLoadingActiveClientsStateChangedForAllClients();
+ }
+}
+
+void ResourceScheduler::IncrementActiveClientsLoading() {
+ ++active_clients_loading_;
+ DCHECK_EQ(active_clients_loading_, CountActiveClientsLoading());
+ if (active_clients_loading_ == 1) {
+ OnLoadingActiveClientsStateChangedForAllClients();
+ }
+}
+
+void ResourceScheduler::OnLoadingActiveClientsStateChangedForAllClients() {
+ 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() const {
+ size_t active_and_loading = 0;
+ ClientMap::const_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::IncrementCoalescedClients() {
+ ++coalesced_clients_;
+ DCHECK(should_coalesce_);
+ DCHECK_EQ(coalesced_clients_, CountCoalescedClients());
+ if (coalesced_clients_ == 1) {
+ coalescing_timer_->Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kCoalescedTimerPeriod),
+ base::Bind(&ResourceScheduler::LoadCoalescedRequests,
+ base::Unretained(this)));
+ }
+}
+
+void ResourceScheduler::DecrementCoalescedClients() {
+ DCHECK(should_coalesce_);
+ DCHECK_NE(0U, coalesced_clients_);
+ --coalesced_clients_;
+ DCHECK_EQ(coalesced_clients_, CountCoalescedClients());
+ if (coalesced_clients_ == 0) {
+ coalescing_timer_->Stop();
+ }
+}
+
+size_t ResourceScheduler::CountCoalescedClients() const {
+ DCHECK(should_coalesce_);
+ size_t coalesced_clients = 0;
+ ClientMap::const_iterator client_it = client_map_.begin();
+ while (client_it != client_map_.end()) {
+ Client* client = client_it->second;
+ if (client->throttle_state() == COALESCED) {
+ ++coalesced_clients;
+ }
+ ++client_it;
+ }
+ return coalesced_clients_;
+}
+
+void ResourceScheduler::LoadCoalescedRequests() {
+ DCHECK(should_coalesce_);
+ ClientMap::iterator client_it = client_map_.begin();
+ while (client_it != client_map_.end()) {
+ Client* client = client_it->second;
+ client->LoadCoalescedRequests();
+ ++client_it;
+ }
+}
+
void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request,
net::RequestPriority new_priority,
int new_intra_priority_value) {