quic_loss (quic v1.3.0)
View SourceQUIC loss detection implementation.
This module implements: - Packet loss detection using time and packet thresholds - RTT estimation (smoothed RTT, RTT variance) - Probe Timeout (PTO) calculation - Loss detection timer management
Loss Detection Methods
1. Packet Threshold: A packet is lost if a packet sent more than kPacketThreshold (3) later has been acknowledged.
2. Time Threshold: A packet is lost if it was sent more than max(kTimeThreshold * smoothed_rtt, kGranularity) ago and a later packet has been acknowledged.
Summary
Functions
Get bytes currently in flight.
Detect lost packets based on time and packet thresholds. Scans the sent queue head-to-tail (oldest first) and splits into {Lost, Surviving}. Returns the new loss_state and the lost packets.
Get the loss time for setting timers. The queue is oldest-first, so the earliest in_flight packet is at the head; this turns the previous O(n) map fold into an O(1) head peek in the common case (head is in_flight).
Calculate the Probe Timeout. PTO = smoothed_rtt + max(4 * rttvar, kGranularity) + max_ack_delay
Check if we have received a real RTT sample. Returns false until the first ACK provides a real RTT measurement.
Get the latest RTT sample.
Get the minimum RTT.
Create a new loss detection state.
Create a new loss detection state with options. Options: - max_ack_delay: Maximum ACK delay (default: 25ms) - initial_rtt: Initial RTT estimate in ms (default: 100ms)
Get the oldest unacked packet (for PTO probe selection). Returns {ok, #sent_packet{}} or none. Head of the sent queue is by construction the oldest in-flight packet.
Process an ACK frame. Returns {NewState, AckedPackets, LostPackets, AckMeta} or {error, ack_range_too_large} AckMeta is a map containing: - acked_bytes: total bytes from ack-eliciting packets that were acknowledged - largest_ae_time: sent_time of the largest ack-eliciting packet acknowledged
Record that a packet was sent (without frames).
Record that a packet was sent with frames. Samples the send time itself. Callers that already hold a Now should use on_packet_sent/6 to avoid a duplicate monotonic_time/1 BIF call.
Like on_packet_sent/5 but uses the caller-supplied monotonic millisecond timestamp. The connection send loop reuses one Now per packet for both loss tracking and last_activity, saving a BIF call.
Handle PTO expiration.
Get current PTO count.
Filter frames to get only retransmittable ones. Per RFC 9002, PADDING, ACK, and CONNECTION_CLOSE frames are not retransmitted.
Get the RTT variance.
Get all sent packets. Returned as a map for API compatibility. Built on demand from the queue; intended for tests and diagnostics, not the hot path.
Get the smoothed RTT.
Update RTT estimates with a new sample.
Types
Functions
-spec bytes_in_flight(loss_state()) -> non_neg_integer().
Get bytes currently in flight.
-spec detect_lost_packets(loss_state(), non_neg_integer()) -> {loss_state(), [#sent_packet{pn :: non_neg_integer(), time_sent :: non_neg_integer(), ack_eliciting :: boolean(), in_flight :: boolean(), size :: non_neg_integer(), frames :: [term()]}]}.
Detect lost packets based on time and packet thresholds. Scans the sent queue head-to-tail (oldest first) and splits into {Lost, Surviving}. Returns the new loss_state and the lost packets.
-spec get_loss_time_and_space(loss_state()) -> {non_neg_integer() | undefined, atom()}.
Get the loss time for setting timers. The queue is oldest-first, so the earliest in_flight packet is at the head; this turns the previous O(n) map fold into an O(1) head peek in the common case (head is in_flight).
-spec get_pto(loss_state()) -> non_neg_integer().
Calculate the Probe Timeout. PTO = smoothed_rtt + max(4 * rttvar, kGranularity) + max_ack_delay
-spec has_rtt_sample(loss_state()) -> boolean().
Check if we have received a real RTT sample. Returns false until the first ACK provides a real RTT measurement.
-spec latest_rtt(loss_state()) -> non_neg_integer().
Get the latest RTT sample.
-spec min_rtt(loss_state()) -> non_neg_integer() | infinity.
Get the minimum RTT.
-spec new() -> loss_state().
Create a new loss detection state.
-spec new(map()) -> loss_state().
Create a new loss detection state with options. Options: - max_ack_delay: Maximum ACK delay (default: 25ms) - initial_rtt: Initial RTT estimate in ms (default: 100ms)
-spec oldest_unacked(loss_state()) -> {ok, #sent_packet{pn :: non_neg_integer(), time_sent :: non_neg_integer(), ack_eliciting :: boolean(), in_flight :: boolean(), size :: non_neg_integer(), frames :: [term()]}} | none.
Get the oldest unacked packet (for PTO probe selection). Returns {ok, #sent_packet{}} or none. Head of the sent queue is by construction the oldest in-flight packet.
-spec on_ack_received(loss_state(), term(), non_neg_integer()) -> {loss_state(), [#sent_packet{pn :: non_neg_integer(), time_sent :: non_neg_integer(), ack_eliciting :: boolean(), in_flight :: boolean(), size :: non_neg_integer(), frames :: [term()]}], [#sent_packet{pn :: non_neg_integer(), time_sent :: non_neg_integer(), ack_eliciting :: boolean(), in_flight :: boolean(), size :: non_neg_integer(), frames :: [term()]}], map()} | {error, ack_range_too_large}.
Process an ACK frame. Returns {NewState, AckedPackets, LostPackets, AckMeta} or {error, ack_range_too_large} AckMeta is a map containing: - acked_bytes: total bytes from ack-eliciting packets that were acknowledged - largest_ae_time: sent_time of the largest ack-eliciting packet acknowledged
Implementation: three passes over the sent queue. 1. classify_ack_q: split queue into (acked, kept-unacked) by the ACK ranges in a single head-to-tail walk. Stops early once we pass LargestAcked. 2. maybe_update_rtt: RTT sample derived from the largest acked ack-eliciting packet, if present. 3. detect_lost_q: over the kept survivors, apply packet-threshold and time-threshold loss criteria using the freshly updated SRTT.
-spec on_packet_sent(loss_state(), non_neg_integer(), non_neg_integer(), boolean()) -> loss_state().
Record that a packet was sent (without frames).
-spec on_packet_sent(loss_state(), non_neg_integer(), non_neg_integer(), boolean(), [term()]) -> loss_state().
Record that a packet was sent with frames. Samples the send time itself. Callers that already hold a Now should use on_packet_sent/6 to avoid a duplicate monotonic_time/1 BIF call.
-spec on_packet_sent(loss_state(), non_neg_integer(), non_neg_integer(), boolean(), [term()], integer()) -> loss_state().
Like on_packet_sent/5 but uses the caller-supplied monotonic millisecond timestamp. The connection send loop reuses one Now per packet for both loss tracking and last_activity, saving a BIF call.
-spec on_pto_expired(loss_state()) -> loss_state().
Handle PTO expiration.
-spec pto_count(loss_state()) -> non_neg_integer().
Get current PTO count.
Filter frames to get only retransmittable ones. Per RFC 9002, PADDING, ACK, and CONNECTION_CLOSE frames are not retransmitted.
-spec rtt_var(loss_state()) -> non_neg_integer().
Get the RTT variance.
-spec sent_packets(loss_state()) -> #{non_neg_integer() => #sent_packet{pn :: non_neg_integer(), time_sent :: non_neg_integer(), ack_eliciting :: boolean(), in_flight :: boolean(), size :: non_neg_integer(), frames :: [term()]}}.
Get all sent packets. Returned as a map for API compatibility. Built on demand from the queue; intended for tests and diagnostics, not the hot path.
-spec smoothed_rtt(loss_state()) -> non_neg_integer().
Get the smoothed RTT.
-spec update_rtt(loss_state(), non_neg_integer(), non_neg_integer()) -> loss_state().
Update RTT estimates with a new sample.