Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add confirmed parameter to PTO calculation #2127

Merged
merged 6 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions neqo-transport/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,13 +629,17 @@ impl Connection {
.unwrap()
}

fn confirmed(&self) -> bool {
larseggert marked this conversation as resolved.
Show resolved Hide resolved
self.state == State::Confirmed
}

/// Get the simplest PTO calculation for all those cases where we need
/// a value of this approximate order. Don't use this for loss recovery,
/// only use it where a more precise value is not important.
fn pto(&self) -> Duration {
self.paths.primary().map_or_else(
|| RttEstimate::default().pto(PacketNumberSpace::ApplicationData),
|p| p.borrow().rtt().pto(PacketNumberSpace::ApplicationData),
|| RttEstimate::default().pto(self.confirmed()),
|p| p.borrow().rtt().pto(self.confirmed()),
)
}

Expand Down Expand Up @@ -1058,7 +1062,7 @@ impl Connection {
if let Some(p) = self.paths.primary() {
let path = p.borrow();
let rtt = path.rtt();
let pto = rtt.pto(PacketNumberSpace::ApplicationData);
let pto = rtt.pto(self.confirmed());

let idle_time = self.idle_timeout.expiry(now, pto);
qtrace!([self], "Idle/keepalive timer {:?}", idle_time);
Expand Down Expand Up @@ -1525,7 +1529,7 @@ impl Connection {
let mut dcid = None;

qtrace!([self], "{} input {}", path.borrow(), hex(&**d));
let pto = path.borrow().rtt().pto(PacketNumberSpace::ApplicationData);
let pto = path.borrow().rtt().pto(self.confirmed());

// Handle each packet in the datagram.
while !slc.is_empty() {
Expand Down Expand Up @@ -2141,7 +2145,7 @@ impl Connection {
// or the PTO timer fired: probe.
true
} else {
let pto = path.borrow().rtt().pto(PacketNumberSpace::ApplicationData);
let pto = path.borrow().rtt().pto(self.confirmed());
if !builder.packet_empty() {
// The packet only contains an ACK. Check whether we want to
// force an ACK with a PING so we can stop tracking packets.
Expand Down
5 changes: 2 additions & 3 deletions neqo-transport/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ use crate::{
rtt::{RttEstimate, RttSource},
sender::PacketSender,
stats::FrameStats,
tracking::PacketNumberSpace,
Stats,
};

Expand Down Expand Up @@ -1020,7 +1019,7 @@ impl Path {
pub fn on_packets_lost(
&mut self,
prev_largest_acked_sent: Option<Instant>,
space: PacketNumberSpace,
confirmed: bool,
lost_packets: &[SentPacket],
stats: &mut Stats,
now: Instant,
Expand All @@ -1030,7 +1029,7 @@ impl Path {
let cwnd_reduced = self.sender.on_packets_lost(
self.rtt.first_sample_time(),
prev_largest_acked_sent,
self.rtt.pto(space), // Important: the base PTO, not adjusted.
self.rtt.pto(confirmed), // Important: the base PTO, not adjusted.
lost_packets,
stats,
now,
Expand Down
58 changes: 31 additions & 27 deletions neqo-transport/src/recovery/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,10 @@ impl LossRecovery {
}
}

const fn confirmed(&self) -> bool {
self.confirmed_time.is_some()
}

/// Returns (acked packets, lost packets)
#[allow(clippy::too_many_arguments)]
pub fn on_ack_received<R>(
Expand Down Expand Up @@ -627,7 +631,7 @@ impl LossRecovery {
// as we rely on the count of in-flight packets to determine whether to send
// another probe. Removing them too soon would result in not sending on PTO.
let loss_delay = primary_path.borrow().rtt().loss_delay();
let cleanup_delay = self.pto_period(primary_path.borrow().rtt(), pn_space);
let cleanup_delay = self.pto_period(primary_path.borrow().rtt());
let mut lost = Vec::new();
self.spaces.get_mut(pn_space).unwrap().detect_lost_packets(
now,
Expand All @@ -642,7 +646,7 @@ impl LossRecovery {
// backoff, so that we can determine persistent congestion.
primary_path.borrow_mut().on_packets_lost(
prev_largest_acked,
pn_space,
self.confirmed(),
&lost,
&mut self.stats.borrow_mut(),
now,
Expand Down Expand Up @@ -679,7 +683,7 @@ impl LossRecovery {
dropped
}

fn confirmed(&mut self, rtt: &RttEstimate, now: Instant) {
fn confirm(&mut self, rtt: &RttEstimate, now: Instant) {
debug_assert!(self.confirmed_time.is_none());
self.confirmed_time = Some(now);
// Up until now, the ApplicationData space has been ignored for PTO.
Expand Down Expand Up @@ -716,7 +720,7 @@ impl LossRecovery {
self.pto_state = None;

if space == PacketNumberSpace::Handshake {
self.confirmed(path.rtt(), now);
self.confirm(path.rtt(), now);
}
}

Expand Down Expand Up @@ -757,41 +761,40 @@ impl LossRecovery {
fn pto_period_inner(
rtt: &RttEstimate,
pto_state: Option<&PtoState>,
pn_space: PacketNumberSpace,
confirmed: bool,
fast_pto: u8,
) -> Duration {
// This is a complicated (but safe) way of calculating:
// base_pto * F * 2^pto_count
// where F = fast_pto / FAST_PTO_SCALE (== 1 by default)
let pto_count = pto_state.map_or(0, |p| u32::try_from(p.count).unwrap_or(0));
rtt.pto(pn_space)
rtt.pto(confirmed)
.checked_mul(u32::from(fast_pto) << min(pto_count, u32::BITS - u8::BITS))
.map_or(Duration::from_secs(3600), |p| p / u32::from(FAST_PTO_SCALE))
}

/// Get the current PTO period for the given packet number space.
/// Unlike calling `RttEstimate::pto` directly, this includes exponential backoff.
fn pto_period(&self, rtt: &RttEstimate, pn_space: PacketNumberSpace) -> Duration {
Self::pto_period_inner(rtt, self.pto_state.as_ref(), pn_space, self.fast_pto)
fn pto_period(&self, rtt: &RttEstimate) -> Duration {
Self::pto_period_inner(
rtt,
self.pto_state.as_ref(),
self.confirmed(),
self.fast_pto,
)
}

// Calculate PTO time for the given space.
fn pto_time(&self, rtt: &RttEstimate, pn_space: PacketNumberSpace) -> Option<Instant> {
if self.confirmed_time.is_none() && pn_space == PacketNumberSpace::ApplicationData {
None
} else {
self.spaces.get(pn_space).and_then(|space| {
space
.pto_base_time()
.map(|t| t + self.pto_period(rtt, pn_space))
})
}
self.spaces
.get(pn_space)
.and_then(|space| space.pto_base_time().map(|t| t + self.pto_period(rtt)))
}

/// Find the earliest PTO time for all active packet number spaces.
/// Ignore Application if either Initial or Handshake have an active PTO.
fn earliest_pto(&self, rtt: &RttEstimate) -> Option<Instant> {
if self.confirmed_time.is_some() {
if self.confirmed() {
self.pto_time(rtt, PacketNumberSpace::ApplicationData)
} else {
self.pto_time(rtt, PacketNumberSpace::Initial)
Expand Down Expand Up @@ -859,21 +862,22 @@ impl LossRecovery {
qtrace!([self], "timeout {:?}", now);

let loss_delay = primary_path.borrow().rtt().loss_delay();
let confirmed = self.confirmed();

let mut lost_packets = Vec::new();
for space in self.spaces.iter_mut() {
let first = lost_packets.len(); // The first packet lost in this space.
let pto = Self::pto_period_inner(
primary_path.borrow().rtt(),
self.pto_state.as_ref(),
space.space,
confirmed,
self.fast_pto,
);
space.detect_lost_packets(now, loss_delay, pto, &mut lost_packets);

primary_path.borrow_mut().on_packets_lost(
space.largest_acked_sent_time,
space.space,
confirmed,
&lost_packets[first..],
&mut self.stats.borrow_mut(),
now,
Expand Down Expand Up @@ -950,7 +954,6 @@ mod tests {
ecn::EcnCount,
packet::{PacketNumber, PacketType},
path::{Path, PathRef},
rtt::RttEstimate,
stats::{Stats, StatsCell},
};

Expand All @@ -961,8 +964,8 @@ mod tests {

const ON_SENT_SIZE: usize = 100;
/// An initial RTT for using with `setup_lr`.
const TEST_RTT: Duration = ms(80);
const TEST_RTTVAR: Duration = ms(40);
const TEST_RTT: Duration = ms(7000);
const TEST_RTTVAR: Duration = ms(3500);
martinthomson marked this conversation as resolved.
Show resolved Hide resolved

struct Fixture {
lr: LossRecovery,
Expand Down Expand Up @@ -1033,6 +1036,7 @@ mod tests {
ConnectionIdEntry::new(0, ConnectionId::from(&[1, 2, 3]), [0; 16]),
);
path.set_primary(true);
path.rtt_mut().set_initial(TEST_RTT);
Self {
lr: LossRecovery::new(StatsCell::default(), FAST_PTO_SCALE),
path: Rc::new(RefCell::new(path)),
Expand Down Expand Up @@ -1510,13 +1514,13 @@ mod tests {
ON_SENT_SIZE,
));

assert_eq!(lr.pto_time(PacketNumberSpace::ApplicationData), None);
assert!(lr.pto_time(PacketNumberSpace::ApplicationData).is_some());
lr.discard(PacketNumberSpace::Initial, pn_time(1));
assert_eq!(lr.pto_time(PacketNumberSpace::ApplicationData), None);
assert!(lr.pto_time(PacketNumberSpace::ApplicationData).is_some());

// Expiring state after the PTO on the ApplicationData space has
// expired should result in setting a PTO state.
let default_pto = RttEstimate::default().pto(PacketNumberSpace::ApplicationData);
let default_pto = lr.path.borrow().rtt().pto(true);
let expected_pto = pn_time(2) + default_pto;
lr.discard(PacketNumberSpace::Handshake, expected_pto);
let profile = lr.send_profile(now());
Expand Down Expand Up @@ -1548,7 +1552,7 @@ mod tests {
ON_SENT_SIZE,
));

let handshake_pto = RttEstimate::default().pto(PacketNumberSpace::Handshake);
let handshake_pto = lr.path.borrow().rtt().pto(false);
let expected_pto = now() + handshake_pto;
assert_eq!(lr.pto_time(PacketNumberSpace::Initial), Some(expected_pto));
let profile = lr.send_profile(now());
Expand Down
5 changes: 2 additions & 3 deletions neqo-transport/src/rtt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use crate::{
qlog::{self, QlogMetric},
recovery::RecoveryToken,
stats::FrameStats,
tracking::PacketNumberSpace,
};

/// The smallest time that the system timer (via `sleep()`, `nanosleep()`,
Expand Down Expand Up @@ -163,9 +162,9 @@ impl RttEstimate {
self.smoothed_rtt
}

pub fn pto(&self, pn_space: PacketNumberSpace) -> Duration {
pub fn pto(&self, confirmed: bool) -> Duration {
let mut t = self.estimate() + max(4 * self.rttvar, GRANULARITY);
if pn_space == PacketNumberSpace::ApplicationData {
if confirmed {
t += self.ack_delay.max();
}
t
Expand Down
Loading