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

Include ack_delay when deciding with PN space to arm PTO for #3666

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 19 additions & 18 deletions draft-ietf-quic-recovery.md
Expand Up @@ -1246,17 +1246,19 @@ timers wake up late. Timers set in the past fire immediately.
Pseudocode for SetLossDetectionTimer follows:

~~~
GetEarliestTimeAndSpace(times):
time = times[Initial]
space = Initial
for pn_space in [ Handshake, ApplicationData ]:
if (times[pn_space] != 0 &&
(time == 0 || times[pn_space] < time) &&
# Skip ApplicationData until handshake completion.
(pn_space != ApplicationData ||
IsHandshakeComplete()):
time = times[pn_space];
space = pn_space
GetEarliestTimeAndSpace(times, ack_delay):
time, space = times[Initial], Initial
if (times[Handshake] != 0 &&
(time == 0 || times[Handshake] < time)):
time, space = times[Handshake], Handshake
# Skip ApplicationData until handshake completion.
if (!IsHandshakeComplete()):
return time, space
# Include ack_delay in ApplicationData.
if (times[ApplicationData] != 0 &&
(time == 0 ||
times[ApplicationData] + ack_delay < time)):
return times[ApplicationData] + ack_delay, ApplicationData
return time, space

PeerCompletedAddressValidation():
Expand All @@ -1270,7 +1272,7 @@ PeerCompletedAddressValidation():
has received HANDSHAKE_DONE

SetLossDetectionTimer():
earliest_loss_time, _ = GetEarliestTimeAndSpace(loss_time)
earliest_loss_time, _ = GetEarliestTimeAndSpace(loss_time, 0)
if (earliest_loss_time != 0):
// Time threshold loss detection.
ianswett marked this conversation as resolved.
Show resolved Hide resolved
loss_detection_timer.update(earliest_loss_time)
Expand All @@ -1291,7 +1293,7 @@ SetLossDetectionTimer():

// Determine which PN space to arm PTO for.
sent_time, pn_space = GetEarliestTimeAndSpace(
time_of_last_sent_ack_eliciting_packet)
time_of_last_sent_ack_eliciting_packet, max_ack_delay)
// Don't arm PTO for ApplicationData until handshake complete.
if (pn_space == ApplicationData &&
handshake is not confirmed):
Expand All @@ -1301,9 +1303,8 @@ SetLossDetectionTimer():
assert(!PeerCompletedAddressValidation())
sent_time = now()

// Calculate PTO duration
timeout = smoothed_rtt + max(4 * rttvar, kGranularity) +
max_ack_delay
// Calculate PTO duration, sent_time includes max_ack_delay.
timeout = smoothed_rtt + max(4 * rttvar, kGranularity)
Comment on lines +1306 to +1307
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that it was great before, but I think that this change obfuscates things.

I think that the inclusion of the max_ack_delay argument overloads GetEarliestTimeAndSpace in a way that just highlights how the dual purpose to which the function is applied is not appropriate.

We use this function for working out two things:

  • when to declare something lost
  • when the PTO timer should start

The former is not affected by max_ack_delay. The PTO calculation is, but this change adds max_ack_delay to the baseline rather than the PTO calculation itself.

For one, that's a fundamental change in logic for one (because PTO number 2 used to include 2*max_ack_delay and now it doesn't. Maybe that was a bug that this fixes, but that needs to be clear.

However, the main problem is the way that this code now communicates its intent. If the intent is to start with a baseline (t_sent) and to add a PTO (RTT + 4*rttvar) that is then doubled each time, and only then compensate for ACK delay, you want to show your work, not hide it:

pto_timer = smoothed_rtt + max(4 * rttvar, kGranularity)
pto_timer *= 1 << pto_count
loss_detection_timer.update(sent_time + pto_timer + ?max_ack_delay)

Or is it, with maybe a fix for the packet number space issue...

pto_timer = smoothed_rtt + max(4 * rttvar, kGranularity) + ?max_ack_delay
pto_timer *= 1 << pto_count
loss_detection_timer.update(sent_time + pto_timer)

The trick is that the code that triggers SendOneOrTwoAckElicitingPackets() needs to follow the same logic. But that too is too clever. Basing that on the reference time (the time you sent the last packet) rather than the time that the timer is expected to pop is obtuse (even if it is correct as you currently formulate it).

Why not provide a CalculatePtoTime() function and use that? This code doesn't need to be optimized, so you can calculate everything always.

pto_time = { Initial: 0, Handshake: 0, ApplicationData: 0 };
for space in time_of_last_sent_ack_eliciting_packet:
    if time_of_last_sent_ack_eliciting_packet[space] == 0:
        continue;
    t = smoothed_rtt + max(4 * rttvar, kGranularity)
    t = t * (2 ^ pto_count)
    if space == ApplicationData: # or move this up as you need
        t += max_ack_delay
    pto_time[space] = time_of_last_sent_ack_eliciting_packet[space] + t
pto_time, pn_space = GetEarliestTimeAndSpace(pto_time)

This needs tweaking for the case where sent_time == 0, but that is just a case of factoring out the PTO interval calculation. Note that you aren't adding max_ack_delay in that case, which is another difference.

timeout = timeout * (2 ^ pto_count)

loss_detection_timer.update(sent_time + timeout)
Expand All @@ -1320,7 +1321,7 @@ Pseudocode for OnLossDetectionTimeout follows:
~~~
OnLossDetectionTimeout():
earliest_loss_time, pn_space =
GetEarliestTimeAndSpace(loss_time)
GetEarliestTimeAndSpace(loss_time, 0)
if (earliest_loss_time != 0):
// Time threshold loss Detection
lost_packets = DetectLostPackets(pn_space)
Expand All @@ -1333,7 +1334,7 @@ OnLossDetectionTimeout():
// PTO. Send new data if available, else retransmit old data.
// If neither is available, send a single PING frame.
_, pn_space = GetEarliestTimeAndSpace(
time_of_last_sent_ack_eliciting_packet)
time_of_last_sent_ack_eliciting_packet, max_ack_delay)
SendOneOrTwoAckElicitingPackets(pn_space)
else:
assert(endpoint is client without 1-RTT keys)
Expand Down