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"
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"
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
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
) {
78 else if (num_clients
<= 5)
80 else if (num_clients
<= 15)
81 return "Max15Clients";
82 else if (num_clients
<= 30)
83 return "Max30Clients";
84 return "Over30Clients";
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
),
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
;
127 class ResourceScheduler::RequestQueue
{
129 typedef std::multiset
<ScheduledResourceRequest
*, ScheduledResourceSorter
>
132 RequestQueue() : fifo_ordering_ids_(0) {}
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
);
146 NetQueue::iterator
GetNextHighestIterator() {
147 return queue_
.begin();
150 NetQueue::iterator
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; }
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_
;
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
{
182 ScheduledResourceRequest(const ClientId
& client_id
,
183 net::URLRequest
* request
,
184 ResourceScheduler
* scheduler
,
185 const RequestPriorityParams
& priority
,
187 : client_id_(client_id
),
188 client_state_on_creation_(scheduler
->GetClientState(client_id_
)),
193 attributes_(kAttributeNone
),
194 scheduler_(scheduler
),
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
))
213 if (!request_
->status().is_success())
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);
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
243 void set_request_priority_params(const RequestPriorityParams
& priority
) {
244 priority_
= priority
;
246 const RequestPriorityParams
& get_request_priority_params() const {
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 {
260 void set_attributes(RequestAttributes attributes
) {
261 attributes_
= attributes
;
265 class UnownedPointer
: public base::SupportsUserData::Data
{
267 explicit UnownedPointer(ScheduledResourceRequest
* pointer
)
268 : pointer_(pointer
) {}
270 ScheduledResourceRequest
* get() const { return pointer_
; }
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_
;
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
{
332 explicit Client(ResourceScheduler
* scheduler
,
335 : is_audible_(is_audible
),
336 is_visible_(is_visible
),
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
) {}
348 // Update to default state and pause to ensure the scheduler has a
349 // correct count of relevant types of clients.
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
);
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
));
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())
422 last_active_switch_time_
= base::TimeTicks::Now();
423 UpdateThrottleState();
426 void OnLoadingStateChanged(bool is_loaded
) {
427 if (is_loaded
== is_loaded_
) {
430 is_loaded_
= is_loaded
;
431 UpdateThrottleState();
433 load_started_time_
= base::TimeTicks::Now();
434 last_active_switch_time_
= base::TimeTicks();
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
,
447 PostHistogram("ClientLoadedTime", "Other.SwitchedToActive", num_clients
,
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.
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
) {
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();
500 has_html_body_
= 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.
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.
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_
) {
555 throttle_state_
= throttle_state
;
556 if (throttle_state_
!= PAUSED
) {
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
) {
572 if (scheduler_
->active_clients_loaded()) {
573 SetThrottleState(UNTHROTTLED
);
575 SetThrottleState(THROTTLED
);
577 LoadAnyStartablePendingRequests();
578 SetThrottleState(COALESCED
);
582 enum ShouldStartReqResult
{
583 DO_NOT_START_REQUEST_AND_STOP_SEARCHING
,
584 DO_NOT_START_REQUEST_AND_KEEP_SEARCHING
,
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
)
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
;
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
)) {
712 if (same_host_count
>= kMaxNumDelayableRequestsPerHost
)
719 void StartRequest(ScheduledResourceRequest
* request
) {
720 InsertInFlightRequest(request
);
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
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
765 // * At most one non-request-priority-capable request will be issued per
767 // * If no high priority requests are in flight, start loading low priority
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
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
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,
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
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
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
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
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())
901 request_iter
= pending_requests_
.GetNextHighestIterator();
902 } else if (query_result
== DO_NOT_START_REQUEST_AND_KEEP_SEARCHING
) {
906 DCHECK(query_result
== DO_NOT_START_REQUEST_AND_STOP_SEARCHING
);
916 // Tracks if the main HTML parser has reached the body which marks the end of
917 // layout-blocking resources.
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
);
1025 return client
->throttle_state();
1028 scoped_ptr
<ResourceThrottle
> ResourceScheduler::ScheduleRequest(
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());
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
);
1062 ClientMap::iterator client_it
= client_map_
.find(request
->client_id());
1063 if (client_it
== client_map_
.end()) {
1067 Client
* client
= client_it
->second
;
1068 client
->RemoveRequest(request
);
1071 void ResourceScheduler::OnClientCreated(int child_id
,
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
);
1102 client_map_
.erase(it
);
1105 void ResourceScheduler::OnLoadingStateChanged(int child_id
,
1108 Client
* client
= GetClient(child_id
, route_id
);
1110 client
->OnLoadingStateChanged(is_loaded
);
1113 void ResourceScheduler::OnVisibilityChanged(int child_id
,
1116 Client
* client
= GetClient(child_id
, route_id
);
1118 client
->OnVisibilityChanged(is_visible
);
1121 void ResourceScheduler::OnAudibilityChanged(int child_id
,
1124 Client
* client
= GetClient(child_id
, route_id
);
1125 // We might get this call after the client has been deleted.
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.
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.
1154 Client
* client
= it
->second
;
1155 client
->OnWillInsertBody();
1158 void ResourceScheduler::OnReceivedSpdyProxiedHttpResponse(
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()) {
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
);
1176 return client
->is_visible();
1179 bool ResourceScheduler::HasLoadingClients() const {
1180 for (const auto& client
: client_map_
) {
1181 if (!client
.second
->is_loaded())
1187 ResourceScheduler::Client
* ResourceScheduler::GetClient(int child_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()) {
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();
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
;
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(
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
;
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();
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())
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.
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
);
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
)
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
);
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