| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/loader/resource_scheduler.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/field_trial_param_associator.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/histogram_tester.h" |
| #include "base/test/mock_entropy_provider.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/timer/mock_timer.h" |
| #include "base/timer/timer.h" |
| #include "content/public/browser/resource_context.h" |
| #include "content/public/browser/resource_throttle.h" |
| #include "content/public/test/mock_render_process_host.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "content/test/test_render_view_host_factory.h" |
| #include "content/test/test_web_contents.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/request_priority.h" |
| #include "net/http/http_server_properties_impl.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/latency/latency_info.h" |
| #include "url/scheme_host_port.h" |
| |
| using std::string; |
| |
| namespace content { |
| |
| namespace { |
| |
| class TestRequestFactory; |
| |
| const int kChildId = 30; |
| const int kRouteId = 75; |
| const int kChildId2 = 43; |
| const int kRouteId2 = 67; |
| const int kBackgroundChildId = 35; |
| const int kBackgroundRouteId = 43; |
| |
| const char kPrioritySupportedRequestsDelayable[] = |
| "PrioritySupportedRequestsDelayable"; |
| |
| const char kNetworkSchedulerYielding[] = "NetworkSchedulerYielding"; |
| const int kMaxRequestsBeforeYielding = 5; // sync with .cc. |
| |
| class TestRequest : public ResourceThrottle::Delegate { |
| public: |
| TestRequest(std::unique_ptr<net::URLRequest> url_request, |
| std::unique_ptr<ResourceThrottle> throttle, |
| ResourceScheduler* scheduler) |
| : started_(false), |
| url_request_(std::move(url_request)), |
| throttle_(std::move(throttle)), |
| scheduler_(scheduler) { |
| throttle_->set_delegate_for_testing(this); |
| } |
| ~TestRequest() override { |
| // The URLRequest must still be valid when the ScheduledResourceRequest is |
| // destroyed, so that it can unregister itself. |
| throttle_.reset(); |
| } |
| |
| bool started() const { return started_; } |
| |
| void Start() { |
| bool deferred = false; |
| throttle_->WillStartRequest(&deferred); |
| started_ = !deferred; |
| } |
| |
| void ChangePriority(net::RequestPriority new_priority, int intra_priority) { |
| scheduler_->ReprioritizeRequest(url_request_.get(), new_priority, |
| intra_priority); |
| } |
| |
| void Cancel() override { |
| // Alert the scheduler that the request can be deleted. |
| throttle_.reset(); |
| } |
| |
| const net::URLRequest* url_request() const { return url_request_.get(); } |
| |
| protected: |
| // ResourceThrottle::Delegate interface: |
| void CancelAndIgnore() override {} |
| void CancelWithError(int error_code) override {} |
| void Resume() override { started_ = true; } |
| |
| private: |
| bool started_; |
| std::unique_ptr<net::URLRequest> url_request_; |
| std::unique_ptr<ResourceThrottle> throttle_; |
| ResourceScheduler* scheduler_; |
| }; |
| |
| class CancelingTestRequest : public TestRequest { |
| public: |
| CancelingTestRequest(std::unique_ptr<net::URLRequest> url_request, |
| std::unique_ptr<ResourceThrottle> throttle, |
| ResourceScheduler* scheduler) |
| : TestRequest(std::move(url_request), std::move(throttle), scheduler) {} |
| |
| void set_request_to_cancel(std::unique_ptr<TestRequest> request_to_cancel) { |
| request_to_cancel_ = std::move(request_to_cancel); |
| } |
| |
| private: |
| void Resume() override { |
| TestRequest::Resume(); |
| request_to_cancel_.reset(); |
| } |
| |
| std::unique_ptr<TestRequest> request_to_cancel_; |
| }; |
| |
| class FakeResourceContext : public ResourceContext { |
| private: |
| net::HostResolver* GetHostResolver() override { return NULL; } |
| net::URLRequestContext* GetRequestContext() override { return NULL; } |
| }; |
| |
| class ResourceSchedulerTest : public testing::Test { |
| protected: |
| ResourceSchedulerTest() { |
| InitializeScheduler(); |
| context_.set_http_server_properties(&http_server_properties_); |
| } |
| |
| ~ResourceSchedulerTest() override { |
| CleanupScheduler(); |
| } |
| |
| // Done separately from construction to allow for modification of command |
| // line flags in tests. |
| void InitializeScheduler() { |
| CleanupScheduler(); |
| |
| // Destroys previous scheduler, also destroys any previously created |
| // mock_timer_. |
| scheduler_.reset(new ResourceScheduler()); |
| |
| scheduler_->OnClientCreated(kChildId, kRouteId); |
| scheduler_->OnClientCreated( |
| kBackgroundChildId, kBackgroundRouteId); |
| } |
| |
| void CleanupScheduler() { |
| if (scheduler_) { |
| scheduler_->OnClientDeleted(kChildId, kRouteId); |
| scheduler_->OnClientDeleted(kBackgroundChildId, kBackgroundRouteId); |
| } |
| } |
| |
| std::unique_ptr<net::URLRequest> NewURLRequestWithChildAndRoute( |
| const char* url, |
| net::RequestPriority priority, |
| int child_id, |
| int route_id) { |
| std::unique_ptr<net::URLRequest> url_request(context_.CreateRequest( |
| GURL(url), priority, NULL, TRAFFIC_ANNOTATION_FOR_TESTS)); |
| return url_request; |
| } |
| |
| std::unique_ptr<net::URLRequest> NewURLRequest( |
| const char* url, |
| net::RequestPriority priority) { |
| return NewURLRequestWithChildAndRoute(url, priority, kChildId, kRouteId); |
| } |
| |
| std::unique_ptr<TestRequest> NewRequestWithRoute( |
| const char* url, |
| net::RequestPriority priority, |
| int route_id) { |
| return NewRequestWithChildAndRoute(url, priority, kChildId, route_id); |
| } |
| |
| std::unique_ptr<TestRequest> NewRequestWithChildAndRoute( |
| const char* url, |
| net::RequestPriority priority, |
| int child_id, |
| int route_id) { |
| return GetNewTestRequest(url, priority, child_id, route_id, true); |
| } |
| |
| std::unique_ptr<TestRequest> NewRequest(const char* url, |
| net::RequestPriority priority) { |
| return NewRequestWithChildAndRoute(url, priority, kChildId, kRouteId); |
| } |
| |
| std::unique_ptr<TestRequest> NewBackgroundRequest( |
| const char* url, |
| net::RequestPriority priority) { |
| return NewRequestWithChildAndRoute( |
| url, priority, kBackgroundChildId, kBackgroundRouteId); |
| } |
| |
| std::unique_ptr<TestRequest> NewSyncRequest(const char* url, |
| net::RequestPriority priority) { |
| return NewSyncRequestWithChildAndRoute(url, priority, kChildId, kRouteId); |
| } |
| |
| std::unique_ptr<TestRequest> NewBackgroundSyncRequest( |
| const char* url, |
| net::RequestPriority priority) { |
| return NewSyncRequestWithChildAndRoute( |
| url, priority, kBackgroundChildId, kBackgroundRouteId); |
| } |
| |
| std::unique_ptr<TestRequest> NewSyncRequestWithChildAndRoute( |
| const char* url, |
| net::RequestPriority priority, |
| int child_id, |
| int route_id) { |
| return GetNewTestRequest(url, priority, child_id, route_id, false); |
| } |
| |
| std::unique_ptr<TestRequest> GetNewTestRequest(const char* url, |
| net::RequestPriority priority, |
| int child_id, |
| int route_id, |
| bool is_async) { |
| std::unique_ptr<net::URLRequest> url_request( |
| NewURLRequestWithChildAndRoute(url, priority, child_id, route_id)); |
| std::unique_ptr<ResourceThrottle> throttle(scheduler_->ScheduleRequest( |
| child_id, route_id, is_async, url_request.get())); |
| auto request = base::MakeUnique<TestRequest>( |
| std::move(url_request), std::move(throttle), scheduler()); |
| request->Start(); |
| return request; |
| } |
| |
| void ChangeRequestPriority(TestRequest* request, |
| net::RequestPriority new_priority, |
| int intra_priority = 0) { |
| request->ChangePriority(new_priority, intra_priority); |
| } |
| |
| void FireCoalescingTimer() { |
| EXPECT_TRUE(mock_timer_->IsRunning()); |
| mock_timer_->Fire(); |
| } |
| |
| ResourceScheduler* scheduler() { |
| return scheduler_.get(); |
| } |
| |
| TestBrowserThreadBundle thread_bundle_; |
| std::unique_ptr<ResourceScheduler> scheduler_; |
| base::MockTimer* mock_timer_; |
| net::HttpServerPropertiesImpl http_server_properties_; |
| net::TestURLRequestContext context_; |
| }; |
| |
| TEST_F(ResourceSchedulerTest, OneIsolatedLowRequest) { |
| std::unique_ptr<TestRequest> request( |
| NewRequest("http://host/1", net::LOWEST)); |
| EXPECT_TRUE(request->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, OneLowLoadsUntilBodyInserted) { |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| std::unique_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); |
| EXPECT_TRUE(high->started()); |
| EXPECT_TRUE(low->started()); |
| EXPECT_FALSE(low2->started()); |
| |
| high.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(low2->started()); |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(low2->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, OneLowLoadsUntilCriticalComplete) { |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| std::unique_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); |
| EXPECT_TRUE(high->started()); |
| EXPECT_TRUE(low->started()); |
| EXPECT_FALSE(low2->started()); |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(low2->started()); |
| |
| high.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(low2->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, MediumDoesNotBlockCriticalComplete) { |
| // kLayoutBlockingPriorityThreshold determines what priority level above which |
| // requests are considered layout-blocking and must be completed before the |
| // critical loading period is complete. It is currently set to net::MEDIUM. |
| std::unique_ptr<TestRequest> medium( |
| NewRequest("http://host/low", net::MEDIUM)); |
| std::unique_ptr<TestRequest> lowest( |
| NewRequest("http://host/lowest", net::LOWEST)); |
| std::unique_ptr<TestRequest> lowest2( |
| NewRequest("http://host/lowest", net::LOWEST)); |
| EXPECT_TRUE(medium->started()); |
| EXPECT_TRUE(lowest->started()); |
| EXPECT_FALSE(lowest2->started()); |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(lowest2->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, SchedulerYieldsWithFeatureEnabled) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitFromCommandLine(kNetworkSchedulerYielding, ""); |
| InitializeScheduler(); |
| |
| // Use spdy so that we don't throttle. |
| http_server_properties_.SetSupportsSpdy( |
| url::SchemeHostPort("https", "spdyhost", 443), true); |
| |
| // Add enough async requests that the last one should yield. |
| std::vector<std::unique_ptr<TestRequest>> requests; |
| for (int i = 0; i < kMaxRequestsBeforeYielding + 1; ++i) |
| requests.push_back(NewRequest("http://host/higher", net::HIGHEST)); |
| |
| // Verify that the number of requests before yielding started. |
| for (int i = 0; i < kMaxRequestsBeforeYielding; ++i) |
| EXPECT_TRUE(requests[i]->started()); |
| |
| // The next async request should have yielded. |
| EXPECT_FALSE(requests[kMaxRequestsBeforeYielding]->started()); |
| |
| // Verify that with time the yielded request eventually runs. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(requests[kMaxRequestsBeforeYielding]->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, SchedulerDoesNotYieldForSyncRequests) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitFromCommandLine(kNetworkSchedulerYielding, ""); |
| InitializeScheduler(); |
| |
| // Use spdy so that we don't throttle. |
| http_server_properties_.SetSupportsSpdy( |
| url::SchemeHostPort("https", "spdyhost", 443), true); |
| |
| // Add enough async requests that the last one should yield. |
| std::vector<std::unique_ptr<TestRequest>> requests; |
| for (int i = 0; i < kMaxRequestsBeforeYielding + 1; ++i) |
| requests.push_back(NewRequest("http://host/higher", net::HIGHEST)); |
| |
| // Add a sync requests. |
| requests.push_back(NewSyncRequest("http://host/higher", net::HIGHEST)); |
| |
| // Verify that the number of requests before yielding started. |
| for (int i = 0; i < kMaxRequestsBeforeYielding; ++i) |
| EXPECT_TRUE(requests[i]->started()); |
| |
| // The next async request should have yielded. |
| EXPECT_FALSE(requests[kMaxRequestsBeforeYielding]->started()); |
| |
| // The next sync request should have started even though async is yielding. |
| EXPECT_TRUE(requests[kMaxRequestsBeforeYielding + 1]->started()); |
| |
| // Verify that with time the yielded request eventually runs. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(requests[kMaxRequestsBeforeYielding]->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, SchedulerDoesNotYieldForAlternativeSchemes) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitFromCommandLine(kNetworkSchedulerYielding, ""); |
| InitializeScheduler(); |
| |
| // Use spdy so that we don't throttle. |
| http_server_properties_.SetSupportsSpdy( |
| url::SchemeHostPort("https", "spdyhost", 443), true); |
| |
| // Add enough async requests that the last one should yield. |
| std::vector<std::unique_ptr<TestRequest>> requests; |
| for (int i = 0; i < kMaxRequestsBeforeYielding + 1; ++i) |
| requests.push_back(NewRequest("http://host/higher", net::HIGHEST)); |
| |
| // Add a non-http request. |
| requests.push_back(NewRequest("zzz://host/higher", net::HIGHEST)); |
| |
| // Verify that the number of requests before yielding started. |
| for (int i = 0; i < kMaxRequestsBeforeYielding; ++i) |
| EXPECT_TRUE(requests[i]->started()); |
| |
| // The next async request should have yielded. |
| EXPECT_FALSE(requests[kMaxRequestsBeforeYielding]->started()); |
| |
| // The non-http(s) request should have started even though async is |
| // yielding. |
| EXPECT_TRUE(requests[kMaxRequestsBeforeYielding + 1]->started()); |
| |
| // Verify that with time the yielded request eventually runs. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(requests[kMaxRequestsBeforeYielding]->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, SchedulerDoesNotYieldWithFeatureDisabled) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitFromCommandLine("", kNetworkSchedulerYielding); |
| InitializeScheduler(); |
| |
| // Use spdy so that we don't throttle. |
| http_server_properties_.SetSupportsSpdy( |
| url::SchemeHostPort("https", "spdyhost", 443), true); |
| |
| // Add enough async requests that the last one would yield if yielding were |
| // enabled. |
| std::vector<std::unique_ptr<TestRequest>> requests; |
| for (int i = 0; i < kMaxRequestsBeforeYielding + 1; ++i) |
| requests.push_back(NewRequest("http://host/higher", net::HIGHEST)); |
| |
| // Verify that none of the requests yield. |
| for (int i = 0; i < kMaxRequestsBeforeYielding + 1; ++i) |
| EXPECT_TRUE(requests[i]->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, OneLowLoadsUntilBodyInsertedExceptSpdy) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitFromCommandLine("", |
| kPrioritySupportedRequestsDelayable); |
| InitializeScheduler(); |
| |
| http_server_properties_.SetSupportsSpdy( |
| url::SchemeHostPort("https", "spdyhost", 443), true); |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| std::unique_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); |
| std::unique_ptr<TestRequest> low_spdy( |
| NewRequest("https://spdyhost/low", net::LOWEST)); |
| EXPECT_TRUE(high->started()); |
| EXPECT_TRUE(low->started()); |
| EXPECT_FALSE(low2->started()); |
| EXPECT_TRUE(low_spdy->started()); |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| high.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(low2->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, |
| OneLowLoadsUntilBodyInsertedEvenSpdyWhenDelayable) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitFromCommandLine(kPrioritySupportedRequestsDelayable, |
| ""); |
| |
| InitializeScheduler(); |
| http_server_properties_.SetSupportsSpdy( |
| url::SchemeHostPort("https", "spdyhost", 443), true); |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| std::unique_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); |
| std::unique_ptr<TestRequest> low_spdy( |
| NewRequest("https://spdyhost/low", net::LOWEST)); |
| EXPECT_TRUE(high->started()); |
| EXPECT_TRUE(low->started()); |
| EXPECT_FALSE(low2->started()); |
| EXPECT_FALSE(low_spdy->started()); |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| high.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(low2->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, SpdyLowBlocksOtherLowUntilBodyInserted) { |
| http_server_properties_.SetSupportsSpdy( |
| url::SchemeHostPort("https", "spdyhost", 443), true); |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low_spdy( |
| NewRequest("https://spdyhost/low", net::LOWEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| EXPECT_TRUE(high->started()); |
| EXPECT_TRUE(low_spdy->started()); |
| EXPECT_FALSE(low->started()); |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| high.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(low->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, NavigationResetsState) { |
| base::HistogramTester histogram_tester; |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| scheduler()->OnNavigate(kChildId, kRouteId); |
| |
| { |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low( |
| NewRequest("http://host/low", net::LOWEST)); |
| std::unique_ptr<TestRequest> low2( |
| NewRequest("http://host/low", net::LOWEST)); |
| EXPECT_TRUE(high->started()); |
| EXPECT_TRUE(low->started()); |
| EXPECT_FALSE(low2->started()); |
| } |
| |
| histogram_tester.ExpectTotalCount("ResourceScheduler.RequestsCount.All", 2); |
| EXPECT_THAT( |
| histogram_tester.GetAllSamples("ResourceScheduler.RequestsCount.All"), |
| testing::ElementsAre(base::Bucket(1, 1), base::Bucket(2, 1))); |
| |
| histogram_tester.ExpectTotalCount("ResourceScheduler.RequestsCount.Delayable", |
| 2); |
| histogram_tester.ExpectTotalCount( |
| "ResourceScheduler.RequestsCount.NonDelayable", 2); |
| histogram_tester.ExpectTotalCount( |
| "ResourceScheduler.RequestsCount.TotalLayoutBlocking", 2); |
| |
| histogram_tester.ExpectUniqueSample( |
| "ResourceScheduler.PeakDelayableRequestsInFlight.LayoutBlocking", 1, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ResourceScheduler.PeakDelayableRequestsInFlight.NonDelayable", 1, 1); |
| } |
| |
| TEST_F(ResourceSchedulerTest, BackgroundRequestStartsImmediately) { |
| const int route_id = 0; // Indicates a background request. |
| std::unique_ptr<TestRequest> request( |
| NewRequestWithRoute("http://host/1", net::LOWEST, route_id)); |
| EXPECT_TRUE(request->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, MoreThanOneHighRequestBlocksDelayableRequests) { |
| // If there are more than kInFlightNonDelayableRequestCountThreshold (=1) |
| // high-priority / non-delayable requests, block all low priority fetches and |
| // allow them through one at a time once the number of high priority requests |
| // drops. |
| std::unique_ptr<TestRequest> high1( |
| NewRequest("http://host/high1", net::HIGHEST)); |
| std::unique_ptr<TestRequest> high2( |
| NewRequest("http://host/high2", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| std::unique_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); |
| EXPECT_TRUE(high1->started()); |
| EXPECT_TRUE(high2->started()); |
| EXPECT_FALSE(low->started()); |
| EXPECT_FALSE(low2->started()); |
| |
| high1.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(low->started()); |
| EXPECT_FALSE(low2->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, CancelOtherRequestsWhileResuming) { |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low1( |
| NewRequest("http://host/low1", net::LOWEST)); |
| |
| std::unique_ptr<net::URLRequest> url_request( |
| NewURLRequest("http://host/low2", net::LOWEST)); |
| std::unique_ptr<ResourceThrottle> throttle(scheduler()->ScheduleRequest( |
| kChildId, kRouteId, true, url_request.get())); |
| std::unique_ptr<CancelingTestRequest> low2(new CancelingTestRequest( |
| std::move(url_request), std::move(throttle), scheduler())); |
| low2->Start(); |
| |
| std::unique_ptr<TestRequest> low3( |
| NewRequest("http://host/low3", net::LOWEST)); |
| low2->set_request_to_cancel(std::move(low3)); |
| std::unique_ptr<TestRequest> low4( |
| NewRequest("http://host/low4", net::LOWEST)); |
| |
| EXPECT_TRUE(high->started()); |
| EXPECT_FALSE(low2->started()); |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| high.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(low1->started()); |
| EXPECT_TRUE(low2->started()); |
| EXPECT_TRUE(low4->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, LimitedNumberOfDelayableRequestsInFlight) { |
| // The yielding feature will sometimes yield requests before they get a |
| // chance to start, which conflicts this test. So disable the feature. |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitFromCommandLine("", kNetworkSchedulerYielding); |
| |
| // We only load low priority resources if there's a body. |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| |
| // Throw in one high priority request to make sure that's not a factor. |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| EXPECT_TRUE(high->started()); |
| |
| const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc. |
| const int kMaxNumDelayableRequestsPerHost = 6; |
| std::vector<std::unique_ptr<TestRequest>> lows_singlehost; |
| // Queue up to the per-host limit (we subtract the current high-pri request). |
| for (int i = 0; i < kMaxNumDelayableRequestsPerHost - 1; ++i) { |
| string url = "http://host/low" + base::IntToString(i); |
| lows_singlehost.push_back(NewRequest(url.c_str(), net::LOWEST)); |
| EXPECT_TRUE(lows_singlehost[i]->started()); |
| } |
| |
| std::unique_ptr<TestRequest> second_last_singlehost( |
| NewRequest("http://host/last", net::LOWEST)); |
| std::unique_ptr<TestRequest> last_singlehost( |
| NewRequest("http://host/s_last", net::LOWEST)); |
| |
| EXPECT_FALSE(second_last_singlehost->started()); |
| |
| high.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(second_last_singlehost->started()); |
| EXPECT_FALSE(last_singlehost->started()); |
| |
| lows_singlehost.erase(lows_singlehost.begin()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(last_singlehost->started()); |
| |
| // Queue more requests from different hosts until we reach the total limit. |
| int expected_slots_left = |
| kMaxNumDelayableRequestsPerClient - kMaxNumDelayableRequestsPerHost; |
| EXPECT_GT(expected_slots_left, 0); |
| std::vector<std::unique_ptr<TestRequest>> lows_different_host; |
| base::RunLoop().RunUntilIdle(); |
| for (int i = 0; i < expected_slots_left; ++i) { |
| string url = "http://host" + base::IntToString(i) + "/low"; |
| lows_different_host.push_back(NewRequest(url.c_str(), net::LOWEST)); |
| EXPECT_TRUE(lows_different_host[i]->started()); |
| } |
| |
| std::unique_ptr<TestRequest> last_different_host( |
| NewRequest("http://host_new/last", net::LOWEST)); |
| EXPECT_FALSE(last_different_host->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, RaisePriorityAndStart) { |
| // Dummies to enforce scheduling. |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/req", net::LOWEST)); |
| |
| std::unique_ptr<TestRequest> request( |
| NewRequest("http://host/req", net::LOWEST)); |
| EXPECT_FALSE(request->started()); |
| |
| ChangeRequestPriority(request.get(), net::HIGHEST); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(request->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, RaisePriorityInQueue) { |
| // Dummies to enforce scheduling. |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| |
| std::unique_ptr<TestRequest> request( |
| NewRequest("http://host/req", net::IDLE)); |
| std::unique_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE)); |
| EXPECT_FALSE(request->started()); |
| EXPECT_FALSE(idle->started()); |
| |
| ChangeRequestPriority(request.get(), net::LOWEST); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(request->started()); |
| EXPECT_FALSE(idle->started()); |
| |
| const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc. |
| std::vector<std::unique_ptr<TestRequest>> lows; |
| for (int i = 0; i < kMaxNumDelayableRequestsPerClient - 1; ++i) { |
| string url = "http://host/low" + base::IntToString(i); |
| lows.push_back(NewRequest(url.c_str(), net::LOWEST)); |
| } |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| high.reset(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(request->started()); |
| EXPECT_FALSE(idle->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, LowerPriority) { |
| // Dummies to enforce scheduling. |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| |
| std::unique_ptr<TestRequest> request( |
| NewRequest("http://host/req", net::LOWEST)); |
| std::unique_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE)); |
| EXPECT_FALSE(request->started()); |
| EXPECT_FALSE(idle->started()); |
| |
| ChangeRequestPriority(request.get(), net::IDLE); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(request->started()); |
| EXPECT_FALSE(idle->started()); |
| |
| const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc. |
| // 2 fewer filler requests: 1 for the "low" dummy at the start, and 1 for the |
| // one at the end, which will be tested. |
| const int kNumFillerRequests = kMaxNumDelayableRequestsPerClient - 2; |
| std::vector<std::unique_ptr<TestRequest>> lows; |
| for (int i = 0; i < kNumFillerRequests; ++i) { |
| string url = "http://host" + base::IntToString(i) + "/low"; |
| lows.push_back(NewRequest(url.c_str(), net::LOWEST)); |
| } |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| high.reset(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(request->started()); |
| EXPECT_TRUE(idle->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, ReprioritizedRequestGoesToBackOfQueue) { |
| // Dummies to enforce scheduling. |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| |
| std::unique_ptr<TestRequest> request( |
| NewRequest("http://host/req", net::LOWEST)); |
| std::unique_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE)); |
| EXPECT_FALSE(request->started()); |
| EXPECT_FALSE(idle->started()); |
| |
| const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc. |
| std::vector<std::unique_ptr<TestRequest>> lows; |
| for (int i = 0; i < kMaxNumDelayableRequestsPerClient; ++i) { |
| string url = "http://host/low" + base::IntToString(i); |
| lows.push_back(NewRequest(url.c_str(), net::LOWEST)); |
| } |
| |
| ChangeRequestPriority(request.get(), net::IDLE); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(request->started()); |
| EXPECT_FALSE(idle->started()); |
| |
| ChangeRequestPriority(request.get(), net::LOWEST); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(request->started()); |
| EXPECT_FALSE(idle->started()); |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(request->started()); |
| EXPECT_FALSE(idle->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, HigherIntraPriorityGoesToFrontOfQueue) { |
| // Dummies to enforce scheduling. |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| |
| const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc. |
| std::vector<std::unique_ptr<TestRequest>> lows; |
| for (int i = 0; i < kMaxNumDelayableRequestsPerClient; ++i) { |
| string url = "http://host/low" + base::IntToString(i); |
| lows.push_back(NewRequest(url.c_str(), net::IDLE)); |
| } |
| |
| std::unique_ptr<TestRequest> request( |
| NewRequest("http://host/req", net::IDLE)); |
| EXPECT_FALSE(request->started()); |
| |
| ChangeRequestPriority(request.get(), net::IDLE, 1); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(request->started()); |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| high.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(request->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, NonHTTPSchedulesImmediately) { |
| // Dummies to enforce scheduling. |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| |
| std::unique_ptr<TestRequest> request( |
| NewRequest("chrome-extension://req", net::LOWEST)); |
| EXPECT_TRUE(request->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, SpdyProxySchedulesImmediately) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitFromCommandLine("", |
| kPrioritySupportedRequestsDelayable); |
| InitializeScheduler(); |
| |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| |
| std::unique_ptr<TestRequest> request( |
| NewRequest("http://host/req", net::IDLE)); |
| EXPECT_FALSE(request->started()); |
| |
| scheduler()->OnReceivedSpdyProxiedHttpResponse(kChildId, kRouteId); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(request->started()); |
| |
| std::unique_ptr<TestRequest> after( |
| NewRequest("http://host/after", net::IDLE)); |
| EXPECT_TRUE(after->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, SpdyProxyDelayable) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitFromCommandLine(kPrioritySupportedRequestsDelayable, |
| ""); |
| InitializeScheduler(); |
| |
| std::unique_ptr<TestRequest> high( |
| NewRequest("http://host/high", net::HIGHEST)); |
| std::unique_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); |
| |
| std::unique_ptr<TestRequest> request( |
| NewRequest("http://host/req", net::IDLE)); |
| EXPECT_FALSE(request->started()); |
| |
| scheduler()->OnReceivedSpdyProxiedHttpResponse(kChildId, kRouteId); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(request->started()); |
| |
| std::unique_ptr<TestRequest> after( |
| NewRequest("http://host/after", net::IDLE)); |
| EXPECT_FALSE(after->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, NewSpdyHostInDelayableRequests) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitFromCommandLine("", |
| kPrioritySupportedRequestsDelayable); |
| InitializeScheduler(); |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc. |
| |
| std::unique_ptr<TestRequest> low1_spdy( |
| NewRequest("http://spdyhost1:8080/low", net::LOWEST)); |
| // Cancel a request after we learn the server supports SPDY. |
| std::vector<std::unique_ptr<TestRequest>> lows; |
| for (int i = 0; i < kMaxNumDelayableRequestsPerClient - 1; ++i) { |
| string url = "http://host" + base::IntToString(i) + "/low"; |
| lows.push_back(NewRequest(url.c_str(), net::LOWEST)); |
| } |
| std::unique_ptr<TestRequest> low1(NewRequest("http://host/low", net::LOWEST)); |
| EXPECT_FALSE(low1->started()); |
| http_server_properties_.SetSupportsSpdy( |
| url::SchemeHostPort("http", "spdyhost1", 8080), true); |
| low1_spdy.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(low1->started()); |
| |
| low1.reset(); |
| base::RunLoop().RunUntilIdle(); |
| std::unique_ptr<TestRequest> low2_spdy( |
| NewRequest("http://spdyhost2:8080/low", net::IDLE)); |
| // Reprioritize a request after we learn the server supports SPDY. |
| EXPECT_TRUE(low2_spdy->started()); |
| http_server_properties_.SetSupportsSpdy( |
| url::SchemeHostPort("http", "spdyhost2", 8080), true); |
| ChangeRequestPriority(low2_spdy.get(), net::LOWEST); |
| base::RunLoop().RunUntilIdle(); |
| std::unique_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); |
| EXPECT_TRUE(low2->started()); |
| } |
| |
| TEST_F(ResourceSchedulerTest, NewDelayableSpdyHostInDelayableRequests) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitFromCommandLine(kPrioritySupportedRequestsDelayable, |
| ""); |
| InitializeScheduler(); |
| |
| scheduler()->OnWillInsertBody(kChildId, kRouteId); |
| const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc. |
| |
| std::unique_ptr<TestRequest> low1_spdy( |
| NewRequest("http://spdyhost1:8080/low", net::LOWEST)); |
| // Cancel a request after we learn the server supports SPDY. |
| std::vector<std::unique_ptr<TestRequest>> lows; |
| for (int i = 0; i < kMaxNumDelayableRequestsPerClient - 1; ++i) { |
| string url = "http://host" + base::IntToString(i) + "/low"; |
| lows.push_back(NewRequest(url.c_str(), net::LOWEST)); |
| } |
| std::unique_ptr<TestRequest> low1(NewRequest("http://host/low", net::LOWEST)); |
| EXPECT_FALSE(low1->started()); |
| http_server_properties_.SetSupportsSpdy( |
| url::SchemeHostPort("http", "spdyhost1", 8080), true); |
| low1_spdy.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(low1->started()); |
| |
| low1.reset(); |
| base::RunLoop().RunUntilIdle(); |
| std::unique_ptr<TestRequest> low2_spdy( |
| NewRequest("http://spdyhost2:8080/low", net::IDLE)); |
| // Reprioritize a request after we learn the server supports SPDY. |
| EXPECT_TRUE(low2_spdy->started()); |
| http_server_properties_.SetSupportsSpdy( |
| url::SchemeHostPort("http", "spdyhost2", 8080), true); |
| ChangeRequestPriority(low2_spdy.get(), net::LOWEST); |
| base::RunLoop().RunUntilIdle(); |
| std::unique_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); |
| EXPECT_FALSE(low2->started()); |
| } |
| |
| // Async revalidations which are not started when the tab is closed must be |
| // started at some point, or they will hang around forever and prevent other |
| // async revalidations to the same URL from being issued. |
| TEST_F(ResourceSchedulerTest, RequestStartedAfterClientDeleted) { |
| scheduler_->OnClientCreated(kChildId2, kRouteId2); |
| std::unique_ptr<TestRequest> high(NewRequestWithChildAndRoute( |
| "http://host/high", net::HIGHEST, kChildId2, kRouteId2)); |
| std::unique_ptr<TestRequest> lowest1(NewRequestWithChildAndRoute( |
| "http://host/lowest", net::LOWEST, kChildId2, kRouteId2)); |
| std::unique_ptr<TestRequest> lowest2(NewRequestWithChildAndRoute( |
| "http://host/lowest", net::LOWEST, kChildId2, kRouteId2)); |
| EXPECT_FALSE(lowest2->started()); |
| |
| scheduler_->OnClientDeleted(kChildId2, kRouteId2); |
| high.reset(); |
| lowest1.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(lowest2->started()); |
| } |
| |
| // The ResourceScheduler::Client destructor calls |
| // LoadAnyStartablePendingRequests(), which may start some pending requests. |
| // This test is to verify that requests will be started at some point |
| // even if they were not started by the destructor. |
| TEST_F(ResourceSchedulerTest, RequestStartedAfterClientDeletedManyDelayable) { |
| scheduler_->OnClientCreated(kChildId2, kRouteId2); |
| std::unique_ptr<TestRequest> high(NewRequestWithChildAndRoute( |
| "http://host/high", net::HIGHEST, kChildId2, kRouteId2)); |
| const int kMaxNumDelayableRequestsPerClient = 10; |
| std::vector<std::unique_ptr<TestRequest>> delayable_requests; |
| for (int i = 0; i < kMaxNumDelayableRequestsPerClient + 1; ++i) { |
| delayable_requests.push_back(NewRequestWithChildAndRoute( |
| "http://host/lowest", net::LOWEST, kChildId2, kRouteId2)); |
| } |
| std::unique_ptr<TestRequest> lowest(NewRequestWithChildAndRoute( |
| "http://host/lowest", net::LOWEST, kChildId2, kRouteId2)); |
| EXPECT_FALSE(lowest->started()); |
| |
| scheduler_->OnClientDeleted(kChildId2, kRouteId2); |
| high.reset(); |
| delayable_requests.clear(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(lowest->started()); |
| } |
| |
| } // unnamed namespace |
| |
| } // namespace content |