blob: 9a1504bf71da992e9e43f633c2cb846a402c39ef [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.
#ifndef BASE_TASK_SEQUENCE_MANAGER_HIERARCHICAL_TIMING_WHEEL_H_
#define BASE_TASK_SEQUENCE_MANAGER_HIERARCHICAL_TIMING_WHEEL_H_
#include <algorithm>
#include <array>
#include <numeric>
#include <vector>
#include "base/containers/intrusive_heap.h"
#include "base/task/sequence_manager/timing_wheel.h"
#include "base/time/time.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace base::sequence_manager {
// A union of |TimingWheelHandle| and |HeapHandle|. At any given
// time it holds a value of one of its alternative types. It can only
// have either. This class is maintained by the hierarchical timing
// wheel as the object moves around within it. It can be used to subsequently
// remove the element.
class BASE_EXPORT HierarchicalTimingWheelHandle {
public:
enum : size_t { kInvalidIndex = std::numeric_limits<size_t>::max() };
HierarchicalTimingWheelHandle();
HierarchicalTimingWheelHandle(const HierarchicalTimingWheelHandle& other) =
default;
HierarchicalTimingWheelHandle(HierarchicalTimingWheelHandle&& other) noexcept;
HierarchicalTimingWheelHandle& operator=(
const HierarchicalTimingWheelHandle& other) = default;
HierarchicalTimingWheelHandle& operator=(
HierarchicalTimingWheelHandle&& other) noexcept;
~HierarchicalTimingWheelHandle();
// TimingWheel contract
internal::TimingWheelHandle GetTimingWheelHandle() const;
void SetTimingWheelHandle(internal::TimingWheelHandle timing_wheel_handle);
void ClearTimingWheelHandle();
// IntrusiveHeap contract
HeapHandle GetHeapHandle();
void SetHeapHandle(HeapHandle handle);
void ClearHeapHandle();
size_t GetHierarchyIndex() const;
void SetHierarchyIndex(size_t hierarchy_index);
void ClearHierarchyIndex();
// Gets a default constructed HierarchicalTimingWheelHandle.
static HierarchicalTimingWheelHandle Invalid();
bool IsValid() const;
private:
// The handle of the timing wheel in the hierarchical timing wheel where the
// element is in.
internal::TimingWheelHandle timing_wheel_handle_;
// The handle of the heap in the hierarchical timing wheel where the element
// is in.
HeapHandle heap_handle_;
// The index in the hierarchy of timing wheels and heaps, this handle belongs
// to.
size_t hierarchy_index_ = kInvalidIndex;
};
// The default HierarchicalTimingWheelHandleAccessor, which simply forwards
// calls to the underlying type. It assumes |T| provides
// HierarchicalTimingWheelHandle storage and will simply forward calls to
// equivalent member function.
template <typename T>
struct DefaultHierarchicalTimingWheelHandleAccessor {
void SetTimingWheelHandle(T* element,
internal::TimingWheelHandle handle) const {
HierarchicalTimingWheelHandle* htw_handle = element->handle();
htw_handle->SetTimingWheelHandle(handle);
}
void ClearTimingWheelHandle(T* element) const {
HierarchicalTimingWheelHandle* htw_handle = element->handle();
htw_handle->ClearTimingWheelHandle();
}
HeapHandle GetHeapHandle(const T* element) const {
HierarchicalTimingWheelHandle* htw_handle = element->handle();
return htw_handle->GetHeapHandle();
}
void SetHeapHandle(T* element, HeapHandle handle) const {
HierarchicalTimingWheelHandle* htw_handle = element->handle();
htw_handle->SetHeapHandle(handle);
}
void ClearHeapHandle(T* element) const {
HierarchicalTimingWheelHandle* htw_handle = element->handle();
htw_handle->ClearHeapHandle();
}
void SetHierarchyIndex(T* element, size_t hierarchy_index) const {
HierarchicalTimingWheelHandle* htw_handle = element->handle();
htw_handle->SetHierarchyIndex(hierarchy_index);
}
void ClearHierarchyIndex(T* element) const {
HierarchicalTimingWheelHandle* htw_handle = element->handle();
htw_handle->ClearHierarchyIndex();
}
};
// Gets the delayed run time of the |element|. Assumes the |element| has a
// public |delayed_run_time| member variable.
template <typename T>
struct GetDelayedRunTime {
TimeTicks operator()(const T& element) { return element.delayed_run_time; }
};
// Used for ordering elements in the IntrusiveHeap in the hierarchy.
template <typename T>
struct Compare {
bool operator()(const T& lhs, const T& rhs) const {
return lhs.delayed_run_time > rhs.delayed_run_time;
}
};
// This class is made to optimize the data structure IntrusiveHeap. Timers are
// implemented by scheduling the user task using TaskRunner::PostDelayedTask().
// The elements are then inserted in an InstrusiveHeap. It suffers from its time
// complexity of O(LgN) removal and insertion.
//
// This class is an implementation of timing wheel technique. It contains a
// hierarchy which is a sequence of timing wheels and heaps with different
// granularities used to span a greater range of intervals. There are two heaps
// in the hierarchy, each placed on the two ends of the sequence of timing
// wheels.
//
// |T| is a typename for the intervals that are inserted in this class.
// |TotalWheels| is the number of timing wheels to be constructed in the
// hierarchy. |WheelSize| is the number of buckets in each of the timing wheel.
// |SmallestBucketDeltaInMicroseconds| corresponds to the time delta per
// bucket for the smallest timing wheel in the hierarchy. The time delta per
// bucket for the following timing wheels are WheelSize *
// |time_delta_per_bucket| of previous timing wheel.
// |HierarchicalTimingWheelHandleAccessor| is the type of the object which under
// the hood manages the HierarchicalTimingWheelHandle. |GetDelayedRunTime| is a
// function which returns the time when the element is due at.
//
// Example:
// Note: The number enclosing in the curly brackets "{}" are the data
// structure's hierarchy number. It exists to understand their order in the
// hierarchy.
//
// TotalWheels = 4
// WheelSize = 100
// SmallestBucketDeltaInMicroseconds = 500 microseconds
//
// Heap{0} - all elements with delays below 500 microseconds
//
// Wheel{1} - each bucket of 500microseconds = 0.5ms.
// bucket0 contains 0 <= delta < 0.5ms
// bucket1 contains 0.5 <= delta < 1ms
// Wheel1 contains 0.5 <= delta < 50ms
//
// Wheel{2} - each bucket of 50ms.
// bucket0 contains 0 <= delta < 50ms
// bucket1 contains 50ms <= delta < 100ms
// Wheel1 contains 50ms <= delta < 5s
//
// Wheel{3} - each bucket of 5s.
// bucket0 contains 0 <= delta < 5s
// bucket1 contains 5s <= delta < 10s
// Wheel1 contains 5s <= delta < 500s
//
// Wheel{4} - each bucket of 500s.
// bucket0 contains 0 <= delta < 500s
// bucket1 contains 500s <= delta < 1000s
// Wheel1 contains 500s <= delta < 50000s
//
// Heap{5} - all elements with delay above or equals to 500microseconds *
// (100^4)
//
// This class takes O(1) time to insert and cancel timers. However, if a element
// has a very small or big timer interval, then it's placed in a heap. This
// means, the removal and insertion won't be as efficient. However, the
// expectation is that such elements with very small or very big intervals would
// be very few.
template <typename T,
size_t TotalWheels,
size_t WheelSize,
size_t SmallestBucketDeltaInMicroseconds,
typename HierarchicalTimingWheelHandleAccessor =
DefaultHierarchicalTimingWheelHandleAccessor<T>,
typename GetDelayedRunTime = GetDelayedRunTime<T>,
typename Compare = Compare<T>>
class HierarchicalTimingWheel {
public:
// Construct a HierarchicalTimingWheel instance where |last_wakeup|
// corresponds to the last time it was updated.
explicit HierarchicalTimingWheel(
TimeTicks last_wakeup,
const HierarchicalTimingWheelHandleAccessor&
hierarchical_timing_wheel_handle_accessor =
HierarchicalTimingWheelHandleAccessor(),
const GetDelayedRunTime& get_delayed_run_time = GetDelayedRunTime(),
const Compare compare = Compare())
: small_delay_heap_(compare, hierarchical_timing_wheel_handle_accessor),
large_delay_heap_(compare, hierarchical_timing_wheel_handle_accessor),
last_wakeup_(last_wakeup),
hierarchical_timing_wheel_handle_accessor_(
hierarchical_timing_wheel_handle_accessor),
get_delayed_run_time_(get_delayed_run_time) {}
HierarchicalTimingWheel(HierarchicalTimingWheel&&) = delete;
HierarchicalTimingWheel& operator=(HierarchicalTimingWheel&&) = delete;
HierarchicalTimingWheel(const HierarchicalTimingWheel&) = delete;
HierarchicalTimingWheel& operator=(const HierarchicalTimingWheel&) = delete;
~HierarchicalTimingWheel() = default;
size_t Size() {
return small_delay_heap_.size() + large_delay_heap_.size() +
std::accumulate(std::begin(wheels_), std::end(wheels_), 0,
[](size_t i, auto& wheel) {
return wheel.total_elements() + i;
});
}
// Inserts the |element| based on its delayed run time into one of the
// |wheels_|.
typename std::vector<T>::const_iterator Insert(T element) {
DCHECK(get_delayed_run_time_(element) > last_wakeup_);
const TimeDelta delay = get_delayed_run_time_(element) - last_wakeup_;
const size_t hierarchy_index = FindHierarchyIndex(delay);
if (IsHeap(hierarchy_index)) {
auto& heap = GetHeapForHierarchyIndex(hierarchy_index);
hierarchical_timing_wheel_handle_accessor_.SetHierarchyIndex(
&element, hierarchy_index);
auto it = heap.insert(std::move(element));
return it;
} else {
auto& wheel = GetTimingWheelForHierarchyIndex(hierarchy_index);
hierarchical_timing_wheel_handle_accessor_.SetHierarchyIndex(
&element, hierarchy_index);
auto it = wheel.Insert(std::move(element), delay);
return it;
}
}
// Updates the hierarchy and reassigns the elements that need to be
// placed in a different timing wheel or heap to reflect their respective
// delay. It returns the elements that are expired.
std::vector<T> Update(TimeTicks now) {
DCHECK(now >= last_wakeup_);
std::vector<T> expired_elements;
// Check for expired elements in the small delay heap.
while (!small_delay_heap_.empty() &&
get_delayed_run_time_(small_delay_heap_.top()) <= now) {
T element = small_delay_heap_.take_top();
// Clear the hierarchy index since the |element| will be returned.
hierarchical_timing_wheel_handle_accessor_.ClearHierarchyIndex(&element);
expired_elements.push_back(std::move(element));
}
// Look into the timing wheels for elements which have either expired or
// need to be moved down the hierarchy.
std::vector<T> elements;
const TimeDelta time_delta = now - last_wakeup_;
const size_t timing_wheels_delay_upperbound =
SmallestBucketDeltaInMicroseconds * Pow(WheelSize, TotalWheels);
const TimeTicks timing_wheels_maximum_delayed_run_time =
now + Milliseconds(timing_wheels_delay_upperbound);
last_wakeup_ = now;
for (size_t wheel_index = 0; wheel_index < TotalWheels; wheel_index++) {
wheels_[wheel_index].AdvanceTimeAndRemoveExpiredElements(time_delta,
elements);
}
// Keep on removing the top elements from the |large_delay_heap_| which
// could be either moved down the hierarchy or are expired.
while (!large_delay_heap_.empty() &&
get_delayed_run_time_(large_delay_heap_.top()) <
timing_wheels_maximum_delayed_run_time) {
elements.push_back(std::move(large_delay_heap_.take_top()));
}
// Re-insert elements which haven't expired yet.
for (auto& element : elements) {
if (now >= get_delayed_run_time_(element)) {
hierarchical_timing_wheel_handle_accessor_.ClearHierarchyIndex(
&element);
expired_elements.emplace_back(std::move(element));
} else {
// Doesn't clear hierarchy index since the element will have their
// hierarchy index overwritten when re-inserted.
Insert(std::move(element));
}
}
return expired_elements;
}
// Removes the |element|. This is considered as the element getting cancelled
// and will never be run.
void Remove(HierarchicalTimingWheelHandle& handle) {
DCHECK(handle.IsValid());
if (handle.GetTimingWheelHandle().IsValid()) {
auto& wheel = GetTimingWheelForHierarchyIndex(handle.GetHierarchyIndex());
wheel.Remove(handle.GetTimingWheelHandle());
} else {
auto& heap = GetHeapForHierarchyIndex(handle.GetHierarchyIndex());
heap.erase(handle.GetHeapHandle());
}
}
// Returns the earliest due element in all of the hierarchy. This method
// should only called when the HierarchicalTimingWheel is not empty.
typename std::vector<T>::const_reference Top() {
DCHECK_NE(Size(), 0u);
// Check for smallest elements heap first.
if (!small_delay_heap_.empty()) {
return small_delay_heap_.top();
}
// Iterate from smallest to biggest element wheel.
for (size_t i = 0; i < TotalWheels; i++) {
if (wheels_[i].total_elements() != 0) {
return wheels_[i].Top();
}
}
// The result must be in the biggest elements heap.
return large_delay_heap_.top();
}
private:
bool IsHeap(size_t hierarchy_index) {
/* Cobalt
return hierarchy_index == 0 or hierarchy_index == TotalWheels + 1;
Cobalt */
return hierarchy_index == 0 || hierarchy_index == TotalWheels + 1;
}
auto& GetHeapForHierarchyIndex(size_t hierarchy_index) {
DCHECK(hierarchy_index == 0 || hierarchy_index == TotalWheels + 1);
return hierarchy_index == 0 ? small_delay_heap_ : large_delay_heap_;
}
auto& GetTimingWheelForHierarchyIndex(size_t hierarchy_index) {
DCHECK(hierarchy_index > 0);
DCHECK(hierarchy_index < TotalWheels + 1);
return wheels_[hierarchy_index - 1];
}
// Calculates the hierarchy index at which a element with |delay| should be
// appended in.
size_t FindHierarchyIndex(TimeDelta delay) {
DCHECK(!delay.is_zero());
if (delay < Microseconds(SmallestBucketDeltaInMicroseconds))
return 0;
for (size_t i = 0; i < TotalWheels; i++) {
if (delay < (wheels_[i].time_delta_per_bucket() * WheelSize)) {
return i + 1;
}
}
// Return the index of the heap placed at the end of the hierarchy.
return TotalWheels + 1;
}
// Computes |a| to the power of |b| at compile time. This is used to compute
// the parameter for |TimingWheel| when generating |wheels_| at compile
// time.
constexpr static std::size_t Pow(size_t a, size_t b) {
size_t res = 1;
for (size_t i = 0; i < b; i++) {
res *= a;
}
return res;
}
using Wheel =
typename internal::TimingWheel<T,
WheelSize,
HierarchicalTimingWheelHandleAccessor,
GetDelayedRunTime>;
// Generates |wheels_| at compile time.
template <size_t... I>
static std::array<Wheel, TotalWheels> MakeWheels(std::index_sequence<I...>) {
return {(Wheel(Microseconds(SmallestBucketDeltaInMicroseconds *
Pow(WheelSize, I))))...};
}
// The timing wheels where the elements are added according to their delay.
std::array<Wheel, TotalWheels> wheels_ =
MakeWheels(std::make_index_sequence<TotalWheels>{});
// There are two heaps enclosing the sequence of timing wheels. The first one
// contains elements whose delay is too small to enter a timing wheel. The
// second one contains elements whose delay is too big to enter a timing
// wheel.
IntrusiveHeap<T, Compare, HierarchicalTimingWheelHandleAccessor>
small_delay_heap_;
IntrusiveHeap<T, Compare, HierarchicalTimingWheelHandleAccessor>
large_delay_heap_;
// The last time when the timing wheels were updated.
TimeTicks last_wakeup_;
HierarchicalTimingWheelHandleAccessor
hierarchical_timing_wheel_handle_accessor_;
GetDelayedRunTime get_delayed_run_time_;
};
} // namespace base::sequence_manager
#endif