Skip to content

SwiftQUIC: Fix off-queue scheduled event - #151

Merged
agnosticdev merged 3 commits into
mainfrom
agnosticdev/FixCrash
Sep 16, 2026
Merged

agnosticdev merged 3 commits into
mainfrom
agnosticdev/FixCrash

Conversation

@agnosticdev

Copy link
Copy Markdown
Collaborator

This is a potential fix for an off-queue scheduled event that can land the upper stream harness in the wrong state. This was seen when testing with swift-nio-quic recently. For example:

0   libswiftCore.dylib            	       0x1969d5d4c _assertionFailure(_:_:file:line:flags:) + 216
1   IntegrationTests              	       0x10e4aa1fc ProtocolEventManagerState.addPendingEventToDeliverToUpperProtocol(_:) + 1436 (ProtocolEventManager.swift:268)
2   IntegrationTests              	       0x10e4acab4 NetworkContext.deliverEventToUpperProtocol(index:parentIndex:event:drain:) + 768 (ProtocolEventManager.swift:430)
3   IntegrationTests              	       0x10e4afb28 ProtocolInstanceReference.deliverEventToUpperProtocol(event:) + 188 (ProtocolEventManager.swift:713)
4   IntegrationTests              	       0x10e4c3650 InboundDataLinkage.deliverOutboundRoomAvailableEvent(_:) + 224 (ProtocolLinkage.swift:77)
5   IntegrationTests              	       0x10e599de0 QUICStreamInstance.updateFlowControlWithSentBytes(_:connection:) + 960 (FlowControl.swift:406)
6   IntegrationTests              	       0x10e6ba018 QUICStreamInstance.recordStreamDataSending(writtenLength:isFinal:pendingItems:connection:) + 400 (QUICStream.swift:1097)
7   IntegrationTests              	       0x10e6f8524 static FrameStreamSendMetadata.write(into:pendingItems:connection:stats:shorthandFrames:) + 12808 (SendItems.swift:840)
8   IntegrationTests              	       0x10e6f94d8 protocol witness for static SendableItem.write(into:pendingItems:connection:stats:shorthandFrames:) in conformance FrameStreamSendMetadata + 20
9   IntegrationTests              	       0x10e6ecfa4 static SendableItem<>.write(into:pendingItems:connection:availableCongestionWindow:stats:transmittedItems:shorthandFrames:) + 788 (SendItems.swift:103)
10  IntegrationTests              	       0x10e7026c8 PrioritizedSendableItems.write(into:pendingItems:connection:availableCongestionWindow:stats:transmittedItems:shorthandFrames:) + 2812 (SendItems.swift:1931)
11  IntegrationTests              	       0x10e70986c PendingItems.write(into:connection:stats:keyState:transmittedItems:availableCongestionWindow:isAckEliciting:isInFlightEligible:maximumFrameCount:shorthandFrames:) + 1136 (SendItems.swift:2762)
12  IntegrationTests              	       0x10e5bce2c static Packet.build(into:number:lastAcked:keyState:path:tagSize:pendingItems:sentPacketRecord:connection:availableCongestionWindow:token:stats:version:isServer:testSendingShortPackets:) + 3684 (PacketBuilder.swift:156)
13  IntegrationTests              	       0x10e634ddc QUICConnection.buildSinglePacketForKeyState(_:pendingItems:sentPackets:on:ignoreCongestionWindow:availableCongestionWindow:totalSendBytes:retransmission:datagramBatch:protector:stats:ecn:totalTxBytes:totalTxPackets:) + 5932 (QUICConnection.swift:3792)
14  IntegrationTests              	       0x10e6333d8 QUICConnection.runApplicationBurstLoop(keyState:path:ignoreCongestionWindow:retransmission:startSendingTimestamp:availableCongestionWindow:totalSendBytes:datagramBatch:sentPackets:protector:stats:ecn:applicationPendingItems:totalTxBytes:totalTxPackets:) + 464 (QUICConnection.swift:3308)
15  IntegrationTests              	       0x10e6312a4 QUICConnection.sendApplicationFrames(path:ignoreCongestionWindow:retransmission:sentPackets:applicationPendingItems:) + 992 (QUICConnection.swift:3421)
16  IntegrationTests              	       0x10e630d20 QUICConnection.sendFrames(ignoreCongestionWindow:delayedACK:sentPackets:recovery:initialPendingItems:handshakePendingItems:applicationPendingItems:) + 948 (QUICConnection.swift:3069)
17  IntegrationTests              	       0x10e6086a0 QUICConnection.sendFrames(ignoreCongestionWindow:delayedACK:) + 296 (QUICConnection.swift:3024)
18  IntegrationTests              	       0x10e64d498 QUICConnection.resumeSendingAfterBurstLimit() + 212 (QUICConnection.swift:5582)
19  IntegrationTests              	       0x10e64d3b4 closure #1 in QUICConnection.burstLimitReached() + 36 (QUICConnection.swift:5575)
20  IntegrationTests              	       0x10e9c190c closure #1 in SelectableEventLoop.run(_:) + 192 (SelectableEventLoop.swift:646)
21  IntegrationTests              	       0x10e9c9848 partial apply for closure #1 in SelectableEventLoop.run(_:) + 28
22  IntegrationTests              	       0x10e9bbd7c closure #1 in withAutoReleasePool<A>(_:) + 52 (SelectableEventLoop.swift:46)

The call to resumeSendingAfterBurstLimit() is run asynchronous and then fires deliverOutboundRoomAvailableEvent on the back-side which ends up tripping the wrong stream it looks like.

@agnosticdev agnosticdev added the 🔨 semver/patch No public API change. label Sep 16, 2026
updateOutboundFlowControlCredit(connection: connection)
if self.maximumStreamDataSize > 0 {
upper.deliverOutboundRoomAvailableEvent(reference)
fromExternal {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the right approach here. This is implying that something was entering the stack without going through one of the correct calls earlier.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to the instance async function as an alternative 7d5abc0

asyncSendRunning = true
log.datapath("async: scheduling restart after packet burst")
self.context.async {
self.async {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the right fix, thanks! Can you also address the same issue in writeQLog?

@agnosticdev
agnosticdev merged commit 7efb30c into main Sep 16, 2026
38 checks passed
@agnosticdev
agnosticdev deleted the agnosticdev/FixCrash branch September 16, 2026 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 semver/patch No public API change.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants