Skip to content

Commit 8275314

Browse files
authored
Async stream initializers order matches existing (#415)
Motivation: When adding in the SPI methods for exposing async sequences of HTTP/2 streams we moved the stream initialization to a subtly different location so that it was easier to exfiltrate the outputs of those initialization functions (such as protocol negotiation outputs). In some cases this broke ordering expectations. Modifications: Yield the (optionally wrapped) channels as initializer outputs to the async stream Result: Changes only exist within SPI. Async inbound stream channel initialization now matches previous behavior.
1 parent 92afa4f commit 8275314

12 files changed

+598
-387
lines changed

Sources/NIOHTTP2/HTTP2ChannelHandler+InlineStreamMultiplexer.swift

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,15 @@ extension InlineStreamMultiplexer {
140140
}
141141

142142
extension InlineStreamMultiplexer {
143-
internal func createStreamChannel(promise: EventLoopPromise<Channel>?, _ streamStateInitializer: @escaping (Channel) -> EventLoopFuture<Void>) {
143+
internal func createStreamChannel(promise: EventLoopPromise<Channel>?, _ streamStateInitializer: @escaping NIOChannelInitializer) {
144144
self.commonStreamMultiplexer.createStreamChannel(multiplexer: .inline(self), promise: promise, streamStateInitializer)
145145
}
146146

147-
internal func createStreamChannel(_ streamStateInitializer: @escaping (Channel) -> EventLoopFuture<Void>) -> EventLoopFuture<Channel> {
147+
internal func createStreamChannel(_ streamStateInitializer: @escaping NIOChannelInitializer) -> EventLoopFuture<Channel> {
148148
self.commonStreamMultiplexer.createStreamChannel(multiplexer: .inline(self), streamStateInitializer)
149149
}
150150

151-
internal func createStreamChannel<Output>(_ initializer: @escaping NIOChannelInitializerWithOutput<Output>) -> EventLoopFuture<Output> {
151+
internal func createStreamChannel<Output: Sendable>(_ initializer: @escaping NIOChannelInitializerWithOutput<Output>) -> EventLoopFuture<Output> {
152152
self.commonStreamMultiplexer.createStreamChannel(multiplexer: .inline(self), initializer)
153153
}
154154
}
@@ -207,7 +207,7 @@ extension NIOHTTP2Handler {
207207
}
208208

209209
extension InlineStreamMultiplexer {
210-
func setChannelContinuation(_ streamChannels: any ChannelContinuation) {
210+
func setChannelContinuation(_ streamChannels: any AnyContinuation) {
211211
self.commonStreamMultiplexer.setChannelContinuation(streamChannels)
212212
}
213213
}
@@ -224,46 +224,23 @@ extension NIOHTTP2Handler {
224224
/// and `IOData`.
225225
///
226226
/// Outbound stream channel objects are initialized upon creation using the supplied `streamStateInitializer` which returns a type
227-
/// `OutboundStreamOutput`. This type may be `HTTP2Frame` or changed to any other type.
227+
/// `Output`. This type may be `HTTP2Frame` or changed to any other type.
228228
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
229229
@_spi(AsyncChannel)
230230
public struct AsyncStreamMultiplexer<InboundStreamOutput> {
231231
private let inlineStreamMultiplexer: InlineStreamMultiplexer
232232
public let inbound: NIOHTTP2InboundStreamChannels<InboundStreamOutput>
233233

234234
// Cannot be created by users.
235-
internal init(_ inlineStreamMultiplexer: InlineStreamMultiplexer, continuation: any ChannelContinuation, inboundStreamChannels: NIOHTTP2InboundStreamChannels<InboundStreamOutput>) {
235+
internal init(_ inlineStreamMultiplexer: InlineStreamMultiplexer, continuation: any AnyContinuation, inboundStreamChannels: NIOHTTP2InboundStreamChannels<InboundStreamOutput>) {
236236
self.inlineStreamMultiplexer = inlineStreamMultiplexer
237237
self.inlineStreamMultiplexer.setChannelContinuation(continuation)
238238
self.inbound = inboundStreamChannels
239239
}
240240

241241
/// Create a stream channel initialized with the provided closure
242-
public func createStreamChannel<OutboundStreamOutput>(_ initializer: @escaping NIOChannelInitializerWithOutput<OutboundStreamOutput>) async throws -> OutboundStreamOutput {
242+
public func createStreamChannel<Output: Sendable>(_ initializer: @escaping NIOChannelInitializerWithOutput<Output>) async throws -> Output {
243243
return try await self.inlineStreamMultiplexer.createStreamChannel(initializer).get()
244244
}
245-
246-
247-
/// Create a stream channel initialized with the provided closure and return it wrapped within a `NIOAsyncChannel`.
248-
///
249-
/// - Parameters:
250-
/// - configuration: Configuration for the ``NIOAsyncChannel`` wrapping the HTTP/2 stream channel.
251-
/// - initializer: A callback that will be invoked to allow you to configure the
252-
/// `ChannelPipeline` for the newly created channel.
253-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
254-
@_spi(AsyncChannel)
255-
public func createStreamChannel<Inbound, Outbound>(
256-
configuration: NIOAsyncChannel<Inbound, Outbound>.Configuration = .init(),
257-
initializer: @escaping NIOChannelInitializer
258-
) async throws -> NIOAsyncChannel<Inbound, Outbound> {
259-
return try await self.createStreamChannel { channel in
260-
initializer(channel).flatMapThrowing { _ in
261-
return try NIOAsyncChannel(
262-
synchronouslyWrapping: channel,
263-
configuration: configuration
264-
)
265-
}
266-
}
267-
}
268245
}
269246
}

Sources/NIOHTTP2/HTTP2ChannelHandler.swift

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,15 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler {
120120
private enum InboundStreamMultiplexerState {
121121
case uninitializedLegacy
122122
case uninitializedInline(StreamConfiguration, StreamInitializer, NIOHTTP2StreamDelegate?)
123+
case uninitializedAsync(StreamConfiguration, StreamInitializerWithAnyOutput, NIOHTTP2StreamDelegate?)
123124
case initialized(InboundStreamMultiplexer)
124125
case deinitialized
125126

126127
internal var multiplexer: InboundStreamMultiplexer? {
127128
switch self {
128129
case .initialized(let inboundStreamMultiplexer):
129130
return inboundStreamMultiplexer
130-
case .uninitializedLegacy, .uninitializedInline, .deinitialized:
131+
case .uninitializedLegacy, .uninitializedInline, .uninitializedAsync, .deinitialized:
131132
return nil
132133
}
133134
}
@@ -153,6 +154,20 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler {
153154
)
154155
))
155156

157+
case .uninitializedAsync(let streamConfiguration, let inboundStreamInitializer, let streamDelegate):
158+
self = .initialized(.inline(
159+
InlineStreamMultiplexer(
160+
context: context,
161+
outboundView: .init(http2Handler: http2Handler),
162+
mode: mode,
163+
inboundStreamStateInitializer: .returnsAny(inboundStreamInitializer),
164+
targetWindowSize: max(0, min(streamConfiguration.targetWindowSize, Int(Int32.max))),
165+
streamChannelOutboundBytesHighWatermark: streamConfiguration.outboundBufferSizeHighWatermark,
166+
streamChannelOutboundBytesLowWatermark: streamConfiguration.outboundBufferSizeLowWatermark,
167+
streamDelegate: streamDelegate
168+
)
169+
))
170+
156171
case .initialized:
157172
break //no-op
158173
}
@@ -989,9 +1004,13 @@ extension NIOHTTP2Handler {
9891004
#if swift(>=5.7)
9901005
/// The type of all `inboundStreamInitializer` callbacks which do not need to return data.
9911006
public typealias StreamInitializer = NIOChannelInitializer
1007+
/// The type of NIO Channel initializer callbacks which need to return untyped data.
1008+
internal typealias StreamInitializerWithAnyOutput = @Sendable (Channel) -> EventLoopFuture<any Sendable>
9921009
#else
9931010
/// The type of all `inboundStreamInitializer` callbacks which need to return data.
9941011
public typealias StreamInitializer = NIOChannelInitializer
1012+
/// The type of NIO Channel initializer callbacks which need to return untyped data.
1013+
internal typealias StreamInitializerWithAnyOutput = (Channel) -> EventLoopFuture<any Sendable>
9951014
#endif
9961015

9971016
/// Creates a new ``NIOHTTP2Handler`` with a local multiplexer. (i.e. using
@@ -1014,17 +1033,39 @@ extension NIOHTTP2Handler {
10141033
streamDelegate: NIOHTTP2StreamDelegate? = nil,
10151034
inboundStreamInitializer: @escaping StreamInitializer
10161035
) {
1017-
self.init(mode: mode,
1018-
eventLoop: eventLoop,
1019-
initialSettings: connectionConfiguration.initialSettings,
1020-
headerBlockValidation: connectionConfiguration.headerBlockValidation,
1021-
contentLengthValidation: connectionConfiguration.contentLengthValidation,
1022-
maximumSequentialEmptyDataFrames: connectionConfiguration.maximumSequentialEmptyDataFrames,
1023-
maximumBufferedControlFrames: connectionConfiguration.maximumBufferedControlFrames
1036+
self.init(
1037+
mode: mode,
1038+
eventLoop: eventLoop,
1039+
initialSettings: connectionConfiguration.initialSettings,
1040+
headerBlockValidation: connectionConfiguration.headerBlockValidation,
1041+
contentLengthValidation: connectionConfiguration.contentLengthValidation,
1042+
maximumSequentialEmptyDataFrames: connectionConfiguration.maximumSequentialEmptyDataFrames,
1043+
maximumBufferedControlFrames: connectionConfiguration.maximumBufferedControlFrames
10241044
)
1045+
10251046
self.inboundStreamMultiplexerState = .uninitializedInline(streamConfiguration, inboundStreamInitializer, streamDelegate)
10261047
}
10271048

1049+
internal convenience init(
1050+
mode: ParserMode,
1051+
eventLoop: EventLoop,
1052+
connectionConfiguration: ConnectionConfiguration = .init(),
1053+
streamConfiguration: StreamConfiguration = .init(),
1054+
streamDelegate: NIOHTTP2StreamDelegate? = nil,
1055+
inboundStreamInitializerWithAnyOutput: @escaping StreamInitializerWithAnyOutput
1056+
) {
1057+
self.init(
1058+
mode: mode,
1059+
eventLoop: eventLoop,
1060+
initialSettings: connectionConfiguration.initialSettings,
1061+
headerBlockValidation: connectionConfiguration.headerBlockValidation,
1062+
contentLengthValidation: connectionConfiguration.contentLengthValidation,
1063+
maximumSequentialEmptyDataFrames: connectionConfiguration.maximumSequentialEmptyDataFrames,
1064+
maximumBufferedControlFrames: connectionConfiguration.maximumBufferedControlFrames
1065+
)
1066+
self.inboundStreamMultiplexerState = .uninitializedAsync(streamConfiguration, inboundStreamInitializerWithAnyOutput, streamDelegate)
1067+
}
1068+
10281069
/// Connection-level configuration.
10291070
///
10301071
/// The settings that will be used when establishing the connection. These will be sent to the peer as part of the
@@ -1101,7 +1142,7 @@ extension NIOHTTP2Handler {
11011142
}
11021143

11031144
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
1104-
internal func syncAsyncStreamMultiplexer<Output>(continuation: any ChannelContinuation, inboundStreamChannels: NIOHTTP2InboundStreamChannels<Output>) throws -> AsyncStreamMultiplexer<Output> {
1145+
internal func syncAsyncStreamMultiplexer<Output: Sendable>(continuation: any AnyContinuation, inboundStreamChannels: NIOHTTP2InboundStreamChannels<Output>) throws -> AsyncStreamMultiplexer<Output> {
11051146
self.eventLoop!.preconditionInEventLoop()
11061147

11071148
switch self.inboundStreamMultiplexer {

0 commit comments

Comments
 (0)