Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add alloc tests for alternative stream creation API #451

Merged
merged 2 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import NIOHTTP2
/// Test use only. Allows abstracting over the two multiplexer implementations to write common testing code
internal protocol MultiplexerChannelCreator {
func createStreamChannel(promise: EventLoopPromise<Channel>?, _ streamStateInitializer: @escaping NIOHTTP2Handler.StreamInitializer)
func createStreamChannel(_ initializer: @escaping NIOChannelInitializer) -> EventLoopFuture<Channel>
}

extension HTTP2StreamMultiplexer: MultiplexerChannelCreator { }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2019-2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import NIOCore
import NIOEmbedded
import NIOHPACK
import NIOHTTP1
import NIOHTTP2

final class ServerHandler: ChannelInboundHandler {
typealias InboundIn = HTTP2Frame.FramePayload
typealias OutboundOut = HTTP2Frame.FramePayload

func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let payload = self.unwrapInboundIn(data)
switch payload {
case .headers(let headers) where headers.endStream:
break
case .data(let data) where data.endStream:
break
default:
// Ignore this frame
return
}

// We got END_STREAM. Let's send a response.
let headers = HPACKHeaders([(":status", "200")])
let responseFramePayload = HTTP2Frame.FramePayload.headers(.init(headers: headers, endStream: true))
context.writeAndFlush(self.wrapOutboundOut(responseFramePayload), promise: nil)
}
}


final class ClientHandler: ChannelInboundHandler {
typealias InboundIn = HTTP2Frame.FramePayload
typealias OutboundOut = HTTP2Frame.FramePayload

let dataBlockCount: Int
let dataBlockLengthBytes: Int

init(dataBlockCount: Int, dataBlockLengthBytes: Int) {
self.dataBlockCount = dataBlockCount
self.dataBlockLengthBytes = dataBlockLengthBytes
}

func channelActive(context: ChannelHandlerContext) {
// Send a request.
let headers = HPACKHeaders([(":path", "/"),
(":authority", "localhost"),
(":method", "GET"),
(":scheme", "https")])

if self.dataBlockCount > 0 {
let requestFramePayload = HTTP2Frame.FramePayload.headers(.init(headers: headers, endStream: false))
context.write(self.wrapOutboundOut(requestFramePayload), promise: nil)

let buffer = ByteBuffer(repeating: 0, count: self.dataBlockLengthBytes)

for _ in 0 ..< self.dataBlockCount-1 {
context.write(self.wrapOutboundOut(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer), endStream: false))), promise: nil)
}
context.writeAndFlush(self.wrapOutboundOut(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer), endStream: true))), promise: nil)
} else {
let requestFramePayload = HTTP2Frame.FramePayload.headers(.init(headers: headers, endStream: true))
context.writeAndFlush(self.wrapOutboundOut(requestFramePayload), promise: nil)
}
}
}

func run(identifier: String) {
testRun(identifier: identifier) { clientChannel in
return try! clientChannel.configureHTTP2Pipeline(mode: .client) { channel in
return channel.eventLoop.makeSucceededVoidFuture()
}.wait()
} serverPipelineConfigurator: { serverChannel in
_ = try! serverChannel.configureHTTP2Pipeline(mode: .server) { channel in
return channel.pipeline.addHandler(ServerHandler())
}.wait()
}

testRun(identifier: identifier + "_many", dataBlockCount: 100, dataBlockLengthBytes: 1000) { clientChannel in
return try! clientChannel.configureHTTP2Pipeline(mode: .client) { channel in
return channel.eventLoop.makeSucceededVoidFuture()
}.wait()
} serverPipelineConfigurator: { serverChannel in
_ = try! serverChannel.configureHTTP2Pipeline(mode: .server) { channel in
return channel.pipeline.addHandler(ServerHandler())
}.wait()
}

//
// MARK: - Inline HTTP2 multiplexer tests
testRun(identifier: identifier + "_inline") { clientChannel in
return try! clientChannel.configureHTTP2Pipeline(mode: .client, connectionConfiguration: .init(), streamConfiguration: .init()) { channel in
return channel.eventLoop.makeSucceededVoidFuture()
}.wait()
} serverPipelineConfigurator: { serverChannel in
_ = try! serverChannel.configureHTTP2Pipeline(mode: .server, connectionConfiguration: .init(), streamConfiguration: .init()) { channel in
return channel.pipeline.addHandler(ServerHandler())
}.wait()
}

testRun(identifier: identifier + "_many_inline", dataBlockCount: 100, dataBlockLengthBytes: 1000) { clientChannel in
return try! clientChannel.configureHTTP2Pipeline(mode: .client, connectionConfiguration: .init(), streamConfiguration: .init()) { channel in
return channel.eventLoop.makeSucceededVoidFuture()
}.wait()
} serverPipelineConfigurator: { serverChannel in
_ = try! serverChannel.configureHTTP2Pipeline(mode: .server, connectionConfiguration: .init(), streamConfiguration: .init()) { channel in
return channel.pipeline.addHandler(ServerHandler())
}.wait()
}
}

private func testRun(identifier: String, dataBlockCount: Int = 0, dataBlockLengthBytes: Int = 0, clientPipelineConfigurator: (Channel) throws -> MultiplexerChannelCreator, serverPipelineConfigurator: (Channel) throws -> ()) {
let loop = EmbeddedEventLoop()

measure(identifier: identifier) {
var sumOfStreamIDs = 0

for _ in 0..<1000 {
let clientChannel = EmbeddedChannel(loop: loop)
let serverChannel = EmbeddedChannel(loop: loop)

let clientMultiplexer = try! clientPipelineConfigurator(clientChannel)
try! serverPipelineConfigurator(serverChannel)
try! clientChannel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait()
try! serverChannel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait()

let channelFuture = clientMultiplexer.createStreamChannel { channel in
return channel.pipeline.addHandler(ClientHandler(dataBlockCount: dataBlockCount, dataBlockLengthBytes: dataBlockLengthBytes))
}
clientChannel.embeddedEventLoop.run()
let child = try! channelFuture.wait()
let streamID = try! Int(child.getOption(HTTP2StreamChannelOptions.streamID).wait())

sumOfStreamIDs += streamID
try! interactInMemory(clientChannel, serverChannel)
try! child.closeFuture.wait()

try! clientChannel.close().wait()
try! serverChannel.close().wait()
}

return sumOfStreamIDs
}
}

17 changes: 17 additions & 0 deletions Sources/NIOHTTP2/HTTP2StreamMultiplexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,23 @@ extension HTTP2StreamMultiplexer {
}
}

/// Create a new `Channel` for a new stream initiated by this peer.
///
/// This method is intended for situations where the NIO application is initiating the stream. For clients,
/// this is for all request streams. For servers, this is for pushed streams.
///
/// > Note: Resources for the stream will be freed after it has been closed.
///
/// - Parameters:
/// - streamStateInitializer: A callback that will be invoked to allow you to configure the
/// `ChannelPipeline` for the newly created channel.
/// - Returns: A future for the initialized `Channel`.
public func createStreamChannel(_ initializer: @escaping NIOChannelInitializer) -> EventLoopFuture<Channel> {
let promise = self.channel.eventLoop.makePromise(of: Channel.self)
self.createStreamChannel(promise: promise, initializer)
return promise.futureResult
}

/// Create a new `Channel` for a new stream initiated by this peer.
///
/// This method is intended for situations where the NIO application is initiating the stream. For clients,
Expand Down
4 changes: 4 additions & 0 deletions docker/docker-compose.2204.510.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ services:
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=244050
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1198050
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=889050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation=253050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_inline=251050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_many=1198050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_many_inline=896050
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=37050
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=37050
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form=200050
Expand Down
4 changes: 4 additions & 0 deletions docker/docker-compose.2204.58.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ services:
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=244050
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1198050
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=889050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation=253050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_inline=251050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_many=1198050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_many_inline=896050
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=37050
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=37050
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form=200050
Expand Down
4 changes: 4 additions & 0 deletions docker/docker-compose.2204.59.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ services:
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=244050
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1198050
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=889050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation=253050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_inline=251050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_many=1198050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_many_inline=896050
gjcairo marked this conversation as resolved.
Show resolved Hide resolved
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=37050
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=37050
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form=200050
Expand Down
32 changes: 18 additions & 14 deletions docker/docker-compose.2204.main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,29 @@ services:
test:
image: swift-nio-http2:22.04-main
environment:
- MAX_ALLOCS_ALLOWED_1k_requests_inline_interleaved=35150
- MAX_ALLOCS_ALLOWED_1k_requests_inline_noninterleaved=34100
- MAX_ALLOCS_ALLOWED_1k_requests_interleaved=41150
- MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=40100
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response=288050
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response_inline=273050
- MAX_ALLOCS_ALLOWED_client_server_request_response=257050
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=248050
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1202050
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=893050
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=39050
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=39050
- MAX_ALLOCS_ALLOWED_1k_requests_inline_interleaved=33150
- MAX_ALLOCS_ALLOWED_1k_requests_inline_noninterleaved=32100
- MAX_ALLOCS_ALLOWED_1k_requests_interleaved=39150
- MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=38100
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response=284050
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response_inline=269050
- MAX_ALLOCS_ALLOWED_client_server_request_response=253050
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=244050
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1198050
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=889050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation=253050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_inline=251050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_many=1198050
- MAX_ALLOCS_ALLOWED_client_server_request_response_no_promise_on_channel_creation_many_inline=896050
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=37050
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=37050
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form=200050
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace=200050
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace_from_long_string=300050
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace_from_short_string=200050
- MAX_ALLOCS_ALLOWED_hpack_decoding=5050
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=292650
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent_inline=291750
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=282650
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent_inline=281750
- IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error

shell:
Expand Down