Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(391)

Unified Diff: net/http/http_network_transaction_unittest.cc

Issue 1901533002: Implementation of network level throttler. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Incorporated latest round of comments." . Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: net/http/http_network_transaction_unittest.cc
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 0d467c666b3eee3cb8c4869fa55031fd344d877b..9225256c740facb432f2dbfe6e1a751b3680c3ea 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -10,6 +10,7 @@
#include <limits>
#include <memory>
+#include <set>
#include <string>
#include <utility>
#include <vector>
@@ -34,6 +35,7 @@
#include "net/base/load_timing_info.h"
#include "net/base/load_timing_info_test_util.h"
#include "net/base/net_errors.h"
+#include "net/base/network_stream_throttler.h"
#include "net/base/proxy_delegate.h"
#include "net/base/request_priority.h"
#include "net/base/test_completion_callback.h"
@@ -115,6 +117,100 @@ enum TestCase {
kTestCaseHTTP2PriorityDependencies
};
+class TestNetworkStreamThrottler : public NetworkStreamThrottler {
+ public:
+ TestNetworkStreamThrottler()
+ : throttle_new_requests_(false),
+ num_set_priority_calls_(0),
+ last_priority_set_(IDLE) {}
+
+ ~TestNetworkStreamThrottler() override {
+ EXPECT_TRUE(outstanding_throttles_.empty());
+ }
+
+ // NetworkStreamThrottler
+ std::unique_ptr<Throttle> CreateThrottle(Delegate* delegate,
+ RequestPriority priority,
+ bool ignore_limits) override {
+ std::unique_ptr<TestThrottle> test_throttle(
+ new TestThrottle(throttle_new_requests_, delegate, this));
+ outstanding_throttles_.insert(test_throttle.get());
+ return std::move(test_throttle);
+ }
+
+ void UnthrottleAllRequests() {
+ std::set<TestThrottle*> outstanding_throttles_copy(outstanding_throttles_);
+ for (auto& throttle : outstanding_throttles_copy) {
+ if (throttle->IsThrottled())
+ throttle->Unthrottle();
+ }
+ }
+
+ void set_throttle_new_requests(bool throttle_new_requests) {
+ throttle_new_requests_ = throttle_new_requests;
+ }
+
+ // Includes both throttled and unthrottled throttles.
+ size_t num_outstanding_requests() const {
+ return outstanding_throttles_.size();
+ }
+
+ int num_set_priority_calls() const { return num_set_priority_calls_; }
+ RequestPriority last_priority_set() const { return last_priority_set_; }
+ void set_priority_change_closure(
+ const base::Closure& priority_change_closure) {
+ priority_change_closure_ = priority_change_closure;
+ }
+
+ private:
+ class TestThrottle : public NetworkStreamThrottler::Throttle {
+ public:
+ ~TestThrottle() override { throttler_->OnThrottleDestroyed(this); }
+
+ // Throttle
+ bool IsThrottled() const override { return throttled_; }
+ void SetPriority(RequestPriority priority) override {
+ throttler_->SetPriorityCalled(priority);
+ }
+
+ TestThrottle(bool throttled,
+ Delegate* delegate,
+ TestNetworkStreamThrottler* throttler)
+ : throttled_(throttled), delegate_(delegate), throttler_(throttler) {}
+
+ void Unthrottle() {
+ EXPECT_TRUE(throttled_);
+
+ throttled_ = false;
+ delegate_->OnThrottleStateChanged();
+ }
+
+ bool throttled_;
+ Delegate* delegate_;
+ TestNetworkStreamThrottler* throttler_;
+ };
+
+ void OnThrottleDestroyed(TestThrottle* throttle) {
+ outstanding_throttles_.erase(throttle);
+ }
+
+ void SetPriorityCalled(RequestPriority priority) {
+ ++num_set_priority_calls_;
+ last_priority_set_ = priority;
+ if (!priority_change_closure_.is_null())
+ priority_change_closure_.Run();
+ }
+
+ // Includes both throttled and unthrottled throttles.
+ std::set<TestThrottle*> outstanding_throttles_;
+ bool throttle_new_requests_;
+ int num_set_priority_calls_;
+ RequestPriority last_priority_set_;
+ base::Closure priority_change_closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestNetworkStreamThrottler);
+};
+
const base::string16 kBar(ASCIIToUTF16("bar"));
const base::string16 kBar2(ASCIIToUTF16("bar2"));
const base::string16 kBar3(ASCIIToUTF16("bar3"));
@@ -260,6 +356,24 @@ std::unique_ptr<HttpNetworkSession> CreateSession(
return SpdySessionDependencies::SpdyCreateSession(session_deps);
}
+// Note that the pointer written into |*throttler| will only be valid
+// for the lifetime of the returned HttpNetworkSession.
+std::unique_ptr<HttpNetworkSession> CreateSessionWithThrottler(
+ SpdySessionDependencies* session_deps,
+ TestNetworkStreamThrottler** throttler) {
+ std::unique_ptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(session_deps));
+
+ std::unique_ptr<TestNetworkStreamThrottler> owned_throttler(
+ new TestNetworkStreamThrottler);
+ *throttler = owned_throttler.get();
+
+ HttpNetworkSessionPeer peer(session.get());
+ peer.SetNetworkStreamThrottler(std::move(owned_throttler));
+
+ return session;
+}
+
} // namespace
class HttpNetworkTransactionTest
@@ -15941,6 +16055,299 @@ TEST_P(HttpNetworkTransactionTest, DisableNPN) {
EXPECT_TRUE(trans.server_ssl_config_.npn_protos.empty());
}
+// Confirm that transactions with throttler is created in the unthrottled
+// state (and stays in that state) is not blocked.
+TEST_P(HttpNetworkTransactionTest, ThrottlingUnthrottled) {
+ TestNetworkStreamThrottler* throttler(nullptr);
+ std::unique_ptr<HttpNetworkSession> session(
+ CreateSessionWithThrottler(&session_deps_, &throttler));
+
+ // Send a simple request and make sure it goes through.
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.example.org/");
+ request.load_flags = 0;
+
+ std::unique_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.example.org\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"), MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider reads(data_reads, arraysize(data_reads), data_writes,
+ arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&reads);
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+// Confirm requests can be blocked by a throttler, and are resumed
+// when the throttle is unblocked.
+TEST_P(HttpNetworkTransactionTest, ThrottlingBasic) {
+ TestNetworkStreamThrottler* throttler(nullptr);
+ std::unique_ptr<HttpNetworkSession> session(
+ CreateSessionWithThrottler(&session_deps_, &throttler));
+
+ // Send a simple request and make sure it goes through.
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.example.org/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.example.org\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"), MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider reads(data_reads, arraysize(data_reads), data_writes,
+ arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&reads);
+
+ // Start a request that will be throttled at start; confirm doesn't continue.
+ throttler->set_throttle_new_requests(true);
+ std::unique_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(LOAD_STATE_THROTTLED, trans->GetLoadState());
+ EXPECT_FALSE(callback.have_result());
+
+ // Confirm the request goes on to complete when unthrottled.
+ throttler->UnthrottleAllRequests();
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_TRUE(callback.have_result());
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+// Destroy a request while it's throttled.
+TEST_P(HttpNetworkTransactionTest, ThrottlingDestruction) {
+ TestNetworkStreamThrottler* throttler(nullptr);
+ std::unique_ptr<HttpNetworkSession> session(
+ CreateSessionWithThrottler(&session_deps_, &throttler));
+
+ // Send a simple request and make sure it goes through.
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.example.org/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.example.org\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"), MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider reads(data_reads, arraysize(data_reads), data_writes,
+ arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&reads);
+
+ // Start a request that will be throttled at start; confirm doesn't continue.
+ throttler->set_throttle_new_requests(true);
+ std::unique_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(LOAD_STATE_THROTTLED, trans->GetLoadState());
+ EXPECT_FALSE(callback.have_result());
+
+ EXPECT_EQ(1u, throttler->num_outstanding_requests());
+ trans.reset();
+ EXPECT_EQ(0u, throttler->num_outstanding_requests());
+}
+
+// Confirm the throttler receives SetPriority calls.
+TEST_P(HttpNetworkTransactionTest, ThrottlingPrioritySet) {
+ TestNetworkStreamThrottler* throttler(nullptr);
+ std::unique_ptr<HttpNetworkSession> session(
+ CreateSessionWithThrottler(&session_deps_, &throttler));
+
+ // Send a simple request and make sure it goes through.
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.example.org/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.example.org\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"), MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider reads(data_reads, arraysize(data_reads), data_writes,
+ arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&reads);
+
+ std::unique_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(IDLE, session.get()));
+ throttler->set_throttle_new_requests(true);
+ // Start the transaction to associate a throttle with it.
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(0, throttler->num_set_priority_calls());
+ trans->SetPriority(LOW);
+ EXPECT_EQ(1, throttler->num_set_priority_calls());
+ EXPECT_EQ(LOW, throttler->last_priority_set());
+
+ throttler->UnthrottleAllRequests();
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_TRUE(callback.have_result());
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+// Confirm that unthrottling from a SetPriority call by the
+// throttler works properly.
+TEST_P(HttpNetworkTransactionTest, ThrottlingPrioritySetUnthrottle) {
+ TestNetworkStreamThrottler* throttler(nullptr);
+ std::unique_ptr<HttpNetworkSession> session(
+ CreateSessionWithThrottler(&session_deps_, &throttler));
+
+ // Send a simple request and make sure it goes through.
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.example.org/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.example.org\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"), MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider reads(data_reads, arraysize(data_reads), data_writes,
+ arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&reads);
+
+ StaticSocketDataProvider reads1(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&reads1);
+
+ // Start a request that will be throttled at start; confirm doesn't continue.
+ throttler->set_throttle_new_requests(true);
+ std::unique_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(LOAD_STATE_THROTTLED, trans->GetLoadState());
+ EXPECT_FALSE(callback.have_result());
+
+ // Create a new request, call SetPriority on it to unthrottle,
+ // and make sure that allows the original request to complete.
+ std::unique_ptr<HttpTransaction> trans1(
+ new HttpNetworkTransaction(LOW, session.get()));
+ throttler->set_priority_change_closure(
+ base::Bind(&TestNetworkStreamThrottler::UnthrottleAllRequests,
+ base::Unretained(throttler)));
+
+ // Start the transaction to associate a throttle with it.
+ TestCompletionCallback callback1;
+ rv = trans1->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ trans1->SetPriority(IDLE);
+
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_TRUE(callback.have_result());
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+// Transaction will be destroyed when the unique_ptr goes out of scope.
+void DestroyTransaction(std::unique_ptr<HttpTransaction> transaction) {}
+
+// Confirm that destroying a transaction from a SetPriority call by the
+// throttler works properly.
+TEST_P(HttpNetworkTransactionTest, ThrottlingPrioritySetDestroy) {
+ TestNetworkStreamThrottler* throttler(nullptr);
+ std::unique_ptr<HttpNetworkSession> session(
+ CreateSessionWithThrottler(&session_deps_, &throttler));
+
+ // Send a simple request and make sure it goes through.
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.example.org/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.example.org\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"), MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider reads(data_reads, arraysize(data_reads), data_writes,
+ arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&reads);
+
+ StaticSocketDataProvider reads1(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&reads1);
+
+ // Start a request that will be throttled at start; confirm doesn't continue.
+ throttler->set_throttle_new_requests(true);
+ std::unique_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(LOAD_STATE_THROTTLED, trans->GetLoadState());
+ EXPECT_FALSE(callback.have_result());
+
+ // Arrange fo the set priority call on the above transaction to delete
+ // the transaction.
+ HttpTransaction* trans_p(trans.get());
+ throttler->set_priority_change_closure(
+ base::Bind(&DestroyTransaction, base::Passed(&trans)));
+
+ // Call it and check results (partially a "doesn't crash" test).
+ trans_p->SetPriority(IDLE);
+ trans_p = nullptr; // No longer a valid pointer.
+
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_FALSE(callback.have_result());
+}
+
#if !defined(OS_IOS)
TEST_P(HttpNetworkTransactionTest, TokenBindingSpdy) {
const std::string https_url = "https://www.example.com";

Powered by Google App Engine
This is Rietveld 408576698