quiche/recovery/congestion/bbr2/
per_ack.rsuse super::*;
use crate::rand;
use crate::recovery;
const PACING_RATE_1_2MBPS: u64 = 1200 * 1000 / 8;
#[inline]
fn bbr2_min_pipe_cwnd(r: &mut Congestion) -> usize {
MIN_PIPE_CWND_PKTS * r.max_datagram_size
}
pub fn bbr2_update_model_and_state(
r: &mut Congestion, packet: &Acked, in_flight: usize, now: Instant,
) {
per_loss::bbr2_update_latest_delivery_signals(r);
per_loss::bbr2_update_congestion_signals(r, packet);
bbr2_update_ack_aggregation(r, packet, now);
bbr2_check_startup_done(r);
bbr2_check_drain(r, in_flight, now);
bbr2_update_probe_bw_cycle_phase(r, in_flight, now);
bbr2_update_min_rtt(r, now);
bbr2_check_probe_rtt(r, in_flight, now);
per_loss::bbr2_advance_latest_delivery_signals(r);
per_loss::bbr2_bound_bw_for_model(r);
}
pub fn bbr2_update_control_parameters(
r: &mut Congestion, in_flight: usize, now: Instant,
) {
pacing::bbr2_set_pacing_rate(r);
bbr2_set_send_quantum(r);
r.set_pacing_rate(r.bbr2_state.pacing_rate, now);
bbr2_set_cwnd(r, in_flight);
}
fn bbr2_check_startup_done(r: &mut Congestion) {
bbr2_check_startup_full_bandwidth(r);
bbr2_check_startup_high_loss(r);
if r.bbr2_state.state == BBR2StateMachine::Startup && r.bbr2_state.filled_pipe
{
bbr2_enter_drain(r);
}
}
fn bbr2_check_startup_full_bandwidth(r: &mut Congestion) {
if r.bbr2_state.filled_pipe ||
!r.bbr2_state.round_start ||
r.delivery_rate.sample_is_app_limited()
{
return;
}
if r.bbr2_state.max_bw >=
(r.bbr2_state.full_bw as f64 * MAX_BW_GROWTH_THRESHOLD) as u64
{
r.bbr2_state.full_bw = r.bbr2_state.max_bw;
r.bbr2_state.full_bw_count = 0;
return;
}
r.bbr2_state.full_bw_count += 1;
if r.bbr2_state.full_bw_count >= MAX_BW_COUNT {
r.bbr2_state.filled_pipe = true;
}
}
fn bbr2_check_startup_high_loss(r: &mut Congestion) {
if r.bbr2_state.loss_round_start &&
r.bbr2_state.in_recovery &&
r.bbr2_state.loss_events_in_round >= FULL_LOSS_COUNT as usize &&
per_loss::bbr2_is_inflight_too_high(r)
{
bbr2_handle_queue_too_high_in_startup(r);
}
if r.bbr2_state.loss_round_start {
r.bbr2_state.loss_events_in_round = 0
}
}
fn bbr2_handle_queue_too_high_in_startup(r: &mut Congestion) {
r.bbr2_state.filled_pipe = true;
r.bbr2_state.inflight_hi = bbr2_inflight(r, r.bbr2_state.max_bw, 1.0);
}
fn bbr2_enter_drain(r: &mut Congestion) {
let bbr = &mut r.bbr2_state;
bbr.state = BBR2StateMachine::Drain;
bbr.pacing_gain = PACING_GAIN / STARTUP_CWND_GAIN;
bbr.cwnd_gain = STARTUP_CWND_GAIN;
}
fn bbr2_check_drain(r: &mut Congestion, in_flight: usize, now: Instant) {
if r.bbr2_state.state == BBR2StateMachine::Drain &&
in_flight <= bbr2_inflight(r, r.bbr2_state.max_bw, 1.0)
{
bbr2_enter_probe_bw(r, now);
}
}
fn bbr2_check_time_to_probe_bw(r: &mut Congestion, now: Instant) -> bool {
if bbr2_has_elapsed_in_phase(r, r.bbr2_state.bw_probe_wait, now) ||
bbr2_is_reno_coexistence_probe_time(r)
{
bbr2_start_probe_bw_refill(r);
return true;
}
false
}
fn bbr2_pick_probe_wait(r: &mut Congestion) {
let bbr = &mut r.bbr2_state;
bbr.rounds_since_probe = rand::rand_u8() as usize % 2;
bbr.bw_probe_wait = Duration::from_secs_f64(
2.0 + rand::rand_u64_uniform(1000000) as f64 / 1000000.0,
);
}
fn bbr2_is_reno_coexistence_probe_time(r: &mut Congestion) -> bool {
let reno_rounds = bbr2_target_inflight(r);
let rounds = reno_rounds.min(63);
r.bbr2_state.rounds_since_probe >= rounds
}
pub fn bbr2_target_inflight(r: &mut Congestion) -> usize {
r.bbr2_state.bdp.min(r.congestion_window)
}
fn bbr2_enter_probe_bw(r: &mut Congestion, now: Instant) {
bbr2_start_probe_bw_down(r, now);
}
pub fn bbr2_start_probe_bw_down(r: &mut Congestion, now: Instant) {
per_loss::bbr2_reset_congestion_signals(r);
r.bbr2_state.probe_up_cnt = usize::MAX;
bbr2_pick_probe_wait(r);
r.bbr2_state.cycle_stamp = now;
r.bbr2_state.ack_phase = BBR2AckPhase::ProbeStopping;
bbr2_start_round(r);
r.bbr2_state.state = BBR2StateMachine::ProbeBWDOWN;
r.bbr2_state.pacing_gain = PROBE_DOWN_PACING_GAIN;
r.bbr2_state.cwnd_gain = CWND_GAIN
}
fn bbr2_start_probe_bw_cruise(r: &mut Congestion) {
let bbr = &mut r.bbr2_state;
bbr.state = BBR2StateMachine::ProbeBWCRUISE;
bbr.pacing_gain = PACING_GAIN;
bbr.cwnd_gain = CWND_GAIN;
}
fn bbr2_start_probe_bw_refill(r: &mut Congestion) {
per_loss::bbr2_reset_lower_bounds(r);
r.bbr2_state.bw_probe_up_rounds = 0;
r.bbr2_state.bw_probe_up_acks = 0;
r.bbr2_state.ack_phase = BBR2AckPhase::Refilling;
bbr2_start_round(r);
r.bbr2_state.state = BBR2StateMachine::ProbeBWREFILL;
r.bbr2_state.pacing_gain = PACING_GAIN;
r.bbr2_state.cwnd_gain = CWND_GAIN;
}
fn bbr2_start_probe_bw_up(r: &mut Congestion, now: Instant) {
r.bbr2_state.ack_phase = BBR2AckPhase::ProbeStarting;
bbr2_start_round(r);
r.bbr2_state.cycle_stamp = now;
r.bbr2_state.state = BBR2StateMachine::ProbeBWUP;
r.bbr2_state.pacing_gain = PROBE_UP_PACING_GAIN;
r.bbr2_state.cwnd_gain = CWND_GAIN;
bbr2_raise_inflight_hi_slope(r);
}
fn bbr2_update_probe_bw_cycle_phase(
r: &mut Congestion, in_flight: usize, now: Instant,
) {
if !r.bbr2_state.filled_pipe {
return;
}
bbr2_adapt_upper_bounds(r, now);
if !bbr2_is_in_a_probe_bw_state(r) {
return;
}
match r.bbr2_state.state {
BBR2StateMachine::ProbeBWDOWN => {
if bbr2_check_time_to_probe_bw(r, now) {
return;
}
if bbr2_check_time_to_cruise(r, in_flight) {
bbr2_start_probe_bw_cruise(r);
}
},
BBR2StateMachine::ProbeBWCRUISE => {
bbr2_check_time_to_probe_bw(r, now);
},
BBR2StateMachine::ProbeBWREFILL => {
if r.bbr2_state.round_start {
r.bbr2_state.bw_probe_samples = true;
bbr2_start_probe_bw_up(r, now);
}
},
BBR2StateMachine::ProbeBWUP => {
if bbr2_has_elapsed_in_phase(r, r.bbr2_state.min_rtt, now) &&
in_flight > bbr2_inflight(r, r.bbr2_state.max_bw, 1.25)
{
bbr2_start_probe_bw_down(r, now);
}
},
_ => (),
}
}
pub fn bbr2_is_in_a_probe_bw_state(r: &mut Congestion) -> bool {
let state = r.bbr2_state.state;
state == BBR2StateMachine::ProbeBWDOWN ||
state == BBR2StateMachine::ProbeBWCRUISE ||
state == BBR2StateMachine::ProbeBWREFILL ||
state == BBR2StateMachine::ProbeBWUP
}
fn bbr2_check_time_to_cruise(r: &mut Congestion, in_flight: usize) -> bool {
if in_flight > bbr2_inflight_with_headroom(r) {
return false;
}
if in_flight <= bbr2_inflight(r, r.bbr2_state.max_bw, 1.0) {
return true;
}
false
}
fn bbr2_has_elapsed_in_phase(
r: &mut Congestion, interval: Duration, now: Instant,
) -> bool {
now > r.bbr2_state.cycle_stamp + interval
}
fn bbr2_inflight_with_headroom(r: &mut Congestion) -> usize {
let bbr = &mut r.bbr2_state;
if bbr.inflight_hi == usize::MAX {
return usize::MAX;
}
let headroom = ((HEADROOM * bbr.inflight_hi as f64) as usize).max(1);
bbr.inflight_hi
.saturating_sub(headroom)
.max(bbr2_min_pipe_cwnd(r))
}
fn bbr2_raise_inflight_hi_slope(r: &mut Congestion) {
let bbr = &mut r.bbr2_state;
let growth_this_round = (1 << bbr.bw_probe_up_rounds) * r.max_datagram_size;
bbr.bw_probe_up_rounds = (bbr.bw_probe_up_rounds + 1).min(30);
bbr.probe_up_cnt = (r.congestion_window / growth_this_round).max(1);
}
fn bbr2_probe_inflight_hi_upward(r: &mut Congestion) {
if r.app_limited || r.congestion_window < r.bbr2_state.inflight_hi {
return;
}
let bbr = &mut r.bbr2_state;
bbr.bw_probe_up_acks += 1;
if bbr.bw_probe_up_acks >= bbr.probe_up_cnt {
let delta = bbr.bw_probe_up_acks / bbr.probe_up_cnt;
bbr.bw_probe_up_acks -= delta * bbr.probe_up_cnt;
bbr.inflight_hi += delta * r.max_datagram_size;
}
if bbr.round_start {
bbr2_raise_inflight_hi_slope(r);
}
}
fn bbr2_adapt_upper_bounds(r: &mut Congestion, now: Instant) {
if r.bbr2_state.ack_phase == BBR2AckPhase::ProbeStarting &&
r.bbr2_state.round_start
{
r.bbr2_state.ack_phase = BBR2AckPhase::ProbeFeedback;
}
if r.bbr2_state.ack_phase == BBR2AckPhase::ProbeStopping &&
r.bbr2_state.round_start
{
r.bbr2_state.bw_probe_samples = false;
r.bbr2_state.ack_phase = BBR2AckPhase::Init;
if bbr2_is_in_a_probe_bw_state(r) &&
!r.delivery_rate.sample_is_app_limited()
{
bbr2_advance_max_bw_filter(r);
}
}
if !per_loss::bbr2_check_inflight_too_high(r, now) {
if r.bbr2_state.inflight_hi == usize::MAX ||
r.bbr2_state.bw_hi == u64::MAX
{
return;
}
if r.bbr2_state.tx_in_flight > r.bbr2_state.inflight_hi {
r.bbr2_state.inflight_hi = r.bbr2_state.tx_in_flight;
}
if r.delivery_rate() > r.bbr2_state.bw_hi {
r.bbr2_state.bw_hi = r.delivery_rate();
}
if r.bbr2_state.state == BBR2StateMachine::ProbeBWUP {
bbr2_probe_inflight_hi_upward(r);
}
}
}
fn bbr2_update_min_rtt(r: &mut Congestion, now: Instant) {
let bbr = &mut r.bbr2_state;
bbr.probe_rtt_expired = now > bbr.probe_rtt_min_stamp + PROBE_RTT_INTERVAL;
let rs_rtt = r.delivery_rate.sample_rtt();
if !rs_rtt.is_zero() &&
(rs_rtt < bbr.probe_rtt_min_delay || bbr.probe_rtt_expired)
{
bbr.probe_rtt_min_delay = rs_rtt;
bbr.probe_rtt_min_stamp = now;
}
let min_rtt_expired =
now > bbr.min_rtt_stamp + rs_rtt.saturating_mul(MIN_RTT_FILTER_LEN);
if bbr.min_rtt == rtt::INITIAL_RTT || min_rtt_expired {
bbr.min_rtt = rs_rtt;
bbr.min_rtt_stamp = now;
}
}
fn bbr2_check_probe_rtt(r: &mut Congestion, in_flight: usize, now: Instant) {
if r.bbr2_state.state != BBR2StateMachine::ProbeRTT &&
r.bbr2_state.probe_rtt_expired &&
!r.bbr2_state.idle_restart
{
bbr2_enter_probe_rtt(r);
r.bbr2_state.prior_cwnd = per_ack::bbr2_save_cwnd(r);
r.bbr2_state.probe_rtt_done_stamp = None;
r.bbr2_state.ack_phase = BBR2AckPhase::ProbeStopping;
bbr2_start_round(r);
}
if r.bbr2_state.state == BBR2StateMachine::ProbeRTT {
bbr2_handle_probe_rtt(r, in_flight, now);
}
if r.delivery_rate.sample_delivered() > 0 {
r.bbr2_state.idle_restart = false;
}
}
fn bbr2_enter_probe_rtt(r: &mut Congestion) {
let bbr = &mut r.bbr2_state;
bbr.state = BBR2StateMachine::ProbeRTT;
bbr.pacing_gain = PACING_GAIN;
bbr.cwnd_gain = PROBE_RTT_CWND_GAIN;
}
fn bbr2_handle_probe_rtt(r: &mut Congestion, in_flight: usize, now: Instant) {
r.delivery_rate.update_app_limited(true);
if r.bbr2_state.probe_rtt_done_stamp.is_some() {
if r.bbr2_state.round_start {
r.bbr2_state.probe_rtt_round_done = true;
}
if r.bbr2_state.probe_rtt_round_done {
bbr2_check_probe_rtt_done(r, now);
}
} else if in_flight <= bbr2_probe_rtt_cwnd(r) {
r.bbr2_state.probe_rtt_done_stamp = Some(now + PROBE_RTT_DURATION);
r.bbr2_state.probe_rtt_round_done = false;
bbr2_start_round(r);
}
}
pub fn bbr2_check_probe_rtt_done(r: &mut Congestion, now: Instant) {
let bbr = &mut r.bbr2_state;
if let Some(probe_rtt_done_stamp) = bbr.probe_rtt_done_stamp {
if now > probe_rtt_done_stamp {
bbr.probe_rtt_min_stamp = now;
bbr2_restore_cwnd(r);
bbr2_exit_probe_rtt(r, now);
}
}
}
fn bbr2_exit_probe_rtt(r: &mut Congestion, now: Instant) {
per_loss::bbr2_reset_lower_bounds(r);
if r.bbr2_state.filled_pipe {
bbr2_start_probe_bw_down(r, now);
bbr2_start_probe_bw_cruise(r);
} else {
init::bbr2_enter_startup(r);
}
}
fn bbr2_update_round(r: &mut Congestion, packet: &Acked) {
if packet.delivered >= r.bbr2_state.next_round_delivered {
bbr2_start_round(r);
r.bbr2_state.round_count += 1;
r.bbr2_state.rounds_since_probe += 1;
r.bbr2_state.round_start = true;
} else {
r.bbr2_state.round_start = false;
}
}
fn bbr2_start_round(r: &mut Congestion) {
r.bbr2_state.next_round_delivered = r.delivery_rate.delivered();
}
pub fn bbr2_update_max_bw(r: &mut Congestion, packet: &Acked) {
bbr2_update_round(r, packet);
if r.delivery_rate() >= r.bbr2_state.max_bw ||
!r.delivery_rate.sample_is_app_limited()
{
let max_bw_filter_len = r
.delivery_rate
.sample_rtt()
.saturating_mul(MIN_RTT_FILTER_LEN);
r.bbr2_state.max_bw = r.bbr2_state.max_bw_filter.running_max(
max_bw_filter_len,
r.bbr2_state.start_time +
Duration::from_secs(r.bbr2_state.cycle_count),
r.delivery_rate(),
);
}
}
fn bbr2_advance_max_bw_filter(r: &mut Congestion) {
r.bbr2_state.cycle_count += 1;
}
fn bbr2_update_offload_budget(r: &mut Congestion) {
r.bbr2_state.offload_budget = 3 * r.send_quantum;
}
fn bbr2_update_ack_aggregation(r: &mut Congestion, packet: &Acked, now: Instant) {
let bbr = &mut r.bbr2_state;
let interval = now - bbr.extra_acked_interval_start;
let mut expected_delivered =
(bbr.bw as f64 * interval.as_secs_f64()) as usize;
if bbr.extra_acked_delivered <= expected_delivered {
bbr.extra_acked_delivered = 0;
bbr.extra_acked_interval_start = now;
expected_delivered = 0;
}
bbr.extra_acked_delivered += packet.size;
let extra = bbr.extra_acked_delivered.saturating_sub(expected_delivered);
let extra = extra.min(r.congestion_window);
let extra_acked_filter_len = r
.delivery_rate
.sample_rtt()
.saturating_mul(MIN_RTT_FILTER_LEN);
bbr.extra_acked = bbr.extra_acked_filter.running_max(
extra_acked_filter_len,
bbr.start_time + Duration::from_secs(bbr.round_count),
extra,
);
}
fn bbr2_set_send_quantum(r: &mut Congestion) {
let bbr = &mut r.bbr2_state;
let rate = bbr.pacing_rate;
let floor = if rate < PACING_RATE_1_2MBPS {
r.max_datagram_size
} else {
2 * r.max_datagram_size
};
r.send_quantum = cmp::min((rate / 1000_u64) as usize, 64 * 1024); r.send_quantum = r.send_quantum.max(floor);
}
fn bbr2_bdp_multiple(r: &mut Congestion, bw: u64, gain: f64) -> usize {
let bbr = &mut r.bbr2_state;
if bbr.min_rtt == Duration::MAX {
return r.max_datagram_size * r.initial_congestion_window_packets;
}
bbr.bdp = (bw as f64 * bbr.min_rtt.as_secs_f64()) as usize;
(gain * bbr.bdp as f64) as usize
}
fn bbr2_quantization_budget(r: &mut Congestion, inflight: usize) -> usize {
bbr2_update_offload_budget(r);
let inflight = inflight.max(r.bbr2_state.offload_budget);
let inflight = inflight.max(bbr2_min_pipe_cwnd(r));
if r.bbr2_state.state == BBR2StateMachine::ProbeBWUP {
return inflight + 2 * r.max_datagram_size;
}
inflight
}
fn bbr2_inflight(r: &mut Congestion, bw: u64, gain: f64) -> usize {
let inflight = bbr2_bdp_multiple(r, bw, gain);
bbr2_quantization_budget(r, inflight)
}
fn bbr2_update_max_inflight(r: &mut Congestion) {
let inflight =
bbr2_bdp_multiple(r, r.bbr2_state.max_bw, r.bbr2_state.cwnd_gain);
let inflight = inflight + r.bbr2_state.extra_acked;
r.bbr2_state.max_inflight = bbr2_quantization_budget(r, inflight);
}
pub fn bbr2_save_cwnd(r: &mut Congestion) -> usize {
if !r.bbr2_state.in_recovery &&
r.bbr2_state.state != BBR2StateMachine::ProbeRTT
{
r.congestion_window
} else {
r.congestion_window.max(r.bbr2_state.prior_cwnd)
}
}
pub fn bbr2_restore_cwnd(r: &mut Congestion) {
r.congestion_window = r.congestion_window.max(r.bbr2_state.prior_cwnd);
}
fn bbr2_modulate_cwnd_for_recovery(r: &mut Congestion, in_flight: usize) {
let acked_bytes = r.bbr2_state.newly_acked_bytes;
let lost_bytes = r.bbr2_state.newly_lost_bytes;
if lost_bytes > 0 {
r.congestion_window = r
.congestion_window
.saturating_sub(lost_bytes)
.max(r.max_datagram_size * recovery::MINIMUM_WINDOW_PACKETS);
}
if r.bbr2_state.packet_conservation {
r.congestion_window = r.congestion_window.max(in_flight + acked_bytes);
}
}
fn bbr2_probe_rtt_cwnd(r: &mut Congestion) -> usize {
let probe_rtt_cwnd =
bbr2_bdp_multiple(r, r.bbr2_state.bw, PROBE_RTT_CWND_GAIN);
probe_rtt_cwnd.max(bbr2_min_pipe_cwnd(r))
}
fn bbr2_bound_cwnd_for_probe_rtt(r: &mut Congestion) {
if r.bbr2_state.state == BBR2StateMachine::ProbeRTT {
r.congestion_window = r.congestion_window.min(bbr2_probe_rtt_cwnd(r));
}
}
fn bbr2_set_cwnd(r: &mut Congestion, in_flight: usize) {
let acked_bytes = r.bbr2_state.newly_acked_bytes;
bbr2_update_max_inflight(r);
bbr2_modulate_cwnd_for_recovery(r, in_flight);
if !r.bbr2_state.packet_conservation {
if r.bbr2_state.filled_pipe {
r.congestion_window = cmp::min(
r.congestion_window + acked_bytes,
r.bbr2_state.max_inflight,
)
} else if r.congestion_window < r.bbr2_state.max_inflight ||
r.delivery_rate.delivered() <
r.max_datagram_size * r.initial_congestion_window_packets
{
r.congestion_window += acked_bytes;
}
r.congestion_window = r.congestion_window.max(bbr2_min_pipe_cwnd(r))
}
bbr2_bound_cwnd_for_probe_rtt(r);
bbr2_bound_cwnd_for_model(r);
}
fn bbr2_bound_cwnd_for_model(r: &mut Congestion) {
let mut cap = usize::MAX;
if bbr2_is_in_a_probe_bw_state(r) &&
r.bbr2_state.state != BBR2StateMachine::ProbeBWCRUISE
{
cap = r.bbr2_state.inflight_hi;
} else if r.bbr2_state.state == BBR2StateMachine::ProbeRTT ||
r.bbr2_state.state == BBR2StateMachine::ProbeBWCRUISE
{
cap = bbr2_inflight_with_headroom(r);
}
cap = cap.min(r.bbr2_state.inflight_lo);
cap = cap.max(bbr2_min_pipe_cwnd(r));
r.congestion_window = r.congestion_window.min(cap);
}