Skip to content

Commit 7e6923f

Browse files
committed
quic: add RTT estimator
Implement the round-trip time estimation algorithm from RFC 9002, Section 5. For golang/go#58547 Change-Id: I494e692e710f77270c9ad28354366f384feb4ac7 Reviewed-on: https://go-review.googlesource.com/c/net/+/499286 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Damien Neil <dneil@google.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
1 parent 2796e09 commit 7e6923f

File tree

3 files changed

+255
-0
lines changed

3 files changed

+255
-0
lines changed

internal/quic/math.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.21
6+
7+
package quic
8+
9+
func abs[T ~int | ~int64](a T) T {
10+
if a < 0 {
11+
return -a
12+
}
13+
return a
14+
}

internal/quic/rtt.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.21
6+
7+
package quic
8+
9+
import (
10+
"time"
11+
)
12+
13+
type rttState struct {
14+
minRTT time.Duration
15+
latestRTT time.Duration
16+
smoothedRTT time.Duration
17+
rttvar time.Duration // RTT variation
18+
firstSampleTime time.Time // time of first RTT sample
19+
}
20+
21+
func (r *rttState) init() {
22+
r.minRTT = -1 // -1 indicates the first sample has not been taken yet
23+
24+
// "[...] the initial RTT SHOULD be set to 333 milliseconds."
25+
// https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2-1
26+
const initialRTT = 333 * time.Millisecond
27+
28+
// https://www.rfc-editor.org/rfc/rfc9002.html#section-5.3-12
29+
r.smoothedRTT = initialRTT
30+
r.rttvar = initialRTT / 2
31+
}
32+
33+
func (r *rttState) establishPersistentCongestion() {
34+
// "Endpoints SHOULD set the min_rtt to the newest RTT sample
35+
// after persistent congestion is established."
36+
// https://www.rfc-editor.org/rfc/rfc9002#section-5.2-5
37+
r.minRTT = r.latestRTT
38+
}
39+
40+
// updateRTTSample is called when we generate a new RTT sample.
41+
// https://www.rfc-editor.org/rfc/rfc9002.html#section-5
42+
func (r *rttState) updateSample(now time.Time, handshakeConfirmed bool, spaceID numberSpace, latestRTT, ackDelay, maxAckDelay time.Duration) {
43+
r.latestRTT = latestRTT
44+
45+
if r.minRTT < 0 {
46+
// First RTT sample.
47+
// "min_rtt MUST be set to the latest_rtt on the first RTT sample."
48+
// https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-2
49+
r.minRTT = latestRTT
50+
// https://www.rfc-editor.org/rfc/rfc9002.html#section-5.3-14
51+
r.smoothedRTT = latestRTT
52+
r.rttvar = latestRTT / 2
53+
r.firstSampleTime = now
54+
return
55+
}
56+
57+
// "min_rtt MUST be set to the lesser of min_rtt and latest_rtt [...]
58+
// on all other samples."
59+
// https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-2
60+
r.minRTT = min(r.minRTT, latestRTT)
61+
62+
// https://www.rfc-editor.org/rfc/rfc9002.html#section-5.3-16
63+
if handshakeConfirmed {
64+
ackDelay = min(ackDelay, maxAckDelay)
65+
}
66+
adjustedRTT := latestRTT - ackDelay
67+
if adjustedRTT < r.minRTT {
68+
adjustedRTT = latestRTT
69+
}
70+
r.smoothedRTT = ((7 * r.smoothedRTT) + adjustedRTT) / 8
71+
rttvarSample := abs(r.smoothedRTT - adjustedRTT)
72+
r.rttvar = (3*r.rttvar + rttvarSample) / 4
73+
}

internal/quic/rtt_test.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.21
6+
7+
package quic
8+
9+
import (
10+
"testing"
11+
"time"
12+
)
13+
14+
func TestRTTMinRTT(t *testing.T) {
15+
var (
16+
handshakeConfirmed = false
17+
ackDelay = 0 * time.Millisecond
18+
maxAckDelay = 25 * time.Millisecond
19+
now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
20+
)
21+
rtt := &rttState{}
22+
rtt.init()
23+
24+
// "min_rtt MUST be set to the latest_rtt on the first RTT sample."
25+
// https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-2
26+
rtt.updateSample(now, handshakeConfirmed, initialSpace, 10*time.Millisecond, ackDelay, maxAckDelay)
27+
if got, want := rtt.latestRTT, 10*time.Millisecond; got != want {
28+
t.Errorf("on first sample: latest_rtt = %v, want %v", got, want)
29+
}
30+
if got, want := rtt.minRTT, 10*time.Millisecond; got != want {
31+
t.Errorf("on first sample: min_rtt = %v, want %v", got, want)
32+
}
33+
34+
// "min_rtt MUST be set to the lesser of min_rtt and latest_rtt [...]
35+
// on all other samples."
36+
rtt.updateSample(now, handshakeConfirmed, initialSpace, 20*time.Millisecond, ackDelay, maxAckDelay)
37+
if got, want := rtt.latestRTT, 20*time.Millisecond; got != want {
38+
t.Errorf("on increasing sample: latest_rtt = %v, want %v", got, want)
39+
}
40+
if got, want := rtt.minRTT, 10*time.Millisecond; got != want {
41+
t.Errorf("on increasing sample: min_rtt = %v, want %v (no change)", got, want)
42+
}
43+
44+
rtt.updateSample(now, handshakeConfirmed, initialSpace, 5*time.Millisecond, ackDelay, maxAckDelay)
45+
if got, want := rtt.latestRTT, 5*time.Millisecond; got != want {
46+
t.Errorf("on new minimum: latest_rtt = %v, want %v", got, want)
47+
}
48+
if got, want := rtt.minRTT, 5*time.Millisecond; got != want {
49+
t.Errorf("on new minimum: min_rtt = %v, want %v", got, want)
50+
}
51+
52+
// "Endpoints SHOULD set the min_rtt to the newest RTT sample
53+
// after persistent congestion is established."
54+
// https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-5
55+
rtt.updateSample(now, handshakeConfirmed, initialSpace, 15*time.Millisecond, ackDelay, maxAckDelay)
56+
if got, want := rtt.latestRTT, 15*time.Millisecond; got != want {
57+
t.Errorf("on increasing sample: latest_rtt = %v, want %v", got, want)
58+
}
59+
if got, want := rtt.minRTT, 5*time.Millisecond; got != want {
60+
t.Errorf("on increasing sample: min_rtt = %v, want %v (no change)", got, want)
61+
}
62+
rtt.establishPersistentCongestion()
63+
if got, want := rtt.minRTT, 15*time.Millisecond; got != want {
64+
t.Errorf("after persistent congestion: min_rtt = %v, want %v", got, want)
65+
}
66+
}
67+
68+
func TestRTTInitialRTT(t *testing.T) {
69+
var (
70+
handshakeConfirmed = false
71+
ackDelay = 0 * time.Millisecond
72+
maxAckDelay = 25 * time.Millisecond
73+
now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
74+
)
75+
rtt := &rttState{}
76+
rtt.init()
77+
78+
// "When no previous RTT is available,
79+
// the initial RTT SHOULD be set to 333 milliseconds."
80+
// https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-1
81+
if got, want := rtt.smoothedRTT, 333*time.Millisecond; got != want {
82+
t.Errorf("initial smoothed_rtt = %v, want %v", got, want)
83+
}
84+
if got, want := rtt.rttvar, 333*time.Millisecond/2; got != want {
85+
t.Errorf("initial rttvar = %v, want %v", got, want)
86+
}
87+
88+
rtt.updateSample(now, handshakeConfirmed, initialSpace, 10*time.Millisecond, ackDelay, maxAckDelay)
89+
smoothedRTT := 10 * time.Millisecond
90+
if got, want := rtt.smoothedRTT, smoothedRTT; got != want {
91+
t.Errorf("after first rtt sample of 10ms, smoothed_rtt = %v, want %v", got, want)
92+
}
93+
rttvar := 5 * time.Millisecond
94+
if got, want := rtt.rttvar, rttvar; got != want {
95+
t.Errorf("after first rtt sample of 10ms, rttvar = %v, want %v", got, want)
96+
}
97+
98+
// "[...] MAY ignore the acknowledgment delay for Initial packets [...]"
99+
// https://www.rfc-editor.org/rfc/rfc9002#section-5.3-7.1
100+
ackDelay = 1 * time.Millisecond
101+
rtt.updateSample(now, handshakeConfirmed, initialSpace, 10*time.Millisecond, ackDelay, maxAckDelay)
102+
adjustedRTT := 10 * time.Millisecond
103+
smoothedRTT = (7*smoothedRTT + adjustedRTT) / 8
104+
if got, want := rtt.smoothedRTT, smoothedRTT; got != want {
105+
t.Errorf("smoothed_rtt = %v, want %v", got, want)
106+
}
107+
rttvarSample := abs(smoothedRTT - adjustedRTT)
108+
rttvar = (3*rttvar + rttvarSample) / 4
109+
if got, want := rtt.rttvar, rttvar; got != want {
110+
t.Errorf("rttvar = %v, want %v", got, want)
111+
}
112+
113+
// "[...] SHOULD ignore the peer's max_ack_delay until the handshake is confirmed [...]"
114+
// https://www.rfc-editor.org/rfc/rfc9002#section-5.3-7.2
115+
ackDelay = 30 * time.Millisecond
116+
maxAckDelay = 25 * time.Millisecond
117+
rtt.updateSample(now, handshakeConfirmed, handshakeSpace, 40*time.Millisecond, ackDelay, maxAckDelay)
118+
adjustedRTT = 10 * time.Millisecond // latest_rtt (40ms) - ack_delay (30ms)
119+
smoothedRTT = (7*smoothedRTT + adjustedRTT) / 8
120+
if got, want := rtt.smoothedRTT, smoothedRTT; got != want {
121+
t.Errorf("smoothed_rtt = %v, want %v", got, want)
122+
}
123+
rttvarSample = abs(smoothedRTT - adjustedRTT)
124+
rttvar = (3*rttvar + rttvarSample) / 4
125+
if got, want := rtt.rttvar, rttvar; got != want {
126+
t.Errorf("rttvar = %v, want %v", got, want)
127+
}
128+
129+
// "[...] MUST use the lesser of the acknowledgment delay and
130+
// the peer's max_ack_delay after the handshake is confirmed [...]"
131+
// https://www.rfc-editor.org/rfc/rfc9002#section-5.3-7.3
132+
ackDelay = 30 * time.Millisecond
133+
maxAckDelay = 25 * time.Millisecond
134+
handshakeConfirmed = true
135+
rtt.updateSample(now, handshakeConfirmed, handshakeSpace, 40*time.Millisecond, ackDelay, maxAckDelay)
136+
adjustedRTT = 15 * time.Millisecond // latest_rtt (40ms) - max_ack_delay (25ms)
137+
smoothedRTT = (7*smoothedRTT + adjustedRTT) / 8
138+
if got, want := rtt.smoothedRTT, smoothedRTT; got != want {
139+
t.Errorf("smoothed_rtt = %v, want %v", got, want)
140+
}
141+
rttvarSample = abs(smoothedRTT - adjustedRTT)
142+
rttvar = (3*rttvar + rttvarSample) / 4
143+
if got, want := rtt.rttvar, rttvar; got != want {
144+
t.Errorf("rttvar = %v, want %v", got, want)
145+
}
146+
147+
// "[...] MUST NOT subtract the acknowledgment delay from
148+
// the RTT sample if the resulting value is smaller than the min_rtt."
149+
// https://www.rfc-editor.org/rfc/rfc9002#section-5.3-7.4
150+
ackDelay = 25 * time.Millisecond
151+
maxAckDelay = 25 * time.Millisecond
152+
handshakeConfirmed = true
153+
rtt.updateSample(now, handshakeConfirmed, handshakeSpace, 30*time.Millisecond, ackDelay, maxAckDelay)
154+
if got, want := rtt.minRTT, 10*time.Millisecond; got != want {
155+
t.Errorf("min_rtt = %v, want %v", got, want)
156+
}
157+
// latest_rtt (30ms) - ack_delay (25ms) = 5ms, which is less than min_rtt (10ms)
158+
adjustedRTT = 30 * time.Millisecond // latest_rtt
159+
smoothedRTT = (7*smoothedRTT + adjustedRTT) / 8
160+
if got, want := rtt.smoothedRTT, smoothedRTT; got != want {
161+
t.Errorf("smoothed_rtt = %v, want %v", got, want)
162+
}
163+
rttvarSample = abs(smoothedRTT - adjustedRTT)
164+
rttvar = (3*rttvar + rttvarSample) / 4
165+
if got, want := rtt.rttvar, rttvar; got != want {
166+
t.Errorf("rttvar = %v, want %v", got, want)
167+
}
168+
}

0 commit comments

Comments
 (0)