Enable custom WINDOW_UPDATE notifying criteria in WindowManager.
This CL introduces ShouldNotifyListener(limit, window, delta) to WindowManager
and adds a WindowManager optional constructor parameter ShouldNotifyListener
to override WindowManager's default WINDOW_UPDATE strategy.
The oghttp2 library will make use of this capability by providing a
ShouldNotifyListener aligned with the nghttp2 WINDOW_UPDATE strategy.
PiperOrigin-RevId: 432234434
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index 8b87090..e1be0d8 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -334,6 +334,7 @@
[this](size_t window_update_delta) {
SendWindowUpdate(kConnectionStreamId, window_update_delta);
},
+ /*should_notify_listener=*/{},
/*update_window_on_notify=*/false),
options_(options) {
decoder_.set_visitor(&receive_logger_);
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index a714825..63d54bc 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -215,6 +215,7 @@
StreamState(int32_t stream_receive_window, int32_t stream_send_window,
WindowManager::WindowUpdateListener listener)
: window_manager(stream_receive_window, std::move(listener),
+ /*should_notify_listener=*/{},
/*update_window_on_notify=*/false),
send_window(stream_send_window) {}
diff --git a/http2/adapter/window_manager.cc b/http2/adapter/window_manager.cc
index 3dd9baf..c93356a 100644
--- a/http2/adapter/window_manager.cc
+++ b/http2/adapter/window_manager.cc
@@ -8,14 +8,37 @@
namespace http2 {
namespace adapter {
+bool DefaultShouldNotifyListener(int64_t limit, int64_t window, int64_t delta) {
+ // For the sake of efficiency, we want to send window updates if less than
+ // half of the max quota is available to the peer at any point in time.
+ const int64_t kDesiredMinWindow = limit / 2;
+ const int64_t kDesiredMinDelta = limit / 3;
+ if (delta >= kDesiredMinDelta) {
+ // This particular window update was sent because the available delta
+ // exceeded the desired minimum.
+ return true;
+ } else if (window < kDesiredMinWindow) {
+ // This particular window update was sent because the quota available to the
+ // peer at this moment is less than the desired minimum.
+ return true;
+ }
+ return false;
+}
+
WindowManager::WindowManager(int64_t window_size_limit,
WindowUpdateListener listener,
+ ShouldNotifyListener should_notify_listener,
bool update_window_on_notify)
: limit_(window_size_limit),
window_(window_size_limit),
buffered_(0),
listener_(std::move(listener)),
- update_window_on_notify_(update_window_on_notify) {}
+ should_notify_listener_(std::move(should_notify_listener)),
+ update_window_on_notify_(update_window_on_notify) {
+ if (!should_notify_listener_) {
+ should_notify_listener_ = DefaultShouldNotifyListener;
+ }
+}
void WindowManager::OnWindowSizeLimitChange(const int64_t new_limit) {
QUICHE_VLOG(2) << "WindowManager@" << this
@@ -65,22 +88,8 @@
}
void WindowManager::MaybeNotifyListener() {
- // For the sake of efficiency, we want to send window updates if less than
- // half of the max quota is available to the peer at any point in time.
- const int64_t kDesiredMinWindow = limit_ / 2;
- const int64_t kDesiredMinDelta = limit_ / 3;
const int64_t delta = limit_ - (buffered_ + window_);
- bool send_update = false;
- if (delta >= kDesiredMinDelta) {
- // This particular window update was sent because the available delta
- // exceeded the desired minimum.
- send_update = true;
- } else if (window_ < kDesiredMinWindow) {
- // This particular window update was sent because the quota available to the
- // peer at this moment is less than the desired minimum.
- send_update = true;
- }
- if (send_update && delta > 0) {
+ if (should_notify_listener_(limit_, window_, delta) && delta > 0) {
QUICHE_VLOG(2) << "WindowManager@" << this
<< " Informing listener of delta: " << delta;
listener_(delta);
diff --git a/http2/adapter/window_manager.h b/http2/adapter/window_manager.h
index 45a6ddd..373e602 100644
--- a/http2/adapter/window_manager.h
+++ b/http2/adapter/window_manager.h
@@ -19,9 +19,15 @@
class QUICHE_EXPORT_PRIVATE WindowManager {
public:
// A WindowUpdateListener is invoked when it is time to send a window update.
- typedef std::function<void(int64_t)> WindowUpdateListener;
+ using WindowUpdateListener = std::function<void(int64_t)>;
+
+ // Invoked to determine whether to call the listener based on the window
+ // limit, window size, and delta that would be sent.
+ using ShouldNotifyListener =
+ std::function<bool(int64_t limit, int64_t size, int64_t delta)>;
WindowManager(int64_t window_size_limit, WindowUpdateListener listener,
+ ShouldNotifyListener should_notify_listener = {},
bool update_window_on_notify = true);
int64_t CurrentWindowSize() const { return window_; }
@@ -75,6 +81,8 @@
WindowUpdateListener listener_;
+ ShouldNotifyListener should_notify_listener_;
+
bool update_window_on_notify_;
};
diff --git a/http2/adapter/window_manager_test.cc b/http2/adapter/window_manager_test.cc
index c5a9ffb..27b5e21 100644
--- a/http2/adapter/window_manager_test.cc
+++ b/http2/adapter/window_manager_test.cc
@@ -218,11 +218,13 @@
WindowManager wm1(
kDefaultLimit,
[&call_sequence1](int64_t delta) { call_sequence1.push_back(delta); },
+ /*should_notify_listener=*/{},
/*update_window_on_notify=*/true); // default
std::list<int64_t> call_sequence2;
WindowManager wm2(
kDefaultLimit,
[&call_sequence2](int64_t delta) { call_sequence2.push_back(delta); },
+ /*should_notify_listener=*/{},
/*update_window_on_notify=*/false);
const int64_t consumed = kDefaultLimit / 3 - 1;
@@ -263,6 +265,71 @@
EXPECT_EQ(wm2.CurrentWindowSize(), kDefaultLimit);
}
+// This test verifies that when the constructor option is specified,
+// WindowManager uses the provided ShouldNotifyListener to determine when to
+// notify the listener.
+TEST(WindowManagerShouldUpdateTest, CustomShouldNotifyListener) {
+ const int64_t kDefaultLimit = 65535;
+
+ // This window manager should always notify.
+ std::list<int64_t> call_sequence1;
+ WindowManager wm1(
+ kDefaultLimit,
+ [&call_sequence1](int64_t delta) { call_sequence1.push_back(delta); },
+ [](int64_t /*limit*/, int64_t /*window*/, int64_t /*delta*/) {
+ return true;
+ });
+ // This window manager should never notify.
+ std::list<int64_t> call_sequence2;
+ WindowManager wm2(
+ kDefaultLimit,
+ [&call_sequence2](int64_t delta) { call_sequence2.push_back(delta); },
+ [](int64_t /*limit*/, int64_t /*window*/, int64_t /*delta*/) {
+ return false;
+ });
+ // This window manager should notify as long as no data is buffered.
+ std::list<int64_t> call_sequence3;
+ WindowManager wm3(
+ kDefaultLimit,
+ [&call_sequence3](int64_t delta) { call_sequence3.push_back(delta); },
+ [](int64_t limit, int64_t window, int64_t delta) {
+ return delta == limit - window;
+ });
+
+ const int64_t consumed = kDefaultLimit / 4;
+
+ wm1.MarkWindowConsumed(consumed);
+ EXPECT_THAT(call_sequence1, testing::ElementsAre(consumed));
+ wm2.MarkWindowConsumed(consumed);
+ EXPECT_TRUE(call_sequence2.empty());
+ wm3.MarkWindowConsumed(consumed);
+ EXPECT_THAT(call_sequence3, testing::ElementsAre(consumed));
+
+ const int64_t buffered = 42;
+
+ wm1.MarkDataBuffered(buffered);
+ EXPECT_THAT(call_sequence1, testing::ElementsAre(consumed));
+ wm2.MarkDataBuffered(buffered);
+ EXPECT_TRUE(call_sequence2.empty());
+ wm3.MarkDataBuffered(buffered);
+ EXPECT_THAT(call_sequence3, testing::ElementsAre(consumed));
+
+ wm1.MarkDataFlushed(buffered / 3);
+ EXPECT_THAT(call_sequence1, testing::ElementsAre(consumed, buffered / 3));
+ wm2.MarkDataFlushed(buffered / 3);
+ EXPECT_TRUE(call_sequence2.empty());
+ wm3.MarkDataFlushed(buffered / 3);
+ EXPECT_THAT(call_sequence3, testing::ElementsAre(consumed));
+
+ wm1.MarkDataFlushed(2 * buffered / 3);
+ EXPECT_THAT(call_sequence1,
+ testing::ElementsAre(consumed, buffered / 3, 2 * buffered / 3));
+ wm2.MarkDataFlushed(2 * buffered / 3);
+ EXPECT_TRUE(call_sequence2.empty());
+ wm3.MarkDataFlushed(2 * buffered / 3);
+ EXPECT_THAT(call_sequence3, testing::ElementsAre(consumed, buffered));
+}
+
} // namespace
} // namespace test
} // namespace adapter