...
1
2
3
4
5
6
7 package quic
8
9 import (
10 "context"
11 "log/slog"
12 "math"
13 "time"
14 )
15
16
17
18 type ccReno struct {
19 maxDatagramSize int
20
21
22 congestionWindow int
23
24
25
26
27 bytesInFlight int
28
29
30
31 slowStartThreshold int
32
33
34
35 recoveryStartTime time.Time
36
37
38 congestionPendingAcks int
39
40
41
42
43 sendOnePacketInRecovery bool
44
45
46 inRecovery bool
47
48
49
50
51 underutilized bool
52
53
54
55 ackLastLoss time.Time
56
57
58
59
60
61
62 persistentCongestion [numberSpaceCount]struct {
63 start time.Time
64 end time.Time
65 next packetNumber
66 }
67 }
68
69 func newReno(maxDatagramSize int) *ccReno {
70 c := &ccReno{
71 maxDatagramSize: maxDatagramSize,
72 }
73
74
75 c.congestionWindow = min(10*maxDatagramSize, max(14720, c.minimumCongestionWindow()))
76
77
78 c.slowStartThreshold = math.MaxInt
79
80 for space := range c.persistentCongestion {
81 c.persistentCongestion[space].next = -1
82 }
83 return c
84 }
85
86
87
88
89
90
91
92
93
94 func (c *ccReno) canSend() bool {
95 if c.sendOnePacketInRecovery {
96 return true
97 }
98 return c.bytesInFlight+c.maxDatagramSize <= c.congestionWindow
99 }
100
101
102
103
104
105
106
107
108 func (c *ccReno) setUnderutilized(log *slog.Logger, v bool) {
109 if c.underutilized == v {
110 return
111 }
112 oldState := c.state()
113 c.underutilized = v
114 if logEnabled(log, QLogLevelPacket) {
115 logCongestionStateUpdated(log, oldState, c.state())
116 }
117 }
118
119
120 func (c *ccReno) packetSent(now time.Time, log *slog.Logger, space numberSpace, sent *sentPacket) {
121 if !sent.inFlight {
122 return
123 }
124 c.bytesInFlight += sent.size
125 if c.sendOnePacketInRecovery {
126 c.sendOnePacketInRecovery = false
127 }
128 }
129
130
131
132
133
134
135
136
137
138
139
140
141 func (c *ccReno) packetAcked(now time.Time, sent *sentPacket) {
142 if !sent.inFlight {
143 return
144 }
145 c.bytesInFlight -= sent.size
146
147 if c.underutilized {
148
149 return
150 }
151 if sent.time.Before(c.recoveryStartTime) {
152
153
154
155
156 return
157 }
158 c.congestionPendingAcks += sent.size
159 }
160
161
162
163 func (c *ccReno) packetLost(now time.Time, space numberSpace, sent *sentPacket, rtt *rttState) {
164
165
166
167
168
169
170 isValidPersistentCongestionSample := (sent.ackEliciting &&
171 !rtt.firstSampleTime.IsZero() &&
172 !sent.time.Before(rtt.firstSampleTime))
173 if isValidPersistentCongestionSample {
174
175
176 if sent.num != c.persistentCongestion[space].next {
177 c.persistentCongestion[space].start = sent.time
178 }
179 c.persistentCongestion[space].end = sent.time
180 c.persistentCongestion[space].next = sent.num + 1
181 } else {
182
183
184
185 if sent.num == c.persistentCongestion[space].next {
186 c.persistentCongestion[space].next = sent.num + 1
187 }
188 }
189
190 if !sent.inFlight {
191 return
192 }
193 c.bytesInFlight -= sent.size
194 if sent.time.After(c.ackLastLoss) {
195 c.ackLastLoss = sent.time
196 }
197 }
198
199
200 func (c *ccReno) packetBatchEnd(now time.Time, log *slog.Logger, space numberSpace, rtt *rttState, maxAckDelay time.Duration) {
201 if logEnabled(log, QLogLevelPacket) {
202 oldState := c.state()
203 defer func() { logCongestionStateUpdated(log, oldState, c.state()) }()
204 }
205 if !c.ackLastLoss.IsZero() && !c.ackLastLoss.Before(c.recoveryStartTime) {
206
207
208 c.recoveryStartTime = now
209 c.slowStartThreshold = c.congestionWindow / 2
210 c.congestionWindow = max(c.slowStartThreshold, c.minimumCongestionWindow())
211 c.sendOnePacketInRecovery = true
212
213
214 c.congestionPendingAcks = 0
215 c.inRecovery = true
216 } else if c.congestionPendingAcks > 0 {
217
218 c.inRecovery = false
219 if c.congestionWindow < c.slowStartThreshold {
220
221
222
223 d := min(c.slowStartThreshold-c.congestionWindow, c.congestionPendingAcks)
224 c.congestionWindow += d
225 c.congestionPendingAcks -= d
226 }
227
228
229
230
231
232
233
234 for c.congestionPendingAcks > c.congestionWindow {
235 c.congestionPendingAcks -= c.congestionWindow
236 c.congestionWindow += c.maxDatagramSize
237 }
238 }
239 if !c.ackLastLoss.IsZero() {
240
241
242
243
244
245
246
247
248 const persistentCongestionThreshold = 3
249 d := (rtt.smoothedRTT + max(4*rtt.rttvar, timerGranularity) + maxAckDelay) *
250 persistentCongestionThreshold
251 start := c.persistentCongestion[space].start
252 end := c.persistentCongestion[space].end
253 if end.Sub(start) >= d {
254 c.congestionWindow = c.minimumCongestionWindow()
255 c.recoveryStartTime = time.Time{}
256 rtt.establishPersistentCongestion()
257 }
258 }
259 c.ackLastLoss = time.Time{}
260 }
261
262
263 func (c *ccReno) packetDiscarded(sent *sentPacket) {
264
265 if sent.inFlight {
266 c.bytesInFlight -= sent.size
267 }
268 }
269
270 func (c *ccReno) minimumCongestionWindow() int {
271
272 return 2 * c.maxDatagramSize
273 }
274
275 func logCongestionStateUpdated(log *slog.Logger, oldState, newState congestionState) {
276 if oldState == newState {
277 return
278 }
279 log.LogAttrs(context.Background(), QLogLevelPacket,
280 "recovery:congestion_state_updated",
281 slog.String("old", oldState.String()),
282 slog.String("new", newState.String()),
283 )
284 }
285
286 type congestionState string
287
288 func (s congestionState) String() string { return string(s) }
289
290 const (
291 congestionSlowStart = congestionState("slow_start")
292 congestionCongestionAvoidance = congestionState("congestion_avoidance")
293 congestionApplicationLimited = congestionState("application_limited")
294 congestionRecovery = congestionState("recovery")
295 )
296
297 func (c *ccReno) state() congestionState {
298 switch {
299 case c.inRecovery:
300 return congestionRecovery
301 case c.underutilized:
302 return congestionApplicationLimited
303 case c.congestionWindow < c.slowStartThreshold:
304 return congestionSlowStart
305 default:
306 return congestionCongestionAvoidance
307 }
308 }
309
View as plain text