Fix the syncOptions on most channels (#202)

Motivation:

Looks like when we previously added syncOptions support to our
channels, we had a few issues. The listeners had code added, but
the code never worked. This is because the code was defined in
subclasses, but the protocol conformance comes from the parent class,
and that parent class did not have a customized protocol witness.

The datagram channel was also entirely missing its support.

Modifications:

- Added the missing unit tests for sync options.
- Added syncOptions to StateManagedListenerChannel base class.
- Added overrides to the listener subclasses.
- Added syncOptions to datagram channel

Result:

Sync options actually work across the board.
This commit is contained in:
Cory Benfield 2024-05-13 16:12:01 +01:00 committed by GitHub
parent 4a0fad7658
commit 715e3179d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 105 additions and 9 deletions

View File

@ -152,7 +152,7 @@ public final class NIOTSDatagramBootstrap {
/// - returns: An `EventLoopFuture<Channel>` to deliver the `Channel` when connected.
public func connect(to address: SocketAddress) -> EventLoopFuture<Channel> {
return self.connect0 { channel, promise in
channel.bind(to: address, promise: promise)
channel.connect(to: address, promise: promise)
}
}

View File

@ -187,4 +187,27 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel {
self.connection = connection
}
}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSDatagramChannel {
internal struct SynchronousOptions: NIOSynchronousChannelOptions {
private let channel: NIOTSDatagramChannel
fileprivate init(channel: NIOTSDatagramChannel) {
self.channel = channel
}
public func setOption<Option: ChannelOption>(_ option: Option, value: Option.Value) throws {
try self.channel.setOption0(option: option, value: value)
}
public func getOption<Option: ChannelOption>(_ option: Option) throws -> Option.Value {
return try self.channel.getOption0(option: option)
}
}
public var syncOptions: NIOSynchronousChannelOptions? {
return SynchronousOptions(channel: self)
}
}
#endif

View File

@ -127,10 +127,7 @@ internal final class NIOTSDatagramListenerChannel: StateManagedListenerChannel<N
self.pipeline.fireChannelRead(NIOAny(newChannel))
self.pipeline.fireChannelReadComplete()
}
}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSDatagramListenerChannel {
internal struct SynchronousOptions: NIOSynchronousChannelOptions {
private let channel: NIOTSDatagramListenerChannel
@ -147,7 +144,7 @@ extension NIOTSDatagramListenerChannel {
}
}
public var syncOptions: NIOSynchronousChannelOptions? {
public override var syncOptions: NIOSynchronousChannelOptions? {
return SynchronousOptions(channel: self)
}
}

View File

@ -129,10 +129,7 @@ internal final class NIOTSListenerChannel: StateManagedListenerChannel<NIOTSConn
self.pipeline.fireChannelRead(NIOAny(newChannel))
self.pipeline.fireChannelReadComplete()
}
}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSListenerChannel {
internal struct SynchronousOptions: NIOSynchronousChannelOptions {
private let channel: NIOTSListenerChannel
@ -149,7 +146,7 @@ extension NIOTSListenerChannel {
}
}
public var syncOptions: NIOSynchronousChannelOptions? {
public override var syncOptions: NIOSynchronousChannelOptions? {
return SynchronousOptions(channel: self)
}
}

View File

@ -170,6 +170,13 @@ internal class StateManagedListenerChannel<ChildChannel: StateManagedChannel>: S
func newConnectionHandler(connection: NWConnection) {
fatalError("This function must be overridden by the subclass")
}
// This needs to be declared here to make sure the child classes can override
// the behaviour.
internal var syncOptions: NIOSynchronousChannelOptions? {
return nil
}
}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)

View File

@ -158,5 +158,51 @@ final class NIOTSDatagramConnectionChannelTests: XCTestCase {
XCTAssertEqual(reads.count, 1)
XCTAssertEqual(reads[0], buffer)
}
func testSyncOptionsAreSupported() throws {
func testSyncOptions(_ channel: Channel) {
if let sync = channel.syncOptions {
do {
let endpointReuse = try sync.getOption(NIOTSChannelOptions.allowLocalEndpointReuse)
try sync.setOption(NIOTSChannelOptions.allowLocalEndpointReuse, value: !endpointReuse)
XCTAssertNotEqual(endpointReuse, try sync.getOption(NIOTSChannelOptions.allowLocalEndpointReuse))
} catch {
XCTFail("Could not get/set allowLocalEndpointReuse: \(error)")
}
} else {
XCTFail("\(channel) unexpectedly returned nil syncOptions")
}
}
let promise = self.group.any().makePromise(of: Channel.self)
let listener = try NIOTSDatagramListenerBootstrap(group: self.group)
.serverChannelInitializer { channel in
testSyncOptions(channel)
return channel.eventLoop.makeSucceededVoidFuture()
}
.childChannelInitializer { channel in
testSyncOptions(channel)
promise.succeed(channel)
return channel.pipeline.addHandler(ReadRecorder<ByteBuffer>(), name: "ByteReadRecorder")
}
.bind(host: "localhost", port: 0)
.wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}
let connection = try! NIOTSDatagramBootstrap(group: self.group)
.channelInitializer { channel in
testSyncOptions(channel)
return channel.eventLoop.makeSucceededVoidFuture()
}
.connect(to: listener.localAddress!)
.wait()
try connection.writeAndFlush(ByteBuffer(string: "hello world")).wait()
let serverHandle = try promise.futureResult.wait()
_ = try serverHandle.waitForDatagrams(count: 1)
XCTAssertNoThrow(try connection.close().wait())
}
}
#endif

View File

@ -306,5 +306,31 @@ class NIOTSListenerChannelTests: XCTestCase {
XCTAssertNoThrow(try workFuture.wait())
}
func testSyncOptionsAreSupported() throws {
func testSyncOptions(_ channel: Channel) {
if let sync = channel.syncOptions {
do {
let endpointReuse = try sync.getOption(NIOTSChannelOptions.allowLocalEndpointReuse)
try sync.setOption(NIOTSChannelOptions.allowLocalEndpointReuse, value: !endpointReuse)
XCTAssertNotEqual(endpointReuse, try sync.getOption(NIOTSChannelOptions.allowLocalEndpointReuse))
} catch {
XCTFail("Could not get/set allowLocalEndpointReuse: \(error)")
}
} else {
XCTFail("\(channel) unexpectedly returned nil syncOptions")
}
}
let listener = try NIOTSListenerBootstrap(group: self.group)
.serverChannelInitializer { channel in
testSyncOptions(channel)
return channel.eventLoop.makeSucceededVoidFuture()
}
.bind(host: "localhost", port: 0)
.wait()
XCTAssertNoThrow(try listener.close().wait())
}
}
#endif