Directly support the allowLocalEndpointReuse channel option (#82)
Motivation: The NetworkFramework directly supports the concept of allowLocalEndPointReuse. Currently to get through the nio system, this is mapped to the SO_REUSEADDR option. Now there is a shorthand option for this, it is sad to map from allowLocalEndPointReuse to SO_REUSEADDR and back again. Modifications: Add a new NIOTS channel option for this setting. Set the underlying based on any of new setting, reuseAddr or reusePort. Add a override to interpret the shorthand option directly into the new option. Add shorthand options for client with tests of equivalence to long options. Result: Behaviour is identical but we can feel happier that the option mapping is less confusing.
This commit is contained in:
parent
f9a95e9bbb
commit
cd49a10c4f
|
|
@ -23,7 +23,7 @@ let package = Package(
|
|||
.executable(name: "NIOTSHTTPServer", targets: ["NIOTSHTTPServer"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/apple/swift-nio.git", from: "2.15.0"),
|
||||
.package(url: "https://github.com/apple/swift-nio.git", from: "2.19.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "NIOTransportServices",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ public struct NIOTSChannelOptions {
|
|||
|
||||
public static let enablePeerToPeer = NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption()
|
||||
|
||||
/// - See: NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse
|
||||
public static let allowLocalEndpointReuse = NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse()
|
||||
|
||||
public static let currentPath = NIOTSChannelOptions.Types.NIOTSCurrentPathOption()
|
||||
|
||||
public static let metadata = { (definition: NWProtocolDefinition) -> NIOTSChannelOptions.Types.NIOTSMetadataOption in
|
||||
|
|
@ -55,7 +58,6 @@ extension NIOTSChannelOptions {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
|
||||
/// `NIOTSEnablePeerToPeerOption` controls whether the `Channel` will advertise services using peer-to-peer
|
||||
/// connectivity. Setting this to true is the equivalent of setting `NWParameters.enablePeerToPeer` to
|
||||
/// `true`. By default this option is set to `false`.
|
||||
|
|
@ -68,6 +70,18 @@ extension NIOTSChannelOptions {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
/// `NIOTSAllowLocalEndpointReuse` controls whether the `Channel` can reuse a TCP address recently used.
|
||||
/// Setting this to true is the equivalent of setting at least one of REUSEADDR and REUSEPORT to
|
||||
/// `true`. By default this option is set to `false`.
|
||||
///
|
||||
/// This option must be set on the bootstrap: setting it after the channel is initialized will have no effect.
|
||||
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
|
||||
public struct NIOTSAllowLocalEndpointReuse: ChannelOption, Equatable {
|
||||
public typealias Value = Bool
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
/// `NIOTSCurrentPathOption` accesses the `NWConnection.currentPath` of the underlying connection.
|
||||
///
|
||||
/// This option is only valid with `NIOTSConnectionBootstrap`.
|
||||
|
|
|
|||
|
|
@ -242,5 +242,17 @@ public final class NIOTSConnectionBootstrap {
|
|||
}
|
||||
|
||||
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
|
||||
extension NIOTSConnectionBootstrap: NIOClientTCPBootstrapProtocol {}
|
||||
extension NIOTSConnectionBootstrap: NIOClientTCPBootstrapProtocol {
|
||||
/// Apply any understood shorthand options to the bootstrap, removing them from the set of options if they are consumed.
|
||||
/// - parameters:
|
||||
/// - options: The options to try applying - the options applied should be consumed from here.
|
||||
/// - returns: The updated bootstrap with any understood options applied.
|
||||
public func _applyChannelConvenienceOptions(_ options: inout ChannelOptions.TCPConvenienceOptions) -> Self {
|
||||
var toReturn = self
|
||||
if options.consumeAllowLocalEndpointReuse().isSet {
|
||||
toReturn = self.channelOption(NIOTSChannelOptions.allowLocalEndpointReuse, value: true)
|
||||
}
|
||||
return toReturn
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -217,6 +217,9 @@ internal final class NIOTSConnectionChannel {
|
|||
|
||||
/// The value of SO_REUSEPORT.
|
||||
private var reusePort = false
|
||||
|
||||
/// The value of the allowLocalEndpointReuse option.
|
||||
private var allowLocalEndpointReuse = false
|
||||
|
||||
/// Whether to use peer-to-peer connectivity when connecting to Bonjour services.
|
||||
private var enablePeerToPeer = false
|
||||
|
|
@ -343,6 +346,8 @@ extension NIOTSConnectionChannel: Channel {
|
|||
}
|
||||
case is NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption:
|
||||
self.enablePeerToPeer = value as! NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption.Value
|
||||
case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse:
|
||||
self.allowLocalEndpointReuse = value as! NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption.Value
|
||||
default:
|
||||
fatalError("option \(type(of: option)).\(option) not supported")
|
||||
}
|
||||
|
|
@ -388,6 +393,8 @@ extension NIOTSConnectionChannel: Channel {
|
|||
return self.options.waitForActivity as! Option.Value
|
||||
case is NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption:
|
||||
return self.enablePeerToPeer as! Option.Value
|
||||
case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse:
|
||||
return self.allowLocalEndpointReuse as! Option.Value
|
||||
case is NIOTSChannelOptions.Types.NIOTSCurrentPathOption:
|
||||
guard let currentPath = self.nwConnection?.currentPath else {
|
||||
throw NIOTSErrors.NoCurrentPath()
|
||||
|
|
@ -498,8 +505,8 @@ extension NIOTSConnectionChannel: StateManagedChannel {
|
|||
let parameters = NWParameters(tls: self.tlsOptions, tcp: self.tcpOptions)
|
||||
|
||||
// Network.framework munges REUSEADDR and REUSEPORT together, so we turn this on if we need
|
||||
// either.
|
||||
parameters.allowLocalEndpointReuse = self.reuseAddress || self.reusePort
|
||||
// either or it's been explicitly set.
|
||||
parameters.allowLocalEndpointReuse = self.reuseAddress || self.reusePort || self.allowLocalEndpointReuse
|
||||
|
||||
parameters.includePeerToPeer = self.enablePeerToPeer
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ internal final class NIOTSListenerChannel {
|
|||
|
||||
/// The value of SO_REUSEPORT.
|
||||
private var reusePort = false
|
||||
|
||||
/// The value of the allowLocalEndpointReuse option.
|
||||
private var allowLocalEndpointReuse = false
|
||||
|
||||
/// Whether to enable peer-to-peer connectivity when using Bonjour services.
|
||||
private var enablePeerToPeer = false
|
||||
|
|
@ -195,6 +198,8 @@ extension NIOTSListenerChannel: Channel {
|
|||
}
|
||||
case is NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption:
|
||||
self.enablePeerToPeer = value as! NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption.Value
|
||||
case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse:
|
||||
self.allowLocalEndpointReuse = value as! NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption.Value
|
||||
default:
|
||||
fatalError("option \(option) not supported")
|
||||
}
|
||||
|
|
@ -232,6 +237,8 @@ extension NIOTSListenerChannel: Channel {
|
|||
}
|
||||
case is NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption:
|
||||
return self.enablePeerToPeer as! Option.Value
|
||||
case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse:
|
||||
return self.allowLocalEndpointReuse as! Option.Value
|
||||
default:
|
||||
fatalError("option \(option) not supported")
|
||||
}
|
||||
|
|
@ -309,8 +316,8 @@ extension NIOTSListenerChannel: StateManagedChannel {
|
|||
}
|
||||
|
||||
// Network.framework munges REUSEADDR and REUSEPORT together, so we turn this on if we need
|
||||
// either.
|
||||
parameters.allowLocalEndpointReuse = self.reuseAddress || self.reusePort
|
||||
// either or it's been explicitly set.
|
||||
parameters.allowLocalEndpointReuse = self.reuseAddress || self.reusePort || self.allowLocalEndpointReuse
|
||||
|
||||
parameters.includePeerToPeer = self.enablePeerToPeer
|
||||
|
||||
|
|
|
|||
|
|
@ -246,6 +246,66 @@ final class NIOTSBootstrapTests: XCTestCase {
|
|||
XCTAssertNil(NIOTSListenerBootstrap(validatingGroup: wrongELG, childGroup: correctELG))
|
||||
XCTAssertNil(NIOTSListenerBootstrap(validatingGroup: wrongEL, childGroup: correctEL))
|
||||
}
|
||||
|
||||
func testEndpointReuseShortcutOption() throws {
|
||||
let group = NIOTSEventLoopGroup()
|
||||
let listenerChannel = try NIOTSListenerBootstrap(group: group)
|
||||
.bind(host: "127.0.0.1", port: 0)
|
||||
.wait()
|
||||
|
||||
|
||||
let bootstrap = NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: group),
|
||||
tls: NIOInsecureNoTLS())
|
||||
.channelConvenienceOptions([.allowLocalEndpointReuse])
|
||||
let client = try bootstrap.connect(to: listenerChannel.localAddress!).wait()
|
||||
let optionValue = try client.getOption(NIOTSChannelOptions.allowLocalEndpointReuse).wait()
|
||||
try client.close().wait()
|
||||
|
||||
XCTAssertEqual(optionValue, true)
|
||||
}
|
||||
|
||||
func testShorthandOptionsAreEquivalent() throws {
|
||||
func setAndGetOption<Option>(option: Option,
|
||||
_ applyOptions : (NIOClientTCPBootstrap) -> NIOClientTCPBootstrap)
|
||||
throws -> Option.Value where Option : ChannelOption {
|
||||
let group = NIOTSEventLoopGroup()
|
||||
let listenerChannel = try NIOTSListenerBootstrap(group: group)
|
||||
.bind(host: "127.0.0.1", port: 0)
|
||||
.wait()
|
||||
|
||||
let bootstrap = applyOptions(NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: group),
|
||||
tls: NIOInsecureNoTLS()))
|
||||
let client = try bootstrap.connect(to: listenerChannel.localAddress!).wait()
|
||||
let optionRead = try client.getOption(option).wait()
|
||||
try client.close().wait()
|
||||
return optionRead
|
||||
}
|
||||
|
||||
func checkOptionEquivalence<Option>(longOption: Option, setValue: Option.Value,
|
||||
shortOption: ChannelOptions.TCPConvenienceOption) throws
|
||||
where Option : ChannelOption, Option.Value : Equatable {
|
||||
let longSetValue = try setAndGetOption(option: longOption) { bs in
|
||||
bs.channelOption(longOption, value: setValue)
|
||||
}
|
||||
let shortSetValue = try setAndGetOption(option: longOption) { bs in
|
||||
bs.channelConvenienceOptions([shortOption])
|
||||
}
|
||||
let unsetValue = try setAndGetOption(option: longOption) { $0 }
|
||||
|
||||
XCTAssertEqual(longSetValue, shortSetValue)
|
||||
XCTAssertNotEqual(longSetValue, unsetValue)
|
||||
}
|
||||
|
||||
try checkOptionEquivalence(longOption: NIOTSChannelOptions.allowLocalEndpointReuse,
|
||||
setValue: true,
|
||||
shortOption: .allowLocalEndpointReuse)
|
||||
try checkOptionEquivalence(longOption: ChannelOptions.allowRemoteHalfClosure,
|
||||
setValue: true,
|
||||
shortOption: .allowRemoteHalfClosure)
|
||||
try checkOptionEquivalence(longOption: ChannelOptions.autoRead,
|
||||
setValue: false,
|
||||
shortOption: .disableAutoRead)
|
||||
}
|
||||
}
|
||||
|
||||
extension Channel {
|
||||
|
|
|
|||
Loading…
Reference in New Issue