Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / content / browser / loader / resource_scheduler.cc
index a826580..85adbdb 100644 (file)
 
 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()
@@ -229,11 +230,26 @@ 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),
+        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,
@@ -268,8 +284,73 @@ 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 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() {
@@ -307,11 +388,55 @@ 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;
+    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) {
@@ -379,46 +504,73 @@ class ResourceScheduler::Client {
 
   // 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
@@ -427,6 +579,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 +647,27 @@ class ResourceScheduler::Client {
     }
   }
 
+  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() {
@@ -501,6 +675,20 @@ 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,
@@ -548,7 +736,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 +753,6 @@ 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.
@@ -617,6 +809,126 @@ 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) {
+    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) {