• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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