Updates OgHttp2Session's internal flow control window state when sending outbound WINDOW_UPDATE frames.
Note that this requires a small change to our usage of WindowManager to avoid double-counting in the case of internally generated WINDOW_UPDATE frames.
PiperOrigin-RevId: 429317678
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index 5ff0020..5791692 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -264,6 +264,37 @@
EXPECT_THAT(visitor.data(), testing::IsEmpty());
}
+TEST(NgHttp2AdapterTest, QueuingWindowUpdateAffectsWindow) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ EXPECT_EQ(adapter->GetReceiveWindowSize(), kInitialFlowControlWindowSize);
+ adapter->SubmitWindowUpdate(0, 10000);
+ EXPECT_EQ(adapter->GetReceiveWindowSize(),
+ kInitialFlowControlWindowSize + 10000);
+
+ const std::vector<Header> headers =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+ const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(WINDOW_UPDATE, 0, 4, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 0, 4, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+
+ EXPECT_EQ(adapter->GetStreamReceiveWindowSize(stream_id),
+ kInitialFlowControlWindowSize);
+ adapter->SubmitWindowUpdate(1, 20000);
+ EXPECT_EQ(adapter->GetStreamReceiveWindowSize(stream_id),
+ kInitialFlowControlWindowSize + 20000);
+}
+
TEST(NgHttp2AdapterTest, ClientRejects100HeadersWithFin) {
DataSavingVisitor visitor;
auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index 2e37880..5b4637b 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -345,6 +345,40 @@
EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING}));
}
+TEST(OgHttp2AdapterTest, QueuingWindowUpdateAffectsWindow) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ EXPECT_EQ(adapter->GetReceiveWindowSize(), kInitialFlowControlWindowSize);
+ adapter->SubmitWindowUpdate(0, 10000);
+ EXPECT_EQ(adapter->GetReceiveWindowSize(),
+ kInitialFlowControlWindowSize + 10000);
+
+ const std::vector<Header> headers =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+ const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(WINDOW_UPDATE, 0, 4, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 0, 4, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+
+ EXPECT_EQ(adapter->GetStreamReceiveWindowSize(stream_id),
+ kInitialFlowControlWindowSize);
+ adapter->SubmitWindowUpdate(1, 20000);
+ EXPECT_EQ(adapter->GetStreamReceiveWindowSize(stream_id),
+ kInitialFlowControlWindowSize + 20000);
+}
+
TEST(OgHttp2AdapterTest, ClientRejects100HeadersWithFin) {
DataSavingVisitor visitor;
OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index 773199c..b7016a5 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -329,11 +329,12 @@
[]() { return kTraceLoggingEnabled; }, this),
headers_handler_(*this, visitor),
noop_headers_handler_(/*listener=*/nullptr),
- connection_window_manager_(kInitialFlowControlWindowSize,
- [this](size_t window_update_delta) {
- SendWindowUpdate(kConnectionStreamId,
- window_update_delta);
- }),
+ connection_window_manager_(
+ kInitialFlowControlWindowSize,
+ [this](size_t window_update_delta) {
+ SendWindowUpdate(kConnectionStreamId, window_update_delta);
+ },
+ /*update_window_on_notify=*/false),
options_(options) {
decoder_.set_visitor(&receive_logger_);
decoder_.set_extension_visitor(this);
@@ -539,6 +540,10 @@
// TODO(diannahu): Condition on existence in the stream map?
streams_reset_.insert(frame->stream_id());
}
+ } else if (frame->frame_type() == spdy::SpdyFrameType::WINDOW_UPDATE) {
+ UpdateReceiveWindow(
+ frame->stream_id(),
+ reinterpret_cast<spdy::SpdyWindowUpdateIR&>(*frame).delta());
}
if (frame->stream_id() != 0) {
auto result = queued_frames_.insert({frame->stream_id(), 1});
@@ -1782,6 +1787,18 @@
stream_id, spdy::ERROR_CODE_PROTOCOL_ERROR));
}
+void OgHttp2Session::UpdateReceiveWindow(Http2StreamId stream_id,
+ int32_t delta) {
+ if (stream_id == 0) {
+ connection_window_manager_.IncreaseWindow(delta);
+ } else {
+ auto iter = stream_map_.find(stream_id);
+ if (iter != stream_map_.end()) {
+ iter->second.window_manager.IncreaseWindow(delta);
+ }
+ }
+}
+
void OgHttp2Session::UpdateInitialWindowSize(uint32_t new_value) {
const int32_t delta =
static_cast<int32_t>(new_value) - initial_stream_send_window_;
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index 29bff34..cdffd4f 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -214,7 +214,8 @@
struct QUICHE_EXPORT_PRIVATE StreamState {
StreamState(int32_t stream_receive_window, int32_t stream_send_window,
WindowManager::WindowUpdateListener listener)
- : window_manager(stream_receive_window, std::move(listener)),
+ : window_manager(stream_receive_window, std::move(listener),
+ /*update_window_on_notify=*/false),
send_window(stream_send_window) {}
WindowManager window_manager;
@@ -401,6 +402,8 @@
void HandleContentLengthError(Http2StreamId stream_id);
+ void UpdateReceiveWindow(Http2StreamId stream_id, int32_t delta);
+
void UpdateInitialWindowSize(uint32_t new_value);
// Receives events when inbound frames are parsed.