blob: 07c9f3d7868fe2be6e8cae99c38bc96d5e78ab36 [file] [log] [blame]
/*
* 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 <algorithm>
#include <cstdint>
#include <limits>
#include <memory>
#include <utility>
#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<uint64_t>(*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> 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<Timer> 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<uint32_t>::max());
std::unique_ptr<Timeout> 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