Coalesce QPACK encoder stream writes in QpackEncoderStreamSender.

gfe-relnote: n/a, change to QUIC v99-only code.  Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 273553497
Change-Id: Ia68206d3ff10995e722e2ba87c84e7047bb418f4
diff --git a/quic/core/qpack/qpack_encoder.cc b/quic/core/qpack/qpack_encoder.cc
index a3420be..eac158b 100644
--- a/quic/core/qpack/qpack_encoder.cc
+++ b/quic/core/qpack/qpack_encoder.cc
@@ -281,6 +281,8 @@
     }
   }
 
+  encoder_stream_sender_.Flush();
+
   // Use local |sent_byte_count| variable to avoid branching and dereferencing
   // each time encoder stream data is sent.
   if (encoder_stream_sent_byte_count) {
@@ -395,6 +397,8 @@
 
 void QpackEncoder::SetDynamicTableCapacity(uint64_t dynamic_table_capacity) {
   encoder_stream_sender_.SendSetDynamicTableCapacity(dynamic_table_capacity);
+  encoder_stream_sender_.Flush();
+
   bool success = header_table_.SetDynamicTableCapacity(dynamic_table_capacity);
   DCHECK(success);
 }
diff --git a/quic/core/qpack/qpack_encoder.h b/quic/core/qpack/qpack_encoder.h
index be4f4e2..1ed56a8 100644
--- a/quic/core/qpack/qpack_encoder.h
+++ b/quic/core/qpack/qpack_encoder.h
@@ -124,10 +124,10 @@
   // Performs first pass of two-pass encoding: represent each header field in
   // |*header_list| as a reference to an existing entry, the name of an existing
   // entry with a literal value, or a literal name and value pair.  Sends
-  // necessary instructions on the encoder stream.  Records absolute indices of
-  // referred dynamic table entries in |*referred_indices|.  If
-  // |encoder_stream_sent_byte_count| is not null, then sets
-  // |*encoder_stream_sent_byte_count| to the number of bytes sent on the
+  // necessary instructions on the encoder stream coalesced in a single write.
+  // Records absolute indices of referred dynamic table entries in
+  // |*referred_indices|.  If |encoder_stream_sent_byte_count| is not null, then
+  // sets |*encoder_stream_sent_byte_count| to the number of bytes sent on the
   // encoder stream to insert dynamic table entries.  Returns list of header
   // field representations, with all dynamic table entries referred to with
   // absolute indices.  Returned Instructions object may have QuicStringPieces
diff --git a/quic/core/qpack/qpack_encoder_stream_sender.cc b/quic/core/qpack/qpack_encoder_stream_sender.cc
index 533e3a1..8df0b03 100644
--- a/quic/core/qpack/qpack_encoder_stream_sender.cc
+++ b/quic/core/qpack/qpack_encoder_stream_sender.cc
@@ -23,11 +23,7 @@
   values_.varint = name_index;
   values_.value = value;
 
-  std::string output;
-  instruction_encoder_.Encode(InsertWithNameReferenceInstruction(), values_,
-                              &output);
-  delegate_->WriteStreamData(output);
-  return output.size();
+  return Encode(InsertWithNameReferenceInstruction());
 }
 
 QuicByteCount QpackEncoderStreamSender::SendInsertWithoutNameReference(
@@ -36,31 +32,36 @@
   values_.name = name;
   values_.value = value;
 
-  std::string output;
-  instruction_encoder_.Encode(InsertWithoutNameReferenceInstruction(), values_,
-                              &output);
-  delegate_->WriteStreamData(output);
-  return output.size();
+  return Encode(InsertWithoutNameReferenceInstruction());
 }
 
 QuicByteCount QpackEncoderStreamSender::SendDuplicate(uint64_t index) {
   values_.varint = index;
 
-  std::string output;
-  instruction_encoder_.Encode(DuplicateInstruction(), values_, &output);
-  delegate_->WriteStreamData(output);
-  return output.size();
+  return Encode(DuplicateInstruction());
 }
 
 QuicByteCount QpackEncoderStreamSender::SendSetDynamicTableCapacity(
     uint64_t capacity) {
   values_.varint = capacity;
 
-  std::string output;
-  instruction_encoder_.Encode(SetDynamicTableCapacityInstruction(), values_,
-                              &output);
-  delegate_->WriteStreamData(output);
-  return output.size();
+  return Encode(SetDynamicTableCapacityInstruction());
+}
+
+void QpackEncoderStreamSender::Flush() {
+  if (buffer_.empty()) {
+    return;
+  }
+
+  delegate_->WriteStreamData(buffer_);
+  buffer_.clear();
+}
+
+QuicByteCount QpackEncoderStreamSender::Encode(
+    const QpackInstruction* instruction) {
+  const size_t old_buffer_size = buffer_.size();
+  instruction_encoder_.Encode(instruction, values_, &buffer_);
+  return buffer_.size() - old_buffer_size;
 }
 
 }  // namespace quic
diff --git a/quic/core/qpack/qpack_encoder_stream_sender.h b/quic/core/qpack/qpack_encoder_stream_sender.h
index c8c7717..5c4af7d 100644
--- a/quic/core/qpack/qpack_encoder_stream_sender.h
+++ b/quic/core/qpack/qpack_encoder_stream_sender.h
@@ -16,13 +16,14 @@
 namespace quic {
 
 // This class serializes instructions for transmission on the encoder stream.
+// Serialized instructions are buffered until Flush() is called.
 class QUIC_EXPORT_PRIVATE QpackEncoderStreamSender {
  public:
   QpackEncoderStreamSender();
   QpackEncoderStreamSender(const QpackEncoderStreamSender&) = delete;
   QpackEncoderStreamSender& operator=(const QpackEncoderStreamSender&) = delete;
 
-  // Methods for sending instructions, see
+  // Methods for serializing and buffering instructions, see
   // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#rfc.section.5.2
 
   // 5.2.1. Insert With Name Reference
@@ -37,15 +38,23 @@
   // 5.2.4. Set Dynamic Table Capacity
   QuicByteCount SendSetDynamicTableCapacity(uint64_t capacity);
 
+  // Writes all buffered instructions on the encoder stream.
+  void Flush();
+
   // delegate must be set if dynamic table capacity is not zero.
   void set_qpack_stream_sender_delegate(QpackStreamSenderDelegate* delegate) {
     delegate_ = delegate;
   }
 
  private:
+  // Encodes |instruction| with |values_| using |instruction_encoder_| into
+  // |buffer_|.  Returns number of bytes encoded.
+  QuicByteCount Encode(const QpackInstruction* instruction);
+
   QpackStreamSenderDelegate* delegate_;
   QpackInstructionEncoder instruction_encoder_;
   QpackInstructionEncoder::Values values_;
+  std::string buffer_;
 };
 
 }  // namespace quic
diff --git a/quic/core/qpack/qpack_encoder_stream_sender_test.cc b/quic/core/qpack/qpack_encoder_stream_sender_test.cc
index dcfd5bd..b1ddd97 100644
--- a/quic/core/qpack/qpack_encoder_stream_sender_test.cc
+++ b/quic/core/qpack/qpack_encoder_stream_sender_test.cc
@@ -32,18 +32,21 @@
   EXPECT_CALL(delegate_, WriteStreamData(Eq(expected_encoded_data)));
   EXPECT_EQ(expected_encoded_data.size(),
             stream_.SendInsertWithNameReference(true, 5, ""));
+  stream_.Flush();
 
   // Static, index fits in prefix, Huffman encoded value.
   expected_encoded_data = QuicTextUtils::HexDecode("c28294e7");
   EXPECT_CALL(delegate_, WriteStreamData(Eq(expected_encoded_data)));
   EXPECT_EQ(expected_encoded_data.size(),
             stream_.SendInsertWithNameReference(true, 2, "foo"));
+  stream_.Flush();
 
   // Not static, index does not fit in prefix, not Huffman encoded value.
   expected_encoded_data = QuicTextUtils::HexDecode("bf4a03626172");
   EXPECT_CALL(delegate_, WriteStreamData(Eq(expected_encoded_data)));
   EXPECT_EQ(expected_encoded_data.size(),
             stream_.SendInsertWithNameReference(false, 137, "bar"));
+  stream_.Flush();
 
   // Value length does not fit in prefix.
   // 'Z' would be Huffman encoded to 8 bits, so no Huffman encoding is used.
@@ -56,6 +59,7 @@
   EXPECT_EQ(
       expected_encoded_data.size(),
       stream_.SendInsertWithNameReference(false, 42, std::string(127, 'Z')));
+  stream_.Flush();
 }
 
 TEST_F(QpackEncoderStreamSenderTest, InsertWithoutNameReference) {
@@ -64,18 +68,21 @@
   EXPECT_CALL(delegate_, WriteStreamData(Eq(expected_encoded_data)));
   EXPECT_EQ(expected_encoded_data.size(),
             stream_.SendInsertWithoutNameReference("", ""));
+  stream_.Flush();
 
   // Huffman encoded short strings.
-  expected_encoded_data = QuicTextUtils::HexDecode("4362617203626172");
-  EXPECT_CALL(delegate_, WriteStreamData(Eq(expected_encoded_data)));
-  EXPECT_EQ(expected_encoded_data.size(),
-            stream_.SendInsertWithoutNameReference("bar", "bar"));
-
-  // Not Huffman encoded short strings.
   expected_encoded_data = QuicTextUtils::HexDecode("6294e78294e7");
   EXPECT_CALL(delegate_, WriteStreamData(Eq(expected_encoded_data)));
   EXPECT_EQ(expected_encoded_data.size(),
             stream_.SendInsertWithoutNameReference("foo", "foo"));
+  stream_.Flush();
+
+  // Not Huffman encoded short strings.
+  expected_encoded_data = QuicTextUtils::HexDecode("4362617203626172");
+  EXPECT_CALL(delegate_, WriteStreamData(Eq(expected_encoded_data)));
+  EXPECT_EQ(expected_encoded_data.size(),
+            stream_.SendInsertWithoutNameReference("bar", "bar"));
+  stream_.Flush();
 
   // Not Huffman encoded long strings; length does not fit on prefix.
   // 'Z' would be Huffman encoded to 8 bits, so no Huffman encoding is used.
@@ -89,6 +96,7 @@
   EXPECT_EQ(expected_encoded_data.size(),
             stream_.SendInsertWithoutNameReference(std::string(31, 'Z'),
                                                    std::string(127, 'Z')));
+  stream_.Flush();
 }
 
 TEST_F(QpackEncoderStreamSenderTest, Duplicate) {
@@ -96,11 +104,13 @@
   std::string expected_encoded_data = QuicTextUtils::HexDecode("11");
   EXPECT_CALL(delegate_, WriteStreamData(Eq(expected_encoded_data)));
   EXPECT_EQ(expected_encoded_data.size(), stream_.SendDuplicate(17));
+  stream_.Flush();
 
   // Large index requires two extension bytes.
   expected_encoded_data = QuicTextUtils::HexDecode("1fd503");
   EXPECT_CALL(delegate_, WriteStreamData(Eq(expected_encoded_data)));
   EXPECT_EQ(expected_encoded_data.size(), stream_.SendDuplicate(500));
+  stream_.Flush();
 }
 
 TEST_F(QpackEncoderStreamSenderTest, SetDynamicTableCapacity) {
@@ -109,12 +119,48 @@
   EXPECT_CALL(delegate_, WriteStreamData(Eq(expected_encoded_data)));
   EXPECT_EQ(expected_encoded_data.size(),
             stream_.SendSetDynamicTableCapacity(17));
+  stream_.Flush();
 
   // Large capacity requires two extension bytes.
   expected_encoded_data = QuicTextUtils::HexDecode("3fd503");
   EXPECT_CALL(delegate_, WriteStreamData(Eq(expected_encoded_data)));
   EXPECT_EQ(expected_encoded_data.size(),
             stream_.SendSetDynamicTableCapacity(500));
+  stream_.Flush();
+}
+
+// No writes should happen until Flush is called.
+TEST_F(QpackEncoderStreamSenderTest, Coalesce) {
+  // Insert entry with static name reference, empty value.
+  std::string expected_encoded_data1 = QuicTextUtils::HexDecode("c500");
+  EXPECT_EQ(expected_encoded_data1.size(),
+            stream_.SendInsertWithNameReference(true, 5, ""));
+
+  // Insert entry with static name reference, Huffman encoded value.
+  std::string expected_encoded_data2 = QuicTextUtils::HexDecode("c28294e7");
+  EXPECT_EQ(expected_encoded_data2.size(),
+            stream_.SendInsertWithNameReference(true, 2, "foo"));
+
+  // Insert literal entry, Huffman encoded short strings.
+  std::string expected_encoded_data3 = QuicTextUtils::HexDecode("6294e78294e7");
+  EXPECT_EQ(expected_encoded_data3.size(),
+            stream_.SendInsertWithoutNameReference("foo", "foo"));
+
+  // Duplicate entry.
+  std::string expected_encoded_data4 = QuicTextUtils::HexDecode("11");
+  EXPECT_EQ(expected_encoded_data4.size(), stream_.SendDuplicate(17));
+
+  EXPECT_CALL(delegate_,
+              WriteStreamData(Eq(QuicStrCat(
+                  expected_encoded_data1 + expected_encoded_data2 +
+                  expected_encoded_data3 + expected_encoded_data4))));
+  stream_.Flush();
+}
+
+// No writes should happen if QpackEncoderStreamSender::Flush() is called
+// when the buffer is empty.
+TEST_F(QpackEncoderStreamSenderTest, FlushEmpty) {
+  stream_.Flush();
 }
 
 }  // namespace
diff --git a/quic/core/qpack/qpack_encoder_test.cc b/quic/core/qpack/qpack_encoder_test.cc
index 9850822..3bc5859 100644
--- a/quic/core/qpack/qpack_encoder_test.cc
+++ b/quic/core/qpack/qpack_encoder_test.cc
@@ -202,32 +202,23 @@
   header_list["cookie"] = "baz";              // name matches static entry
 
   // Insert three entries into the dynamic table.
-  std::string insert_entry1 = QuicTextUtils::HexDecode(
+  std::string insert_entries = QuicTextUtils::HexDecode(
       "62"          // insert without name reference
       "94e7"        // Huffman-encoded name "foo"
-      "03626172");  // value "bar"
-  EXPECT_CALL(encoder_stream_sender_delegate_,
-              WriteStreamData(Eq(insert_entry1)));
-
-  std::string insert_entry2 = QuicTextUtils::HexDecode(
+      "03626172"    // value "bar"
       "80"          // insert with name reference, dynamic index 0
-      "0362617a");  // value "baz"
-  EXPECT_CALL(encoder_stream_sender_delegate_,
-              WriteStreamData(Eq(insert_entry2)));
-
-  std::string insert_entry3 = QuicTextUtils::HexDecode(
+      "0362617a"    // value "baz"
       "c5"          // insert with name reference, static index 5
       "0362617a");  // value "baz"
   EXPECT_CALL(encoder_stream_sender_delegate_,
-              WriteStreamData(Eq(insert_entry3)));
+              WriteStreamData(Eq(insert_entries)));
 
   EXPECT_EQ(QuicTextUtils::HexDecode(
                 "0400"      // prefix
                 "828180"),  // dynamic entries with relative index 0, 1, and 2
             Encode(header_list));
 
-  EXPECT_EQ(insert_entry1.size() + insert_entry2.size() + insert_entry3.size(),
-            encoder_stream_sent_byte_count_);
+  EXPECT_EQ(insert_entries.size(), encoder_stream_sent_byte_count_);
 }
 
 // There is no room in the dynamic table after inserting the first entry.
@@ -320,31 +311,22 @@
   encoder_.OnInsertCountIncrement(1);
 
   // Insert three entries into the dynamic table.
-  std::string insert_entry2 = QuicTextUtils::HexDecode(
+  std::string insert_entries = QuicTextUtils::HexDecode(
       "80"          // insert with name reference, dynamic index 0
-      "0362617a");  // value "baz"
-  EXPECT_CALL(encoder_stream_sender_delegate_,
-              WriteStreamData(Eq(insert_entry2)));
-
-  std::string insert_entry3 = QuicTextUtils::HexDecode(
+      "0362617a"    // value "baz"
       "c5"          // insert with name reference, static index 5
-      "0362617a");  // value "baz"
-  EXPECT_CALL(encoder_stream_sender_delegate_,
-              WriteStreamData(Eq(insert_entry3)));
-
-  std::string insert_entry4 = QuicTextUtils::HexDecode(
+      "0362617a"    // value "baz"
       "43"          // insert without name reference
       "626172"      // name "bar"
       "0362617a");  // value "baz"
   EXPECT_CALL(encoder_stream_sender_delegate_,
-              WriteStreamData(Eq(insert_entry4)));
+              WriteStreamData(Eq(insert_entries)));
 
   EXPECT_EQ(QuicTextUtils::HexDecode("0500"        // prefix
                                      "83828180"),  // dynamic entries
             encoder_.EncodeHeaderList(/* stream_id = */ 3, header_list2,
                                       &encoder_stream_sent_byte_count_));
-  EXPECT_EQ(insert_entry2.size() + insert_entry3.size() + insert_entry4.size(),
-            encoder_stream_sent_byte_count_);
+  EXPECT_EQ(insert_entries.size(), encoder_stream_sent_byte_count_);
 
   // Stream 3 is blocked.  Stream 4 is not allowed to block, but it can
   // reference already acknowledged dynamic entry 0.
@@ -414,7 +396,7 @@
   encoder_.SetDynamicTableCapacity(maximum_dynamic_table_capacity);
 
   // Insert ten entries into the dynamic table.
-  EXPECT_CALL(encoder_stream_sender_delegate_, WriteStreamData(_)).Times(10);
+  EXPECT_CALL(encoder_stream_sender_delegate_, WriteStreamData(_));
 
   EXPECT_EQ(
       QuicTextUtils::HexDecode("0b00"                    // prefix