blob: 6f48a3b67cca57b803531a090465945f76ca2a55 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <array>
#include <vector>
#include "base/task/sequence_manager/timing_wheel.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base::sequence_manager::internal {
namespace {
class Element {
public:
Element(TimeTicks delayed_run_time)
: delayed_run_time(delayed_run_time),
handle_(std::make_unique<TimingWheelHandle>()) {}
// Implementation of TimingWheel contract.
void SetTimingWheelHandle(TimingWheelHandle handle) {
DCHECK(handle.IsValid());
if (handle_)
*handle_ = handle;
}
void ClearTimingWheelHandle() {
if (handle_)
handle_->Reset();
}
TimingWheelHandle* handle() const { return handle_.get(); }
TimeTicks delayed_run_time;
private:
std::unique_ptr<TimingWheelHandle> handle_;
};
// The default TimingWheelHandleAccessor, which simply forwards calls to the
// underlying type.
template <typename T>
struct TimingWheelHandleAccessor {
void SetTimingWheelHandle(T* element, TimingWheelHandle handle) const {
element->SetTimingWheelHandle(handle);
}
void ClearTimingWheelHandle(T* element) const {
element->ClearTimingWheelHandle();
}
};
// Gets the delayed run time of the |element|.
struct GetDelayedRunTime {
template <typename T>
TimeTicks operator()(const T& element) {
return element.delayed_run_time;
}
};
// The total number of buckets that we use for the TimingWheel in all the unit
// tests.
const size_t kWheelSize = 100;
// The time period used for each bucket in the TimingWheel in all the unit
// tests.
const TimeDelta kBucketTimeDelta = Microseconds(500);
// The TimingWheel has an index which points to the last updated bucket. On
// initialization, the index points at bucket 0. The immediate
// insertable bucket is always the bucket after the index. Hence, index 1 is
// considered the first bucket. Suppose, the timing wheel is updated and the
// index now points at Index X. This would mean that while the index is
// pointing at Index X, no element can be inserted at Index X.
const size_t kFirstBucketIndex = 1;
// The index of the last bucket in the TimingWheel
const size_t kLastBucketIndex = kWheelSize - 1;
} // namespace
// Tests the construction of the object.
TEST(TimingWheelTest, Initialize) {
TimingWheel<Element, kWheelSize, TimingWheelHandleAccessor<Element>,
GetDelayedRunTime>
timing_wheel(kBucketTimeDelta);
EXPECT_EQ(timing_wheel.time_delta_per_bucket(), kBucketTimeDelta);
EXPECT_EQ(timing_wheel.total_elements(), 0u);
}
// Tests whether an element can be added to the first bucket.
TEST(TimingWheelTest, InsertElement) {
TimingWheel<Element, kWheelSize, TimingWheelHandleAccessor<Element>,
GetDelayedRunTime>
timing_wheel(kBucketTimeDelta);
const TimeTicks baseline = TimeTicks::Now();
// Pick a time delta that ensures the element is added to the first bucket.
const TimeDelta kTestDelay = kFirstBucketIndex * kBucketTimeDelta;
const auto it = timing_wheel.Insert({baseline + kTestDelay}, kTestDelay);
const TimingWheelHandle* handle = it->handle();
EXPECT_EQ(handle->element_index(), 0u);
EXPECT_EQ(handle->bucket_index(), kFirstBucketIndex);
EXPECT_EQ(timing_wheel.total_elements(), 1u);
}
// Tests advancing time by a delta equal to the wheel's complete time delta.
TEST(TimingWheelTest, AdvancingFullWheel) {
TimingWheel<Element, kWheelSize, TimingWheelHandleAccessor<Element>,
GetDelayedRunTime>
timing_wheel(kBucketTimeDelta);
const TimeTicks baseline = TimeTicks::Now();
const TimeDelta kTimePassed = kBucketTimeDelta * kLastBucketIndex;
// Pick time delta that ensures the elements are added to the last bucket.
const TimeDelta kLastBucketMinDelay = kLastBucketIndex * kBucketTimeDelta;
timing_wheel.Insert({baseline + kLastBucketMinDelay}, kLastBucketMinDelay);
std::vector<Element> expired_elements;
timing_wheel.AdvanceTimeAndRemoveExpiredElements(kTimePassed,
expired_elements);
EXPECT_EQ(expired_elements.size(), 1u);
EXPECT_EQ(timing_wheel.total_elements(), 0u);
}
// Tests advancing time by a delta greater than the wheel's complete time delta.
TEST(TimingWheelTest, AdvancingMoreThanFullWheel) {
TimingWheel<Element, kWheelSize, TimingWheelHandleAccessor<Element>,
GetDelayedRunTime>
timing_wheel(kBucketTimeDelta);
std::vector<Element> expired_elements;
const TimeDelta kTimePassed =
kBucketTimeDelta * kWheelSize + kBucketTimeDelta;
timing_wheel.AdvanceTimeAndRemoveExpiredElements(kTimePassed,
expired_elements);
EXPECT_EQ(timing_wheel.total_elements(), 0u);
EXPECT_EQ(expired_elements.size(), 0u);
}
// Tests whether multiple elements can be added to different buckets.
TEST(TimingWheelTest, InsertMultipleElementsInDifferentBuckets) {
TimingWheel<Element, kWheelSize, TimingWheelHandleAccessor<Element>,
GetDelayedRunTime>
timing_wheel(kBucketTimeDelta);
const TimeTicks baseline = TimeTicks::Now();
const size_t kBucketIndex1 = 50;
const size_t kBucketIndex2 = 75;
const TimeDelta kTestDelay1 = kBucketIndex1 * kBucketTimeDelta;
const TimeDelta kTestDelay2 = kBucketIndex2 * kBucketTimeDelta;
// Insert five elements that are due before |kTestDelay1|.
timing_wheel.Insert({baseline + kTestDelay1}, kTestDelay1);
timing_wheel.Insert({baseline + kTestDelay1 - Milliseconds(2)}, kTestDelay1);
timing_wheel.Insert({baseline + kTestDelay1 - Milliseconds(5)}, kTestDelay1);
timing_wheel.Insert({baseline + kTestDelay1 - Milliseconds(10)}, kTestDelay1);
timing_wheel.Insert({baseline + kTestDelay1 - Milliseconds(10)}, kTestDelay1);
// Insert two elements that are due after |kTestDelay1|.
timing_wheel.Insert({baseline + kTestDelay2 - Milliseconds(4)}, kTestDelay2);
timing_wheel.Insert({baseline + kTestDelay2}, kTestDelay2);
std::vector<Element> expired_elements;
// Advances time by |kTestDelay1| so that all the elements except the last
// two can be picked up.
timing_wheel.AdvanceTimeAndRemoveExpiredElements(kTestDelay1,
expired_elements);
EXPECT_EQ(expired_elements.size(), 5u);
EXPECT_EQ(timing_wheel.total_elements(), 2u);
// Need to advance time by at least (kTestDelay2 - kTestDelay1) to pick the
// last elements.
timing_wheel.AdvanceTimeAndRemoveExpiredElements(kTestDelay2 - kTestDelay1,
expired_elements);
EXPECT_EQ(expired_elements.size(), 7u);
EXPECT_EQ(timing_wheel.total_elements(), 0u);
}
// Tests whether multiple elements that expire at the same time can be added
// to the same buckets.
TEST(TimingWheelTest, InsertMultipleSimilarElementsInSameBucket) {
TimingWheel<Element, kWheelSize, TimingWheelHandleAccessor<Element>,
GetDelayedRunTime>
timing_wheel(kBucketTimeDelta);
const TimeTicks baseline = TimeTicks::Now();
const size_t kBucketIndex = 15;
const TimeDelta kTestDelay = kBucketIndex * kBucketTimeDelta;
timing_wheel.Insert({baseline + kTestDelay}, kTestDelay);
timing_wheel.Insert({baseline + kTestDelay}, kTestDelay);
std::vector<Element> expired_elements;
timing_wheel.AdvanceTimeAndRemoveExpiredElements(kTestDelay,
expired_elements);
EXPECT_EQ(expired_elements.size(), 2u);
EXPECT_EQ(timing_wheel.total_elements(), 0u);
}
// Tests the circular nature of the timing wheel, and ensure that when the
// current index advances, the buckets before it can now be used for elements
// whose deadline is greater than kBucketTimeDelta * kWheelSize.
TEST(TimingWheelTest, InsertToLastBucketButNotLastIndex) {
TimingWheel<Element, kWheelSize, TimingWheelHandleAccessor<Element>,
GetDelayedRunTime>
timing_wheel(kBucketTimeDelta);
const TimeTicks baseline = TimeTicks::Now();
const TimeDelta kTimePassed = 2 * kBucketTimeDelta;
const TimeDelta kTestDelay = kLastBucketIndex * kBucketTimeDelta;
std::vector<Element> expired_elements;
// First advance time to change the current index of the wheel.
timing_wheel.AdvanceTimeAndRemoveExpiredElements(kTimePassed,
expired_elements);
timing_wheel.Insert({baseline + kTestDelay + kTimePassed}, kTestDelay);
// Advance time to pick up the element.
timing_wheel.AdvanceTimeAndRemoveExpiredElements(kTestDelay,
expired_elements);
EXPECT_EQ(expired_elements.size(), 1u);
}
// Tests removal of an element using its handle.
TEST(TimingWheelTest, RemoveElement) {
TimingWheel<Element, kWheelSize, TimingWheelHandleAccessor<Element>,
GetDelayedRunTime>
timing_wheel(kBucketTimeDelta);
const TimeTicks baseline = TimeTicks::Now();
const TimeDelta kLastBucketMinDelay = kLastBucketIndex * kBucketTimeDelta;
const TimeDelta kLastBucketMaxDelay =
(kLastBucketIndex + 1) * kBucketTimeDelta - Microseconds(1u);
const TimeDelta kTimePassed = kBucketTimeDelta * kLastBucketIndex;
timing_wheel.Insert({baseline + kLastBucketMinDelay}, kLastBucketMinDelay);
const TimingWheelHandle* handle =
timing_wheel
.Insert({baseline + kLastBucketMaxDelay}, kLastBucketMaxDelay)
->handle();
timing_wheel.Remove(*handle);
EXPECT_EQ(timing_wheel.total_elements(), 1u);
std::vector<Element> expired_elements;
timing_wheel.AdvanceTimeAndRemoveExpiredElements(kTimePassed,
expired_elements);
EXPECT_EQ(expired_elements.size(), 1u);
EXPECT_EQ(timing_wheel.total_elements(), 0u);
}
// Tests whether we get the earliest due element from two elements in different
// buckets.
TEST(TimingWheelTest, TopElementfromDifferentElements) {
TimingWheel<Element, kWheelSize, TimingWheelHandleAccessor<Element>,
GetDelayedRunTime>
timing_wheel(kBucketTimeDelta);
const TimeTicks baseline = TimeTicks::Now();
const TimeDelta kTestDelay1 = kBucketTimeDelta;
const TimeDelta kTestDelay2 = kBucketTimeDelta * 2;
Element element1(baseline + kTestDelay1);
Element element2(baseline + kTestDelay2);
const TimingWheelHandle* handle = element1.handle();
timing_wheel.Insert(std::move(element1), kTestDelay1);
timing_wheel.Insert(std::move(element2), kTestDelay2);
EXPECT_EQ(handle, timing_wheel.Top().handle());
EXPECT_EQ(timing_wheel.total_elements(), 2u);
}
// Tests whether we get the earliest due element when two elements are in the
// same bucket.
TEST(TimingWheelTest, TopElementFromSimilarElements) {
TimingWheel<Element, kWheelSize, TimingWheelHandleAccessor<Element>,
GetDelayedRunTime>
timing_wheel(kBucketTimeDelta);
const TimeTicks baseline = TimeTicks::Now();
const TimeDelta kTestDelay1 = kBucketTimeDelta;
const TimeDelta kTestDelay2 = kBucketTimeDelta + Milliseconds(1);
Element element1(baseline + kTestDelay1);
Element element2(baseline + kTestDelay2);
const TimingWheelHandle* handle = element1.handle();
timing_wheel.Insert(std::move(element1), kTestDelay1);
timing_wheel.Insert(std::move(element2), kTestDelay2);
EXPECT_EQ(handle, timing_wheel.Top().handle());
EXPECT_EQ(timing_wheel.total_elements(), 2u);
}
} // namespace base::sequence_manager::internal