nquic_loss (nquic v1.0.0)
View SourceLoss detection per RFC 9002.
Tracks sent packets per packet number space with shared RTT estimation and congestion control. Uses both packet threshold (3 packets) and time threshold (9/8 of max(smoothed_rtt, latest_rtt)) for loss detection. PTO with exponential backoff triggers probes when no ACKs arrive.
Persistent congestion (RFC 9002 Section 7.6) is declared when a contiguous
run of ack-eliciting packets is lost spanning at least
(SRTT + max(4 * RTTvar, kGranularity) + max_ack_delay) * 3 and both
endpoints were sent after the first RTT sample. On detection the congestion
controller's window collapses to 2 * max_datagram_size.
Summary
Functions
Clear the ecn_socket_dirty flag after the caller has applied the transition.
Clear initial and handshake sent-packet maps after handshake is confirmed.
Clear the Initial-space sent-packet buffer after RFC 9001 §4.9.1 key discard.
Run loss detection for a specific packet number space at the given time.
Get the cached count of ack-eliciting packets currently in flight.
Get the total bytes currently in flight across all packet number spaces.
Get the congestion control algorithm from the current loss state.
Get the current congestion window size in bytes.
Get the largest acknowledged packet number for a given space, or 0 if none.
Get the earliest loss detection time across all packet number spaces, or undefined.
Get the current PTO count.
Calculate the probe timeout (microseconds) with exponential backoff.
MaxAckDelayUs must be in microseconds. Callers that hold the peer's
advertised max_ack_delay (milliseconds, per RFC 9000 S18.2) must
convert: MaxAckDelayMs * 1000.
Get the current RTT statistics as a map.
Get all outstanding sent packet numbers across every packet number space.
Get all currently tracked #sent_packet{} records across every packet
number space. Intended for tests and diagnostics; production code should
prefer the dedicated counters (get_bytes_in_flight/1,
has_ack_eliciting_in_flight/1).
Return true if any ack-eliciting packets are currently in flight.
Initialize loss detection state with default RTT and NewReno congestion control.
Initialize loss detection state with the given congestion control algorithm.
Initialize loss detection state.
Opts is a single map carrying both pacer (enabled, factor,
burst_packets) and congestion-control (slow_start) knobs. Callers
that only have pacer config can keep passing a pacer_opts/0 shape;
init/2 deduplicates the two via the structural key set.
Check whether ECN is enabled for this connection.
Cheap predicate for the ecn_socket_dirty flag.
Pure record-field read, no allocation. The flush hot path uses this to
short-circuit out of the rare validation-failure transition without
paying for a tuple return.
Process an ACK frame, returning updated state plus acked and lost frames.
MaxAckDelayUs is the peer's advertised max_ack_delay in microseconds.
It is used to compute the persistent congestion window (RFC 9002 Section
7.6.1). Pass 0 if it is not yet known.
Record a sent packet in the given packet number space.
Increment the PTO count after a probe timeout fires.
Decide whether the pacer admits a send at Now (microsecond
monotonic). Returns pass to send immediately or {block, NextUs}
when the caller must hold off until NextUs. Disabled or in-slow-
start pacers always pass.
Project the pacer + CC configuration as a map suitable for re-passing
to init/2. Used by code paths that re-initialise loss state on path
/ retry transitions and want to preserve pacing and HyStart++ settings.
Disable the pacer. Used by the caller to opt out at runtime.
Whether the pacer is configured on this connection. The pacer may be
present-and-disengaged if cwnd =< ssthresh (still in slow start) or
if the pacing budget has not been built up yet.
Earliest microsecond (monotonic, may be negative on Erlang's
monotonic clock) at which the pacer permits the next send.
undefined if the pacer has never engaged on this connection.
Project a flat path-stats map for nquic_conn:path_stats/1.
Sums per-PN-space ECN counts (peer-reported CE and total) into single
integers so the consumer does not have to know about packet-number
spaces.
Process ECN counts from a received ACK frame (RFC 9002 Section 7.1).
Compares the peer's reported CE count against the last known baseline.
If CE count increased, triggers a congestion event on the CC algorithm.
If total ECN marks decreased (validation failure), disables ECN for the path.
Returns {ok, State} when no CE increase was detected, or
{ok, State} after congestion window reduction when CE increased.
Reset the PTO count to zero after receiving an ACK.
Enable or disable ECN for this connection.
Set the maximum datagram size used by the congestion controller.
Types
-type cc_opts() :: #{slow_start => standard | hystart_plus_plus, _ => _}.
-type loss_state() :: #loss_state{sent_packets :: #{nquic_packet:space() => nquic_pn_buf:buf()}, largest_acked_packet :: #{nquic_packet:space() => nquic_packet_number:t()}, loss_time :: #{nquic_packet:space() => non_neg_integer()}, rtt_state :: nquic_rtt:rtt_state(), cc_state :: dynamic(), bytes_in_flight :: non_neg_integer(), pto_count :: non_neg_integer(), ack_eliciting_in_flight :: non_neg_integer(), first_rtt_sample :: non_neg_integer() | undefined, last_send_time :: non_neg_integer() | undefined, recently_lost :: #{nquic_packet:space() => nquic_pn_buf:buf()}, peer_ecn_ce :: #{nquic_packet:space() => non_neg_integer()}, peer_ecn_total :: #{nquic_packet:space() => non_neg_integer()}, ecn_enabled :: boolean(), ecn_socket_dirty :: boolean(), pacer :: pacer_state()}.
-type pacer_opts() :: #{enabled => boolean(), factor => number(), burst_packets => pos_integer()}.
-type pacer_state() :: #pacer_state{enabled :: boolean(), factor :: number(), burst_packets :: pos_integer(), next_send_time :: undefined | integer()}.
-type path_stats() :: #{srtt_us := non_neg_integer(), rttvar_us := non_neg_integer(), min_rtt_us := non_neg_integer(), latest_rtt_us := non_neg_integer(), cwnd := non_neg_integer(), bytes_in_flight := non_neg_integer(), ssthresh := non_neg_integer() | undefined, mss := pos_integer(), ecn_enabled := boolean(), peer_ecn_ce := non_neg_integer(), peer_ecn_total := non_neg_integer(), pto_count := non_neg_integer()}.
-type sent_packet() :: #sent_packet{packet_number :: nquic_packet_number:t(), time_sent :: non_neg_integer(), size :: non_neg_integer(), ack_eliciting :: boolean(), in_flight :: boolean(), frames :: [nquic_frame:t()]}.
Functions
-spec clear_ecn_socket_dirty(loss_state()) -> loss_state().
Clear the ecn_socket_dirty flag after the caller has applied the transition.
-spec clear_handshake_spaces(loss_state()) -> loss_state().
Clear initial and handshake sent-packet maps after handshake is confirmed.
-spec clear_initial_space(loss_state()) -> loss_state().
Clear the Initial-space sent-packet buffer after RFC 9001 §4.9.1 key discard.
-spec detect_loss(loss_state(), nquic_packet:space(), non_neg_integer()) -> {ok, loss_state(), [nquic_frame:t()]}.
Run loss detection for a specific packet number space at the given time.
-spec get_ack_eliciting_in_flight(loss_state()) -> non_neg_integer().
Get the cached count of ack-eliciting packets currently in flight.
-spec get_bytes_in_flight(loss_state()) -> non_neg_integer().
Get the total bytes currently in flight across all packet number spaces.
-spec get_cc_algorithm(loss_state()) -> atom().
Get the congestion control algorithm from the current loss state.
-spec get_cwnd(loss_state()) -> non_neg_integer().
Get the current congestion window size in bytes.
-spec get_largest_acked(loss_state(), nquic_packet:space()) -> non_neg_integer().
Get the largest acknowledged packet number for a given space, or 0 if none.
-spec get_loss_timer(loss_state()) -> non_neg_integer() | undefined.
Get the earliest loss detection time across all packet number spaces, or undefined.
-spec get_pto_count(loss_state()) -> non_neg_integer().
Get the current PTO count.
-spec get_pto_timeout(loss_state(), non_neg_integer()) -> non_neg_integer().
Calculate the probe timeout (microseconds) with exponential backoff.
MaxAckDelayUs must be in microseconds. Callers that hold the peer's
advertised max_ack_delay (milliseconds, per RFC 9000 S18.2) must
convert: MaxAckDelayMs * 1000.
-spec get_rtt_stats(loss_state()) -> map().
Get the current RTT statistics as a map.
-spec get_sent_packet_numbers(loss_state()) -> [nquic_packet_number:t()].
Get all outstanding sent packet numbers across every packet number space.
-spec get_sent_packets(loss_state()) -> [sent_packet()].
Get all currently tracked #sent_packet{} records across every packet
number space. Intended for tests and diagnostics; production code should
prefer the dedicated counters (get_bytes_in_flight/1,
has_ack_eliciting_in_flight/1).
-spec has_ack_eliciting_in_flight(loss_state()) -> boolean().
Return true if any ack-eliciting packets are currently in flight.
-spec init() -> loss_state().
Initialize loss detection state with default RTT and NewReno congestion control.
-spec init(atom()) -> loss_state().
Initialize loss detection state with the given congestion control algorithm.
-spec init(atom(), pacer_opts() | cc_opts()) -> loss_state().
Initialize loss detection state.
Opts is a single map carrying both pacer (enabled, factor,
burst_packets) and congestion-control (slow_start) knobs. Callers
that only have pacer config can keep passing a pacer_opts/0 shape;
init/2 deduplicates the two via the structural key set.
-spec is_ecn_enabled(loss_state()) -> boolean().
Check whether ECN is enabled for this connection.
-spec is_ecn_socket_dirty(loss_state()) -> boolean().
Cheap predicate for the ecn_socket_dirty flag.
Pure record-field read, no allocation. The flush hot path uses this to
short-circuit out of the rare validation-failure transition without
paying for a tuple return.
-spec on_ack_received(loss_state(), nquic_packet:space(), [{non_neg_integer(), non_neg_integer()}], non_neg_integer(), non_neg_integer(), non_neg_integer()) -> {ok, loss_state(), [nquic_frame:t()], [nquic_frame:t()]}.
Process an ACK frame, returning updated state plus acked and lost frames.
MaxAckDelayUs is the peer's advertised max_ack_delay in microseconds.
It is used to compute the persistent congestion window (RFC 9002 Section
7.6.1). Pass 0 if it is not yet known.
-spec on_packet_sent(loss_state(), nquic_packet:space(), nquic_packet_number:t(), [nquic_frame:t()], non_neg_integer(), non_neg_integer()) -> loss_state().
Record a sent packet in the given packet number space.
-spec on_pto(loss_state()) -> loss_state().
Increment the PTO count after a probe timeout fires.
-spec pacer_check(loss_state() | undefined, integer()) -> pass | {block, integer()}.
Decide whether the pacer admits a send at Now (microsecond
monotonic). Returns pass to send immediately or {block, NextUs}
when the caller must hold off until NextUs. Disabled or in-slow-
start pacers always pass.
-spec pacer_config(loss_state()) -> map().
Project the pacer + CC configuration as a map suitable for re-passing
to init/2. Used by code paths that re-initialise loss state on path
/ retry transitions and want to preserve pacing and HyStart++ settings.
-spec pacer_disable(loss_state()) -> loss_state().
Disable the pacer. Used by the caller to opt out at runtime.
-spec pacer_is_enabled(loss_state()) -> boolean().
Whether the pacer is configured on this connection. The pacer may be
present-and-disengaged if cwnd =< ssthresh (still in slow start) or
if the pacing budget has not been built up yet.
-spec pacer_next_send_time(loss_state()) -> undefined | integer().
Earliest microsecond (monotonic, may be negative on Erlang's
monotonic clock) at which the pacer permits the next send.
undefined if the pacer has never engaged on this connection.
-spec path_stats(loss_state()) -> path_stats().
Project a flat path-stats map for nquic_conn:path_stats/1.
Sums per-PN-space ECN counts (peer-reported CE and total) into single
integers so the consumer does not have to know about packet-number
spaces.
-spec process_ecn_counts(loss_state(), nquic_packet:space(), {non_neg_integer(), non_neg_integer(), non_neg_integer()} | undefined) -> loss_state().
Process ECN counts from a received ACK frame (RFC 9002 Section 7.1).
Compares the peer's reported CE count against the last known baseline.
If CE count increased, triggers a congestion event on the CC algorithm.
If total ECN marks decreased (validation failure), disables ECN for the path.
Returns {ok, State} when no CE increase was detected, or
{ok, State} after congestion window reduction when CE increased.
-spec reset_pto_count(loss_state()) -> loss_state().
Reset the PTO count to zero after receiving an ACK.
-spec set_ecn_enabled(loss_state(), boolean()) -> loss_state().
Enable or disable ECN for this connection.
-spec set_max_datagram_size(loss_state(), pos_integer()) -> loss_state().
Set the maximum datagram size used by the congestion controller.