/// Controls how the CSS baseline RTT is set when entering Conservative Slow Start (CSS). /// /// The baseline determines when CSS is considered spurious: if the RTT drops below it, /// CSS is exited and normal slow start resumes. #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] pubenum HyStartCssBaseline { /// RFC 9406 behavior: baseline = `currentRoundMinRTT` at CSS entry. /// /// Can cause oscillation: values slightly below the entry RTT trigger CSS exit even /// though they would still have triggered CSS entry (and might do so on the next ACK). #[default]
CurrentRoundMinRtt, /// Alternative behavior: baseline = `lastRoundMinRTT + rttThresh`. /// /// CSS is only exited when the RTT drops below the level that would have triggered /// CSS entry, avoiding oscillation between CSS entry and exit.
EntryThreshold,
}
#[derive(Debug)] pubstruct HyStart { /// > While an arriving ACK may newly acknowledge an arbitrary number of bytes, the HyStart++ /// > algorithm limits the number of those bytes applied to increase the cwnd to `L*SMSS` /// > bytes. /// /// <https://datatracker.ietf.org/doc/html/rfc9406#section-4.2-1> /// /// > A paced TCP implementation SHOULD use `L = infinity`. Burst concerns are mitigated by /// > pacing, and this setting allows for optimal cwnd growth on modern networks. /// /// <https://datatracker.ietf.org/doc/html/rfc9406#section-4.3-9>
limit: usize,
css_baseline_mode: HyStartCssBaseline,
last_round_min_rtt: Option<Duration>,
current_round_min_rtt: Option<Duration>,
rtt_sample_count: usize,
window_end: Option<packet::Number>,
css_baseline_min_rtt: Option<Duration>,
css_round_count: usize,
}
/// > HyStart++ measures rounds using sequence numbers, as follows: /// > /// > - Define windowEnd as a sequence number initialized to SND.NXT. /// > - When windowEnd is ACKed, the current round ends and windowEnd is set to SND.NXT. /// > /// > At the start of each round during standard slow start and CSS, initialize the /// > variables used to compute the last round's and current round's minimum RTT: /// > /// > ```pseudo /// > lastRoundMinRTT = currentRoundMinRTT /// > currentRoundMinRTT = infinity /// > rttSampleCount = 0 /// > ``` /// /// <https://datatracker.ietf.org/doc/html/rfc9406#section-4.2-4> /// /// Called immediately when a round ends so that ACKs arriving before the next packet is sent /// are attributed to the new round rather than being in limbo. fn start_next_round(&mutself) { self.window_end = None; self.last_round_min_rtt = self.current_round_min_rtt; self.current_round_min_rtt = None; self.rtt_sample_count = 0;
qdebug!( "HyStart: start_next_round -> started new round with last_min_rtt: {:?}", self.last_round_min_rtt
);
}
/// Sets `window_end` to `sent_pn` to mark when the current round will end. /// /// Called from [`SlowStart::on_packet_sent`]. Only sets `window_end` if it is `None`, /// i.e., the previous round ended and the end marker for the current round hasn't been set yet. fn maybe_set_window_end(&mutself, sent_pn: packet::Number) { ifself.window_end.is_some() { return;
} self.window_end = Some(sent_pn);
qdebug!( "HyStart: maybe_set_window_end -> set window_end to {:?}", self.window_end
);
}
/// Checks if HyStart is in Conservative Slow Start. Is `pub` for use in tests. pubconstfn in_css(&self) -> bool { self.css_baseline_min_rtt.is_some()
}
// The HyStart++ RFC recommends only running HyStart++ in initial slow start. // // > An implementation SHOULD use HyStart++ only for the initial slow start (when the ssthresh // > is at its initial value of arbitrarily high per [RFC5681]) and fall back to using standard // > slow start for the remainder of the connection lifetime. This is acceptable because // > subsequent slow starts will use the discovered ssthresh value to exit slow start and avoid // > the overshoot problem. // // <https://datatracker.ietf.org/doc/html/rfc9406#section-4.3-11> // // We ignore this SHOULD and run HyStart++ every slow start if it is enabled. That is for the // following reasons: // - reduces code complexity in [`ClassicCongestionController::on_packets_acked`] because there // is one less state to distinguish // - the RFC is only stating this as a SHOULD, so we are not in direct conflict with it // - in QUIC we only have non-initial slow start after persistent congestion, so it is quite // rare, as opposed to TCP where it also happens after an RTO timeout (see RFC 5681 3.1) // - after persistent congestion the established `ssthresh` might be still too high, so we could // still overshoot and induce loss before reaching it, thus no harm in still using HyStart++ // to exit based on RTT if necessary // - if we do reach the previously established `ssthresh` we still exit slow start and continue // with congestion avoidance fn on_packets_acked(
&mutself,
rtt_est: &RttEstimate,
largest_acked: packet::Number,
curr_cwnd: usize,
cc_stats: &mut CongestionControlStats,
_now: Instant,
) -> Option<usize> { self.collect_rtt_sample(rtt_est.latest_rtt());
// > For rounds where at least N_RTT_SAMPLE RTT samples have been obtained and // > currentRoundMinRTT and lastRoundMinRTT are valid, check to see if delay increase // > triggers slow start exit. // // <https://datatracker.ietf.org/doc/html/rfc9406#section-4.2-13> if !self.in_css()
&& self.enough_samples()
&& let Some(current) = self.current_round_min_rtt
&& let Some(last) = self.last_round_min_rtt
{ let rtt_thresh = max( Self::MIN_RTT_THRESH,
min(last / Self::MIN_RTT_DIVISOR, Self::MAX_RTT_THRESH),
); if current >= last + rtt_thresh { self.rtt_sample_count = 0; self.css_baseline_min_rtt = Some(matchself.css_baseline_mode {
HyStartCssBaseline::CurrentRoundMinRtt => current,
HyStartCssBaseline::EntryThreshold => last + rtt_thresh,
});
cc_stats.hystart_css_entries += 1;
qdebug!( "HyStart: on_packets_acked -> entered CSS because cur_min={current:?} >= last_min={last:?} + thresh={rtt_thresh:?}"
);
} // > For CSS rounds where at least N_RTT_SAMPLE RTT samples have been obtained, check to see // > if the current round's minRTT drops below baseline (cssBaselineMinRtt) indicating that // > slow start exit was spurious: // > // > ``` // > if (currentRoundMinRTT < cssBaselineMinRtt) // > cssBaselineMinRtt = infinity // > resume slow start including HyStart++ // > ``` // // <https://datatracker.ietf.org/doc/html/rfc9406#section-4.2-20>
} elseifself.enough_samples()
&& let Some(current) = self.current_round_min_rtt
&& let Some(baseline) = self.css_baseline_min_rtt
&& current < baseline
{
qdebug!( "HyStart: on_packets_acked -> exiting CSS after {} rounds because cur_min={:?} < baseline_min={:?}", self.css_round_count, self.current_round_min_rtt, self.css_baseline_min_rtt
);
// Check for end of round. If `window_end` is acked, call `start_next_round` to immediately // begin the next round's RTT tracking. [`SlowStart::on_packet_sent`] will then call // `maybe_set_window_end` to mark when the new round will end. ifself
.window_end
.is_none_or(|window_end| largest_acked < window_end)
{ return None;
}
// If a round ends while in CSS increase the counter and do a check if enough rounds // to exit to congestion avoidance have been completed. self.css_round_count += 1;
cc_stats.hystart_css_rounds_finished += 1; let exit_slow_start = self.css_round_count >= Self::CSS_ROUNDS;
qdebug!( "HyStart: on_packets_acked -> exit={exit_slow_start} because css_rounds={} >= {}", self.css_round_count, Self::CSS_ROUNDS
); if !exit_slow_start { return None;
} // > If CSS_ROUNDS rounds are complete, enter congestion avoidance by setting the ssthresh // > to // > the current cwnd. // // <https://datatracker.ietf.org/doc/html/rfc9406#section-4.2-23>
Some(curr_cwnd)
}
fn calc_cwnd_increase(&self, new_acked: usize, max_datagram_size: usize) -> usize { // > For each arriving ACK in slow start, where N is the number of previously unacknowledged // > bytes acknowledged in the arriving ACK: // > // > Update the cwnd: // > // > `cwnd = cwnd + min(N, L*SMSS)` // // <https://datatracker.ietf.org/doc/html/rfc9406#section-4.2-8> letmut cwnd_increase = min(self.limit.saturating_mul(max_datagram_size), new_acked);
// > For each arriving ACK in CSS, where N is the number of previously unacknowledged // > bytes acknowledged in the arriving ACK: // > // > Update the cwnd: // > // > `cwnd = cwnd + (min(N, L*SMSS) / CSS_GROWTH_DIVISOR)` // // <https://datatracker.ietf.org/doc/html/rfc9406#section-4.2-15> ifself.in_css() {
cwnd_increase /= Self::CSS_GROWTH_DIVISOR;
}
cwnd_increase
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet am 2026-08-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.