Allocate the size of stream sequencer buffer block pointer container on demand. This should save 16K bytes memory per stream on the server side, as currently blocks_ in QuicStreamSequencerBuffer starts with 2048 nullptr as soon as there is any data to read.

Protected by quic_reloadable_flag_quic_allocate_stream_sequencer_buffer_blocks_on_demand.

PiperOrigin-RevId: 341541273
Change-Id: I882d09cc8b41a0874e94b46958b04562fa96e619
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 7b02787..442e5ea 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -8,6 +8,7 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_abort_qpack_on_stream_close, true)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ack_delay_alarm_granularity, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_stream_info_to_idle_close_detail, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allocate_stream_sequencer_buffer_blocks_on_demand, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allow_client_enabled_bbr_v2, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_avoid_too_low_probe_bw_cwnd, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_fewer_startup_round_trips, false)
diff --git a/quic/core/quic_stream_sequencer_buffer.cc b/quic/core/quic_stream_sequencer_buffer.cc
index b4143ef..9208de6 100644
--- a/quic/core/quic_stream_sequencer_buffer.cc
+++ b/quic/core/quic_stream_sequencer_buffer.cc
@@ -4,6 +4,9 @@
 
 #include "net/third_party/quiche/src/quic/core/quic_stream_sequencer_buffer.h"
 
+#include <algorithm>
+#include <cstddef>
+#include <memory>
 #include <string>
 
 #include "absl/strings/string_view.h"
@@ -32,9 +35,13 @@
 
 QuicStreamSequencerBuffer::QuicStreamSequencerBuffer(size_t max_capacity_bytes)
     : max_buffer_capacity_bytes_(max_capacity_bytes),
-      blocks_count_(CalculateBlockCount(max_capacity_bytes)),
+      max_blocks_count_(CalculateBlockCount(max_capacity_bytes)),
+      current_blocks_count_(0u),
       total_bytes_read_(0),
       blocks_(nullptr) {
+  if (allocate_blocks_on_demand_) {
+    DCHECK_GE(max_blocks_count_, kInitialBlockCount);
+  }
   Clear();
 }
 
@@ -44,7 +51,9 @@
 
 void QuicStreamSequencerBuffer::Clear() {
   if (blocks_ != nullptr) {
-    for (size_t i = 0; i < blocks_count_; ++i) {
+    size_t blocks_to_clear =
+        allocate_blocks_on_demand_ ? current_blocks_count_ : max_blocks_count_;
+    for (size_t i = 0; i < blocks_to_clear; ++i) {
       if (blocks_[i] != nullptr) {
         RetireBlock(i);
       }
@@ -66,6 +75,35 @@
   return true;
 }
 
+void QuicStreamSequencerBuffer::MaybeAddMoreBlocks(size_t next_expected_byte) {
+  if (current_blocks_count_ == max_blocks_count_) {
+    return;
+  }
+  size_t last_byte = next_expected_byte - 1;
+  size_t num_of_blocks_needed;
+  // As long as last_byte does not wrap around, its index plus one blocks are
+  // needed. Otherwise, block_count_ blocks are needed.
+  if (last_byte < max_buffer_capacity_bytes_) {
+    num_of_blocks_needed =
+        std::max(GetBlockIndex(last_byte) + 1, kInitialBlockCount);
+  } else {
+    num_of_blocks_needed = max_blocks_count_;
+  }
+  if (current_blocks_count_ >= num_of_blocks_needed) {
+    return;
+  }
+  size_t new_block_count_ =
+      std::clamp(kBlocksGrowthFactor * current_blocks_count_,
+                 num_of_blocks_needed, max_blocks_count_);
+  auto new_blocks = std::make_unique<BufferBlock*[]>(new_block_count_);
+  if (blocks_ != nullptr) {
+    memcpy(new_blocks.get(), blocks_.get(),
+           current_blocks_count_ * sizeof(BufferBlock*));
+  }
+  blocks_ = std::move(new_blocks);
+  current_blocks_count_ = new_block_count_;
+}
+
 QuicErrorCode QuicStreamSequencerBuffer::OnStreamData(
     QuicStreamOffset starting_offset,
     absl::string_view data,
@@ -83,6 +121,12 @@
     *error_details = "Received data beyond available range.";
     return QUIC_INTERNAL_ERROR;
   }
+  if (allocate_blocks_on_demand_) {
+    QUIC_RELOADABLE_FLAG_COUNT(
+        quic_allocate_stream_sequencer_buffer_blocks_on_demand);
+    MaybeAddMoreBlocks(starting_offset + size);
+  }
+
   if (bytes_received_.Empty() ||
       starting_offset >= bytes_received_.rbegin()->max() ||
       bytes_received_.IsDisjoint(QuicInterval<QuicStreamOffset>(
@@ -150,7 +194,9 @@
   while (source_remaining > 0) {
     const size_t write_block_num = GetBlockIndex(offset);
     const size_t write_block_offset = GetInBlockOffset(offset);
-    DCHECK_GT(blocks_count_, write_block_num);
+    size_t current_blocks_count =
+        allocate_blocks_on_demand_ ? current_blocks_count_ : max_blocks_count_;
+    DCHECK_GT(current_blocks_count, write_block_num);
 
     size_t block_capacity = GetBlockCapacity(write_block_num);
     size_t bytes_avail = block_capacity - write_block_offset;
@@ -161,19 +207,21 @@
       bytes_avail = total_bytes_read_ + max_buffer_capacity_bytes_ - offset;
     }
 
-    if (blocks_ == nullptr) {
-      blocks_.reset(new BufferBlock*[blocks_count_]());
-      for (size_t i = 0; i < blocks_count_; ++i) {
-        blocks_[i] = nullptr;
+    if (!allocate_blocks_on_demand_) {
+      if (blocks_ == nullptr) {
+        blocks_.reset(new BufferBlock*[max_blocks_count_]());
+        for (size_t i = 0; i < max_blocks_count_; ++i) {
+          blocks_[i] = nullptr;
+        }
       }
     }
 
-    if (write_block_num >= blocks_count_) {
+    if (write_block_num >= current_blocks_count) {
       *error_details = quiche::QuicheStrCat(
           "QuicStreamSequencerBuffer error: OnStreamData() exceed array bounds."
           "write offset = ",
           offset, " write_block_num = ", write_block_num,
-          " blocks_count_ = ", blocks_count_);
+          " current_blocks_count_ = ", current_blocks_count);
       return false;
     }
     if (blocks_ == nullptr) {
@@ -307,14 +355,14 @@
   // before gap is met or |iov| is filled. For these blocks, one whole block is
   // a region.
   int iov_used = 1;
-  size_t block_idx = (start_block_idx + iov_used) % blocks_count_;
+  size_t block_idx = (start_block_idx + iov_used) % max_blocks_count_;
   while (block_idx != end_block_idx && iov_used < iov_len) {
     DCHECK(nullptr != blocks_[block_idx]);
     iov[iov_used].iov_base = blocks_[block_idx]->buffer;
     iov[iov_used].iov_len = GetBlockCapacity(block_idx);
     QUIC_DVLOG(1) << "Got block with index: " << block_idx;
     ++iov_used;
-    block_idx = (start_block_idx + iov_used) % blocks_count_;
+    block_idx = (start_block_idx + iov_used) % max_blocks_count_;
   }
 
   // Deal with last block if |iov| can hold more.
@@ -397,6 +445,7 @@
 
 void QuicStreamSequencerBuffer::ReleaseWholeBuffer() {
   Clear();
+  current_blocks_count_ = 0;
   blocks_.reset(nullptr);
 }
 
@@ -472,7 +521,7 @@
 }
 
 size_t QuicStreamSequencerBuffer::GetBlockCapacity(size_t block_index) const {
-  if ((block_index + 1) == blocks_count_) {
+  if ((block_index + 1) == max_blocks_count_) {
     size_t result = max_buffer_capacity_bytes_ % kBlockSizeBytes;
     if (result == 0) {  // whole block
       result = kBlockSizeBytes;
diff --git a/quic/core/quic_stream_sequencer_buffer.h b/quic/core/quic_stream_sequencer_buffer.h
index 98ffa9c..66acc53 100644
--- a/quic/core/quic_stream_sequencer_buffer.h
+++ b/quic/core/quic_stream_sequencer_buffer.h
@@ -82,7 +82,14 @@
   // Size of blocks used by this buffer.
   // Choose 8K to make block large enough to hold multiple frames, each of
   // which could be up to 1.5 KB.
-  static const size_t kBlockSizeBytes = 8 * 1024;  // 8KB
+  static constexpr size_t kBlockSizeBytes = 8 * 1024;  // 8KB
+
+  // Number of blocks allocated initially.
+  static constexpr size_t kInitialBlockCount = 8u;
+
+  // How fast block pointers container grow in size.
+  // Choose 4 to reduce the amount of reallocation.
+  static constexpr int kBlocksGrowthFactor = 4;
 
   // The basic storage block used by this buffer.
   struct QUIC_EXPORT_PRIVATE BufferBlock {
@@ -212,17 +219,29 @@
   // Return all received frames as a string.
   std::string ReceivedFramesDebugString() const;
 
+  // Resize blocks_ if more blocks are needed to accomodate bytes before
+  // next_expected_byte.
+  void MaybeAddMoreBlocks(size_t next_expected_byte);
+
   // The maximum total capacity of this buffer in byte, as constructed.
   size_t max_buffer_capacity_bytes_;
 
-  // How many blocks this buffer would need when it reaches full capacity.
-  size_t blocks_count_;
+  // Number of blocks this buffer would have when it reaches full capacity,
+  // i.e., maximal number of blocks in blocks_.
+  size_t max_blocks_count_;
+
+  // Number of blocks this buffer currently has.
+  size_t current_blocks_count_;
 
   // Number of bytes read out of buffer.
   QuicStreamOffset total_bytes_read_;
 
+  // Whether size of blocks_ grows on demand.
+  bool allocate_blocks_on_demand_ = GetQuicReloadableFlag(
+      quic_allocate_stream_sequencer_buffer_blocks_on_demand);
+
   // An ordered, variable-length list of blocks, with the length limited
-  // such that the number of blocks never exceeds blocks_count_.
+  // such that the number of blocks never exceeds max_blocks_count_.
   // Each list entry can hold up to kBlockSizeBytes bytes.
   std::unique_ptr<BufferBlock*[]> blocks_;
 
diff --git a/quic/core/quic_stream_sequencer_buffer_test.cc b/quic/core/quic_stream_sequencer_buffer_test.cc
index f12810b..e6a8914 100644
--- a/quic/core/quic_stream_sequencer_buffer_test.cc
+++ b/quic/core/quic_stream_sequencer_buffer_test.cc
@@ -72,10 +72,10 @@
     helper_ = std::make_unique<QuicStreamSequencerBufferPeer>((buffer_.get()));
   }
 
-  // Use 2.5 here to make sure the buffer has more than one block and its end
-  // doesn't align with the end of a block in order to test all the offset
-  // calculation.
-  size_t max_capacity_bytes_ = 2.5 * kBlockSizeBytes;
+  // Use 8.5 here to make sure that the buffer has more than
+  // QuicStreamSequencerBuffer::kInitialBlockCount block and its end doesn't
+  // align with the end of a block in order to test all the offset calculation.
+  size_t max_capacity_bytes_ = 8.5 * kBlockSizeBytes;
 
   std::unique_ptr<QuicStreamSequencerBuffer> buffer_;
   std::unique_ptr<QuicStreamSequencerBufferPeer> helper_;
@@ -86,18 +86,18 @@
 TEST_F(QuicStreamSequencerBufferTest, InitializeWithMaxRecvWindowSize) {
   ResetMaxCapacityBytes(16 * 1024 * 1024);  // 16MB
   EXPECT_EQ(2 * 1024u,                      // 16MB / 8KB = 2K
-            helper_->block_count());
+            helper_->max_blocks_count());
   EXPECT_EQ(max_capacity_bytes_, helper_->max_buffer_capacity());
   EXPECT_TRUE(helper_->CheckInitialState());
 }
 
 TEST_F(QuicStreamSequencerBufferTest, InitializationWithDifferentSizes) {
-  const size_t kCapacity = 2 * QuicStreamSequencerBuffer::kBlockSizeBytes;
+  const size_t kCapacity = 16 * QuicStreamSequencerBuffer::kBlockSizeBytes;
   ResetMaxCapacityBytes(kCapacity);
   EXPECT_EQ(max_capacity_bytes_, helper_->max_buffer_capacity());
   EXPECT_TRUE(helper_->CheckInitialState());
 
-  const size_t kCapacity1 = 8 * QuicStreamSequencerBuffer::kBlockSizeBytes;
+  const size_t kCapacity1 = 32 * QuicStreamSequencerBuffer::kBlockSizeBytes;
   ResetMaxCapacityBytes(kCapacity1);
   EXPECT_EQ(kCapacity1, helper_->max_buffer_capacity());
   EXPECT_TRUE(helper_->CheckInitialState());
@@ -399,7 +399,7 @@
   // Try to write from [max_capacity_bytes_ - 0.5 * kBlockSizeBytes,
   // max_capacity_bytes_ +  512 + 1). But last bytes exceeds current capacity.
   source = std::string(0.5 * kBlockSizeBytes + 512 + 1, 'b');
-  EXPECT_THAT(buffer_->OnStreamData(2 * kBlockSizeBytes, source, &written_,
+  EXPECT_THAT(buffer_->OnStreamData(8 * kBlockSizeBytes, source, &written_,
                                     &error_details_),
               IsError(QUIC_INTERNAL_ERROR));
   EXPECT_TRUE(helper_->CheckBufferInvariants());
@@ -523,15 +523,15 @@
 
 TEST_F(QuicStreamSequencerBufferTest,
        GetReadableRegionsWithMultipleIOVsAcrossEnd) {
-  // Write into [0, 2 * kBlockSizeBytes + 1024) and then read out [0, 1024)
+  // Write into [0, 8.5 * kBlockSizeBytes - 1024) and then read out [0, 1024)
   // and then append 1024 + 512 bytes.
-  std::string source(2.5 * kBlockSizeBytes - 1024, 'a');
+  std::string source(8.5 * kBlockSizeBytes - 1024, 'a');
   buffer_->OnStreamData(0, source, &written_, &error_details_);
   char dest[1024];
   helper_->Read(dest, 1024);
   // Write across the end.
   source = std::string(1024 + 512, 'b');
-  buffer_->OnStreamData(2.5 * kBlockSizeBytes - 1024, source, &written_,
+  buffer_->OnStreamData(8.5 * kBlockSizeBytes - 1024, source, &written_,
                         &error_details_);
   // Use short iovec's.
   iovec iovs[2];
@@ -540,11 +540,11 @@
   EXPECT_EQ(kBlockSizeBytes - 1024, iovs[0].iov_len);
   EXPECT_EQ(kBlockSizeBytes, iovs[1].iov_len);
   // Use long iovec's and wrap the end of buffer.
-  iovec iovs1[5];
-  EXPECT_EQ(4, buffer_->GetReadableRegions(iovs1, 5));
-  EXPECT_EQ(0.5 * kBlockSizeBytes, iovs1[2].iov_len);
-  EXPECT_EQ(512u, iovs1[3].iov_len);
-  EXPECT_EQ(std::string(512, 'b'), IovecToStringPiece(iovs1[3]));
+  iovec iovs1[11];
+  EXPECT_EQ(10, buffer_->GetReadableRegions(iovs1, 11));
+  EXPECT_EQ(0.5 * kBlockSizeBytes, iovs1[8].iov_len);
+  EXPECT_EQ(512u, iovs1[9].iov_len);
+  EXPECT_EQ(std::string(512, 'b'), IovecToStringPiece(iovs1[9]));
 }
 
 TEST_F(QuicStreamSequencerBufferTest, GetReadableRegionEmpty) {
@@ -795,20 +795,20 @@
 }
 
 TEST_F(QuicStreamSequencerBufferTest, MarkConsumedAcrossEnd) {
-  // Write into [0, 2.5 * kBlockSizeBytes - 1024) and then read out [0, 1024)
+  // Write into [0, 8.5 * kBlockSizeBytes - 1024) and then read out [0, 1024)
   // and then append 1024 + 512 bytes.
-  std::string source(2.5 * kBlockSizeBytes - 1024, 'a');
+  std::string source(8.5 * kBlockSizeBytes - 1024, 'a');
   buffer_->OnStreamData(0, source, &written_, &error_details_);
   char dest[1024];
   helper_->Read(dest, 1024);
   source = std::string(1024 + 512, 'b');
-  buffer_->OnStreamData(2.5 * kBlockSizeBytes - 1024, source, &written_,
+  buffer_->OnStreamData(8.5 * kBlockSizeBytes - 1024, source, &written_,
                         &error_details_);
   EXPECT_EQ(1024u, buffer_->BytesConsumed());
 
-  // Consume to the end of 2nd block.
-  buffer_->MarkConsumed(2 * kBlockSizeBytes - 1024);
-  EXPECT_EQ(2 * kBlockSizeBytes, buffer_->BytesConsumed());
+  // Consume to the end of 8th block.
+  buffer_->MarkConsumed(8 * kBlockSizeBytes - 1024);
+  EXPECT_EQ(8 * kBlockSizeBytes, buffer_->BytesConsumed());
   // Consume across the physical end of buffer
   buffer_->MarkConsumed(0.5 * kBlockSizeBytes + 500);
   EXPECT_EQ(max_capacity_bytes_ + 500, buffer_->BytesConsumed());
@@ -821,7 +821,7 @@
 }
 
 TEST_F(QuicStreamSequencerBufferTest, FlushBufferedFrames) {
-  // Write into [0, 2.5 * kBlockSizeBytes - 1024) and then read out [0, 1024).
+  // Write into [0, 8.5 * kBlockSizeBytes - 1024) and then read out [0, 1024).
   std::string source(max_capacity_bytes_ - 1024, 'a');
   buffer_->OnStreamData(0, source, &written_, &error_details_);
   char dest[1024];
@@ -869,7 +869,7 @@
   void SetUp() override {
     // Test against a larger capacity then above tests. Also make sure the last
     // block is partially available to use.
-    max_capacity_bytes_ = 6.25 * kBlockSizeBytes;
+    max_capacity_bytes_ = 8.25 * kBlockSizeBytes;
     // Stream to be buffered should be larger than the capacity to test wrap
     // around.
     bytes_to_buffer_ = 2 * max_capacity_bytes_;
@@ -1090,6 +1090,50 @@
   EXPECT_LE(bytes_to_buffer_, total_bytes_written_);
 }
 
+TEST_F(QuicStreamSequencerBufferTest, GrowBlockSizeOnDemand) {
+  SetQuicReloadableFlag(quic_allocate_stream_sequencer_buffer_blocks_on_demand,
+                        true);
+  max_capacity_bytes_ = 1024 * kBlockSizeBytes;
+  std::string source_of_one_block(kBlockSizeBytes, 'a');
+  Initialize();
+
+  ASSERT_EQ(helper_->current_blocks_count(), 0u);
+
+  // A minimum of 8 blocks are allocated
+  buffer_->OnStreamData(0, source_of_one_block, &written_, &error_details_);
+  ASSERT_EQ(helper_->current_blocks_count(), 8u);
+
+  // Number of blocks doesn't grow if the data is within the capacity.
+  buffer_->OnStreamData(kBlockSizeBytes * 7, source_of_one_block, &written_,
+                        &error_details_);
+  ASSERT_EQ(helper_->current_blocks_count(), 8u);
+
+  // Number of blocks grows by a factor of 4 normally.
+  buffer_->OnStreamData(kBlockSizeBytes * 8, "a", &written_, &error_details_);
+  ASSERT_EQ(helper_->current_blocks_count(), 32u);
+
+  // Number of blocks grow to the demanded size of 140 instead of 128 since
+  // that's not enough.
+  buffer_->OnStreamData(kBlockSizeBytes * 139, source_of_one_block, &written_,
+                        &error_details_);
+  ASSERT_EQ(helper_->current_blocks_count(), 140u);
+
+  // Number of blocks grows by a factor of 4 normally.
+  buffer_->OnStreamData(kBlockSizeBytes * 140, source_of_one_block, &written_,
+                        &error_details_);
+  ASSERT_EQ(helper_->current_blocks_count(), 560u);
+
+  // max_capacity_bytes is reached and number of blocks is capped.
+  buffer_->OnStreamData(kBlockSizeBytes * 560, source_of_one_block, &written_,
+                        &error_details_);
+  ASSERT_EQ(helper_->current_blocks_count(), 1024u);
+
+  // max_capacity_bytes is reached and number of blocks is capped.
+  buffer_->OnStreamData(kBlockSizeBytes * 1025, source_of_one_block, &written_,
+                        &error_details_);
+  ASSERT_EQ(helper_->current_blocks_count(), 1024u);
+}
+
 }  // anonymous namespace
 
 }  // namespace test
diff --git a/quic/test_tools/quic_stream_sequencer_buffer_peer.cc b/quic/test_tools/quic_stream_sequencer_buffer_peer.cc
index b3bf224..eaf2c31 100644
--- a/quic/test_tools/quic_stream_sequencer_buffer_peer.cc
+++ b/quic/test_tools/quic_stream_sequencer_buffer_peer.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "net/third_party/quiche/src/quic/test_tools/quic_stream_sequencer_buffer_peer.h"
+#include <cstddef>
 
 #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
@@ -45,7 +46,8 @@
     return true;
   }
 
-  size_t count = buffer_->blocks_count_;
+  size_t count = buffer_->allocate_blocks_on_demand_ ? current_blocks_count()
+                                                     : max_blocks_count();
   for (size_t i = 0; i < count; i++) {
     if (buffer_->blocks_[i] != nullptr) {
       return false;
@@ -79,10 +81,11 @@
   if (!capacity_sane) {
     QUIC_LOG(ERROR) << "read offset go beyond 1st block";
   }
-  bool block_match_capacity = (buffer_->max_buffer_capacity_bytes_ <=
-                               buffer_->blocks_count_ * kBlockSizeBytes) &&
-                              (buffer_->max_buffer_capacity_bytes_ >
-                               (buffer_->blocks_count_ - 1) * kBlockSizeBytes);
+  bool block_match_capacity =
+      (buffer_->max_buffer_capacity_bytes_ <=
+       buffer_->max_blocks_count_ * kBlockSizeBytes) &&
+      (buffer_->max_buffer_capacity_bytes_ >
+       (buffer_->max_blocks_count_ - 1) * kBlockSizeBytes);
   if (!capacity_sane) {
     QUIC_LOG(ERROR) << "block number not match capcaity.";
   }
@@ -143,8 +146,12 @@
   return buffer_->blocks_ != nullptr;
 }
 
-size_t QuicStreamSequencerBufferPeer::block_count() {
-  return buffer_->blocks_count_;
+size_t QuicStreamSequencerBufferPeer::max_blocks_count() {
+  return buffer_->max_blocks_count_;
+}
+
+size_t QuicStreamSequencerBufferPeer::current_blocks_count() {
+  return buffer_->current_blocks_count_;
 }
 
 const QuicIntervalSet<QuicStreamOffset>&
diff --git a/quic/test_tools/quic_stream_sequencer_buffer_peer.h b/quic/test_tools/quic_stream_sequencer_buffer_peer.h
index da2d054..4cac6f1 100644
--- a/quic/test_tools/quic_stream_sequencer_buffer_peer.h
+++ b/quic/test_tools/quic_stream_sequencer_buffer_peer.h
@@ -49,7 +49,9 @@
 
   bool IsBufferAllocated();
 
-  size_t block_count();
+  size_t max_blocks_count();
+
+  size_t current_blocks_count();
 
   const QuicIntervalSet<QuicStreamOffset>& bytes_received();