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