Skip to content

Commit

Permalink
Add sync option support to HTTP2StreamChannel
Browse files Browse the repository at this point in the history
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'
  • Loading branch information
glbrntt committed Mar 15, 2021
1 parent ce6f6be commit 320e4a3
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 11 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
51 changes: 41 additions & 10 deletions Sources/NIOHTTP2/HTTP2StreamChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -344,31 +344,31 @@ final class HTTP2StreamChannel: Channel, ChannelCore {
}

func setOption<Option: ChannelOption>(_ option: Option, value: Option.Value) -> EventLoopFuture<Void> {
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: ChannelOption>(_ option: Option) -> EventLoopFuture<Option.Value> {
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: ChannelOption>(_ option: Option, value: Option.Value) throws {
assert(eventLoop.inEventLoop)
self.eventLoop.preconditionInEventLoop()

switch option {
case _ as ChannelOptions.Types.AutoReadOption:
Expand All @@ -379,7 +379,7 @@ final class HTTP2StreamChannel: Channel, ChannelCore {
}

private func getOption0<Option: ChannelOption>(_ option: Option) throws -> Option.Value {
assert(eventLoop.inEventLoop)
self.eventLoop.preconditionInEventLoop()

switch option {
case _ as HTTP2StreamChannelOptions.Types.StreamIDOption:
Expand Down Expand Up @@ -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: ChannelOption>(_ 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: ChannelOption>(_ 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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ extension HTTP2FramePayloadStreamMultiplexerTests {
("testReadWhenUsingAutoreadOnChildChannel", testReadWhenUsingAutoreadOnChildChannel),
("testWindowUpdateIsNotEmittedAfterStreamIsClosed", testWindowUpdateIsNotEmittedAfterStreamIsClosed),
("testWindowUpdateIsNotEmittedAfterStreamIsClosedEvenOnLaterFrame", testWindowUpdateIsNotEmittedAfterStreamIsClosedEvenOnLaterFrame),
("testStreamChannelSupportsSyncOptions", testStreamChannelSupportsSyncOptions),
]
}
}
Expand Down
26 changes: 26 additions & 0 deletions Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

0 comments on commit 320e4a3

Please sign in to comment.