Add support for syncOptions to NIOTSListenerChannel and NIOTSConnectionChannel (#117)

Motivation:

NIO 2.27.0 added optional support for synchronous channel options.
NIOTSListenerChannel and NIOTSConnectionChannel should support this.

Modifications:

- Add synchronous options for NIOTSConnectionChannel and
  NIOTSListenerChannel
- Avoid an allocation in 'getOption' for each channel if the caller is
  already on the right event loop by using 'makeSucceededFuture'
- Remove a no longer used internal function and an already dead private
  helper

Result:

- Support for sync options and fewer allocations
This commit is contained in:
George Barnett 2021-03-16 08:38:47 +00:00 committed by GitHub
parent b9fd95b8d5
commit 657537c2cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 98 additions and 49 deletions

View File

@ -23,7 +23,7 @@ let package = Package(
.executable(name: "NIOTSHTTPServer", targets: ["NIOTSHTTPServer"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "2.19.0"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.27.0"),
],
targets: [
.target(name: "NIOTransportServices",

View File

@ -22,31 +22,6 @@ import Dispatch
import Network
import Security
/// Execute the given function and synchronously complete the given `EventLoopPromise` (if not `nil`).
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
func executeAndComplete<T>(_ promise: EventLoopPromise<T>?, _ body: () throws -> T) {
do {
let result = try body()
promise?.succeed(result)
} catch let e {
promise?.fail(e)
}
}
/// Merge two possible promises together such that firing the result will fire both.
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
private func mergePromises(_ first: EventLoopPromise<Void>?, _ second: EventLoopPromise<Void>?) -> EventLoopPromise<Void>? {
if let first = first {
if let second = second {
first.futureResult.cascade(to: second)
}
return first
} else {
return second
}
}
/// Channel options for the connection channel.
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
private struct ConnectionChannelOptions {
@ -298,17 +273,15 @@ extension NIOTSConnectionChannel: Channel {
}
public func setOption<Option: ChannelOption>(_ option: Option, value: Option.Value) -> EventLoopFuture<Void> {
if eventLoop.inEventLoop {
let promise: EventLoopPromise<Void> = eventLoop.makePromise()
executeAndComplete(promise) { try setOption0(option: option, value: value) }
return promise.futureResult
if self.eventLoop.inEventLoop {
return self.eventLoop.makeCompletedFuture(Result { try setOption0(option: option, value: value) })
} else {
return eventLoop.submit { try self.setOption0(option: option, value: value) }
return self.eventLoop.submit { try self.setOption0(option: option, value: value) }
}
}
private func setOption0<Option: ChannelOption>(option: Option, value: Option.Value) throws {
self.eventLoop.assertInEventLoop()
self.eventLoop.preconditionInEventLoop()
guard !self.closed else {
throw ChannelError.ioOnClosedChannel
@ -354,17 +327,15 @@ extension NIOTSConnectionChannel: Channel {
}
public func getOption<Option: ChannelOption>(_ option: Option) -> EventLoopFuture<Option.Value> {
if eventLoop.inEventLoop {
let promise: EventLoopPromise<Option.Value> = eventLoop.makePromise()
executeAndComplete(promise) { try getOption0(option: option) }
return promise.futureResult
if self.eventLoop.inEventLoop {
return self.eventLoop.makeCompletedFuture(Result { try getOption0(option: option) })
} else {
return eventLoop.submit { try self.getOption0(option: option) }
}
}
func getOption0<Option: ChannelOption>(option: Option) throws -> Option.Value {
self.eventLoop.assertInEventLoop()
self.eventLoop.preconditionInEventLoop()
guard !self.closed else {
throw ChannelError.ioOnClosedChannel
@ -891,4 +862,27 @@ fileprivate extension ChannelState where ActiveSubstate == NIOTSConnectionChanne
}
}
}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSConnectionChannel {
internal struct SynchronousOptions: NIOSynchronousChannelOptions {
private let channel: NIOTSConnectionChannel
fileprivate init(channel: NIOTSConnectionChannel) {
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

@ -163,17 +163,15 @@ extension NIOTSListenerChannel: Channel {
}
public func setOption<Option: ChannelOption>(_ option: Option, value: Option.Value) -> EventLoopFuture<Void> {
if eventLoop.inEventLoop {
let promise: EventLoopPromise<Void> = eventLoop.makePromise()
executeAndComplete(promise) { try setOption0(option: option, value: value) }
return promise.futureResult
if self.eventLoop.inEventLoop {
return self.eventLoop.makeCompletedFuture(Result { try setOption0(option: option, value: value) })
} else {
return eventLoop.submit { try self.setOption0(option: option, value: value) }
return self.eventLoop.submit { try self.setOption0(option: option, value: value) }
}
}
private func setOption0<Option: ChannelOption>(option: Option, value: Option.Value) throws {
self.eventLoop.assertInEventLoop()
fileprivate func setOption0<Option: ChannelOption>(option: Option, value: Option.Value) throws {
self.eventLoop.preconditionInEventLoop()
guard !self.closed else {
throw ChannelError.ioOnClosedChannel
@ -207,16 +205,14 @@ extension NIOTSListenerChannel: Channel {
public func getOption<Option: ChannelOption>(_ option: Option) -> EventLoopFuture<Option.Value> {
if eventLoop.inEventLoop {
let promise: EventLoopPromise<Option.Value> = eventLoop.makePromise()
executeAndComplete(promise) { try getOption0(option: option) }
return promise.futureResult
return self.eventLoop.makeCompletedFuture(Result { try getOption0(option: option) })
} else {
return eventLoop.submit { try self.getOption0(option: option) }
}
}
func getOption0<Option: ChannelOption>(option: Option) throws -> Option.Value {
self.eventLoop.assertInEventLoop()
fileprivate func getOption0<Option: ChannelOption>(option: Option) throws -> Option.Value {
self.eventLoop.preconditionInEventLoop()
guard !self.closed else {
throw ChannelError.ioOnClosedChannel
@ -474,4 +470,27 @@ extension NIOTSListenerChannel {
self.becomeActive0(promise: promise)
}
}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSListenerChannel {
internal struct SynchronousOptions: NIOSynchronousChannelOptions {
private let channel: NIOTSListenerChannel
fileprivate init(channel: NIOTSListenerChannel) {
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

@ -785,5 +785,41 @@ class NIOTSConnectionChannelTests: XCTestCase {
XCTAssertTrue(error is NIOTSErrors.InvalidHostname)
}
}
func testSyncOptionsAreSupported() throws {
func testSyncOptions(_ channel: Channel) {
if let sync = channel.syncOptions {
do {
let autoRead = try sync.getOption(ChannelOptions.autoRead)
try sync.setOption(ChannelOptions.autoRead, value: !autoRead)
XCTAssertNotEqual(autoRead, try sync.getOption(ChannelOptions.autoRead))
} catch {
XCTFail("Could not get/set autoRead: \(error)")
}
} else {
XCTFail("\(channel) unexpectedly returned nil syncOptions")
}
}
let listener = try NIOTSListenerBootstrap(group: self.group)
.childChannelInitializer { channel in
testSyncOptions(channel)
return channel.eventLoop.makeSucceededVoidFuture()
}
.bind(host: "localhost", port: 0)
.wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}
let connection = try NIOTSConnectionBootstrap(group: self.group)
.channelInitializer { channel in
testSyncOptions(channel)
return channel.eventLoop.makeSucceededVoidFuture()
}
.connect(to: listener.localAddress!)
.wait()
XCTAssertNoThrow(try connection.close().wait())
}
}
#endif