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:

committed by
WebRTC LUCI CQ

parent
c788feacca
commit
cebbff7f58
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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_));
|
||||
}
|
||||
|
@ -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).
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user