• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef BASE_TASK_SEQUENCE_MANAGER_HIERARCHICAL_TIMING_WHEEL_H_
6 #define BASE_TASK_SEQUENCE_MANAGER_HIERARCHICAL_TIMING_WHEEL_H_
7 
8 #include <algorithm>
9 #include <array>
10 #include <numeric>
11 #include <vector>
12 
13 #include "base/containers/intrusive_heap.h"
14 #include "base/task/sequence_manager/timing_wheel.h"
15 #include "base/time/time.h"
16 #include "third_party/abseil-cpp/absl/types/optional.h"
17 
18 namespace base::sequence_manager {
19 
20 // A union of |TimingWheelHandle| and |HeapHandle|. At any given
21 // time it holds a value of one of its alternative types. It can only
22 // have either. This class is maintained by the hierarchical timing
23 // wheel as the object moves around within it. It can be used to subsequently
24 // remove the element.
25 class BASE_EXPORT HierarchicalTimingWheelHandle {
26  public:
27   enum : size_t { kInvalidIndex = std::numeric_limits<size_t>::max() };
28 
29   HierarchicalTimingWheelHandle();
30 
31   HierarchicalTimingWheelHandle(const HierarchicalTimingWheelHandle& other) =
32       default;
33   HierarchicalTimingWheelHandle(HierarchicalTimingWheelHandle&& other) noexcept;
34 
35   HierarchicalTimingWheelHandle& operator=(
36       const HierarchicalTimingWheelHandle& other) = default;
37   HierarchicalTimingWheelHandle& operator=(
38       HierarchicalTimingWheelHandle&& other) noexcept;
39 
40   ~HierarchicalTimingWheelHandle();
41 
42   // TimingWheel contract
43   internal::TimingWheelHandle GetTimingWheelHandle() const;
44   void SetTimingWheelHandle(internal::TimingWheelHandle timing_wheel_handle);
45   void ClearTimingWheelHandle();
46 
47   // IntrusiveHeap contract
48   HeapHandle GetHeapHandle();
49   void SetHeapHandle(HeapHandle handle);
50   void ClearHeapHandle();
51 
52   size_t GetHierarchyIndex() const;
53   void SetHierarchyIndex(size_t hierarchy_index);
54   void ClearHierarchyIndex();
55 
56   // Gets a default constructed HierarchicalTimingWheelHandle.
57   static HierarchicalTimingWheelHandle Invalid();
58 
59   bool IsValid() const;
60 
61  private:
62   // The handle of the timing wheel in the hierarchical timing wheel where the
63   // element is in.
64   internal::TimingWheelHandle timing_wheel_handle_;
65 
66   // The handle of the heap in the hierarchical timing wheel where the element
67   // is in.
68   HeapHandle heap_handle_;
69 
70   // The index in the hierarchy of timing wheels and heaps, this handle belongs
71   // to.
72   size_t hierarchy_index_ = kInvalidIndex;
73 };
74 
75 // The default HierarchicalTimingWheelHandleAccessor, which simply forwards
76 // calls to the underlying type. It assumes |T| provides
77 // HierarchicalTimingWheelHandle storage and will simply forward calls to
78 // equivalent member function.
79 template <typename T>
80 struct DefaultHierarchicalTimingWheelHandleAccessor {
SetTimingWheelHandleDefaultHierarchicalTimingWheelHandleAccessor81   void SetTimingWheelHandle(T* element,
82                             internal::TimingWheelHandle handle) const {
83     HierarchicalTimingWheelHandle* htw_handle = element->handle();
84     htw_handle->SetTimingWheelHandle(handle);
85   }
86 
ClearTimingWheelHandleDefaultHierarchicalTimingWheelHandleAccessor87   void ClearTimingWheelHandle(T* element) const {
88     HierarchicalTimingWheelHandle* htw_handle = element->handle();
89     htw_handle->ClearTimingWheelHandle();
90   }
91 
GetHeapHandleDefaultHierarchicalTimingWheelHandleAccessor92   HeapHandle GetHeapHandle(const T* element) const {
93     HierarchicalTimingWheelHandle* htw_handle = element->handle();
94     return htw_handle->GetHeapHandle();
95   }
96 
SetHeapHandleDefaultHierarchicalTimingWheelHandleAccessor97   void SetHeapHandle(T* element, HeapHandle handle) const {
98     HierarchicalTimingWheelHandle* htw_handle = element->handle();
99     htw_handle->SetHeapHandle(handle);
100   }
101 
ClearHeapHandleDefaultHierarchicalTimingWheelHandleAccessor102   void ClearHeapHandle(T* element) const {
103     HierarchicalTimingWheelHandle* htw_handle = element->handle();
104     htw_handle->ClearHeapHandle();
105   }
106 
SetHierarchyIndexDefaultHierarchicalTimingWheelHandleAccessor107   void SetHierarchyIndex(T* element, size_t hierarchy_index) const {
108     HierarchicalTimingWheelHandle* htw_handle = element->handle();
109     htw_handle->SetHierarchyIndex(hierarchy_index);
110   }
111 
ClearHierarchyIndexDefaultHierarchicalTimingWheelHandleAccessor112   void ClearHierarchyIndex(T* element) const {
113     HierarchicalTimingWheelHandle* htw_handle = element->handle();
114     htw_handle->ClearHierarchyIndex();
115   }
116 };
117 
118 // Gets the delayed run time of the |element|. Assumes the |element| has a
119 // public |delayed_run_time| member variable.
120 template <typename T>
121 struct GetDelayedRunTime {
operatorGetDelayedRunTime122   TimeTicks operator()(const T& element) { return element.delayed_run_time; }
123 };
124 
125 // Used for ordering elements in the IntrusiveHeap in the hierarchy.
126 template <typename T>
127 struct Compare {
operatorCompare128   bool operator()(const T& lhs, const T& rhs) const {
129     return lhs.delayed_run_time > rhs.delayed_run_time;
130   }
131 };
132 
133 // This class is made to optimize the data structure IntrusiveHeap. Timers are
134 // implemented by scheduling the user task using TaskRunner::PostDelayedTask().
135 // The elements are then inserted in an InstrusiveHeap. It suffers from its time
136 // complexity of O(LgN) removal and insertion.
137 //
138 // This class is an implementation of timing wheel technique. It contains a
139 // hierarchy which is a sequence of timing wheels and heaps with different
140 // granularities used to span a greater range of intervals. There are two heaps
141 // in the hierarchy, each placed on the two ends of the sequence of timing
142 // wheels.
143 //
144 // |T| is a typename for the intervals that are inserted in this class.
145 // |TotalWheels| is the number of timing wheels to be constructed in the
146 // hierarchy. |WheelSize| is the number of buckets in each of the timing wheel.
147 // |SmallestBucketDeltaInMicroseconds| corresponds to the time delta per
148 // bucket for the smallest timing wheel in the hierarchy. The time delta per
149 // bucket for the following timing wheels are WheelSize *
150 // |time_delta_per_bucket| of previous timing wheel.
151 // |HierarchicalTimingWheelHandleAccessor| is the type of the object which under
152 // the hood manages the HierarchicalTimingWheelHandle. |GetDelayedRunTime| is a
153 // function which returns the time when the element is due at.
154 //
155 // Example:
156 // Note: The number enclosing in the curly brackets "{}" are the data
157 // structure's hierarchy number. It exists to understand their order in the
158 // hierarchy.
159 //
160 // TotalWheels = 4
161 // WheelSize = 100
162 // SmallestBucketDeltaInMicroseconds = 500 microseconds
163 //
164 // Heap{0} - all elements with delays below 500 microseconds
165 //
166 // Wheel{1} - each bucket of 500microseconds = 0.5ms.
167 //          bucket0 contains 0 <= delta < 0.5ms
168 //          bucket1 contains 0.5 <= delta < 1ms
169 //          Wheel1 contains 0.5 <= delta < 50ms
170 //
171 // Wheel{2} - each bucket of 50ms.
172 //          bucket0 contains 0 <= delta < 50ms
173 //          bucket1 contains 50ms <= delta < 100ms
174 //          Wheel1 contains 50ms <= delta < 5s
175 //
176 // Wheel{3} - each bucket of 5s.
177 //          bucket0 contains 0 <= delta < 5s
178 //          bucket1 contains 5s <= delta < 10s
179 //          Wheel1 contains 5s <= delta < 500s
180 //
181 // Wheel{4} - each bucket of 500s.
182 //          bucket0 contains 0 <= delta < 500s
183 //          bucket1 contains 500s <= delta < 1000s
184 //          Wheel1 contains 500s <= delta < 50000s
185 //
186 // Heap{5} - all elements with delay above or equals to 500microseconds *
187 // (100^4)
188 //
189 // This class takes O(1) time to insert and cancel timers. However, if a element
190 // has a very small or big timer interval, then it's placed in a heap. This
191 // means, the removal and insertion won't be as efficient. However, the
192 // expectation is that such elements with very small or very big intervals would
193 // be very few.
194 
195 template <typename T,
196           size_t TotalWheels,
197           size_t WheelSize,
198           size_t SmallestBucketDeltaInMicroseconds,
199           typename HierarchicalTimingWheelHandleAccessor =
200               DefaultHierarchicalTimingWheelHandleAccessor<T>,
201           typename GetDelayedRunTime = GetDelayedRunTime<T>,
202           typename Compare = Compare<T>>
203 class HierarchicalTimingWheel {
204  public:
205   // Construct a HierarchicalTimingWheel instance where |last_wakeup|
206   // corresponds to the last time it was updated.
207   explicit HierarchicalTimingWheel(
208       TimeTicks last_wakeup,
209       const HierarchicalTimingWheelHandleAccessor&
210           hierarchical_timing_wheel_handle_accessor =
211               HierarchicalTimingWheelHandleAccessor(),
212       const GetDelayedRunTime& get_delayed_run_time = GetDelayedRunTime(),
213       const Compare compare = Compare())
small_delay_heap_(compare,hierarchical_timing_wheel_handle_accessor)214       : small_delay_heap_(compare, hierarchical_timing_wheel_handle_accessor),
215         large_delay_heap_(compare, hierarchical_timing_wheel_handle_accessor),
216         last_wakeup_(last_wakeup),
217         hierarchical_timing_wheel_handle_accessor_(
218             hierarchical_timing_wheel_handle_accessor),
219         get_delayed_run_time_(get_delayed_run_time) {}
220 
221   HierarchicalTimingWheel(HierarchicalTimingWheel&&) = delete;
222   HierarchicalTimingWheel& operator=(HierarchicalTimingWheel&&) = delete;
223 
224   HierarchicalTimingWheel(const HierarchicalTimingWheel&) = delete;
225   HierarchicalTimingWheel& operator=(const HierarchicalTimingWheel&) = delete;
226 
227   ~HierarchicalTimingWheel() = default;
228 
Size()229   size_t Size() {
230     return small_delay_heap_.size() + large_delay_heap_.size() +
231            std::accumulate(std::begin(wheels_), std::end(wheels_), 0,
232                            [](size_t i, auto& wheel) {
233                              return wheel.total_elements() + i;
234                            });
235   }
236 
237   // Inserts the |element| based on its delayed run time into one of the
238   // |wheels_|.
Insert(T element)239   typename std::vector<T>::const_iterator Insert(T element) {
240     DCHECK(get_delayed_run_time_(element) > last_wakeup_);
241 
242     const TimeDelta delay = get_delayed_run_time_(element) - last_wakeup_;
243     const size_t hierarchy_index = FindHierarchyIndex(delay);
244 
245     if (IsHeap(hierarchy_index)) {
246       auto& heap = GetHeapForHierarchyIndex(hierarchy_index);
247       hierarchical_timing_wheel_handle_accessor_.SetHierarchyIndex(
248           &element, hierarchy_index);
249       auto it = heap.insert(std::move(element));
250       return it;
251     } else {
252       auto& wheel = GetTimingWheelForHierarchyIndex(hierarchy_index);
253       hierarchical_timing_wheel_handle_accessor_.SetHierarchyIndex(
254           &element, hierarchy_index);
255       auto it = wheel.Insert(std::move(element), delay);
256       return it;
257     }
258   }
259 
260   // Updates the hierarchy and reassigns the elements that need to be
261   // placed in a different timing wheel or heap to reflect their respective
262   // delay. It returns the elements that are expired.
Update(TimeTicks now)263   std::vector<T> Update(TimeTicks now) {
264     DCHECK(now >= last_wakeup_);
265     std::vector<T> expired_elements;
266 
267     // Check for expired elements in the small delay heap.
268     while (!small_delay_heap_.empty() &&
269            get_delayed_run_time_(small_delay_heap_.top()) <= now) {
270       T element = small_delay_heap_.take_top();
271 
272       // Clear the hierarchy index since the |element| will be returned.
273       hierarchical_timing_wheel_handle_accessor_.ClearHierarchyIndex(&element);
274 
275       expired_elements.push_back(std::move(element));
276     }
277 
278     // Look into the timing wheels for elements which have either expired or
279     // need to be moved down the hierarchy.
280     std::vector<T> elements;
281     const TimeDelta time_delta = now - last_wakeup_;
282     const size_t timing_wheels_delay_upperbound =
283         SmallestBucketDeltaInMicroseconds * Pow(WheelSize, TotalWheels);
284     const TimeTicks timing_wheels_maximum_delayed_run_time =
285         now + Milliseconds(timing_wheels_delay_upperbound);
286     last_wakeup_ = now;
287 
288     for (size_t wheel_index = 0; wheel_index < TotalWheels; wheel_index++) {
289       wheels_[wheel_index].AdvanceTimeAndRemoveExpiredElements(time_delta,
290                                                                elements);
291     }
292 
293     // Keep on removing the top elements from the |large_delay_heap_| which
294     // could be either moved down the hierarchy or are expired.
295     while (!large_delay_heap_.empty() &&
296            get_delayed_run_time_(large_delay_heap_.top()) <
297                timing_wheels_maximum_delayed_run_time) {
298       elements.push_back(std::move(large_delay_heap_.take_top()));
299     }
300 
301     // Re-insert elements which haven't expired yet.
302     for (auto& element : elements) {
303       if (now >= get_delayed_run_time_(element)) {
304         hierarchical_timing_wheel_handle_accessor_.ClearHierarchyIndex(
305             &element);
306         expired_elements.emplace_back(std::move(element));
307       } else {
308         // Doesn't clear hierarchy index since the element will have their
309         // hierarchy index overwritten when re-inserted.
310         Insert(std::move(element));
311       }
312     }
313 
314     return expired_elements;
315   }
316 
317   // Removes the |element|. This is considered as the element getting cancelled
318   // and will never be run.
Remove(HierarchicalTimingWheelHandle & handle)319   void Remove(HierarchicalTimingWheelHandle& handle) {
320     DCHECK(handle.IsValid());
321     if (handle.GetTimingWheelHandle().IsValid()) {
322       auto& wheel = GetTimingWheelForHierarchyIndex(handle.GetHierarchyIndex());
323       wheel.Remove(handle.GetTimingWheelHandle());
324     } else {
325       auto& heap = GetHeapForHierarchyIndex(handle.GetHierarchyIndex());
326       heap.erase(handle.GetHeapHandle());
327     }
328   }
329 
330   // Returns the earliest due element in all of the hierarchy. This method
331   // should only called when the HierarchicalTimingWheel is not empty.
Top()332   typename std::vector<T>::const_reference Top() {
333     DCHECK_NE(Size(), 0u);
334 
335     // Check for smallest elements heap first.
336     if (!small_delay_heap_.empty()) {
337       return small_delay_heap_.top();
338     }
339 
340     // Iterate from smallest to biggest element wheel.
341     for (size_t i = 0; i < TotalWheels; i++) {
342       if (wheels_[i].total_elements() != 0) {
343         return wheels_[i].Top();
344       }
345     }
346 
347     // The result must be in the biggest elements heap.
348     return large_delay_heap_.top();
349   }
350 
351  private:
IsHeap(size_t hierarchy_index)352   bool IsHeap(size_t hierarchy_index) {
353     return hierarchy_index == 0 or hierarchy_index == TotalWheels + 1;
354   }
355 
GetHeapForHierarchyIndex(size_t hierarchy_index)356   auto& GetHeapForHierarchyIndex(size_t hierarchy_index) {
357     DCHECK(hierarchy_index == 0 || hierarchy_index == TotalWheels + 1);
358     return hierarchy_index == 0 ? small_delay_heap_ : large_delay_heap_;
359   }
360 
GetTimingWheelForHierarchyIndex(size_t hierarchy_index)361   auto& GetTimingWheelForHierarchyIndex(size_t hierarchy_index) {
362     DCHECK(hierarchy_index > 0);
363     DCHECK(hierarchy_index < TotalWheels + 1);
364     return wheels_[hierarchy_index - 1];
365   }
366 
367   // Calculates the hierarchy index at which a element with |delay| should be
368   // appended in.
FindHierarchyIndex(TimeDelta delay)369   size_t FindHierarchyIndex(TimeDelta delay) {
370     DCHECK(!delay.is_zero());
371 
372     if (delay < Microseconds(SmallestBucketDeltaInMicroseconds))
373       return 0;
374 
375     for (size_t i = 0; i < TotalWheels; i++) {
376       if (delay < (wheels_[i].time_delta_per_bucket() * WheelSize)) {
377         return i + 1;
378       }
379     }
380 
381     // Return the index of the heap placed at the end of the hierarchy.
382     return TotalWheels + 1;
383   }
384 
385   // Computes |a| to the power of |b| at compile time. This is used to compute
386   // the parameter for |TimingWheel| when generating |wheels_| at compile
387   // time.
Pow(size_t a,size_t b)388   constexpr static std::size_t Pow(size_t a, size_t b) {
389     size_t res = 1;
390     for (size_t i = 0; i < b; i++) {
391       res *= a;
392     }
393     return res;
394   }
395 
396   using Wheel =
397       typename internal::TimingWheel<T,
398                                      WheelSize,
399                                      HierarchicalTimingWheelHandleAccessor,
400                                      GetDelayedRunTime>;
401 
402   // Generates |wheels_| at compile time.
403   template <size_t... I>
MakeWheels(std::index_sequence<I...>)404   static std::array<Wheel, TotalWheels> MakeWheels(std::index_sequence<I...>) {
405     return {(Wheel(Microseconds(SmallestBucketDeltaInMicroseconds *
406                                 Pow(WheelSize, I))))...};
407   }
408 
409   // The timing wheels where the elements are added according to their delay.
410   std::array<Wheel, TotalWheels> wheels_ =
411       MakeWheels(std::make_index_sequence<TotalWheels>{});
412 
413   // There are two heaps enclosing the sequence of timing wheels. The first one
414   // contains elements whose delay is too small to enter a timing wheel. The
415   // second one contains elements whose delay is too big to enter a timing
416   // wheel.
417   IntrusiveHeap<T, Compare, HierarchicalTimingWheelHandleAccessor>
418       small_delay_heap_;
419   IntrusiveHeap<T, Compare, HierarchicalTimingWheelHandleAccessor>
420       large_delay_heap_;
421 
422   // The last time when the timing wheels were updated.
423   TimeTicks last_wakeup_;
424 
425   HierarchicalTimingWheelHandleAccessor
426       hierarchical_timing_wheel_handle_accessor_;
427 
428   GetDelayedRunTime get_delayed_run_time_;
429 };
430 
431 }  // namespace base::sequence_manager
432 
433 #endif
434