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:
parent
10aaa3f1e7
commit
6fa0cfa4dd
@ -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
41
net/dcsctp/timer/BUILD.gn
Normal 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" ]
|
||||
}
|
||||
}
|
||||
94
net/dcsctp/timer/fake_timeout.h
Normal file
94
net/dcsctp/timer/fake_timeout.h
Normal 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
120
net/dcsctp/timer/timer.cc
Normal 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
167
net/dcsctp/timer/timer.h
Normal 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_
|
||||
314
net/dcsctp/timer/timer_test.cc
Normal file
314
net/dcsctp/timer/timer_test.cc
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user