Allow setting a delay for a response. This serves as infrastructure for testing that timing metrics measure what they're supposed to (see https://chromium-review.googlesource.com/c/chromium/src/+/3815441) PiperOrigin-RevId: 468108169
diff --git a/quiche/quic/test_tools/quic_test_utils.cc b/quiche/quic/test_tools/quic_test_utils.cc index 03948b7..39ba0a7 100644 --- a/quiche/quic/test_tools/quic_test_utils.cc +++ b/quiche/quic/test_tools/quic_test_utils.cc
@@ -455,6 +455,8 @@ const QuicClock* MockQuicConnectionHelper::GetClock() const { return &clock_; } +QuicClock* MockQuicConnectionHelper::GetClock() { return &clock_; } + QuicRandom* MockQuicConnectionHelper::GetRandomGenerator() { return &random_generator_; } @@ -483,8 +485,8 @@ clock_.AdvanceTime(delta); } -MockQuicConnection::MockQuicConnection(MockQuicConnectionHelper* helper, - MockAlarmFactory* alarm_factory, +MockQuicConnection::MockQuicConnection(QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, Perspective perspective) : MockQuicConnection(TestConnectionId(), QuicSocketAddress(TestPeerIPAddress(), kTestPort), @@ -492,16 +494,16 @@ ParsedVersionOfIndex(CurrentSupportedVersions(), 0)) {} MockQuicConnection::MockQuicConnection(QuicSocketAddress address, - MockQuicConnectionHelper* helper, - MockAlarmFactory* alarm_factory, + QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, Perspective perspective) : MockQuicConnection(TestConnectionId(), address, helper, alarm_factory, perspective, ParsedVersionOfIndex(CurrentSupportedVersions(), 0)) {} MockQuicConnection::MockQuicConnection(QuicConnectionId connection_id, - MockQuicConnectionHelper* helper, - MockAlarmFactory* alarm_factory, + QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, Perspective perspective) : MockQuicConnection(connection_id, QuicSocketAddress(TestPeerIPAddress(), kTestPort), @@ -509,7 +511,7 @@ ParsedVersionOfIndex(CurrentSupportedVersions(), 0)) {} MockQuicConnection::MockQuicConnection( - MockQuicConnectionHelper* helper, MockAlarmFactory* alarm_factory, + QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory, Perspective perspective, const ParsedQuicVersionVector& supported_versions) : MockQuicConnection( TestConnectionId(), QuicSocketAddress(TestPeerIPAddress(), kTestPort), @@ -517,7 +519,7 @@ MockQuicConnection::MockQuicConnection( QuicConnectionId connection_id, QuicSocketAddress initial_peer_address, - MockQuicConnectionHelper* helper, MockAlarmFactory* alarm_factory, + QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory, Perspective perspective, const ParsedQuicVersionVector& supported_versions) : QuicConnection( connection_id, @@ -546,13 +548,13 @@ return false; } -PacketSavingConnection::PacketSavingConnection(MockQuicConnectionHelper* helper, - MockAlarmFactory* alarm_factory, - Perspective perspective) +PacketSavingConnection::PacketSavingConnection( + QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory, + Perspective perspective) : MockQuicConnection(helper, alarm_factory, perspective) {} PacketSavingConnection::PacketSavingConnection( - MockQuicConnectionHelper* helper, MockAlarmFactory* alarm_factory, + QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory, Perspective perspective, const ParsedQuicVersionVector& supported_versions) : MockQuicConnection(helper, alarm_factory, perspective, supported_versions) {} @@ -1125,7 +1127,7 @@ void CreateClientSessionForTest( QuicServerId server_id, QuicTime::Delta connection_start_time, const ParsedQuicVersionVector& supported_versions, - MockQuicConnectionHelper* helper, MockAlarmFactory* alarm_factory, + QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory, QuicCryptoClientConfig* crypto_client_config, PacketSavingConnection** client_connection, TestQuicSpdyClientSession** client_session) { @@ -1148,7 +1150,7 @@ void CreateServerSessionForTest( QuicServerId /*server_id*/, QuicTime::Delta connection_start_time, ParsedQuicVersionVector supported_versions, - MockQuicConnectionHelper* helper, MockAlarmFactory* alarm_factory, + QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory, QuicCryptoServerConfig* server_crypto_config, QuicCompressedCertsCache* compressed_certs_cache, PacketSavingConnection** server_connection,
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h index 5e1397c..af9cf2c 100644 --- a/quiche/quic/test_tools/quic_test_utils.h +++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -517,6 +517,7 @@ MockQuicConnectionHelper& operator=(const MockQuicConnectionHelper&) = delete; ~MockQuicConnectionHelper() override; const QuicClock* GetClock() const override; + QuicClock* GetClock(); QuicRandom* GetRandomGenerator() override; quiche::QuicheBufferAllocator* GetStreamSendBufferAllocator() override; void AdvanceTime(QuicTime::Delta delta); @@ -581,36 +582,35 @@ class MockQuicConnection : public QuicConnection { public: // Uses a ConnectionId of 42 and 127.0.0.1:123. - MockQuicConnection(MockQuicConnectionHelper* helper, - MockAlarmFactory* alarm_factory, Perspective perspective); + MockQuicConnection(QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, Perspective perspective); // Uses a ConnectionId of 42. MockQuicConnection(QuicSocketAddress address, - MockQuicConnectionHelper* helper, - MockAlarmFactory* alarm_factory, Perspective perspective); + QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, Perspective perspective); // Uses 127.0.0.1:123. MockQuicConnection(QuicConnectionId connection_id, - MockQuicConnectionHelper* helper, - MockAlarmFactory* alarm_factory, Perspective perspective); + QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, Perspective perspective); // Uses a ConnectionId of 42, and 127.0.0.1:123. - MockQuicConnection(MockQuicConnectionHelper* helper, - MockAlarmFactory* alarm_factory, Perspective perspective, + MockQuicConnection(QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, Perspective perspective, const ParsedQuicVersionVector& supported_versions); MockQuicConnection(QuicConnectionId connection_id, QuicSocketAddress address, - MockQuicConnectionHelper* helper, - MockAlarmFactory* alarm_factory, Perspective perspective, + QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, Perspective perspective, const ParsedQuicVersionVector& supported_versions); MockQuicConnection(const MockQuicConnection&) = delete; MockQuicConnection& operator=(const MockQuicConnection&) = delete; ~MockQuicConnection() override; - // If the constructor that uses a MockQuicConnectionHelper has been used then - // this method - // will advance the time of the MockClock. + // If the constructor that uses a QuicConnectionHelperInterface has been used + // then this method will advance the time of the MockClock. void AdvanceTime(QuicTime::Delta delta); MOCK_METHOD(void, ProcessUdpPacket, @@ -692,6 +692,7 @@ } bool OnProtocolVersionMismatch(ParsedQuicVersion version) override; + void OnIdleNetworkDetected() override {} bool ReallySendControlFrame(const QuicFrame& frame) { return QuicConnection::SendControlFrame(frame); @@ -722,12 +723,12 @@ class PacketSavingConnection : public MockQuicConnection { public: - PacketSavingConnection(MockQuicConnectionHelper* helper, - MockAlarmFactory* alarm_factory, + PacketSavingConnection(QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, Perspective perspective); - PacketSavingConnection(MockQuicConnectionHelper* helper, - MockAlarmFactory* alarm_factory, + PacketSavingConnection(QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, Perspective perspective, const ParsedQuicVersionVector& supported_versions); PacketSavingConnection(const PacketSavingConnection&) = delete; @@ -1516,7 +1517,7 @@ void CreateClientSessionForTest( QuicServerId server_id, QuicTime::Delta connection_start_time, const ParsedQuicVersionVector& supported_versions, - MockQuicConnectionHelper* helper, MockAlarmFactory* alarm_factory, + QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory, QuicCryptoClientConfig* crypto_client_config, PacketSavingConnection** client_connection, TestQuicSpdyClientSession** client_session); @@ -1539,7 +1540,7 @@ void CreateServerSessionForTest( QuicServerId server_id, QuicTime::Delta connection_start_time, ParsedQuicVersionVector supported_versions, - MockQuicConnectionHelper* helper, MockAlarmFactory* alarm_factory, + QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory, QuicCryptoServerConfig* server_crypto_config, QuicCompressedCertsCache* compressed_certs_cache, PacketSavingConnection** server_connection,
diff --git a/quiche/quic/tools/quic_backend_response.cc b/quiche/quic/tools/quic_backend_response.cc index 8fce5d5..8a54204 100644 --- a/quiche/quic/tools/quic_backend_response.cc +++ b/quiche/quic/tools/quic_backend_response.cc
@@ -20,7 +20,8 @@ priority(other.priority), body(other.body) {} -QuicBackendResponse::QuicBackendResponse() : response_type_(REGULAR_RESPONSE) {} +QuicBackendResponse::QuicBackendResponse() + : response_type_(REGULAR_RESPONSE), delay_(QuicTime::Delta::Zero()) {} QuicBackendResponse::~QuicBackendResponse() = default;
diff --git a/quiche/quic/tools/quic_backend_response.h b/quiche/quic/tools/quic_backend_response.h index 8e52e2e..6d1b105 100644 --- a/quiche/quic/tools/quic_backend_response.h +++ b/quiche/quic/tools/quic_backend_response.h
@@ -6,6 +6,7 @@ #define QUICHE_QUIC_TOOLS_QUIC_BACKEND_RESPONSE_H_ #include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_time.h" #include "quiche/quic/tools/quic_url.h" #include "quiche/spdy/core/http2_header_block.h" #include "quiche/spdy/core/spdy_protocol.h" @@ -78,12 +79,18 @@ body_.assign(body.data(), body.size()); } + // This would simulate a delay before sending the response + // back to the client. Intended for testing purposes. + void set_delay(QuicTime::Delta delay) { delay_ = delay; } + QuicTime::Delta delay() const { return delay_; } + private: std::vector<spdy::Http2HeaderBlock> early_hints_; SpecialResponseType response_type_; spdy::Http2HeaderBlock headers_; spdy::Http2HeaderBlock trailers_; std::string body_; + QuicTime::Delta delay_; }; } // namespace quic
diff --git a/quiche/quic/tools/quic_memory_cache_backend.cc b/quiche/quic/tools/quic_memory_cache_backend.cc index 0e50139..0fe984d 100644 --- a/quiche/quic/tools/quic_memory_cache_backend.cc +++ b/quiche/quic/tools/quic_memory_cache_backend.cc
@@ -215,6 +215,17 @@ std::vector<spdy::Http2HeaderBlock>()); } +bool QuicMemoryCacheBackend::SetResponseDelay(absl::string_view host, + absl::string_view path, + QuicTime::Delta delay) { + QuicWriterMutexLock lock(&response_mutex_); + auto it = responses_.find(GetKey(host, path)); + if (it == responses_.end()) return false; + + it->second->set_delay(delay); + return true; +} + void QuicMemoryCacheBackend::AddResponseWithEarlyHints( absl::string_view host, absl::string_view path, spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
diff --git a/quiche/quic/tools/quic_memory_cache_backend.h b/quiche/quic/tools/quic_memory_cache_backend.h index 7dfd372..ccdaf59 100644 --- a/quiche/quic/tools/quic_memory_cache_backend.h +++ b/quiche/quic/tools/quic_memory_cache_backend.h
@@ -121,6 +121,12 @@ spdy::Http2HeaderBlock response_headers, absl::string_view response_body, QuicBackendResponse::SpecialResponseType response_type); + // Finds a response with the given host and path, and assign it a simulated + // delay. Returns true if the requisite response was found and the delay was + // set. + bool SetResponseDelay(absl::string_view host, absl::string_view path, + QuicTime::Delta delay); + // Sets a default response in case of cache misses. Takes ownership of // 'response'. void AddDefaultResponse(QuicBackendResponse* response);
diff --git a/quiche/quic/tools/quic_simple_server_stream.cc b/quiche/quic/tools/quic_simple_server_stream.cc index 1b66aac..beea65a 100644 --- a/quiche/quic/tools/quic_simple_server_stream.cc +++ b/quiche/quic/tools/quic_simple_server_stream.cc
@@ -245,6 +245,31 @@ return spdy_session()->peer_address().host().ToString(); } +namespace { + +class DelayedResponseAlarm : public QuicAlarm::DelegateWithContext { + public: + DelayedResponseAlarm(QuicSimpleServerStream* stream, + const QuicBackendResponse* response) + : QuicAlarm::DelegateWithContext( + stream->spdy_session()->connection()->context()), + stream_(stream), + response_(response) { + stream_ = stream; + response_ = response; + } + + ~DelayedResponseAlarm() override = default; + + void OnAlarm() override { stream_->Respond(response_); } + + private: + QuicSimpleServerStream* stream_; + const QuicBackendResponse* response_; +}; + +} // namespace + void QuicSimpleServerStream::OnResponseBackendComplete( const QuicBackendResponse* response) { if (response == nullptr) { @@ -253,6 +278,19 @@ return; } + auto delay = response->delay(); + if (delay.IsZero()) { + Respond(response); + return; + } + + auto* connection = session()->connection(); + delayed_response_alarm_.reset(connection->alarm_factory()->CreateAlarm( + new DelayedResponseAlarm(this, response))); + delayed_response_alarm_->Set(connection->clock()->Now() + delay); +} + +void QuicSimpleServerStream::Respond(const QuicBackendResponse* response) { // Send Early Hints first. for (const auto& headers : response->early_hints()) { QUIC_DVLOG(1) << "Stream " << id() << " sending an Early Hints response: "
diff --git a/quiche/quic/tools/quic_simple_server_stream.h b/quiche/quic/tools/quic_simple_server_stream.h index 62c6339..3bcd9ed 100644 --- a/quiche/quic/tools/quic_simple_server_stream.h +++ b/quiche/quic/tools/quic_simple_server_stream.h
@@ -61,6 +61,8 @@ void SendStreamData(absl::string_view data, bool close_stream) override; void TerminateStreamWithError(QuicResetStreamError error) override; + void Respond(const QuicBackendResponse* response); + protected: // Handles fresh body data whenever received when method is CONNECT. void HandleRequestConnectData(bool fin_received); @@ -118,6 +120,8 @@ // Whether response headers have already been sent. bool response_sent_ = false; + std::unique_ptr<QuicAlarm> delayed_response_alarm_; + QuicSimpleServerBackend* quic_simple_server_backend_; // Not owned. };
diff --git a/quiche/quic/tools/quic_simple_server_stream_test.cc b/quiche/quic/tools/quic_simple_server_stream_test.cc index 1aa0a7f..92d06bd 100644 --- a/quiche/quic/tools/quic_simple_server_stream_test.cc +++ b/quiche/quic/tools/quic_simple_server_stream_test.cc
@@ -15,6 +15,8 @@ #include "quiche/quic/core/crypto/null_encrypter.h" #include "quiche/quic/core/http/http_encoder.h" #include "quiche/quic/core/http/spdy_utils.h" +#include "quiche/quic/core/quic_alarm_factory.h" +#include "quiche/quic/core/quic_default_clock.h" #include "quiche/quic/core/quic_error_codes.h" #include "quiche/quic/core/quic_types.h" #include "quiche/quic/core/quic_utils.h" @@ -29,6 +31,7 @@ #include "quiche/quic/test_tools/quic_spdy_session_peer.h" #include "quiche/quic/test_tools/quic_stream_peer.h" #include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/quic/test_tools/simulator/simulator.h" #include "quiche/quic/tools/quic_backend_response.h" #include "quiche/quic/tools/quic_memory_cache_backend.h" #include "quiche/quic/tools/quic_simple_server_backend.h" @@ -63,6 +66,7 @@ ~TestStream() override = default; + MOCK_METHOD(void, FireAlarmMock, (), ()); MOCK_METHOD(void, WriteHeadersMock, (bool fin), ()); MOCK_METHOD(void, WriteEarlyHintsHeadersMock, (bool fin), ()); MOCK_METHOD(void, WriteOrBufferBody, (absl::string_view data, bool fin), @@ -205,7 +209,7 @@ public: QuicSimpleServerStreamTest() : connection_(new StrictMock<MockQuicConnection>( - &helper_, &alarm_factory_, Perspective::IS_SERVER, + &simulator_, simulator_.GetAlarmFactory(), Perspective::IS_SERVER, SupportedVersions(GetParam()))), crypto_config_(new QuicCryptoServerConfig( QuicCryptoServerConfig::TESTING, QuicRandom::GetInstance(), @@ -275,9 +279,9 @@ stream_->ReplaceBackend(replacement_backend_.get()); } + quic::simulator::Simulator simulator_; spdy::Http2HeaderBlock response_headers_; MockQuicConnectionHelper helper_; - MockAlarmFactory alarm_factory_; StrictMock<MockQuicConnection>* connection_; StrictMock<MockQuicSessionVisitor> session_owner_; StrictMock<MockQuicCryptoServerStreamHelper> session_helper_; @@ -565,6 +569,61 @@ EXPECT_TRUE(stream_->write_side_closed()); } +class AlarmTestDelegate : public QuicAlarm::DelegateWithoutContext { + public: + AlarmTestDelegate(TestStream* stream) : stream_(stream) {} + + void OnAlarm() override { stream_->FireAlarmMock(); } + + private: + TestStream* stream_; +}; + +TEST_P(QuicSimpleServerStreamTest, SendResponseWithDelay) { + // Add a request and response with valid headers. + spdy::Http2HeaderBlock* request_headers = stream_->mutable_headers(); + std::string host = "www.google.com"; + std::string path = "/bar"; + (*request_headers)[":path"] = path; + (*request_headers)[":authority"] = host; + (*request_headers)[":method"] = "GET"; + + response_headers_[":status"] = "200"; + response_headers_["content-length"] = "5"; + std::string body = "Yummm"; + QuicTime::Delta delay = QuicTime::Delta::FromMilliseconds(3000); + + quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader( + body.length(), quiche::SimpleBufferAllocator::Get()); + + memory_cache_backend_.AddResponse(host, path, std::move(response_headers_), + body); + auto did_delay_succeed = + memory_cache_backend_.SetResponseDelay(host, path, delay); + EXPECT_TRUE(did_delay_succeed); + auto did_invalid_delay_succeed = + memory_cache_backend_.SetResponseDelay(host, "nonsense", delay); + EXPECT_FALSE(did_invalid_delay_succeed); + std::unique_ptr<QuicAlarm> alarm(connection_->alarm_factory()->CreateAlarm( + new AlarmTestDelegate(stream_))); + alarm->Set(connection_->clock()->Now() + delay); + QuicStreamPeer::SetFinReceived(stream_); + InSequence s; + EXPECT_CALL(*stream_, FireAlarmMock()); + EXPECT_CALL(*stream_, WriteHeadersMock(false)); + + if (UsesHttp3()) { + EXPECT_CALL(session_, WritevData(_, header.size(), _, NO_FIN, _, _)); + } + EXPECT_CALL(session_, WritevData(_, body.length(), _, FIN, _, _)); + + stream_->DoSendResponse(); + simulator_.RunFor(delay); + + EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_)); + EXPECT_TRUE(stream_->write_side_closed()); +} + TEST_P(QuicSimpleServerStreamTest, PushResponseOnClientInitiatedStream) { // EXPECT_QUIC_BUG tests are expensive so only run one instance of them. if (GetParam() != AllSupportedVersions()[0]) {