From 1f354a871fbf47c7436c94da4c5dc1086434be7f Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Tue, 8 Apr 2025 15:13:32 +0100 Subject: [PATCH 01/11] Add NWParameters configurator to bootstraps --- .../Datagram/NIOTSDatagramBootstrap.swift | 12 ++++++++++- .../Datagram/NIOTSDatagramChannel.swift | 16 ++++++++++---- .../Datagram/NIOTSDatagramListener.swift | 17 +++++++++++++-- .../NIOTSDatagramListenerChannel.swift | 19 ++++++++++++----- .../NIOTSConnectionBootstrap.swift | 21 +++++++++++++++---- .../NIOTSConnectionChannel.swift | 16 ++++++++++---- .../NIOTSListenerBootstrap.swift | 17 +++++++++++---- .../NIOTSListenerChannel.swift | 19 ++++++++++++----- .../StateManagedListenerChannel.swift | 16 +++++++++++--- .../NIOTSChannelMetadataTests.swift | 5 +++-- 10 files changed, 124 insertions(+), 34 deletions(-) diff --git a/Sources/NIOTransportServices/Datagram/NIOTSDatagramBootstrap.swift b/Sources/NIOTransportServices/Datagram/NIOTSDatagramBootstrap.swift index e8ea793..0872cc8 100644 --- a/Sources/NIOTransportServices/Datagram/NIOTSDatagramBootstrap.swift +++ b/Sources/NIOTransportServices/Datagram/NIOTSDatagramBootstrap.swift @@ -47,6 +47,7 @@ public final class NIOTSDatagramBootstrap { private var qos: DispatchQoS? private var udpOptions: NWProtocolUDP.Options = .init() private var tlsOptions: NWProtocolTLS.Options? + private var nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? /// Create a `NIOTSDatagramConnectionBootstrap` on the `EventLoopGroup` `group`. /// @@ -133,6 +134,14 @@ public final class NIOTSDatagramBootstrap { return self } + /// Customise the `NWParameters` to be used when creating the connection. + public func configureNWParameters( + _ configurator: @Sendable @escaping (inout NWParameters) -> Void + ) -> Self { + self.nwParametersConfigurator = configurator + return self + } + /// Specify the `host` and `port` to connect to for the UDP `Channel` that will be established. /// /// - parameters: @@ -188,7 +197,8 @@ public final class NIOTSDatagramBootstrap { eventLoop: self.group.next() as! NIOTSEventLoop, qos: self.qos, udpOptions: self.udpOptions, - tlsOptions: self.tlsOptions + tlsOptions: self.tlsOptions, + nwParametersConfigurator: self.nwParametersConfigurator ) let initializer = self.channelInitializer ?? { @Sendable _ in conn.eventLoop.makeSucceededFuture(()) } diff --git a/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift b/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift index 9f9e20c..80d2c7e 100644 --- a/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift +++ b/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift @@ -140,8 +140,12 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { internal var allowLocalEndpointReuse = false internal var multipathServiceType: NWParameters.MultipathServiceType = .disabled + private let nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + var parameters: NWParameters { - NWParameters(dtls: self.tlsOptions, udp: self.udpOptions) + var parameters = NWParameters(dtls: self.tlsOptions, udp: self.udpOptions) + self.nwParametersConfigurator?(¶meters) + return parameters } var _inboundStreamOpen: Bool { @@ -182,7 +186,8 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { minimumIncompleteReceiveLength: Int = 1, maximumReceiveLength: Int = 8192, udpOptions: NWProtocolUDP.Options, - tlsOptions: NWProtocolTLS.Options? + tlsOptions: NWProtocolTLS.Options?, + nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? ) { self.tsEventLoop = eventLoop self.closePromise = eventLoop.makePromise() @@ -192,6 +197,7 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { self.connectionQueue = eventLoop.channelQueue(label: "nio.nioTransportServices.connectionchannel", qos: qos) self.udpOptions = udpOptions self.tlsOptions = tlsOptions + self.nwParametersConfigurator = nwParametersConfigurator // Must come last, as it requires self to be completely initialized. self._pipeline = ChannelPipeline(channel: self) @@ -206,7 +212,8 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { minimumIncompleteReceiveLength: Int = 1, maximumReceiveLength: Int = 8192, udpOptions: NWProtocolUDP.Options, - tlsOptions: NWProtocolTLS.Options? + tlsOptions: NWProtocolTLS.Options?, + nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? ) { self.init( eventLoop: eventLoop, @@ -215,7 +222,8 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { minimumIncompleteReceiveLength: minimumIncompleteReceiveLength, maximumReceiveLength: maximumReceiveLength, udpOptions: udpOptions, - tlsOptions: tlsOptions + tlsOptions: tlsOptions, + nwParametersConfigurator: nwParametersConfigurator ) self.connection = connection } diff --git a/Sources/NIOTransportServices/Datagram/NIOTSDatagramListener.swift b/Sources/NIOTransportServices/Datagram/NIOTSDatagramListener.swift index 7ee0a5c..b81ef96 100644 --- a/Sources/NIOTransportServices/Datagram/NIOTSDatagramListener.swift +++ b/Sources/NIOTransportServices/Datagram/NIOTSDatagramListener.swift @@ -66,6 +66,7 @@ public final class NIOTSDatagramListenerBootstrap { private var udpOptions: NWProtocolUDP.Options = .init() private var tlsOptions: NWProtocolTLS.Options? private var bindTimeout: TimeAmount? + private var nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? /// Create a ``NIOTSListenerBootstrap`` for the `EventLoopGroup` `group`. /// @@ -236,6 +237,14 @@ public final class NIOTSDatagramListenerBootstrap { return self } + /// Customise the `NWParameters` to be used when creating the connection. + public func configureNWParameters( + _ configurator: @Sendable @escaping (inout NWParameters) -> Void + ) -> Self { + self.nwParametersConfigurator = configurator + return self + } + /// Bind the `NIOTSListenerChannel` to `host` and `port`. /// /// - parameters: @@ -327,10 +336,12 @@ public final class NIOTSDatagramListenerBootstrap { qos: self.serverQoS, udpOptions: self.udpOptions, tlsOptions: self.tlsOptions, + nwParametersConfigurator: self.nwParametersConfigurator, childLoopGroup: self.childGroup, childChannelQoS: self.childQoS, childUDPOptions: self.udpOptions, - childTLSOptions: self.tlsOptions + childTLSOptions: self.tlsOptions, + childNWParametersConfigurator: self.nwParametersConfigurator ) } else { serverChannel = NIOTSDatagramListenerChannel( @@ -338,10 +349,12 @@ public final class NIOTSDatagramListenerBootstrap { qos: self.serverQoS, udpOptions: self.udpOptions, tlsOptions: self.tlsOptions, + nwParametersConfigurator: self.nwParametersConfigurator, childLoopGroup: self.childGroup, childChannelQoS: self.childQoS, childUDPOptions: self.udpOptions, - childTLSOptions: self.tlsOptions + childTLSOptions: self.tlsOptions, + childNWParametersConfigurator: self.nwParametersConfigurator ) } diff --git a/Sources/NIOTransportServices/Datagram/NIOTSDatagramListenerChannel.swift b/Sources/NIOTransportServices/Datagram/NIOTSDatagramListenerChannel.swift index 74ba87b..ec504df 100644 --- a/Sources/NIOTransportServices/Datagram/NIOTSDatagramListenerChannel.swift +++ b/Sources/NIOTransportServices/Datagram/NIOTSDatagramListenerChannel.swift @@ -81,19 +81,23 @@ internal final class NIOTSDatagramListenerChannel: StateManagedListenerChannel Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childUDPOptions: NWProtocolUDP.Options, - childTLSOptions: NWProtocolTLS.Options? + childTLSOptions: NWProtocolTLS.Options?, + childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? ) { self.init( eventLoop: eventLoop, protocolOptions: .udp(udpOptions), tlsOptions: tlsOptions, + nwParametersConfigurator: nwParametersConfigurator, childLoopGroup: childLoopGroup, childChannelQoS: childChannelQoS, childProtocolOptions: .udp(childUDPOptions), - childTLSOptions: childTLSOptions + childTLSOptions: childTLSOptions, + childNWParametersConfigurator: childNWParametersConfigurator ) } @@ -104,20 +108,24 @@ internal final class NIOTSDatagramListenerChannel: StateManagedListenerChannel Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childUDPOptions: NWProtocolUDP.Options, - childTLSOptions: NWProtocolTLS.Options? + childTLSOptions: NWProtocolTLS.Options?, + childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? ) { self.init( wrapping: listener, eventLoop: eventLoop, protocolOptions: .udp(udpOptions), tlsOptions: tlsOptions, + nwParametersConfigurator: nwParametersConfigurator, childLoopGroup: childLoopGroup, childChannelQoS: childChannelQoS, childProtocolOptions: .udp(childUDPOptions), - childTLSOptions: childTLSOptions + childTLSOptions: childTLSOptions, + childNWParametersConfigurator: childNWParametersConfigurator ) } @@ -132,7 +140,8 @@ internal final class NIOTSDatagramListenerChannel: StateManagedListenerChannel [ChannelHandler])? = nil + private var nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? /// Create a `NIOTSConnectionBootstrap` on the `EventLoopGroup` `group`. /// @@ -165,6 +166,14 @@ public final class NIOTSConnectionBootstrap { self.channelOption(NIOTSChannelOptions.multipathServiceType, value: type) } + /// Customise the `NWParameters` to be used when creating the connection. + public func configureNWParameters( + _ configurator: @Sendable @escaping (inout NWParameters) -> Void + ) -> Self { + self.nwParametersConfigurator = configurator + return self + } + /// Specify the `host` and `port` to connect to for the TCP `Channel` that will be established. /// /// - parameters: @@ -243,14 +252,16 @@ public final class NIOTSConnectionBootstrap { wrapping: newConnection, on: self.group.next() as! NIOTSEventLoop, tcpOptions: self.tcpOptions, - tlsOptions: self.tlsOptions + tlsOptions: self.tlsOptions, + nwParametersConfigurator: self.nwParametersConfigurator ) } else { conn = NIOTSConnectionChannel( eventLoop: self.group.next() as! NIOTSEventLoop, qos: self.qos, tcpOptions: self.tcpOptions, - tlsOptions: self.tlsOptions + tlsOptions: self.tlsOptions, + nwParametersConfigurator: self.nwParametersConfigurator ) } let initializer = self.channelInitializer @@ -437,14 +448,16 @@ extension NIOTSConnectionBootstrap { wrapping: newConnection, on: self.group.next() as! NIOTSEventLoop, tcpOptions: self.tcpOptions, - tlsOptions: self.tlsOptions + tlsOptions: self.tlsOptions, + nwParametersConfigurator: self.nwParametersConfigurator ) } else { connectionChannel = NIOTSConnectionChannel( eventLoop: self.group.next() as! NIOTSEventLoop, qos: self.qos, tcpOptions: self.tcpOptions, - tlsOptions: self.tlsOptions + tlsOptions: self.tlsOptions, + nwParametersConfigurator: self.nwParametersConfigurator ) } let initializer = self.channelInitializer diff --git a/Sources/NIOTransportServices/NIOTSConnectionChannel.swift b/Sources/NIOTransportServices/NIOTSConnectionChannel.swift index a29258f..4588d59 100644 --- a/Sources/NIOTransportServices/NIOTSConnectionChannel.swift +++ b/Sources/NIOTransportServices/NIOTSConnectionChannel.swift @@ -164,8 +164,12 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { /// An `EventLoopPromise` that will be succeeded or failed when a connection attempt succeeds or fails. internal var connectPromise: EventLoopPromise? + private let nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + internal var parameters: NWParameters { - NWParameters(tls: self.tlsOptions, tcp: self.tcpOptions) + var parameters = NWParameters(tls: self.tlsOptions, tcp: self.tcpOptions) + self.nwParametersConfigurator?(¶meters) + return parameters } /// The TCP options for this connection. @@ -242,7 +246,8 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { minimumIncompleteReceiveLength: Int = 1, maximumReceiveLength: Int = 8192, tcpOptions: NWProtocolTCP.Options, - tlsOptions: NWProtocolTLS.Options? + tlsOptions: NWProtocolTLS.Options?, + nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? ) { self.tsEventLoop = eventLoop self.closePromise = eventLoop.makePromise() @@ -252,6 +257,7 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { self.connectionQueue = eventLoop.channelQueue(label: "nio.nioTransportServices.connectionchannel", qos: qos) self.tcpOptions = tcpOptions self.tlsOptions = tlsOptions + self.nwParametersConfigurator = nwParametersConfigurator // Must come last, as it requires self to be completely initialized. self._pipeline = ChannelPipeline(channel: self) @@ -266,7 +272,8 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { minimumIncompleteReceiveLength: Int = 1, maximumReceiveLength: Int = 8192, tcpOptions: NWProtocolTCP.Options, - tlsOptions: NWProtocolTLS.Options? + tlsOptions: NWProtocolTLS.Options?, + nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? ) { self.init( eventLoop: eventLoop, @@ -275,7 +282,8 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { minimumIncompleteReceiveLength: minimumIncompleteReceiveLength, maximumReceiveLength: maximumReceiveLength, tcpOptions: tcpOptions, - tlsOptions: tlsOptions + tlsOptions: tlsOptions, + nwParametersConfigurator: nwParametersConfigurator ) self.connection = connection } diff --git a/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift b/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift index dd36a0f..d621cfe 100644 --- a/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift +++ b/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift @@ -66,6 +66,7 @@ public final class NIOTSListenerBootstrap { private var tcpOptions: NWProtocolTCP.Options = .init() private var tlsOptions: NWProtocolTLS.Options? private var bindTimeout: TimeAmount? + private var nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? /// Create a ``NIOTSListenerBootstrap`` for the `EventLoopGroup` `group`. /// @@ -337,10 +338,12 @@ public final class NIOTSListenerBootstrap { qos: self.serverQoS, tcpOptions: self.tcpOptions, tlsOptions: self.tlsOptions, + nwParametersConfigurator: self.nwParametersConfigurator, childLoopGroup: self.childGroup, childChannelQoS: self.childQoS, childTCPOptions: self.tcpOptions, - childTLSOptions: self.tlsOptions + childTLSOptions: self.tlsOptions, + childNWParametersConfigurator: self.nwParametersConfigurator ) } else { serverChannel = NIOTSListenerChannel( @@ -348,10 +351,12 @@ public final class NIOTSListenerBootstrap { qos: self.serverQoS, tcpOptions: self.tcpOptions, tlsOptions: self.tlsOptions, + nwParametersConfigurator: self.nwParametersConfigurator, childLoopGroup: self.childGroup, childChannelQoS: self.childQoS, childTCPOptions: self.tcpOptions, - childTLSOptions: self.tlsOptions + childTLSOptions: self.tlsOptions, + childNWParametersConfigurator: self.nwParametersConfigurator ) } @@ -558,10 +563,12 @@ extension NIOTSListenerBootstrap { qos: self.serverQoS, tcpOptions: self.tcpOptions, tlsOptions: self.tlsOptions, + nwParametersConfigurator: self.nwParametersConfigurator, childLoopGroup: self.childGroup, childChannelQoS: self.childQoS, childTCPOptions: self.tcpOptions, - childTLSOptions: self.tlsOptions + childTLSOptions: self.tlsOptions, + childNWParametersConfigurator: self.nwParametersConfigurator ) } else { serverChannel = NIOTSListenerChannel( @@ -569,10 +576,12 @@ extension NIOTSListenerBootstrap { qos: self.serverQoS, tcpOptions: self.tcpOptions, tlsOptions: self.tlsOptions, + nwParametersConfigurator: self.nwParametersConfigurator, childLoopGroup: self.childGroup, childChannelQoS: self.childQoS, childTCPOptions: self.tcpOptions, - childTLSOptions: self.tlsOptions + childTLSOptions: self.tlsOptions, + childNWParametersConfigurator: self.nwParametersConfigurator ) } diff --git a/Sources/NIOTransportServices/NIOTSListenerChannel.swift b/Sources/NIOTransportServices/NIOTSListenerChannel.swift index cbf8079..b14d9d3 100644 --- a/Sources/NIOTransportServices/NIOTSListenerChannel.swift +++ b/Sources/NIOTransportServices/NIOTSListenerChannel.swift @@ -81,19 +81,23 @@ internal final class NIOTSListenerChannel: StateManagedListenerChannel Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childTCPOptions: NWProtocolTCP.Options, - childTLSOptions: NWProtocolTLS.Options? + childTLSOptions: NWProtocolTLS.Options?, + childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? ) { self.init( eventLoop: eventLoop, protocolOptions: .tcp(tcpOptions), tlsOptions: tlsOptions, + nwParametersConfigurator: nwParametersConfigurator, childLoopGroup: childLoopGroup, childChannelQoS: childChannelQoS, childProtocolOptions: .tcp(childTCPOptions), - childTLSOptions: childTLSOptions + childTLSOptions: childTLSOptions, + childNWParametersConfigurator: childNWParametersConfigurator ) } @@ -104,10 +108,12 @@ internal final class NIOTSListenerChannel: StateManagedListenerChannel Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childTCPOptions: NWProtocolTCP.Options, - childTLSOptions: NWProtocolTLS.Options? + childTLSOptions: NWProtocolTLS.Options?, + childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? ) { self.init( wrapping: listener, @@ -115,10 +121,12 @@ internal final class NIOTSListenerChannel: StateManagedListenerChannel: S /// The TLS options for this listener. internal let tlsOptions: NWProtocolTLS.Options? + internal var nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + /// The `DispatchQueue` that socket events for this connection will be dispatched onto. internal let connectionQueue: DispatchQueue @@ -113,6 +115,8 @@ internal class StateManagedListenerChannel: S /// The TLS options to use for child channels. internal let childTLSOptions: NWProtocolTLS.Options? + internal var childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + /// The cache of the local and remote socket addresses. Must be accessed using _addressCacheLock. internal var addressCache = AddressCache(local: nil, remote: nil) @@ -130,10 +134,12 @@ internal class StateManagedListenerChannel: S qos: DispatchQoS? = nil, protocolOptions: ProtocolOptions, tlsOptions: NWProtocolTLS.Options?, + nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childProtocolOptions: ProtocolOptions, - childTLSOptions: NWProtocolTLS.Options? + childTLSOptions: NWProtocolTLS.Options?, + childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? ) { self.tsEventLoop = eventLoop self.closePromise = eventLoop.makePromise() @@ -155,20 +161,24 @@ internal class StateManagedListenerChannel: S qos: DispatchQoS? = nil, protocolOptions: ProtocolOptions, tlsOptions: NWProtocolTLS.Options?, + nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childProtocolOptions: ProtocolOptions, - childTLSOptions: NWProtocolTLS.Options? + childTLSOptions: NWProtocolTLS.Options?, + childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? ) { self.init( eventLoop: eventLoop, qos: qos, protocolOptions: protocolOptions, tlsOptions: tlsOptions, + nwParametersConfigurator: nwParametersConfigurator, childLoopGroup: childLoopGroup, childChannelQoS: childChannelQoS, childProtocolOptions: childProtocolOptions, - childTLSOptions: childTLSOptions + childTLSOptions: childTLSOptions, + childNWParametersConfigurator: childNWParametersConfigurator ) self.nwListener = listener } diff --git a/Tests/NIOTransportServicesTests/NIOTSChannelMetadataTests.swift b/Tests/NIOTransportServicesTests/NIOTSChannelMetadataTests.swift index f26aa04..7cb8822 100644 --- a/Tests/NIOTransportServicesTests/NIOTSChannelMetadataTests.swift +++ b/Tests/NIOTransportServicesTests/NIOTSChannelMetadataTests.swift @@ -37,13 +37,14 @@ final class NIOTSChannelMetadataTests: XCTestCase { }.wait() } - func testThowsIfCalledOnANonInitializedChannel() { + func testThrowsIfCalledOnANonInitializedChannel() { let eventLoopGroup = NIOTSEventLoopGroup() defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let channel = NIOTSConnectionChannel( eventLoop: eventLoopGroup.next() as! NIOTSEventLoop, tcpOptions: .init(), - tlsOptions: .init() + tlsOptions: .init(), + nwParametersConfigurator: nil ) XCTAssertThrowsError(try channel.getMetadata(definition: NWProtocolTLS.definition).wait()) { error in XCTAssertTrue(error is NIOTSConnectionNotInitialized, "unexpected error \(error)") From fca19c5367ce5307bf5dbec04bcf862e4b0c7829 Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Wed, 9 Apr 2025 14:41:54 +0100 Subject: [PATCH 02/11] Undo NWParameters as inout in configurator closure NWParameters is a class, so we don't need it to be inout when passing it to the configurator closure --- .../Datagram/NIOTSDatagramBootstrap.swift | 4 ++-- .../Datagram/NIOTSDatagramChannel.swift | 10 +++++----- .../Datagram/NIOTSDatagramListener.swift | 4 ++-- .../Datagram/NIOTSDatagramListenerChannel.swift | 8 ++++---- .../NIOTSConnectionBootstrap.swift | 4 ++-- .../NIOTSConnectionChannel.swift | 10 +++++----- .../NIOTSListenerBootstrap.swift | 2 +- .../NIOTransportServices/NIOTSListenerChannel.swift | 8 ++++---- .../StateManagedListenerChannel.swift | 12 ++++++------ 9 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Sources/NIOTransportServices/Datagram/NIOTSDatagramBootstrap.swift b/Sources/NIOTransportServices/Datagram/NIOTSDatagramBootstrap.swift index 0872cc8..5ed0e40 100644 --- a/Sources/NIOTransportServices/Datagram/NIOTSDatagramBootstrap.swift +++ b/Sources/NIOTransportServices/Datagram/NIOTSDatagramBootstrap.swift @@ -47,7 +47,7 @@ public final class NIOTSDatagramBootstrap { private var qos: DispatchQoS? private var udpOptions: NWProtocolUDP.Options = .init() private var tlsOptions: NWProtocolTLS.Options? - private var nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + private var nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? /// Create a `NIOTSDatagramConnectionBootstrap` on the `EventLoopGroup` `group`. /// @@ -136,7 +136,7 @@ public final class NIOTSDatagramBootstrap { /// Customise the `NWParameters` to be used when creating the connection. public func configureNWParameters( - _ configurator: @Sendable @escaping (inout NWParameters) -> Void + _ configurator: @Sendable @escaping (NWParameters) -> Void ) -> Self { self.nwParametersConfigurator = configurator return self diff --git a/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift b/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift index 80d2c7e..7bbba93 100644 --- a/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift +++ b/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift @@ -140,11 +140,11 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { internal var allowLocalEndpointReuse = false internal var multipathServiceType: NWParameters.MultipathServiceType = .disabled - private let nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + private let nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? var parameters: NWParameters { - var parameters = NWParameters(dtls: self.tlsOptions, udp: self.udpOptions) - self.nwParametersConfigurator?(¶meters) + let parameters = NWParameters(dtls: self.tlsOptions, udp: self.udpOptions) + self.nwParametersConfigurator?(parameters) return parameters } @@ -187,7 +187,7 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { maximumReceiveLength: Int = 8192, udpOptions: NWProtocolUDP.Options, tlsOptions: NWProtocolTLS.Options?, - nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? ) { self.tsEventLoop = eventLoop self.closePromise = eventLoop.makePromise() @@ -213,7 +213,7 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { maximumReceiveLength: Int = 8192, udpOptions: NWProtocolUDP.Options, tlsOptions: NWProtocolTLS.Options?, - nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? ) { self.init( eventLoop: eventLoop, diff --git a/Sources/NIOTransportServices/Datagram/NIOTSDatagramListener.swift b/Sources/NIOTransportServices/Datagram/NIOTSDatagramListener.swift index b81ef96..9f6640e 100644 --- a/Sources/NIOTransportServices/Datagram/NIOTSDatagramListener.swift +++ b/Sources/NIOTransportServices/Datagram/NIOTSDatagramListener.swift @@ -66,7 +66,7 @@ public final class NIOTSDatagramListenerBootstrap { private var udpOptions: NWProtocolUDP.Options = .init() private var tlsOptions: NWProtocolTLS.Options? private var bindTimeout: TimeAmount? - private var nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + private var nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? /// Create a ``NIOTSListenerBootstrap`` for the `EventLoopGroup` `group`. /// @@ -239,7 +239,7 @@ public final class NIOTSDatagramListenerBootstrap { /// Customise the `NWParameters` to be used when creating the connection. public func configureNWParameters( - _ configurator: @Sendable @escaping (inout NWParameters) -> Void + _ configurator: @Sendable @escaping (NWParameters) -> Void ) -> Self { self.nwParametersConfigurator = configurator return self diff --git a/Sources/NIOTransportServices/Datagram/NIOTSDatagramListenerChannel.swift b/Sources/NIOTransportServices/Datagram/NIOTSDatagramListenerChannel.swift index ec504df..de8fdec 100644 --- a/Sources/NIOTransportServices/Datagram/NIOTSDatagramListenerChannel.swift +++ b/Sources/NIOTransportServices/Datagram/NIOTSDatagramListenerChannel.swift @@ -81,12 +81,12 @@ internal final class NIOTSDatagramListenerChannel: StateManagedListenerChannel Void)?, + nwParametersConfigurator: (@Sendable (NWParameters) -> Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childUDPOptions: NWProtocolUDP.Options, childTLSOptions: NWProtocolTLS.Options?, - childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + childNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? ) { self.init( eventLoop: eventLoop, @@ -108,12 +108,12 @@ internal final class NIOTSDatagramListenerChannel: StateManagedListenerChannel Void)?, + nwParametersConfigurator: (@Sendable (NWParameters) -> Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childUDPOptions: NWProtocolUDP.Options, childTLSOptions: NWProtocolTLS.Options?, - childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + childNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? ) { self.init( wrapping: listener, diff --git a/Sources/NIOTransportServices/NIOTSConnectionBootstrap.swift b/Sources/NIOTransportServices/NIOTSConnectionBootstrap.swift index ad632d4..4649704 100644 --- a/Sources/NIOTransportServices/NIOTSConnectionBootstrap.swift +++ b/Sources/NIOTransportServices/NIOTSConnectionBootstrap.swift @@ -62,7 +62,7 @@ public final class NIOTSConnectionBootstrap { private var tcpOptions: NWProtocolTCP.Options = .init() private var tlsOptions: NWProtocolTLS.Options? private var protocolHandlers: (@Sendable () -> [ChannelHandler])? = nil - private var nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + private var nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? /// Create a `NIOTSConnectionBootstrap` on the `EventLoopGroup` `group`. /// @@ -168,7 +168,7 @@ public final class NIOTSConnectionBootstrap { /// Customise the `NWParameters` to be used when creating the connection. public func configureNWParameters( - _ configurator: @Sendable @escaping (inout NWParameters) -> Void + _ configurator: @Sendable @escaping (NWParameters) -> Void ) -> Self { self.nwParametersConfigurator = configurator return self diff --git a/Sources/NIOTransportServices/NIOTSConnectionChannel.swift b/Sources/NIOTransportServices/NIOTSConnectionChannel.swift index 4588d59..11c3f87 100644 --- a/Sources/NIOTransportServices/NIOTSConnectionChannel.swift +++ b/Sources/NIOTransportServices/NIOTSConnectionChannel.swift @@ -164,11 +164,11 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { /// An `EventLoopPromise` that will be succeeded or failed when a connection attempt succeeds or fails. internal var connectPromise: EventLoopPromise? - private let nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + private let nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? internal var parameters: NWParameters { - var parameters = NWParameters(tls: self.tlsOptions, tcp: self.tcpOptions) - self.nwParametersConfigurator?(¶meters) + let parameters = NWParameters(tls: self.tlsOptions, tcp: self.tcpOptions) + self.nwParametersConfigurator?(parameters) return parameters } @@ -247,7 +247,7 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { maximumReceiveLength: Int = 8192, tcpOptions: NWProtocolTCP.Options, tlsOptions: NWProtocolTLS.Options?, - nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? ) { self.tsEventLoop = eventLoop self.closePromise = eventLoop.makePromise() @@ -273,7 +273,7 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { maximumReceiveLength: Int = 8192, tcpOptions: NWProtocolTCP.Options, tlsOptions: NWProtocolTLS.Options?, - nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? ) { self.init( eventLoop: eventLoop, diff --git a/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift b/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift index d621cfe..22b1280 100644 --- a/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift +++ b/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift @@ -66,7 +66,7 @@ public final class NIOTSListenerBootstrap { private var tcpOptions: NWProtocolTCP.Options = .init() private var tlsOptions: NWProtocolTLS.Options? private var bindTimeout: TimeAmount? - private var nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + private var nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? /// Create a ``NIOTSListenerBootstrap`` for the `EventLoopGroup` `group`. /// diff --git a/Sources/NIOTransportServices/NIOTSListenerChannel.swift b/Sources/NIOTransportServices/NIOTSListenerChannel.swift index b14d9d3..d8b4d3e 100644 --- a/Sources/NIOTransportServices/NIOTSListenerChannel.swift +++ b/Sources/NIOTransportServices/NIOTSListenerChannel.swift @@ -81,12 +81,12 @@ internal final class NIOTSListenerChannel: StateManagedListenerChannel Void)?, + nwParametersConfigurator: (@Sendable (NWParameters) -> Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childTCPOptions: NWProtocolTCP.Options, childTLSOptions: NWProtocolTLS.Options?, - childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + childNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? ) { self.init( eventLoop: eventLoop, @@ -108,12 +108,12 @@ internal final class NIOTSListenerChannel: StateManagedListenerChannel Void)?, + nwParametersConfigurator: (@Sendable (NWParameters) -> Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childTCPOptions: NWProtocolTCP.Options, childTLSOptions: NWProtocolTLS.Options?, - childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + childNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? ) { self.init( wrapping: listener, diff --git a/Sources/NIOTransportServices/StateManagedListenerChannel.swift b/Sources/NIOTransportServices/StateManagedListenerChannel.swift index 2762ba2..c28d8fb 100644 --- a/Sources/NIOTransportServices/StateManagedListenerChannel.swift +++ b/Sources/NIOTransportServices/StateManagedListenerChannel.swift @@ -67,7 +67,7 @@ internal class StateManagedListenerChannel: S /// The TLS options for this listener. internal let tlsOptions: NWProtocolTLS.Options? - internal var nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + internal var nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? /// The `DispatchQueue` that socket events for this connection will be dispatched onto. internal let connectionQueue: DispatchQueue @@ -115,7 +115,7 @@ internal class StateManagedListenerChannel: S /// The TLS options to use for child channels. internal let childTLSOptions: NWProtocolTLS.Options? - internal var childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + internal var childNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? /// The cache of the local and remote socket addresses. Must be accessed using _addressCacheLock. internal var addressCache = AddressCache(local: nil, remote: nil) @@ -134,12 +134,12 @@ internal class StateManagedListenerChannel: S qos: DispatchQoS? = nil, protocolOptions: ProtocolOptions, tlsOptions: NWProtocolTLS.Options?, - nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)?, + nwParametersConfigurator: (@Sendable (NWParameters) -> Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childProtocolOptions: ProtocolOptions, childTLSOptions: NWProtocolTLS.Options?, - childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + childNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? ) { self.tsEventLoop = eventLoop self.closePromise = eventLoop.makePromise() @@ -161,12 +161,12 @@ internal class StateManagedListenerChannel: S qos: DispatchQoS? = nil, protocolOptions: ProtocolOptions, tlsOptions: NWProtocolTLS.Options?, - nwParametersConfigurator: (@Sendable (inout NWParameters) -> Void)?, + nwParametersConfigurator: (@Sendable (NWParameters) -> Void)?, childLoopGroup: EventLoopGroup, childChannelQoS: DispatchQoS?, childProtocolOptions: ProtocolOptions, childTLSOptions: NWProtocolTLS.Options?, - childNWParametersConfigurator: (@Sendable (inout NWParameters) -> Void)? + childNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? ) { self.init( eventLoop: eventLoop, From f129546483bd20f8cc67a5bc1ee2a0e2337369f0 Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Wed, 9 Apr 2025 14:42:44 +0100 Subject: [PATCH 03/11] Add missed initialization of the configurator to StateManagedListenerChannel --- Sources/NIOTransportServices/StateManagedListenerChannel.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/NIOTransportServices/StateManagedListenerChannel.swift b/Sources/NIOTransportServices/StateManagedListenerChannel.swift index c28d8fb..8dc3501 100644 --- a/Sources/NIOTransportServices/StateManagedListenerChannel.swift +++ b/Sources/NIOTransportServices/StateManagedListenerChannel.swift @@ -146,10 +146,12 @@ internal class StateManagedListenerChannel: S self.connectionQueue = eventLoop.channelQueue(label: "nio.transportservices.listenerchannel", qos: qos) self.protocolOptions = protocolOptions self.tlsOptions = tlsOptions + self.nwParametersConfigurator = nwParametersConfigurator self.childLoopGroup = childLoopGroup self.childChannelQoS = childChannelQoS self.childProtocolOptions = childProtocolOptions self.childTLSOptions = childTLSOptions + self.childNWParametersConfigurator = childNWParametersConfigurator // Must come last, as it requires self to be completely initialized. self._pipeline = ChannelPipeline(channel: self) From d61bf6d0985844dc4ca8c648fc1cd853b6b70bb1 Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Wed, 9 Apr 2025 14:42:57 +0100 Subject: [PATCH 04/11] Add missing configurator method to the NIOTSListenerBootstrap --- Sources/NIOTransportServices/NIOTSListenerBootstrap.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift b/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift index 22b1280..ef4cd32 100644 --- a/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift +++ b/Sources/NIOTransportServices/NIOTSListenerBootstrap.swift @@ -240,6 +240,14 @@ public final class NIOTSListenerBootstrap { return self } + /// Customise the `NWParameters` to be used when creating the connection. + public func configureNWParameters( + _ configurator: @Sendable @escaping (NWParameters) -> Void + ) -> Self { + self.nwParametersConfigurator = configurator + return self + } + /// Specifies a type of Multipath service to use for this listener, instead of the default /// service type for the event loop. /// From b37cc8bd40a3b8b3030a70d0097e2ffe21de278b Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Wed, 9 Apr 2025 14:43:09 +0100 Subject: [PATCH 05/11] Add missing call to configurator to NIOTSManagedListenerChannel --- Sources/NIOTransportServices/StateManagedListenerChannel.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/NIOTransportServices/StateManagedListenerChannel.swift b/Sources/NIOTransportServices/StateManagedListenerChannel.swift index 8dc3501..42dcb7c 100644 --- a/Sources/NIOTransportServices/StateManagedListenerChannel.swift +++ b/Sources/NIOTransportServices/StateManagedListenerChannel.swift @@ -410,6 +410,8 @@ extension StateManagedListenerChannel { parameters.multipathServiceType = self.multipathServiceType + self.nwParametersConfigurator?(parameters) + let listener: NWListener do { listener = try NWListener(using: parameters) From 7a8dcde5826d9c70dfcf6b2980e5aef3ff6026b9 Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Wed, 9 Apr 2025 14:43:16 +0100 Subject: [PATCH 06/11] Add tests --- .../NIOTSBootstrapTests.swift | 30 ++++++++++++++++++ .../NIOTSDatagramConnectionChannelTests.swift | 31 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift b/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift index 33c0bd0..c6242cc 100644 --- a/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift +++ b/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift @@ -371,6 +371,36 @@ final class NIOTSBootstrapTests: XCTestCase { XCTAssertEqual(try listenerChannel.getOption(NIOTSChannelOptions.multipathServiceType).wait(), .handover) XCTAssertEqual(try connectionChannel.getOption(NIOTSChannelOptions.multipathServiceType).wait(), .handover) } + + func testNWParametersConfigurator() throws { + let group = NIOTSEventLoopGroup() + defer { + try! group.syncShutdownGracefully() + } + + let configuratorListenerCounter = NIOLockedValueBox(0) + let configuratorConnectionCounter = NIOLockedValueBox(0) + + let listenerChannel = try NIOTSListenerBootstrap(group: group) + .configureNWParameters { _ in + configuratorListenerCounter.withLockedValue { $0 += 1 } + } + .bind(host: "localhost", port: 0) + .wait() + + let connectionChannel: Channel = try NIOTSConnectionBootstrap(group: group) + .configureNWParameters { _ in + configuratorConnectionCounter.withLockedValue { $0 += 1 } + } + .connect(to: listenerChannel.localAddress!) + .wait() + + try listenerChannel.close().wait() + try connectionChannel.close().wait() + + XCTAssertEqual(1, configuratorListenerCounter.withLockedValue { $0 }) + XCTAssertEqual(1, configuratorConnectionCounter.withLockedValue { $0 }) + } } extension Channel { diff --git a/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift b/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift index 9b74395..b9f8b2f 100644 --- a/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift +++ b/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift @@ -18,6 +18,7 @@ import Network import NIOCore import NIOTransportServices import Foundation +import NIOConcurrencyHelpers extension Channel { func wait(for type: T.Type, count: Int) throws -> [T] { @@ -232,6 +233,36 @@ final class NIOTSDatagramConnectionChannelTests: XCTestCase { XCTAssertNoThrow(try connection.close().wait()) } + func testNWParametersConfigurator() throws { + let group = NIOTSEventLoopGroup() + defer { + try! group.syncShutdownGracefully() + } + + let configuratorListenerCounter = NIOLockedValueBox(0) + let configuratorConnectionCounter = NIOLockedValueBox(0) + + let listenerChannel = try NIOTSDatagramListenerBootstrap(group: group) + .configureNWParameters { _ in + configuratorListenerCounter.withLockedValue { $0 += 1 } + } + .bind(host: "localhost", port: 0) + .wait() + + let connectionChannel: Channel = try NIOTSDatagramBootstrap(group: group) + .configureNWParameters { _ in + configuratorConnectionCounter.withLockedValue { $0 += 1 } + } + .connect(to: listenerChannel.localAddress!) + .wait() + + try listenerChannel.close().wait() + try connectionChannel.close().wait() + + XCTAssertEqual(1, configuratorListenerCounter.withLockedValue { $0 }) + XCTAssertEqual(1, configuratorConnectionCounter.withLockedValue { $0 }) + } + func testCanExtractTheConnection() throws { guard #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) else { throw XCTSkip("Option not available") From 9e6e4ed7abe5121807a3976a25abd9801dcf5083 Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Thu, 10 Apr 2025 16:01:27 +0100 Subject: [PATCH 07/11] Turn vars into lets --- .../NIOTransportServices/StateManagedListenerChannel.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/NIOTransportServices/StateManagedListenerChannel.swift b/Sources/NIOTransportServices/StateManagedListenerChannel.swift index 42dcb7c..4f19c9e 100644 --- a/Sources/NIOTransportServices/StateManagedListenerChannel.swift +++ b/Sources/NIOTransportServices/StateManagedListenerChannel.swift @@ -67,7 +67,8 @@ internal class StateManagedListenerChannel: S /// The TLS options for this listener. internal let tlsOptions: NWProtocolTLS.Options? - internal var nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? + /// A customization point for this listener's `NWParameters`. + internal let nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? /// The `DispatchQueue` that socket events for this connection will be dispatched onto. internal let connectionQueue: DispatchQueue @@ -115,7 +116,8 @@ internal class StateManagedListenerChannel: S /// The TLS options to use for child channels. internal let childTLSOptions: NWProtocolTLS.Options? - internal var childNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? + /// A customization point for each child's `NWParameters`. + internal let childNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? /// The cache of the local and remote socket addresses. Must be accessed using _addressCacheLock. internal var addressCache = AddressCache(local: nil, remote: nil) From 08a31ceb2f7665844ffd78653cb6c9ea79e840bc Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Mon, 14 Apr 2025 14:18:30 +0100 Subject: [PATCH 08/11] Call param configurator when wrapping an existing NWConnection --- .../NIOTransportServices/Datagram/NIOTSDatagramChannel.swift | 2 +- Sources/NIOTransportServices/NIOTSConnectionChannel.swift | 2 +- .../NIOTransportServices/StateManagedNWConnectionChannel.swift | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift b/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift index 7bbba93..25ccfd2 100644 --- a/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift +++ b/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift @@ -140,7 +140,7 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { internal var allowLocalEndpointReuse = false internal var multipathServiceType: NWParameters.MultipathServiceType = .disabled - private let nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? + internal let nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? var parameters: NWParameters { let parameters = NWParameters(dtls: self.tlsOptions, udp: self.udpOptions) diff --git a/Sources/NIOTransportServices/NIOTSConnectionChannel.swift b/Sources/NIOTransportServices/NIOTSConnectionChannel.swift index 11c3f87..9a19f3b 100644 --- a/Sources/NIOTransportServices/NIOTSConnectionChannel.swift +++ b/Sources/NIOTransportServices/NIOTSConnectionChannel.swift @@ -164,7 +164,7 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { /// An `EventLoopPromise` that will be succeeded or failed when a connection attempt succeeds or fails. internal var connectPromise: EventLoopPromise? - private let nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? + internal let nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? internal var parameters: NWParameters { let parameters = NWParameters(tls: self.tlsOptions, tcp: self.tcpOptions) diff --git a/Sources/NIOTransportServices/StateManagedNWConnectionChannel.swift b/Sources/NIOTransportServices/StateManagedNWConnectionChannel.swift index ba37756..792f7e1 100644 --- a/Sources/NIOTransportServices/StateManagedNWConnectionChannel.swift +++ b/Sources/NIOTransportServices/StateManagedNWConnectionChannel.swift @@ -83,6 +83,8 @@ internal protocol StateManagedNWConnectionChannel: StateManagedChannel where Act var multipathServiceType: NWParameters.MultipathServiceType { get } + var nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? { get } + func setChannelSpecificOption0(option: Option, value: Option.Value) throws func getChannelSpecificOption0(option: Option) throws -> Option.Value @@ -242,6 +244,7 @@ extension StateManagedNWConnectionChannel { connection.betterPathUpdateHandler = self.betterPathHandler connection.viabilityUpdateHandler = self.viabilityUpdateHandler connection.pathUpdateHandler = self.pathChangedHandler(newPath:) + self.nwParametersConfigurator?(connection.parameters) connection.start(queue: self.connectionQueue) } From c90c6432fd5abb3ad3b771a2782a3d34c0fe730f Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Mon, 14 Apr 2025 14:18:35 +0100 Subject: [PATCH 09/11] Add tests --- .../NIOTSBootstrapTests.swift | 30 +++++++++------- .../NIOTSDatagramConnectionChannelTests.swift | 34 ++++++++++++------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift b/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift index c6242cc..3d4264e 100644 --- a/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift +++ b/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift @@ -18,7 +18,7 @@ import XCTest import Network import NIOCore import NIOEmbedded -import NIOTransportServices +@testable import NIOTransportServices import NIOConcurrencyHelpers import Foundation @@ -372,34 +372,40 @@ final class NIOTSBootstrapTests: XCTestCase { XCTAssertEqual(try connectionChannel.getOption(NIOTSChannelOptions.multipathServiceType).wait(), .handover) } - func testNWParametersConfigurator() throws { + func testNWParametersConfigurator() async throws { let group = NIOTSEventLoopGroup() - defer { - try! group.syncShutdownGracefully() - } let configuratorListenerCounter = NIOLockedValueBox(0) let configuratorConnectionCounter = NIOLockedValueBox(0) - let listenerChannel = try NIOTSListenerBootstrap(group: group) + let listenerChannel = try await NIOTSListenerBootstrap(group: group) .configureNWParameters { _ in configuratorListenerCounter.withLockedValue { $0 += 1 } } .bind(host: "localhost", port: 0) - .wait() + .get() - let connectionChannel: Channel = try NIOTSConnectionBootstrap(group: group) + let connectionChannel: Channel = try await NIOTSConnectionBootstrap(group: group) .configureNWParameters { _ in configuratorConnectionCounter.withLockedValue { $0 += 1 } } .connect(to: listenerChannel.localAddress!) - .wait() + .get() - try listenerChannel.close().wait() - try connectionChannel.close().wait() + // Need to wait for the connection channel to be activated. We cannot wait for the connect + // promise (`StateManagedNWConnectionChannel/connectPromise`) from here, because it is + // nulled out as soon as it's completed, and the listener channel doesn't wait on it either. + // If we don't wait, we can race with the connection channel's parameter configurator + // closure being run, and the assertion below would fail. + try await Task.sleep(for: .milliseconds(100)) - XCTAssertEqual(1, configuratorListenerCounter.withLockedValue { $0 }) + try await listenerChannel.close().get() + try await connectionChannel.close().get() + + XCTAssertEqual(2, configuratorListenerCounter.withLockedValue { $0 }) XCTAssertEqual(1, configuratorConnectionCounter.withLockedValue { $0 }) + + try await group.shutdownGracefully() } } diff --git a/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift b/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift index b9f8b2f..8efd327 100644 --- a/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift +++ b/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift @@ -16,7 +16,7 @@ import XCTest import Network import NIOCore -import NIOTransportServices +@testable import NIOTransportServices import Foundation import NIOConcurrencyHelpers @@ -233,34 +233,44 @@ final class NIOTSDatagramConnectionChannelTests: XCTestCase { XCTAssertNoThrow(try connection.close().wait()) } - func testNWParametersConfigurator() throws { + func testNWParametersConfigurator() async throws { let group = NIOTSEventLoopGroup() - defer { - try! group.syncShutdownGracefully() - } let configuratorListenerCounter = NIOLockedValueBox(0) let configuratorConnectionCounter = NIOLockedValueBox(0) - let listenerChannel = try NIOTSDatagramListenerBootstrap(group: group) + let listenerChannel = try await NIOTSDatagramListenerBootstrap(group: group) +// .childChannelInitializer { connectionChannel in +// print((connectionChannel as! NIOTSDatagramChannel).connectPromise) +// return connectionChannel.eventLoop.makeSucceededFuture(()) +// } .configureNWParameters { _ in configuratorListenerCounter.withLockedValue { $0 += 1 } } .bind(host: "localhost", port: 0) - .wait() + .get() - let connectionChannel: Channel = try NIOTSDatagramBootstrap(group: group) + let connectionChannel: Channel = try await NIOTSDatagramBootstrap(group: group) .configureNWParameters { _ in configuratorConnectionCounter.withLockedValue { $0 += 1 } } .connect(to: listenerChannel.localAddress!) - .wait() + .get() - try listenerChannel.close().wait() - try connectionChannel.close().wait() + // Need to write something so the server can activate the connection channel: this is UDP, + // so there is no handshaking that happens and thus the server cannot know that the + // connection has been established and the channel can be activated. + try await connectionChannel.writeAndFlush(ByteBuffer(bytes: [42])) - XCTAssertEqual(1, configuratorListenerCounter.withLockedValue { $0 }) + try await Task.sleep(for: .milliseconds(100)) + + try await listenerChannel.close().get() + try await connectionChannel.close().get() + + XCTAssertEqual(2, configuratorListenerCounter.withLockedValue { $0 }) XCTAssertEqual(1, configuratorConnectionCounter.withLockedValue { $0 }) + + try await group.shutdownGracefully() } func testCanExtractTheConnection() throws { From d597722cca7857fdc588746758dfd4164ed9ecdf Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Mon, 14 Apr 2025 14:28:41 +0100 Subject: [PATCH 10/11] Replace sleep in tests with waiter channel handler --- .../NIOTSBootstrapTests.swift | 32 ++++++++++++++---- .../NIOTSDatagramConnectionChannelTests.swift | 33 +++++++++++++++---- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift b/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift index 3d4264e..969597a 100644 --- a/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift +++ b/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift @@ -373,12 +373,34 @@ final class NIOTSBootstrapTests: XCTestCase { } func testNWParametersConfigurator() async throws { - let group = NIOTSEventLoopGroup() + final class WaitForConnectionHandler: ChannelInboundHandler, Sendable { + typealias InboundIn = Never + + let connectionPromise: EventLoopPromise + + init(connectionPromise: EventLoopPromise) { + self.connectionPromise = connectionPromise + } + + func channelActive(context: ChannelHandlerContext) { + self.connectionPromise.succeed() + } + } + + let group = NIOTSEventLoopGroup(loopCount: 1) let configuratorListenerCounter = NIOLockedValueBox(0) let configuratorConnectionCounter = NIOLockedValueBox(0) + let waitForConnectionHandler = WaitForConnectionHandler( + connectionPromise: group.next().makePromise() + ) let listenerChannel = try await NIOTSListenerBootstrap(group: group) + .childChannelInitializer { connectionChannel in + connectionChannel.eventLoop.makeCompletedFuture { + try connectionChannel.pipeline.syncOperations.addHandler(waitForConnectionHandler) + } + } .configureNWParameters { _ in configuratorListenerCounter.withLockedValue { $0 += 1 } } @@ -392,12 +414,8 @@ final class NIOTSBootstrapTests: XCTestCase { .connect(to: listenerChannel.localAddress!) .get() - // Need to wait for the connection channel to be activated. We cannot wait for the connect - // promise (`StateManagedNWConnectionChannel/connectPromise`) from here, because it is - // nulled out as soon as it's completed, and the listener channel doesn't wait on it either. - // If we don't wait, we can race with the connection channel's parameter configurator - // closure being run, and the assertion below would fail. - try await Task.sleep(for: .milliseconds(100)) + // Wait for the server to activate the connection channel to the client. + try await waitForConnectionHandler.connectionPromise.futureResult.get() try await listenerChannel.close().get() try await connectionChannel.close().get() diff --git a/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift b/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift index 8efd327..39e1c99 100644 --- a/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift +++ b/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift @@ -16,7 +16,7 @@ import XCTest import Network import NIOCore -@testable import NIOTransportServices +import NIOTransportServices import Foundation import NIOConcurrencyHelpers @@ -234,16 +234,34 @@ final class NIOTSDatagramConnectionChannelTests: XCTestCase { } func testNWParametersConfigurator() async throws { - let group = NIOTSEventLoopGroup() + final class WaitForConnectionHandler: ChannelInboundHandler, Sendable { + typealias InboundIn = Never + + let connectionPromise: EventLoopPromise + + init(connectionPromise: EventLoopPromise) { + self.connectionPromise = connectionPromise + } + + func channelActive(context: ChannelHandlerContext) { + self.connectionPromise.succeed() + } + } + + let group = NIOTSEventLoopGroup(loopCount: 1) let configuratorListenerCounter = NIOLockedValueBox(0) let configuratorConnectionCounter = NIOLockedValueBox(0) + let waitForConnectionHandler = WaitForConnectionHandler( + connectionPromise: group.next().makePromise() + ) let listenerChannel = try await NIOTSDatagramListenerBootstrap(group: group) -// .childChannelInitializer { connectionChannel in -// print((connectionChannel as! NIOTSDatagramChannel).connectPromise) -// return connectionChannel.eventLoop.makeSucceededFuture(()) -// } + .childChannelInitializer { connectionChannel in + connectionChannel.eventLoop.makeCompletedFuture { + try connectionChannel.pipeline.syncOperations.addHandler(waitForConnectionHandler) + } + } .configureNWParameters { _ in configuratorListenerCounter.withLockedValue { $0 += 1 } } @@ -262,7 +280,8 @@ final class NIOTSDatagramConnectionChannelTests: XCTestCase { // connection has been established and the channel can be activated. try await connectionChannel.writeAndFlush(ByteBuffer(bytes: [42])) - try await Task.sleep(for: .milliseconds(100)) + // Wait for the server to activate the connection channel to the client. + try await waitForConnectionHandler.connectionPromise.futureResult.get() try await listenerChannel.close().get() try await connectionChannel.close().get() From a19c933c37007aedf9d94b16958da229493bc4c1 Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Tue, 15 Apr 2025 15:08:35 +0100 Subject: [PATCH 11/11] PR changes --- .../NIOTSBootstrapTests.swift | 12 ++++++++---- .../NIOTSDatagramConnectionChannelTests.swift | 10 +++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift b/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift index 969597a..4a00dd4 100644 --- a/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift +++ b/Tests/NIOTransportServicesTests/NIOTSBootstrapTests.swift @@ -18,7 +18,7 @@ import XCTest import Network import NIOCore import NIOEmbedded -@testable import NIOTransportServices +import NIOTransportServices import NIOConcurrencyHelpers import Foundation @@ -384,10 +384,16 @@ final class NIOTSBootstrapTests: XCTestCase { func channelActive(context: ChannelHandlerContext) { self.connectionPromise.succeed() + context.pipeline.fireChannelActive() } } - let group = NIOTSEventLoopGroup(loopCount: 1) + let group = NIOTSEventLoopGroup() + defer { + Task { + try await group.shutdownGracefully() + } + } let configuratorListenerCounter = NIOLockedValueBox(0) let configuratorConnectionCounter = NIOLockedValueBox(0) @@ -422,8 +428,6 @@ final class NIOTSBootstrapTests: XCTestCase { XCTAssertEqual(2, configuratorListenerCounter.withLockedValue { $0 }) XCTAssertEqual(1, configuratorConnectionCounter.withLockedValue { $0 }) - - try await group.shutdownGracefully() } } diff --git a/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift b/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift index 39e1c99..eca509b 100644 --- a/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift +++ b/Tests/NIOTransportServicesTests/NIOTSDatagramConnectionChannelTests.swift @@ -245,10 +245,16 @@ final class NIOTSDatagramConnectionChannelTests: XCTestCase { func channelActive(context: ChannelHandlerContext) { self.connectionPromise.succeed() + context.fireChannelActive() } } - let group = NIOTSEventLoopGroup(loopCount: 1) + let group = NIOTSEventLoopGroup() + defer { + Task { + try await group.shutdownGracefully() + } + } let configuratorListenerCounter = NIOLockedValueBox(0) let configuratorConnectionCounter = NIOLockedValueBox(0) @@ -288,8 +294,6 @@ final class NIOTSDatagramConnectionChannelTests: XCTestCase { XCTAssertEqual(2, configuratorListenerCounter.withLockedValue { $0 }) XCTAssertEqual(1, configuratorConnectionCounter.withLockedValue { $0 }) - - try await group.shutdownGracefully() } func testCanExtractTheConnection() throws {