blob: 584e3872f5ad085a8c2f006a82aba042277d4cdc [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 "base/task/sequence_manager/hierarchical_timing_wheel.h"
#include <math.h>
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base::sequence_manager {
namespace {
// Custom comparator for testing.
template <typename T>
struct CustomCompare {
bool operator()(const T& lhs, const T& rhs) const {
return std::tie(lhs.delayed_run_time, lhs.name) >
std::tie(rhs.delayed_run_time, rhs.name);
}
};
class Task {
public:
enum : size_t { kInvalidIndex = std::numeric_limits<size_t>::max() };
explicit Task(TimeTicks delayed_run_time, std::string name = std::string())
: delayed_run_time(delayed_run_time),
name(name),
handle_(std::make_unique<HierarchicalTimingWheelHandle>()) {}
HierarchicalTimingWheelHandle* handle() const { return handle_.get(); }
TimeTicks delayed_run_time;
// Used as a second comparator key to test the custom comparator
// functionality.
std::string name;
private:
std::unique_ptr<HierarchicalTimingWheelHandle> handle_;
};
} // namespace
// Tests the construction of the object.
TEST(HierarchicalTimingWheelTest, SimpleTest) {
const TimeTicks baseline = TimeTicks::Now();
HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
baseline};
EXPECT_EQ(hierarchical_timing_wheel.Size(), 0u);
auto* handle =
hierarchical_timing_wheel.Insert(Task{baseline + Microseconds(100)})
->handle();
EXPECT_EQ(hierarchical_timing_wheel.Size(), 1u);
hierarchical_timing_wheel.Remove(*handle);
EXPECT_EQ(hierarchical_timing_wheel.Size(), 0u);
}
// Tests whether an element can be added in all the places in the hierarchy.
TEST(HierarchicalTimingWheelTest, InsertAllDistinctElements) {
const size_t kHierarchyCount = 6;
const TimeTicks baseline = TimeTicks::Now();
HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
baseline};
// Create time delta to insert a task in each of the hierarchy. The element's
// index represents the hierarchy index in which it will be inserted. The
// delays are chosen as per the example given in the class's header file.
const TimeDelta kDelay[kHierarchyCount] = {
Microseconds(100), Microseconds(500), Milliseconds(50),
Seconds(5), Seconds(500), Seconds(50000)};
HierarchicalTimingWheelHandle* handles[kHierarchyCount];
for (size_t i = 0; i < kHierarchyCount; i++) {
handles[i] =
hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
}
for (size_t i = 0; i < kHierarchyCount; i++) {
EXPECT_EQ(handles[i]->GetHierarchyIndex(), i);
const bool is_heap_handle = i == 0 || i == kHierarchyCount - 1;
EXPECT_EQ(handles[i]->GetHeapHandle().IsValid(), is_heap_handle);
EXPECT_EQ(handles[i]->GetTimingWheelHandle().IsValid(), !is_heap_handle);
}
}
// Tests whether multiple elements can be added in the same place in the
// hierarchy.
TEST(HierarchicalTimingWheelTest, InsertSimilarElements) {
const size_t kTotalElements = 3;
const size_t kExpectedHierarchyIndex = 1;
const TimeTicks baseline = TimeTicks::Now();
HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
baseline};
// Create time delta to insert a task in the second hierarchy.
const TimeDelta kDelay[kTotalElements] = {Microseconds(500), Milliseconds(21),
Milliseconds(49)};
HierarchicalTimingWheelHandle* handles[kTotalElements];
for (size_t i = 0; i < kTotalElements; i++) {
handles[i] =
hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
}
for (auto* handle : handles) {
EXPECT_EQ(handle->GetHierarchyIndex(), kExpectedHierarchyIndex);
EXPECT_EQ(handle->GetHeapHandle().IsValid(), false);
EXPECT_EQ(handle->GetTimingWheelHandle().IsValid(), true);
}
}
// Tests whether the hierarchy can be updated and cascading take place from one
// hierarchy to another for an element.
TEST(HierarchicalTimingWheelTest, UpdateOneElement) {
const size_t kHierarchyCount = 6;
TimeTicks baseline = TimeTicks::Now();
HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
baseline};
// An array of deltas which cascades an element from the biggest
// hierarchy to the smallest sequentially, and then finally expiring the
// element.
const TimeDelta kTimeDelta[] = {Seconds(50000) - Seconds(500),
Seconds(500) - Seconds(5),
Seconds(5) - Milliseconds(50),
Milliseconds(50) - Microseconds(500),
Microseconds(500) - Microseconds(100),
Microseconds(100)};
// Create time delta to insert a task at the end of the hierarchy.
const TimeTicks delayed_run_time = baseline + Seconds(50000);
HierarchicalTimingWheelHandle* handle =
hierarchical_timing_wheel.Insert(Task{delayed_run_time})->handle();
std::vector<Task> expired_tasks;
for (size_t i = 0; i < kHierarchyCount; i++) {
const size_t expected_hierarchy_index = kHierarchyCount - i - 1;
EXPECT_EQ(handle->GetHierarchyIndex(), expected_hierarchy_index);
baseline += kTimeDelta[i];
expired_tasks = hierarchical_timing_wheel.Update(baseline);
// An element will be returned as expired on the last Update.
const bool expired = i == kHierarchyCount - 1;
// We expect one element to be returned, once.
EXPECT_EQ(expired_tasks.size() == 0, !expired);
EXPECT_EQ(expired_tasks.size() == 1, expired);
}
}
// Tests whether the hierarchy can be updated and cascading take place of
// multiple existing elements in the hierarchy.
TEST(HierarchicalTimingWheelTest, UpdateMultipleElements) {
const size_t kHierarchyCount = 6;
TimeTicks baseline = TimeTicks::Now();
HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
baseline};
// Create time delta to insert a task in each of the hierarchy. The element's
// index represents the hierarchy index in which it will be inserted.
const TimeDelta kDelay[kHierarchyCount] = {
Microseconds(100), Microseconds(500), Milliseconds(50),
Seconds(5), Seconds(500), Seconds(50000)};
HierarchicalTimingWheelHandle* handles[kHierarchyCount];
for (size_t i = 0; i < kHierarchyCount; i++) {
handles[i] =
hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
}
// This update expires all the inserted elements except the last two.
baseline += Seconds(499);
std::vector<Task> expired_tasks = hierarchical_timing_wheel.Update(baseline);
EXPECT_EQ(expired_tasks.size(), 4u);
EXPECT_EQ(handles[kHierarchyCount - 2]->GetHierarchyIndex(), 2u);
EXPECT_EQ(handles[kHierarchyCount - 1]->GetHierarchyIndex(), 4u);
// Expires the last two elements by updating much more than latest delay
// element.
baseline += Seconds(100000);
expired_tasks = hierarchical_timing_wheel.Update(baseline);
EXPECT_EQ(expired_tasks.size(), 2u);
}
// Tests whether an element can be removed from each hierarchy.
TEST(HierarchicalTimingWheelTest, RemoveElements) {
const size_t kHierarchyCount = 6;
const TimeTicks baseline = TimeTicks::Now();
HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
baseline};
// Create time delta to insert a task in each of the hierarchy. The element's
// index represents the hierarchy index in which it will be inserted.
const TimeDelta kDelay[kHierarchyCount] = {
Microseconds(100), Microseconds(500), Milliseconds(50),
Seconds(5), Seconds(500), Seconds(50000)};
HierarchicalTimingWheelHandle* handles[kHierarchyCount];
for (size_t i = 0; i < kHierarchyCount; i++) {
handles[i] =
hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
}
for (auto* handle : handles) {
hierarchical_timing_wheel.Remove(*handle);
}
// The biggest delay was Seconds(50000). Hence, this would remove any leftover
// element, which there aren't supposed to be.
std::vector<Task> expired_tasks =
hierarchical_timing_wheel.Update(baseline + Seconds(50000));
EXPECT_EQ(expired_tasks.empty(), true);
}
// Tests whether the top element of the hierarchy returned is correct when all
// distinct elements exist in the hierarchy.
TEST(HierarchicalTimingWheelTest, TopDifferentElements) {
const size_t kHierarchyCount = 6;
const TimeTicks baseline = TimeTicks::Now();
HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
baseline};
// Create time delta to insert a task in each of the hierarchy. The element's
// index represents the hierarchy index in which it will be inserted.
const TimeDelta kDelay[kHierarchyCount] = {
Microseconds(100), Microseconds(500), Milliseconds(50),
Seconds(5), Seconds(500), Seconds(50000)};
HierarchicalTimingWheelHandle* handles[kHierarchyCount];
for (size_t i = 0; i < kHierarchyCount; i++) {
handles[i] =
hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
const Task& task = hierarchical_timing_wheel.Top();
EXPECT_EQ(task.delayed_run_time, baseline + kDelay[0]);
}
for (size_t i = 0; i < kHierarchyCount; i++) {
const Task& task = hierarchical_timing_wheel.Top();
EXPECT_EQ(task.delayed_run_time, baseline + kDelay[i]);
hierarchical_timing_wheel.Remove(*handles[i]);
}
}
// Tests whether the top element of the hierarchy returned is correct when
// multiple similar elements are in the hierarchy.
TEST(HierarchicalTimingWheelTest, TopSimilarElements) {
const size_t kTotalElements = 3;
const TimeTicks baseline = TimeTicks::Now();
HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
baseline};
// Create time delta to insert a task in the first hierarchy.
const TimeDelta kDelay[kTotalElements] = {
Microseconds(100), Microseconds(200), Microseconds(300)};
HierarchicalTimingWheelHandle* handles[kTotalElements];
for (size_t i = 0; i < kTotalElements; i++) {
handles[i] =
hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
const Task& task = hierarchical_timing_wheel.Top();
EXPECT_EQ(task.delayed_run_time, baseline + kDelay[0]);
}
for (size_t i = 0; i < kTotalElements; i++) {
const Task& task = hierarchical_timing_wheel.Top();
EXPECT_EQ(task.delayed_run_time, baseline + kDelay[i]);
hierarchical_timing_wheel.Remove(*handles[i]);
}
}
// Tests whether the |Compare| functor is correctly used.
TEST(HierarchicalTimingWheelTest, CustomComparator) {
const std::string expectedTopTaskName = "a";
const TimeTicks baseline = TimeTicks::Now();
HierarchicalTimingWheel<Task, 4, 100, 500,
DefaultHierarchicalTimingWheelHandleAccessor<Task>,
GetDelayedRunTime<Task>, CustomCompare<Task>>
hierarchical_timing_wheel{baseline};
// Create time delta to insert a task in the first hierarchy.
const TimeDelta kDelay = Microseconds(100);
// Inserts two elements in the same bucket.
hierarchical_timing_wheel.Insert(Task{baseline + kDelay, "z"});
hierarchical_timing_wheel.Insert(Task{baseline + kDelay, "a"});
const Task& task = hierarchical_timing_wheel.Top();
// The custom comparator orders by the name's lexicographical order,
// since both the elements have the same delayed run time.
EXPECT_EQ(task.name, expectedTopTaskName);
}
} // namespace base::sequence_manager