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}
This commit is contained in:
Victor Boivie 2021-03-30 22:54:41 +02:00 committed by Commit Bot
parent 10aaa3f1e7
commit 6fa0cfa4dd
6 changed files with 737 additions and 0 deletions

View File

@ -16,6 +16,7 @@ if (rtc_include_tests) {
"common:dcsctp_common_unittests",
"packet:dcsctp_packet_unittests",
"public:dcsctp_public_unittests",
"timer:dcsctp_timer_unittests",
]
}
}

41
net/dcsctp/timer/BUILD.gn Normal file
View File

@ -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" ]
}
}

View File

@ -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_

120
net/dcsctp/timer/timer.cc Normal file
View File

@ -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

167
net/dcsctp/timer/timer.h Normal file
View File

@ -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_

View File

@ -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