dcsctp: Add Timer and TimerManager
Timer is 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 callback will be triggered.
Timers can be configured to do e.g. exponential backoff when they expire
and how many times they should be automatically restarted.
Bug: webrtc:12614
Change-Id: Id5eddd58dd0af62184b10dd1f98e3e886e3f1d50
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/213350
Reviewed-by: Tommi <tommi@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33666}
diff --git a/net/dcsctp/BUILD.gn b/net/dcsctp/BUILD.gn
index 8510f42..ff93f7e 100644
--- a/net/dcsctp/BUILD.gn
+++ b/net/dcsctp/BUILD.gn
@@ -16,6 +16,7 @@
"common:dcsctp_common_unittests",
"packet:dcsctp_packet_unittests",
"public:dcsctp_public_unittests",
+ "timer:dcsctp_timer_unittests",
]
}
}
diff --git a/net/dcsctp/timer/BUILD.gn b/net/dcsctp/timer/BUILD.gn
new file mode 100644
index 0000000..845504e
--- /dev/null
+++ b/net/dcsctp/timer/BUILD.gn
@@ -0,0 +1,41 @@
+# 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 = [
+ "../public:types",
+ "//api:array_view",
+ "//rtc_base",
+ "//rtc_base:checks",
+ "//rtc_base:rtc_base_approved",
+ ]
+ sources = [
+ "fake_timeout.h",
+ "timer.cc",
+ "timer.h",
+ ]
+}
+
+if (rtc_include_tests) {
+ rtc_library("dcsctp_timer_unittests") {
+ testonly = true
+
+ defines = []
+ deps = [
+ ":timer",
+ "//api:array_view",
+ "//rtc_base:checks",
+ "//rtc_base:gunit_helpers",
+ "//rtc_base:rtc_base_approved",
+ "//test:test_support",
+ ]
+ sources = [ "timer_test.cc" ]
+ }
+}
diff --git a/net/dcsctp/timer/fake_timeout.h b/net/dcsctp/timer/fake_timeout.h
new file mode 100644
index 0000000..06e3085
--- /dev/null
+++ b/net/dcsctp/timer/fake_timeout.h
@@ -0,0 +1,94 @@
+/*
+ * 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 <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "net/dcsctp/public/timeout.h"
+
+namespace dcsctp {
+
+// A timeout used in tests.
+class FakeTimeout : public Timeout {
+ public:
+ explicit 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 {
+ timeout_id_ = timeout_id;
+ expiry_ = TimeMs(*get_time_() + *duration_ms);
+ }
+ void Stop() override { expiry_ = InfiniteFuture(); }
+
+ bool EvaluateHasExpired(TimeMs now) {
+ if (now >= expiry_) {
+ expiry_ = InfiniteFuture();
+ return true;
+ }
+ return false;
+ }
+
+ TimeoutID timeout_id() const { return timeout_id_; }
+
+ private:
+ static constexpr TimeMs InfiniteFuture() {
+ return TimeMs(std::numeric_limits<TimeMs::UnderlyingType>::max());
+ }
+
+ const std::function<TimeMs()> get_time_;
+ const std::function<void(FakeTimeout*)> on_delete_;
+
+ TimeoutID timeout_id_ = TimeoutID(0);
+ TimeMs expiry_ = 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<Timeout> CreateTimeout() {
+ auto timer = std::make_unique<FakeTimeout>(
+ get_time_, [this](FakeTimeout* timer) { timers_.erase(timer); });
+ timers_.insert(timer.get());
+ return timer;
+ }
+
+ std::vector<TimeoutID> RunTimers() {
+ TimeMs now = get_time_();
+ std::vector<TimeoutID> expired_timers;
+ for (auto& timer : timers_) {
+ if (timer->EvaluateHasExpired(now)) {
+ expired_timers.push_back(timer->timeout_id());
+ }
+ }
+ return expired_timers;
+ }
+
+ private:
+ const std::function<TimeMs()> get_time_;
+ std::unordered_set<FakeTimeout*> timers_;
+};
+
+} // namespace dcsctp
+
+#endif // NET_DCSCTP_TIMER_FAKE_TIMEOUT_H_
diff --git a/net/dcsctp/timer/timer.cc b/net/dcsctp/timer/timer.cc
new file mode 100644
index 0000000..2376e7a
--- /dev/null
+++ b/net/dcsctp/timer/timer.cc
@@ -0,0 +1,120 @@
+/*
+ * 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 <cstdint>
+#include <memory>
+#include <unordered_map>
+#include <utility>
+
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "net/dcsctp/public/timeout.h"
+
+namespace dcsctp {
+namespace {
+TimeoutID MakeTimeoutId(uint32_t timer_id, uint32_t generation) {
+ return TimeoutID(static_cast<uint64_t>(timer_id) << 32 | generation);
+}
+
+DurationMs GetBackoffDuration(TimerBackoffAlgorithm algorithm,
+ DurationMs base_duration,
+ int expiration_count) {
+ switch (algorithm) {
+ case TimerBackoffAlgorithm::kFixed:
+ return base_duration;
+ case TimerBackoffAlgorithm::kExponential:
+ return DurationMs(*base_duration * (1 << expiration_count));
+ }
+}
+} // namespace
+
+Timer::Timer(uint32_t 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;
+ timeout_->Start(duration_, MakeTimeoutId(id_, ++generation_));
+ } else {
+ // Timer was running - stop and restart it, to make it expire in `duration_`
+ // from now.
+ timeout_->Restart(duration_, MakeTimeoutId(id_, ++generation_));
+ }
+}
+
+void Timer::Stop() {
+ if (is_running()) {
+ timeout_->Stop();
+ expiration_count_ = 0;
+ is_running_ = false;
+ }
+}
+
+void Timer::Trigger(uint32_t generation) {
+ if (is_running_ && generation == generation_) {
+ ++expiration_count_;
+ if (options_.max_restarts >= 0 &&
+ expiration_count_ > options_.max_restarts) {
+ is_running_ = false;
+ }
+
+ absl::optional<DurationMs> new_duration = on_expired_();
+ if (new_duration.has_value()) {
+ duration_ = new_duration.value();
+ }
+
+ if (is_running_) {
+ // Restart it with new duration.
+ DurationMs duration = GetBackoffDuration(options_.backoff_algorithm,
+ duration_, expiration_count_);
+ timeout_->Start(duration, MakeTimeoutId(id_, ++generation_));
+ }
+ }
+}
+
+void TimerManager::HandleTimeout(TimeoutID timeout_id) {
+ uint32_t timer_id = *timeout_id >> 32;
+ uint32_t 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) {
+ uint32_t id = ++next_id_;
+ auto timer = absl::WrapUnique(new Timer(
+ id, name, std::move(on_expired), [this, id]() { timers_.erase(id); },
+ create_timeout_(), options));
+ timers_[id] = timer.get();
+ return timer;
+}
+
+} // namespace dcsctp
diff --git a/net/dcsctp/timer/timer.h b/net/dcsctp/timer/timer.h
new file mode 100644
index 0000000..6b68c98
--- /dev/null
+++ b/net/dcsctp/timer/timer.h
@@ -0,0 +1,167 @@
+/*
+ * 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 <functional>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "net/dcsctp/public/timeout.h"
+
+namespace dcsctp {
+
+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, -1) {}
+ TimerOptions(DurationMs duration,
+ TimerBackoffAlgorithm backoff_algorithm,
+ int max_restarts)
+ : duration(duration),
+ backoff_algorithm(backoff_algorithm),
+ max_restarts(max_restarts) {}
+
+ // 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.
+ const int max_restarts;
+};
+
+// 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:
+ // 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_ = duration; }
+
+ // 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(uint32_t 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(uint32_t generation);
+
+ const uint32_t 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.
+ uint32_t generation_ = 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>()> 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>()> create_timeout_;
+ std::unordered_map<int, Timer*> timers_;
+ uint32_t next_id_ = 0;
+};
+
+} // namespace dcsctp
+
+#endif // NET_DCSCTP_TIMER_TIMER_H_
diff --git a/net/dcsctp/timer/timer_test.cc b/net/dcsctp/timer/timer_test.cc
new file mode 100644
index 0000000..263f535
--- /dev/null
+++ b/net/dcsctp/timer/timer_test.cc
@@ -0,0 +1,314 @@
+/*
+ * 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 "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]() { return timeout_manager_.CreateTimeout(); }) {
+ ON_CALL(on_expired_, Call).WillByDefault(Return(absl::nullopt));
+ }
+
+ void AdvanceTimeAndRunTimers(DurationMs duration) {
+ now_ = TimeMs(*now_ + *duration);
+
+ for (TimeoutID timeout_id : timeout_manager_.RunTimers()) {
+ 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));
+}
+
+} // namespace
+} // namespace dcsctp