tiny.quic.connection.recovery
Defined in connection.
API (24)
Actions
Public operations.
Estimator.update: Folds one fresh measurement into what the connection already knows for each ACK that settles a packet the peer owed an answer to.LostRange.acknowledge: Pulls in whichever end of the stretch the settled bytes reach, so an acknowledgment arriving after the loss keeps those bytes from traveling a third time.LostRange.add: Widens the stretch until it reaches overcountbytes fromoffset.LostRange.advance: Dropscountbytes off the front.LostRange.isEmpty: Says whether anything is waiting to travel a second time, so the send path chooses between resending and reaching for new bytes.LostRange.length: Counts the bytes waiting to travel a second time, so the send path sizes a resent frame against it.ackDelayNs: Turns one ACK frame's delay field into nanoseconds, because an ACK frame carries its delay scaled by the sender's own exponent.expired: Says whether elapsed time has already condemned a packet that went out atsent_ns, so the caller settles the time question outright and avoids comparing deadlines itself.lossDeadlineNs: Works out the moment at which elapsed time will condemn a packet that went out atsent_ns, so the space keeps the answer as the deadlinenextTimeoutoffers.lossDelayNs: Works out how long a packet is given before elapsed time condemns it, so the connection hands one delay to every space's detection pass.probeDeadlineNs: Adds the wait to the instant it is measured from so the connection keeps the answer as the probe deadlinenextTimeoutoffers.probePeriodNs: Works out how long to wait for an answer for one space, then doubles that wait once per unanswered probe up to the ceiling.reordered: Says whether enough later packets have been acknowledged to condemn this one, so the detection pass runs this reordering test over each held packet.reportedDelayNs: Works out how much of the gap the peer says it spent before answering.sample: Measures the gap between a packet going out and its answer coming back, so the connection can turn one settled packet into a measurement.
Types and contracts
Public types and contracts.
Estimator: Holds what one connection has learned about how long the path takes, so the loss and probe rules can read its fields onceConnection.roundTriphands one back.LostRange: Marks off the stretch of a stream that a loss has put back in the queue.
Values and defaults
Public values and defaults.
backoff_max: The wait may double sixteen times as probes go unanswered, and past that count the wait holds steady.granularity_ns: One millisecond establishes how fine the timing rules are willing to be, so the loss delay and the probe wait are each held at or above it.initial_rtt_ns: A connection assumes the path costs 333 milliseconds until it measures, so the first probe deadline rests on this value before any acknowledgment arrives.packet_threshold: Three is how far under the largest acknowledged number a packet has to sit before reordering alone condemns it, so the constant fixes how many later packets have to be acknowledged before an earlier one is given up on.probe_packets: Two packets, each one the peer owes an answer to, fix what the connection owes the peer when a probe deadline passes with packets still outstanding.time_threshold_denominatortime_threshold_numerator: Nine eighths of a round trip is held as a numerator beside a denominator of eight, so the constant fixes how much of a round trip a packet is given before elapsed time alone condemns it.
Source
Source: lib/quic/src/connection/recovery/resend.zig:8
/// Marks off the stretch of a stream that a loss has put back in the queue. The sending half of/// stream 0 and each space's CRYPTO stream hold one, and the send path reads it to empty that/// stretch before it takes anything new. Holding a single stretch keeps the bookkeeping to two/// numbers whatever the pattern of loss. Two losses with a settled stretch between them therefore/// widen into one, and the bytes in the middle travel a second time.pub const LostRange = struct { start: u62 = 0, end: u62 = 0, /// Says whether anything is waiting to travel a second time, so the send path chooses between /// resending and reaching for new bytes. pub fn isEmpty(self: LostRange) bool { std.debug.assert(self.start <= self.end); return self.start == self.end; } /// Counts the bytes waiting to travel a second time, so the send path sizes a resent frame /// against it. pub fn length(self: LostRange) u62 { std.debug.assert(self.start <= self.end); return self.end - self.start; } /// Widens the stretch until it reaches over `count` bytes from `offset`. The loss path calls it /// with the stretch each lost frame covered. A call carrying no bytes leaves the stretch as it /// was. pub fn add(self: *LostRange, offset: u62, count: u62) void { std.debug.assert(count <= std.math.maxInt(u62) - offset); if (count == 0) return; const end = offset + count; if (self.isEmpty()) { self.* = .{ .start = offset, .end = end }; } else { self.start = @min(self.start, offset); self.end = @max(self.end, end); } std.debug.assert(self.start <= offset); std.debug.assert(end <= self.end); } /// Pulls in whichever end of the stretch the settled bytes reach, so an acknowledgment arriving /// after the loss keeps those bytes from traveling a third time. Bytes settled in the middle /// change nothing, because a single stretch has no way to hold a hole. A stretch pulled in from /// both ends until it is bare returns to empty. pub fn acknowledge(self: *LostRange, offset: u62, count: u62) void { std.debug.assert(count <= std.math.maxInt(u62) - offset); if (self.isEmpty()) return; const waiting = self.length(); const end = offset + count; if (offset <= self.start and self.start < end) self.start = @min(end, self.end); if (offset < self.end and self.end <= end) self.end = @max(offset, self.start); if (self.start == self.end) self.* = .{}; std.debug.assert(self.start <= self.end); std.debug.assert(self.length() <= waiting); } /// Drops `count` bytes off the front. The send path calls it once a packet carries the resent /// bytes. The count stays within what was waiting. pub fn advance(self: *LostRange, count: u62) void { std.debug.assert(count <= self.length()); self.start += count; if (self.start == self.end) self.* = .{}; std.debug.assert(self.start <= self.end); }};Source: lib/quic/src/connection/recovery/rtt.zig:16
/// Holds what one connection has learned about how long the path takes, so the loss and probe rules/// can read its fields once `Connection.roundTrip` hands one back. The struct keeps the most recent/// measurement, the smallest seen, a smoothed value, a measure of spread, and whether anything has/// been measured yet.pub const Estimator = struct { latest_ns: u64 = 0, minimum_ns: u64 = 0, smoothed_ns: u64 = initial_ns, variation_ns: u64 = initial_ns / 2, sampled: bool = false, /// Folds one fresh measurement into what the connection already knows for each ACK that settles /// a packet the peer owed an answer to. The very first measurement seeds the smallest and the /// smoothed value with itself, and the spread with half of itself. Each later measurement moves /// the smoothed value by an eighth and the spread by a quarter, so a single odd measurement /// cannot swing either far. The caller settles the peer's delay before it calls, so the figure /// arriving here has already met the cap and been reduced to zero when the space demands it. /// That delay comes off the measurement only while the answer stays at or above the smallest /// seen. Each product and sum holds at the maximum once it reaches it. pub fn update(self: *Estimator, latest_ns: u64, delay_ns: u64) void { std.debug.assert(latest_ns != 0); self.latest_ns = latest_ns; if (!self.sampled) { self.sampled = true; self.minimum_ns = latest_ns; self.smoothed_ns = latest_ns; self.variation_ns = latest_ns / 2; return; } self.minimum_ns = @min(self.minimum_ns, latest_ns); const adjusted_ns = adjusted(latest_ns, self.minimum_ns, delay_ns); const deviation_ns = difference(self.smoothed_ns, adjusted_ns); self.variation_ns = (self.variation_ns *| 3 +| deviation_ns) / 4; self.smoothed_ns = (self.smoothed_ns *| 7 +| adjusted_ns) / 8; }};Source: lib/quic/src/connection/recovery/loss.zig:39
/// Works out the moment at which elapsed time will condemn a packet that went out at `sent_ns`, so/// the space keeps the answer as the deadline `nextTimeout` offers. The calculation adds the delay/// to the send time. A sum that runs over stops at the largest u64 value.pub fn deadlineNs(sent_ns: u64, delay_ns: u64) u64 { return std.math.add(u64, sent_ns, delay_ns) catch std.math.maxInt(u64);}Source: lib/quic/src/connection/recovery/loss.zig:20
/// Works out how long a packet is given before elapsed time condemns it, so the connection hands/// one delay to every space's detection pass. The calculation takes whichever of the two estimates/// is larger and stretches it by nine eighths. The answer stays at or above the one millisecond/// floor under the timing rules, the timer granularity. An estimate large enough to run the product/// over stops at the largest u64 value.pub fn delayNs(latest_ns: u64, smoothed_ns: u64) u64 { const base = @max(latest_ns, smoothed_ns); const scaled = @as(u128, base) * time_threshold_numerator / time_threshold_denominator; const bounded = std.math.cast(u64, scaled) orelse std.math.maxInt(u64); return @max(bounded, rtt.granularity_ns);}Source: lib/quic/src/connection/recovery/loss.zig:46
/// Says whether elapsed time has already condemned a packet that went out at `sent_ns`, so the/// caller settles the time question outright and avoids comparing deadlines itself. The answer/// turns at the deadline itself, so a packet stands condemned from that moment on.pub fn expired(sent_ns: u64, now_ns: u64, delay_ns: u64) bool { return now_ns >= deadlineNs(sent_ns, delay_ns);}Source: lib/quic/src/connection/recovery/loss.zig:7
/// Three is how far under the largest acknowledged number a packet has to sit before reordering/// alone condemns it, so the constant fixes how many later packets have to be acknowledged before/// an earlier one is given up on. `reordered` measures that distance against it.pub const packet_threshold: u62 = 3;Source: lib/quic/src/connection/recovery/loss.zig:31
/// Says whether enough later packets have been acknowledged to condemn this one, so the detection/// pass runs this reordering test over each held packet. Three is enough to give up on a packet/// sitting that far under the largest acknowledged number. A number above the largest acknowledged/// one is safe.pub fn reordered(packet_number: u62, largest_acknowledged: u62) bool { if (largest_acknowledged < packet_number) return false; return largest_acknowledged - packet_number >= packet_threshold;}Source: lib/quic/src/connection/recovery/loss.zig:13
pub const time_threshold_denominator: u64 = 8;Source: lib/quic/src/connection/recovery/loss.zig:12
/// Nine eighths of a round trip is held as a numerator beside a denominator of eight, so the/// constant fixes how much of a round trip a packet is given before elapsed time alone condemns it./// `delayNs` scales the larger of the two estimates by that fraction.pub const time_threshold_numerator: u64 = 9;Source: lib/quic/src/connection/recovery/pto.zig:7
/// The wait may double sixteen times as probes go unanswered, and past that count the wait holds/// steady. Holding the wait there keeps the shift under the 64 bits the period is held in, which a/// compile-time check enforces.pub const backoff_max: u32 = 16;Source: lib/quic/src/connection/recovery/pto.zig:39
/// Adds the wait to the instant it is measured from so the connection keeps the answer as the probe/// deadline `nextTimeout` offers. A sum that runs over stops at the largest u64 value. The instant/// measured from is when the space last sent a packet the peer owes an answer to, or, with nothing/// outstanding, the anchor the anti-deadlock probe holds.pub fn deadlineNs(sent_ns: u64, period_ns: u64) u64 { const deadline_ns = std.math.add(u64, sent_ns, period_ns) catch std.math.maxInt(u64); std.debug.assert(deadline_ns >= sent_ns); return deadline_ns;}Source: lib/quic/src/connection/recovery/pto.zig:25
/// Works out how long to wait for an answer for one space, then doubles that wait once per/// unanswered probe up to the ceiling. The connection works out one wait per space and arms the/// soonest deadline they produce. The wait itself is the smoothed estimate, plus four times the/// variation, plus whatever delay the peer has told this endpoint to expect. The variation term is/// held at or above the one millisecond floor under the timing rules, the timer granularity. Only/// the application space passes a nonzero delay, and only after the handshake is confirmed, so/// every other caller passes zero. Each sum and each shift holds at the maximum once it reaches it.pub fn periodNs(smoothed_ns: u64, variation_ns: u64, max_ack_delay_ns: u64, count: u32) u64 { const variance_ns = @max(variation_ns *| 4, rtt.granularity_ns); const base_ns = smoothed_ns +| variance_ns +| max_ack_delay_ns; std.debug.assert(base_ns >= rtt.granularity_ns); const shift: u6 = @intCast(@min(count, backoff_max)); const period_ns = base_ns <<| shift; std.debug.assert(period_ns >= base_ns); return period_ns;}Source: lib/quic/src/connection/recovery/pto.zig:16
/// Two packets, each one the peer owes an answer to, fix what the connection owes the peer when a/// probe deadline passes with packets still outstanding. With nothing outstanding the connection/// owes one.pub const probe_packets: u8 = 2;Source: lib/quic/src/connection/recovery/rtt.zig:60
/// Turns one ACK frame's delay field into nanoseconds, because an ACK frame carries its delay/// scaled by the sender's own exponent. The field counts microseconds shifted left by the peer's/// exponent. A figure too large for 64 bits stops at the largest u64 value.pub fn delayNs(encoded: u62, exponent: u5) u64 { const microseconds = @as(u128, encoded) << exponent; const nanoseconds = microseconds * std.time.ns_per_us; return std.math.cast(u64, nanoseconds) orelse std.math.maxInt(u64);}Source: lib/quic/src/connection/recovery/rtt.zig:10
/// A connection assumes the path costs 333 milliseconds until it measures, so the first probe/// deadline rests on this value before any acknowledgment arrives. A fresh estimator opens with/// that duration as its smoothed value and half of it as its variation.pub const initial_ns: u64 = 333 * std.time.ns_per_ms;Source: lib/quic/src/connection/recovery/rtt.zig:71
/// Works out how much of the gap the peer says it spent before answering. The call settles the two/// questions that decide whether an ACK's delay counts at all, so the caller hands the answer/// straight to `update`. Initial and Handshake acknowledgments carry no delay field, and the caller/// passes false to say so, so the call yields zero. A cap applies only after the handshake is/// confirmed, so the caller passes none before then.pub fn reportedDelayNs(reported: bool, encoded: u62, exponent: u5, maximum_ns: ?u64) u64 { if (!reported) return 0; const delay_ns = delayNs(encoded, exponent); const maximum = maximum_ns orelse return delay_ns; return @min(delay_ns, maximum);}Source: lib/quic/src/connection/recovery/rtt.zig:52
/// Measures the gap between a packet going out and its answer coming back, so the connection can/// turn one settled packet into a measurement. A clock that has not moved yields nothing to/// measure, and the call offers none.pub fn sample(sent_ns: u64, received_ns: u64) ?u64 { if (received_ns <= sent_ns) return null; return received_ns - sent_ns;}Source: lib/quic/src/connection/recovery/root.zig
const loss = @import("loss.zig");const pto = @import("pto.zig");const resend = @import("resend.zig");const rtt = @import("rtt.zig");pub const granularity_ns = rtt.granularity_ns;pub const initial_rtt_ns = rtt.initial_ns;pub const Estimator = rtt.Estimator;pub const sample = rtt.sample;pub const ackDelayNs = rtt.delayNs;pub const reportedDelayNs = rtt.reportedDelayNs;pub const packet_threshold = loss.packet_threshold;pub const time_threshold_numerator = loss.time_threshold_numerator;pub const time_threshold_denominator = loss.time_threshold_denominator;pub const lossDelayNs = loss.delayNs;pub const reordered = loss.reordered;pub const lossDeadlineNs = loss.deadlineNs;pub const expired = loss.expired;pub const backoff_max = pto.backoff_max;pub const probe_packets = pto.probe_packets;pub const probePeriodNs = pto.periodNs;pub const probeDeadlineNs = pto.deadlineNs;pub const LostRange = resend.LostRange;Source: lib/quic/src/connection/root.zig:12
pub const recovery = @import("recovery/root.zig");Source: lib/quic/src/connection/recovery/rtt.zig:5
/// One millisecond establishes how fine the timing rules are willing to be, so the loss delay and/// the probe wait are each held at or above it.pub const granularity_ns: u64 = std.time.ns_per_ms;Audit
| Definitions | 25 |
|---|---|
| Public names | 25 |
| Members | 7 |
| Version | 26.7.0 |
| Revision | daab053ee433 |