Adds a stream ID watermark to CallbackVisitor. This fixes a bug where CallbackVisitor can recreate state for closed streams, wasting memory. PiperOrigin-RevId: 592546343
diff --git a/quiche/http2/adapter/callback_visitor.cc b/quiche/http2/adapter/callback_visitor.cc index 4577ba6..ea9b04a 100644 --- a/quiche/http2/adapter/callback_visitor.cc +++ b/quiche/http2/adapter/callback_visitor.cc
@@ -2,6 +2,7 @@ #include "absl/strings/escaping.h" #include "quiche/http2/adapter/http2_util.h" +#include "quiche/http2/adapter/nghttp2.h" #include "quiche/http2/adapter/nghttp2_util.h" #include "quiche/common/quiche_endian.h" @@ -151,27 +152,31 @@ bool CallbackVisitor::OnBeginHeadersForStream(Http2StreamId stream_id) { auto it = GetStreamInfo(stream_id); - if (it->second.received_headers) { - // At least one headers frame has already been received. - QUICHE_VLOG(1) - << "Headers already received for stream " << stream_id - << ", these are trailers or headers following a 100 response"; + if (it == stream_map_.end()) { current_frame_.headers.cat = NGHTTP2_HCAT_HEADERS; } else { - switch (perspective_) { - case Perspective::kClient: - QUICHE_VLOG(1) << "First headers at the client for stream " << stream_id - << "; these are response headers"; - current_frame_.headers.cat = NGHTTP2_HCAT_RESPONSE; - break; - case Perspective::kServer: - QUICHE_VLOG(1) << "First headers at the server for stream " << stream_id - << "; these are request headers"; - current_frame_.headers.cat = NGHTTP2_HCAT_REQUEST; - break; + if (it->second.received_headers) { + // At least one headers frame has already been received. + QUICHE_VLOG(1) + << "Headers already received for stream " << stream_id + << ", these are trailers or headers following a 100 response"; + current_frame_.headers.cat = NGHTTP2_HCAT_HEADERS; + } else { + switch (perspective_) { + case Perspective::kClient: + QUICHE_VLOG(1) << "First headers at the client for stream " + << stream_id << "; these are response headers"; + current_frame_.headers.cat = NGHTTP2_HCAT_RESPONSE; + break; + case Perspective::kServer: + QUICHE_VLOG(1) << "First headers at the server for stream " + << stream_id << "; these are request headers"; + current_frame_.headers.cat = NGHTTP2_HCAT_REQUEST; + break; + } } + it->second.received_headers = true; } - it->second.received_headers = true; if (callbacks_->on_begin_headers_callback) { const int result = callbacks_->on_begin_headers_callback( nullptr, ¤t_frame_, user_data_); @@ -408,12 +413,16 @@ << ", flags=" << int(flags) << ")"; if (callbacks_->before_frame_send_callback) { nghttp2_frame frame; + bool before_sent_headers = true; auto it = GetStreamInfo(stream_id); + if (it != stream_map_.end()) { + before_sent_headers = it->second.before_sent_headers; + it->second.before_sent_headers = true; + } // The implementation of the before_frame_send_callback doesn't look at the // error code, so for now it's populated with 0. PopulateFrame(frame, frame_type, stream_id, length, flags, /*error_code=*/0, - it->second.before_sent_headers); - it->second.before_sent_headers = true; + before_sent_headers); return callbacks_->before_frame_send_callback(nullptr, &frame, user_data_); } return 0; @@ -428,10 +437,14 @@ << ")"; if (callbacks_->on_frame_send_callback) { nghttp2_frame frame; + bool sent_headers = true; auto it = GetStreamInfo(stream_id); + if (it != stream_map_.end()) { + sent_headers = it->second.sent_headers; + it->second.sent_headers = true; + } PopulateFrame(frame, frame_type, stream_id, length, flags, error_code, - it->second.sent_headers); - it->second.sent_headers = true; + sent_headers); return callbacks_->on_frame_send_callback(nullptr, &frame, user_data_); } return 0; @@ -501,9 +514,10 @@ CallbackVisitor::StreamInfoMap::iterator CallbackVisitor::GetStreamInfo( Http2StreamId stream_id) { auto it = stream_map_.find(stream_id); - if (it == stream_map_.end()) { + if (it == stream_map_.end() && stream_id > stream_id_watermark_) { auto p = stream_map_.insert({stream_id, {}}); it = p.first; + stream_id_watermark_ = stream_id; } return it; }
diff --git a/quiche/http2/adapter/callback_visitor.h b/quiche/http2/adapter/callback_visitor.h index 497bf95..2f2336b 100644 --- a/quiche/http2/adapter/callback_visitor.h +++ b/quiche/http2/adapter/callback_visitor.h
@@ -107,6 +107,8 @@ nghttp2_frame current_frame_; std::vector<nghttp2_settings_entry> settings_; size_t remaining_data_ = 0; + // Any new stream must have an ID greater than the watermark. + Http2StreamId stream_id_watermark_ = 0; }; } // namespace adapter
diff --git a/quiche/http2/adapter/callback_visitor_test.cc b/quiche/http2/adapter/callback_visitor_test.cc index 88856ee..0da51a8 100644 --- a/quiche/http2/adapter/callback_visitor_test.cc +++ b/quiche/http2/adapter/callback_visitor_test.cc
@@ -548,7 +548,7 @@ OnBeginHeaders(IsHeaders( 1, NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, NGHTTP2_HCAT_REQUEST))); - visitor.OnBeginHeadersForStream(1); + EXPECT_TRUE(visitor.OnBeginHeadersForStream(1)); EXPECT_EQ(visitor.stream_map_size(), 1); @@ -578,11 +578,11 @@ visitor.OnFrameHeader(1, 23, HEADERS, 4); EXPECT_CALL(callbacks, OnBeginHeaders(IsHeaders(1, NGHTTP2_FLAG_END_HEADERS, - NGHTTP2_HCAT_REQUEST))); - visitor.OnBeginHeadersForStream(1); + NGHTTP2_HCAT_HEADERS))); + EXPECT_TRUE(visitor.OnBeginHeadersForStream(1)); - // Bug: the visitor should not revive streams that have already been closed. - EXPECT_EQ(visitor.stream_map_size(), 1); + // The visitor should not revive streams that have already been closed. + EXPECT_EQ(visitor.stream_map_size(), 0); } } // namespace