Upgrade grpc-swift-nio-transport to 2.9.0 and remove HTTP2ConnectBuff… (#1790)

- Fixes #1789.
- Release 2.9.0 of `grpc-swift-nio-transport` fixes
  an HTTP/2 initialization race where the server could
  send SETTINGS before gRPC handlers are added to
  the pipeline, causing the client to hang. The new
  `WrappedChannel.wrapping(config:serviceConfig:makeChannel:)`
  API calls `configure(channel)` inside the channel
  initializer, ensuring the pipeline is set up before any inbound
  bytes arrive. This eliminates the need for the custom
  `HTTP2ConnectBufferingHandler` workaround.
This commit is contained in:
adityabagchi24
2026-07-07 10:23:50 -07:00
committed by GitHub
parent edd6dee2db
commit 36dc063902
4 changed files with 22 additions and 66 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
{
"originHash" : "5fed298f6f504f48c76238742b1bd097d525322e552bef8dd4b544b8f82c39e8",
"originHash" : "52d35c69b016703412ccbf3c76706f3158921c94200e15dd5e93251c08ce55a0",
"pins" : [
{
"identity" : "async-http-client",
@@ -33,8 +33,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/grpc/grpc-swift-nio-transport.git",
"state" : {
"revision" : "f37e0c2d293cea668b11e10e1fb1c24cb40781ff",
"version" : "2.4.4"
"revision" : "2ca31f06658ed288a2560e23ad649acbb3d6b3a3",
"version" : "2.9.0"
}
},
{
+1 -1
View File
@@ -62,7 +62,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.36.0"),
.package(url: "https://github.com/apple/swift-system.git", from: "1.6.4"),
.package(url: "https://github.com/grpc/grpc-swift-2.git", from: "2.3.0"),
.package(url: "https://github.com/grpc/grpc-swift-nio-transport.git", from: "2.4.4"),
.package(url: "https://github.com/grpc/grpc-swift-nio-transport.git", from: "2.9.0"),
.package(url: "https://github.com/grpc/grpc-swift-protobuf.git", from: "2.2.0"),
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.20.1"),
.package(url: "https://github.com/swiftlang/swift-docc-plugin.git", from: "1.1.0"),
+17 -61
View File
@@ -24,9 +24,6 @@ import GRPCCore
import GRPCNIOTransportHTTP2
import Logging
import NIO
import NIOCore
import NIOHPACK
import NIOHTTP2
import NIOPosix
public struct Builder: Sendable {
@@ -39,22 +36,27 @@ public struct Builder: Sendable {
let clientTask: Task<Void, any Swift.Error>
let logger: Logger
public init(socket: FileHandle, group: EventLoopGroup, logger: Logger) throws {
public init(socket: FileHandle, group: EventLoopGroup, logger: Logger) async throws {
try socket.setSendBufSize(4 << 20)
try socket.setRecvBufSize(2 << 20)
let channel = try ClientBootstrap(group: group)
.channelInitializer { channel in
channel.eventLoop.makeCompletedFuture(withResultOf: {
try channel.pipeline.syncOperations.addHandler(HTTP2ConnectBufferingHandler())
})
let transport = try await HTTP2ClientTransport.WrappedChannel.wrapping(
config: .defaults,
serviceConfig: .init()
) { configure in
try await withCheckedThrowingContinuation { continuation in
ClientBootstrap(group: group)
.channelInitializer { channel in
configure(channel).map { configured in
continuation.resume(returning: configured)
}
}
.withConnectedSocket(socket.fileDescriptor)
.whenFailure { error in
continuation.resume(throwing: error)
}
}
.withConnectedSocket(socket.fileDescriptor)
.wait()
let transport = HTTP2ClientTransport.WrappedChannel.wrapping(
channel: channel
)
}
let grpcClient = GRPCClient(transport: transport)
self.grpcClient = grpcClient
@@ -429,49 +431,3 @@ extension FileHandle {
}
}
}
/// Buffers incoming bytes until the full gRPC HTTP/2 pipeline is configured, then replays them.
///
/// See the equivalent in Containerization/Vminitd.swift for a full explanation.
private final class HTTP2ConnectBufferingHandler: ChannelDuplexHandler, RemovableChannelHandler {
typealias InboundIn = ByteBuffer
typealias InboundOut = ByteBuffer
typealias OutboundIn = ByteBuffer
typealias OutboundOut = ByteBuffer
private var removalScheduled = false
private var bufferedReads: [NIOAny] = []
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
bufferedReads.append(data)
}
func channelReadComplete(context: ChannelHandlerContext) {}
func flush(context: ChannelHandlerContext) {
if !removalScheduled {
removalScheduled = true
context.eventLoop.assumeIsolatedUnsafeUnchecked().execute {
context.pipeline.syncOperations.removeHandler(self, promise: nil)
}
}
context.flush()
}
func removeHandler(context: ChannelHandlerContext, removalToken: ChannelHandlerContext.RemovalToken) {
var didRead = false
while !bufferedReads.isEmpty {
context.fireChannelRead(bufferedReads.removeFirst())
didRead = true
}
if didRead {
context.fireChannelReadComplete()
}
context.leavePipeline(removalToken: removalToken)
}
func channelInactive(context: ChannelHandlerContext) {
bufferedReads.removeAll()
context.fireChannelInactive()
}
}
+1 -1
View File
@@ -177,7 +177,7 @@ extension Application {
let fh = try await client.dial(id: "buildkit", port: vsockPort)
let threadGroup: MultiThreadedEventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
let b = try Builder(socket: fh, group: threadGroup, logger: log)
let b = try await Builder(socket: fh, group: threadGroup, logger: log)
// If this call succeeds, then BuildKit is running.
let _ = try await b.info()