Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / content / browser / loader / resource_scheduler.cc
blobe1098ebf4aa1cddb69069d50dfe31537a234f0dd
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>
8 #include <set>
10 #include "base/metrics/field_trial.h"
11 #include "base/metrics/histogram.h"
12 #include "base/stl_util.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_piece.h"
15 #include "base/supports_user_data.h"
16 #include "base/time/time.h"
17 #include "content/common/resource_messages.h"
18 #include "content/public/browser/resource_controller.h"
19 #include "content/public/browser/resource_request_info.h"
20 #include "content/public/browser/resource_throttle.h"
21 #include "net/base/host_port_pair.h"
22 #include "net/base/load_flags.h"
23 #include "net/base/request_priority.h"
24 #include "net/http/http_server_properties.h"
25 #include "net/url_request/url_request.h"
26 #include "net/url_request/url_request_context.h"
28 namespace content {
30 namespace {
32 // Field trial constants
33 const char kThrottleCoalesceFieldTrial[] = "RequestThrottlingAndCoalescing";
34 const char kThrottleCoalesceFieldTrialThrottle[] = "Throttle";
35 const char kThrottleCoalesceFieldTrialCoalesce[] = "Coalesce";
37 const char kRequestLimitFieldTrial[] = "OutstandingRequestLimiting";
38 const char kRequestLimitFieldTrialGroupPrefix[] = "Limit";
40 const char kResourcePrioritiesFieldTrial[] = "ResourcePriorities";
42 // Flags identifying various attributes of the request that are used
43 // when making scheduling decisions.
44 using RequestAttributes = uint8_t;
45 const RequestAttributes kAttributeNone = 0x00;
46 const RequestAttributes kAttributeInFlight = 0x01;
47 const RequestAttributes kAttributeDelayable = 0x02;
48 const RequestAttributes kAttributeLayoutBlocking = 0x04;
50 // Post ResourceScheduler histograms of the following forms:
51 // If |histogram_suffix| is NULL or the empty string:
52 // ResourceScheduler.base_name.histogram_name
53 // Else:
54 // ResourceScheduler.base_name.histogram_name.histogram_suffix
55 void PostHistogram(const char* base_name,
56 const char* histogram_name,
57 const char* histogram_suffix,
58 base::TimeDelta time) {
59 std::string histogram =
60 base::StringPrintf("ResourceScheduler.%s.%s", base_name, histogram_name);
61 if (histogram_suffix && histogram_suffix[0] != '\0')
62 histogram = histogram + "." + histogram_suffix;
63 base::HistogramBase* histogram_counter = base::Histogram::FactoryTimeGet(
64 histogram, base::TimeDelta::FromMilliseconds(1),
65 base::TimeDelta::FromMinutes(5), 50,
66 base::Histogram::kUmaTargetedHistogramFlag);
67 histogram_counter->AddTime(time);
70 // For use with PostHistogram to specify the correct string for histogram
71 // suffixes based on number of Clients.
72 const char* GetNumClientsString(size_t num_clients) {
73 if (num_clients == 1)
74 return "1Client";
75 else if (num_clients <= 5)
76 return "Max5Clients";
77 else if (num_clients <= 15)
78 return "Max15Clients";
79 else if (num_clients <= 30)
80 return "Max30Clients";
81 return "Over30Clients";
84 } // namespace
86 static const size_t kCoalescedTimerPeriod = 5000;
87 static const size_t kDefaultMaxNumDelayableRequestsPerClient = 10;
88 static const size_t kMaxNumDelayableRequestsPerHost = 6;
89 static const size_t kMaxNumThrottledRequestsPerClient = 1;
90 static const size_t kDefaultMaxNumDelayableWhileLayoutBlocking = 1;
91 static const net::RequestPriority
92 kDefaultLayoutBlockingPriorityThreshold = net::LOW;
94 struct ResourceScheduler::RequestPriorityParams {
95 RequestPriorityParams()
96 : priority(net::DEFAULT_PRIORITY),
97 intra_priority(0) {
100 RequestPriorityParams(net::RequestPriority priority, int intra_priority)
101 : priority(priority),
102 intra_priority(intra_priority) {
105 bool operator==(const RequestPriorityParams& other) const {
106 return (priority == other.priority) &&
107 (intra_priority == other.intra_priority);
110 bool operator!=(const RequestPriorityParams& other) const {
111 return !(*this == other);
114 bool GreaterThan(const RequestPriorityParams& other) const {
115 if (priority != other.priority)
116 return priority > other.priority;
117 return intra_priority > other.intra_priority;
120 net::RequestPriority priority;
121 int intra_priority;
124 class ResourceScheduler::RequestQueue {
125 public:
126 typedef std::multiset<ScheduledResourceRequest*, ScheduledResourceSorter>
127 NetQueue;
129 RequestQueue() : fifo_ordering_ids_(0) {}
130 ~RequestQueue() {}
132 // Adds |request| to the queue with given |priority|.
133 void Insert(ScheduledResourceRequest* request);
135 // Removes |request| from the queue.
136 void Erase(ScheduledResourceRequest* request) {
137 PointerMap::iterator it = pointers_.find(request);
138 DCHECK(it != pointers_.end());
139 if (it == pointers_.end())
140 return;
141 queue_.erase(it->second);
142 pointers_.erase(it);
145 NetQueue::iterator GetNextHighestIterator() {
146 return queue_.begin();
149 NetQueue::iterator End() {
150 return queue_.end();
153 // Returns true if |request| is queued.
154 bool IsQueued(ScheduledResourceRequest* request) const {
155 return ContainsKey(pointers_, request);
158 // Returns true if no requests are queued.
159 bool IsEmpty() const { return queue_.size() == 0; }
161 private:
162 typedef std::map<ScheduledResourceRequest*, NetQueue::iterator> PointerMap;
164 uint32 MakeFifoOrderingId() {
165 fifo_ordering_ids_ += 1;
166 return fifo_ordering_ids_;
169 // Used to create an ordering ID for scheduled resources so that resources
170 // with same priority/intra_priority stay in fifo order.
171 uint32 fifo_ordering_ids_;
173 NetQueue queue_;
174 PointerMap pointers_;
177 // This is the handle we return to the ResourceDispatcherHostImpl so it can
178 // interact with the request.
179 class ResourceScheduler::ScheduledResourceRequest : public ResourceThrottle {
180 public:
181 ScheduledResourceRequest(const ClientId& client_id,
182 net::URLRequest* request,
183 ResourceScheduler* scheduler,
184 const RequestPriorityParams& priority,
185 bool is_async)
186 : client_id_(client_id),
187 client_state_on_creation_(scheduler->GetClientState(client_id_)),
188 request_(request),
189 ready_(false),
190 deferred_(false),
191 is_async_(is_async),
192 attributes_(kAttributeNone),
193 scheduler_(scheduler),
194 priority_(priority),
195 fifo_ordering_(0) {
196 DCHECK(!request_->GetUserData(kUserDataKey));
197 request_->SetUserData(kUserDataKey, new UnownedPointer(this));
200 ~ScheduledResourceRequest() override {
201 request_->RemoveUserData(kUserDataKey);
202 scheduler_->RemoveRequest(this);
205 static ScheduledResourceRequest* ForRequest(net::URLRequest* request) {
206 return static_cast<UnownedPointer*>(request->GetUserData(kUserDataKey))
207 ->get();
210 void Start() {
211 ready_ = true;
212 if (!request_->status().is_success())
213 return;
214 base::TimeTicks time = base::TimeTicks::Now();
215 ClientState current_state = scheduler_->GetClientState(client_id_);
216 // Note: the client state isn't perfectly accurate since it won't capture
217 // tabs which have switched between active and background multiple times.
218 // Ex: A tab with the following transitions Active -> Background -> Active
219 // will be recorded as Active.
220 const char* client_state = "Other";
221 if (current_state == client_state_on_creation_ && current_state == ACTIVE) {
222 client_state = "Active";
223 } else if (current_state == client_state_on_creation_ &&
224 current_state == BACKGROUND) {
225 client_state = "Background";
228 base::TimeDelta time_was_deferred = base::TimeDelta::FromMicroseconds(0);
229 if (deferred_) {
230 deferred_ = false;
231 controller()->Resume();
232 time_was_deferred = time - time_deferred_;
234 PostHistogram("RequestTimeDeferred", client_state, NULL, time_was_deferred);
235 PostHistogram("RequestTimeThrottled", client_state, NULL,
236 time - request_->creation_time());
237 // TODO(aiolos): Remove one of the above histograms after gaining an
238 // understanding of the difference between them and which one is more
239 // interesting.
242 void set_request_priority_params(const RequestPriorityParams& priority) {
243 priority_ = priority;
245 const RequestPriorityParams& get_request_priority_params() const {
246 return priority_;
248 const ClientId& client_id() const { return client_id_; }
249 net::URLRequest* url_request() { return request_; }
250 const net::URLRequest* url_request() const { return request_; }
251 bool is_async() const { return is_async_; }
252 uint32 fifo_ordering() const { return fifo_ordering_; }
253 void set_fifo_ordering(uint32 fifo_ordering) {
254 fifo_ordering_ = fifo_ordering;
256 RequestAttributes attributes() const {
257 return attributes_;
259 void set_attributes(RequestAttributes attributes) {
260 attributes_ = attributes;
263 private:
264 class UnownedPointer : public base::SupportsUserData::Data {
265 public:
266 explicit UnownedPointer(ScheduledResourceRequest* pointer)
267 : pointer_(pointer) {}
269 ScheduledResourceRequest* get() const { return pointer_; }
271 private:
272 ScheduledResourceRequest* const pointer_;
274 DISALLOW_COPY_AND_ASSIGN(UnownedPointer);
277 static const void* const kUserDataKey;
279 // ResourceThrottle interface:
280 void WillStartRequest(bool* defer) override {
281 deferred_ = *defer = !ready_;
282 time_deferred_ = base::TimeTicks::Now();
285 const char* GetNameForLogging() const override { return "ResourceScheduler"; }
287 const ClientId client_id_;
288 const ResourceScheduler::ClientState client_state_on_creation_;
289 net::URLRequest* request_;
290 bool ready_;
291 bool deferred_;
292 bool is_async_;
293 RequestAttributes attributes_;
294 ResourceScheduler* scheduler_;
295 RequestPriorityParams priority_;
296 uint32 fifo_ordering_;
297 base::TimeTicks time_deferred_;
299 DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest);
302 const void* const ResourceScheduler::ScheduledResourceRequest::kUserDataKey =
303 &ResourceScheduler::ScheduledResourceRequest::kUserDataKey;
305 bool ResourceScheduler::ScheduledResourceSorter::operator()(
306 const ScheduledResourceRequest* a,
307 const ScheduledResourceRequest* b) const {
308 // Want the set to be ordered first by decreasing priority, then by
309 // decreasing intra_priority.
310 // ie. with (priority, intra_priority)
311 // [(1, 0), (1, 0), (0, 100), (0, 0)]
312 if (a->get_request_priority_params() != b->get_request_priority_params())
313 return a->get_request_priority_params().GreaterThan(
314 b->get_request_priority_params());
316 // If priority/intra_priority is the same, fall back to fifo ordering.
317 // std::multiset doesn't guarantee this until c++11.
318 return a->fifo_ordering() < b->fifo_ordering();
321 void ResourceScheduler::RequestQueue::Insert(
322 ScheduledResourceRequest* request) {
323 DCHECK(!ContainsKey(pointers_, request));
324 request->set_fifo_ordering(MakeFifoOrderingId());
325 pointers_[request] = queue_.insert(request);
328 // Each client represents a tab.
329 class ResourceScheduler::Client {
330 public:
331 explicit Client(ResourceScheduler* scheduler,
332 bool is_visible,
333 bool is_audible)
334 : is_audible_(is_audible),
335 is_visible_(is_visible),
336 is_loaded_(false),
337 is_paused_(false),
338 has_html_body_(false),
339 using_spdy_proxy_(false),
340 load_started_time_(base::TimeTicks::Now()),
341 scheduler_(scheduler),
342 in_flight_delayable_count_(0),
343 total_layout_blocking_count_(0),
344 throttle_state_(ResourceScheduler::THROTTLED) {}
346 ~Client() {
347 // Update to default state and pause to ensure the scheduler has a
348 // correct count of relevant types of clients.
349 is_visible_ = false;
350 is_audible_ = false;
351 is_paused_ = true;
352 UpdateThrottleState();
355 void ScheduleRequest(net::URLRequest* url_request,
356 ScheduledResourceRequest* request) {
357 SetRequestAttributes(request, DetermineRequestAttributes(request));
358 if (ShouldStartRequest(request) == START_REQUEST)
359 StartRequest(request);
360 else
361 pending_requests_.Insert(request);
364 void RemoveRequest(ScheduledResourceRequest* request) {
365 if (pending_requests_.IsQueued(request)) {
366 pending_requests_.Erase(request);
367 DCHECK(!ContainsKey(in_flight_requests_, request));
368 } else {
369 EraseInFlightRequest(request);
371 // Removing this request may have freed up another to load.
372 LoadAnyStartablePendingRequests();
376 RequestSet StartAndRemoveAllRequests() {
377 // First start any pending requests so that they will be moved into
378 // in_flight_requests_. This may exceed the limits
379 // kDefaultMaxNumDelayableRequestsPerClient, kMaxNumDelayableRequestsPerHost
380 // and kMaxNumThrottledRequestsPerClient, so this method must not do
381 // anything that depends on those limits before calling
382 // ClearInFlightRequests() below.
383 while (!pending_requests_.IsEmpty()) {
384 ScheduledResourceRequest* request =
385 *pending_requests_.GetNextHighestIterator();
386 pending_requests_.Erase(request);
387 // StartRequest() may modify pending_requests_. TODO(ricea): Does it?
388 StartRequest(request);
390 RequestSet unowned_requests;
391 for (RequestSet::iterator it = in_flight_requests_.begin();
392 it != in_flight_requests_.end(); ++it) {
393 unowned_requests.insert(*it);
394 (*it)->set_attributes(kAttributeNone);
396 ClearInFlightRequests();
397 return unowned_requests;
400 bool is_active() const { return is_visible_ || is_audible_; }
402 bool is_loaded() const { return is_loaded_; }
404 bool is_visible() const { return is_visible_; }
406 void OnAudibilityChanged(bool is_audible) {
407 UpdateState(is_audible, &is_audible_);
410 void OnVisibilityChanged(bool is_visible) {
411 UpdateState(is_visible, &is_visible_);
414 // Function to update any client state variable used to determine whether a
415 // Client is active or background. Used for is_visible_ and is_audible_.
416 void UpdateState(bool new_state, bool* current_state) {
417 bool was_active = is_active();
418 *current_state = new_state;
419 if (was_active == is_active())
420 return;
421 last_active_switch_time_ = base::TimeTicks::Now();
422 UpdateThrottleState();
425 void OnLoadingStateChanged(bool is_loaded) {
426 if (is_loaded == is_loaded_) {
427 return;
429 is_loaded_ = is_loaded;
430 UpdateThrottleState();
431 if (!is_loaded_) {
432 load_started_time_ = base::TimeTicks::Now();
433 last_active_switch_time_ = base::TimeTicks();
434 return;
436 base::TimeTicks cur_time = base::TimeTicks::Now();
437 const char* num_clients =
438 GetNumClientsString(scheduler_->client_map_.size());
439 const char* client_catagory = "Other";
440 if (last_active_switch_time_.is_null()) {
441 client_catagory = is_active() ? "Active" : "Background";
442 } else if (is_active()) {
443 base::TimeDelta time_since_active = cur_time - last_active_switch_time_;
444 PostHistogram("ClientLoadedTime", "Other.SwitchedToActive", NULL,
445 time_since_active);
446 PostHistogram("ClientLoadedTime", "Other.SwitchedToActive", num_clients,
447 time_since_active);
449 base::TimeDelta time_since_load_started = cur_time - load_started_time_;
450 PostHistogram("ClientLoadedTime", client_catagory, NULL,
451 time_since_load_started);
452 PostHistogram("ClientLoadedTime", client_catagory, num_clients,
453 time_since_load_started);
454 // TODO(aiolos): The above histograms will not take main resource load time
455 // into account with PlzNavigate into account. The ResourceScheduler also
456 // will load the main resources without a clients with the current logic.
457 // Find a way to fix both of these issues.
460 void SetPaused() {
461 is_paused_ = true;
462 UpdateThrottleState();
465 void UpdateThrottleState() {
466 ClientThrottleState old_throttle_state = throttle_state_;
467 if (!scheduler_->should_throttle()) {
468 SetThrottleState(UNTHROTTLED);
469 } else if (is_active() && !is_loaded_) {
470 SetThrottleState(ACTIVE_AND_LOADING);
471 } else if (is_active()) {
472 SetThrottleState(UNTHROTTLED);
473 } else if (is_paused_) {
474 SetThrottleState(PAUSED);
475 } else if (!scheduler_->active_clients_loaded()) {
476 SetThrottleState(THROTTLED);
477 } else if (is_loaded_ && scheduler_->should_coalesce()) {
478 SetThrottleState(COALESCED);
479 } else if (!is_active()) {
480 SetThrottleState(UNTHROTTLED);
483 if (throttle_state_ == old_throttle_state) {
484 return;
486 if (throttle_state_ == ACTIVE_AND_LOADING) {
487 scheduler_->IncrementActiveClientsLoading();
488 } else if (old_throttle_state == ACTIVE_AND_LOADING) {
489 scheduler_->DecrementActiveClientsLoading();
491 if (throttle_state_ == COALESCED) {
492 scheduler_->IncrementCoalescedClients();
493 } else if (old_throttle_state == COALESCED) {
494 scheduler_->DecrementCoalescedClients();
498 void OnNavigate() {
499 has_html_body_ = false;
500 is_loaded_ = false;
503 void OnWillInsertBody() {
504 has_html_body_ = true;
505 LoadAnyStartablePendingRequests();
508 void OnReceivedSpdyProxiedHttpResponse() {
509 if (!using_spdy_proxy_) {
510 using_spdy_proxy_ = true;
511 LoadAnyStartablePendingRequests();
515 void ReprioritizeRequest(ScheduledResourceRequest* request,
516 RequestPriorityParams old_priority_params,
517 RequestPriorityParams new_priority_params) {
518 request->url_request()->SetPriority(new_priority_params.priority);
519 request->set_request_priority_params(new_priority_params);
520 SetRequestAttributes(request, DetermineRequestAttributes(request));
521 if (!pending_requests_.IsQueued(request)) {
522 DCHECK(ContainsKey(in_flight_requests_, request));
523 // Request has already started.
524 return;
527 pending_requests_.Erase(request);
528 pending_requests_.Insert(request);
530 if (new_priority_params.priority > old_priority_params.priority) {
531 // Check if this request is now able to load at its new priority.
532 LoadAnyStartablePendingRequests();
536 // Called on Client creation, when a Client changes user observability,
537 // possibly when all observable Clients have finished loading, and
538 // possibly when this Client has finished loading.
539 // State changes:
540 // Client became observable.
541 // any state -> UNTHROTTLED
542 // Client is unobservable, but all observable clients finished loading.
543 // THROTTLED -> UNTHROTTLED
544 // Non-observable client finished loading.
545 // THROTTLED || UNTHROTTLED -> COALESCED
546 // Non-observable client, an observable client starts loading.
547 // COALESCED -> THROTTLED
548 // A COALESCED client will transition into UNTHROTTLED when the network is
549 // woken up by a heartbeat and then transition back into COALESCED.
550 void SetThrottleState(ResourceScheduler::ClientThrottleState throttle_state) {
551 if (throttle_state == throttle_state_) {
552 return;
554 throttle_state_ = throttle_state;
555 if (throttle_state_ != PAUSED) {
556 is_paused_ = false;
558 LoadAnyStartablePendingRequests();
559 // TODO(aiolos): Stop any started but not inflght requests when
560 // switching to stricter throttle state?
563 ResourceScheduler::ClientThrottleState throttle_state() const {
564 return throttle_state_;
567 void LoadCoalescedRequests() {
568 if (throttle_state_ != COALESCED) {
569 return;
571 if (scheduler_->active_clients_loaded()) {
572 SetThrottleState(UNTHROTTLED);
573 } else {
574 SetThrottleState(THROTTLED);
576 LoadAnyStartablePendingRequests();
577 SetThrottleState(COALESCED);
580 private:
581 enum ShouldStartReqResult {
582 DO_NOT_START_REQUEST_AND_STOP_SEARCHING,
583 DO_NOT_START_REQUEST_AND_KEEP_SEARCHING,
584 START_REQUEST,
587 void InsertInFlightRequest(ScheduledResourceRequest* request) {
588 in_flight_requests_.insert(request);
589 SetRequestAttributes(request, DetermineRequestAttributes(request));
592 void EraseInFlightRequest(ScheduledResourceRequest* request) {
593 size_t erased = in_flight_requests_.erase(request);
594 DCHECK_EQ(1u, erased);
595 // Clear any special state that we were tracking for this request.
596 SetRequestAttributes(request, kAttributeNone);
599 void ClearInFlightRequests() {
600 in_flight_requests_.clear();
601 in_flight_delayable_count_ = 0;
602 total_layout_blocking_count_ = 0;
605 size_t CountRequestsWithAttributes(
606 const RequestAttributes attributes,
607 ScheduledResourceRequest* current_request) {
608 size_t matching_request_count = 0;
609 for (RequestSet::const_iterator it = in_flight_requests_.begin();
610 it != in_flight_requests_.end(); ++it) {
611 if (RequestAttributesAreSet((*it)->attributes(), attributes))
612 matching_request_count++;
614 if (!RequestAttributesAreSet(attributes, kAttributeInFlight)) {
615 bool current_request_is_pending = false;
616 for (RequestQueue::NetQueue::const_iterator
617 it = pending_requests_.GetNextHighestIterator();
618 it != pending_requests_.End(); ++it) {
619 if (RequestAttributesAreSet((*it)->attributes(), attributes))
620 matching_request_count++;
621 if (*it == current_request)
622 current_request_is_pending = true;
624 // Account for the current request if it is not in one of the lists yet.
625 if (current_request &&
626 !ContainsKey(in_flight_requests_, current_request) &&
627 !current_request_is_pending) {
628 if (RequestAttributesAreSet(current_request->attributes(), attributes))
629 matching_request_count++;
632 return matching_request_count;
635 bool RequestAttributesAreSet(RequestAttributes request_attributes,
636 RequestAttributes matching_attributes) const {
637 return (request_attributes & matching_attributes) == matching_attributes;
640 void SetRequestAttributes(ScheduledResourceRequest* request,
641 RequestAttributes attributes) {
642 RequestAttributes old_attributes = request->attributes();
643 if (old_attributes == attributes)
644 return;
646 if (RequestAttributesAreSet(old_attributes,
647 kAttributeInFlight | kAttributeDelayable)) {
648 in_flight_delayable_count_--;
650 if (RequestAttributesAreSet(old_attributes, kAttributeLayoutBlocking))
651 total_layout_blocking_count_--;
653 if (RequestAttributesAreSet(attributes,
654 kAttributeInFlight | kAttributeDelayable)) {
655 in_flight_delayable_count_++;
657 if (RequestAttributesAreSet(attributes, kAttributeLayoutBlocking))
658 total_layout_blocking_count_++;
660 request->set_attributes(attributes);
661 DCHECK_EQ(CountRequestsWithAttributes(
662 kAttributeInFlight | kAttributeDelayable, request),
663 in_flight_delayable_count_);
664 DCHECK_EQ(CountRequestsWithAttributes(kAttributeLayoutBlocking, request),
665 total_layout_blocking_count_);
668 RequestAttributes DetermineRequestAttributes(
669 ScheduledResourceRequest* request) {
670 RequestAttributes attributes = kAttributeNone;
672 if (ContainsKey(in_flight_requests_, request))
673 attributes |= kAttributeInFlight;
675 if (RequestAttributesAreSet(request->attributes(),
676 kAttributeLayoutBlocking)) {
677 // If a request is already marked as layout-blocking make sure to keep the
678 // attribute across redirects.
679 attributes |= kAttributeLayoutBlocking;
680 } else if (!has_html_body_ &&
681 request->url_request()->priority() >
682 scheduler_->non_delayable_threshold()) {
683 // Requests that are above the non_delayable threshold before the HTML
684 // body has been parsed are inferred to be layout-blocking.
685 attributes |= kAttributeLayoutBlocking;
686 } else if (request->url_request()->priority() <
687 scheduler_->non_delayable_threshold()) {
688 // Resources below the non_delayable_threshold that are being requested
689 // from a server that does not support native prioritization are
690 // considered delayable.
691 net::HostPortPair host_port_pair =
692 net::HostPortPair::FromURL(request->url_request()->url());
693 net::HttpServerProperties& http_server_properties =
694 *request->url_request()->context()->http_server_properties();
695 if (!http_server_properties.SupportsRequestPriority(host_port_pair))
696 attributes |= kAttributeDelayable;
699 return attributes;
702 bool ShouldKeepSearching(
703 const net::HostPortPair& active_request_host) const {
704 size_t same_host_count = 0;
705 for (RequestSet::const_iterator it = in_flight_requests_.begin();
706 it != in_flight_requests_.end(); ++it) {
707 net::HostPortPair host_port_pair =
708 net::HostPortPair::FromURL((*it)->url_request()->url());
709 if (active_request_host.Equals(host_port_pair)) {
710 same_host_count++;
711 if (same_host_count >= kMaxNumDelayableRequestsPerHost)
712 return true;
715 return false;
718 void StartRequest(ScheduledResourceRequest* request) {
719 InsertInFlightRequest(request);
720 request->Start();
723 // ShouldStartRequest is the main scheduling algorithm.
725 // Requests are evaluated on five attributes:
727 // 1. Non-delayable requests:
728 // * Synchronous requests.
729 // * Non-HTTP[S] requests.
731 // 2. Requests to request-priority-capable origin servers.
733 // 3. High-priority requests:
734 // * Higher priority requests (>= net::LOW).
736 // 4. Layout-blocking requests:
737 // * High-priority requests (> net::LOW) initiated before the renderer has
738 // a <body>.
740 // 5. Low priority requests
742 // The following rules are followed:
744 // All types of requests:
745 // * If an outstanding request limit is in place, only that number
746 // of requests may be in flight for a single client at the same time.
748 // ACTIVE_AND_LOADING and UNTHROTTLED Clients follow these rules:
749 // * Non-delayable, High-priority and request-priority capable requests are
750 // issued immediately.
751 // * Low priority requests are delayable.
752 // * While layout-blocking requests are loading or the body tag has not
753 // yet been parsed, limit the number of delayable requests that may be
754 // in flight (to 1 by default, or to zero if there's an outstanding
755 // request limit in place).
756 // * If no high priority or layout-blocking requests are in flight, start
757 // loading delayable requests.
758 // * Never exceed 10 delayable requests in flight per client.
759 // * Never exceed 6 delayable requests for a given host.
761 // THROTTLED Clients follow these rules:
762 // * Non-delayable and request-priority-capable requests are issued
763 // immediately.
764 // * At most one non-request-priority-capable request will be issued per
765 // THROTTLED Client
766 // * If no high priority requests are in flight, start loading low priority
767 // requests.
769 // COALESCED Clients never load requests, with the following exceptions:
770 // * Non-delayable requests are issued imediately.
771 // * On a (currently 5 second) heart beat, they load all requests as an
772 // UNTHROTTLED Client, and then return to the COALESCED state.
773 // * When an active Client makes a request, they are THROTTLED until the
774 // active Client finishes loading.
775 ShouldStartReqResult ShouldStartRequest(
776 ScheduledResourceRequest* request) const {
777 const net::URLRequest& url_request = *request->url_request();
778 // Syncronous requests could block the entire render, which could impact
779 // user-observable Clients.
780 if (!request->is_async())
781 return START_REQUEST;
783 // TODO(simonjam): This may end up causing disk contention. We should
784 // experiment with throttling if that happens.
785 // TODO(aiolos): We probably want to Coalesce these as well to avoid
786 // waking the disk.
787 if (!url_request.url().SchemeIsHTTPOrHTTPS())
788 return START_REQUEST;
790 if (throttle_state_ == COALESCED)
791 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
793 if (using_spdy_proxy_ && url_request.url().SchemeIs(url::kHttpScheme))
794 return START_REQUEST;
796 // Implementation of the kRequestLimitFieldTrial.
797 if (scheduler_->limit_outstanding_requests() &&
798 in_flight_requests_.size() >= scheduler_->outstanding_request_limit()) {
799 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
802 net::HostPortPair host_port_pair =
803 net::HostPortPair::FromURL(url_request.url());
804 net::HttpServerProperties& http_server_properties =
805 *url_request.context()->http_server_properties();
807 // TODO(willchan): We should really improve this algorithm as described in
808 // crbug.com/164101. Also, theoretically we should not count a
809 // request-priority capable request against the delayable requests limit.
810 if (http_server_properties.SupportsRequestPriority(host_port_pair))
811 return START_REQUEST;
813 if (throttle_state_ == THROTTLED &&
814 in_flight_requests_.size() >= kMaxNumThrottledRequestsPerClient) {
815 // There may still be request-priority-capable requests that should be
816 // issued.
817 return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING;
820 // Non-delayable requests.
821 if (!RequestAttributesAreSet(request->attributes(), kAttributeDelayable))
822 return START_REQUEST;
824 if (in_flight_delayable_count_ >=
825 scheduler_->max_num_delayable_requests()) {
826 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
829 if (ShouldKeepSearching(host_port_pair)) {
830 // There may be other requests for other hosts that may be allowed,
831 // so keep checking.
832 return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING;
835 // The in-flight requests consist of layout-blocking requests,
836 // normal requests and delayable requests. Everything except for
837 // delayable requests is handled above here so this is deciding what to
838 // do with a delayable request while we are in the layout-blocking phase
839 // of loading.
840 if (!has_html_body_ || total_layout_blocking_count_ != 0) {
841 size_t non_delayable_requests_in_flight_count =
842 in_flight_requests_.size() - in_flight_delayable_count_;
843 if (scheduler_->enable_in_flight_non_delayable_threshold()) {
844 if (non_delayable_requests_in_flight_count >
845 scheduler_->in_flight_non_delayable_threshold()) {
846 // Too many higher priority in-flight requests to allow lower priority
847 // requests through.
848 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
850 if (in_flight_requests_.size() > 0 &&
851 (scheduler_->limit_outstanding_requests() ||
852 in_flight_delayable_count_ >=
853 scheduler_->max_num_delayable_while_layout_blocking())) {
854 // Block the request if at least one request is in flight and the
855 // number of in-flight delayable requests has hit the configured
856 // limit.
857 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
859 } else if (non_delayable_requests_in_flight_count > 0 &&
860 (scheduler_->limit_outstanding_requests() ||
861 in_flight_delayable_count_ >=
862 scheduler_->max_num_delayable_while_layout_blocking())) {
863 // If there are no high-priority requests in flight the floodgates open.
864 // If there are high-priority requests in-flight then limit the number
865 // of lower-priority requests (or zero if a limit field trial is
866 // active).
867 return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
871 return START_REQUEST;
874 void LoadAnyStartablePendingRequests() {
875 // We iterate through all the pending requests, starting with the highest
876 // priority one. For each entry, one of three things can happen:
877 // 1) We start the request, remove it from the list, and keep checking.
878 // 2) We do NOT start the request, but ShouldStartRequest() signals us that
879 // there may be room for other requests, so we keep checking and leave
880 // the previous request still in the list.
881 // 3) We do not start the request, same as above, but StartRequest() tells
882 // us there's no point in checking any further requests.
883 RequestQueue::NetQueue::iterator request_iter =
884 pending_requests_.GetNextHighestIterator();
886 while (request_iter != pending_requests_.End()) {
887 ScheduledResourceRequest* request = *request_iter;
888 ShouldStartReqResult query_result = ShouldStartRequest(request);
890 if (query_result == START_REQUEST) {
891 pending_requests_.Erase(request);
892 StartRequest(request);
894 // StartRequest can modify the pending list, so we (re)start evaluation
895 // from the currently highest priority request. Avoid copying a singular
896 // iterator, which would trigger undefined behavior.
897 if (pending_requests_.GetNextHighestIterator() ==
898 pending_requests_.End())
899 break;
900 request_iter = pending_requests_.GetNextHighestIterator();
901 } else if (query_result == DO_NOT_START_REQUEST_AND_KEEP_SEARCHING) {
902 ++request_iter;
903 continue;
904 } else {
905 DCHECK(query_result == DO_NOT_START_REQUEST_AND_STOP_SEARCHING);
906 break;
911 bool is_audible_;
912 bool is_visible_;
913 bool is_loaded_;
914 bool is_paused_;
915 // Tracks if the main HTML parser has reached the body which marks the end of
916 // layout-blocking resources.
917 bool has_html_body_;
918 bool using_spdy_proxy_;
919 RequestQueue pending_requests_;
920 RequestSet in_flight_requests_;
921 base::TimeTicks load_started_time_;
922 // The last time the client switched state between active and background.
923 base::TimeTicks last_active_switch_time_;
924 ResourceScheduler* scheduler_;
925 // The number of delayable in-flight requests.
926 size_t in_flight_delayable_count_;
927 // The number of layout-blocking in-flight requests.
928 size_t total_layout_blocking_count_;
929 ResourceScheduler::ClientThrottleState throttle_state_;
932 ResourceScheduler::ResourceScheduler()
933 : should_coalesce_(false),
934 should_throttle_(false),
935 active_clients_loading_(0),
936 coalesced_clients_(0),
937 limit_outstanding_requests_(false),
938 outstanding_request_limit_(0),
939 non_delayable_threshold_(
940 kDefaultLayoutBlockingPriorityThreshold),
941 enable_in_flight_non_delayable_threshold_(false),
942 in_flight_non_delayable_threshold_(0),
943 max_num_delayable_while_layout_blocking_(
944 kDefaultMaxNumDelayableWhileLayoutBlocking),
945 max_num_delayable_requests_(kDefaultMaxNumDelayableRequestsPerClient),
946 coalescing_timer_(new base::Timer(true /* retain_user_task */,
947 true /* is_repeating */)) {
948 std::string throttling_trial_group =
949 base::FieldTrialList::FindFullName(kThrottleCoalesceFieldTrial);
950 if (throttling_trial_group == kThrottleCoalesceFieldTrialThrottle) {
951 should_throttle_ = true;
952 } else if (throttling_trial_group == kThrottleCoalesceFieldTrialCoalesce) {
953 should_coalesce_ = true;
954 should_throttle_ = true;
957 std::string outstanding_limit_trial_group =
958 base::FieldTrialList::FindFullName(kRequestLimitFieldTrial);
959 std::vector<std::string> split_group(
960 base::SplitString(outstanding_limit_trial_group, "=",
961 base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL));
962 int outstanding_limit = 0;
963 if (split_group.size() == 2 &&
964 split_group[0] == kRequestLimitFieldTrialGroupPrefix &&
965 base::StringToInt(split_group[1], &outstanding_limit) &&
966 outstanding_limit > 0) {
967 limit_outstanding_requests_ = true;
968 outstanding_request_limit_ = outstanding_limit;
971 // Set up the ResourceScheduling field trial options.
972 // The field trial parameters are also encoded into the group name since
973 // the variations component is not available from here and plumbing the
974 // options through the code is overkill for a short experiment.
976 // The group name encoding looks like this:
977 // <descriptiveName>_ABCDE_E2_F_G
978 // A - fetchDeferLateScripts (1 for true, 0 for false)
979 // B - fetchIncreaseFontPriority (1 for true, 0 for false)
980 // C - fetchIncreaseAsyncScriptPriority (1 for true, 0 for false)
981 // D - fetchIncreasePriorities (1 for true, 0 for false)
982 // E - fetchEnableLayoutBlockingThreshold (1 for true, 0 for false)
983 // E2 - fetchLayoutBlockingThreshold (Numeric)
984 // F - fetchMaxNumDelayableWhileLayoutBlocking (Numeric)
985 // G - fetchMaxNumDelayableRequests (Numeric)
986 std::string resource_priorities_trial_group =
987 base::FieldTrialList::FindFullName(kResourcePrioritiesFieldTrial);
988 std::vector<std::string> resource_priorities_split_group(
989 base::SplitString(resource_priorities_trial_group, "_",
990 base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL));
991 if (resource_priorities_split_group.size() == 5 &&
992 resource_priorities_split_group[1].length() == 5) {
993 // fetchIncreasePriorities
994 if (resource_priorities_split_group[1].at(3) == '1')
995 non_delayable_threshold_ = net::MEDIUM;
996 enable_in_flight_non_delayable_threshold_ =
997 resource_priorities_split_group[1].at(4) == '1';
998 size_t numeric_value = 0;
999 if (base::StringToSizeT(resource_priorities_split_group[2], &numeric_value))
1000 in_flight_non_delayable_threshold_ = numeric_value;
1001 if (base::StringToSizeT(resource_priorities_split_group[3], &numeric_value))
1002 max_num_delayable_while_layout_blocking_ = numeric_value;
1003 if (base::StringToSizeT(resource_priorities_split_group[4], &numeric_value))
1004 max_num_delayable_requests_ = numeric_value;
1008 ResourceScheduler::~ResourceScheduler() {
1009 DCHECK(unowned_requests_.empty());
1010 DCHECK(client_map_.empty());
1013 void ResourceScheduler::SetThrottleOptionsForTesting(bool should_throttle,
1014 bool should_coalesce) {
1015 should_coalesce_ = should_coalesce;
1016 should_throttle_ = should_throttle;
1017 OnLoadingActiveClientsStateChangedForAllClients();
1020 ResourceScheduler::ClientThrottleState
1021 ResourceScheduler::GetClientStateForTesting(int child_id, int route_id) {
1022 Client* client = GetClient(child_id, route_id);
1023 DCHECK(client);
1024 return client->throttle_state();
1027 scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest(
1028 int child_id,
1029 int route_id,
1030 bool is_async,
1031 net::URLRequest* url_request) {
1032 DCHECK(CalledOnValidThread());
1033 ClientId client_id = MakeClientId(child_id, route_id);
1034 scoped_ptr<ScheduledResourceRequest> request(new ScheduledResourceRequest(
1035 client_id, url_request, this,
1036 RequestPriorityParams(url_request->priority(), 0), is_async));
1038 ClientMap::iterator it = client_map_.find(client_id);
1039 if (it == client_map_.end()) {
1040 // There are several ways this could happen:
1041 // 1. <a ping> requests don't have a route_id.
1042 // 2. Most unittests don't send the IPCs needed to register Clients.
1043 // 3. The tab is closed while a RequestResource IPC is in flight.
1044 unowned_requests_.insert(request.get());
1045 request->Start();
1046 return request.Pass();
1049 Client* client = it->second;
1050 client->ScheduleRequest(url_request, request.get());
1051 return request.Pass();
1054 void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) {
1055 DCHECK(CalledOnValidThread());
1056 if (ContainsKey(unowned_requests_, request)) {
1057 unowned_requests_.erase(request);
1058 return;
1061 ClientMap::iterator client_it = client_map_.find(request->client_id());
1062 if (client_it == client_map_.end()) {
1063 return;
1066 Client* client = client_it->second;
1067 client->RemoveRequest(request);
1070 void ResourceScheduler::OnClientCreated(int child_id,
1071 int route_id,
1072 bool is_visible,
1073 bool is_audible) {
1074 DCHECK(CalledOnValidThread());
1075 ClientId client_id = MakeClientId(child_id, route_id);
1076 DCHECK(!ContainsKey(client_map_, client_id));
1078 Client* client = new Client(this, is_visible, is_audible);
1079 client_map_[client_id] = client;
1081 client->UpdateThrottleState();
1084 void ResourceScheduler::OnClientDeleted(int child_id, int route_id) {
1085 DCHECK(CalledOnValidThread());
1086 ClientId client_id = MakeClientId(child_id, route_id);
1087 DCHECK(ContainsKey(client_map_, client_id));
1088 ClientMap::iterator it = client_map_.find(client_id);
1089 if (it == client_map_.end())
1090 return;
1092 Client* client = it->second;
1093 // ResourceDispatcherHost cancels all requests except for cross-renderer
1094 // navigations, async revalidations and detachable requests after
1095 // OnClientDeleted() returns.
1096 RequestSet client_unowned_requests = client->StartAndRemoveAllRequests();
1097 for (RequestSet::iterator it = client_unowned_requests.begin();
1098 it != client_unowned_requests.end(); ++it) {
1099 unowned_requests_.insert(*it);
1102 delete client;
1103 client_map_.erase(it);
1106 void ResourceScheduler::OnLoadingStateChanged(int child_id,
1107 int route_id,
1108 bool is_loaded) {
1109 Client* client = GetClient(child_id, route_id);
1110 DCHECK(client);
1111 client->OnLoadingStateChanged(is_loaded);
1114 void ResourceScheduler::OnVisibilityChanged(int child_id,
1115 int route_id,
1116 bool is_visible) {
1117 Client* client = GetClient(child_id, route_id);
1118 DCHECK(client);
1119 client->OnVisibilityChanged(is_visible);
1122 void ResourceScheduler::OnAudibilityChanged(int child_id,
1123 int route_id,
1124 bool is_audible) {
1125 Client* client = GetClient(child_id, route_id);
1126 // We might get this call after the client has been deleted.
1127 if (client)
1128 client->OnAudibilityChanged(is_audible);
1131 void ResourceScheduler::OnNavigate(int child_id, int route_id) {
1132 DCHECK(CalledOnValidThread());
1133 ClientId client_id = MakeClientId(child_id, route_id);
1135 ClientMap::iterator it = client_map_.find(client_id);
1136 if (it == client_map_.end()) {
1137 // The client was likely deleted shortly before we received this IPC.
1138 return;
1141 Client* client = it->second;
1142 client->OnNavigate();
1145 void ResourceScheduler::OnWillInsertBody(int child_id, int route_id) {
1146 DCHECK(CalledOnValidThread());
1147 ClientId client_id = MakeClientId(child_id, route_id);
1149 ClientMap::iterator it = client_map_.find(client_id);
1150 if (it == client_map_.end()) {
1151 // The client was likely deleted shortly before we received this IPC.
1152 return;
1155 Client* client = it->second;
1156 client->OnWillInsertBody();
1159 void ResourceScheduler::OnReceivedSpdyProxiedHttpResponse(
1160 int child_id,
1161 int route_id) {
1162 DCHECK(CalledOnValidThread());
1163 ClientId client_id = MakeClientId(child_id, route_id);
1165 ClientMap::iterator client_it = client_map_.find(client_id);
1166 if (client_it == client_map_.end()) {
1167 return;
1170 Client* client = client_it->second;
1171 client->OnReceivedSpdyProxiedHttpResponse();
1174 bool ResourceScheduler::IsClientVisibleForTesting(int child_id, int route_id) {
1175 Client* client = GetClient(child_id, route_id);
1176 DCHECK(client);
1177 return client->is_visible();
1180 bool ResourceScheduler::HasLoadingClients() const {
1181 for (const auto& client : client_map_) {
1182 if (!client.second->is_loaded())
1183 return true;
1185 return false;
1188 ResourceScheduler::Client* ResourceScheduler::GetClient(int child_id,
1189 int route_id) {
1190 ClientId client_id = MakeClientId(child_id, route_id);
1191 ClientMap::iterator client_it = client_map_.find(client_id);
1192 if (client_it == client_map_.end()) {
1193 return NULL;
1195 return client_it->second;
1198 void ResourceScheduler::DecrementActiveClientsLoading() {
1199 DCHECK_NE(0u, active_clients_loading_);
1200 --active_clients_loading_;
1201 DCHECK_EQ(active_clients_loading_, CountActiveClientsLoading());
1202 if (active_clients_loading_ == 0) {
1203 OnLoadingActiveClientsStateChangedForAllClients();
1207 void ResourceScheduler::IncrementActiveClientsLoading() {
1208 ++active_clients_loading_;
1209 DCHECK_EQ(active_clients_loading_, CountActiveClientsLoading());
1210 if (active_clients_loading_ == 1) {
1211 OnLoadingActiveClientsStateChangedForAllClients();
1215 void ResourceScheduler::OnLoadingActiveClientsStateChangedForAllClients() {
1216 ClientMap::iterator client_it = client_map_.begin();
1217 while (client_it != client_map_.end()) {
1218 Client* client = client_it->second;
1219 client->UpdateThrottleState();
1220 ++client_it;
1224 size_t ResourceScheduler::CountActiveClientsLoading() const {
1225 size_t active_and_loading = 0;
1226 ClientMap::const_iterator client_it = client_map_.begin();
1227 while (client_it != client_map_.end()) {
1228 Client* client = client_it->second;
1229 if (client->throttle_state() == ACTIVE_AND_LOADING) {
1230 ++active_and_loading;
1232 ++client_it;
1234 return active_and_loading;
1237 void ResourceScheduler::IncrementCoalescedClients() {
1238 ++coalesced_clients_;
1239 DCHECK(should_coalesce_);
1240 DCHECK_EQ(coalesced_clients_, CountCoalescedClients());
1241 if (coalesced_clients_ == 1) {
1242 coalescing_timer_->Start(
1243 FROM_HERE,
1244 base::TimeDelta::FromMilliseconds(kCoalescedTimerPeriod),
1245 base::Bind(&ResourceScheduler::LoadCoalescedRequests,
1246 base::Unretained(this)));
1250 void ResourceScheduler::DecrementCoalescedClients() {
1251 DCHECK(should_coalesce_);
1252 DCHECK_NE(0U, coalesced_clients_);
1253 --coalesced_clients_;
1254 DCHECK_EQ(coalesced_clients_, CountCoalescedClients());
1255 if (coalesced_clients_ == 0) {
1256 coalescing_timer_->Stop();
1260 size_t ResourceScheduler::CountCoalescedClients() const {
1261 DCHECK(should_coalesce_);
1262 size_t coalesced_clients = 0;
1263 ClientMap::const_iterator client_it = client_map_.begin();
1264 while (client_it != client_map_.end()) {
1265 Client* client = client_it->second;
1266 if (client->throttle_state() == COALESCED) {
1267 ++coalesced_clients;
1269 ++client_it;
1271 return coalesced_clients_;
1274 void ResourceScheduler::LoadCoalescedRequests() {
1275 DCHECK(should_coalesce_);
1276 ClientMap::iterator client_it = client_map_.begin();
1277 while (client_it != client_map_.end()) {
1278 Client* client = client_it->second;
1279 client->LoadCoalescedRequests();
1280 ++client_it;
1284 ResourceScheduler::ClientState ResourceScheduler::GetClientState(
1285 ClientId client_id) const {
1286 ClientMap::const_iterator client_it = client_map_.find(client_id);
1287 if (client_it == client_map_.end())
1288 return UNKNOWN;
1289 return client_it->second->is_active() ? ACTIVE : BACKGROUND;
1292 void ResourceScheduler::ReprioritizeRequest(net::URLRequest* request,
1293 net::RequestPriority new_priority,
1294 int new_intra_priority_value) {
1295 if (request->load_flags() & net::LOAD_IGNORE_LIMITS) {
1296 // We should not be re-prioritizing requests with the
1297 // IGNORE_LIMITS flag.
1298 NOTREACHED();
1299 return;
1302 auto* scheduled_resource_request =
1303 ScheduledResourceRequest::ForRequest(request);
1305 // Downloads don't use the resource scheduler.
1306 if (!scheduled_resource_request) {
1307 request->SetPriority(new_priority);
1308 return;
1311 RequestPriorityParams new_priority_params(new_priority,
1312 new_intra_priority_value);
1313 RequestPriorityParams old_priority_params =
1314 scheduled_resource_request->get_request_priority_params();
1316 DCHECK(old_priority_params != new_priority_params);
1318 ClientMap::iterator client_it =
1319 client_map_.find(scheduled_resource_request->client_id());
1320 if (client_it == client_map_.end()) {
1321 // The client was likely deleted shortly before we received this IPC.
1322 request->SetPriority(new_priority_params.priority);
1323 scheduled_resource_request->set_request_priority_params(
1324 new_priority_params);
1325 return;
1328 if (old_priority_params == new_priority_params)
1329 return;
1331 Client *client = client_it->second;
1332 client->ReprioritizeRequest(scheduled_resource_request, old_priority_params,
1333 new_priority_params);
1336 ResourceScheduler::ClientId ResourceScheduler::MakeClientId(
1337 int child_id, int route_id) {
1338 return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id;
1341 } // namespace content