Home | History | Annotate | Download | only in loader
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/loader/resource_scheduler.h"
      6 
      7 #include "base/stl_util.h"
      8 #include "content/common/resource_messages.h"
      9 #include "content/browser/loader/resource_message_delegate.h"
     10 #include "content/public/browser/resource_controller.h"
     11 #include "content/public/browser/resource_request_info.h"
     12 #include "content/public/browser/resource_throttle.h"
     13 #include "ipc/ipc_message_macros.h"
     14 #include "net/base/host_port_pair.h"
     15 #include "net/base/load_flags.h"
     16 #include "net/base/request_priority.h"
     17 #include "net/http/http_server_properties.h"
     18 #include "net/url_request/url_request.h"
     19 #include "net/url_request/url_request_context.h"
     20 
     21 namespace content {
     22 
     23 static const size_t kMaxNumDelayableRequestsPerClient = 10;
     24 static const size_t kMaxNumDelayableRequestsPerHost = 6;
     25 
     26 // A thin wrapper around net::PriorityQueue that deals with
     27 // ScheduledResourceRequests instead of PriorityQueue::Pointers.
     28 class ResourceScheduler::RequestQueue {
     29  private:
     30   typedef net::PriorityQueue<ScheduledResourceRequest*> NetQueue;
     31 
     32  public:
     33   class Iterator {
     34    public:
     35     Iterator(NetQueue* queue) : queue_(queue) {
     36       DCHECK(queue != NULL);
     37       current_pointer_ = queue_->FirstMax();
     38     }
     39 
     40     Iterator& operator++() {
     41       current_pointer_ = queue_->GetNextTowardsLastMin(current_pointer_);
     42       return *this;
     43     }
     44 
     45     Iterator operator++(int) {
     46       Iterator result(*this);
     47       ++(*this);
     48       return result;
     49     }
     50 
     51     ScheduledResourceRequest* value() {
     52       return current_pointer_.value();
     53     }
     54 
     55     bool is_null() {
     56       return current_pointer_.is_null();
     57     }
     58 
     59    private:
     60     NetQueue* queue_;
     61     NetQueue::Pointer current_pointer_;
     62   };
     63 
     64   RequestQueue() : queue_(net::NUM_PRIORITIES) {}
     65   ~RequestQueue() {}
     66 
     67   // Adds |request| to the queue with given |priority|.
     68   void Insert(ScheduledResourceRequest* request,
     69               net::RequestPriority priority) {
     70     DCHECK(!ContainsKey(pointers_, request));
     71     NetQueue::Pointer pointer = queue_.Insert(request, priority);
     72     pointers_[request] = pointer;
     73   }
     74 
     75   // Removes |request| from the queue.
     76   void Erase(ScheduledResourceRequest* request) {
     77     PointerMap::iterator it = pointers_.find(request);
     78     DCHECK(it != pointers_.end());
     79     if (it == pointers_.end())
     80       return;
     81     queue_.Erase(it->second);
     82     pointers_.erase(it);
     83   }
     84 
     85   // Returns the highest priority request that's queued, or NULL if none are.
     86   ScheduledResourceRequest* FirstMax() {
     87     return queue_.FirstMax().value();
     88   }
     89 
     90   Iterator GetNextHighestIterator() {
     91     return Iterator(&queue_);
     92   }
     93 
     94   // Returns true if |request| is queued.
     95   bool IsQueued(ScheduledResourceRequest* request) const {
     96     return ContainsKey(pointers_, request);
     97   }
     98 
     99   // Returns true if no requests are queued.
    100   bool IsEmpty() const { return queue_.size() == 0; }
    101 
    102  private:
    103   typedef std::map<ScheduledResourceRequest*, NetQueue::Pointer> PointerMap;
    104 
    105   NetQueue queue_;
    106   PointerMap pointers_;
    107 };
    108 
    109 // This is the handle we return to the ResourceDispatcherHostImpl so it can
    110 // interact with the request.
    111 class ResourceScheduler::ScheduledResourceRequest
    112     : public ResourceMessageDelegate,
    113       public ResourceThrottle {
    114  public:
    115   ScheduledResourceRequest(const ClientId& client_id,
    116                            net::URLRequest* request,
    117                            ResourceScheduler* scheduler)
    118       : ResourceMessageDelegate(request),
    119         client_id_(client_id),
    120         request_(request),
    121         ready_(false),
    122         deferred_(false),
    123         scheduler_(scheduler) {
    124     TRACE_EVENT_ASYNC_BEGIN1("net", "URLRequest", request_,
    125                              "url", request->url().spec());
    126   }
    127 
    128   virtual ~ScheduledResourceRequest() {
    129     scheduler_->RemoveRequest(this);
    130   }
    131 
    132   void Start() {
    133     TRACE_EVENT_ASYNC_STEP_PAST0("net", "URLRequest", request_, "Queued");
    134     ready_ = true;
    135     if (deferred_ && request_->status().is_success()) {
    136       deferred_ = false;
    137       controller()->Resume();
    138     }
    139   }
    140 
    141   const ClientId& client_id() const { return client_id_; }
    142   net::URLRequest* url_request() { return request_; }
    143   const net::URLRequest* url_request() const { return request_; }
    144 
    145  private:
    146   // ResourceMessageDelegate interface:
    147   virtual bool OnMessageReceived(const IPC::Message& message,
    148                                  bool* message_was_ok) OVERRIDE {
    149     bool handled = true;
    150     IPC_BEGIN_MESSAGE_MAP_EX(ScheduledResourceRequest, message, *message_was_ok)
    151       IPC_MESSAGE_HANDLER(ResourceHostMsg_DidChangePriority, DidChangePriority)
    152       IPC_MESSAGE_UNHANDLED(handled = false)
    153     IPC_END_MESSAGE_MAP_EX()
    154     return handled;
    155   }
    156 
    157   // ResourceThrottle interface:
    158   virtual void WillStartRequest(bool* defer) OVERRIDE {
    159     deferred_ = *defer = !ready_;
    160   }
    161 
    162   virtual const char* GetNameForLogging() const OVERRIDE {
    163     return "ResourceScheduler";
    164   }
    165 
    166   void DidChangePriority(int request_id, net::RequestPriority new_priority) {
    167     scheduler_->ReprioritizeRequest(this, new_priority);
    168   }
    169 
    170   ClientId client_id_;
    171   net::URLRequest* request_;
    172   bool ready_;
    173   bool deferred_;
    174   ResourceScheduler* scheduler_;
    175 
    176   DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest);
    177 };
    178 
    179 // Each client represents a tab.
    180 struct ResourceScheduler::Client {
    181   Client() : has_body(false) {}
    182   ~Client() {}
    183 
    184   bool has_body;
    185   RequestQueue pending_requests;
    186   RequestSet in_flight_requests;
    187 };
    188 
    189 ResourceScheduler::ResourceScheduler() {
    190 }
    191 
    192 ResourceScheduler::~ResourceScheduler() {
    193   DCHECK(unowned_requests_.empty());
    194   DCHECK(client_map_.empty());
    195 }
    196 
    197 scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest(
    198     int child_id,
    199     int route_id,
    200     net::URLRequest* url_request) {
    201   DCHECK(CalledOnValidThread());
    202   ClientId client_id = MakeClientId(child_id, route_id);
    203   scoped_ptr<ScheduledResourceRequest> request(
    204       new ScheduledResourceRequest(client_id, url_request, this));
    205 
    206   ClientMap::iterator it = client_map_.find(client_id);
    207   if (it == client_map_.end()) {
    208     // There are several ways this could happen:
    209     // 1. <a ping> requests don't have a route_id.
    210     // 2. Most unittests don't send the IPCs needed to register Clients.
    211     // 3. The tab is closed while a RequestResource IPC is in flight.
    212     unowned_requests_.insert(request.get());
    213     request->Start();
    214     return request.PassAs<ResourceThrottle>();
    215   }
    216 
    217   Client* client = it->second;
    218   if (ShouldStartRequest(request.get(), client) == START_REQUEST) {
    219     StartRequest(request.get(), client);
    220   } else {
    221     client->pending_requests.Insert(request.get(), url_request->priority());
    222   }
    223   return request.PassAs<ResourceThrottle>();
    224 }
    225 
    226 void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) {
    227   DCHECK(CalledOnValidThread());
    228   if (ContainsKey(unowned_requests_, request)) {
    229     unowned_requests_.erase(request);
    230     return;
    231   }
    232 
    233   ClientMap::iterator client_it = client_map_.find(request->client_id());
    234   if (client_it == client_map_.end()) {
    235     return;
    236   }
    237 
    238   Client* client = client_it->second;
    239 
    240   if (client->pending_requests.IsQueued(request)) {
    241     client->pending_requests.Erase(request);
    242     DCHECK(!ContainsKey(client->in_flight_requests, request));
    243   } else {
    244     size_t erased = client->in_flight_requests.erase(request);
    245     DCHECK(erased);
    246 
    247     // Removing this request may have freed up another to load.
    248     LoadAnyStartablePendingRequests(client);
    249   }
    250 }
    251 
    252 void ResourceScheduler::OnClientCreated(int child_id, int route_id) {
    253   DCHECK(CalledOnValidThread());
    254   ClientId client_id = MakeClientId(child_id, route_id);
    255   DCHECK(!ContainsKey(client_map_, client_id));
    256 
    257   client_map_[client_id] = new Client;
    258 }
    259 
    260 void ResourceScheduler::OnClientDeleted(int child_id, int route_id) {
    261   DCHECK(CalledOnValidThread());
    262   ClientId client_id = MakeClientId(child_id, route_id);
    263   DCHECK(ContainsKey(client_map_, client_id));
    264   ClientMap::iterator it = client_map_.find(client_id);
    265   if (it == client_map_.end())
    266     return;
    267 
    268   Client* client = it->second;
    269 
    270   // FYI, ResourceDispatcherHost cancels all of the requests after this function
    271   // is called. It should end up canceling all of the requests except for a
    272   // cross-renderer navigation.
    273   for (RequestSet::iterator it = client->in_flight_requests.begin();
    274        it != client->in_flight_requests.end(); ++it) {
    275     unowned_requests_.insert(*it);
    276   }
    277   client->in_flight_requests.clear();
    278 
    279   delete client;
    280   client_map_.erase(it);
    281 }
    282 
    283 void ResourceScheduler::OnNavigate(int child_id, int route_id) {
    284   DCHECK(CalledOnValidThread());
    285   ClientId client_id = MakeClientId(child_id, route_id);
    286 
    287   ClientMap::iterator it = client_map_.find(client_id);
    288   if (it == client_map_.end()) {
    289     // The client was likely deleted shortly before we received this IPC.
    290     return;
    291   }
    292 
    293   Client* client = it->second;
    294   client->has_body = false;
    295 }
    296 
    297 void ResourceScheduler::OnWillInsertBody(int child_id, int route_id) {
    298   DCHECK(CalledOnValidThread());
    299   ClientId client_id = MakeClientId(child_id, route_id);
    300 
    301   ClientMap::iterator it = client_map_.find(client_id);
    302   if (it == client_map_.end()) {
    303     // The client was likely deleted shortly before we received this IPC.
    304     return;
    305   }
    306 
    307   Client* client = it->second;
    308   client->has_body = false;
    309   if (!client->has_body) {
    310     client->has_body = true;
    311     LoadAnyStartablePendingRequests(client);
    312   }
    313 }
    314 
    315 void ResourceScheduler::StartRequest(ScheduledResourceRequest* request,
    316                                      Client* client) {
    317   client->in_flight_requests.insert(request);
    318   request->Start();
    319 }
    320 
    321 void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request,
    322                                             net::RequestPriority new_priority) {
    323   if (request->url_request()->load_flags() & net::LOAD_IGNORE_LIMITS) {
    324     // We should not be re-prioritizing requests with the
    325     // IGNORE_LIMITS flag.
    326     NOTREACHED();
    327     return;
    328   }
    329   net::RequestPriority old_priority = request->url_request()->priority();
    330   DCHECK_NE(new_priority, old_priority);
    331   request->url_request()->SetPriority(new_priority);
    332   ClientMap::iterator client_it = client_map_.find(request->client_id());
    333   if (client_it == client_map_.end()) {
    334     // The client was likely deleted shortly before we received this IPC.
    335     return;
    336   }
    337 
    338   Client *client = client_it->second;
    339   if (!client->pending_requests.IsQueued(request)) {
    340     DCHECK(ContainsKey(client->in_flight_requests, request));
    341     // Request has already started.
    342     return;
    343   }
    344 
    345   client->pending_requests.Erase(request);
    346   client->pending_requests.Insert(request,
    347                                   request->url_request()->priority());
    348 
    349   if (new_priority > old_priority) {
    350     // Check if this request is now able to load at its new priority.
    351     LoadAnyStartablePendingRequests(client);
    352   }
    353 }
    354 
    355 void ResourceScheduler::LoadAnyStartablePendingRequests(Client* client) {
    356   // We iterate through all the pending requests, starting with the highest
    357   // priority one. For each entry, one of three things can happen:
    358   // 1) We start the request, remove it from the list, and keep checking.
    359   // 2) We do NOT start the request, but ShouldStartRequest() signals us that
    360   //    there may be room for other requests, so we keep checking and leave
    361   //    the previous request still in the list.
    362   // 3) We do not start the request, same as above, but StartRequest() tells
    363   //    us there's no point in checking any further requests.
    364 
    365   RequestQueue::Iterator request_iter =
    366       client->pending_requests.GetNextHighestIterator();
    367 
    368   while (!request_iter.is_null()) {
    369     ScheduledResourceRequest* request = request_iter.value();
    370     ShouldStartReqResult query_result = ShouldStartRequest(request, client);
    371 
    372     if (query_result == START_REQUEST) {
    373       client->pending_requests.Erase(request);
    374       StartRequest(request, client);
    375 
    376       // StartRequest can modify the pending list, so we (re)start evaluation
    377       // from the currently highest priority request. Avoid copying a singular
    378       // iterator, which would trigger undefined behavior.
    379       if (client->pending_requests.GetNextHighestIterator().is_null())
    380         break;
    381       request_iter = client->pending_requests.GetNextHighestIterator();
    382     } else if (query_result == DO_NOT_START_REQUEST_AND_KEEP_SEARCHING) {
    383       ++request_iter;
    384       continue;
    385     } else {
    386       DCHECK(query_result == DO_NOT_START_REQUEST_AND_STOP_SEARCHING);
    387       break;
    388     }
    389   }
    390 }
    391 
    392 void ResourceScheduler::GetNumDelayableRequestsInFlight(
    393     Client* client,
    394     const net::HostPortPair& active_request_host,
    395     size_t* total_delayable,
    396     size_t* total_for_active_host) const {
    397   DCHECK(client != NULL && total_delayable != NULL &&
    398          total_for_active_host != NULL);
    399 
    400   size_t total_delayable_count = 0;
    401   size_t same_host_count = 0;
    402   for (RequestSet::iterator it = client->in_flight_requests.begin();
    403        it != client->in_flight_requests.end(); ++it) {
    404     net::HostPortPair host_port_pair =
    405         net::HostPortPair::FromURL((*it)->url_request()->url());
    406 
    407     if (active_request_host.Equals(host_port_pair)) {
    408       same_host_count++;
    409     }
    410 
    411     if ((*it)->url_request()->priority() < net::LOW) {
    412       const net::HttpServerProperties& http_server_properties =
    413           *(*it)->url_request()->context()->http_server_properties();
    414 
    415       if (!http_server_properties.SupportsSpdy(host_port_pair)) {
    416         ++total_delayable_count;
    417       }
    418     }
    419   }
    420   *total_delayable = total_delayable_count;
    421   *total_for_active_host = same_host_count;
    422 }
    423 
    424 // ShouldStartRequest is the main scheduling algorithm.
    425 //
    426 // Requests are categorized into two categories:
    427 //
    428 // 1. Immediately issued requests, which are:
    429 //
    430 //   * Higher priority requests (>= net::LOW).
    431 //   * Synchronous requests.
    432 //   * Requests to SPDY-capable origin servers.
    433 //   * Non-HTTP[S] requests.
    434 //
    435 // 2. The remainder are delayable requests, which follow these rules:
    436 //
    437 //   * If no high priority requests are in flight, start loading low priority
    438 //     requests.
    439 //   * Once the renderer has a <body>, start loading delayable requests.
    440 //   * Never exceed 10 delayable requests in flight per client.
    441 //   * Never exceed 6 delayable requests for a given host.
    442 //   * Prior to <body>, allow one delayable request to load at a time.
    443 ResourceScheduler::ShouldStartReqResult ResourceScheduler::ShouldStartRequest(
    444     ScheduledResourceRequest* request,
    445     Client* client) const {
    446   const net::URLRequest& url_request = *request->url_request();
    447 
    448   // TODO(simonjam): This may end up causing disk contention. We should
    449   // experiment with throttling if that happens.
    450   if (!url_request.url().SchemeIsHTTPOrHTTPS()) {
    451     return START_REQUEST;
    452   }
    453 
    454   const net::HttpServerProperties& http_server_properties =
    455       *url_request.context()->http_server_properties();
    456 
    457   if (url_request.priority() >= net::LOW ||
    458       !ResourceRequestInfo::ForRequest(&url_request)->IsAsync()) {
    459     return START_REQUEST;
    460   }
    461 
    462   net::HostPortPair host_port_pair =
    463       net::HostPortPair::FromURL(url_request.url());
    464 
    465   // TODO(willchan): We should really improve this algorithm as described in
    466   // crbug.com/164101. Also, theoretically we should not count a SPDY request
    467   // against the delayable requests limit.
    468   if (http_server_properties.SupportsSpdy(host_port_pair)) {
    469     return START_REQUEST;
    470   }
    471 
    472   size_t num_delayable_requests_in_flight = 0;
    473   size_t num_requests_in_flight_for_host = 0;
    474   GetNumDelayableRequestsInFlight(client, host_port_pair,
    475                                   &num_delayable_requests_in_flight,
    476                                   &num_requests_in_flight_for_host);
    477 
    478   if (num_delayable_requests_in_flight >= kMaxNumDelayableRequestsPerClient) {
    479     return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
    480   }
    481 
    482   if (num_requests_in_flight_for_host >= kMaxNumDelayableRequestsPerHost) {
    483     // There may be other requests for other hosts we'd allow, so keep checking.
    484     return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING;
    485   }
    486 
    487   bool have_immediate_requests_in_flight =
    488       client->in_flight_requests.size() > num_delayable_requests_in_flight;
    489   if (have_immediate_requests_in_flight && !client->has_body &&
    490       num_delayable_requests_in_flight != 0) {
    491     return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
    492   }
    493 
    494   return START_REQUEST;
    495 }
    496 
    497 ResourceScheduler::ClientId ResourceScheduler::MakeClientId(
    498     int child_id, int route_id) {
    499   return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id;
    500 }
    501 
    502 }  // namespace content
    503