blob: c5a9ffbaf6a3c046879d8942908962ef9fce67b0 [file] [log] [blame]
#include "http2/adapter/window_manager.h"
#include <list>
#include "absl/functional/bind_front.h"
#include "http2/test_tools/http2_random.h"
#include "common/platform/api/quiche_test.h"
#include "common/platform/api/quiche_test_helpers.h"
namespace http2 {
namespace adapter {
namespace test {
// Use the peer to access private vars of WindowManager.
class WindowManagerPeer {
public:
explicit WindowManagerPeer(const WindowManager& wm) : wm_(wm) {}
int64_t buffered() { return wm_.buffered_; }
private:
const WindowManager& wm_;
};
namespace {
class WindowManagerTest : public ::testing::Test {
protected:
WindowManagerTest()
: wm_(kDefaultLimit, absl::bind_front(&WindowManagerTest::OnCall, this)),
peer_(wm_) {}
void OnCall(int64_t s) { call_sequence_.push_back(s); }
const int64_t kDefaultLimit = 32 * 1024 * 3;
std::list<int64_t> call_sequence_;
WindowManager wm_;
WindowManagerPeer peer_;
::http2::test::Http2Random random_;
};
// A few no-op calls.
TEST_F(WindowManagerTest, NoOps) {
wm_.SetWindowSizeLimit(kDefaultLimit);
wm_.SetWindowSizeLimit(0);
wm_.SetWindowSizeLimit(kDefaultLimit);
wm_.MarkDataBuffered(0);
wm_.MarkDataFlushed(0);
EXPECT_TRUE(call_sequence_.empty());
}
// This test verifies that WindowManager does not notify its listener when data
// is only buffered, and never flushed.
TEST_F(WindowManagerTest, DataOnlyBuffered) {
int64_t total = 0;
while (total < kDefaultLimit) {
int64_t s = std::min<int64_t>(kDefaultLimit - total, random_.Uniform(1024));
total += s;
wm_.MarkDataBuffered(s);
}
EXPECT_THAT(call_sequence_, ::testing::IsEmpty());
}
// This test verifies that WindowManager does notify its listener when data is
// buffered and subsequently flushed.
TEST_F(WindowManagerTest, DataBufferedAndFlushed) {
int64_t total_buffered = 0;
int64_t total_flushed = 0;
while (call_sequence_.empty()) {
int64_t buffered = std::min<int64_t>(kDefaultLimit - total_buffered,
random_.Uniform(1024));
wm_.MarkDataBuffered(buffered);
total_buffered += buffered;
EXPECT_TRUE(call_sequence_.empty());
int64_t flushed = random_.Uniform(total_buffered - total_flushed);
wm_.MarkDataFlushed(flushed);
total_flushed += flushed;
}
// If WindowManager decided to send an update, at least one third of the
// window must have been consumed by buffered data.
EXPECT_GE(total_buffered, kDefaultLimit / 3);
}
// Window manager should avoid window underflow.
TEST_F(WindowManagerTest, AvoidWindowUnderflow) {
EXPECT_EQ(wm_.CurrentWindowSize(), wm_.WindowSizeLimit());
// Don't buffer more than the total window!
wm_.MarkDataBuffered(wm_.WindowSizeLimit() + 1);
EXPECT_EQ(wm_.CurrentWindowSize(), 0u);
}
// Window manager should GFE_BUG and avoid buffered underflow.
TEST_F(WindowManagerTest, AvoidBufferedUnderflow) {
EXPECT_EQ(peer_.buffered(), 0u);
// Don't flush more than has been buffered!
EXPECT_QUICHE_BUG(wm_.MarkDataFlushed(1), "buffered underflow");
EXPECT_EQ(peer_.buffered(), 0u);
wm_.MarkDataBuffered(42);
EXPECT_EQ(peer_.buffered(), 42u);
// Don't flush more than has been buffered!
EXPECT_QUICHE_BUG(wm_.MarkDataFlushed(43), "buffered underflow");
EXPECT_EQ(peer_.buffered(), 0u);
}
// This test verifies that WindowManager notifies its listener when window is
// consumed (data is ignored or immediately dropped).
TEST_F(WindowManagerTest, WindowConsumed) {
int64_t consumed = kDefaultLimit / 3 - 1;
wm_.MarkWindowConsumed(consumed);
EXPECT_TRUE(call_sequence_.empty());
const int64_t extra = 1;
wm_.MarkWindowConsumed(extra);
EXPECT_THAT(call_sequence_, testing::ElementsAre(consumed + extra));
}
// This test verifies that WindowManager notifies its listener when the window
// size limit is increased.
TEST_F(WindowManagerTest, ListenerCalledOnSizeUpdate) {
wm_.SetWindowSizeLimit(kDefaultLimit - 1024);
EXPECT_TRUE(call_sequence_.empty());
wm_.SetWindowSizeLimit(kDefaultLimit * 5);
// Because max(outstanding window, previous limit) is kDefaultLimit, it is
// only appropriate to increase the window by kDefaultLimit * 4.
EXPECT_THAT(call_sequence_, testing::ElementsAre(kDefaultLimit * 4));
}
// This test verifies that when data is buffered and then the limit is
// decreased, WindowManager only notifies the listener once any outstanding
// window has been consumed.
TEST_F(WindowManagerTest, WindowUpdateAfterLimitDecreased) {
wm_.MarkDataBuffered(kDefaultLimit - 1024);
wm_.SetWindowSizeLimit(kDefaultLimit - 2048);
// Now there are 2048 bytes of window outstanding beyond the current limit,
// and we have 1024 bytes of data buffered beyond the current limit. This is
// intentional, to be sure that WindowManager works properly if the limit is
// decreased at runtime.
wm_.MarkDataFlushed(512);
EXPECT_TRUE(call_sequence_.empty());
wm_.MarkDataFlushed(512);
EXPECT_TRUE(call_sequence_.empty());
wm_.MarkDataFlushed(512);
EXPECT_TRUE(call_sequence_.empty());
wm_.MarkDataFlushed(1024);
EXPECT_THAT(call_sequence_, testing::ElementsAre(512));
}
// For normal behavior, we only call MaybeNotifyListener() when data is
// flushed. But if window runs out entirely, we still need to call
// MaybeNotifyListener() to avoid becoming artificially blocked when data isn't
// being flushed.
TEST_F(WindowManagerTest, ZeroWindowNotification) {
// Consume a byte of window, but not enough to trigger an update.
wm_.MarkWindowConsumed(1);
// Buffer the remaining window.
wm_.MarkDataBuffered(kDefaultLimit - 1);
// Listener is notified of the remaining byte of possible window.
EXPECT_THAT(call_sequence_, testing::ElementsAre(1));
}
TEST_F(WindowManagerTest, OnWindowSizeLimitChange) {
wm_.MarkDataBuffered(10000);
EXPECT_EQ(wm_.CurrentWindowSize(), kDefaultLimit - 10000);
EXPECT_EQ(wm_.WindowSizeLimit(), kDefaultLimit);
wm_.OnWindowSizeLimitChange(kDefaultLimit + 1000);
EXPECT_EQ(wm_.CurrentWindowSize(), kDefaultLimit - 9000);
EXPECT_EQ(wm_.WindowSizeLimit(), kDefaultLimit + 1000);
wm_.OnWindowSizeLimitChange(kDefaultLimit - 1000);
EXPECT_EQ(wm_.CurrentWindowSize(), kDefaultLimit - 11000);
EXPECT_EQ(wm_.WindowSizeLimit(), kDefaultLimit - 1000);
}
TEST_F(WindowManagerTest, NegativeWindowSize) {
wm_.MarkDataBuffered(80000);
// 98304 window - 80000 buffered = 18304 available
EXPECT_EQ(wm_.CurrentWindowSize(), 18304);
wm_.OnWindowSizeLimitChange(65535);
// limit decreases by 98304 - 65535 = 32769, window becomes -14465
EXPECT_EQ(wm_.CurrentWindowSize(), -14465);
wm_.MarkDataFlushed(70000);
// Still 10000 bytes buffered, so window manager grants sufficient quota to
// reach a window of 65535 - 10000.
EXPECT_EQ(wm_.CurrentWindowSize(), 55535);
// Desired window minus existing window: 55535 - (-14465) = 70000
EXPECT_THAT(call_sequence_, testing::ElementsAre(70000));
}
TEST_F(WindowManagerTest, IncreaseWindow) {
wm_.MarkDataBuffered(1000);
EXPECT_EQ(wm_.CurrentWindowSize(), kDefaultLimit - 1000);
EXPECT_EQ(wm_.WindowSizeLimit(), kDefaultLimit);
// Increasing the window beyond the limit is allowed.
wm_.IncreaseWindow(5000);
EXPECT_EQ(wm_.CurrentWindowSize(), kDefaultLimit + 4000);
EXPECT_EQ(wm_.WindowSizeLimit(), kDefaultLimit);
// 80000 bytes are buffered, then flushed.
wm_.MarkWindowConsumed(80000);
// The window manager replenishes the consumed quota up to the limit.
EXPECT_THAT(call_sequence_, testing::ElementsAre(75000));
// The window is the limit, minus buffered data, as expected.
EXPECT_EQ(wm_.CurrentWindowSize(), kDefaultLimit - 1000);
}
// This test verifies that when the constructor option is specified,
// WindowManager does not update its internal accounting of the flow control
// window when notifying the listener.
TEST(WindowManagerNoUpdateTest, NoWindowUpdateOnListener) {
const int64_t kDefaultLimit = 65535;
std::list<int64_t> call_sequence1;
WindowManager wm1(
kDefaultLimit,
[&call_sequence1](int64_t delta) { call_sequence1.push_back(delta); },
/*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); },
/*update_window_on_notify=*/false);
const int64_t consumed = kDefaultLimit / 3 - 1;
wm1.MarkWindowConsumed(consumed);
EXPECT_TRUE(call_sequence1.empty());
wm2.MarkWindowConsumed(consumed);
EXPECT_TRUE(call_sequence2.empty());
EXPECT_EQ(wm1.CurrentWindowSize(), kDefaultLimit - consumed);
EXPECT_EQ(wm2.CurrentWindowSize(), kDefaultLimit - consumed);
const int64_t extra = 1;
wm1.MarkWindowConsumed(extra);
EXPECT_THAT(call_sequence1, testing::ElementsAre(consumed + extra));
// Window size *is* updated after invoking the listener.
EXPECT_EQ(wm1.CurrentWindowSize(), kDefaultLimit);
call_sequence1.clear();
wm2.MarkWindowConsumed(extra);
EXPECT_THAT(call_sequence2, testing::ElementsAre(consumed + extra));
// Window size is *not* updated after invoking the listener.
EXPECT_EQ(wm2.CurrentWindowSize(), kDefaultLimit - (consumed + extra));
call_sequence2.clear();
// Manually increase the window by the listener notification amount.
wm2.IncreaseWindow(consumed + extra);
EXPECT_EQ(wm2.CurrentWindowSize(), kDefaultLimit);
wm1.SetWindowSizeLimit(kDefaultLimit * 5);
EXPECT_THAT(call_sequence1, testing::ElementsAre(kDefaultLimit * 4));
// *Does* update the window size.
EXPECT_EQ(wm1.CurrentWindowSize(), kDefaultLimit * 5);
wm2.SetWindowSizeLimit(kDefaultLimit * 5);
EXPECT_THAT(call_sequence2, testing::ElementsAre(kDefaultLimit * 4));
// Does *not* update the window size.
EXPECT_EQ(wm2.CurrentWindowSize(), kDefaultLimit);
}
} // namespace
} // namespace test
} // namespace adapter
} // namespace http2