diff --git a/net/dcsctp/public/dcsctp_options.h b/net/dcsctp/public/dcsctp_options.h index a96611589f..6e3c2334a3 100644 --- a/net/dcsctp/public/dcsctp_options.h +++ b/net/dcsctp/public/dcsctp_options.h @@ -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 max_timer_backoff_duration = absl::nullopt; + // Hearbeat interval (on idle connections only). Set to zero to disable. DurationMs heartbeat_interval = DurationMs(30000); diff --git a/net/dcsctp/socket/transmission_control_block.h b/net/dcsctp/socket/transmission_control_block.h index 4510d83365..c3766d1546 100644 --- a/net/dcsctp/socket/transmission_control_block.h +++ b/net/dcsctp/socket/transmission_control_block.h @@ -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, diff --git a/net/dcsctp/timer/timer.cc b/net/dcsctp/timer/timer.cc index 2ad6d74971..b3168e3b11 100644 --- a/net/dcsctp/timer/timer.cc +++ b/net/dcsctp/timer/timer.cc @@ -26,10 +26,10 @@ TimeoutID MakeTimeoutId(TimerID timer_id, TimerGeneration generation) { return TimeoutID(static_cast(*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_)); } diff --git a/net/dcsctp/timer/timer.h b/net/dcsctp/timer/timer.h index ed072de7d5..1d846b67c6 100644 --- a/net/dcsctp/timer/timer.h +++ b/net/dcsctp/timer/timer.h @@ -46,9 +46,16 @@ struct TimerOptions { TimerOptions(DurationMs duration, TimerBackoffAlgorithm backoff_algorithm, absl::optional max_restarts) + : TimerOptions(duration, backoff_algorithm, max_restarts, absl::nullopt) { + } + TimerOptions(DurationMs duration, + TimerBackoffAlgorithm backoff_algorithm, + absl::optional max_restarts, + absl::optional 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 max_restarts; + // The maximum timeout value for exponential backoff. + const absl::optional max_backoff_duration; }; // A high-level timer (in contrast to the low-level `Timeout` class). diff --git a/net/dcsctp/timer/timer_test.cc b/net/dcsctp/timer/timer_test.cc index a403bb6b4b..5550f7fabf 100644 --- a/net/dcsctp/timer/timer_test.cc +++ b/net/dcsctp/timer/timer_test.cc @@ -386,5 +386,42 @@ TEST_F(TimerTest, TimerCanBeStartedFromWithinExpirationHandler) { AdvanceTimeAndRunTimers(DurationMs(1)); } +TEST_F(TimerTest, DurationStaysWithinMaxTimerBackOffDuration) { + std::unique_ptr 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