Conversation
3320193 to
e990af0
Compare
agnosticdev
left a comment
There was a problem hiding this comment.
First pass complete. Looks good!
| case tcp(NetworkStateIndex) | ||
| case demux(NetworkStateIndex) | ||
| case datagramEndpointFlow(ProtocolInstanceBox<DatagramEndpointFlowProtocol<BaseDatagramLinkageFamily>>) | ||
| case quicPath(ProtocolInstanceBox<QUICPath>) |
There was a problem hiding this comment.
What is the wrapping here of ProtocolInstanceBox about?
There was a problem hiding this comment.
That's to make sure it is a type that is hashable and equatable on ===
| public let storage: BaseNetworkProtocolStorage? | ||
| let protocolType: ProtocolType | ||
|
|
||
| public static func == (lhs: borrowing Self, rhs: borrowing Self) -> Bool { |
There was a problem hiding this comment.
Is self set to borrowing here to provide flexibility for non-copyable types or is this left over from something?
There was a problem hiding this comment.
Yes, it lets it work for non-copyable
|
|
||
| @_spi(ProtocolProvider) | ||
| @available(Network 0.1.0, *) | ||
| public protocol ExternalOutboundStreamLinkage: ExternalLowerProtocolLinkage { |
There was a problem hiding this comment.
So this would be the external stream protocol (or one of them) that the server side folks could adopt then, correct?
There was a problem hiding this comment.
Correct, and this is demonstrated in the test code
| reference.parentReference = parentProtocol.reference | ||
| return reference | ||
| } | ||
|
|
| var reference = ProtocolInstanceReference(quicPath: self) | ||
| reference.parentReference = parentProtocol.reference | ||
| return reference | ||
| } |
| @_spi(ProtocolProvider) | ||
| @available(Network 0.1.0, *) | ||
| public final class QUICPath: MultiplexingDatagramPath<QUICConnection>, Equatable, PrefixedLoggable { | ||
| public final class QUICPath: MultiplexingDatagramPath< |
There was a problem hiding this comment.
This style is more confusing IMO
8c3e481 to
b1dfc65
Compare
This is a big one! Thanks for your patience in reviewing this -- it's all very entangled, so can't meaningfully be broken up. This is a very large API break for adopters, but allows us to push through performance and architectural bottlenecks that otherwise would not be possible.
Here is some AI-assisted explainer content to help in your review and evaluation.
Summary
Protocol-to-protocol dispatch stops going through a type-erased
ProtocolInstanceReferenceand instead goes through concrete, per-direction linkage structs that switch on a closed enum of framework protocols, while all mutable per-instance state moves into a~CopyableNetworkContext.EventContextthreadedinoutthrough every call.Major changes
1.
ProtocolInstanceReferenceis goneProtocols/ProtocolInstanceReference.swiftis deleted and zero references remain inSources. Itsplits in two:
InstanceIdentifier(new —Protocols/InstanceIdentifier.swift) is a pure identity/hash tokenwrapping a
NetworkStateIndex, plus an optional parent index.Protocols/BaseProtocolLinkages.swift(+2,548 lines — the biggestsingle file to read).
Each
Base{Inbound,Outbound}{Datagram,Stream}Linkageholds aProtocolTypeenum naming everyframework protocol and switches on it:
So every hop is a direct call on a known type — no existential, no unspecialized generic. Read one
linkage method (e.g.
handleConnectedEvent) and the other ~50 are the same shape.2.
NetworkContext.EventContextis a~Copyablestruct passedinouteverywhereIt owns
protocolEventStatesand is effectively an exclusive-access token: holding it proves you'reon the context's scheduler. Almost all of the QUIC diff (
QUICConnection.swift+1,263, and the restof
QUIC/) is mechanicalin eventContext:threading — skim it; the interesting bits are elsewhere.The convention introduced throughout: the no-context overload is the external entry point (it
does
fromExternal { … }), and thein:-taking overload is for callers already inside the stack.Other notable changes
Unregistering protocol instances from the context on teardown is now enforced with preconditions, and there is no more async during a deinit of a protocol. This is far better, but requires precision in teardown.
OneToOneProtocol and ManyToManyProtocol no longer have special cases around attaching that don't work on embedded. Generic types are more consistently used.
Test / packaging shape
New
SwiftNetworkTestHarnesstarget (Package.swift). The harness protocols move out ofSwiftNetworkproper, so theNETWORK_NO_TESTING_HARNESScases disappear from the production enum.Both test targets,
SwiftNetworkBenchmarks, and the three tools now depend on it.TestProtocolLinkages.swiftis the test of the extension point. Itsubclasses
BaseNetworkProtocolStorageand defines a parallelTest*linkage family for protocolsthe framework doesn't know about:
Performance
Comparison of
mainagainst this branch forIPUDPTransfer,QUICStreamLoad -stream-count 100000 -download-size 10000, andQUICTransfer.All three benchmarks improve: IPUDPTransfer ~18% cheaper, QUICStreamLoad ~8.7% cheaper,
QUICTransfer ~1.4% cheaper.
mainQUICStreamLoad detail
Many concurrent streams rather than one bulk transfer, so per-flow identity and event dispatch
carry much more of the profile (crypto is 28% of CPU here vs 42% in QUICTransfer). Self-time by
category, whole run:
main