use std::time::Instant;
use crate::recovery::gcongestion::bbr2::Params;
use crate::recovery::RecoveryStats;
use crate::recovery::StartupExit;
use crate::recovery::StartupExitReason;
use super::mode::Mode;
use super::mode::ModeImpl;
use super::network_model::BBRv2NetworkModel;
use super::Acked;
use super::BBRv2CongestionEvent;
use super::Limits;
use super::Lost;
#[derive(Debug)]
pub(super) struct Startup {
pub(super) model: BBRv2NetworkModel,
}
impl ModeImpl for Startup {
#[cfg(feature = "qlog")]
fn state_str(&self) -> &'static str {
"bbr_startup"
}
fn is_probing_for_bandwidth(&self) -> bool {
true
}
fn on_congestion_event(
mut self, _prior_in_flight: usize, event_time: Instant,
_acked_packets: &[Acked], _lost_packets: &[Lost],
congestion_event: &mut BBRv2CongestionEvent,
_target_bytes_inflight: usize, params: &Params,
recovery_stats: &mut RecoveryStats, cwnd: usize,
) -> Mode {
if self.model.full_bandwidth_reached() {
return self.into_drain(event_time, Some(congestion_event), params);
}
if !congestion_event.end_of_round_trip {
return Mode::Startup(self);
}
let has_bandwidth_growth =
self.model.has_bandwidth_growth(congestion_event, params);
if self.model.full_bandwidth_reached() {
recovery_stats.set_startup_exit(StartupExit::new(
cwnd,
Some(self.model.max_bandwidth()),
StartupExitReason::BandwidthPlateau,
));
}
let check_persisten_queue =
params.max_startup_queue_rounds > 0 && !has_bandwidth_growth;
if check_persisten_queue {
self.model.check_persistent_queue(1.75, params);
if self.model.full_bandwidth_reached() {
recovery_stats.set_startup_exit(StartupExit::new(
cwnd,
Some(self.model.max_bandwidth()),
StartupExitReason::PersistentQueue,
));
}
}
let check_for_excessive_loss = !congestion_event.last_packet_send_state.is_app_limited &&
!has_bandwidth_growth &&
!self.model.full_bandwidth_reached();
if check_for_excessive_loss {
self.check_excessive_losses(congestion_event, params);
if self.model.full_bandwidth_reached() {
recovery_stats.set_startup_exit(StartupExit::new(
cwnd,
Some(self.model.max_bandwidth()),
StartupExitReason::Loss,
));
}
}
if self.model.full_bandwidth_reached() {
self.into_drain(event_time, Some(congestion_event), params)
} else {
Mode::Startup(self)
}
}
fn get_cwnd_limits(&self, _params: &Params) -> Limits<usize> {
Limits {
lo: 0,
hi: self.model.inflight_lo(),
}
}
fn on_exit_quiescence(
self, _now: Instant, _quiescence_start_time: Instant, _params: &Params,
) -> Mode {
Mode::Startup(self)
}
fn enter(
&mut self, _now: Instant,
_congestion_event: Option<&BBRv2CongestionEvent>, _params: &Params,
) {
unreachable!("Enter should never be called for startup")
}
fn leave(
&mut self, _now: Instant,
_congestion_event: Option<&BBRv2CongestionEvent>,
) {
self.model.clear_bandwidth_lo();
}
}
impl Startup {
fn into_drain(
mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,
params: &Params,
) -> Mode {
self.leave(now, congestion_event);
let mut next_mode = Mode::drain(self.model);
next_mode.enter(now, congestion_event, params);
next_mode
}
fn check_excessive_losses(
&mut self, congestion_event: &mut BBRv2CongestionEvent, params: &Params,
) {
if self.model.is_inflight_too_high(
congestion_event,
params.startup_full_loss_count,
params,
) {
let mut new_inflight_hi = self.model.bdp0();
if params.startup_loss_exit_use_max_delivered_for_inflight_hi {
new_inflight_hi = new_inflight_hi
.max(self.model.max_bytes_delivered_in_round());
}
self.model.set_inflight_hi(new_inflight_hi);
self.model.set_full_bandwidth_reached();
}
}
}