From 320e4a3ab7c4a51a0830d75fa707aed02447422c Mon Sep 17 00:00:00 2001 From: George Barnett Date: Thu, 25 Feb 2021 09:38:26 +0000 Subject: [PATCH] Add sync option support to HTTP2StreamChannel Motivation: SwiftNIO 2.27.0 added support for synchronous channel options allowing callers to get options synchronously -- saving a future allocation -- if they know they're on the correct event loop. 'HTTP2StreamChannel' should support this. Modifications: - Add 'HTTP2StreamChannel.SynchronousOptions' with conformance to 'NIOSynchronousChannelOptions' Result: Callers can get and set options synchronously on 'HTTP2StreamChannel' --- Package.swift | 2 +- Sources/NIOHTTP2/HTTP2StreamChannel.swift | 51 +++++++++++++++---- ...PayloadStreamMultiplexerTests+XCTest.swift | 1 + ...P2FramePayloadStreamMultiplexerTests.swift | 26 ++++++++++ 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/Package.swift b/Package.swift index 1bfac6f4..2e4c6fe6 100644 --- a/Package.swift +++ b/Package.swift @@ -23,7 +23,7 @@ let package = Package( .library(name: "NIOHTTP2", targets: ["NIOHTTP2"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", from: "2.18.0"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.27.0") ], targets: [ .target(name: "NIOHTTP2Server", diff --git a/Sources/NIOHTTP2/HTTP2StreamChannel.swift b/Sources/NIOHTTP2/HTTP2StreamChannel.swift index d51511be..3d0f7b08 100644 --- a/Sources/NIOHTTP2/HTTP2StreamChannel.swift +++ b/Sources/NIOHTTP2/HTTP2StreamChannel.swift @@ -344,31 +344,31 @@ final class HTTP2StreamChannel: Channel, ChannelCore { } func setOption(_ option: Option, value: Option.Value) -> EventLoopFuture { - if eventLoop.inEventLoop { + if self.eventLoop.inEventLoop { do { - return eventLoop.makeSucceededFuture(try setOption0(option, value: value)) + return self.eventLoop.makeSucceededFuture(try self.setOption0(option, value: value)) } catch { - return eventLoop.makeFailedFuture(error) + return self.eventLoop.makeFailedFuture(error) } } else { - return eventLoop.submit { try self.setOption0(option, value: value) } + return self.eventLoop.submit { try self.setOption0(option, value: value) } } } public func getOption(_ option: Option) -> EventLoopFuture { - if eventLoop.inEventLoop { + if self.eventLoop.inEventLoop { do { - return eventLoop.makeSucceededFuture(try getOption0(option)) + return self.eventLoop.makeSucceededFuture(try self.getOption0(option)) } catch { - return eventLoop.makeFailedFuture(error) + return self.eventLoop.makeFailedFuture(error) } } else { - return eventLoop.submit { try self.getOption0(option) } + return self.eventLoop.submit { try self.getOption0(option) } } } private func setOption0(_ option: Option, value: Option.Value) throws { - assert(eventLoop.inEventLoop) + self.eventLoop.preconditionInEventLoop() switch option { case _ as ChannelOptions.Types.AutoReadOption: @@ -379,7 +379,7 @@ final class HTTP2StreamChannel: Channel, ChannelCore { } private func getOption0(_ option: Option) throws -> Option.Value { - assert(eventLoop.inEventLoop) + self.eventLoop.preconditionInEventLoop() switch option { case _ as HTTP2StreamChannelOptions.Types.StreamIDOption: @@ -878,3 +878,34 @@ extension HTTP2StreamChannel { return "HTTP2StreamChannel(streamID: \(String(describing: self.streamID)), isActive: \(self.isActive), isWritable: \(self.isWritable))" } } + +extension HTTP2StreamChannel { + public struct SynchronousOptions: NIOSynchronousChannelOptions { + private let channel: HTTP2StreamChannel + + fileprivate init(channel: HTTP2StreamChannel) { + self.channel = channel + } + + /// Set `option` to `value` on this `Channel`. + /// + /// - Important: Must be called on the `EventLoop` the `Channel` is running on. + @inlinable + public func setOption(_ option: Option, value: Option.Value) throws { + try self.channel.setOption0(option, value: value) + } + + /// Get the value of `option` for this `Channel`. + /// + /// - Important: Must be called on the `EventLoop` the `Channel` is running on. + @inlinable + public func getOption(_ option: Option) throws -> Option.Value { + return try self.channel.getOption0(option) + } + } + + /// Returns a view of the `Channel` exposing synchronous versions of `setOption` and `getOption`. + public final var syncOptions: NIOSynchronousChannelOptions? { + return SynchronousOptions(channel: self) + } +} diff --git a/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests+XCTest.swift b/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests+XCTest.swift index a702a081..5698832c 100644 --- a/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests+XCTest.swift +++ b/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests+XCTest.swift @@ -77,6 +77,7 @@ extension HTTP2FramePayloadStreamMultiplexerTests { ("testReadWhenUsingAutoreadOnChildChannel", testReadWhenUsingAutoreadOnChildChannel), ("testWindowUpdateIsNotEmittedAfterStreamIsClosed", testWindowUpdateIsNotEmittedAfterStreamIsClosed), ("testWindowUpdateIsNotEmittedAfterStreamIsClosedEvenOnLaterFrame", testWindowUpdateIsNotEmittedAfterStreamIsClosedEvenOnLaterFrame), + ("testStreamChannelSupportsSyncOptions", testStreamChannelSupportsSyncOptions), ] } } diff --git a/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests.swift b/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests.swift index e1d86c7a..d34ff3d4 100644 --- a/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests.swift +++ b/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests.swift @@ -1898,4 +1898,30 @@ final class HTTP2FramePayloadStreamMultiplexerTests: XCTestCase { // the stream has closed we don't expect to read anything out. XCTAssertNil(try self.channel.readOutbound(as: HTTP2Frame.self)) } + + func testStreamChannelSupportsSyncOptions() throws { + let multiplexer = HTTP2StreamMultiplexer(mode: .server, channel: self.channel) { channel in + XCTAssert(channel is HTTP2StreamChannel) + if let sync = channel.syncOptions { + do { + let streamID = try sync.getOption(HTTP2StreamChannelOptions.streamID) + XCTAssertEqual(streamID, HTTP2StreamID(1)) + + let autoRead = try sync.getOption(ChannelOptions.autoRead) + try sync.setOption(ChannelOptions.autoRead, value: !autoRead) + XCTAssertNotEqual(autoRead, try sync.getOption(ChannelOptions.autoRead)) + } catch { + XCTFail("Missing StreamID") + } + } else { + XCTFail("syncOptions was nil but should be supported for HTTP2StreamChannel") + } + + return channel.close() + } + XCTAssertNoThrow(try self.channel.pipeline.addHandler(multiplexer).wait()) + + let frame = HTTP2Frame(streamID: HTTP2StreamID(1), payload: .headers(.init(headers: HPACKHeaders()))) + XCTAssertNoThrow(try self.channel.writeInbound(frame)) + } }