Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / browser / loader / resource_scheduler.cc
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 <set>
6
7 #include "content/browser/loader/resource_scheduler.h"
8
9 #include "base/stl_util.h"
10 #include "content/common/resource_messages.h"
11 #include "content/browser/loader/resource_message_delegate.h"
12 #include "content/public/browser/resource_controller.h"
13 #include "content/public/browser/resource_request_info.h"
14 #include "content/public/browser/resource_throttle.h"
15 #include "ipc/ipc_message_macros.h"
16 #include "net/base/host_port_pair.h"
17 #include "net/base/load_flags.h"
18 #include "net/base/request_priority.h"
19 #include "net/http/http_server_properties.h"
20 #include "net/url_request/url_request.h"
21 #include "net/url_request/url_request_context.h"
22
23 namespace content {
24
25 static const size_t kMaxNumDelayableRequestsPerClient = 10;
26 static const size_t kMaxNumDelayableRequestsPerHost = 6;
27
28
29 struct ResourceScheduler::RequestPriorityParams {
30   RequestPriorityParams()
31     : priority(net::DEFAULT_PRIORITY),
32       intra_priority(0) {
33   }
34
35   RequestPriorityParams(net::RequestPriority priority, int intra_priority)
36     : priority(priority),
37       intra_priority(intra_priority) {
38   }
39
40   bool operator==(const RequestPriorityParams& other) const {
41     return (priority == other.priority) &&
42         (intra_priority == other.intra_priority);
43   }
44
45   bool operator!=(const RequestPriorityParams& other) const {
46     return !(*this == other);
47   }
48
49   bool GreaterThan(const RequestPriorityParams& other) const {
50     if (priority != other.priority)
51       return priority > other.priority;
52     return intra_priority > other.intra_priority;
53   }
54
55   net::RequestPriority priority;
56   int intra_priority;
57 };
58
59 class ResourceScheduler::RequestQueue {
60  public:
61   typedef std::multiset<ScheduledResourceRequest*, ScheduledResourceSorter>
62       NetQueue;
63
64   RequestQueue() : fifo_ordering_ids_(0) {}
65   ~RequestQueue() {}
66
67   // Adds |request| to the queue with given |priority|.
68   void Insert(ScheduledResourceRequest* request);
69
70   // Removes |request| from the queue.
71   void Erase(ScheduledResourceRequest* request) {
72     PointerMap::iterator it = pointers_.find(request);
73     DCHECK(it != pointers_.end());
74     if (it == pointers_.end())
75       return;
76     queue_.erase(it->second);
77     pointers_.erase(it);
78   }
79
80   NetQueue::iterator GetNextHighestIterator() {
81     return queue_.begin();
82   }
83
84   NetQueue::iterator End() {
85     return queue_.end();
86   }
87
88   // Returns true if |request| is queued.
89   bool IsQueued(ScheduledResourceRequest* request) const {
90     return ContainsKey(pointers_, request);
91   }
92
93   // Returns true if no requests are queued.
94   bool IsEmpty() const { return queue_.size() == 0; }
95
96  private:
97   typedef std::map<ScheduledResourceRequest*, NetQueue::iterator> PointerMap;
98
99   uint32 MakeFifoOrderingId() {
100     fifo_ordering_ids_ += 1;
101     return fifo_ordering_ids_;
102   }
103
104   // Used to create an ordering ID for scheduled resources so that resources
105   // with same priority/intra_priority stay in fifo order.
106   uint32 fifo_ordering_ids_;
107
108   NetQueue queue_;
109   PointerMap pointers_;
110 };
111
112 // This is the handle we return to the ResourceDispatcherHostImpl so it can
113 // interact with the request.
114 class ResourceScheduler::ScheduledResourceRequest
115     : public ResourceMessageDelegate,
116       public ResourceThrottle {
117  public:
118   ScheduledResourceRequest(const ClientId& client_id,
119                            net::URLRequest* request,
120                            ResourceScheduler* scheduler,
121                            const RequestPriorityParams& priority)
122       : ResourceMessageDelegate(request),
123         client_id_(client_id),
124         request_(request),
125         ready_(false),
126         deferred_(false),
127         scheduler_(scheduler),
128         priority_(priority),
129         fifo_ordering_(0),
130         accounted_as_delayable_request_(false) {
131     TRACE_EVENT_ASYNC_BEGIN1("net", "URLRequest", request_,
132                              "url", request->url().spec());
133   }
134
135   virtual ~ScheduledResourceRequest() {
136     scheduler_->RemoveRequest(this);
137   }
138
139   void Start() {
140     TRACE_EVENT_ASYNC_STEP_PAST0("net", "URLRequest", request_, "Queued");
141     ready_ = true;
142     if (deferred_ && request_->status().is_success()) {
143       deferred_ = false;
144       controller()->Resume();
145     }
146   }
147
148   void set_request_priority_params(const RequestPriorityParams& priority) {
149     priority_ = priority;
150   }
151   const RequestPriorityParams& get_request_priority_params() const {
152     return priority_;
153   }
154   const ClientId& client_id() const { return client_id_; }
155   net::URLRequest* url_request() { return request_; }
156   const net::URLRequest* url_request() const { return request_; }
157   uint32 fifo_ordering() const { return fifo_ordering_; }
158   void set_fifo_ordering(uint32 fifo_ordering) {
159     fifo_ordering_ = fifo_ordering;
160   }
161   bool accounted_as_delayable_request() const {
162     return accounted_as_delayable_request_;
163   }
164   void set_accounted_as_delayable_request(bool accounted) {
165     accounted_as_delayable_request_ = accounted;
166   }
167
168  private:
169   // ResourceMessageDelegate interface:
170   virtual bool OnMessageReceived(const IPC::Message& message,
171                                  bool* message_was_ok) OVERRIDE {
172     bool handled = true;
173     IPC_BEGIN_MESSAGE_MAP_EX(ScheduledResourceRequest, message, *message_was_ok)
174       IPC_MESSAGE_HANDLER(ResourceHostMsg_DidChangePriority, DidChangePriority)
175       IPC_MESSAGE_UNHANDLED(handled = false)
176     IPC_END_MESSAGE_MAP_EX()
177     return handled;
178   }
179
180   // ResourceThrottle interface:
181   virtual void WillStartRequest(bool* defer) OVERRIDE {
182     deferred_ = *defer = !ready_;
183   }
184
185   virtual const char* GetNameForLogging() const OVERRIDE {
186     return "ResourceScheduler";
187   }
188
189   void DidChangePriority(int request_id, net::RequestPriority new_priority,
190                          int intra_priority_value) {
191     scheduler_->ReprioritizeRequest(this, new_priority, intra_priority_value);
192   }
193
194   ClientId client_id_;
195   net::URLRequest* request_;
196   bool ready_;
197   bool deferred_;
198   ResourceScheduler* scheduler_;
199   RequestPriorityParams priority_;
200   uint32 fifo_ordering_;
201   // True if the request is delayable in |in_flight_requests_|.
202   bool accounted_as_delayable_request_;
203
204   DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest);
205 };
206
207 bool ResourceScheduler::ScheduledResourceSorter::operator()(
208     const ScheduledResourceRequest* a,
209     const ScheduledResourceRequest* b) const {
210   // Want the set to be ordered first by decreasing priority, then by
211   // decreasing intra_priority.
212   // ie. with (priority, intra_priority)
213   // [(1, 0), (1, 0), (0, 100), (0, 0)]
214   if (a->get_request_priority_params() != b->get_request_priority_params())
215     return a->get_request_priority_params().GreaterThan(
216         b->get_request_priority_params());
217
218   // If priority/intra_priority is the same, fall back to fifo ordering.
219   // std::multiset doesn't guarantee this until c++11.
220   return a->fifo_ordering() < b->fifo_ordering();
221 }
222
223 void ResourceScheduler::RequestQueue::Insert(
224     ScheduledResourceRequest* request) {
225   DCHECK(!ContainsKey(pointers_, request));
226   request->set_fifo_ordering(MakeFifoOrderingId());
227   pointers_[request] = queue_.insert(request);
228 }
229
230 // Each client represents a tab.
231 class ResourceScheduler::Client {
232  public:
233   Client()
234       : has_body_(false),
235         using_spdy_proxy_(false),
236         total_delayable_count_(0) {}
237   ~Client() {}
238
239   void ScheduleRequest(
240       net::URLRequest* url_request,
241       ScheduledResourceRequest* request) {
242     if (ShouldStartRequest(request) == START_REQUEST) {
243       StartRequest(request);
244     } else {
245       pending_requests_.Insert(request);
246     }
247   }
248
249   void RemoveRequest(ScheduledResourceRequest* request) {
250     if (pending_requests_.IsQueued(request)) {
251       pending_requests_.Erase(request);
252       DCHECK(!ContainsKey(in_flight_requests_, request));
253     } else {
254       EraseInFlightRequest(request);
255
256       // Removing this request may have freed up another to load.
257       LoadAnyStartablePendingRequests();
258     }
259   }
260
261   RequestSet RemoveAllRequests() {
262     RequestSet unowned_requests;
263     for (RequestSet::iterator it = in_flight_requests_.begin();
264          it != in_flight_requests_.end(); ++it) {
265       unowned_requests.insert(*it);
266       (*it)->set_accounted_as_delayable_request(false);
267     }
268     ClearInFlightRequests();
269     return unowned_requests;
270   }
271
272   void OnNavigate() {
273     has_body_ = false;
274   }
275
276   void OnWillInsertBody() {
277     has_body_ = true;
278     LoadAnyStartablePendingRequests();
279   }
280
281   void OnReceivedSpdyProxiedHttpResponse() {
282     if (!using_spdy_proxy_) {
283       using_spdy_proxy_ = true;
284       LoadAnyStartablePendingRequests();
285     }
286   }
287
288   void ReprioritizeRequest(ScheduledResourceRequest* request,
289                            RequestPriorityParams old_priority_params,
290                            RequestPriorityParams new_priority_params) {
291     request->url_request()->SetPriority(new_priority_params.priority);
292     request->set_request_priority_params(new_priority_params);
293     if (!pending_requests_.IsQueued(request)) {
294       DCHECK(ContainsKey(in_flight_requests_, request));
295       // The priority and SPDY support may have changed, so update the
296       // delayable count.
297       SetRequestDelayable(request, IsDelayableRequest(request));
298       // Request has already started.
299       return;
300     }
301
302     pending_requests_.Erase(request);
303     pending_requests_.Insert(request);
304
305     if (new_priority_params.priority > old_priority_params.priority) {
306       // Check if this request is now able to load at its new priority.
307       LoadAnyStartablePendingRequests();
308     }
309   }
310
311  private:
312   enum ShouldStartReqResult {
313       DO_NOT_START_REQUEST_AND_STOP_SEARCHING = -2,
314       DO_NOT_START_REQUEST_AND_KEEP_SEARCHING = -1,
315       START_REQUEST = 1,
316   };
317
318   void InsertInFlightRequest(ScheduledResourceRequest* request) {
319     in_flight_requests_.insert(request);
320     if (IsDelayableRequest(request))
321       SetRequestDelayable(request, true);
322   }
323
324   void EraseInFlightRequest(ScheduledResourceRequest* request) {
325     size_t erased = in_flight_requests_.erase(request);
326     DCHECK_EQ(1u, erased);
327     SetRequestDelayable(request, false);
328     DCHECK_LE(total_delayable_count_, in_flight_requests_.size());
329   }
330
331   void ClearInFlightRequests() {
332     in_flight_requests_.clear();
333     total_delayable_count_ = 0;
334   }
335
336   bool IsDelayableRequest(ScheduledResourceRequest* request) {
337     if (request->url_request()->priority() < net::LOW) {
338       net::HostPortPair host_port_pair =
339           net::HostPortPair::FromURL(request->url_request()->url());
340       net::HttpServerProperties& http_server_properties =
341           *request->url_request()->context()->http_server_properties();
342       if (!http_server_properties.SupportsSpdy(host_port_pair)) {
343         return true;
344       }
345     }
346     return false;
347   }
348
349   void SetRequestDelayable(ScheduledResourceRequest* request,
350                            bool delayable) {
351     if (request->accounted_as_delayable_request() == delayable)
352       return;
353     if (delayable)
354       total_delayable_count_++;
355     else
356       total_delayable_count_--;
357     request->set_accounted_as_delayable_request(delayable);
358   }
359
360   bool ShouldKeepSearching(
361       const net::HostPortPair& active_request_host) const {
362     size_t same_host_count = 0;
363     for (RequestSet::const_iterator it = in_flight_requests_.begin();
364          it != in_flight_requests_.end(); ++it) {
365       net::HostPortPair host_port_pair =
366           net::HostPortPair::FromURL((*it)->url_request()->url());
367       if (active_request_host.Equals(host_port_pair)) {
368         same_host_count++;
369         if (same_host_count >= kMaxNumDelayableRequestsPerHost)
370           return true;
371       }
372     }
373     return false;
374   }
375
376   void StartRequest(ScheduledResourceRequest* request) {
377     InsertInFlightRequest(request);
378     request->Start();
379   }
380
381   // ShouldStartRequest is the main scheduling algorithm.
382   //
383   // Requests are categorized into two categories:
384   //
385   // 1. Immediately issued requests, which are:
386   //
387   //   * Higher priority requests (>= net::LOW).
388   //   * Synchronous requests.
389   //   * Requests to SPDY-capable origin servers.
390   //   * Non-HTTP[S] requests.
391   //
392   // 2. The remainder are delayable requests, which follow these rules:
393   //
394   //   * If no high priority requests are in flight, start loading low priority
395   //      requests.
396   //   * Once the renderer has a <body>, start loading delayable requests.
397   //   * Never exceed 10 delayable requests in flight per client.
398   //   * Never exceed 6 delayable requests for a given host.
399   //   * Prior to <body>, allow one delayable request to load at a time.
400   ShouldStartReqResult ShouldStartRequest(
401       ScheduledResourceRequest* request) const {
402     const net::URLRequest& url_request = *request->url_request();
403     // TODO(simonjam): This may end up causing disk contention. We should
404     // experiment with throttling if that happens.
405     if (!url_request.url().SchemeIsHTTPOrHTTPS()) {
406       return START_REQUEST;
407     }
408
409     if (using_spdy_proxy_ && url_request.url().SchemeIs("http")) {
410       return START_REQUEST;
411     }
412
413     net::HttpServerProperties& http_server_properties =
414         *url_request.context()->http_server_properties();
415
416     if (url_request.priority() >= net::LOW ||
417         !ResourceRequestInfo::ForRequest(&url_request)->IsAsync()) {
418       return START_REQUEST;
419     }
420
421     net::HostPortPair host_port_pair =
422         net::HostPortPair::FromURL(url_request.url());
423
424     // TODO(willchan): We should really improve this algorithm as described in
425     // crbug.com/164101. Also, theoretically we should not count a SPDY request
426     // against the delayable requests limit.
427     if (http_server_properties.SupportsSpdy(host_port_pair)) {
428       return START_REQUEST;
429     }
430
431     size_t num_delayable_requests_in_flight = total_delayable_count_;
432     if (num_delayable_requests_in_flight >= kMaxNumDelayableRequestsPerClient) {
433       return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
434     }
435
436     if (ShouldKeepSearching(host_port_pair)) {
437       // There may be other requests for other hosts we'd allow,
438       // so keep checking.
439       return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING;
440     }
441
442     bool have_immediate_requests_in_flight =
443         in_flight_requests_.size() > num_delayable_requests_in_flight;
444     if (have_immediate_requests_in_flight && !has_body_ &&
445         num_delayable_requests_in_flight != 0) {
446       return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
447     }
448
449     return START_REQUEST;
450   }
451
452   void LoadAnyStartablePendingRequests() {
453     // We iterate through all the pending requests, starting with the highest
454     // priority one. For each entry, one of three things can happen:
455     // 1) We start the request, remove it from the list, and keep checking.
456     // 2) We do NOT start the request, but ShouldStartRequest() signals us that
457     //     there may be room for other requests, so we keep checking and leave
458     //     the previous request still in the list.
459     // 3) We do not start the request, same as above, but StartRequest() tells
460     //     us there's no point in checking any further requests.
461     RequestQueue::NetQueue::iterator request_iter =
462         pending_requests_.GetNextHighestIterator();
463
464     while (request_iter != pending_requests_.End()) {
465       ScheduledResourceRequest* request = *request_iter;
466       ShouldStartReqResult query_result = ShouldStartRequest(request);
467
468       if (query_result == START_REQUEST) {
469         pending_requests_.Erase(request);
470         StartRequest(request);
471
472         // StartRequest can modify the pending list, so we (re)start evaluation
473         // from the currently highest priority request. Avoid copying a singular
474         // iterator, which would trigger undefined behavior.
475         if (pending_requests_.GetNextHighestIterator() ==
476             pending_requests_.End())
477           break;
478         request_iter = pending_requests_.GetNextHighestIterator();
479       } else if (query_result == DO_NOT_START_REQUEST_AND_KEEP_SEARCHING) {
480         ++request_iter;
481         continue;
482       } else {
483         DCHECK(query_result == DO_NOT_START_REQUEST_AND_STOP_SEARCHING);
484         break;
485       }
486     }
487   }
488
489   bool has_body_;
490   bool using_spdy_proxy_;
491   RequestQueue pending_requests_;
492   RequestSet in_flight_requests_;
493   // The number of delayable in-flight requests.
494   size_t total_delayable_count_;
495 };
496
497 ResourceScheduler::ResourceScheduler() {
498 }
499
500 ResourceScheduler::~ResourceScheduler() {
501   DCHECK(unowned_requests_.empty());
502   DCHECK(client_map_.empty());
503 }
504
505 scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest(
506     int child_id,
507     int route_id,
508     net::URLRequest* url_request) {
509   DCHECK(CalledOnValidThread());
510   ClientId client_id = MakeClientId(child_id, route_id);
511   scoped_ptr<ScheduledResourceRequest> request(
512       new ScheduledResourceRequest(client_id, url_request, this,
513           RequestPriorityParams(url_request->priority(), 0)));
514
515   ClientMap::iterator it = client_map_.find(client_id);
516   if (it == client_map_.end()) {
517     // There are several ways this could happen:
518     // 1. <a ping> requests don't have a route_id.
519     // 2. Most unittests don't send the IPCs needed to register Clients.
520     // 3. The tab is closed while a RequestResource IPC is in flight.
521     unowned_requests_.insert(request.get());
522     request->Start();
523     return request.PassAs<ResourceThrottle>();
524   }
525
526   Client* client = it->second;
527   client->ScheduleRequest(url_request, request.get());
528   return request.PassAs<ResourceThrottle>();
529 }
530
531 void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) {
532   DCHECK(CalledOnValidThread());
533   if (ContainsKey(unowned_requests_, request)) {
534     unowned_requests_.erase(request);
535     return;
536   }
537
538   ClientMap::iterator client_it = client_map_.find(request->client_id());
539   if (client_it == client_map_.end()) {
540     return;
541   }
542
543   Client* client = client_it->second;
544   client->RemoveRequest(request);
545 }
546
547 void ResourceScheduler::OnClientCreated(int child_id, int route_id) {
548   DCHECK(CalledOnValidThread());
549   ClientId client_id = MakeClientId(child_id, route_id);
550   DCHECK(!ContainsKey(client_map_, client_id));
551
552   client_map_[client_id] = new Client;
553 }
554
555 void ResourceScheduler::OnClientDeleted(int child_id, int route_id) {
556   DCHECK(CalledOnValidThread());
557   ClientId client_id = MakeClientId(child_id, route_id);
558   DCHECK(ContainsKey(client_map_, client_id));
559   ClientMap::iterator it = client_map_.find(client_id);
560   if (it == client_map_.end())
561     return;
562
563   Client* client = it->second;
564
565   // FYI, ResourceDispatcherHost cancels all of the requests after this function
566   // is called. It should end up canceling all of the requests except for a
567   // cross-renderer navigation.
568   RequestSet client_unowned_requests = client->RemoveAllRequests();
569   for (RequestSet::iterator it = client_unowned_requests.begin();
570        it != client_unowned_requests.end(); ++it) {
571     unowned_requests_.insert(*it);
572   }
573
574   delete client;
575   client_map_.erase(it);
576 }
577
578 void ResourceScheduler::OnNavigate(int child_id, int route_id) {
579   DCHECK(CalledOnValidThread());
580   ClientId client_id = MakeClientId(child_id, route_id);
581
582   ClientMap::iterator it = client_map_.find(client_id);
583   if (it == client_map_.end()) {
584     // The client was likely deleted shortly before we received this IPC.
585     return;
586   }
587
588   Client* client = it->second;
589   client->OnNavigate();
590 }
591
592 void ResourceScheduler::OnWillInsertBody(int child_id, int route_id) {
593   DCHECK(CalledOnValidThread());
594   ClientId client_id = MakeClientId(child_id, route_id);
595
596   ClientMap::iterator it = client_map_.find(client_id);
597   if (it == client_map_.end()) {
598     // The client was likely deleted shortly before we received this IPC.
599     return;
600   }
601
602   Client* client = it->second;
603   client->OnWillInsertBody();
604 }
605
606 void ResourceScheduler::OnReceivedSpdyProxiedHttpResponse(
607     int child_id,
608     int route_id) {
609   DCHECK(CalledOnValidThread());
610   ClientId client_id = MakeClientId(child_id, route_id);
611
612   ClientMap::iterator client_it = client_map_.find(client_id);
613   if (client_it == client_map_.end()) {
614     return;
615   }
616
617   Client* client = client_it->second;
618   client->OnReceivedSpdyProxiedHttpResponse();
619 }
620
621 void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request,
622                                             net::RequestPriority new_priority,
623                                             int new_intra_priority_value) {
624   if (request->url_request()->load_flags() & net::LOAD_IGNORE_LIMITS) {
625     // We should not be re-prioritizing requests with the
626     // IGNORE_LIMITS flag.
627     NOTREACHED();
628     return;
629   }
630   RequestPriorityParams new_priority_params(new_priority,
631       new_intra_priority_value);
632   RequestPriorityParams old_priority_params =
633       request->get_request_priority_params();
634
635   DCHECK(old_priority_params != new_priority_params);
636
637   ClientMap::iterator client_it = client_map_.find(request->client_id());
638   if (client_it == client_map_.end()) {
639     // The client was likely deleted shortly before we received this IPC.
640     request->url_request()->SetPriority(new_priority_params.priority);
641     request->set_request_priority_params(new_priority_params);
642     return;
643   }
644
645   if (old_priority_params == new_priority_params)
646     return;
647
648   Client *client = client_it->second;
649   client->ReprioritizeRequest(
650       request, old_priority_params, new_priority_params);
651 }
652
653 ResourceScheduler::ClientId ResourceScheduler::MakeClientId(
654     int child_id, int route_id) {
655   return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id;
656 }
657
658 }  // namespace content