Do not instantiate QpackSendStream if qpack_maximum_dynamic_table_capacity_ is 0 in QuicSpdySession.

This change saves up to 4K memory per QUIC connection (from 2 QpackSendStream and 2 QpackReceiveStream) when QPACK dynamic table is disabled.

Protected by FLAGS_quic_reloadable_flag_quic_not_instantiate_unused_qpack_send_stream.

PiperOrigin-RevId: 852984965
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index aa47aa2..7b26e5d 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -49,6 +49,7 @@
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_no_path_degrading_before_handshake_confirmed, true, true, "If true, an endpoint does not detect path degrading or blackholing until handshake gets confirmed.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_no_write_control_frame_upon_connection_close, false, true, "If trrue, early return before write control frame in OnCanWrite() if the connection is already closed.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_no_write_control_frame_upon_connection_close2, false, true, "If true, QuicSession will block outgoing control frames when the connection is closed.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_not_instantiate_unused_qpack_send_stream, false, false, "When qpack_maximum_dynamic_table_capacity is zero, don't bother to instantiate the unused QpackSendStream.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_notify_ack_listener_earlier, true, true, "If true, call QuicAckListenerInterface::OnPacketAcked() before moving the stream to closed stream list.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_notify_stream_soon_to_destroy, true, true, "If true, notify each QUIC stream before it gets destroyed and update ACK listener before that.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_on_packet_header_return_connected, false, true, "If true, QuicConnection::OnPacketHeader will return connected_ at the end of the function.")
diff --git a/quiche/quic/core/http/quic_spdy_session.cc b/quiche/quic/core/http/quic_spdy_session.cc
index 479abfa..227294c 100644
--- a/quiche/quic/core/http/quic_spdy_session.cc
+++ b/quiche/quic/core/http/quic_spdy_session.cc
@@ -1633,6 +1633,16 @@
     }
   }
 
+  // When qpack_maximum_dynamic_table_capacity_ is zero, decoder's dynamic table
+  // capacity is always zero and encoder's dynamic table capacity is MIN(0,
+  // SETTINGS_QPACK_MAX_TABLE_CAPACITY from peer) = 0. Hence, we don't need to
+  // instantiate qpack send streams for both decoder and encoder.
+  if (GetQuicheReloadableFlag(quic_not_instantiate_unused_qpack_send_stream) &&
+      qpack_maximum_dynamic_table_capacity_ == 0) {
+    QUICHE_RELOADABLE_FLAG_COUNT(quic_not_instantiate_unused_qpack_send_stream);
+    return;
+  }
+
   if (!qpack_decoder_send_stream_ &&
       CanOpenNextOutgoingUnidirectionalStream()) {
     auto decoder_send = std::make_unique<QpackSendStream>(
diff --git a/quiche/quic/core/http/quic_spdy_session_test.cc b/quiche/quic/core/http/quic_spdy_session_test.cc
index e99c3df..6a39fe3 100644
--- a/quiche/quic/core/http/quic_spdy_session_test.cc
+++ b/quiche/quic/core/http/quic_spdy_session_test.cc
@@ -2105,6 +2105,14 @@
       QpackEncoderPeer::header_table(qpack_encoder);
   EXPECT_EQ(0, encoder_header_table->dynamic_table_capacity());
   EXPECT_EQ(capacity, encoder_header_table->maximum_dynamic_table_capacity());
+  if (GetQuicReloadableFlag(quic_not_instantiate_unused_qpack_send_stream)) {
+    EXPECT_EQ(nullptr, QuicSpdySessionPeer::GetQpackDecoderSendStream(
+                           &session_.value()));
+
+  } else {
+    EXPECT_NE(nullptr, QuicSpdySessionPeer::GetQpackDecoderSendStream(
+                           &session_.value()));
+  }
 
   // Verify that the advertised capacity is 0.
   SettingsFrame outgoing_settings = session_->settings();
@@ -2659,6 +2667,13 @@
       QpackEncoderPeer::header_table(qpack_encoder);
   EXPECT_EQ(capacity, encoder_header_table->maximum_dynamic_table_capacity());
   EXPECT_EQ(0, encoder_header_table->dynamic_table_capacity());
+  if (GetQuicheReloadableFlag(quic_not_instantiate_unused_qpack_send_stream)) {
+    EXPECT_EQ(nullptr, QuicSpdySessionPeer::GetQpackDecoderSendStream(
+                           &session_.value()));
+  } else {
+    EXPECT_NE(nullptr, QuicSpdySessionPeer::GetQpackDecoderSendStream(
+                           &session_.value()));
+  }
 
   // Verify that the advertised capacity is 0.
   SettingsFrame outgoing_settings = session_->settings();