diff --git a/congestion/cubic_sender.go b/congestion/cubic_sender.go index 792bf8230..ecf4b2e80 100644 --- a/congestion/cubic_sender.go +++ b/congestion/cubic_sender.go @@ -174,6 +174,9 @@ func (c *cubicSender) onPacketLost(packetNumber protocol.PacketNumber, lostBytes return } c.lastCutbackExitedSlowstart = c.InSlowStart() + if c.InSlowStart() { + c.stats.slowstartPacketsLost++ + } c.prr.OnPacketLost(bytesInFlight) @@ -292,3 +295,8 @@ func (c *cubicSender) OnConnectionMigration() { c.slowstartThreshold = c.initialMaxCongestionWindow c.maxTCPCongestionWindow = c.initialMaxCongestionWindow } + +// SetSlowStartLargeReduction allows enabling the SSLR experiment +func (c *cubicSender) SetSlowStartLargeReduction(enabled bool) { + c.slowStartLargeReduction = enabled +} diff --git a/congestion/cubic_sender_test.go b/congestion/cubic_sender_test.go index 7e5800c61..85b51c76c 100644 --- a/congestion/cubic_sender_test.go +++ b/congestion/cubic_sender_test.go @@ -42,36 +42,35 @@ var _ = Describe("Cubic Sender", func() { sender = congestion.NewCubicSender(&clock, rttStats, true /*reno*/, initialCongestionWindowPackets, protocol.MaxCongestionWindow) }) - SendAvailableSendWindow := func() int { + SendAvailableSendWindowLen := func(packetLength uint64) int { // Send as long as TimeUntilSend returns Zero. packets_sent := 0 can_send := sender.TimeUntilSend(clock.Now(), bytesInFlight) == 0 for can_send { - sender.OnPacketSent(clock.Now(), bytesInFlight, packetNumber, protocol.DefaultTCPMSS, true) + sender.OnPacketSent(clock.Now(), bytesInFlight, packetNumber, packetLength, true) packetNumber++ packets_sent++ - bytesInFlight += protocol.DefaultTCPMSS + bytesInFlight += packetLength can_send = sender.TimeUntilSend(clock.Now(), bytesInFlight) == 0 } return packets_sent } // Normal is that TCP acks every other segment. - AckNPackets := func(n int) { + AckNPacketsLen := func(n int, packetLength uint64) { rttStats.UpdateRTT(60*time.Millisecond, 0, clock.Now()) var ackedPackets congestion.PacketVector var lostPackets congestion.PacketVector for i := 0; i < n; i++ { ackedPacketNumber++ - ackedPackets = append(ackedPackets, congestion.PacketInfo{Number: ackedPacketNumber, Length: protocol.DefaultTCPMSS}) + ackedPackets = append(ackedPackets, congestion.PacketInfo{Number: ackedPacketNumber, Length: packetLength}) } sender.OnCongestionEvent(true, bytesInFlight, ackedPackets, lostPackets) - bytesInFlight -= uint64(n) * protocol.DefaultTCPMSS + bytesInFlight -= uint64(n) * packetLength clock.Advance(time.Millisecond) } - LoseNPackets := func(n int) { - packetLength := uint64(protocol.DefaultTCPMSS) + LoseNPacketsLen := func(n int, packetLength uint64) { var ackedPackets congestion.PacketVector var lostPackets congestion.PacketVector for i := 0; i < n; i++ { @@ -82,6 +81,10 @@ var _ = Describe("Cubic Sender", func() { bytesInFlight -= uint64(n) * packetLength } + SendAvailableSendWindow := func() int { return SendAvailableSendWindowLen(protocol.DefaultTCPMSS) } + AckNPackets := func(n int) { AckNPacketsLen(n, protocol.DefaultTCPMSS) } + LoseNPackets := func(n int) { LoseNPacketsLen(n, protocol.DefaultTCPMSS) } + It("simpler sender", func() { // At startup make sure we are at the default. Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP)) @@ -176,6 +179,84 @@ var _ = Describe("Cubic Sender", func() { Expect(sender.HybridSlowStart().Started()).To(BeFalse()) }) + It("slow start packet loss with large reduction", func() { + sender.SetSlowStartLargeReduction(true) + + sender.SetNumEmulatedConnections(1) + const kNumberOfAcks = 10 + for i := 0; i < kNumberOfAcks; i++ { + // Send our full send window. + SendAvailableSendWindow() + AckNPackets(2) + } + SendAvailableSendWindow() + expected_send_window := defaultWindowTCP + (protocol.DefaultTCPMSS * 2 * kNumberOfAcks) + Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window)) + + // Lose a packet to exit slow start. We should now have fallen out of + // slow start with a window reduced by 1. + LoseNPackets(1) + expected_send_window -= protocol.DefaultTCPMSS + Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window)) + + // Lose 5 packets in recovery and verify that congestion window is reduced + // further. + LoseNPackets(5) + expected_send_window -= 5 * protocol.DefaultTCPMSS + Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window)) + + packets_in_recovery_window := expected_send_window / protocol.DefaultTCPMSS + + // Recovery phase. We need to ack every packet in the recovery window before + // we exit recovery. + number_of_packets_in_window := expected_send_window / protocol.DefaultTCPMSS + AckNPackets(int(packets_in_recovery_window)) + SendAvailableSendWindow() + Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window)) + + // We need to ack the rest of the window before cwnd increases by 1. + AckNPackets(int(number_of_packets_in_window - 1)) + SendAvailableSendWindow() + Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window)) + + // Next ack should increase cwnd by 1. + AckNPackets(1) + expected_send_window += protocol.DefaultTCPMSS + Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window)) + + // Now RTO and ensure slow start gets reset. + Expect(sender.HybridSlowStart().Started()).To(BeTrue()) + sender.OnRetransmissionTimeout(true) + Expect(sender.HybridSlowStart().Started()).To(BeFalse()) + }) + + It("slow start half packet loss with large reduction", func() { + sender.SetSlowStartLargeReduction(true) + + sender.SetNumEmulatedConnections(1) + const kNumberOfAcks = 10 + for i := 0; i < kNumberOfAcks; i++ { + // Send our full send window in half sized packets. + SendAvailableSendWindowLen(protocol.DefaultTCPMSS / 2) + AckNPackets(2) + } + SendAvailableSendWindowLen(protocol.DefaultTCPMSS / 2) + expected_send_window := defaultWindowTCP + (protocol.DefaultTCPMSS * 2 * kNumberOfAcks) + Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window)) + + // Lose a packet to exit slow start. We should now have fallen out of + // slow start with a window reduced by 1. + LoseNPackets(1) + expected_send_window -= protocol.DefaultTCPMSS + Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window)) + + // Lose 10 packets in recovery and verify that congestion window is reduced + // by 5 packets. + LoseNPacketsLen(10, protocol.DefaultTCPMSS/2) + expected_send_window -= 5 * protocol.DefaultTCPMSS + Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window)) + }) + It("no PRR when less than one packet in flight", func() { SendAvailableSendWindow() LoseNPackets(int(initialCongestionWindowPackets) - 1) diff --git a/congestion/interface.go b/congestion/interface.go index 1ba80019f..24cd7d770 100644 --- a/congestion/interface.go +++ b/congestion/interface.go @@ -17,6 +17,9 @@ type SendAlgorithm interface { InRecovery() bool OnConnectionMigration() + // Experiments + SetSlowStartLargeReduction(enabled bool) + HybridSlowStart() *HybridSlowStart // only for testing SlowstartThreshold() protocol.PacketNumber // only for testing RenoBeta() float32 // only for testing