dcsctp: Specify the max timer backoff duration

By allowing the max timer backoff duration to be limited, a socket can
fast recover in case of intermittent network issues. Before this CL, the
exponential backoff algorithm could result in very long retry durations
(in the order of minutes), when connection has been lost or been flaky
for a long while.

Note that limiting the maximum backoff duration might require
compensating the maximum retransmission limit to avoid closing the
socket prematurely due to reaching the maximum retransmission limit much
faster than previously.

Bug: webrtc:13129
Change-Id: Ib94030d666433e3fa1a2c8ef69750a1afab8ef94
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/230702
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34913}
This commit is contained in:
Victor Boivie
2021-08-30 15:21:01 +02:00
committed by WebRTC LUCI CQ
parent c788feacca
commit cebbff7f58
5 changed files with 70 additions and 8 deletions

View File

@ -113,6 +113,14 @@ struct DcSctpOptions {
// T2-shutdown timeout.
DurationMs t2_shutdown_timeout = DurationMs(1000);
// For t1-init, t1-cookie, t2-shutdown, t3-rtx, this value - if set - will be
// the upper bound on how large the exponentially backed off timeout can
// become. The lower the duration, the faster the connection can recover on
// transient network issues. Setting this value may require changing
// `max_retransmissions` and `max_init_retransmits` to ensure that the
// connection is not closed too quickly.
absl::optional<DurationMs> max_timer_backoff_duration = absl::nullopt;
// Hearbeat interval (on idle connections only). Set to zero to disable.
DurationMs heartbeat_interval = DurationMs(30000);

View File

@ -66,7 +66,10 @@ class TransmissionControlBlock : public Context {
t3_rtx_(timer_manager_.CreateTimer(
"t3-rtx",
absl::bind_front(&TransmissionControlBlock::OnRtxTimerExpiry, this),
TimerOptions(options.rto_initial))),
TimerOptions(options.rto_initial,
TimerBackoffAlgorithm::kExponential,
/*max_restarts=*/absl::nullopt,
options.max_timer_backoff_duration))),
delayed_ack_timer_(timer_manager_.CreateTimer(
"delayed-ack",
absl::bind_front(&TransmissionControlBlock::OnDelayedAckTimerExpiry,

View File

@ -26,10 +26,10 @@ TimeoutID MakeTimeoutId(TimerID timer_id, TimerGeneration generation) {
return TimeoutID(static_cast<uint64_t>(*timer_id) << 32 | *generation);
}
DurationMs GetBackoffDuration(TimerBackoffAlgorithm algorithm,
DurationMs GetBackoffDuration(const TimerOptions& options,
DurationMs base_duration,
int expiration_count) {
switch (algorithm) {
switch (options.backoff_algorithm) {
case TimerBackoffAlgorithm::kFixed:
return base_duration;
case TimerBackoffAlgorithm::kExponential: {
@ -38,6 +38,11 @@ DurationMs GetBackoffDuration(TimerBackoffAlgorithm algorithm,
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));
@ -99,8 +104,8 @@ void Timer::Trigger(TimerGeneration generation) {
// 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_.backoff_algorithm,
duration_, expiration_count_);
DurationMs duration =
GetBackoffDuration(options_, duration_, expiration_count_);
generation_ = TimerGeneration(*generation_ + 1);
timeout_->Start(duration, MakeTimeoutId(id_, generation_));
}
@ -112,8 +117,8 @@ void Timer::Trigger(TimerGeneration generation) {
// Restart it with new duration.
timeout_->Stop();
DurationMs duration = GetBackoffDuration(options_.backoff_algorithm,
duration_, expiration_count_);
DurationMs duration =
GetBackoffDuration(options_, duration_, expiration_count_);
generation_ = TimerGeneration(*generation_ + 1);
timeout_->Start(duration, MakeTimeoutId(id_, generation_));
}

View File

@ -46,9 +46,16 @@ struct TimerOptions {
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)
: duration(duration),
backoff_algorithm(backoff_algorithm),
max_restarts(max_restarts) {}
max_restarts(max_restarts),
max_backoff_duration(max_backoff_duration) {}
// The initial timer duration. Can be overridden with `set_duration`.
const DurationMs duration;
@ -58,6 +65,8 @@ struct TimerOptions {
// 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;
};
// A high-level timer (in contrast to the low-level `Timeout` class).

View File

@ -386,5 +386,42 @@ TEST_F(TimerTest, TimerCanBeStartedFromWithinExpirationHandler) {
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));
}
} // namespace
} // namespace dcsctp