1 /* 2 * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 #ifndef NET_DCSCTP_TIMER_TIMER_H_ 11 #define NET_DCSCTP_TIMER_TIMER_H_ 12 13 #include <stdint.h> 14 15 #include <algorithm> 16 #include <functional> 17 #include <map> 18 #include <memory> 19 #include <string> 20 #include <utility> 21 22 #include "absl/strings/string_view.h" 23 #include "absl/types/optional.h" 24 #include "api/task_queue/task_queue_base.h" 25 #include "net/dcsctp/public/timeout.h" 26 #include "rtc_base/strong_alias.h" 27 28 namespace dcsctp { 29 30 using TimerID = webrtc::StrongAlias<class TimerIDTag, uint32_t>; 31 using TimerGeneration = webrtc::StrongAlias<class TimerGenerationTag, uint32_t>; 32 33 enum class TimerBackoffAlgorithm { 34 // The base duration will be used for any restart. 35 kFixed, 36 // An exponential backoff is used for restarts, with a 2x multiplier, meaning 37 // that every restart will use a duration that is twice as long as the 38 // previous. 39 kExponential, 40 }; 41 42 struct TimerOptions { TimerOptionsTimerOptions43 explicit TimerOptions(DurationMs duration) 44 : TimerOptions(duration, TimerBackoffAlgorithm::kExponential) {} TimerOptionsTimerOptions45 TimerOptions(DurationMs duration, TimerBackoffAlgorithm backoff_algorithm) 46 : TimerOptions(duration, backoff_algorithm, absl::nullopt) {} TimerOptionsTimerOptions47 TimerOptions(DurationMs duration, 48 TimerBackoffAlgorithm backoff_algorithm, 49 absl::optional<int> max_restarts) 50 : TimerOptions(duration, backoff_algorithm, max_restarts, absl::nullopt) { 51 } TimerOptionsTimerOptions52 TimerOptions(DurationMs duration, 53 TimerBackoffAlgorithm backoff_algorithm, 54 absl::optional<int> max_restarts, 55 absl::optional<DurationMs> max_backoff_duration) 56 : TimerOptions(duration, 57 backoff_algorithm, 58 max_restarts, 59 max_backoff_duration, 60 webrtc::TaskQueueBase::DelayPrecision::kLow) {} TimerOptionsTimerOptions61 TimerOptions(DurationMs duration, 62 TimerBackoffAlgorithm backoff_algorithm, 63 absl::optional<int> max_restarts, 64 absl::optional<DurationMs> max_backoff_duration, 65 webrtc::TaskQueueBase::DelayPrecision precision) 66 : duration(duration), 67 backoff_algorithm(backoff_algorithm), 68 max_restarts(max_restarts), 69 max_backoff_duration(max_backoff_duration), 70 precision(precision) {} 71 72 // The initial timer duration. Can be overridden with `set_duration`. 73 const DurationMs duration; 74 // If the duration should be increased (using exponential backoff) when it is 75 // restarted. If not set, the same duration will be used. 76 const TimerBackoffAlgorithm backoff_algorithm; 77 // The maximum number of times that the timer will be automatically restarted, 78 // or absl::nullopt if there is no limit. 79 const absl::optional<int> max_restarts; 80 // The maximum timeout value for exponential backoff. 81 const absl::optional<DurationMs> max_backoff_duration; 82 // The precision of the webrtc::TaskQueueBase used for scheduling. 83 const webrtc::TaskQueueBase::DelayPrecision precision; 84 }; 85 86 // A high-level timer (in contrast to the low-level `Timeout` class). 87 // 88 // Timers are started and can be stopped or restarted. When a timer expires, 89 // the provided `on_expired` callback will be triggered. A timer is 90 // automatically restarted, as long as the number of restarts is below the 91 // configurable `max_restarts` parameter. The `is_running` property can be 92 // queried to know if it's still running after having expired. 93 // 94 // When a timer is restarted, it will use a configurable `backoff_algorithm` to 95 // possibly adjust the duration of the next expiry. It is also possible to 96 // return a new base duration (which is the duration before it's adjusted by the 97 // backoff algorithm). 98 class Timer { 99 public: 100 // The maximum timer duration - one day. 101 static constexpr DurationMs kMaxTimerDuration = DurationMs(24 * 3600 * 1000); 102 103 // When expired, the timer handler can optionally return a new duration which 104 // will be set as `duration` and used as base duration when the timer is 105 // restarted and as input to the backoff algorithm. 106 using OnExpired = std::function<absl::optional<DurationMs>()>; 107 108 // TimerManager will have pointers to these instances, so they must not move. 109 Timer(const Timer&) = delete; 110 Timer& operator=(const Timer&) = delete; 111 112 ~Timer(); 113 114 // Starts the timer if it's stopped or restarts the timer if it's already 115 // running. The `expiration_count` will be reset. 116 void Start(); 117 118 // Stops the timer. This can also be called when the timer is already stopped. 119 // The `expiration_count` will be reset. 120 void Stop(); 121 122 // Sets the base duration. The actual timer duration may be larger depending 123 // on the backoff algorithm. set_duration(DurationMs duration)124 void set_duration(DurationMs duration) { 125 duration_ = std::min(duration, kMaxTimerDuration); 126 } 127 128 // Retrieves the base duration. The actual timer duration may be larger 129 // depending on the backoff algorithm. duration()130 DurationMs duration() const { return duration_; } 131 132 // Returns the number of times the timer has expired. expiration_count()133 int expiration_count() const { return expiration_count_; } 134 135 // Returns the timer's options. options()136 const TimerOptions& options() const { return options_; } 137 138 // Returns the name of the timer. name()139 absl::string_view name() const { return name_; } 140 141 // Indicates if this timer is currently running. is_running()142 bool is_running() const { return is_running_; } 143 144 private: 145 friend class TimerManager; 146 using UnregisterHandler = std::function<void()>; 147 Timer(TimerID id, 148 absl::string_view name, 149 OnExpired on_expired, 150 UnregisterHandler unregister, 151 std::unique_ptr<Timeout> timeout, 152 const TimerOptions& options); 153 154 // Called by TimerManager. Will trigger the callback and increment 155 // `expiration_count`. The timer will automatically be restarted at the 156 // duration as decided by the backoff algorithm, unless the 157 // `TimerOptions::max_restarts` has been reached and then it will be stopped 158 // and `is_running()` will return false. 159 void Trigger(TimerGeneration generation); 160 161 const TimerID id_; 162 const std::string name_; 163 const TimerOptions options_; 164 const OnExpired on_expired_; 165 const UnregisterHandler unregister_handler_; 166 const std::unique_ptr<Timeout> timeout_; 167 168 DurationMs duration_; 169 170 // Increased on each start, and is matched on Trigger, to avoid races. And by 171 // race, meaning that a timeout - which may be evaluated/expired on a 172 // different thread while this thread has stopped that timer already. Note 173 // that the entire socket is not thread-safe, so `TimerManager::HandleTimeout` 174 // is never executed concurrently with any timer starting/stopping. 175 // 176 // This will wrap around after 4 billion timer restarts, and if it wraps 177 // around, it would just trigger _this_ timer in advance (but it's hard to 178 // restart it 4 billion times within its duration). 179 TimerGeneration generation_ = TimerGeneration(0); 180 bool is_running_ = false; 181 // Incremented each time time has expired and reset when stopped or restarted. 182 int expiration_count_ = 0; 183 }; 184 185 // Creates and manages timers. 186 class TimerManager { 187 public: TimerManager(std::function<std::unique_ptr<Timeout> (webrtc::TaskQueueBase::DelayPrecision)> create_timeout)188 explicit TimerManager( 189 std::function<std::unique_ptr<Timeout>( 190 webrtc::TaskQueueBase::DelayPrecision)> create_timeout) 191 : create_timeout_(std::move(create_timeout)) {} 192 193 // Creates a timer with name `name` that will expire (when started) after 194 // `options.duration` and call `on_expired`. There are more `options` that 195 // affects the behavior. Note that timers are created initially stopped. 196 std::unique_ptr<Timer> CreateTimer(absl::string_view name, 197 Timer::OnExpired on_expired, 198 const TimerOptions& options); 199 200 void HandleTimeout(TimeoutID timeout_id); 201 202 private: 203 const std::function<std::unique_ptr<Timeout>( 204 webrtc::TaskQueueBase::DelayPrecision)> 205 create_timeout_; 206 std::map<TimerID, Timer*> timers_; 207 TimerID next_id_ = TimerID(0); 208 }; 209 210 } // namespace dcsctp 211 212 #endif // NET_DCSCTP_TIMER_TIMER_H_ 213