Add a configuration option for a custom WINDOW_UPDATE strategy in OgHttp2Session.
This CL adds an entry to Http2Options for a custom
WindowManager::ShouldWindowUpdateFn, as a WINDOW_UPDATE sending strategy, that
the OgHttp2Session WindowManager (for connection and streams) would use in
place of the default strategy.
This CL introduces DeltaAtLeastHalfLimit in the spirit of the nghttp2 analogue:
http://google3/third_party/nghttp2/src/lib/nghttp2_helper.c;l=248-251;rcl=431825270
Http2CodecImplFlowControlTest.TestFlowControlInPendingSendData passes with
these changes using oghttp2:
http://sponge2/ff7fda20-364a-4594-95c2-d3cccd70ac69
PiperOrigin-RevId: 432298189
diff --git a/http2/adapter/http2_util.cc b/http2/adapter/http2_util.cc
index 0419952..fd21173 100644
--- a/http2/adapter/http2_util.cc
+++ b/http2/adapter/http2_util.cc
@@ -122,5 +122,9 @@
return "UnknownInvalidFrameError";
}
+bool DeltaAtLeastHalfLimit(int64_t limit, int64_t /*size*/, int64_t delta) {
+ return delta > 0 && delta >= limit / 2;
+}
+
} // namespace adapter
} // namespace http2
diff --git a/http2/adapter/http2_util.h b/http2/adapter/http2_util.h
index 3e25b8c..feea543 100644
--- a/http2/adapter/http2_util.h
+++ b/http2/adapter/http2_util.h
@@ -1,6 +1,8 @@
#ifndef QUICHE_HTTP2_ADAPTER_HTTP2_UTIL_H_
#define QUICHE_HTTP2_ADAPTER_HTTP2_UTIL_H_
+#include <cstdint>
+
#include "http2/adapter/http2_protocol.h"
#include "http2/adapter/http2_visitor_interface.h"
#include "common/platform/api/quiche_export.h"
@@ -20,6 +22,12 @@
QUICHE_EXPORT_PRIVATE absl::string_view InvalidFrameErrorToString(
Http2VisitorInterface::InvalidFrameError error);
+// A WINDOW_UPDATE sending strategy that returns true if the `delta` to be sent
+// is positive and at least half of the window `limit`.
+QUICHE_EXPORT_PRIVATE bool DeltaAtLeastHalfLimit(int64_t limit,
+ int64_t /*size*/,
+ int64_t delta);
+
} // namespace adapter
} // namespace http2
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index 91768f6..e797e48 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -4742,11 +4742,11 @@
// without recursion guards in OgHttp2Session.
TEST(OgHttp2AdapterInteractionTest, ClientServerInteractionTest) {
MockHttp2Visitor client_visitor;
- auto client_adapter =
- OgHttp2Adapter::Create(client_visitor, {Perspective::kClient});
+ auto client_adapter = OgHttp2Adapter::Create(
+ client_visitor, {.perspective = Perspective::kClient});
MockHttp2Visitor server_visitor;
- auto server_adapter =
- OgHttp2Adapter::Create(server_visitor, {Perspective::kServer});
+ auto server_adapter = OgHttp2Adapter::Create(
+ server_visitor, {.perspective = Perspective::kServer});
// Feeds bytes sent from the client into the server's ProcessBytes.
EXPECT_CALL(client_visitor, OnReadyToSend(_))
@@ -6018,6 +6018,68 @@
SpdyFrameType::RST_STREAM}));
}
+TEST(OgHttp2AdapterTest, ServerUsesCustomWindowUpdateStrategy) {
+ // Test the use of a custom WINDOW_UPDATE strategy.
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{
+ .should_window_update_fn = [](int64_t /*limit*/, int64_t /*size*/,
+ int64_t /*delta*/) { return true; },
+ .perspective = Perspective::kServer};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/false)
+ .Data(1, "This is the request body.",
+ /*fin=*/true)
+ .Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4);
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0x1));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, _));
+ EXPECT_CALL(visitor, OnDataForStream(1, "This is the request body."));
+ EXPECT_CALL(visitor, OnEndStream(1));
+
+ const int64_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(static_cast<int64_t>(frames.size()), result);
+
+ // Mark a small number of bytes for the stream as consumed. Because of the
+ // custom WINDOW_UPDATE strategy, the session should send WINDOW_UPDATEs.
+ adapter->MarkDataConsumedForStream(1, 5);
+
+ EXPECT_TRUE(adapter->want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(WINDOW_UPDATE, 1, 4, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 1, 4, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(WINDOW_UPDATE, 0, 4, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 0, 4, 0x0, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS,
+ SpdyFrameType::WINDOW_UPDATE,
+ SpdyFrameType::WINDOW_UPDATE}));
+}
+
} // namespace
} // namespace test
} // namespace adapter
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index 89d6d6f..35f498c 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -334,7 +334,7 @@
[this](size_t window_update_delta) {
SendWindowUpdate(kConnectionStreamId, window_update_delta);
},
- /*should_window_update_fn=*/{},
+ options.should_window_update_fn,
/*update_window_on_notify=*/false),
options_(options) {
decoder_.set_visitor(&receive_logger_);
@@ -1650,8 +1650,9 @@
SendWindowUpdate(stream_id, window_update_delta);
};
auto [iter, inserted] = stream_map_.try_emplace(
- stream_id, StreamState(initial_stream_receive_window_,
- initial_stream_send_window_, std::move(listener)));
+ stream_id,
+ StreamState(initial_stream_receive_window_, initial_stream_send_window_,
+ std::move(listener), options_.should_window_update_fn));
if (inserted) {
// Add the stream to the write scheduler.
const WriteScheduler::StreamPrecedenceType precedence(3);
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index c9eed29..045eac4 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -38,6 +38,11 @@
public spdy::ExtensionVisitorInterface {
public:
struct QUICHE_EXPORT_PRIVATE Options {
+ // Returns whether to send a WINDOW_UPDATE based on the window limit, window
+ // size, and delta that would be sent in the WINDOW_UPDATE.
+ WindowManager::ShouldWindowUpdateFn should_window_update_fn =
+ DeltaAtLeastHalfLimit;
+ // The perspective of this session.
Perspective perspective = Perspective::kClient;
// The maximum HPACK table size to use.
absl::optional<size_t> max_hpack_encoding_table_capacity = absl::nullopt;
@@ -213,9 +218,10 @@
struct QUICHE_EXPORT_PRIVATE StreamState {
StreamState(int32_t stream_receive_window, int32_t stream_send_window,
- WindowManager::WindowUpdateListener listener)
+ WindowManager::WindowUpdateListener listener,
+ WindowManager::ShouldWindowUpdateFn should_window_update_fn)
: window_manager(stream_receive_window, std::move(listener),
- /*should_window_update_fn=*/{},
+ std::move(should_window_update_fn),
/*update_window_on_notify=*/false),
send_window(stream_send_window) {}