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:
parent
b9fd95b8d5
commit
657537c2cf
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue