Skip to content

ACK_FREQUENCY: max_ack_delay ratchets up by the timer resolution on every exchange, and PTO keeps using the handshake max_ack_delay #6334

Open

Description

Describe the bug

Two related behaviours in the ACK frequency extension, read from source at a01333c (v2.6.1) and still present on main at 9262a4a.

1. max_ack_delay ratchets upward by one timer tick per ACK_FREQUENCY exchange.

  • QuicConnGetAckDelay returns Settings.MaxAckDelayMs + MsQuicLib.TimerResolutionMs when delayed ACKs are on (connection.h:1469 at a01333c; 1470 on main). On Windows TimerResolutionMs is 16 (15 + 1, library.c:501).
  • The ACK_FREQUENCY frame's Requested Max Ack Delay is that value: Frame.RequestedMaxAckDelay = MS_TO_US(QuicConnGetAckDelay(Connection)) (send.c:923; 924 on main).
  • The receiver overwrites its own Settings.MaxAckDelayMs with it (connection.c:5383-5389; 5398-5403 on main).
  • Either endpoint sends ACK_FREQUENCY when it is scheduling-limited and its packet-tolerance hint changes (send.c:1514, QuicConnUpdatePeerPacketTolerance at connection.c:6398).

So when both endpoints send the frame, each one adds a tick to the other's current value: 25 → 41 → 57 → 73 ms, and so on. The value only ever rises. What an endpoint configured is not what its peer ends up using, and the result depends on how many exchanges happened.

2. PTO uses the peer's handshake max_ack_delay, never the value this endpoint requested.

QuicLossDetectionComputeProbeTimeout adds Connection->PeerTransportParams.MaxAckDelay (loss_detection.c:242, same on main). That is the transport parameter from the handshake, and ACK_FREQUENCY never updates it. After the endpoint requests a larger max_ack_delay, the peer may legitimately delay ACKs longer than the PTO allows for. draft-ietf-quic-ack-frequency-14, Section 7, says a sender can use the requested value for PTO, and MUST use the greater of the current and in-flight values while the frame is unacknowledged.

Combined: on a connection that has ratcheted to 57 ms here and 73 ms at the peer, this endpoint's PTO still allows for 41 ms.

Observed

Between two Windows machines running MsQuic 2.6.1 (a01333c, OpenSSL TLS), neither side sets MaxAckDelayMs. QUIC_PARAM_CONN_SETTINGS read on each end at the start and end of 79 streams (a value holds for its connection, which carries several streams) gave the following pairs (sender / receiver of the video datagrams):

Sender Receiver Streams
25 41 66
41 57 3
57 73 10

The receiver always read the sender's value + 16. Values read at the start and end of each stream were equal.

Impact

We could not measure a cost in our data. Across those 79 streams, the rate of datagrams declared lost and then acknowledged (QUIC_DATAGRAM_SEND_ACKNOWLEDGED_SPURIOUS) was 3.7e-3 at 25/41 and 3.3e-3 at 57/73. There was an excess in the 60-90 ms window, where the mismatch would act, but two streams from one run carried all of it. This was a small sample at our rates, and we are not claiming a cost. We are reporting the behaviour because it doesn't follow the draft, and because an application cannot choose its ACK timing: MaxAckDelayMs is overwritten by the peer's frames, and there is no setting for the packet tolerance (related: #2823).

Expected

  • Requested Max Ack Delay reflects what the sender wants the peer to use (for example its configured MaxAckDelayMs), without adding its own timer allowance on every hop.
  • PTO uses the requested max_ack_delay (the maximum of the current value and any in flight) after an ACK_FREQUENCY frame is sent, per draft Section 7.

Also noted

The receive path rejects RequestedMaxAckDelay < TimerResolution as a PROTOCOL_VIOLATION (connection.c:5363; 5377 on main) before the == 0 branch that follows, so that branch cannot be reached. #6212 / #6213 fixed the transport-parameter side of MaxAckDelayMs = 0. We haven't checked whether a sender with MaxAckDelayMs = 0 can still emit a Requested Max Ack Delay of 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions