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.
7 #include "content/browser/loader/resource_scheduler.h"
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"
25 static const size_t kMaxNumDelayableRequestsPerClient = 10;
26 static const size_t kMaxNumDelayableRequestsPerHost = 6;
29 struct ResourceScheduler::RequestPriorityParams {
30 RequestPriorityParams()
31 : priority(net::DEFAULT_PRIORITY),
35 RequestPriorityParams(net::RequestPriority priority, int intra_priority)
37 intra_priority(intra_priority) {
40 bool operator==(const RequestPriorityParams& other) const {
41 return (priority == other.priority) &&
42 (intra_priority == other.intra_priority);
45 bool operator!=(const RequestPriorityParams& other) const {
46 return !(*this == other);
49 bool GreaterThan(const RequestPriorityParams& other) const {
50 if (priority != other.priority)
51 return priority > other.priority;
52 return intra_priority > other.intra_priority;
55 net::RequestPriority priority;
59 class ResourceScheduler::RequestQueue {
61 typedef std::multiset<ScheduledResourceRequest*, ScheduledResourceSorter>
64 RequestQueue() : fifo_ordering_ids_(0) {}
67 // Adds |request| to the queue with given |priority|.
68 void Insert(ScheduledResourceRequest* request);
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())
76 queue_.erase(it->second);
80 NetQueue::iterator GetNextHighestIterator() {
81 return queue_.begin();
84 NetQueue::iterator End() {
88 // Returns true if |request| is queued.
89 bool IsQueued(ScheduledResourceRequest* request) const {
90 return ContainsKey(pointers_, request);
93 // Returns true if no requests are queued.
94 bool IsEmpty() const { return queue_.size() == 0; }
97 typedef std::map<ScheduledResourceRequest*, NetQueue::iterator> PointerMap;
99 uint32 MakeFifoOrderingId() {
100 fifo_ordering_ids_ += 1;
101 return fifo_ordering_ids_;
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_;
109 PointerMap pointers_;
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 {
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),
127 scheduler_(scheduler),
130 accounted_as_delayable_request_(false) {
131 TRACE_EVENT_ASYNC_BEGIN1("net", "URLRequest", request_,
132 "url", request->url().spec());
135 virtual ~ScheduledResourceRequest() {
136 scheduler_->RemoveRequest(this);
140 TRACE_EVENT_ASYNC_STEP_PAST0("net", "URLRequest", request_, "Queued");
142 if (deferred_ && request_->status().is_success()) {
144 controller()->Resume();
148 void set_request_priority_params(const RequestPriorityParams& priority) {
149 priority_ = priority;
151 const RequestPriorityParams& get_request_priority_params() const {
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;
161 bool accounted_as_delayable_request() const {
162 return accounted_as_delayable_request_;
164 void set_accounted_as_delayable_request(bool accounted) {
165 accounted_as_delayable_request_ = accounted;
169 // ResourceMessageDelegate interface:
170 virtual bool OnMessageReceived(const IPC::Message& message,
171 bool* message_was_ok) OVERRIDE {
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()
180 // ResourceThrottle interface:
181 virtual void WillStartRequest(bool* defer) OVERRIDE {
182 deferred_ = *defer = !ready_;
185 virtual const char* GetNameForLogging() const OVERRIDE {
186 return "ResourceScheduler";
189 void DidChangePriority(int request_id, net::RequestPriority new_priority,
190 int intra_priority_value) {
191 scheduler_->ReprioritizeRequest(this, new_priority, intra_priority_value);
195 net::URLRequest* request_;
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_;
204 DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest);
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());
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();
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);
230 // Each client represents a tab.
231 class ResourceScheduler::Client {
235 using_spdy_proxy_(false),
236 total_delayable_count_(0) {}
239 void ScheduleRequest(
240 net::URLRequest* url_request,
241 ScheduledResourceRequest* request) {
242 if (ShouldStartRequest(request) == START_REQUEST) {
243 StartRequest(request);
245 pending_requests_.Insert(request);
249 void RemoveRequest(ScheduledResourceRequest* request) {
250 if (pending_requests_.IsQueued(request)) {
251 pending_requests_.Erase(request);
252 DCHECK(!ContainsKey(in_flight_requests_, request));
254 EraseInFlightRequest(request);
256 // Removing this request may have freed up another to load.
257 LoadAnyStartablePendingRequests();
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);
268 ClearInFlightRequests();
269 return unowned_requests;
276 void OnWillInsertBody() {
278 LoadAnyStartablePendingRequests();
281 void OnReceivedSpdyProxiedHttpResponse() {
282 if (!using_spdy_proxy_) {
283 using_spdy_proxy_ = true;
284 LoadAnyStartablePendingRequests();
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
297 SetRequestDelayable(request, IsDelayableRequest(request));
298 // Request has already started.
302 pending_requests_.Erase(request);
303 pending_requests_.Insert(request);
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();
312 enum ShouldStartReqResult {
313 DO_NOT_START_REQUEST_AND_STOP_SEARCHING = -2,
314 DO_NOT_START_REQUEST_AND_KEEP_SEARCHING = -1,
318 void InsertInFlightRequest(ScheduledResourceRequest* request) {
319 in_flight_requests_.insert(request);
320 if (IsDelayableRequest(request))
321 SetRequestDelayable(request, true);
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());
331 void ClearInFlightRequests() {
332 in_flight_requests_.clear();
333 total_delayable_count_ = 0;
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)) {
349 void SetRequestDelayable(ScheduledResourceRequest* request,
351 if (request->accounted_as_delayable_request() == delayable)
354 total_delayable_count_++;
356 total_delayable_count_--;
357 request->set_accounted_as_delayable_request(delayable);
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)) {
369 if (same_host_count >= kMaxNumDelayableRequestsPerHost)
376 void StartRequest(ScheduledResourceRequest* request) {
377 InsertInFlightRequest(request);
381 // ShouldStartRequest is the main scheduling algorithm.
383 // Requests are categorized into two categories:
385 // 1. Immediately issued requests, which are:
387 // * Higher priority requests (>= net::LOW).
388 // * Synchronous requests.
389 // * Requests to SPDY-capable origin servers.
390 // * Non-HTTP[S] requests.
392 // 2. The remainder are delayable requests, which follow these rules:
394 // * If no high priority requests are in flight, start loading low priority
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;
409 if (using_spdy_proxy_ && url_request.url().SchemeIs("http")) {
410 return START_REQUEST;
413 net::HttpServerProperties& http_server_properties =
414 *url_request.context()->http_server_properties();
416 if (url_request.priority() >= net::LOW ||
417 !ResourceRequestInfo::ForRequest(&url_request)->IsAsync()) {
418 return START_REQUEST;
421 net::HostPortPair host_port_pair =
422 net::HostPortPair::FromURL(url_request.url());
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;
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;
436 if (ShouldKeepSearching(host_port_pair)) {
437 // There may be other requests for other hosts we'd allow,
439 return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING;
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;
449 return START_REQUEST;
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();
464 while (request_iter != pending_requests_.End()) {
465 ScheduledResourceRequest* request = *request_iter;
466 ShouldStartReqResult query_result = ShouldStartRequest(request);
468 if (query_result == START_REQUEST) {
469 pending_requests_.Erase(request);
470 StartRequest(request);
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())
478 request_iter = pending_requests_.GetNextHighestIterator();
479 } else if (query_result == DO_NOT_START_REQUEST_AND_KEEP_SEARCHING) {
483 DCHECK(query_result == DO_NOT_START_REQUEST_AND_STOP_SEARCHING);
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_;
497 ResourceScheduler::ResourceScheduler() {
500 ResourceScheduler::~ResourceScheduler() {
501 DCHECK(unowned_requests_.empty());
502 DCHECK(client_map_.empty());
505 scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest(
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)));
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());
523 return request.PassAs<ResourceThrottle>();
526 Client* client = it->second;
527 client->ScheduleRequest(url_request, request.get());
528 return request.PassAs<ResourceThrottle>();
531 void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) {
532 DCHECK(CalledOnValidThread());
533 if (ContainsKey(unowned_requests_, request)) {
534 unowned_requests_.erase(request);
538 ClientMap::iterator client_it = client_map_.find(request->client_id());
539 if (client_it == client_map_.end()) {
543 Client* client = client_it->second;
544 client->RemoveRequest(request);
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));
552 client_map_[client_id] = new Client;
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())
563 Client* client = it->second;
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);
575 client_map_.erase(it);
578 void ResourceScheduler::OnNavigate(int child_id, int route_id) {
579 DCHECK(CalledOnValidThread());
580 ClientId client_id = MakeClientId(child_id, route_id);
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.
588 Client* client = it->second;
589 client->OnNavigate();
592 void ResourceScheduler::OnWillInsertBody(int child_id, int route_id) {
593 DCHECK(CalledOnValidThread());
594 ClientId client_id = MakeClientId(child_id, route_id);
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.
602 Client* client = it->second;
603 client->OnWillInsertBody();
606 void ResourceScheduler::OnReceivedSpdyProxiedHttpResponse(
609 DCHECK(CalledOnValidThread());
610 ClientId client_id = MakeClientId(child_id, route_id);
612 ClientMap::iterator client_it = client_map_.find(client_id);
613 if (client_it == client_map_.end()) {
617 Client* client = client_it->second;
618 client->OnReceivedSpdyProxiedHttpResponse();
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.
630 RequestPriorityParams new_priority_params(new_priority,
631 new_intra_priority_value);
632 RequestPriorityParams old_priority_params =
633 request->get_request_priority_params();
635 DCHECK(old_priority_params != new_priority_params);
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);
645 if (old_priority_params == new_priority_params)
648 Client *client = client_it->second;
649 client->ReprioritizeRequest(
650 request, old_priority_params, new_priority_params);
653 ResourceScheduler::ClientId ResourceScheduler::MakeClientId(
654 int child_id, int route_id) {
655 return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id;
658 } // namespace content