Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
806 changes: 395 additions & 411 deletions Sources/SwiftNetwork/QUIC/CongestionControl.swift

Large diffs are not rendered by default.

250 changes: 123 additions & 127 deletions Sources/SwiftNetwork/QUIC/Cubic.swift

Large diffs are not rendered by default.

199 changes: 93 additions & 106 deletions Sources/SwiftNetwork/QUIC/Ledbat.swift

Large diffs are not rendered by default.

299 changes: 145 additions & 154 deletions Sources/SwiftNetwork/QUIC/Prague.swift

Large diffs are not rendered by default.

138 changes: 79 additions & 59 deletions Sources/SwiftNetwork/QUIC/QUICPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public final class QUICPath: MultiplexingDatagramPath<

var bdp = BandwidthDelayProduct()

private var congestionControl: CongestionControl?
private var congestionControl: CongestionControl

var pacer: Pacer

Expand Down Expand Up @@ -337,6 +337,14 @@ public final class QUICPath: MultiplexingDatagramPath<
required init(parent: QUICConnection, in eventContext: inout NetworkContext.EventContext) {
self.rtt = RTT(logPrefixer: parent.logPrefixer)
self.pacer = Pacer()
// Overwritten with the real mss/qlog once `setup()` runs; RX/TX aren't allowed on a
// path until then, so this placeholder is never observed.
self.congestionControl = CongestionControl.createCubic(
pacer: &self.pacer,
mss: 0,
qlog: nil,
logPrefixer: parent.logPrefixer
)
super.init(parent: parent, in: &eventContext)
}

Expand Down Expand Up @@ -371,13 +379,11 @@ public final class QUICPath: MultiplexingDatagramPath<

let pacerEnabled = (pacePackets || QUICPreferences.shared.pacePackets)
self.pacer = Pacer(enabled: pacerEnabled)
self.congestionControl = .cubic(
algorithm: Cubic(
pacer: &self.pacer,
mss: self.initialMSS,
qlog: parentProtocol.qLog,
logPrefixer: self.log
)
self.congestionControl = .createCubic(
pacer: &self.pacer,
mss: self.initialMSS,
qlog: parentProtocol.qLog,
logPrefixer: self.log
)

self.spinValue = parentProtocol.initialSpinValue
Expand Down Expand Up @@ -438,42 +444,34 @@ public final class QUICPath: MultiplexingDatagramPath<
}

func resetCongestionControl() {
switch self.congestionControl {
switch self.congestionControl.algorithm {
case .cubic:
self.congestionControl = .cubic(
algorithm: Cubic(
pacer: &self.pacer,
mss: self.initialMSS,
qlog: parentProtocol.qLog,
logPrefixer: self.log
)
self.congestionControl = .createCubic(
pacer: &self.pacer,
mss: self.initialMSS,
qlog: parentProtocol.qLog,
logPrefixer: self.log
)
#if !NETWORK_EMBEDDED
case .ledbat:
self.congestionControl = .ledbat(
algorithm: Ledbat(
mss: self.initialMSS,
qlog: parentProtocol.qLog,
logPrefixer: self.log
)
self.congestionControl = .createLedbat(
mss: self.initialMSS,
qlog: parentProtocol.qLog,
logPrefixer: self.log
)
case .prague:
self.congestionControl = .prague(
algorithm: Prague(
pacer: &self.pacer,
mss: self.initialMSS,
qlog: parentProtocol.qLog,
logPrefixer: self.log
)
self.congestionControl = .createPrague(
pacer: &self.pacer,
mss: self.initialMSS,
qlog: parentProtocol.qLog,
logPrefixer: self.log
)
#endif
case .none:
break
}
}

func idleTimeoutCongestionControl() {
self.congestionControl?.idleTimeout(mss: mss)
self.congestionControl.idleTimeout(mss: mss)
}

func setupL4SState(l4sEnabled: Bool?) {
Expand All @@ -494,50 +492,72 @@ public final class QUICPath: MultiplexingDatagramPath<
func markAsBackground(_ background: Bool) {
#if !NETWORK_EMBEDDED
// Use LEDBAT for background cases
switch self.congestionControl {
switch self.congestionControl.algorithm {
case .cubic:
if !background { return } // Nothing to do, already not background
let oldState = self.congestionControl.state
var newState = CongestionControlState()
var ledbat = Ledbat(
state: &newState,
mss: self.initialMSS,
qlog: parentProtocol.qLog,
logPrefixer: self.log
)
ledbat.inherit(
from: self.congestionControl!,
from: oldState,
state: &newState,
mss: self.initialMSS,
qlog: parentProtocol.qLog
)
self.congestionControl = .ledbat(algorithm: ledbat)
self.congestionControl = CongestionControl(
state: newState,
log: self.log,
algorithm: .ledbat(algorithm: ledbat)
)
case .ledbat:
if background { return } // Nothing to do, already background
// Inherit from the current LEDBAT so bytes in flight (and the window) carry over.
let oldState = self.congestionControl.state
var newState = CongestionControlState()
var cubic = Cubic(
state: &newState,
pacer: &self.pacer,
mss: self.initialMSS,
qlog: parentProtocol.qLog,
logPrefixer: self.log
)
cubic.inherit(
from: self.congestionControl!,
from: oldState,
state: &newState,
mss: self.initialMSS,
qlog: parentProtocol.qLog
)
self.congestionControl = .cubic(algorithm: cubic)
self.congestionControl = CongestionControl(
state: newState,
log: self.log,
algorithm: .cubic(algorithm: cubic)
)
case .prague:
if !background { return } // Nothing to do, already not background
let oldState = self.congestionControl.state
var newState = CongestionControlState()
var ledbat = Ledbat(
state: &newState,
mss: self.initialMSS,
qlog: parentProtocol.qLog,
logPrefixer: self.log
)
ledbat.inherit(
from: self.congestionControl!,
from: oldState,
state: &newState,
mss: self.initialMSS,
qlog: parentProtocol.qLog
)
self.congestionControl = .ledbat(algorithm: ledbat)
case .none:
break
self.congestionControl = CongestionControl(
state: newState,
log: self.log,
algorithm: .ledbat(algorithm: ledbat)
)
}
#endif
}
Expand Down Expand Up @@ -718,22 +738,22 @@ public final class QUICPath: MultiplexingDatagramPath<
extension QUICPath {
@inline(always)
var congestionControlWindow: UInt64 {
congestionControl?.congestionWindow ?? 0
congestionControl.congestionWindow
}

@inline(always)
var congestionControlAvailableCongestionWindow: UInt64 {
congestionControl?.availableCongestionWindow ?? 0
congestionControl.availableCongestionWindow
}

@inline(always)
func congestionControlCanSend(packetLength: Int) -> Bool {
congestionControl?.canSend(packetLength: packetLength) ?? false
congestionControl.canSend(packetLength: packetLength)
}

@inline(always)
func congestionControlPersistentCongestion(mss: Int, qlog: QLog? = nil) {
congestionControl?.persistentCongestion(mss: mss, qlog: qlog)
congestionControl.persistentCongestion(mss: mss, qlog: qlog)
}

@inline(always)
Expand All @@ -744,7 +764,7 @@ extension QUICPath {
packetsLost: Bool,
qlog: QLog? = nil
) {
congestionControl?.ackEnd(
congestionControl.ackEnd(
rtt: rtt,
path: self,
mss: mss,
Expand All @@ -756,12 +776,12 @@ extension QUICPath {

@inline(always)
func congestionControlPacketsSent(bytesSent: Int, qlog: QLog? = nil) {
congestionControl?.packetSent(bytesSent: bytesSent, qlog: qlog)
congestionControl.packetSent(bytesSent: bytesSent, qlog: qlog)
}

@inline(always)
func congestionControlPacketsAcked(bytesAcked: Int, sentTime: NetworkClock.Instant) {
congestionControl?.packetsAcked(bytesAcked: bytesAcked, sentTime: sentTime)
congestionControl.packetsAcked(bytesAcked: bytesAcked, sentTime: sentTime)
}

@inline(always)
Expand All @@ -773,49 +793,49 @@ extension QUICPath {
) -> Bool {
// Loss accounting doesn't repace this path, so there is no path to hand down.
let unpacedPath: QUICPath? = nil
return congestionControl?.packetsLost(
return congestionControl.packetsLost(
path: unpacedPath,
bytesLost: bytesLost,
largestLostSentTime: largestLostSentTime,
mss: mss,
smoothedRTT: smoothedRTT,
now: parentProtocol.now
) ?? false
)
}

@inline(always)
func congestionControlPacketDiscarded(bytesSent: Int, qlog: QLog? = nil) {
congestionControl?.packetDiscarded(bytesSent: bytesSent, qlog: qlog)
congestionControl.packetDiscarded(bytesSent: bytesSent, qlog: qlog)
}

@inline(always)
func congestionControlAckBegin() {
congestionControl?.ackBegin()
congestionControl.ackBegin()
}

@inline(always)
var congestionControlBytesInFlight: UInt64 {
congestionControl?.bytesInFlight ?? 0
congestionControl.bytesInFlight
}

@inline(always)
var congestionControlName: String {
congestionControl?.name ?? "none"
congestionControl.name
}

@inline(always)
func congestionControlSpuriousRetransmit(qlog: QLog? = nil) {
congestionControl?.spuriousRetransmit(qlog: qlog)
congestionControl.spuriousRetransmit(qlog: qlog)
}

@inline(always)
func congestionControlMSSChanged(mss: Int) {
congestionControl?.mssChanged(mss: mss)
congestionControl.mssChanged(mss: mss)
}

@inline(always)
func congestionControlIdleTimeout(mss: Int) {
congestionControl?.idleTimeout(mss: mss)
congestionControl.idleTimeout(mss: mss)
}

@inline(always)
Expand All @@ -831,7 +851,7 @@ extension QUICPath {
) {
// ECN accounting doesn't repace this path, so there is no path to hand down.
let unpacedPath: QUICPath? = nil
congestionControl?.processECN(
congestionControl.processECN(
path: unpacedPath,
ceCount: ceCount,
packetsAcked: packetsAcked,
Expand All @@ -847,7 +867,7 @@ extension QUICPath {

@inline(always)
func congestionControlFilloutDataTransferSnapshot(snapshot: inout DataTransferSnapshot) {
congestionControl?.filloutDataTransferSnapshot(dataTransferSnapshot: &snapshot)
congestionControl.filloutDataTransferSnapshot(dataTransferSnapshot: &snapshot)
}
}

Expand Down
26 changes: 15 additions & 11 deletions Sources/SwiftNetwork/QUIC/Recovery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,32 +129,32 @@ struct Recovery: ~Copyable, PrefixedLoggable, NonCopyableTimerUser {
if frontNumber == target { return 0 }
if frontNumber > target { return nil }

var right = entryCount - 1
var right = entryCount &- 1
let backNumber = outstandingPackets[right].packet.number.value
if backNumber == target { return right }
if backNumber < target { return nil }

// Both ends are strictly inside the range now, so narrow the window
// using the density bounds described above.
var left = 1
let fromFront = target - frontNumber
let fromFront = target &- frontNumber
if fromFront < Int64(right) {
right = Int(fromFront)
}
let fromBack = backNumber - target
if fromBack < Int64(entryCount - 1 - left) {
left = entryCount - 1 - Int(fromBack)
let fromBack = backNumber &- target
if fromBack < Int64(entryCount &- 1 &- left) {
left = entryCount &- 1 &- Int(fromBack)
}

while left <= right {
let middle = left + (right - left) / 2
let middle = left &+ (right &- left) / 2
let middleNumber = outstandingPackets[middle].packet.number.value
if middleNumber == target {
return middle
} else if middleNumber < target {
left = middle + 1
left = middle &+ 1
} else {
right = middle - 1
right = middle &- 1
}
}
return nil
Expand All @@ -172,7 +172,7 @@ struct Recovery: ~Copyable, PrefixedLoggable, NonCopyableTimerUser {
? outstandingPackets.removeFirst()
: outstandingPackets.remove(at: index)
if removedEntry.packet.largerPacket, largerPacketCount > 0 {
largerPacketCount -= 1
largerPacketCount &-= 1
}
return removedEntry
}
Expand Down Expand Up @@ -543,10 +543,14 @@ struct Recovery: ~Copyable, PrefixedLoggable, NonCopyableTimerUser {
newlyECTAcked += 1
}

guard let sentPath = connection.path(for: ackedEntry.packet.sentPath) else {
let sentPath: QUICPath
if ackedEntry.packet.sentPath == path.pathIdentifier {
sentPath = path
} else if let lookedUpPath = connection.path(for: ackedEntry.packet.sentPath) {
sentPath = lookedUpPath
} else {
continue
}

if ackedEntry.lostTime != .zero {
let sRTT = sentPath.rtt.smoothedRTT
let latestRTT = sentPath.rtt.latestRTT
Expand Down
Loading
Loading