In QuicConnection::OnCanWrite(), early return after writing buffered crypto frames if the connection gets closed.
Protected by quic_reloadable_flag_quic_no_write_control_frame_upon_connection_close.
PiperOrigin-RevId: 467718239
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index 24386b3..f043e97 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -17,6 +17,8 @@
QUIC_FLAG(quic_restart_flag_quic_testonly_default_true, true)
// If bytes in flight has dipped below 1.25*MaxBW in the last round, do not exit PROBE_UP due to excess queue buildup.
QUIC_FLAG(quic_reloadable_flag_quic_bbr2_no_probe_up_exit_if_no_queue, true)
+// If trrue, early return before write control frame in OnCanWrite() if the connection is already closed.
+QUIC_FLAG(quic_reloadable_flag_quic_no_write_control_frame_upon_connection_close, true)
// If true and BBQ1 connection option is set, QUIC BBR will use a pacing gain of 2.773 at startup and 0.5 at DRAIN.
QUIC_FLAG(quic_reloadable_flag_quic_bbr2_support_new_startup_pacing_gain, true)
// If true, 1) remove all experiments that tunes blackhole detection delay or path degrading delay, and 2) ensure network blackhole delay is at least path degrading delay plus 2 PTOs.
diff --git a/quiche/quic/core/quic_session.cc b/quiche/quic/core/quic_session.cc
index f8fd2af..8d87de1 100644
--- a/quiche/quic/core/quic_session.cc
+++ b/quiche/quic/core/quic_session.cc
@@ -627,9 +627,12 @@
if (crypto_stream->HasBufferedCryptoFrames()) {
crypto_stream->WriteBufferedCryptoFrames();
}
- if (crypto_stream->HasBufferedCryptoFrames()) {
- // Cannot finish writing buffered crypto frames, connection is write
- // blocked.
+ if ((GetQuicReloadableFlag(
+ quic_no_write_control_frame_upon_connection_close) &&
+ !connection_->connected()) ||
+ crypto_stream->HasBufferedCryptoFrames()) {
+ // Cannot finish writing buffered crypto frames, connection is either
+ // write blocked or closed.
return;
}
}
@@ -846,7 +849,7 @@
bool QuicSession::WriteControlFrame(const QuicFrame& frame,
TransmissionType type) {
- QUICHE_DCHECK(connection()->connected())
+ QUIC_BUG_IF(quic_bug_12435_11, !connection()->connected())
<< ENDPOINT << "Try to write control frames when connection is closed.";
if (!IsEncryptionEstablished()) {
// Suppress the write before encryption gets established.
diff --git a/quiche/quic/core/quic_session_test.cc b/quiche/quic/core/quic_session_test.cc
index 5cd4cb9..916ed1b 100644
--- a/quiche/quic/core/quic_session_test.cc
+++ b/quiche/quic/core/quic_session_test.cc
@@ -3088,6 +3088,46 @@
}
}
+TEST_P(QuicSessionTestServer, BufferedCryptoFrameCausesWriteError) {
+ if (!VersionHasIetfQuicFrames(transport_version())) {
+ return;
+ }
+ std::string data(1350, 'a');
+ TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+ // Only consumed 1000 bytes.
+ EXPECT_CALL(*connection_, SendCryptoData(ENCRYPTION_FORWARD_SECURE, 1350, 0))
+ .WillOnce(Return(1000));
+ crypto_stream->WriteCryptoData(ENCRYPTION_FORWARD_SECURE, data);
+ EXPECT_TRUE(session_.HasPendingHandshake());
+ EXPECT_TRUE(session_.WillingAndAbleToWrite());
+
+ EXPECT_CALL(*connection_,
+ SendCryptoData(ENCRYPTION_FORWARD_SECURE, 350, 1000))
+ .WillOnce(Return(0));
+ // Buffer the HANDSHAKE_DONE frame.
+ EXPECT_CALL(*connection_, SendControlFrame(_)).WillOnce(Return(false));
+ CryptoHandshakeMessage msg;
+ session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+
+ // Flush both frames.
+ EXPECT_CALL(*connection_,
+ SendCryptoData(ENCRYPTION_FORWARD_SECURE, 350, 1000))
+ .WillOnce(testing::InvokeWithoutArgs([this]() {
+ connection_->ReallyCloseConnection(
+ QUIC_PACKET_WRITE_ERROR, "write error",
+ ConnectionCloseBehavior::SILENT_CLOSE);
+ return 350;
+ }));
+ if (!GetQuicReloadableFlag(
+ quic_no_write_control_frame_upon_connection_close)) {
+ EXPECT_CALL(*connection_, SendControlFrame(_)).WillOnce(Return(false));
+ EXPECT_QUIC_BUG(session_.OnCanWrite(),
+ "Try to write control frames when connection is closed");
+ } else {
+ session_.OnCanWrite();
+ }
+}
+
// A client test class that can be used when the automatic configuration is not
// desired.
class QuicSessionTestClientUnconfigured : public QuicSessionTestBase {