
The methodology used to generate this CL is documented in https://crbug.com/1098010#c95. No-Try: true Bug: 1098010 Change-Id: I26884c98578ee1ba4d7708ace5b25d1df5c0e576 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3895939 Owners-Override: Avi Drissman <avi@chromium.org> Commit-Queue: Mark Mentovai <mark@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Auto-Submit: Avi Drissman <avi@chromium.org> Cr-Commit-Position: refs/heads/main@{#1047098}
315 lines
11 KiB
C++
315 lines
11 KiB
C++
// Copyright 2012 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/base/backoff_entry.h"
|
|
|
|
#include "base/time/tick_clock.h"
|
|
#include "base/time/time.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace net {
|
|
|
|
namespace {
|
|
|
|
using base::TimeTicks;
|
|
|
|
BackoffEntry::Policy base_policy = { 0, 1000, 2.0, 0.0, 20000, 2000, false };
|
|
|
|
class TestTickClock : public base::TickClock {
|
|
public:
|
|
TestTickClock() = default;
|
|
TestTickClock(const TestTickClock&) = delete;
|
|
TestTickClock& operator=(const TestTickClock&) = delete;
|
|
~TestTickClock() override = default;
|
|
|
|
TimeTicks NowTicks() const override { return now_ticks_; }
|
|
void set_now(TimeTicks now) { now_ticks_ = now; }
|
|
|
|
private:
|
|
TimeTicks now_ticks_;
|
|
};
|
|
|
|
TEST(BackoffEntryTest, BaseTest) {
|
|
TestTickClock now_ticks;
|
|
BackoffEntry entry(&base_policy, &now_ticks);
|
|
EXPECT_FALSE(entry.ShouldRejectRequest());
|
|
EXPECT_EQ(base::TimeDelta(), entry.GetTimeUntilRelease());
|
|
|
|
entry.InformOfRequest(false);
|
|
EXPECT_TRUE(entry.ShouldRejectRequest());
|
|
EXPECT_EQ(base::Milliseconds(1000), entry.GetTimeUntilRelease());
|
|
}
|
|
|
|
TEST(BackoffEntryTest, CanDiscardNeverExpires) {
|
|
BackoffEntry::Policy never_expires_policy = base_policy;
|
|
never_expires_policy.entry_lifetime_ms = -1;
|
|
TestTickClock now_ticks;
|
|
BackoffEntry never_expires(&never_expires_policy, &now_ticks);
|
|
EXPECT_FALSE(never_expires.CanDiscard());
|
|
now_ticks.set_now(TimeTicks() + base::Days(100));
|
|
EXPECT_FALSE(never_expires.CanDiscard());
|
|
}
|
|
|
|
TEST(BackoffEntryTest, CanDiscard) {
|
|
TestTickClock now_ticks;
|
|
BackoffEntry entry(&base_policy, &now_ticks);
|
|
// Because lifetime is non-zero, we shouldn't be able to discard yet.
|
|
EXPECT_FALSE(entry.CanDiscard());
|
|
|
|
// Test the "being used" case.
|
|
entry.InformOfRequest(false);
|
|
EXPECT_FALSE(entry.CanDiscard());
|
|
|
|
// Test the case where there are errors but we can time out.
|
|
now_ticks.set_now(entry.GetReleaseTime() + base::Milliseconds(1));
|
|
EXPECT_FALSE(entry.CanDiscard());
|
|
now_ticks.set_now(entry.GetReleaseTime() +
|
|
base::Milliseconds(base_policy.maximum_backoff_ms + 1));
|
|
EXPECT_TRUE(entry.CanDiscard());
|
|
|
|
// Test the final case (no errors, dependent only on specified lifetime).
|
|
now_ticks.set_now(entry.GetReleaseTime() +
|
|
base::Milliseconds(base_policy.entry_lifetime_ms - 1));
|
|
entry.InformOfRequest(true);
|
|
EXPECT_FALSE(entry.CanDiscard());
|
|
now_ticks.set_now(entry.GetReleaseTime() +
|
|
base::Milliseconds(base_policy.entry_lifetime_ms));
|
|
EXPECT_TRUE(entry.CanDiscard());
|
|
}
|
|
|
|
TEST(BackoffEntryTest, CanDiscardAlwaysDelay) {
|
|
BackoffEntry::Policy always_delay_policy = base_policy;
|
|
always_delay_policy.always_use_initial_delay = true;
|
|
always_delay_policy.entry_lifetime_ms = 0;
|
|
|
|
TestTickClock now_ticks;
|
|
BackoffEntry entry(&always_delay_policy, &now_ticks);
|
|
|
|
// Because lifetime is non-zero, we shouldn't be able to discard yet.
|
|
now_ticks.set_now(entry.GetReleaseTime() + base::Milliseconds(2000));
|
|
EXPECT_TRUE(entry.CanDiscard());
|
|
|
|
// Even with no failures, we wait until the delay before we allow discard.
|
|
entry.InformOfRequest(true);
|
|
EXPECT_FALSE(entry.CanDiscard());
|
|
|
|
// Wait until the delay expires, and we can discard the entry again.
|
|
now_ticks.set_now(entry.GetReleaseTime() + base::Milliseconds(1000));
|
|
EXPECT_TRUE(entry.CanDiscard());
|
|
}
|
|
|
|
TEST(BackoffEntryTest, CanDiscardNotStored) {
|
|
BackoffEntry::Policy no_store_policy = base_policy;
|
|
no_store_policy.entry_lifetime_ms = 0;
|
|
TestTickClock now_ticks;
|
|
BackoffEntry not_stored(&no_store_policy, &now_ticks);
|
|
EXPECT_TRUE(not_stored.CanDiscard());
|
|
}
|
|
|
|
TEST(BackoffEntryTest, ShouldIgnoreFirstTwo) {
|
|
BackoffEntry::Policy lenient_policy = base_policy;
|
|
lenient_policy.num_errors_to_ignore = 2;
|
|
|
|
BackoffEntry entry(&lenient_policy);
|
|
|
|
entry.InformOfRequest(false);
|
|
EXPECT_FALSE(entry.ShouldRejectRequest());
|
|
|
|
entry.InformOfRequest(false);
|
|
EXPECT_FALSE(entry.ShouldRejectRequest());
|
|
|
|
entry.InformOfRequest(false);
|
|
EXPECT_TRUE(entry.ShouldRejectRequest());
|
|
}
|
|
|
|
TEST(BackoffEntryTest, ReleaseTimeCalculation) {
|
|
TestTickClock now_ticks;
|
|
BackoffEntry entry(&base_policy, &now_ticks);
|
|
|
|
// With zero errors, should return "now".
|
|
TimeTicks result = entry.GetReleaseTime();
|
|
EXPECT_EQ(now_ticks.NowTicks(), result);
|
|
|
|
// 1 error.
|
|
entry.InformOfRequest(false);
|
|
result = entry.GetReleaseTime();
|
|
EXPECT_EQ(now_ticks.NowTicks() + base::Milliseconds(1000), result);
|
|
EXPECT_EQ(base::Milliseconds(1000), entry.GetTimeUntilRelease());
|
|
|
|
// 2 errors.
|
|
entry.InformOfRequest(false);
|
|
result = entry.GetReleaseTime();
|
|
EXPECT_EQ(now_ticks.NowTicks() + base::Milliseconds(2000), result);
|
|
EXPECT_EQ(base::Milliseconds(2000), entry.GetTimeUntilRelease());
|
|
|
|
// 3 errors.
|
|
entry.InformOfRequest(false);
|
|
result = entry.GetReleaseTime();
|
|
EXPECT_EQ(now_ticks.NowTicks() + base::Milliseconds(4000), result);
|
|
EXPECT_EQ(base::Milliseconds(4000), entry.GetTimeUntilRelease());
|
|
|
|
// 6 errors (to check it doesn't pass maximum).
|
|
entry.InformOfRequest(false);
|
|
entry.InformOfRequest(false);
|
|
entry.InformOfRequest(false);
|
|
result = entry.GetReleaseTime();
|
|
EXPECT_EQ(now_ticks.NowTicks() + base::Milliseconds(20000), result);
|
|
}
|
|
|
|
TEST(BackoffEntryTest, ReleaseTimeCalculationAlwaysDelay) {
|
|
BackoffEntry::Policy always_delay_policy = base_policy;
|
|
always_delay_policy.always_use_initial_delay = true;
|
|
always_delay_policy.num_errors_to_ignore = 2;
|
|
|
|
TestTickClock now_ticks;
|
|
BackoffEntry entry(&always_delay_policy, &now_ticks);
|
|
|
|
// With previous requests, should return "now".
|
|
TimeTicks result = entry.GetReleaseTime();
|
|
EXPECT_EQ(base::TimeDelta(), entry.GetTimeUntilRelease());
|
|
|
|
// 1 error.
|
|
entry.InformOfRequest(false);
|
|
EXPECT_EQ(base::Milliseconds(1000), entry.GetTimeUntilRelease());
|
|
|
|
// 2 errors.
|
|
entry.InformOfRequest(false);
|
|
EXPECT_EQ(base::Milliseconds(1000), entry.GetTimeUntilRelease());
|
|
|
|
// 3 errors, exponential backoff starts.
|
|
entry.InformOfRequest(false);
|
|
EXPECT_EQ(base::Milliseconds(2000), entry.GetTimeUntilRelease());
|
|
|
|
// 4 errors.
|
|
entry.InformOfRequest(false);
|
|
EXPECT_EQ(base::Milliseconds(4000), entry.GetTimeUntilRelease());
|
|
|
|
// 8 errors (to check it doesn't pass maximum).
|
|
entry.InformOfRequest(false);
|
|
entry.InformOfRequest(false);
|
|
entry.InformOfRequest(false);
|
|
entry.InformOfRequest(false);
|
|
result = entry.GetReleaseTime();
|
|
EXPECT_EQ(base::Milliseconds(20000), entry.GetTimeUntilRelease());
|
|
}
|
|
|
|
TEST(BackoffEntryTest, ReleaseTimeCalculationWithJitter) {
|
|
for (int i = 0; i < 10; ++i) {
|
|
BackoffEntry::Policy jittery_policy = base_policy;
|
|
jittery_policy.jitter_factor = 0.2;
|
|
|
|
TestTickClock now_ticks;
|
|
BackoffEntry entry(&jittery_policy, &now_ticks);
|
|
|
|
entry.InformOfRequest(false);
|
|
entry.InformOfRequest(false);
|
|
entry.InformOfRequest(false);
|
|
TimeTicks result = entry.GetReleaseTime();
|
|
EXPECT_LE(now_ticks.NowTicks() + base::Milliseconds(3200), result);
|
|
EXPECT_GE(now_ticks.NowTicks() + base::Milliseconds(4000), result);
|
|
}
|
|
}
|
|
|
|
TEST(BackoffEntryTest, FailureThenSuccess) {
|
|
TestTickClock now_ticks;
|
|
BackoffEntry entry(&base_policy, &now_ticks);
|
|
|
|
// Failure count 1, establishes horizon.
|
|
entry.InformOfRequest(false);
|
|
TimeTicks release_time = entry.GetReleaseTime();
|
|
EXPECT_EQ(TimeTicks() + base::Milliseconds(1000), release_time);
|
|
|
|
// Success, failure count 0, should not advance past
|
|
// the horizon that was already set.
|
|
now_ticks.set_now(release_time - base::Milliseconds(200));
|
|
entry.InformOfRequest(true);
|
|
EXPECT_EQ(release_time, entry.GetReleaseTime());
|
|
|
|
// Failure, failure count 1.
|
|
entry.InformOfRequest(false);
|
|
EXPECT_EQ(release_time + base::Milliseconds(800), entry.GetReleaseTime());
|
|
}
|
|
|
|
TEST(BackoffEntryTest, FailureThenSuccessAlwaysDelay) {
|
|
BackoffEntry::Policy always_delay_policy = base_policy;
|
|
always_delay_policy.always_use_initial_delay = true;
|
|
always_delay_policy.num_errors_to_ignore = 1;
|
|
|
|
TestTickClock now_ticks;
|
|
BackoffEntry entry(&always_delay_policy, &now_ticks);
|
|
|
|
// Failure count 1.
|
|
entry.InformOfRequest(false);
|
|
EXPECT_EQ(base::Milliseconds(1000), entry.GetTimeUntilRelease());
|
|
|
|
// Failure count 2.
|
|
entry.InformOfRequest(false);
|
|
EXPECT_EQ(base::Milliseconds(2000), entry.GetTimeUntilRelease());
|
|
now_ticks.set_now(entry.GetReleaseTime() + base::Milliseconds(2000));
|
|
|
|
// Success. We should go back to the original delay.
|
|
entry.InformOfRequest(true);
|
|
EXPECT_EQ(base::Milliseconds(1000), entry.GetTimeUntilRelease());
|
|
|
|
// Failure count reaches 2 again. We should increase the delay once more.
|
|
entry.InformOfRequest(false);
|
|
EXPECT_EQ(base::Milliseconds(2000), entry.GetTimeUntilRelease());
|
|
now_ticks.set_now(entry.GetReleaseTime() + base::Milliseconds(2000));
|
|
}
|
|
|
|
TEST(BackoffEntryTest, RetainCustomHorizon) {
|
|
TestTickClock now_ticks;
|
|
BackoffEntry custom(&base_policy, &now_ticks);
|
|
TimeTicks custom_horizon = TimeTicks() + base::Days(3);
|
|
custom.SetCustomReleaseTime(custom_horizon);
|
|
custom.InformOfRequest(false);
|
|
custom.InformOfRequest(true);
|
|
now_ticks.set_now(TimeTicks() + base::Days(2));
|
|
custom.InformOfRequest(false);
|
|
custom.InformOfRequest(true);
|
|
EXPECT_EQ(custom_horizon, custom.GetReleaseTime());
|
|
|
|
// Now check that once we are at or past the custom horizon,
|
|
// we get normal behavior.
|
|
now_ticks.set_now(TimeTicks() + base::Days(3));
|
|
custom.InformOfRequest(false);
|
|
EXPECT_EQ(TimeTicks() + base::Days(3) + base::Milliseconds(1000),
|
|
custom.GetReleaseTime());
|
|
}
|
|
|
|
TEST(BackoffEntryTest, RetainCustomHorizonWhenInitialErrorsIgnored) {
|
|
// Regression test for a bug discovered during code review.
|
|
BackoffEntry::Policy lenient_policy = base_policy;
|
|
lenient_policy.num_errors_to_ignore = 1;
|
|
TestTickClock now_ticks;
|
|
BackoffEntry custom(&lenient_policy, &now_ticks);
|
|
TimeTicks custom_horizon = TimeTicks() + base::Days(3);
|
|
custom.SetCustomReleaseTime(custom_horizon);
|
|
custom.InformOfRequest(false); // This must not reset the horizon.
|
|
EXPECT_EQ(custom_horizon, custom.GetReleaseTime());
|
|
}
|
|
|
|
TEST(BackoffEntryTest, OverflowProtection) {
|
|
BackoffEntry::Policy large_multiply_policy = base_policy;
|
|
large_multiply_policy.multiply_factor = 256;
|
|
TestTickClock now_ticks;
|
|
BackoffEntry custom(&large_multiply_policy, &now_ticks);
|
|
|
|
// Trigger enough failures such that more than 11 bits of exponent are used
|
|
// to represent the exponential backoff intermediate values. Given a multiply
|
|
// factor of 256 (2^8), 129 iterations is enough: 2^(8*(129-1)) = 2^1024.
|
|
for (int i = 0; i < 129; ++i) {
|
|
now_ticks.set_now(now_ticks.NowTicks() + custom.GetTimeUntilRelease());
|
|
custom.InformOfRequest(false);
|
|
ASSERT_TRUE(custom.ShouldRejectRequest());
|
|
}
|
|
|
|
// Max delay should still be respected.
|
|
EXPECT_EQ(20000, custom.GetTimeUntilRelease().InMilliseconds());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
} // namespace net
|