iroh_quinn_proto/congestion/new_reno.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
use std::any::Any;
use std::sync::Arc;
use std::time::Instant;
use super::{Controller, ControllerFactory, BASE_DATAGRAM_SIZE};
use crate::connection::RttEstimator;
/// A simple, standard congestion controller
#[derive(Debug, Clone)]
pub struct NewReno {
config: Arc<NewRenoConfig>,
current_mtu: u64,
/// Maximum number of bytes in flight that may be sent.
window: u64,
/// Slow start threshold in bytes. When the congestion window is below ssthresh, the mode is
/// slow start and the window grows by the number of bytes acknowledged.
ssthresh: u64,
/// The time when QUIC first detects a loss, causing it to enter recovery. When a packet sent
/// after this time is acknowledged, QUIC exits recovery.
recovery_start_time: Instant,
/// Bytes which had been acked by the peer since leaving slow start
bytes_acked: u64,
}
impl NewReno {
/// Construct a state using the given `config` and current time `now`
pub fn new(config: Arc<NewRenoConfig>, now: Instant, current_mtu: u16) -> Self {
Self {
window: config.initial_window,
ssthresh: u64::MAX,
recovery_start_time: now,
current_mtu: current_mtu as u64,
config,
bytes_acked: 0,
}
}
fn minimum_window(&self) -> u64 {
2 * self.current_mtu
}
}
impl Controller for NewReno {
fn on_ack(
&mut self,
_now: Instant,
sent: Instant,
bytes: u64,
app_limited: bool,
_rtt: &RttEstimator,
) {
if app_limited || sent <= self.recovery_start_time {
return;
}
if self.window < self.ssthresh {
// Slow start
self.window += bytes;
if self.window >= self.ssthresh {
// Exiting slow start
// Initialize `bytes_acked` for congestion avoidance. The idea
// here is that any bytes over `sshthresh` will already be counted
// towards the congestion avoidance phase - independent of when
// how close to `sshthresh` the `window` was when switching states,
// and independent of datagram sizes.
self.bytes_acked = self.window - self.ssthresh;
}
} else {
// Congestion avoidance
// This implementation uses the method which does not require
// floating point math, which also increases the window by 1 datagram
// for every round trip.
// This mechanism is called Appropriate Byte Counting in
// https://tools.ietf.org/html/rfc3465
self.bytes_acked += bytes;
if self.bytes_acked >= self.window {
self.bytes_acked -= self.window;
self.window += self.current_mtu;
}
}
}
fn on_congestion_event(
&mut self,
now: Instant,
sent: Instant,
is_persistent_congestion: bool,
_lost_bytes: u64,
) {
if sent <= self.recovery_start_time {
return;
}
self.recovery_start_time = now;
self.window = (self.window as f32 * self.config.loss_reduction_factor) as u64;
self.window = self.window.max(self.minimum_window());
self.ssthresh = self.window;
if is_persistent_congestion {
self.window = self.minimum_window();
}
}
fn on_mtu_update(&mut self, new_mtu: u16) {
self.current_mtu = new_mtu as u64;
self.window = self.window.max(self.minimum_window());
}
fn window(&self) -> u64 {
self.window
}
fn clone_box(&self) -> Box<dyn Controller> {
Box::new(self.clone())
}
fn initial_window(&self) -> u64 {
self.config.initial_window
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
/// Configuration for the `NewReno` congestion controller
#[derive(Debug, Clone)]
pub struct NewRenoConfig {
initial_window: u64,
loss_reduction_factor: f32,
}
impl NewRenoConfig {
/// Default limit on the amount of outstanding data in bytes.
///
/// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))`
pub fn initial_window(&mut self, value: u64) -> &mut Self {
self.initial_window = value;
self
}
/// Reduction in congestion window when a new loss event is detected.
pub fn loss_reduction_factor(&mut self, value: f32) -> &mut Self {
self.loss_reduction_factor = value;
self
}
}
impl Default for NewRenoConfig {
fn default() -> Self {
Self {
initial_window: 14720.clamp(2 * BASE_DATAGRAM_SIZE, 10 * BASE_DATAGRAM_SIZE),
loss_reduction_factor: 0.5,
}
}
}
impl ControllerFactory for NewRenoConfig {
fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller> {
Box::new(NewReno::new(self, now, current_mtu))
}
}