| /* | |
| * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #ifndef NET_DCSCTP_TIMER_TIMER_H_ | |
| #define NET_DCSCTP_TIMER_TIMER_H_ | |
| #include <stdint.h> | |
| #include <algorithm> | |
| #include <functional> | |
| #include <map> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/strings/string_view.h" | |
| #include "absl/types/optional.h" | |
| #include "api/task_queue/task_queue_base.h" | |
| #include "net/dcsctp/public/timeout.h" | |
| #include "rtc_base/strong_alias.h" | |
| namespace dcsctp { | |
| using TimerID = webrtc::StrongAlias<class TimerIDTag, uint32_t>; | |
| using TimerGeneration = webrtc::StrongAlias<class TimerGenerationTag, uint32_t>; | |
| enum class TimerBackoffAlgorithm { | |
| // The base duration will be used for any restart. | |
| kFixed, | |
| // An exponential backoff is used for restarts, with a 2x multiplier, meaning | |
| // that every restart will use a duration that is twice as long as the | |
| // previous. | |
| kExponential, | |
| }; | |
| struct TimerOptions { | |
| explicit TimerOptions(DurationMs duration) | |
| : TimerOptions(duration, TimerBackoffAlgorithm::kExponential) {} | |
| TimerOptions(DurationMs duration, TimerBackoffAlgorithm backoff_algorithm) | |
| : TimerOptions(duration, backoff_algorithm, absl::nullopt) {} | |
| TimerOptions(DurationMs duration, | |
| TimerBackoffAlgorithm backoff_algorithm, | |
| absl::optional<int> max_restarts) | |
| : TimerOptions(duration, backoff_algorithm, max_restarts, absl::nullopt) { | |
| } | |
| TimerOptions(DurationMs duration, | |
| TimerBackoffAlgorithm backoff_algorithm, | |
| absl::optional<int> max_restarts, | |
| absl::optional<DurationMs> max_backoff_duration) | |
| : TimerOptions(duration, | |
| backoff_algorithm, | |
| max_restarts, | |
| max_backoff_duration, | |
| webrtc::TaskQueueBase::DelayPrecision::kLow) {} | |
| TimerOptions(DurationMs duration, | |
| TimerBackoffAlgorithm backoff_algorithm, | |
| absl::optional<int> max_restarts, | |
| absl::optional<DurationMs> max_backoff_duration, | |
| webrtc::TaskQueueBase::DelayPrecision precision) | |
| : duration(duration), | |
| backoff_algorithm(backoff_algorithm), | |
| max_restarts(max_restarts), | |
| max_backoff_duration(max_backoff_duration), | |
| precision(precision) {} | |
| // The initial timer duration. Can be overridden with `set_duration`. | |
| const DurationMs duration; | |
| // If the duration should be increased (using exponential backoff) when it is | |
| // restarted. If not set, the same duration will be used. | |
| const TimerBackoffAlgorithm backoff_algorithm; | |
| // The maximum number of times that the timer will be automatically restarted, | |
| // or absl::nullopt if there is no limit. | |
| const absl::optional<int> max_restarts; | |
| // The maximum timeout value for exponential backoff. | |
| const absl::optional<DurationMs> max_backoff_duration; | |
| // The precision of the webrtc::TaskQueueBase used for scheduling. | |
| const webrtc::TaskQueueBase::DelayPrecision precision; | |
| }; | |
| // A high-level timer (in contrast to the low-level `Timeout` class). | |
| // | |
| // Timers are started and can be stopped or restarted. When a timer expires, | |
| // the provided `on_expired` callback will be triggered. A timer is | |
| // automatically restarted, as long as the number of restarts is below the | |
| // configurable `max_restarts` parameter. The `is_running` property can be | |
| // queried to know if it's still running after having expired. | |
| // | |
| // When a timer is restarted, it will use a configurable `backoff_algorithm` to | |
| // possibly adjust the duration of the next expiry. It is also possible to | |
| // return a new base duration (which is the duration before it's adjusted by the | |
| // backoff algorithm). | |
| class Timer { | |
| public: | |
| // The maximum timer duration - one day. | |
| static constexpr DurationMs kMaxTimerDuration = DurationMs(24 * 3600 * 1000); | |
| // When expired, the timer handler can optionally return a new duration which | |
| // will be set as `duration` and used as base duration when the timer is | |
| // restarted and as input to the backoff algorithm. | |
| using OnExpired = std::function<absl::optional<DurationMs>()>; | |
| // TimerManager will have pointers to these instances, so they must not move. | |
| Timer(const Timer&) = delete; | |
| Timer& operator=(const Timer&) = delete; | |
| ~Timer(); | |
| // Starts the timer if it's stopped or restarts the timer if it's already | |
| // running. The `expiration_count` will be reset. | |
| void Start(); | |
| // Stops the timer. This can also be called when the timer is already stopped. | |
| // The `expiration_count` will be reset. | |
| void Stop(); | |
| // Sets the base duration. The actual timer duration may be larger depending | |
| // on the backoff algorithm. | |
| void set_duration(DurationMs duration) { | |
| duration_ = std::min(duration, kMaxTimerDuration); | |
| } | |
| // Retrieves the base duration. The actual timer duration may be larger | |
| // depending on the backoff algorithm. | |
| DurationMs duration() const { return duration_; } | |
| // Returns the number of times the timer has expired. | |
| int expiration_count() const { return expiration_count_; } | |
| // Returns the timer's options. | |
| const TimerOptions& options() const { return options_; } | |
| // Returns the name of the timer. | |
| absl::string_view name() const { return name_; } | |
| // Indicates if this timer is currently running. | |
| bool is_running() const { return is_running_; } | |
| private: | |
| friend class TimerManager; | |
| using UnregisterHandler = std::function<void()>; | |
| Timer(TimerID id, | |
| absl::string_view name, | |
| OnExpired on_expired, | |
| UnregisterHandler unregister, | |
| std::unique_ptr<Timeout> timeout, | |
| const TimerOptions& options); | |
| // Called by TimerManager. Will trigger the callback and increment | |
| // `expiration_count`. The timer will automatically be restarted at the | |
| // duration as decided by the backoff algorithm, unless the | |
| // `TimerOptions::max_restarts` has been reached and then it will be stopped | |
| // and `is_running()` will return false. | |
| void Trigger(TimerGeneration generation); | |
| const TimerID id_; | |
| const std::string name_; | |
| const TimerOptions options_; | |
| const OnExpired on_expired_; | |
| const UnregisterHandler unregister_handler_; | |
| const std::unique_ptr<Timeout> timeout_; | |
| DurationMs duration_; | |
| // Increased on each start, and is matched on Trigger, to avoid races. And by | |
| // race, meaning that a timeout - which may be evaluated/expired on a | |
| // different thread while this thread has stopped that timer already. Note | |
| // that the entire socket is not thread-safe, so `TimerManager::HandleTimeout` | |
| // is never executed concurrently with any timer starting/stopping. | |
| // | |
| // This will wrap around after 4 billion timer restarts, and if it wraps | |
| // around, it would just trigger _this_ timer in advance (but it's hard to | |
| // restart it 4 billion times within its duration). | |
| TimerGeneration generation_ = TimerGeneration(0); | |
| bool is_running_ = false; | |
| // Incremented each time time has expired and reset when stopped or restarted. | |
| int expiration_count_ = 0; | |
| }; | |
| // Creates and manages timers. | |
| class TimerManager { | |
| public: | |
| explicit TimerManager( | |
| std::function<std::unique_ptr<Timeout>( | |
| webrtc::TaskQueueBase::DelayPrecision)> create_timeout) | |
| : create_timeout_(std::move(create_timeout)) {} | |
| // Creates a timer with name `name` that will expire (when started) after | |
| // `options.duration` and call `on_expired`. There are more `options` that | |
| // affects the behavior. Note that timers are created initially stopped. | |
| std::unique_ptr<Timer> CreateTimer(absl::string_view name, | |
| Timer::OnExpired on_expired, | |
| const TimerOptions& options); | |
| void HandleTimeout(TimeoutID timeout_id); | |
| private: | |
| const std::function<std::unique_ptr<Timeout>( | |
| webrtc::TaskQueueBase::DelayPrecision)> | |
| create_timeout_; | |
| std::map<TimerID, Timer*> timers_; | |
| TimerID next_id_ = TimerID(0); | |
| }; | |
| } // namespace dcsctp | |
| #endif // NET_DCSCTP_TIMER_TIMER_H_ |