Add a regression test for b/488057588.

PiperOrigin-RevId: 884597309
diff --git a/quiche/quic/core/http/quic_spdy_stream_test.cc b/quiche/quic/core/http/quic_spdy_stream_test.cc
index 401fdad..8acea52 100644
--- a/quiche/quic/core/http/quic_spdy_stream_test.cc
+++ b/quiche/quic/core/http/quic_spdy_stream_test.cc
@@ -30,6 +30,7 @@
 #include "quiche/quic/core/quic_connection.h"
 #include "quiche/quic/core/quic_stream_priority.h"
 #include "quiche/quic/core/quic_stream_sequencer_buffer.h"
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/core/quic_versions.h"
 #include "quiche/quic/core/quic_write_blocked_list.h"
@@ -250,12 +251,21 @@
     if (!should_process_data_) {
       return;
     }
-    char buffer[2048];
-    struct iovec vec;
-    vec.iov_base = buffer;
-    vec.iov_len = ABSL_ARRAYSIZE(buffer);
-    size_t bytes_read = Readv(&vec, 1);
-    data_ += std::string(buffer, bytes_read);
+    if (read_side_closed()) {
+      return;
+    }
+    if (HasBytesToRead()) {
+      char buffer[2048];
+      struct iovec vec;
+      vec.iov_base = buffer;
+      vec.iov_len = ABSL_ARRAYSIZE(buffer);
+      size_t bytes_read = Readv(&vec, 1);
+      data_ += std::string(buffer, bytes_read);
+    }
+    if (!sequencer()->IsClosed() || read_side_closed()) {
+      return;
+    }
+    OnFinRead();
   }
 
   void OnSoonToBeDestroyed() override {
@@ -3825,6 +3835,62 @@
   }
 }
 
+// Regression test for b/488057588.
+TEST_P(QuicSpdyStreamTest, ReadSideNotClosedAfterStopReading) {
+  if (!IsIetfQuic()) {
+    return;
+  }
+  SetQuicReloadableFlag(quic_clear_body_manager_along_with_sequencer, true);
+
+  Initialize(kShouldProcessData);
+
+  QuicHeaderList headers = ProcessHeaders(false, headers_);
+  stream_->ConsumeHeaderList();
+
+  // In Envoy QUIC, application can stop processing data before getting read
+  // blocked.
+  stream_->set_should_process_data(false);
+  std::string body = "this is the body";
+  std::string data = DataFrame(body);
+  QuicStreamOffset offset = 0;
+  QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), /*fin=*/false,
+                         offset, data);
+  offset += data.size();
+  stream_->OnStreamFrame(frame1);
+  EXPECT_TRUE(stream_->sequencer()->HasBytesToRead());
+  EXPECT_TRUE(QuicSpdyStreamPeer::BodyManager(stream_).HasBytesToRead());
+
+  // QUIC gets blocked.
+  stream_->sequencer()->SetBlockedUntilFlush();
+
+  // In Envoy QUIC, StopReading can be called after local reset.
+  stream_->StopReading();
+  // Sequencer is not closed but the bytes in body_manager is cleared.
+  EXPECT_FALSE(stream_->sequencer()->IsClosed());
+  EXPECT_FALSE(QuicSpdyStreamPeer::BodyManager(stream_).HasBytesToRead());
+
+  // FIN does not close the read side since the stream is blocked.
+  QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(0), /*fin=*/true,
+                         offset, "second body");
+  stream_->OnStreamFrame(frame2);
+  EXPECT_FALSE(stream_->sequencer()->IsClosed());
+
+  // Because sequencer has bytes to read, On DataAvailable is called.
+  // However, since neither body_manager has bytes to read nor sequencer is
+  // closed,  OnBodyAvailable is not called to close the read side when
+  // SetUnblocked is called.
+  EXPECT_FALSE(QuicSpdyStreamPeer::BodyManager(stream_).HasBytesToRead());
+  EXPECT_FALSE(stream_->sequencer()->IsClosed());
+  EXPECT_TRUE(stream_->sequencer()->HasBytesToRead());
+
+  // Application gets unblocked.
+  stream_->set_should_process_data(true);
+  // QUIC gets unblocked.
+  stream_->sequencer()->SetUnblocked();
+
+  EXPECT_FALSE(stream_->read_side_closed());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/test_tools/quic_spdy_stream_peer.cc b/quiche/quic/test_tools/quic_spdy_stream_peer.cc
index 972440b..61cea11 100644
--- a/quiche/quic/test_tools/quic_spdy_stream_peer.cc
+++ b/quiche/quic/test_tools/quic_spdy_stream_peer.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "quiche/quic/core/http/quic_spdy_stream.h"
+#include "quiche/quic/core/http/quic_spdy_stream_body_manager.h"
 #include "quiche/quic/test_tools/quic_test_utils.h"
 
 namespace quic {
@@ -33,5 +34,10 @@
   stream->header_decoding_delay_ = delay;
 }
 
+QuicSpdyStreamBodyManager& QuicSpdyStreamPeer::BodyManager(
+    QuicSpdyStream* stream) {
+  return stream->body_manager_;
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/test_tools/quic_spdy_stream_peer.h b/quiche/quic/test_tools/quic_spdy_stream_peer.h
index f5c2e44..7b7723f 100644
--- a/quiche/quic/test_tools/quic_spdy_stream_peer.h
+++ b/quiche/quic/test_tools/quic_spdy_stream_peer.h
@@ -5,6 +5,7 @@
 #ifndef QUICHE_QUIC_TEST_TOOLS_QUIC_SPDY_STREAM_PEER_H_
 #define QUICHE_QUIC_TEST_TOOLS_QUIC_SPDY_STREAM_PEER_H_
 
+#include "quiche/quic/core/http/quic_spdy_stream_body_manager.h"
 #include "quiche/quic/core/quic_ack_listener_interface.h"
 #include "quiche/quic/core/quic_interval_set.h"
 #include "quiche/quic/core/quic_time.h"
@@ -27,6 +28,7 @@
   static bool OnHeadersFrameEnd(QuicSpdyStream* stream);
   static void set_header_decoding_delay(QuicSpdyStream* stream,
                                         QuicTime::Delta delay);
+  static QuicSpdyStreamBodyManager& BodyManager(QuicSpdyStream* stream);
 };
 
 }  // namespace test