From 581f76a6cc807532398c4233142b0b437987395d Mon Sep 17 00:00:00 2001 From: agnosticdev Date: Wed, 10 Jun 2026 06:57:02 -0700 Subject: [PATCH] agnosticdev/AckBundling: SwiftQUIC: Improve ACK Bundling --- Sources/SwiftNetwork/QUIC/Ack.swift | 4 ++-- Sources/SwiftNetwork/QUIC/QUICConnection.swift | 13 ++++++++++--- Tests/SwiftNetworkTests/QUICTestHarness.swift | 2 +- .../SwiftNetworkQUICEarlyDataTests.swift | 10 ++++++++-- .../SwiftNetworkQUICHarnessTests.swift | 11 +++++++++++ 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftNetwork/QUIC/Ack.swift b/Sources/SwiftNetwork/QUIC/Ack.swift index 83c4f2f..9984f41 100644 --- a/Sources/SwiftNetwork/QUIC/Ack.swift +++ b/Sources/SwiftNetwork/QUIC/Ack.swift @@ -497,7 +497,7 @@ final class Ack: PrefixedLoggable, TimerUser { setAckFrame: connection.scheduleAckFrame, ecn: connection.ecn ) { - connection.sendFrames() + connection.sendFrames(delayedACK: true) } } @@ -718,7 +718,7 @@ final class Ack: PrefixedLoggable, TimerUser { ) } - private func scheduleDelayedAck() { + func scheduleDelayedAck() { // ACK timer is already scheduled if timerScheduled { return diff --git a/Sources/SwiftNetwork/QUIC/QUICConnection.swift b/Sources/SwiftNetwork/QUIC/QUICConnection.swift index e00d776..8d1d300 100644 --- a/Sources/SwiftNetwork/QUIC/QUICConnection.swift +++ b/Sources/SwiftNetwork/QUIC/QUICConnection.swift @@ -2923,14 +2923,21 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, } @discardableResult - func sendFrames(ignoreCongestionWindow: Bool = false) -> Bool { - // Make sure there is something to send first + func sendFrames(ignoreCongestionWindow: Bool = false, delayedACK: Bool = false) -> Bool { + // Make sure there are packets to send guard - initialPendingItems.hasPendingItems || handshakePendingItems.hasPendingItems + initialPendingItems.hasPendingItems + || handshakePendingItems.hasPendingItems || applicationPendingItems.hasPendingItems else { return false } + // For ACK bundling purposes make sure to rely on the ACK-delay timer as much as possible + guard !applicationPendingItems.isAckOnly || delayedACK else { + // Make sure the ack-delay timer is armed if returning early + ack.scheduleDelayedAck() + return false + } return withCurrentPath { path in var sentPackets = NetworkUniqueDeque(minimumCapacity: capacityForPacketNumberSpace()) var discardInitialRecoveryState = false diff --git a/Tests/SwiftNetworkTests/QUICTestHarness.swift b/Tests/SwiftNetworkTests/QUICTestHarness.swift index 2b71a07..68dc568 100644 --- a/Tests/SwiftNetworkTests/QUICTestHarness.swift +++ b/Tests/SwiftNetworkTests/QUICTestHarness.swift @@ -990,7 +990,7 @@ final class QUICTestHarness { generator = TestDataGenerator( blockSize: blockSize, numberOfBlocks: blockCount, - uniqueBits: UInt8(index), + uniqueBits: UInt8(clamping: index), sendFIN: sendFIN ) } diff --git a/Tests/SwiftNetworkTests/SwiftNetworkQUICEarlyDataTests.swift b/Tests/SwiftNetworkTests/SwiftNetworkQUICEarlyDataTests.swift index c04acd6..d004bb7 100644 --- a/Tests/SwiftNetworkTests/SwiftNetworkQUICEarlyDataTests.swift +++ b/Tests/SwiftNetworkTests/SwiftNetworkQUICEarlyDataTests.swift @@ -413,15 +413,21 @@ final class SwiftNetworkQUICEarlyDataTests: NetTestCase { let receiveExpectation = XCTestExpectation() context.async { Logger.test.info("Trying to read data") - for _ in 0..<10 { + func attemptServerRead() { if pairedPathsArray.first?.transferPackets() == 0 { - break + // Wait for new writes from the client to be sent + context.async { + attemptServerRead() + } + return } + Logger.test.info("Trying to read data") if let readData = serverUpperHarness?.upperHarnesses.first?.read() { XCTAssertEqual(readData, dataToSend) receiveExpectation.fulfill() } } + attemptServerRead() } wait(for: [receiveExpectation], timeout: 5.0) } diff --git a/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift b/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift index 056a78f..6dba7d0 100644 --- a/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift +++ b/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift @@ -293,6 +293,17 @@ final class SwiftNetworkQUICHarnessTests: NetTestCase { ) } + func testQUIC1000BidirectionalStreamsEcho1k() { + let serverOptions = QUICProtocol.options() + serverOptions.connectionOptions.initialMaxStreamsBidirectional = 1000 + QUICTestHarness().runQUICTest( + streamCount: 1000, + blockSize: 1000, + blockCount: 1, + serverOptions: serverOptions + ) + } + func testQUICConnectionCloseError() { QUICTestHarness().runQUICTest( streamCount: 2,