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