mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-04 12:47:36 +03:00
add cubic_sender plus initial test
This commit is contained in:
parent
6eeccfd123
commit
c641e24e8c
8 changed files with 171 additions and 18 deletions
72
congestion/cubic_sender.go
Normal file
72
congestion/cubic_sender.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package congestion
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go/protocol"
|
||||
)
|
||||
|
||||
type cubicSender struct {
|
||||
hybridSlowStart HybridSlowStart
|
||||
prr PrrSender
|
||||
|
||||
// Track the largest packet that has been sent.
|
||||
largestSentPacketNumber protocol.PacketNumber
|
||||
|
||||
// Track the largest packet that has been acked.
|
||||
largestAckedPacketNumber protocol.PacketNumber
|
||||
|
||||
// Track the largest packet number outstanding when a CWND cutback occurs.
|
||||
largestSentAtLastCutback protocol.PacketNumber
|
||||
|
||||
// Congestion window in packets.
|
||||
congestionWindow protocol.PacketNumber
|
||||
|
||||
// Slow start congestion window in packets, aka ssthresh.
|
||||
slowstartThreshold protocol.PacketNumber
|
||||
}
|
||||
|
||||
// NewCubicSender makes a new cubic sender
|
||||
func NewCubicSender(initialCongestionWindow protocol.PacketNumber) SendAlgorithm {
|
||||
return &cubicSender{
|
||||
congestionWindow: initialCongestionWindow,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cubicSender) TimeUntilSend(now time.Time, bytesInFlight uint64) time.Duration {
|
||||
if c.InRecovery() {
|
||||
// PRR is used when in recovery.
|
||||
return c.prr.TimeUntilSend(c.GetCongestionWindow(), bytesInFlight, c.GetSlowStartThreshold())
|
||||
}
|
||||
if c.GetCongestionWindow() > bytesInFlight {
|
||||
return 0
|
||||
}
|
||||
return math.MaxInt64
|
||||
}
|
||||
|
||||
func (c *cubicSender) OnPacketSent(sentTime time.Time, bytesInFlight uint64, packetNumber protocol.PacketNumber, bytes uint64, isRetransmittable bool) bool {
|
||||
// Only update bytesInFlight for data packets.
|
||||
if !isRetransmittable {
|
||||
return false
|
||||
}
|
||||
if c.InRecovery() {
|
||||
// PRR is used when in recovery.
|
||||
c.prr.OnPacketSent(bytes)
|
||||
}
|
||||
c.largestSentPacketNumber = packetNumber
|
||||
c.hybridSlowStart.OnPacketSent(packetNumber)
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *cubicSender) InRecovery() bool {
|
||||
return c.largestAckedPacketNumber <= c.largestSentAtLastCutback && c.largestAckedPacketNumber != 0
|
||||
}
|
||||
|
||||
func (c *cubicSender) GetCongestionWindow() uint64 {
|
||||
return uint64(c.congestionWindow) * protocol.DefaultTCPMSS
|
||||
}
|
||||
|
||||
func (c *cubicSender) GetSlowStartThreshold() uint64 {
|
||||
return uint64(c.slowstartThreshold) * protocol.DefaultTCPMSS
|
||||
}
|
68
congestion/cubic_sender_test.go
Normal file
68
congestion/cubic_sender_test.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package congestion_test
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"github.com/lucas-clemente/quic-go/protocol"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const initialCongestionWindowPackets protocol.PacketNumber = 10
|
||||
const defaultWindowTCP = uint64(initialCongestionWindowPackets * protocol.DefaultTCPMSS)
|
||||
|
||||
type mockClock time.Time
|
||||
|
||||
func (c *mockClock) Now() time.Time {
|
||||
return time.Time(*c)
|
||||
}
|
||||
|
||||
func (c *mockClock) Advance(d time.Duration) {
|
||||
*c = mockClock(time.Time(*c).Add(d))
|
||||
}
|
||||
|
||||
var _ = Describe("Cubic Sender", func() {
|
||||
var (
|
||||
sender congestion.SendAlgorithm
|
||||
clock mockClock
|
||||
bytesInFlight uint64
|
||||
packetNumber protocol.PacketNumber
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
bytesInFlight = 0
|
||||
clock = mockClock{}
|
||||
sender = congestion.NewCubicSender(initialCongestionWindowPackets)
|
||||
packetNumber = 1
|
||||
})
|
||||
|
||||
SendAvailableSendWindow := 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 {
|
||||
packetNumber++
|
||||
sender.OnPacketSent(clock.Now(), bytesInFlight, packetNumber, protocol.DefaultTCPMSS, true)
|
||||
packets_sent++
|
||||
bytesInFlight += protocol.DefaultTCPMSS
|
||||
can_send = sender.TimeUntilSend(clock.Now(), bytesInFlight) == 0
|
||||
}
|
||||
return packets_sent
|
||||
}
|
||||
|
||||
It("works with default values", func() {
|
||||
// At startup make sure we are at the default.
|
||||
Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP))
|
||||
// At startup make sure we can send.
|
||||
Expect(sender.TimeUntilSend(clock.Now(), 0)).To(BeZero())
|
||||
// Make sure we can send.
|
||||
Expect(sender.TimeUntilSend(clock.Now(), 0)).To(BeZero())
|
||||
// And that window is un-affected.
|
||||
Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP))
|
||||
|
||||
// Fill the send window with data, then verify that we can't send.
|
||||
SendAvailableSendWindow(protocol.DefaultTCPMSS)
|
||||
Expect(sender.TimeUntilSend(clock.Now(), sender.GetCongestionWindow())).ToNot(BeZero())
|
||||
})
|
||||
})
|
|
@ -13,16 +13,6 @@ const kNumConnections uint32 = 2
|
|||
const kNConnectionBeta float32 = (float32(kNumConnections) - 1 + kBeta) / float32(kNumConnections)
|
||||
const kNConnectionAlpha float32 = 3 * float32(kNumConnections) * float32(kNumConnections) * (1 - kNConnectionBeta) / (1 + kNConnectionBeta)
|
||||
|
||||
type mockClock time.Time
|
||||
|
||||
func (c *mockClock) Now() time.Time {
|
||||
return time.Time(*c)
|
||||
}
|
||||
|
||||
func (c *mockClock) Advance(d time.Duration) {
|
||||
*c = mockClock(time.Time(*c).Add(d))
|
||||
}
|
||||
|
||||
var _ = Describe("Cubic", func() {
|
||||
var (
|
||||
clock mockClock
|
||||
|
|
|
@ -3,6 +3,7 @@ package congestion
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go/protocol"
|
||||
"github.com/lucas-clemente/quic-go/utils"
|
||||
)
|
||||
|
||||
|
@ -21,8 +22,8 @@ const hybridStartDelayMaxThresholdUs = int64(16000)
|
|||
|
||||
// HybridSlowStart implements the TCP hybrid slow start algorithm
|
||||
type HybridSlowStart struct {
|
||||
endPacketNumber uint64
|
||||
lastSentPacketNumber uint64
|
||||
endPacketNumber protocol.PacketNumber
|
||||
lastSentPacketNumber protocol.PacketNumber
|
||||
started bool
|
||||
currentMinRTT time.Duration
|
||||
rttSampleCount uint32
|
||||
|
@ -30,7 +31,7 @@ type HybridSlowStart struct {
|
|||
}
|
||||
|
||||
// StartReceiveRound is called for the start of each receive round (burst) in the slow start phase.
|
||||
func (s *HybridSlowStart) StartReceiveRound(lastSent uint64) {
|
||||
func (s *HybridSlowStart) StartReceiveRound(lastSent protocol.PacketNumber) {
|
||||
s.endPacketNumber = lastSent
|
||||
s.currentMinRTT = 0
|
||||
s.rttSampleCount = 0
|
||||
|
@ -38,7 +39,7 @@ func (s *HybridSlowStart) StartReceiveRound(lastSent uint64) {
|
|||
}
|
||||
|
||||
// IsEndOfRound returns true if this ack is the last packet number of our current slow start round.
|
||||
func (s *HybridSlowStart) IsEndOfRound(ack uint64) bool {
|
||||
func (s *HybridSlowStart) IsEndOfRound(ack protocol.PacketNumber) bool {
|
||||
return s.endPacketNumber < ack
|
||||
}
|
||||
|
||||
|
@ -83,3 +84,8 @@ func (s *HybridSlowStart) ShouldExitSlowStart(latestRTT time.Duration, minRTT ti
|
|||
// increasing delay is found.
|
||||
return congestionWindow >= hybridStartLowWindow && s.hystartFound
|
||||
}
|
||||
|
||||
// OnPacketSent is called when a packet was sent
|
||||
func (s *HybridSlowStart) OnPacketSent(packetNumber protocol.PacketNumber) {
|
||||
s.lastSentPacketNumber = packetNumber
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"github.com/lucas-clemente/quic-go/protocol"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
@ -18,8 +19,8 @@ var _ = Describe("Hybrid slow start", func() {
|
|||
})
|
||||
|
||||
It("works in a simple case", func() {
|
||||
packet_number := uint64(1)
|
||||
end_packet_number := uint64(3)
|
||||
packet_number := protocol.PacketNumber(1)
|
||||
end_packet_number := protocol.PacketNumber(3)
|
||||
slowStart.StartReceiveRound(end_packet_number)
|
||||
|
||||
packet_number++
|
||||
|
@ -53,7 +54,7 @@ var _ = Describe("Hybrid slow start", func() {
|
|||
// RTT of 60ms the detection will happen at 67.5 ms.
|
||||
const kHybridStartMinSamples = 8 // Number of acks required to trigger.
|
||||
|
||||
end_packet_number := uint64(1)
|
||||
end_packet_number := protocol.PacketNumber(1)
|
||||
end_packet_number++
|
||||
slowStart.StartReceiveRound(end_packet_number)
|
||||
|
||||
|
|
13
congestion/interface.go
Normal file
13
congestion/interface.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package congestion
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go/protocol"
|
||||
)
|
||||
|
||||
type SendAlgorithm interface {
|
||||
TimeUntilSend(now time.Time, bytesInFlight uint64) time.Duration
|
||||
OnPacketSent(sentTime time.Time, bytesInFlight uint64, packetNumber protocol.PacketNumber, bytes uint64, isRetransmittable bool) bool
|
||||
GetCongestionWindow() uint64
|
||||
}
|
|
@ -73,7 +73,7 @@ var _ = Describe("PRR sender", func() {
|
|||
|
||||
})
|
||||
|
||||
It("bust loss results in slow start", func() {
|
||||
It("burst loss results in slow start", func() {
|
||||
bytes_in_flight := uint64(20 * protocol.DefaultTCPMSS)
|
||||
const num_packets_lost = 13
|
||||
const ssthresh_after_loss = 10
|
||||
|
|
|
@ -21,3 +21,6 @@ const MaxFrameSize = MaxPacketSize - (1 + 8 + 6) /*public header*/ - 1 /*private
|
|||
// DefaultTCPMSS is the default maximum packet size used in the Linux TCP implementation.
|
||||
// Used in QUIC for congestion window computations in bytes.
|
||||
const DefaultTCPMSS = 1460
|
||||
|
||||
// InitialCongestionWindow is the initial congestion window in QUIC packets
|
||||
const InitialCongestionWindow = 32
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue