github.com/daeuniverse/quic-go@v0.0.0-20240413031024-943f218e0810/internal/congestion/cubic_sender_test.go (about) 1 package congestion 2 3 import ( 4 "time" 5 6 "github.com/daeuniverse/quic-go/internal/protocol" 7 "github.com/daeuniverse/quic-go/internal/utils" 8 9 . "github.com/onsi/ginkgo/v2" 10 . "github.com/onsi/gomega" 11 ) 12 13 const ( 14 initialCongestionWindowPackets = 10 15 defaultWindowTCP = protocol.ByteCount(initialCongestionWindowPackets) * maxDatagramSize 16 ) 17 18 type mockClock time.Time 19 20 func (c *mockClock) Now() time.Time { 21 return time.Time(*c) 22 } 23 24 func (c *mockClock) Advance(d time.Duration) { 25 *c = mockClock(time.Time(*c).Add(d)) 26 } 27 28 const MaxCongestionWindow protocol.ByteCount = 200 * maxDatagramSize 29 30 var _ = Describe("Cubic Sender", func() { 31 var ( 32 sender *cubicSender 33 clock mockClock 34 bytesInFlight protocol.ByteCount 35 packetNumber protocol.PacketNumber 36 ackedPacketNumber protocol.PacketNumber 37 rttStats *utils.RTTStats 38 ) 39 40 BeforeEach(func() { 41 bytesInFlight = 0 42 packetNumber = 1 43 ackedPacketNumber = 0 44 clock = mockClock{} 45 rttStats = utils.NewRTTStats() 46 sender = newCubicSender( 47 &clock, 48 rttStats, 49 true, /*reno*/ 50 protocol.InitialPacketSizeIPv4, 51 initialCongestionWindowPackets*maxDatagramSize, 52 MaxCongestionWindow, 53 nil, 54 ) 55 }) 56 57 SendAvailableSendWindowLen := func(packetLength protocol.ByteCount) int { 58 var packetsSent int 59 for sender.CanSend(bytesInFlight) { 60 sender.OnPacketSent(clock.Now(), bytesInFlight, packetNumber, packetLength, true) 61 packetNumber++ 62 packetsSent++ 63 bytesInFlight += packetLength 64 } 65 return packetsSent 66 } 67 68 // Normal is that TCP acks every other segment. 69 AckNPackets := func(n int) { 70 rttStats.UpdateRTT(60*time.Millisecond, 0, clock.Now()) 71 sender.MaybeExitSlowStart() 72 for i := 0; i < n; i++ { 73 ackedPacketNumber++ 74 sender.OnPacketAcked(ackedPacketNumber, maxDatagramSize, bytesInFlight, clock.Now()) 75 } 76 bytesInFlight -= protocol.ByteCount(n) * maxDatagramSize 77 clock.Advance(time.Millisecond) 78 } 79 80 LoseNPacketsLen := func(n int, packetLength protocol.ByteCount) { 81 for i := 0; i < n; i++ { 82 ackedPacketNumber++ 83 sender.OnCongestionEvent(ackedPacketNumber, packetLength, bytesInFlight) 84 } 85 bytesInFlight -= protocol.ByteCount(n) * packetLength 86 } 87 88 // Does not increment acked_packet_number_. 89 LosePacket := func(number protocol.PacketNumber) { 90 sender.OnCongestionEvent(number, maxDatagramSize, bytesInFlight) 91 bytesInFlight -= maxDatagramSize 92 } 93 94 SendAvailableSendWindow := func() int { return SendAvailableSendWindowLen(maxDatagramSize) } 95 LoseNPackets := func(n int) { LoseNPacketsLen(n, maxDatagramSize) } 96 97 It("has the right values at startup", func() { 98 // At startup make sure we are at the default. 99 Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP)) 100 // Make sure we can send. 101 Expect(sender.TimeUntilSend(0)).To(BeZero()) 102 Expect(sender.CanSend(bytesInFlight)).To(BeTrue()) 103 // And that window is un-affected. 104 Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP)) 105 106 // Fill the send window with data, then verify that we can't send. 107 SendAvailableSendWindow() 108 Expect(sender.CanSend(bytesInFlight)).To(BeFalse()) 109 }) 110 111 It("paces", func() { 112 rttStats.UpdateRTT(10*time.Millisecond, 0, time.Now()) 113 clock.Advance(time.Hour) 114 // Fill the send window with data, then verify that we can't send. 115 SendAvailableSendWindow() 116 AckNPackets(1) 117 delay := sender.TimeUntilSend(bytesInFlight) 118 Expect(delay).ToNot(BeZero()) 119 Expect(delay).ToNot(Equal(utils.InfDuration)) 120 }) 121 122 It("application limited slow start", func() { 123 // Send exactly 10 packets and ensure the CWND ends at 14 packets. 124 const numberOfAcks = 5 125 // At startup make sure we can send. 126 Expect(sender.CanSend(0)).To(BeTrue()) 127 Expect(sender.TimeUntilSend(0)).To(BeZero()) 128 129 SendAvailableSendWindow() 130 for i := 0; i < numberOfAcks; i++ { 131 AckNPackets(2) 132 } 133 bytesToSend := sender.GetCongestionWindow() 134 // It's expected 2 acks will arrive when the bytes_in_flight are greater than 135 // half the CWND. 136 Expect(bytesToSend).To(Equal(defaultWindowTCP + maxDatagramSize*2*2)) 137 }) 138 139 It("exponential slow start", func() { 140 const numberOfAcks = 20 141 // At startup make sure we can send. 142 Expect(sender.CanSend(0)).To(BeTrue()) 143 Expect(sender.TimeUntilSend(0)).To(BeZero()) 144 Expect(sender.BandwidthEstimate()).To(Equal(infBandwidth)) 145 // Make sure we can send. 146 Expect(sender.TimeUntilSend(0)).To(BeZero()) 147 148 for i := 0; i < numberOfAcks; i++ { 149 // Send our full send window. 150 SendAvailableSendWindow() 151 AckNPackets(2) 152 } 153 cwnd := sender.GetCongestionWindow() 154 Expect(cwnd).To(Equal(defaultWindowTCP + maxDatagramSize*2*numberOfAcks)) 155 Expect(sender.BandwidthEstimate()).To(Equal(BandwidthFromDelta(cwnd, rttStats.SmoothedRTT()))) 156 }) 157 158 It("slow start packet loss", func() { 159 const numberOfAcks = 10 160 for i := 0; i < numberOfAcks; i++ { 161 // Send our full send window. 162 SendAvailableSendWindow() 163 AckNPackets(2) 164 } 165 SendAvailableSendWindow() 166 expectedSendWindow := defaultWindowTCP + (maxDatagramSize * 2 * numberOfAcks) 167 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 168 169 // Lose a packet to exit slow start. 170 LoseNPackets(1) 171 packetsInRecoveryWindow := expectedSendWindow / maxDatagramSize 172 173 // We should now have fallen out of slow start with a reduced window. 174 expectedSendWindow = protocol.ByteCount(float32(expectedSendWindow) * renoBeta) 175 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 176 177 // Recovery phase. We need to ack every packet in the recovery window before 178 // we exit recovery. 179 numberOfPacketsInWindow := expectedSendWindow / maxDatagramSize 180 AckNPackets(int(packetsInRecoveryWindow)) 181 SendAvailableSendWindow() 182 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 183 184 // We need to ack an entire window before we increase CWND by 1. 185 AckNPackets(int(numberOfPacketsInWindow) - 2) 186 SendAvailableSendWindow() 187 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 188 189 // Next ack should increase cwnd by 1. 190 AckNPackets(1) 191 expectedSendWindow += maxDatagramSize 192 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 193 194 // Now RTO and ensure slow start gets reset. 195 Expect(sender.hybridSlowStart.Started()).To(BeTrue()) 196 sender.OnRetransmissionTimeout(true) 197 Expect(sender.hybridSlowStart.Started()).To(BeFalse()) 198 }) 199 200 It("slow start packet loss PRR", func() { 201 // Test based on the first example in RFC6937. 202 // Ack 10 packets in 5 acks to raise the CWND to 20, as in the example. 203 const numberOfAcks = 5 204 for i := 0; i < numberOfAcks; i++ { 205 // Send our full send window. 206 SendAvailableSendWindow() 207 AckNPackets(2) 208 } 209 SendAvailableSendWindow() 210 expectedSendWindow := defaultWindowTCP + (maxDatagramSize * 2 * numberOfAcks) 211 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 212 213 LoseNPackets(1) 214 215 // We should now have fallen out of slow start with a reduced window. 216 sendWindowBeforeLoss := expectedSendWindow 217 expectedSendWindow = protocol.ByteCount(float32(expectedSendWindow) * renoBeta) 218 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 219 220 // Testing TCP proportional rate reduction. 221 // We should send packets paced over the received acks for the remaining 222 // outstanding packets. The number of packets before we exit recovery is the 223 // original CWND minus the packet that has been lost and the one which 224 // triggered the loss. 225 remainingPacketsInRecovery := sendWindowBeforeLoss/maxDatagramSize - 2 226 227 for i := protocol.ByteCount(0); i < remainingPacketsInRecovery; i++ { 228 AckNPackets(1) 229 SendAvailableSendWindow() 230 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 231 } 232 233 // We need to ack another window before we increase CWND by 1. 234 numberOfPacketsInWindow := expectedSendWindow / maxDatagramSize 235 for i := protocol.ByteCount(0); i < numberOfPacketsInWindow; i++ { 236 AckNPackets(1) 237 Expect(SendAvailableSendWindow()).To(Equal(1)) 238 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 239 } 240 241 AckNPackets(1) 242 expectedSendWindow += maxDatagramSize 243 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 244 }) 245 246 It("slow start burst packet loss PRR", func() { 247 // Test based on the second example in RFC6937, though we also implement 248 // forward acknowledgements, so the first two incoming acks will trigger 249 // PRR immediately. 250 // Ack 20 packets in 10 acks to raise the CWND to 30. 251 const numberOfAcks = 10 252 for i := 0; i < numberOfAcks; i++ { 253 // Send our full send window. 254 SendAvailableSendWindow() 255 AckNPackets(2) 256 } 257 SendAvailableSendWindow() 258 expectedSendWindow := defaultWindowTCP + (maxDatagramSize * 2 * numberOfAcks) 259 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 260 261 // Lose one more than the congestion window reduction, so that after loss, 262 // bytes_in_flight is lesser than the congestion window. 263 sendWindowAfterLoss := protocol.ByteCount(renoBeta * float32(expectedSendWindow)) 264 numPacketsToLose := (expectedSendWindow-sendWindowAfterLoss)/maxDatagramSize + 1 265 LoseNPackets(int(numPacketsToLose)) 266 // Immediately after the loss, ensure at least one packet can be sent. 267 // Losses without subsequent acks can occur with timer based loss detection. 268 Expect(sender.CanSend(bytesInFlight)).To(BeTrue()) 269 AckNPackets(1) 270 271 // We should now have fallen out of slow start with a reduced window. 272 expectedSendWindow = protocol.ByteCount(float32(expectedSendWindow) * renoBeta) 273 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 274 275 // Only 2 packets should be allowed to be sent, per PRR-SSRB 276 Expect(SendAvailableSendWindow()).To(Equal(2)) 277 278 // Ack the next packet, which triggers another loss. 279 LoseNPackets(1) 280 AckNPackets(1) 281 282 // Send 2 packets to simulate PRR-SSRB. 283 Expect(SendAvailableSendWindow()).To(Equal(2)) 284 285 // Ack the next packet, which triggers another loss. 286 LoseNPackets(1) 287 AckNPackets(1) 288 289 // Send 2 packets to simulate PRR-SSRB. 290 Expect(SendAvailableSendWindow()).To(Equal(2)) 291 292 // Exit recovery and return to sending at the new rate. 293 for i := 0; i < numberOfAcks; i++ { 294 AckNPackets(1) 295 Expect(SendAvailableSendWindow()).To(Equal(1)) 296 } 297 }) 298 299 It("RTO congestion window", func() { 300 Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP)) 301 Expect(sender.slowStartThreshold).To(Equal(protocol.MaxByteCount)) 302 303 // Expect the window to decrease to the minimum once the RTO fires 304 // and slow start threshold to be set to 1/2 of the CWND. 305 sender.OnRetransmissionTimeout(true) 306 Expect(sender.GetCongestionWindow()).To(Equal(2 * maxDatagramSize)) 307 Expect(sender.slowStartThreshold).To(Equal(5 * maxDatagramSize)) 308 }) 309 310 It("RTO congestion window no retransmission", func() { 311 Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP)) 312 313 // Expect the window to remain unchanged if the RTO fires but no 314 // packets are retransmitted. 315 sender.OnRetransmissionTimeout(false) 316 Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP)) 317 }) 318 319 It("tcp cubic reset epoch on quiescence", func() { 320 const maxCongestionWindow = 50 321 const maxCongestionWindowBytes = maxCongestionWindow * maxDatagramSize 322 sender = newCubicSender(&clock, rttStats, false, protocol.InitialPacketSizeIPv4, initialCongestionWindowPackets*maxDatagramSize, maxCongestionWindowBytes, nil) 323 324 numSent := SendAvailableSendWindow() 325 326 // Make sure we fall out of slow start. 327 savedCwnd := sender.GetCongestionWindow() 328 LoseNPackets(1) 329 Expect(savedCwnd).To(BeNumerically(">", sender.GetCongestionWindow())) 330 331 // Ack the rest of the outstanding packets to get out of recovery. 332 for i := 1; i < numSent; i++ { 333 AckNPackets(1) 334 } 335 Expect(bytesInFlight).To(BeZero()) 336 337 // Send a new window of data and ack all; cubic growth should occur. 338 savedCwnd = sender.GetCongestionWindow() 339 numSent = SendAvailableSendWindow() 340 for i := 0; i < numSent; i++ { 341 AckNPackets(1) 342 } 343 Expect(savedCwnd).To(BeNumerically("<", sender.GetCongestionWindow())) 344 Expect(maxCongestionWindowBytes).To(BeNumerically(">", sender.GetCongestionWindow())) 345 Expect(bytesInFlight).To(BeZero()) 346 347 // Quiescent time of 100 seconds 348 clock.Advance(100 * time.Second) 349 350 // Send new window of data and ack one packet. Cubic epoch should have 351 // been reset; ensure cwnd increase is not dramatic. 352 savedCwnd = sender.GetCongestionWindow() 353 SendAvailableSendWindow() 354 AckNPackets(1) 355 Expect(savedCwnd).To(BeNumerically("~", sender.GetCongestionWindow(), maxDatagramSize)) 356 Expect(maxCongestionWindowBytes).To(BeNumerically(">", sender.GetCongestionWindow())) 357 }) 358 359 It("multiple losses in one window", func() { 360 SendAvailableSendWindow() 361 initialWindow := sender.GetCongestionWindow() 362 LosePacket(ackedPacketNumber + 1) 363 postLossWindow := sender.GetCongestionWindow() 364 Expect(initialWindow).To(BeNumerically(">", postLossWindow)) 365 LosePacket(ackedPacketNumber + 3) 366 Expect(sender.GetCongestionWindow()).To(Equal(postLossWindow)) 367 LosePacket(packetNumber - 1) 368 Expect(sender.GetCongestionWindow()).To(Equal(postLossWindow)) 369 370 // Lose a later packet and ensure the window decreases. 371 LosePacket(packetNumber) 372 Expect(postLossWindow).To(BeNumerically(">", sender.GetCongestionWindow())) 373 }) 374 375 It("1 connection congestion avoidance at end of recovery", func() { 376 // Ack 10 packets in 5 acks to raise the CWND to 20. 377 const numberOfAcks = 5 378 for i := 0; i < numberOfAcks; i++ { 379 // Send our full send window. 380 SendAvailableSendWindow() 381 AckNPackets(2) 382 } 383 SendAvailableSendWindow() 384 expectedSendWindow := defaultWindowTCP + (maxDatagramSize * 2 * numberOfAcks) 385 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 386 387 LoseNPackets(1) 388 389 // We should now have fallen out of slow start with a reduced window. 390 expectedSendWindow = protocol.ByteCount(float32(expectedSendWindow) * renoBeta) 391 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 392 393 // No congestion window growth should occur in recovery phase, i.e., until the 394 // currently outstanding 20 packets are acked. 395 for i := 0; i < 10; i++ { 396 // Send our full send window. 397 SendAvailableSendWindow() 398 Expect(sender.InRecovery()).To(BeTrue()) 399 AckNPackets(2) 400 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 401 } 402 Expect(sender.InRecovery()).To(BeFalse()) 403 404 // Out of recovery now. Congestion window should not grow during RTT. 405 for i := protocol.ByteCount(0); i < expectedSendWindow/maxDatagramSize-2; i += 2 { 406 // Send our full send window. 407 SendAvailableSendWindow() 408 AckNPackets(2) 409 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 410 } 411 412 // Next ack should cause congestion window to grow by 1MSS. 413 SendAvailableSendWindow() 414 AckNPackets(2) 415 expectedSendWindow += maxDatagramSize 416 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 417 }) 418 419 It("no PRR", func() { 420 SendAvailableSendWindow() 421 LoseNPackets(9) 422 AckNPackets(1) 423 424 Expect(sender.GetCongestionWindow()).To(Equal(protocol.ByteCount(renoBeta * float32(defaultWindowTCP)))) 425 windowInPackets := renoBeta * float32(defaultWindowTCP) / float32(maxDatagramSize) 426 numSent := SendAvailableSendWindow() 427 Expect(numSent).To(BeEquivalentTo(windowInPackets)) 428 }) 429 430 It("reset after connection migration", func() { 431 Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP)) 432 Expect(sender.slowStartThreshold).To(Equal(protocol.MaxByteCount)) 433 434 // Starts with slow start. 435 const numberOfAcks = 10 436 for i := 0; i < numberOfAcks; i++ { 437 // Send our full send window. 438 SendAvailableSendWindow() 439 AckNPackets(2) 440 } 441 SendAvailableSendWindow() 442 expectedSendWindow := defaultWindowTCP + (maxDatagramSize * 2 * numberOfAcks) 443 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 444 445 // Loses a packet to exit slow start. 446 LoseNPackets(1) 447 448 // We should now have fallen out of slow start with a reduced window. Slow 449 // start threshold is also updated. 450 expectedSendWindow = protocol.ByteCount(float32(expectedSendWindow) * renoBeta) 451 Expect(sender.GetCongestionWindow()).To(Equal(expectedSendWindow)) 452 Expect(sender.slowStartThreshold).To(Equal(expectedSendWindow)) 453 454 // Resets cwnd and slow start threshold on connection migrations. 455 sender.OnConnectionMigration() 456 Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP)) 457 Expect(sender.slowStartThreshold).To(Equal(MaxCongestionWindow)) 458 Expect(sender.hybridSlowStart.Started()).To(BeFalse()) 459 }) 460 461 It("slow starts up to the maximum congestion window", func() { 462 const initialMaxCongestionWindow = protocol.MaxCongestionWindowPackets * initialMaxDatagramSize 463 sender = newCubicSender(&clock, rttStats, true, protocol.InitialPacketSizeIPv4, initialCongestionWindowPackets*maxDatagramSize, initialMaxCongestionWindow, nil) 464 465 for i := 1; i < protocol.MaxCongestionWindowPackets; i++ { 466 sender.MaybeExitSlowStart() 467 sender.OnPacketAcked(protocol.PacketNumber(i), 1350, sender.GetCongestionWindow(), clock.Now()) 468 } 469 Expect(sender.GetCongestionWindow()).To(Equal(initialMaxCongestionWindow)) 470 }) 471 472 It("doesn't allow reductions of the maximum packet size", func() { 473 Expect(func() { sender.SetMaxDatagramSize(initialMaxDatagramSize - 1) }).To(Panic()) 474 }) 475 476 It("slow starts up to maximum congestion window, if larger packets are sent", func() { 477 const initialMaxCongestionWindow = protocol.MaxCongestionWindowPackets * initialMaxDatagramSize 478 sender = newCubicSender(&clock, rttStats, true, protocol.InitialPacketSizeIPv4, initialCongestionWindowPackets*maxDatagramSize, initialMaxCongestionWindow, nil) 479 const packetSize = initialMaxDatagramSize + 100 480 sender.SetMaxDatagramSize(packetSize) 481 for i := 1; i < protocol.MaxCongestionWindowPackets; i++ { 482 sender.OnPacketAcked(protocol.PacketNumber(i), packetSize, sender.GetCongestionWindow(), clock.Now()) 483 } 484 const maxCwnd = protocol.MaxCongestionWindowPackets * packetSize 485 Expect(sender.GetCongestionWindow()).To(And( 486 BeNumerically(">", maxCwnd), 487 BeNumerically("<=", maxCwnd+packetSize), 488 )) 489 }) 490 491 It("limit cwnd increase in congestion avoidance", func() { 492 // Enable Cubic. 493 sender = newCubicSender(&clock, rttStats, false, protocol.InitialPacketSizeIPv4, initialCongestionWindowPackets*maxDatagramSize, MaxCongestionWindow, nil) 494 numSent := SendAvailableSendWindow() 495 496 // Make sure we fall out of slow start. 497 savedCwnd := sender.GetCongestionWindow() 498 LoseNPackets(1) 499 Expect(savedCwnd).To(BeNumerically(">", sender.GetCongestionWindow())) 500 501 // Ack the rest of the outstanding packets to get out of recovery. 502 for i := 1; i < numSent; i++ { 503 AckNPackets(1) 504 } 505 Expect(bytesInFlight).To(BeZero()) 506 507 savedCwnd = sender.GetCongestionWindow() 508 SendAvailableSendWindow() 509 510 // Ack packets until the CWND increases. 511 for sender.GetCongestionWindow() == savedCwnd { 512 AckNPackets(1) 513 SendAvailableSendWindow() 514 } 515 // Bytes in flight may be larger than the CWND if the CWND isn't an exact 516 // multiple of the packet sizes being sent. 517 Expect(bytesInFlight).To(BeNumerically(">=", sender.GetCongestionWindow())) 518 savedCwnd = sender.GetCongestionWindow() 519 520 // Advance time 2 seconds waiting for an ack. 521 clock.Advance(2 * time.Second) 522 523 // Ack two packets. The CWND should increase by only one packet. 524 AckNPackets(2) 525 Expect(sender.GetCongestionWindow()).To(Equal(savedCwnd + maxDatagramSize)) 526 }) 527 })