Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / loader / resource_scheduler.cc
blob2d9071612a3a67220044ce14b41679ddcc3963d9
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.
5 #include "content/browser/loader/resource_scheduler.h"
7 #include <stdint.h>
9 #include <set>
10 #include <string>
11 #include <vector>
13 #include "base/metrics/field_trial.h"
14 #include "base/metrics/histogram.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_piece.h"
18 #include "base/supports_user_data.h"
19 #include "base/time/time.h"
20 #include "content/common/resource_messages.h"
21 #include "content/public/browser/resource_controller.h"
22 #include "content/public/browser/resource_request_info.h"
23 #include "content/public/browser/resource_throttle.h"
24 #include "net/base/host_port_pair.h"
25 #include "net/base/load_flags.h"
26 #include "net/base/request_priority.h"
27 #include "net/http/http_server_properties.h"
28 #include "net/url_request/url_request.h"
29 #include "net/url_request/url_request_context.h"
31 namespace content {
33 namespace {
35 // Field trial constants
36 const char kThrottleCoalesceFieldTrial[] = "RequestThrottlingAndCoalescing";
37 const char kThrottleCoalesceFieldTrialThrottle[] = "Throttle";
38 const char kThrottleCoalesceFieldTrialCoalesce[] = "Coalesce";
40 const char kRequestLimitFieldTrial[] = "OutstandingRequestLimiting";
41 const char kRequestLimitFieldTrialGroupPrefix[] = "Limit";
43 const char kResourcePrioritiesFieldTrial[] = "ResourcePriorities";
45 // Flags identifying various attributes of the request that are used
46 // when making scheduling decisions.
47 using RequestAttributes = uint8_t;
48 const RequestAttributes kAttributeNone = 0x00;
49 const RequestAttributes kAttributeInFlight = 0x01;
50 const RequestAttributes kAttributeDelayable = 0x02;
51 const RequestAttributes kAttributeLayoutBlocking = 0x04;
53 // Post ResourceScheduler histograms of the following forms:
54 // If |histogram_suffix| is NULL or the empty string:
55 // ResourceScheduler.base_name.histogram_name
56 // Else:
57 // ResourceScheduler.base_name.histogram_name.histogram_suffix
58 void PostHistogram(const char* base_name,
59 const char* histogram_name,
60 const char* histogram_suffix,
61 base::TimeDelta time) {
62 std::string histogram =
63 base::StringPrintf("ResourceScheduler.%s.%s", base_name, histogram_name);
64 if (histogram_suffix && histogram_suffix[0] != '\0')
65 histogram = histogram + "." + histogram_suffix;
66 base::HistogramBase* histogram_counter = base::Histogram::FactoryTimeGet(
67 histogram, base::TimeDelta::FromMilliseconds(1),
68 base::TimeDelta::FromMinutes(5), 50,
69 base::Histogram::kUmaTargetedHistogramFlag);
70 histogram_counter->AddTime(time);
73 // For use with PostHistogram to specify the correct string for histogram
74 // suffixes based on number of Clients.
75 const char* GetNumClientsString(size_t num_clients) {
76 if (num_clients == 1)
77 return "1Client";
78 else if (num_clients <= 5)
79 return "Max5Clients";
80 else if (num_clients <= 15)
81 return "Max15Clients";
82 else if (num_clients <= 30)
83 return "Max30Clients";
84 return "Over30Clients";
87 } // namespace
89 static const size_t kCoalescedTimerPeriod = 5000;
90 static const size_t kDefaultMaxNumDelayableRequestsPerClient = 10;
91 static const size_t kMaxNumDelayableRequestsPerHost = 6;
92 static const size_t kMaxNumThrottledRequestsPerClient = 1;
93 static const size_t kDefaultMaxNumDelayableWhileLayoutBlocking = 1;
94 static const net::RequestPriority
95 kDefaultLayoutBlockingPriorityThreshold = net::LOW;
97 struct ResourceScheduler::RequestPriorityParams {
98 RequestPriorityParams()
99 : priority(net::DEFAULT_PRIORITY),
100 intra_priority(0) {
103 RequestPriorityParams(net::RequestPriority priority, int intra_priority)
104 : priority(priority),
105 intra_priority(intra_priority) {
108 bool operator==(const RequestPriorityParams& other) const {
109 return (priority == other.priority) &&
110 (intra_priority == other.intra_priority);
113 bool operator!=(const RequestPriorityParams& other) const {
114 return !(*this == other);
117 bool GreaterThan(const RequestPriorityParams& other) const {
118 if (priority != other.priority)
119 return priority > other.priority;
120 return intra_priority > other.intra_priority;
123 net::RequestPriority priority;
124 int intra_priority;
127 class ResourceScheduler::RequestQueue {
128 public:
129 typedef std::multiset<ScheduledResourceRequest*, ScheduledResourceSorter>
130 NetQueue;
132 RequestQueue() : fifo_ordering_ids_(0) {}
133 ~RequestQueue() {}
135 // Adds |request| to the queue with given |priority|.
136 void Insert(ScheduledResourceRequest* request);
138 // Removes |request| from the queue.
139 void Erase(ScheduledResourceRequest* request) {
140 PointerMap::iterator it = pointers_.find(request);
141 CHECK(it != pointers_.end());
142 queue_.erase(it->second);
143 pointers_.erase(it);
146 NetQueue::iterator GetNextHighestIterator() {
147 return queue_.begin();
150 NetQueue::iterator End() {
151 return queue_.end();
154 // Returns true if |request| is queued.
155 bool IsQueued(ScheduledResourceRequest* request) const {
156 return ContainsKey(pointers_, request);
159 // Returns true if no requests are queued.
160 bool IsEmpty() const { return queue_.size() == 0; }
162 private:
163 typedef std::map<ScheduledResourceRequest*, NetQueue::iterator> PointerMap;
165 uint32 MakeFifoOrderingId() {
166 fifo_ordering_ids_ += 1;
167 return fifo_ordering_ids_;
170 // Used to create an ordering ID for scheduled resources so that resources
171 // with same priority/intra_priority stay in fifo order.
172 uint32 fifo_ordering_ids_;
174 NetQueue queue_;
175 PointerMap pointers_;
178 // This is the handle we return to the ResourceDispatcherHostImpl so it can
179 // interact with the request.
180 class ResourceScheduler::ScheduledResourceRequest : public ResourceThrottle {
181 public:
182 ScheduledResourceRequest(const ClientId& client_id,
183 net::URLRequest* request,
184 ResourceScheduler* scheduler,
185 const RequestPriorityParams& priority,
186 bool is_async)
187 : client_id_(client_id),
188 client_state_on_creation_(scheduler->GetClientState(client_id_)),
189 request_(request),
190 ready_(false),
191 deferred_(false),
192 is_async_(is_async),
193 attributes_(kAttributeNone),
194 scheduler_(scheduler),
195 priority_(priority),
196 fifo_ordering_(0) {
197 DCHECK(!request_->GetUserData(kUserDataKey));
198 request_->SetUserData(kUserDataKey, new UnownedPointer(this));
201 ~ScheduledResourceRequest() override {
202 request_->RemoveUserData(kUserDataKey);
203 scheduler_->RemoveRequest(this);
206 static ScheduledResourceRequest* ForRequest(net::URLRequest* request) {
207 return static_cast<UnownedPointer*>(request->GetUserData(kUserDataKey))
208 ->get();
211 void Start() {
212 ready_ = true;
213 if (!request_->status().is_success())
214 return;
215 base::TimeTicks time = base::TimeTicks::Now();
216 ClientState current_state = scheduler_->GetClientState(client_id_);
217 // Note: the client state isn't perfectly accurate since it won't capture
218 // tabs which have switched between active and background multiple times.
219 // Ex: A tab with the following transitions Active -> Background -> Active
220 // will be recorded as Active.
221 const char* client_state = "Other";
222 if (current_state == client_state_on_creation_ && current_state == ACTIVE) {
223 client_state = "Active";
224 } else if (current_state == client_state_on_creation_ &&
225 current_state == BACKGROUND) {
226 client_state = "Background";
229 base::TimeDelta time_was_deferred = base::TimeDelta::FromMicroseconds(0);
230 if (deferred_) {
231 deferred_ = false;
232 controller()->Resume();
233 time_was_deferred = time - time_deferred_;
235 PostHistogram("RequestTimeDeferred", client_state, NULL, time_was_deferred);
236 PostHistogram("RequestTimeThrottled", client_state, NULL,
237 time - request_->creation_time());
238 // TODO(aiolos): Remove one of the above histograms after gaining an
239 // understanding of the difference between them and which one is more
240 // interesting.
243 void set_request_priority_params(const RequestPriorityParams& priority) {
244 priority_ = priority;
246 const RequestPriorityParams& get_request_priority_params() const {
247 return priority_;
249 const ClientId& client_id() const { return client_id_; }
250 net::URLRequest* url_request() { return request_; }
251 const net::URLRequest* url_request() const { return request_; }
252 bool is_async() const { return is_async_; }
253 uint32 fifo_ordering() const { return fifo_ordering_; }
254 void set_fifo_ordering(uint32 fifo_ordering) {
255 fifo_ordering_ = fifo_ordering;
257 RequestAttributes attributes() const {
258 return attributes_;
260 void set_attributes(RequestAttributes attributes) {
261 attributes_ = attributes;
264 private:
265 class UnownedPointer : public base::SupportsUserData::Data {
266 public:
267 explicit UnownedPointer(ScheduledResourceRequest* pointer)
268 : pointer_(pointer) {}
270 ScheduledResourceRequest* get() const { return pointer_; }
272 private:
273 ScheduledResourceRequest* const pointer_;
275 DISALLOW_COPY_AND_ASSIGN(UnownedPointer);
278 static const void* const kUserDataKey;
280 // ResourceThrottle interface:
281 void WillStartRequest(bool* defer) override {
282 deferred_ = *defer = !ready_;
283 time_deferred_ = base::TimeTicks::Now();
286 const char* GetNameForLogging() const override { return "ResourceScheduler"; }
288 const ClientId client_id_;
289 const ResourceScheduler::ClientState client_state_on_creation_;
290 net::URLRequest* request_;
291 bool ready_;
292 bool deferred_;
293 bool is_async_;
294 RequestAttributes attributes_;
295 ResourceScheduler* scheduler_;
296 RequestPriorityParams priority_;
297 uint32 fifo_ordering_;
298 base::TimeTicks time_deferred_;
300 DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest);
303 const void* const ResourceScheduler::ScheduledResourceRequest::kUserDataKey =
304 &ResourceScheduler::ScheduledResourceRequest::kUserDataKey;
306 bool ResourceScheduler::ScheduledResourceSorter::operator()(
307 const ScheduledResourceRequest* a,
308 const ScheduledResourceRequest* b) const {
309 // Want the set to be ordered first by decreasing priority, then by
310 // decreasing intra_priority.
311 // ie. with (priority, intra_priority)
312 // [(1, 0), (1, 0), (0, 100), (0, 0)]
313 if (a->get_request_priority_params() != b->get_request_priority_params())
314 return a->get_request_priority_params().GreaterThan(
315 b->get_request_priority_params());
317 // If priority/intra_priority is the same, fall back to fifo ordering.
318 // std::multiset doesn't guarantee this until c++11.
319 return a->fifo_ordering() < b->fifo_ordering();
322 void ResourceScheduler::RequestQueue::Insert(
323 ScheduledResourceRequest* request) {
324 DCHECK(!ContainsKey(pointers_, request));
325 request->set_fifo_ordering(MakeFifoOrderingId());
326 pointers_[request] = queue_.insert(request);
329 // Each client represents a tab.
330 class ResourceScheduler::Client {
331 public:
332 explicit Client(ResourceScheduler* scheduler,
333 bool is_visible,
334 bool is_audible)
335 : is_audible_(is_audible),
336 is_visible_(is_visible),
337 is_loaded_(false),
338 is_paused_(false),
339 has_html_body_(false),
340 using_spdy_proxy_(false),
341 load_started_time_(base::TimeTicks::Now()),
342 scheduler_(scheduler),
343 in_flight_delayable_count_(0),
344 total_layout_blocking_count_(0),
345 throttle_state_(ResourceScheduler::THROTTLED) {}
347 ~Client() {
348 // Update to default state and pause to ensure the scheduler has a
349 // correct count of relevant types of clients.
350 is_visible_ = false;
351 is_audible_ = false;
352 is_paused_ = true;
353 UpdateThrottleState();
356 void ScheduleRequest(net::URLRequest* url_request,
357 ScheduledResourceRequest* request) {
358 SetRequestAttributes(request, DetermineRequestAttributes(request));
359 if (ShouldStartRequest(request) == START_REQUEST)
360 StartRequest(request);
361 else
362 pending_requests_.Insert(request);
365 void RemoveRequest(ScheduledResourceRequest* request) {
366 if (pending_requests_.IsQueued(request)) {
367 pending_requests_.Erase(request);
368 DCHECK(!ContainsKey(in_flight_requests_, request));
369 } else {
370 EraseInFlightRequest(request);
372 // Removing this request may have freed up another to load.
373 LoadAnyStartablePendingRequests();
377 RequestSet StartAndRemoveAllRequests() {
378 // First start any pending requests so that they will be moved into
379 // in_flight_requests_. This may exceed the limits
380 // kDefaultMaxNumDelayableRequestsPerClient, kMaxNumDelayableRequestsPerHost
381 // and kMaxNumThrottledRequestsPerClient, so this method must not do
382 // anything that depends on those limits before calling
383 // ClearInFlightRequests() below.
384 while (!pending_requests_.IsEmpty()) {
385 ScheduledResourceRequest* request =
386 *pending_requests_.GetNextHighestIterator();
387 pending_requests_.Erase(request);
388 // StartRequest() may modify pending_requests_. TODO(ricea): Does it?
389 StartRequest(request);
391 RequestSet unowned_requests;
392 for (RequestSet::iterator it = in_flight_requests_.begin();
393 it != in_flight_requests_.end(); ++it) {
394 unowned_requests.insert(*it);
395 (*it)->set_attributes(kAttributeNone);
397 ClearInFlightRequests();
398 return unowned_requests;
401 bool is_active() const { return is_visible_ || is_audible_; }
403 bool is_loaded() const { return is_loaded_; }
405 bool is_visible() const { return is_visible_; }
407 void OnAudibilityChanged(bool is_audible) {
408 UpdateState(is_audible, &is_audible_);
411 void OnVisibilityChanged(bool is_visible) {
412 UpdateState(is_visible, &is_visible_);
415 // Function to update any client state variable used to determine whether a
416 // Client is active or background. Used for is_visible_ and is_audible_.
417 void UpdateState(bool new_state, bool* current_state) {
418 bool was_active = is_active();
419 *current_state = new_state;
420 if (was_active == is_active())
421 return;
422 last_active_switch_time_ = base::TimeTicks::Now();
423 UpdateThrottleState();
426 void OnLoadingStateChanged(bool is_loaded) {
427 if (is_loaded == is_loaded_) {
428 return;
430 is_loaded_ = is_loaded;
431 UpdateThrottleState();
432 if (!is_loaded_) {
433 load_started_time_ = base::TimeTicks::Now();
434 last_active_switch_time_ = base::TimeTicks();
435 return;
437 base::TimeTicks cur_time = base::TimeTicks::Now();
438 const char* num_clients =
439 GetNumClientsString(scheduler_->client_map_.size());
440 const char* client_catagory = "Other";
441 if (last_active_switch_time_.is_null()) {
442 client_catagory = is_active() ? "Active" : "Background";
443 } else if (is_active()) {
444 base::TimeDelta time_since_active = cur_time - last_active_switch_time_;
445 PostHistogram("ClientLoadedTime", "Other.SwitchedToActive", NULL,
446 time_since_active);
447 PostHistogram("ClientLoadedTime", "Other.SwitchedToActive", num_clients,
448 time_since_active);
450 base::TimeDelta time_since_load_started = cur_time - load_started_time_;
451 PostHistogram("ClientLoadedTime", client_catagory, NULL,
452 time_since_load_started);
453 PostHistogram("ClientLoadedTime", client_catagory, num_clients,
454 time_since_load_started);
455 // TODO(aiolos): The above histograms will not take main resource load time
456 // into account with PlzNavigate into account. The ResourceScheduler also
457 // will load the main resources without a clients with the current logic.
458 // Find a way to fix both of these issues.
461 void SetPaused() {
462 is_paused_ = true;
463 UpdateThrottleState();
466 void UpdateThrottleState() {
467 ClientThrottleState old_throttle_state = throttle_state_;
468 if (!scheduler_->should_throttle()) {
469 SetThrottleState(UNTHROTTLED);
470 } else if (is_active() && !is_loaded_) {
471 SetThrottleState(ACTIVE_AND_LOADING);
472 } else if (is_active()) {
473 SetThrottleState(UNTHROTTLED);
474 } else if (is_paused_) {
475 SetThrottleState(PAUSED);
476 } else if (!scheduler_->active_clients_loaded()) {
477 SetThrottleState(THROTTLED);
478 } else if (is_loaded_ && scheduler_->should_coalesce()) {
479 SetThrottleState(COALESCED);
480 } else if (!is_active()) {
481 SetThrottleState(UNTHROTTLED);
484 if (throttle_state_ == old_throttle_state) {
485 return;
487 if (throttle_state_ == ACTIVE_AND_LOADING) {
488 scheduler_->IncrementActiveClientsLoading();
489 } else if (old_throttle_state == ACTIVE_AND_LOADING) {
490 scheduler_->DecrementActiveClientsLoading();
492 if (throttle_state_ == COALESCED) {
493 scheduler_->IncrementCoalescedClients();
494 } else if (old_throttle_state == COALESCED) {
495 scheduler_->DecrementCoalescedClients();
499 void OnNavigate() {
500 has_html_body_ = false;
501 is_loaded_ = false;
504 void OnWillInsertBody() {
505 has_html_body_ = true;
506 LoadAnyStartablePendingRequests();
509 void OnReceivedSpdyProxiedHttpResponse() {
510 if (!using_spdy_proxy_) {
511 using_spdy_proxy_ = true;
512 LoadAnyStartablePendingRequests();
516 void ReprioritizeRequest(ScheduledResourceRequest* request,
517 RequestPriorityParams old_priority_params,
518 RequestPriorityParams new_priority_params) {
519 request->url_request()->SetPriority(new_priority_params.priority);
520 request->set_request_priority_params(new_priority_params);
521 SetRequestAttributes(request, DetermineRequestAttributes(request));
522 if (!pending_requests_.IsQueued(request)) {
523 DCHECK(ContainsKey(in_flight_requests_, request));
524 // Request has already started.
525 return;
528 pending_requests_.Erase(request);
529 pending_requests_.Insert(request);
531 if (new_priority_params.priority > old_priority_params.priority) {
532 // Check if this request is now able to load at its new priority.
533 LoadAnyStartablePendingRequests();
537 // Called on Client creation, when a Client changes user observability,
538 // possibly when all observable Clients have finished loading, and
539 // possibly when this Client has finished loading.
540 // State changes:
541 // Client became observable.
542 // any state -> UNTHROTTLED
543 // Client is unobservable, but all observable clients finished loading.
544 // THROTTLED -> UNTHROTTLED
545 // Non-observable client finished loading.
546 // THROTTLED || UNTHROTTLED -> COALESCED
547 // Non-observable client, an observable client starts loading.
548 // COALESCED -> THROTTLED
549 // A COALESCED client will transition into UNTHROTTLED when the network is
550 // woken up by a heartbeat and then transition back into COALESCED.
551 void SetThrottleState(ResourceScheduler::ClientThrottleState throttle_state) {
552 if (throttle_state == throttle_state_) {
553 return;
555 throttle_state_ = throttle_state;
556 if (throttle_state_ != PAUSED) {
557 is_paused_ = false;
559 LoadAnyStartablePendingRequests();
560 // TODO(aiolos): Stop any started but not inflght requests when
561 // switching to stricter throttle state?
564 ResourceScheduler::ClientThrottleState throttle_state() const {
565 return throttle_state_;
568 void LoadCoalescedRequests() {
569 if (throttle_state_ != COALESCED) {
570 return;
572 if (scheduler_->active_clients_loaded()) {
573 SetThrottleState(UNTHROTTLED);
574 } else {
575 SetThrottleState(THROTTLED);
577 LoadAnyStartablePendingRequests();
578 SetThrottleState(COALESCED);
581 private:
582 enum ShouldStartReqResult {
583 DO_NOT_START_REQUEST_AND_STOP_SEARCHING,
584 DO_NOT_START_REQUEST_AND_KEEP_SEARCHING,
585 START_REQUEST,
588 void InsertInFlightRequest(ScheduledResourceRequest* request) {
589 in_flight_requests_.insert(request);
590 SetRequestAttributes(request, DetermineRequestAttributes(request));
593 void EraseInFlightRequest(ScheduledResourceRequest* request) {
594 size_t erased = in_flight_requests_.erase(request);
595 DCHECK_EQ(1u, erased);
596 // Clear any special state that we were tracking for this request.
597 SetRequestAttributes(request, kAttributeNone);
600 void ClearInFlightRequests() {
601 in_flight_requests_.clear();
602 in_flight_delayable_count_ = 0;
603 total_layout_blocking_count_ = 0;
606 size_t CountRequestsWithAttributes(
607 const RequestAttributes attributes,
608 ScheduledResourceRequest* current_request) {
609 size_t matching_request_count = 0;
610 for (RequestSet::const_iterator it = in_flight_requests_.begin();
611 it != in_flight_requests_.end(); ++it) {
612 if (RequestAttributesAreSet((*it)->attributes(), attributes))
613 matching_request_count++;
615 if (!RequestAttributesAreSet(attributes, kAttributeInFlight)) {
616 bool current_request_is_pending = false;
617 for (RequestQueue::NetQueue::const_iterator
618 it = pending_requests_.GetNextHighestIterator();
619 it != pending_requests_.End(); ++it) {
620 if (RequestAttributesAreSet((*it)->attributes(), attributes))
621 matching_request_count++;
622 if (*it == current_request)
623 current_request_is_pending = true;
625 // Account for the current request if it is not in one of the lists yet.
626 if (current_request &&
627 !ContainsKey(in_flight_requests_, current_request) &&
628 !current_request_is_pending) {
629 if (RequestAttributesAreSet(current_request->attributes(), attributes))
630 matching_request_count++;
633 return matching_request_count;
636 bool RequestAttributesAreSet(RequestAttributes request_attributes,
637 RequestAttributes matching_attributes) const {
638 return (request_attributes & matching_attributes) == matching_attributes;
641 void SetRequestAttributes(ScheduledResourceRequest* request,
642 RequestAttributes attributes) {
643 RequestAttributes old_attributes = request->attributes();
644 if (old_attributes == attributes)
645 return;
647 if (RequestAttributesAreSet(old_attributes,
648 kAttributeInFlight | kAttributeDelayable)) {
649 in_flight_delayable_count_--;
651 if (RequestAttributesAreSet(old_attributes, kAttributeLayoutBlocking))
652 total_layout_blocking_count_--;
654 if (RequestAttributesAreSet(attributes,
655 kAttributeInFlight | kAttributeDelayable)) {
656 in_flight_delayable_count_++;
658 if (RequestAttributesAreSet(attributes, kAttributeLayoutBlocking))
659 total_layout_blocking_count_++;
661 request->set_attributes(attributes);
662 DCHECK_EQ(CountRequestsWithAttributes(
663 kAttributeInFlight | kAttributeDelayable, request),
664 in_flight_delayable_count_);
665 DCHECK_EQ(CountRequestsWithAttributes(kAttributeLayoutBlocking, request),
666 total_layout_blocking_count_);
669 RequestAttributes DetermineRequestAttributes(
670 ScheduledResourceRequest* request) {
671 RequestAttributes attributes = kAttributeNone;
673 if (ContainsKey(in_flight_requests_, request))
674 attributes |= kAttributeInFlight;
676 if (RequestAttributesAreSet(request->attributes(),
677 kAttributeLayoutBlocking)) {
678 // If a request is already marked as layout-blocking make sure to keep the
679 // attribute across redirects.
680 attributes |= kAttributeLayoutBlocking;
681 } else if (!has_html_body_ &&
682 request->url_request()->priority() >
683 scheduler_->non_delayable_threshold()) {
684 // Requests that are above the non_delayable threshold before the HTML
685 // body has been parsed are inferred to be layout-blocking.
686 attributes |= kAttributeLayoutBlocking;
687 } else if (request->url_request()->priority() <
688 scheduler_->non_delayable_threshold()) {
689 // Resources below the non_delayable_threshold that are being requested
690 // from a server that does not support native prioritization are
691 // considered delayable.
692 net::HostPortPair host_port_pair =
693 net::HostPortPair::FromURL(request->url_request()->url());
694 net::HttpServerProperties& http_server_properties =
695 *request->url_request()->context()->http_server_properties();
696 if (!http_server_properties.SupportsRequestPriority(host_port_pair))
697 attributes |= kAttributeDelayable;
700 return attributes;
703 bool ShouldKeepSearching(
704 const net::HostPortPair& active_request_host) const {
705 size_t same_host_count = 0;
706 for (RequestSet::const_iterator it = in_flight_requests_.begin();
707 it != in_flight_requests_.end(); ++it) {
708 net::HostPortPair host_port_pair =
709 net::HostPortPair::FromURL((*it)->url_request()->url());
710 if (active_request_host.Equals(host_port_pair)) {
711 same_host_count++;
712 if (same_host_count >= kMaxNumDelayableRequestsPerHost)
713 return true;
716 return false;
719 void StartRequest(ScheduledResourceRequest* request) {
720 InsertInFlightRequest(request);
721 request->Start();
724 // ShouldStartRequest is the main scheduling algorithm.
726 // Requests are evaluated on five attributes:
728 // 1. Non-delayable requests:
729 // * Synchronous requests.
730 // * Non-HTTP[S] requests.
732 // 2. Requests to request-priority-capable origin servers.
734 // 3. High-priority requests:
735 // * Higher priority requests (>= net::LOW).
737 // 4. Layout-blocking requests:
738 // * High-priority requests (> net::LOW) initiated before the renderer has
739 // a <body>.
741 // 5. Low priority requests
743 // The following rules are followed:
745 // All types of requests:
746 // * If an outstanding request limit is in place, only that number
747 // of requests may be in flight for a single client at the same time.
749 // ACTIVE_AND_LOADING and UNTHROTTLED Clients follow these rules:
750 // * Non-delayable, High-priority and request-priority capable requests are
751 // issued immediately.
752 // * Low priority requests are delayable.
753 // * While layout-blocking requests are loading or the body tag has not
754 // yet been parsed, limit the number of delayable requests that may be
755 // in flight (to 1 by default, or to zero if there's an outstanding
756 // request limit in place).
757 // * If no high priority or layout-blocking requests are in flight, start
758 // loading delayable requests.
759 // * Never exceed 10 delayable requests in flight per client.
760 // * Never exceed 6 delayable requests for a given host.
762 // THROTTLED Clients follow these rules:
763 // * Non-delayable and request-priority-capable requests are issued
764 // immediately.
765 // * At most one non-request-priority-capable request will be issued per
766 // THROTTLED Client
767 // * If no high priority requests are in flight, start loading low priority
768 // requests.
770 // COALESCED Clients never load requests, with the following exceptions:
771 // * Non-delayable requests are issued imediately.
772 // * On a (currently 5 second) heart beat, they load all requests as an
773 // UNTHROTTLED Client, and then return to the COALESCED state.
774 // * When an active Client makes a request, they are THROTTLED until the
775 // active Client finishes loading.
776 ShouldStartReqResult ShouldStartRequest(
777 ScheduledResourceRequest* request) const {
778 const net::URLRequest& url_request = *request->url_request();
779 // Syncronous requests could block the entire render, which could impact
780 // user-observable Clients.
781 if (!request->is_async())
782 return START_REQUEST;
784 // TODO(simonjam): This may end up causing disk contention. We should
785 // experiment with throttling if that happens.
786 // TODO(aiolos): We probably want to Coalesce these as well to avoid
787 // waking the disk.
788 if (!url_request.url().SchemeIsHTTPOrHTTPS())
789 return START_REQUEST;
791 if (throttle_state_ == COALESCED)
792 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
794 if (using_spdy_proxy_ && url_request.url().SchemeIs(url::kHttpScheme))
795 return START_REQUEST;
797 // Implementation of the kRequestLimitFieldTrial.
798 if (scheduler_->limit_outstanding_requests() &&
799 in_flight_requests_.size() >= scheduler_->outstanding_request_limit()) {
800 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
803 net::HostPortPair host_port_pair =
804 net::HostPortPair::FromURL(url_request.url());
805 net::HttpServerProperties& http_server_properties =
806 *url_request.context()->http_server_properties();
808 // TODO(willchan): We should really improve this algorithm as described in
809 // crbug.com/164101. Also, theoretically we should not count a
810 // request-priority capable request against the delayable requests limit.
811 if (http_server_properties.SupportsRequestPriority(host_port_pair))
812 return START_REQUEST;
814 if (throttle_state_ == THROTTLED &&
815 in_flight_requests_.size() >= kMaxNumThrottledRequestsPerClient) {
816 // There may still be request-priority-capable requests that should be
817 // issued.
818 return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING;
821 // Non-delayable requests.
822 if (!RequestAttributesAreSet(request->attributes(), kAttributeDelayable))
823 return START_REQUEST;
825 if (in_flight_delayable_count_ >=
826 scheduler_->max_num_delayable_requests()) {
827 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
830 if (ShouldKeepSearching(host_port_pair)) {
831 // There may be other requests for other hosts that may be allowed,
832 // so keep checking.
833 return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING;
836 // The in-flight requests consist of layout-blocking requests,
837 // normal requests and delayable requests. Everything except for
838 // delayable requests is handled above here so this is deciding what to
839 // do with a delayable request while we are in the layout-blocking phase
840 // of loading.
841 if (!has_html_body_ || total_layout_blocking_count_ != 0) {
842 size_t non_delayable_requests_in_flight_count =
843 in_flight_requests_.size() - in_flight_delayable_count_;
844 if (scheduler_->enable_in_flight_non_delayable_threshold()) {
845 if (non_delayable_requests_in_flight_count >
846 scheduler_->in_flight_non_delayable_threshold()) {
847 // Too many higher priority in-flight requests to allow lower priority
848 // requests through.
849 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
851 if (in_flight_requests_.size() > 0 &&
852 (scheduler_->limit_outstanding_requests() ||
853 in_flight_delayable_count_ >=
854 scheduler_->max_num_delayable_while_layout_blocking())) {
855 // Block the request if at least one request is in flight and the
856 // number of in-flight delayable requests has hit the configured
857 // limit.
858 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
860 } else if (non_delayable_requests_in_flight_count > 0 &&
861 (scheduler_->limit_outstanding_requests() ||
862 in_flight_delayable_count_ >=
863 scheduler_->max_num_delayable_while_layout_blocking())) {
864 // If there are no high-priority requests in flight the floodgates open.
865 // If there are high-priority requests in-flight then limit the number
866 // of lower-priority requests (or zero if a limit field trial is
867 // active).
868 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
872 return START_REQUEST;
875 void LoadAnyStartablePendingRequests() {
876 // We iterate through all the pending requests, starting with the highest
877 // priority one. For each entry, one of three things can happen:
878 // 1) We start the request, remove it from the list, and keep checking.
879 // 2) We do NOT start the request, but ShouldStartRequest() signals us that
880 // there may be room for other requests, so we keep checking and leave
881 // the previous request still in the list.
882 // 3) We do not start the request, same as above, but StartRequest() tells
883 // us there's no point in checking any further requests.
884 RequestQueue::NetQueue::iterator request_iter =
885 pending_requests_.GetNextHighestIterator();
887 while (request_iter != pending_requests_.End()) {
888 ScheduledResourceRequest* request = *request_iter;
889 ShouldStartReqResult query_result = ShouldStartRequest(request);
891 if (query_result == START_REQUEST) {
892 pending_requests_.Erase(request);
893 StartRequest(request);
895 // StartRequest can modify the pending list, so we (re)start evaluation
896 // from the currently highest priority request. Avoid copying a singular
897 // iterator, which would trigger undefined behavior.
898 if (pending_requests_.GetNextHighestIterator() ==
899 pending_requests_.End())
900 break;
901 request_iter = pending_requests_.GetNextHighestIterator();
902 } else if (query_result == DO_NOT_START_REQUEST_AND_KEEP_SEARCHING) {
903 ++request_iter;
904 continue;
905 } else {
906 DCHECK(query_result == DO_NOT_START_REQUEST_AND_STOP_SEARCHING);
907 break;
912 bool is_audible_;
913 bool is_visible_;
914 bool is_loaded_;
915 bool is_paused_;
916 // Tracks if the main HTML parser has reached the body which marks the end of
917 // layout-blocking resources.
918 bool has_html_body_;
919 bool using_spdy_proxy_;
920 RequestQueue pending_requests_;
921 RequestSet in_flight_requests_;
922 base::TimeTicks load_started_time_;
923 // The last time the client switched state between active and background.
924 base::TimeTicks last_active_switch_time_;
925 ResourceScheduler* scheduler_;
926 // The number of delayable in-flight requests.
927 size_t in_flight_delayable_count_;
928 // The number of layout-blocking in-flight requests.
929 size_t total_layout_blocking_count_;
930 ResourceScheduler::ClientThrottleState throttle_state_;
933 ResourceScheduler::ResourceScheduler()
934 : should_coalesce_(false),
935 should_throttle_(false),
936 active_clients_loading_(0),
937 coalesced_clients_(0),
938 limit_outstanding_requests_(false),
939 outstanding_request_limit_(0),
940 non_delayable_threshold_(
941 kDefaultLayoutBlockingPriorityThreshold),
942 enable_in_flight_non_delayable_threshold_(false),
943 in_flight_non_delayable_threshold_(0),
944 max_num_delayable_while_layout_blocking_(
945 kDefaultMaxNumDelayableWhileLayoutBlocking),
946 max_num_delayable_requests_(kDefaultMaxNumDelayableRequestsPerClient),
947 coalescing_timer_(new base::Timer(true /* retain_user_task */,
948 true /* is_repeating */)) {
949 std::string throttling_trial_group =
950 base::FieldTrialList::FindFullName(kThrottleCoalesceFieldTrial);
951 if (throttling_trial_group == kThrottleCoalesceFieldTrialThrottle) {
952 should_throttle_ = true;
953 } else if (throttling_trial_group == kThrottleCoalesceFieldTrialCoalesce) {
954 should_coalesce_ = true;
955 should_throttle_ = true;
958 std::string outstanding_limit_trial_group =
959 base::FieldTrialList::FindFullName(kRequestLimitFieldTrial);
960 std::vector<std::string> split_group(
961 base::SplitString(outstanding_limit_trial_group, "=",
962 base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL));
963 int outstanding_limit = 0;
964 if (split_group.size() == 2 &&
965 split_group[0] == kRequestLimitFieldTrialGroupPrefix &&
966 base::StringToInt(split_group[1], &outstanding_limit) &&
967 outstanding_limit > 0) {
968 limit_outstanding_requests_ = true;
969 outstanding_request_limit_ = outstanding_limit;
972 // Set up the ResourceScheduling field trial options.
973 // The field trial parameters are also encoded into the group name since
974 // the variations component is not available from here and plumbing the
975 // options through the code is overkill for a short experiment.
977 // The group name encoding looks like this:
978 // <descriptiveName>_ABCDE_E2_F_G
979 // A - fetchDeferLateScripts (1 for true, 0 for false)
980 // B - fetchIncreaseFontPriority (1 for true, 0 for false)
981 // C - fetchIncreaseAsyncScriptPriority (1 for true, 0 for false)
982 // D - fetchIncreasePriorities (1 for true, 0 for false)
983 // E - fetchEnableLayoutBlockingThreshold (1 for true, 0 for false)
984 // E2 - fetchLayoutBlockingThreshold (Numeric)
985 // F - fetchMaxNumDelayableWhileLayoutBlocking (Numeric)
986 // G - fetchMaxNumDelayableRequests (Numeric)
987 std::string resource_priorities_trial_group =
988 base::FieldTrialList::FindFullName(kResourcePrioritiesFieldTrial);
989 std::vector<std::string> resource_priorities_split_group(
990 base::SplitString(resource_priorities_trial_group, "_",
991 base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL));
992 if (resource_priorities_split_group.size() == 5 &&
993 resource_priorities_split_group[1].length() == 5) {
994 // fetchIncreasePriorities
995 if (resource_priorities_split_group[1].at(3) == '1')
996 non_delayable_threshold_ = net::MEDIUM;
997 enable_in_flight_non_delayable_threshold_ =
998 resource_priorities_split_group[1].at(4) == '1';
999 size_t numeric_value = 0;
1000 if (base::StringToSizeT(resource_priorities_split_group[2], &numeric_value))
1001 in_flight_non_delayable_threshold_ = numeric_value;
1002 if (base::StringToSizeT(resource_priorities_split_group[3], &numeric_value))
1003 max_num_delayable_while_layout_blocking_ = numeric_value;
1004 if (base::StringToSizeT(resource_priorities_split_group[4], &numeric_value))
1005 max_num_delayable_requests_ = numeric_value;
1009 ResourceScheduler::~ResourceScheduler() {
1010 DCHECK(unowned_requests_.empty());
1011 DCHECK(client_map_.empty());
1014 void ResourceScheduler::SetThrottleOptionsForTesting(bool should_throttle,
1015 bool should_coalesce) {
1016 should_coalesce_ = should_coalesce;
1017 should_throttle_ = should_throttle;
1018 OnLoadingActiveClientsStateChangedForAllClients();
1021 ResourceScheduler::ClientThrottleState
1022 ResourceScheduler::GetClientStateForTesting(int child_id, int route_id) {
1023 Client* client = GetClient(child_id, route_id);
1024 DCHECK(client);
1025 return client->throttle_state();
1028 scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest(
1029 int child_id,
1030 int route_id,
1031 bool is_async,
1032 net::URLRequest* url_request) {
1033 DCHECK(CalledOnValidThread());
1034 ClientId client_id = MakeClientId(child_id, route_id);
1035 scoped_ptr<ScheduledResourceRequest> request(new ScheduledResourceRequest(
1036 client_id, url_request, this,
1037 RequestPriorityParams(url_request->priority(), 0), is_async));
1039 ClientMap::iterator it = client_map_.find(client_id);
1040 if (it == client_map_.end()) {
1041 // There are several ways this could happen:
1042 // 1. <a ping> requests don't have a route_id.
1043 // 2. Most unittests don't send the IPCs needed to register Clients.
1044 // 3. The tab is closed while a RequestResource IPC is in flight.
1045 unowned_requests_.insert(request.get());
1046 request->Start();
1047 return request.Pass();
1050 Client* client = it->second;
1051 client->ScheduleRequest(url_request, request.get());
1052 return request.Pass();
1055 void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) {
1056 DCHECK(CalledOnValidThread());
1057 if (ContainsKey(unowned_requests_, request)) {
1058 unowned_requests_.erase(request);
1059 return;
1062 ClientMap::iterator client_it = client_map_.find(request->client_id());
1063 if (client_it == client_map_.end()) {
1064 return;
1067 Client* client = client_it->second;
1068 client->RemoveRequest(request);
1071 void ResourceScheduler::OnClientCreated(int child_id,
1072 int route_id,
1073 bool is_visible,
1074 bool is_audible) {
1075 DCHECK(CalledOnValidThread());
1076 ClientId client_id = MakeClientId(child_id, route_id);
1077 DCHECK(!ContainsKey(client_map_, client_id));
1079 Client* client = new Client(this, is_visible, is_audible);
1080 client_map_[client_id] = client;
1082 client->UpdateThrottleState();
1085 void ResourceScheduler::OnClientDeleted(int child_id, int route_id) {
1086 DCHECK(CalledOnValidThread());
1087 ClientId client_id = MakeClientId(child_id, route_id);
1088 ClientMap::iterator it = client_map_.find(client_id);
1089 CHECK(it != client_map_.end());
1091 Client* client = it->second;
1092 // ResourceDispatcherHost cancels all requests except for cross-renderer
1093 // navigations, async revalidations and detachable requests after
1094 // OnClientDeleted() returns.
1095 RequestSet client_unowned_requests = client->StartAndRemoveAllRequests();
1096 for (RequestSet::iterator it = client_unowned_requests.begin();
1097 it != client_unowned_requests.end(); ++it) {
1098 unowned_requests_.insert(*it);
1101 delete client;
1102 client_map_.erase(it);
1105 void ResourceScheduler::OnLoadingStateChanged(int child_id,
1106 int route_id,
1107 bool is_loaded) {
1108 Client* client = GetClient(child_id, route_id);
1109 DCHECK(client);
1110 client->OnLoadingStateChanged(is_loaded);
1113 void ResourceScheduler::OnVisibilityChanged(int child_id,
1114 int route_id,
1115 bool is_visible) {
1116 Client* client = GetClient(child_id, route_id);
1117 DCHECK(client);
1118 client->OnVisibilityChanged(is_visible);
1121 void ResourceScheduler::OnAudibilityChanged(int child_id,
1122 int route_id,
1123 bool is_audible) {
1124 Client* client = GetClient(child_id, route_id);
1125 // We might get this call after the client has been deleted.
1126 if (client)
1127 client->OnAudibilityChanged(is_audible);
1130 void ResourceScheduler::OnNavigate(int child_id, int route_id) {
1131 DCHECK(CalledOnValidThread());
1132 ClientId client_id = MakeClientId(child_id, route_id);
1134 ClientMap::iterator it = client_map_.find(client_id);
1135 if (it == client_map_.end()) {
1136 // The client was likely deleted shortly before we received this IPC.
1137 return;
1140 Client* client = it->second;
1141 client->OnNavigate();
1144 void ResourceScheduler::OnWillInsertBody(int child_id, int route_id) {
1145 DCHECK(CalledOnValidThread());
1146 ClientId client_id = MakeClientId(child_id, route_id);
1148 ClientMap::iterator it = client_map_.find(client_id);
1149 if (it == client_map_.end()) {
1150 // The client was likely deleted shortly before we received this IPC.
1151 return;
1154 Client* client = it->second;
1155 client->OnWillInsertBody();
1158 void ResourceScheduler::OnReceivedSpdyProxiedHttpResponse(
1159 int child_id,
1160 int route_id) {
1161 DCHECK(CalledOnValidThread());
1162 ClientId client_id = MakeClientId(child_id, route_id);
1164 ClientMap::iterator client_it = client_map_.find(client_id);
1165 if (client_it == client_map_.end()) {
1166 return;
1169 Client* client = client_it->second;
1170 client->OnReceivedSpdyProxiedHttpResponse();
1173 bool ResourceScheduler::IsClientVisibleForTesting(int child_id, int route_id) {
1174 Client* client = GetClient(child_id, route_id);
1175 DCHECK(client);
1176 return client->is_visible();
1179 bool ResourceScheduler::HasLoadingClients() const {
1180 for (const auto& client : client_map_) {
1181 if (!client.second->is_loaded())
1182 return true;
1184 return false;
1187 ResourceScheduler::Client* ResourceScheduler::GetClient(int child_id,
1188 int route_id) {
1189 ClientId client_id = MakeClientId(child_id, route_id);
1190 ClientMap::iterator client_it = client_map_.find(client_id);
1191 if (client_it == client_map_.end()) {
1192 return NULL;
1194 return client_it->second;
1197 void ResourceScheduler::DecrementActiveClientsLoading() {
1198 DCHECK_NE(0u, active_clients_loading_);
1199 --active_clients_loading_;
1200 DCHECK_EQ(active_clients_loading_, CountActiveClientsLoading());
1201 if (active_clients_loading_ == 0) {
1202 OnLoadingActiveClientsStateChangedForAllClients();
1206 void ResourceScheduler::IncrementActiveClientsLoading() {
1207 ++active_clients_loading_;
1208 DCHECK_EQ(active_clients_loading_, CountActiveClientsLoading());
1209 if (active_clients_loading_ == 1) {
1210 OnLoadingActiveClientsStateChangedForAllClients();
1214 void ResourceScheduler::OnLoadingActiveClientsStateChangedForAllClients() {
1215 ClientMap::iterator client_it = client_map_.begin();
1216 while (client_it != client_map_.end()) {
1217 Client* client = client_it->second;
1218 client->UpdateThrottleState();
1219 ++client_it;
1223 size_t ResourceScheduler::CountActiveClientsLoading() const {
1224 size_t active_and_loading = 0;
1225 ClientMap::const_iterator client_it = client_map_.begin();
1226 while (client_it != client_map_.end()) {
1227 Client* client = client_it->second;
1228 if (client->throttle_state() == ACTIVE_AND_LOADING) {
1229 ++active_and_loading;
1231 ++client_it;
1233 return active_and_loading;
1236 void ResourceScheduler::IncrementCoalescedClients() {
1237 ++coalesced_clients_;
1238 DCHECK(should_coalesce_);
1239 DCHECK_EQ(coalesced_clients_, CountCoalescedClients());
1240 if (coalesced_clients_ == 1) {
1241 coalescing_timer_->Start(
1242 FROM_HERE,
1243 base::TimeDelta::FromMilliseconds(kCoalescedTimerPeriod),
1244 base::Bind(&ResourceScheduler::LoadCoalescedRequests,
1245 base::Unretained(this)));
1249 void ResourceScheduler::DecrementCoalescedClients() {
1250 DCHECK(should_coalesce_);
1251 DCHECK_NE(0U, coalesced_clients_);
1252 --coalesced_clients_;
1253 DCHECK_EQ(coalesced_clients_, CountCoalescedClients());
1254 if (coalesced_clients_ == 0) {
1255 coalescing_timer_->Stop();
1259 size_t ResourceScheduler::CountCoalescedClients() const {
1260 DCHECK(should_coalesce_);
1261 size_t coalesced_clients = 0;
1262 ClientMap::const_iterator client_it = client_map_.begin();
1263 while (client_it != client_map_.end()) {
1264 Client* client = client_it->second;
1265 if (client->throttle_state() == COALESCED) {
1266 ++coalesced_clients;
1268 ++client_it;
1270 return coalesced_clients_;
1273 void ResourceScheduler::LoadCoalescedRequests() {
1274 DCHECK(should_coalesce_);
1275 ClientMap::iterator client_it = client_map_.begin();
1276 while (client_it != client_map_.end()) {
1277 Client* client = client_it->second;
1278 client->LoadCoalescedRequests();
1279 ++client_it;
1283 ResourceScheduler::ClientState ResourceScheduler::GetClientState(
1284 ClientId client_id) const {
1285 ClientMap::const_iterator client_it = client_map_.find(client_id);
1286 if (client_it == client_map_.end())
1287 return UNKNOWN;
1288 return client_it->second->is_active() ? ACTIVE : BACKGROUND;
1291 void ResourceScheduler::ReprioritizeRequest(net::URLRequest* request,
1292 net::RequestPriority new_priority,
1293 int new_intra_priority_value) {
1294 if (request->load_flags() & net::LOAD_IGNORE_LIMITS) {
1295 // Requests with the IGNORE_LIMITS flag must stay at MAXIMUM_PRIORITY.
1296 return;
1299 auto* scheduled_resource_request =
1300 ScheduledResourceRequest::ForRequest(request);
1302 // Downloads don't use the resource scheduler.
1303 if (!scheduled_resource_request) {
1304 request->SetPriority(new_priority);
1305 return;
1308 RequestPriorityParams new_priority_params(new_priority,
1309 new_intra_priority_value);
1310 RequestPriorityParams old_priority_params =
1311 scheduled_resource_request->get_request_priority_params();
1313 if (old_priority_params == new_priority_params)
1314 return;
1316 ClientMap::iterator client_it =
1317 client_map_.find(scheduled_resource_request->client_id());
1318 if (client_it == client_map_.end()) {
1319 // The client was likely deleted shortly before we received this IPC.
1320 request->SetPriority(new_priority_params.priority);
1321 scheduled_resource_request->set_request_priority_params(
1322 new_priority_params);
1323 return;
1326 Client* client = client_it->second;
1327 client->ReprioritizeRequest(scheduled_resource_request, old_priority_params,
1328 new_priority_params);
1331 ResourceScheduler::ClientId ResourceScheduler::MakeClientId(
1332 int child_id, int route_id) {
1333 return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id;
1336 } // namespace content