• 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_TIMING_WHEEL_H_
6 #define BASE_TASK_SEQUENCE_MANAGER_TIMING_WHEEL_H_
7 
8 #include <algorithm>
9 #include <array>
10 #include <limits>
11 #include <memory>
12 #include <vector>
13 
14 #include "base/notreached.h"
15 #include "base/time/time.h"
16 
17 namespace base::sequence_manager::internal {
18 
19 // Intended as a wrapper around a |bucket_index_| and |element_element_index_|
20 // in the vector storage backing a TimingWheel. A TimingWheelHandle is
21 // associated with each element in a TimingWheel, and is maintained by
22 // the timing wheel as the object moves around within it. It can be used to
23 // subsequently remove the element, or update it in place.
24 class BASE_EXPORT TimingWheelHandle {
25  public:
26   enum : size_t { kInvalidIndex = std::numeric_limits<size_t>::max() };
27 
28   constexpr TimingWheelHandle() = default;
29 
30   constexpr TimingWheelHandle(const TimingWheelHandle& other) = default;
31   TimingWheelHandle(TimingWheelHandle&& other) noexcept;
32 
33   TimingWheelHandle& operator=(const TimingWheelHandle& other) = default;
34   TimingWheelHandle& operator=(TimingWheelHandle&& other) noexcept;
35 
36   ~TimingWheelHandle() = default;
37 
38   // Returns an invalid TimingWheelHandle.
39   static TimingWheelHandle Invalid();
40 
41   // Resets this handle back to an invalid state.
42   void Reset();
43 
44   bool IsValid() const;
45 
46   // Accessors.
47   size_t bucket_index() const;
48   size_t element_index() const;
49 
50  private:
51   template <typename T,
52             size_t WheelSize,
53             typename TimingWheelHandleAccessor,
54             typename GetDelayedRunTime>
55   friend class TimingWheel;
56 
57   // Only TimingWheels can create valid TimingWheelHandles.
58   explicit TimingWheelHandle(size_t bucket_index, size_t element_index);
59 
60   // The index of the bucket in the timing wheel where the element is in.
61   size_t bucket_index_ = kInvalidIndex;
62 
63   // The index of the element in the bucket where the element is in.
64   size_t element_index_ = kInvalidIndex;
65 };
66 
67 // This class implements a container that acts as timer queue where element are
68 // associated with a delay. It provides efficient retrieval of earliest
69 // elements. It also provides constant time element removal. To facilitate this,
70 // each element has associated with it a TimingWheelHandle (an opaque wrapper
71 // around the index at which the element is stored), which is maintained by the
72 // wheel as elements move within it. Only elements whose delay is between
73 // |time_delta_per_bucket_| and WheelSize*|time_delta_per_bucket_| can be
74 // inserted in a TimingWheel. |T| is a typename for element. |WheelSize| is the
75 // number of buckets this TimingWheel has. |TimingWheelHandleAccessor| is the
76 // type of the object which under the hood manages the TimingWheelHandle.
77 // |GetDelayedRunTime| is a functor which returns the time when the element is
78 // due at.
79 template <typename T,
80           size_t WheelSize,
81           typename TimingWheelHandleAccessor,
82           typename GetDelayedRunTime>
83 class TimingWheel {
84  public:
85   // Constructs a TimingWheel instance where each bucket corresponds to a
86   // TimeDelta of |time_delta_per_bucket_|.
87   explicit TimingWheel(
88       TimeDelta time_delta_per_bucket,
89       const GetDelayedRunTime& get_delayed_run_time = GetDelayedRunTime())
time_delta_per_bucket_(time_delta_per_bucket)90       : time_delta_per_bucket_(time_delta_per_bucket),
91         last_updated_bucket_index_(0),
92         time_passed_(Microseconds(0)),
93         get_delayed_run_time_(get_delayed_run_time) {}
94 
95   TimingWheel(TimingWheel&&) = delete;
96   TimingWheel& operator=(TimingWheel&&) = delete;
97 
98   TimingWheel(const TimingWheel&) = delete;
99   TimingWheel& operator=(const TimingWheel&) = delete;
100 
101   ~TimingWheel() = default;
102 
103   // Inserts the |element| into the bucket based on its delay. This is the delay
104   // relative to a baseline implied by the last call to
105   // |AdvanceTimeAndRemoveExpiredElements| method.
Insert(T element,const TimeDelta delay)106   typename std::vector<T>::const_iterator Insert(T element,
107                                                  const TimeDelta delay) {
108     DCHECK_GE(delay, time_delta_per_bucket_);
109     DCHECK_LT(delay, time_delta_per_bucket_ * WheelSize);
110 
111     const size_t bucket_index = CalculateBucketIndex(delay);
112     auto& bucket = buckets_[bucket_index];
113     bucket.push_back(std::move(element));
114     const size_t element_index = bucket.size() - 1;
115 
116     // Sets the handle for the element.
117     timing_wheel_handle_accessor.SetTimingWheelHandle(
118         &buckets_[bucket_index][element_index],
119         TimingWheelHandle(bucket_index, element_index));
120 
121     total_elements_ += 1;
122     return bucket.cend() - 1;
123   }
124 
125   // Removes the element which holds this |handle|.
Remove(TimingWheelHandle handle)126   void Remove(TimingWheelHandle handle) {
127     DCHECK(handle.IsValid());
128 
129     const size_t bucket_index = handle.bucket_index();
130     const size_t element_index = handle.element_index();
131     DCHECK(IsBounded(bucket_index, element_index));
132 
133     auto& bucket = buckets_[bucket_index];
134     const size_t last_index_of_bucket = bucket.size() - 1;
135 
136     // Swaps the element's position with the last element for removal. The
137     // swapped element is assigned with an updated handle.
138     if (element_index != last_index_of_bucket) {
139       timing_wheel_handle_accessor.SetTimingWheelHandle(
140           &bucket[last_index_of_bucket],
141           TimingWheelHandle(bucket_index, element_index));
142       std::swap(bucket[element_index], bucket[last_index_of_bucket]);
143     }
144 
145     // The handle of the last element doesn't need to be cleared since the
146     // element is destroyed right after.
147     bucket.pop_back();
148     total_elements_ -= 1;
149   }
150 
151   // Updates the internal state to reflect the latest wakeup and returns the
152   // expired elements through an out-parameter so that the caller can keep using
153   // the same vector when advancing multiple TimingWheels.
AdvanceTimeAndRemoveExpiredElements(const TimeDelta time_delta,std::vector<T> & expired_elements)154   void AdvanceTimeAndRemoveExpiredElements(const TimeDelta time_delta,
155                                            std::vector<T>& expired_elements) {
156     const size_t nb_buckets_passed =
157         (time_passed_ + time_delta) / time_delta_per_bucket_ + 1;
158     const size_t new_bucket_index =
159         (last_updated_bucket_index_ + nb_buckets_passed) % WheelSize;
160     const TimeDelta new_time_passed =
161         (time_passed_ + time_delta) % time_delta_per_bucket_;
162 
163     // Ensures each bucket is iterated over at most once if |nb_buckets_passed|
164     // is bigger than the total number of buckets.
165     const size_t nb_buckets_to_traverse =
166         std::min(nb_buckets_passed, WheelSize);
167     for (size_t i = 0; i < nb_buckets_to_traverse; i++) {
168       last_updated_bucket_index_ = (last_updated_bucket_index_ + 1) % WheelSize;
169       ExtractElementsFromBucket(last_updated_bucket_index_, expired_elements);
170     }
171 
172     last_updated_bucket_index_ = new_bucket_index;
173     time_passed_ = new_time_passed;
174   }
175 
176   // Returns the earliest due element.
Top()177   typename std::vector<T>::const_reference Top() {
178     DCHECK(total_elements_ != 0);
179     for (size_t i = 0; i < WheelSize; i++) {
180       const size_t bucket_index = (i + last_updated_bucket_index_) % WheelSize;
181       auto& bucket = buckets_[bucket_index];
182       if (bucket.size() == 0) {
183         continue;
184       }
185 
186       auto it = std::min_element(
187           bucket.begin(), bucket.end(), [this](const T& a, const T& b) {
188             return get_delayed_run_time_(a) > get_delayed_run_time_(b);
189           });
190       return *it;
191     }
192 
193     NOTREACHED();
194     return buckets_[0].back();
195   }
196 
time_delta_per_bucket()197   TimeDelta time_delta_per_bucket() { return time_delta_per_bucket_; }
total_elements()198   size_t total_elements() { return total_elements_; }
199 
200  private:
201   // Checks if the |bucket_index| and |element_index| is bounded.
IsBounded(const size_t bucket_index,const size_t element_index)202   bool IsBounded(const size_t bucket_index, const size_t element_index) const {
203     return bucket_index < WheelSize &&
204            element_index < buckets_[bucket_index].size();
205   }
206 
207   // Calculates the index at which a task with |delay| should be inserted in.
CalculateBucketIndex(const TimeDelta delay)208   size_t CalculateBucketIndex(const TimeDelta delay) const {
209     const size_t nb_buckets_passed =
210         (delay + time_passed_) / time_delta_per_bucket_;
211     const size_t bucket_index = last_updated_bucket_index_ + nb_buckets_passed;
212 
213     // |bucket_index| should not be more than |WheelSize|.
214     return bucket_index % WheelSize;
215   }
216 
217   // Removes the elements from the index in |buckets_| and returns the
218   // expired elements through an out-parameter.
ExtractElementsFromBucket(const size_t bucket_index,std::vector<T> & expired_elements)219   void ExtractElementsFromBucket(const size_t bucket_index,
220                                  std::vector<T>& expired_elements) {
221     std::vector<T>& bucket = buckets_[bucket_index];
222     expired_elements.reserve(expired_elements.size() + bucket.size());
223 
224     for (auto& element : bucket) {
225       timing_wheel_handle_accessor.ClearTimingWheelHandle(&element);
226       expired_elements.push_back(std::move(element));
227     }
228 
229     total_elements_ -= bucket.size();
230     bucket.clear();
231   }
232 
233   TimingWheelHandleAccessor timing_wheel_handle_accessor;
234 
235   // The time period each bucket contains.
236   const TimeDelta time_delta_per_bucket_;
237 
238   // The buckets where the elements are added according to their delay.
239   std::array<std::vector<T>, WheelSize> buckets_;
240 
241   // The index of the bucket that was last updated. This helps in Inserting and
242   // expired elements.
243   size_t last_updated_bucket_index_;
244 
245   // The time passed unaccounted for after updating
246   // |last_updated_bucket_index_|. This will be aggregated with the
247   // |time_passed_| at the next wakeup.
248   TimeDelta time_passed_;
249 
250   // The number of elements in |buckets_|.
251   size_t total_elements_ = 0;
252 
253   // The functor to get the delayed run time of elements.
254   GetDelayedRunTime get_delayed_run_time_;
255 };
256 
257 }  // namespace base::sequence_manager::internal
258 
259 #endif
260