/* * 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. */ #include "net/dcsctp/timer/timer.h" #include #include #include #include #include #include "absl/memory/memory.h" #include "absl/strings/string_view.h" #include "net/dcsctp/public/timeout.h" #include "rtc_base/checks.h" namespace dcsctp { namespace { using ::webrtc::TimeDelta; TimeoutID MakeTimeoutId(TimerID timer_id, TimerGeneration generation) { return TimeoutID(static_cast(*timer_id) << 32 | *generation); } TimeDelta GetBackoffDuration(const TimerOptions& options, TimeDelta base_duration, int expiration_count) { switch (options.backoff_algorithm) { case TimerBackoffAlgorithm::kFixed: return base_duration; case TimerBackoffAlgorithm::kExponential: { TimeDelta duration = base_duration; while (expiration_count > 0 && duration < Timer::kMaxTimerDuration) { duration = duration * 2; --expiration_count; if (duration > options.max_backoff_duration) { return options.max_backoff_duration; } } return TimeDelta(std::min(duration, Timer::kMaxTimerDuration)); } } } } // namespace constexpr TimeDelta Timer::kMaxTimerDuration; Timer::Timer(TimerID id, absl::string_view name, OnExpired on_expired, UnregisterHandler unregister_handler, std::unique_ptr timeout, const TimerOptions& options) : id_(id), name_(name), options_(options), on_expired_(std::move(on_expired)), unregister_handler_(std::move(unregister_handler)), timeout_(std::move(timeout)), duration_(options.duration) {} Timer::~Timer() { Stop(); unregister_handler_(); } void Timer::Start() { expiration_count_ = 0; if (!is_running()) { is_running_ = true; generation_ = TimerGeneration(*generation_ + 1); timeout_->Start(DurationMs(duration_), MakeTimeoutId(id_, generation_)); } else { // Timer was running - stop and restart it, to make it expire in `duration_` // from now. generation_ = TimerGeneration(*generation_ + 1); timeout_->Restart(DurationMs(duration_), MakeTimeoutId(id_, generation_)); } } void Timer::Stop() { if (is_running()) { timeout_->Stop(); expiration_count_ = 0; is_running_ = false; } } void Timer::Trigger(TimerGeneration generation) { if (is_running_ && generation == generation_) { ++expiration_count_; is_running_ = false; if (!options_.max_restarts.has_value() || expiration_count_ <= *options_.max_restarts) { // The timer should still be running after this triggers. Start a new // timer. Note that it might be very quickly restarted again, if the // `on_expired_` callback returns a new duration. is_running_ = true; TimeDelta duration = GetBackoffDuration(options_, duration_, expiration_count_); generation_ = TimerGeneration(*generation_ + 1); timeout_->Start(DurationMs(duration), MakeTimeoutId(id_, generation_)); } TimeDelta new_duration = on_expired_(); RTC_DCHECK(new_duration != TimeDelta::PlusInfinity()); if (new_duration > TimeDelta::Zero() && new_duration != duration_) { duration_ = new_duration; if (is_running_) { // Restart it with new duration. timeout_->Stop(); TimeDelta duration = GetBackoffDuration(options_, duration_, expiration_count_); generation_ = TimerGeneration(*generation_ + 1); timeout_->Start(DurationMs(duration), MakeTimeoutId(id_, generation_)); } } } } void TimerManager::HandleTimeout(TimeoutID timeout_id) { TimerID timer_id(*timeout_id >> 32); TimerGeneration generation(*timeout_id); auto it = timers_.find(timer_id); if (it != timers_.end()) { it->second->Trigger(generation); } } std::unique_ptr TimerManager::CreateTimer(absl::string_view name, Timer::OnExpired on_expired, const TimerOptions& options) { next_id_ = TimerID(*next_id_ + 1); TimerID id = next_id_; // This would overflow after 4 billion timers created, which in SCTP would be // after 800 million reconnections on a single socket. Ensure this will never // happen. RTC_CHECK_NE(*id, std::numeric_limits::max()); std::unique_ptr timeout = create_timeout_(options.precision); RTC_CHECK(timeout != nullptr); auto timer = absl::WrapUnique(new Timer( id, name, std::move(on_expired), [this, id]() { timers_.erase(id); }, std::move(timeout), options)); timers_[id] = timer.get(); return timer; } } // namespace dcsctp