summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/net/dcsctp/timer
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/net/dcsctp/timer')
-rw-r--r--third_party/libwebrtc/net/dcsctp/timer/BUILD.gn74
-rw-r--r--third_party/libwebrtc/net/dcsctp/timer/fake_timeout.h107
-rw-r--r--third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout.cc99
-rw-r--r--third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout.h92
-rw-r--r--third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout_test.cc152
-rw-r--r--third_party/libwebrtc/net/dcsctp/timer/timer.cc156
-rw-r--r--third_party/libwebrtc/net/dcsctp/timer/timer.h212
-rw-r--r--third_party/libwebrtc/net/dcsctp/timer/timer_test.cc459
8 files changed, 1351 insertions, 0 deletions
diff --git a/third_party/libwebrtc/net/dcsctp/timer/BUILD.gn b/third_party/libwebrtc/net/dcsctp/timer/BUILD.gn
new file mode 100644
index 0000000000..d3be1ec872
--- /dev/null
+++ b/third_party/libwebrtc/net/dcsctp/timer/BUILD.gn
@@ -0,0 +1,74 @@
+# 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.
+
+import("../../../webrtc.gni")
+
+rtc_library("timer") {
+ deps = [
+ "../../../api:array_view",
+ "../../../api/task_queue:task_queue",
+ "../../../rtc_base:checks",
+ "../../../rtc_base:strong_alias",
+ "../../../rtc_base/containers:flat_map",
+ "../../../rtc_base/containers:flat_set",
+ "../public:socket",
+ "../public:types",
+ ]
+ sources = [
+ "fake_timeout.h",
+ "timer.cc",
+ "timer.h",
+ ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/memory",
+ "//third_party/abseil-cpp/absl/strings",
+ "//third_party/abseil-cpp/absl/types:optional",
+ ]
+}
+
+rtc_library("task_queue_timeout") {
+ deps = [
+ "../../../api:array_view",
+ "../../../api/task_queue:pending_task_safety_flag",
+ "../../../api/task_queue:task_queue",
+ "../../../api/units:time_delta",
+ "../../../rtc_base:checks",
+ "../../../rtc_base:logging",
+ "../public:socket",
+ "../public:types",
+ ]
+ sources = [
+ "task_queue_timeout.cc",
+ "task_queue_timeout.h",
+ ]
+}
+
+if (rtc_include_tests) {
+ rtc_library("dcsctp_timer_unittests") {
+ testonly = true
+
+ defines = []
+ deps = [
+ ":task_queue_timeout",
+ ":timer",
+ "../../../api:array_view",
+ "../../../api/task_queue:task_queue",
+ "../../../api/task_queue/test:mock_task_queue_base",
+ "../../../rtc_base:checks",
+ "../../../rtc_base:gunit_helpers",
+ "../../../test:test_support",
+ "../../../test/time_controller:time_controller",
+ "../public:socket",
+ ]
+ sources = [
+ "task_queue_timeout_test.cc",
+ "timer_test.cc",
+ ]
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
+ }
+}
diff --git a/third_party/libwebrtc/net/dcsctp/timer/fake_timeout.h b/third_party/libwebrtc/net/dcsctp/timer/fake_timeout.h
new file mode 100644
index 0000000000..74ffe5af29
--- /dev/null
+++ b/third_party/libwebrtc/net/dcsctp/timer/fake_timeout.h
@@ -0,0 +1,107 @@
+/*
+ * 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_FAKE_TIMEOUT_H_
+#define NET_DCSCTP_TIMER_FAKE_TIMEOUT_H_
+
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "api/task_queue/task_queue_base.h"
+#include "net/dcsctp/public/timeout.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/containers/flat_set.h"
+
+namespace dcsctp {
+
+// A timeout used in tests.
+class FakeTimeout : public Timeout {
+ public:
+ FakeTimeout(std::function<TimeMs()> get_time,
+ std::function<void(FakeTimeout*)> on_delete)
+ : get_time_(std::move(get_time)), on_delete_(std::move(on_delete)) {}
+
+ ~FakeTimeout() override { on_delete_(this); }
+
+ void Start(DurationMs duration_ms, TimeoutID timeout_id) override {
+ RTC_DCHECK(expiry_ == TimeMs::InfiniteFuture());
+ timeout_id_ = timeout_id;
+ expiry_ = get_time_() + duration_ms;
+ }
+ void Stop() override {
+ RTC_DCHECK(expiry_ != TimeMs::InfiniteFuture());
+ expiry_ = TimeMs::InfiniteFuture();
+ }
+
+ bool EvaluateHasExpired(TimeMs now) {
+ if (now >= expiry_) {
+ expiry_ = TimeMs::InfiniteFuture();
+ return true;
+ }
+ return false;
+ }
+
+ TimeoutID timeout_id() const { return timeout_id_; }
+
+ private:
+ const std::function<TimeMs()> get_time_;
+ const std::function<void(FakeTimeout*)> on_delete_;
+
+ TimeoutID timeout_id_ = TimeoutID(0);
+ TimeMs expiry_ = TimeMs::InfiniteFuture();
+};
+
+class FakeTimeoutManager {
+ public:
+ // The `get_time` function must return the current time, relative to any
+ // epoch.
+ explicit FakeTimeoutManager(std::function<TimeMs()> get_time)
+ : get_time_(std::move(get_time)) {}
+
+ std::unique_ptr<FakeTimeout> CreateTimeout() {
+ auto timer = std::make_unique<FakeTimeout>(
+ get_time_, [this](FakeTimeout* timer) { timers_.erase(timer); });
+ timers_.insert(timer.get());
+ return timer;
+ }
+ std::unique_ptr<FakeTimeout> CreateTimeout(
+ webrtc::TaskQueueBase::DelayPrecision precision) {
+ // FakeTimeout does not support implement |precision|.
+ return CreateTimeout();
+ }
+
+ // NOTE: This can't return a vector, as calling EvaluateHasExpired requires
+ // calling socket->HandleTimeout directly afterwards, as the owning Timer
+ // still believes it's running, and it needs to be updated to set
+ // Timer::is_running_ to false before you operate on the Timer or Timeout
+ // again.
+ absl::optional<TimeoutID> GetNextExpiredTimeout() {
+ TimeMs now = get_time_();
+ std::vector<TimeoutID> expired_timers;
+ for (auto& timer : timers_) {
+ if (timer->EvaluateHasExpired(now)) {
+ return timer->timeout_id();
+ }
+ }
+ return absl::nullopt;
+ }
+
+ private:
+ const std::function<TimeMs()> get_time_;
+ webrtc::flat_set<FakeTimeout*> timers_;
+};
+
+} // namespace dcsctp
+
+#endif // NET_DCSCTP_TIMER_FAKE_TIMEOUT_H_
diff --git a/third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout.cc b/third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout.cc
new file mode 100644
index 0000000000..6c43640d39
--- /dev/null
+++ b/third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout.cc
@@ -0,0 +1,99 @@
+/*
+ * 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/task_queue_timeout.h"
+
+#include "api/task_queue/pending_task_safety_flag.h"
+#include "api/units/time_delta.h"
+#include "rtc_base/logging.h"
+
+namespace dcsctp {
+
+TaskQueueTimeoutFactory::TaskQueueTimeout::TaskQueueTimeout(
+ TaskQueueTimeoutFactory& parent,
+ webrtc::TaskQueueBase::DelayPrecision precision)
+ : parent_(parent),
+ precision_(precision),
+ pending_task_safety_flag_(webrtc::PendingTaskSafetyFlag::Create()) {}
+
+TaskQueueTimeoutFactory::TaskQueueTimeout::~TaskQueueTimeout() {
+ RTC_DCHECK_RUN_ON(&parent_.thread_checker_);
+ pending_task_safety_flag_->SetNotAlive();
+}
+
+void TaskQueueTimeoutFactory::TaskQueueTimeout::Start(DurationMs duration_ms,
+ TimeoutID timeout_id) {
+ RTC_DCHECK_RUN_ON(&parent_.thread_checker_);
+ RTC_DCHECK(timeout_expiration_ == TimeMs::InfiniteFuture());
+ timeout_expiration_ = parent_.get_time_() + duration_ms;
+ timeout_id_ = timeout_id;
+
+ if (timeout_expiration_ >= posted_task_expiration_) {
+ // There is already a running task, and it's scheduled to expire sooner than
+ // the new expiration time. Don't do anything; The `timeout_expiration_` has
+ // already been updated and if the delayed task _does_ expire and the timer
+ // hasn't been stopped, that will be noticed in the timeout handler, and the
+ // task will be re-scheduled. Most timers are stopped before they expire.
+ return;
+ }
+
+ if (posted_task_expiration_ != TimeMs::InfiniteFuture()) {
+ RTC_DLOG(LS_VERBOSE) << "New timeout duration is less than scheduled - "
+ "ghosting old delayed task.";
+ // There is already a scheduled delayed task, but its expiration time is
+ // further away than the new expiration, so it can't be used. It will be
+ // "killed" by replacing the safety flag. This is not expected to happen
+ // especially often; Mainly when a timer did exponential backoff and
+ // later recovered.
+ pending_task_safety_flag_->SetNotAlive();
+ pending_task_safety_flag_ = webrtc::PendingTaskSafetyFlag::Create();
+ }
+
+ posted_task_expiration_ = timeout_expiration_;
+ parent_.task_queue_.PostDelayedTaskWithPrecision(
+ precision_,
+ webrtc::SafeTask(
+ pending_task_safety_flag_,
+ [timeout_id, this]() {
+ RTC_DLOG(LS_VERBOSE) << "Timout expired: " << timeout_id.value();
+ RTC_DCHECK_RUN_ON(&parent_.thread_checker_);
+ RTC_DCHECK(posted_task_expiration_ != TimeMs::InfiniteFuture());
+ posted_task_expiration_ = TimeMs::InfiniteFuture();
+
+ if (timeout_expiration_ == TimeMs::InfiniteFuture()) {
+ // The timeout was stopped before it expired. Very common.
+ } else {
+ // Note that the timeout might have been restarted, which updated
+ // `timeout_expiration_` but left the scheduled task running. So
+ // if it's not quite time to trigger the timeout yet, schedule a
+ // new delayed task with what's remaining and retry at that point
+ // in time.
+ DurationMs remaining = timeout_expiration_ - parent_.get_time_();
+ timeout_expiration_ = TimeMs::InfiniteFuture();
+ if (*remaining > 0) {
+ Start(remaining, timeout_id_);
+ } else {
+ // It has actually triggered.
+ RTC_DLOG(LS_VERBOSE)
+ << "Timout triggered: " << timeout_id.value();
+ parent_.on_expired_(timeout_id_);
+ }
+ }
+ }),
+ webrtc::TimeDelta::Millis(duration_ms.value()));
+}
+
+void TaskQueueTimeoutFactory::TaskQueueTimeout::Stop() {
+ // As the TaskQueue doesn't support deleting a posted task, just mark the
+ // timeout as not running.
+ RTC_DCHECK_RUN_ON(&parent_.thread_checker_);
+ timeout_expiration_ = TimeMs::InfiniteFuture();
+}
+
+} // namespace dcsctp
diff --git a/third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout.h b/third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout.h
new file mode 100644
index 0000000000..faae14464f
--- /dev/null
+++ b/third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout.h
@@ -0,0 +1,92 @@
+/*
+ * 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_TASK_QUEUE_TIMEOUT_H_
+#define NET_DCSCTP_TIMER_TASK_QUEUE_TIMEOUT_H_
+
+#include <memory>
+#include <utility>
+
+#include "api/task_queue/pending_task_safety_flag.h"
+#include "api/task_queue/task_queue_base.h"
+#include "net/dcsctp/public/timeout.h"
+
+namespace dcsctp {
+
+// The TaskQueueTimeoutFactory creates `Timeout` instances, which schedules
+// itself to be triggered on the provided `task_queue`, which may be a thread,
+// an actual TaskQueue or something else which supports posting a delayed task.
+//
+// Note that each `DcSctpSocket` must have its own `TaskQueueTimeoutFactory`,
+// as the `TimeoutID` are not unique among sockets.
+//
+// This class must outlive any created Timeout that it has created. Note that
+// the `DcSctpSocket` will ensure that all Timeouts are deleted when the socket
+// is destructed, so this means that this class must outlive the `DcSctpSocket`.
+//
+// This class, and the timeouts created it, are not thread safe.
+class TaskQueueTimeoutFactory {
+ public:
+ // The `get_time` function must return the current time, relative to any
+ // epoch. Whenever a timeout expires, the `on_expired` callback will be
+ // triggered, and then the client should provided `timeout_id` to
+ // `DcSctpSocketInterface::HandleTimeout`.
+ TaskQueueTimeoutFactory(webrtc::TaskQueueBase& task_queue,
+ std::function<TimeMs()> get_time,
+ std::function<void(TimeoutID timeout_id)> on_expired)
+ : task_queue_(task_queue),
+ get_time_(std::move(get_time)),
+ on_expired_(std::move(on_expired)) {}
+
+ // Creates an implementation of `Timeout`.
+ std::unique_ptr<Timeout> CreateTimeout(
+ webrtc::TaskQueueBase::DelayPrecision precision =
+ webrtc::TaskQueueBase::DelayPrecision::kLow) {
+ return std::make_unique<TaskQueueTimeout>(*this, precision);
+ }
+
+ private:
+ class TaskQueueTimeout : public Timeout {
+ public:
+ TaskQueueTimeout(TaskQueueTimeoutFactory& parent,
+ webrtc::TaskQueueBase::DelayPrecision precision);
+ ~TaskQueueTimeout();
+
+ void Start(DurationMs duration_ms, TimeoutID timeout_id) override;
+ void Stop() override;
+
+ private:
+ TaskQueueTimeoutFactory& parent_;
+ const webrtc::TaskQueueBase::DelayPrecision precision_;
+ // A safety flag to ensure that posted tasks to the task queue don't
+ // reference these object when they go out of scope. Note that this safety
+ // flag will be re-created if the scheduled-but-not-yet-expired task is not
+ // to be run. This happens when there is a posted delayed task with an
+ // expiration time _further away_ than what is now the expected expiration
+ // time. In this scenario, a new delayed task has to be posted with a
+ // shorter duration and the old task has to be forgotten.
+ rtc::scoped_refptr<webrtc::PendingTaskSafetyFlag> pending_task_safety_flag_;
+ // The time when the posted delayed task is set to expire. Will be set to
+ // the infinite future if there is no such task running.
+ TimeMs posted_task_expiration_ = TimeMs::InfiniteFuture();
+ // The time when the timeout expires. It will be set to the infinite future
+ // if the timeout is not running/not started.
+ TimeMs timeout_expiration_ = TimeMs::InfiniteFuture();
+ // The current timeout ID that will be reported when expired.
+ TimeoutID timeout_id_ = TimeoutID(0);
+ };
+
+ RTC_NO_UNIQUE_ADDRESS webrtc::SequenceChecker thread_checker_;
+ webrtc::TaskQueueBase& task_queue_;
+ const std::function<TimeMs()> get_time_;
+ const std::function<void(TimeoutID)> on_expired_;
+};
+} // namespace dcsctp
+
+#endif // NET_DCSCTP_TIMER_TASK_QUEUE_TIMEOUT_H_
diff --git a/third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout_test.cc b/third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout_test.cc
new file mode 100644
index 0000000000..f360ba7a58
--- /dev/null
+++ b/third_party/libwebrtc/net/dcsctp/timer/task_queue_timeout_test.cc
@@ -0,0 +1,152 @@
+/*
+ * 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/task_queue_timeout.h"
+
+#include <memory>
+
+#include "api/task_queue/task_queue_base.h"
+#include "api/task_queue/test/mock_task_queue_base.h"
+#include "rtc_base/gunit.h"
+#include "test/gmock.h"
+#include "test/time_controller/simulated_time_controller.h"
+
+namespace dcsctp {
+namespace {
+using ::testing::_;
+using ::testing::MockFunction;
+using ::testing::NiceMock;
+
+class TaskQueueTimeoutTest : public testing::Test {
+ protected:
+ TaskQueueTimeoutTest()
+ : time_controller_(webrtc::Timestamp::Millis(1234)),
+ task_queue_(time_controller_.GetMainThread()),
+ factory_(
+ *task_queue_,
+ [this]() {
+ return TimeMs(time_controller_.GetClock()->CurrentTime().ms());
+ },
+ on_expired_.AsStdFunction()) {}
+
+ void AdvanceTime(DurationMs duration) {
+ time_controller_.AdvanceTime(webrtc::TimeDelta::Millis(*duration));
+ }
+
+ MockFunction<void(TimeoutID)> on_expired_;
+ webrtc::GlobalSimulatedTimeController time_controller_;
+
+ rtc::Thread* task_queue_;
+ TaskQueueTimeoutFactory factory_;
+};
+
+TEST_F(TaskQueueTimeoutTest, StartPostsDelayedTask) {
+ std::unique_ptr<Timeout> timeout = factory_.CreateTimeout();
+ timeout->Start(DurationMs(1000), TimeoutID(1));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTime(DurationMs(999));
+
+ EXPECT_CALL(on_expired_, Call(TimeoutID(1)));
+ AdvanceTime(DurationMs(1));
+}
+
+TEST_F(TaskQueueTimeoutTest, StopBeforeExpiringDoesntTrigger) {
+ std::unique_ptr<Timeout> timeout = factory_.CreateTimeout();
+ timeout->Start(DurationMs(1000), TimeoutID(1));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTime(DurationMs(999));
+
+ timeout->Stop();
+
+ AdvanceTime(DurationMs(1));
+ AdvanceTime(DurationMs(1000));
+}
+
+TEST_F(TaskQueueTimeoutTest, RestartPrologingTimeoutDuration) {
+ std::unique_ptr<Timeout> timeout = factory_.CreateTimeout();
+ timeout->Start(DurationMs(1000), TimeoutID(1));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTime(DurationMs(500));
+
+ timeout->Restart(DurationMs(1000), TimeoutID(2));
+
+ AdvanceTime(DurationMs(999));
+
+ EXPECT_CALL(on_expired_, Call(TimeoutID(2)));
+ AdvanceTime(DurationMs(1));
+}
+
+TEST_F(TaskQueueTimeoutTest, RestartWithShorterDurationExpiresWhenExpected) {
+ std::unique_ptr<Timeout> timeout = factory_.CreateTimeout();
+ timeout->Start(DurationMs(1000), TimeoutID(1));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTime(DurationMs(500));
+
+ timeout->Restart(DurationMs(200), TimeoutID(2));
+
+ AdvanceTime(DurationMs(199));
+
+ EXPECT_CALL(on_expired_, Call(TimeoutID(2)));
+ AdvanceTime(DurationMs(1));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTime(DurationMs(1000));
+}
+
+TEST_F(TaskQueueTimeoutTest, KilledBeforeExpired) {
+ std::unique_ptr<Timeout> timeout = factory_.CreateTimeout();
+ timeout->Start(DurationMs(1000), TimeoutID(1));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTime(DurationMs(500));
+
+ timeout = nullptr;
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTime(DurationMs(1000));
+}
+
+TEST(TaskQueueTimeoutWithMockTaskQueueTest, CanSetTimeoutPrecisionToLow) {
+ NiceMock<webrtc::MockTaskQueueBase> mock_task_queue;
+ EXPECT_CALL(mock_task_queue, PostDelayedTask(_, _));
+ TaskQueueTimeoutFactory factory(
+ mock_task_queue, []() { return TimeMs(1337); },
+ [](TimeoutID timeout_id) {});
+ std::unique_ptr<Timeout> timeout =
+ factory.CreateTimeout(webrtc::TaskQueueBase::DelayPrecision::kLow);
+ timeout->Start(DurationMs(1), TimeoutID(1));
+}
+
+TEST(TaskQueueTimeoutWithMockTaskQueueTest, CanSetTimeoutPrecisionToHigh) {
+ NiceMock<webrtc::MockTaskQueueBase> mock_task_queue;
+ EXPECT_CALL(mock_task_queue, PostDelayedHighPrecisionTask(_, _));
+ TaskQueueTimeoutFactory factory(
+ mock_task_queue, []() { return TimeMs(1337); },
+ [](TimeoutID timeout_id) {});
+ std::unique_ptr<Timeout> timeout =
+ factory.CreateTimeout(webrtc::TaskQueueBase::DelayPrecision::kHigh);
+ timeout->Start(DurationMs(1), TimeoutID(1));
+}
+
+TEST(TaskQueueTimeoutWithMockTaskQueueTest, TimeoutPrecisionIsLowByDefault) {
+ NiceMock<webrtc::MockTaskQueueBase> mock_task_queue;
+ EXPECT_CALL(mock_task_queue, PostDelayedTask(_, _));
+ TaskQueueTimeoutFactory factory(
+ mock_task_queue, []() { return TimeMs(1337); },
+ [](TimeoutID timeout_id) {});
+ std::unique_ptr<Timeout> timeout = factory.CreateTimeout();
+ timeout->Start(DurationMs(1), TimeoutID(1));
+}
+
+} // namespace
+} // namespace dcsctp
diff --git a/third_party/libwebrtc/net/dcsctp/timer/timer.cc b/third_party/libwebrtc/net/dcsctp/timer/timer.cc
new file mode 100644
index 0000000000..bde07638a5
--- /dev/null
+++ b/third_party/libwebrtc/net/dcsctp/timer/timer.cc
@@ -0,0 +1,156 @@
+/*
+ * 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 {
+TimeoutID MakeTimeoutId(TimerID timer_id, TimerGeneration generation) {
+ return TimeoutID(static_cast<uint64_t>(*timer_id) << 32 | *generation);
+}
+
+DurationMs GetBackoffDuration(const TimerOptions& options,
+ DurationMs base_duration,
+ int expiration_count) {
+ switch (options.backoff_algorithm) {
+ case TimerBackoffAlgorithm::kFixed:
+ return base_duration;
+ case TimerBackoffAlgorithm::kExponential: {
+ int32_t duration_ms = *base_duration;
+
+ while (expiration_count > 0 && duration_ms < *Timer::kMaxTimerDuration) {
+ duration_ms *= 2;
+ --expiration_count;
+
+ if (options.max_backoff_duration.has_value() &&
+ duration_ms > **options.max_backoff_duration) {
+ return *options.max_backoff_duration;
+ }
+ }
+
+ return DurationMs(std::min(duration_ms, *Timer::kMaxTimerDuration));
+ }
+ }
+}
+} // namespace
+
+constexpr DurationMs 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(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(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;
+ DurationMs duration =
+ GetBackoffDuration(options_, duration_, expiration_count_);
+ generation_ = TimerGeneration(*generation_ + 1);
+ timeout_->Start(duration, MakeTimeoutId(id_, generation_));
+ }
+
+ absl::optional<DurationMs> new_duration = on_expired_();
+ if (new_duration.has_value() && new_duration != duration_) {
+ duration_ = new_duration.value();
+ if (is_running_) {
+ // Restart it with new duration.
+ timeout_->Stop();
+
+ DurationMs duration =
+ GetBackoffDuration(options_, duration_, expiration_count_);
+ generation_ = TimerGeneration(*generation_ + 1);
+ timeout_->Start(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
diff --git a/third_party/libwebrtc/net/dcsctp/timer/timer.h b/third_party/libwebrtc/net/dcsctp/timer/timer.h
new file mode 100644
index 0000000000..31b496dc81
--- /dev/null
+++ b/third_party/libwebrtc/net/dcsctp/timer/timer.h
@@ -0,0 +1,212 @@
+/*
+ * 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_
diff --git a/third_party/libwebrtc/net/dcsctp/timer/timer_test.cc b/third_party/libwebrtc/net/dcsctp/timer/timer_test.cc
new file mode 100644
index 0000000000..4aebe65b48
--- /dev/null
+++ b/third_party/libwebrtc/net/dcsctp/timer/timer_test.cc
@@ -0,0 +1,459 @@
+/*
+ * 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 <memory>
+
+#include "absl/types/optional.h"
+#include "api/task_queue/task_queue_base.h"
+#include "net/dcsctp/public/timeout.h"
+#include "net/dcsctp/timer/fake_timeout.h"
+#include "rtc_base/gunit.h"
+#include "test/gmock.h"
+
+namespace dcsctp {
+namespace {
+using ::testing::Return;
+
+class TimerTest : public testing::Test {
+ protected:
+ TimerTest()
+ : timeout_manager_([this]() { return now_; }),
+ manager_([this](webrtc::TaskQueueBase::DelayPrecision precision) {
+ return timeout_manager_.CreateTimeout(precision);
+ }) {
+ ON_CALL(on_expired_, Call).WillByDefault(Return(absl::nullopt));
+ }
+
+ void AdvanceTimeAndRunTimers(DurationMs duration) {
+ now_ = now_ + duration;
+
+ for (;;) {
+ absl::optional<TimeoutID> timeout_id =
+ timeout_manager_.GetNextExpiredTimeout();
+ if (!timeout_id.has_value()) {
+ break;
+ }
+ manager_.HandleTimeout(*timeout_id);
+ }
+ }
+
+ TimeMs now_ = TimeMs(0);
+ FakeTimeoutManager timeout_manager_;
+ TimerManager manager_;
+ testing::MockFunction<absl::optional<DurationMs>()> on_expired_;
+};
+
+TEST_F(TimerTest, TimerIsInitiallyStopped) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(5000), TimerBackoffAlgorithm::kFixed));
+
+ EXPECT_FALSE(t1->is_running());
+}
+
+TEST_F(TimerTest, TimerExpiresAtGivenTime) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(5000), TimerBackoffAlgorithm::kFixed));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ t1->Start();
+ EXPECT_TRUE(t1->is_running());
+
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+}
+
+TEST_F(TimerTest, TimerReschedulesAfterExpiredWithFixedBackoff) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(5000), TimerBackoffAlgorithm::kFixed));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ t1->Start();
+ EXPECT_EQ(t1->expiration_count(), 0);
+
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+
+ // Fire first time
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_TRUE(t1->is_running());
+ EXPECT_EQ(t1->expiration_count(), 1);
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+
+ // Second time
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_TRUE(t1->is_running());
+ EXPECT_EQ(t1->expiration_count(), 2);
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+
+ // Third time
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_TRUE(t1->is_running());
+ EXPECT_EQ(t1->expiration_count(), 3);
+}
+
+TEST_F(TimerTest, TimerWithNoRestarts) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(5000), TimerBackoffAlgorithm::kFixed,
+ /*max_restart=*/0));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ t1->Start();
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+
+ // Fire first time
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+
+ EXPECT_FALSE(t1->is_running());
+
+ // Second time - shouldn't fire
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(5000));
+ EXPECT_FALSE(t1->is_running());
+}
+
+TEST_F(TimerTest, TimerWithOneRestart) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(5000), TimerBackoffAlgorithm::kFixed,
+ /*max_restart=*/1));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ t1->Start();
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+
+ // Fire first time
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_TRUE(t1->is_running());
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+
+ // Second time - max restart limit reached.
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_FALSE(t1->is_running());
+
+ // Third time - should not fire.
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(5000));
+ EXPECT_FALSE(t1->is_running());
+}
+
+TEST_F(TimerTest, TimerWithTwoRestart) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(5000), TimerBackoffAlgorithm::kFixed,
+ /*max_restart=*/2));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ t1->Start();
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+
+ // Fire first time
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_TRUE(t1->is_running());
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+
+ // Second time
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_TRUE(t1->is_running());
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+
+ // Third time
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_FALSE(t1->is_running());
+}
+
+TEST_F(TimerTest, TimerWithExponentialBackoff) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(5000), TimerBackoffAlgorithm::kExponential));
+
+ t1->Start();
+
+ // Fire first time at 5 seconds
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(5000));
+
+ // Second time at 5*2^1 = 10 seconds later.
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(9000));
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+
+ // Third time at 5*2^2 = 20 seconds later.
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(19000));
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+
+ // Fourth time at 5*2^3 = 40 seconds later.
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(39000));
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+}
+
+TEST_F(TimerTest, StartTimerWillStopAndStart) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(5000), TimerBackoffAlgorithm::kExponential));
+
+ t1->Start();
+
+ AdvanceTimeAndRunTimers(DurationMs(3000));
+
+ t1->Start();
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(2000));
+
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(3000));
+}
+
+TEST_F(TimerTest, ExpirationCounterWillResetIfStopped) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(5000), TimerBackoffAlgorithm::kExponential));
+
+ t1->Start();
+
+ // Fire first time at 5 seconds
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(5000));
+ EXPECT_EQ(t1->expiration_count(), 1);
+
+ // Second time at 5*2^1 = 10 seconds later.
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(9000));
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_EQ(t1->expiration_count(), 2);
+
+ t1->Start();
+ EXPECT_EQ(t1->expiration_count(), 0);
+
+ // Third time at 5*2^0 = 5 seconds later.
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_EQ(t1->expiration_count(), 1);
+}
+
+TEST_F(TimerTest, StopTimerWillMakeItNotExpire) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(5000), TimerBackoffAlgorithm::kExponential));
+
+ t1->Start();
+ EXPECT_TRUE(t1->is_running());
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+ t1->Stop();
+ EXPECT_FALSE(t1->is_running());
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+}
+
+TEST_F(TimerTest, ReturningNewDurationWhenExpired) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(5000), TimerBackoffAlgorithm::kFixed));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ t1->Start();
+ EXPECT_EQ(t1->duration(), DurationMs(5000));
+
+ AdvanceTimeAndRunTimers(DurationMs(4000));
+
+ // Fire first time
+ EXPECT_CALL(on_expired_, Call).WillOnce(Return(DurationMs(2000)));
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_EQ(t1->duration(), DurationMs(2000));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+
+ // Second time
+ EXPECT_CALL(on_expired_, Call).WillOnce(Return(DurationMs(10000)));
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+ EXPECT_EQ(t1->duration(), DurationMs(10000));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(9000));
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+}
+
+TEST_F(TimerTest, TimersHaveMaximumDuration) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(1000), TimerBackoffAlgorithm::kExponential));
+
+ t1->set_duration(DurationMs(2 * *Timer::kMaxTimerDuration));
+ EXPECT_EQ(t1->duration(), Timer::kMaxTimerDuration);
+}
+
+TEST_F(TimerTest, TimersHaveMaximumBackoffDuration) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(1000), TimerBackoffAlgorithm::kExponential));
+
+ t1->Start();
+
+ int max_exponent = static_cast<int>(log2(*Timer::kMaxTimerDuration / 1000));
+ for (int i = 0; i < max_exponent; ++i) {
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000 * (1 << i)));
+ }
+
+ // Reached the maximum duration.
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(Timer::kMaxTimerDuration);
+
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(Timer::kMaxTimerDuration);
+
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(Timer::kMaxTimerDuration);
+
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(Timer::kMaxTimerDuration);
+}
+
+TEST_F(TimerTest, TimerCanBeStartedFromWithinExpirationHandler) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(1000), TimerBackoffAlgorithm::kFixed));
+
+ t1->Start();
+
+ // Start a timer, but don't return any new duration in callback.
+ EXPECT_CALL(on_expired_, Call).WillOnce([&]() {
+ EXPECT_TRUE(t1->is_running());
+ t1->set_duration(DurationMs(5000));
+ t1->Start();
+ return absl::nullopt;
+ });
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(4999));
+
+ // Start a timer, and return any new duration in callback.
+ EXPECT_CALL(on_expired_, Call).WillOnce([&]() {
+ EXPECT_TRUE(t1->is_running());
+ t1->set_duration(DurationMs(5000));
+ t1->Start();
+ return absl::make_optional(DurationMs(8000));
+ });
+ AdvanceTimeAndRunTimers(DurationMs(1));
+
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(7999));
+
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1));
+}
+
+TEST_F(TimerTest, DurationStaysWithinMaxTimerBackOffDuration) {
+ std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+ "t1", on_expired_.AsStdFunction(),
+ TimerOptions(DurationMs(1000), TimerBackoffAlgorithm::kExponential,
+ /*max_restarts=*/absl::nullopt, DurationMs(5000)));
+
+ t1->Start();
+
+ // Initial timeout, 1000 ms
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1000));
+
+ // Exponential backoff -> 2000 ms
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(1999));
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1));
+
+ // Exponential backoff -> 4000 ms
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(3999));
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1));
+
+ // Limited backoff -> 5000ms
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(4999));
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1));
+
+ // ... where it plateaus
+ EXPECT_CALL(on_expired_, Call).Times(0);
+ AdvanceTimeAndRunTimers(DurationMs(4999));
+ EXPECT_CALL(on_expired_, Call).Times(1);
+ AdvanceTimeAndRunTimers(DurationMs(1));
+}
+
+TEST(TimerManagerTest, TimerManagerPassesPrecisionToCreateTimeoutMethod) {
+ FakeTimeoutManager timeout_manager([&]() { return TimeMs(0); });
+ absl::optional<webrtc::TaskQueueBase::DelayPrecision> create_timer_precison;
+ TimerManager manager([&](webrtc::TaskQueueBase::DelayPrecision precision) {
+ create_timer_precison = precision;
+ return timeout_manager.CreateTimeout(precision);
+ });
+ // Default TimerOptions.
+ manager.CreateTimer(
+ "test_timer", []() { return absl::optional<DurationMs>(); },
+ TimerOptions(DurationMs(123)));
+ EXPECT_EQ(create_timer_precison, webrtc::TaskQueueBase::DelayPrecision::kLow);
+ // High precision TimerOptions.
+ manager.CreateTimer(
+ "test_timer", []() { return absl::optional<DurationMs>(); },
+ TimerOptions(DurationMs(123), TimerBackoffAlgorithm::kExponential,
+ absl::nullopt, absl::nullopt,
+ webrtc::TaskQueueBase::DelayPrecision::kHigh));
+ EXPECT_EQ(create_timer_precison,
+ webrtc::TaskQueueBase::DelayPrecision::kHigh);
+ // Low precision TimerOptions.
+ manager.CreateTimer(
+ "test_timer", []() { return absl::optional<DurationMs>(); },
+ TimerOptions(DurationMs(123), TimerBackoffAlgorithm::kExponential,
+ absl::nullopt, absl::nullopt,
+ webrtc::TaskQueueBase::DelayPrecision::kLow));
+ EXPECT_EQ(create_timer_precison, webrtc::TaskQueueBase::DelayPrecision::kLow);
+}
+
+} // namespace
+} // namespace dcsctp