Skip to content

Commit 36dc063

Browse files
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.
1 parent edd6dee commit 36dc063

4 files changed

Lines changed: 22 additions & 66 deletions

File tree

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ let package = Package(
6262
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.36.0"),
6363
.package(url: "https://github.com/apple/swift-system.git", from: "1.6.4"),
6464
.package(url: "https://github.com/grpc/grpc-swift-2.git", from: "2.3.0"),
65-
.package(url: "https://github.com/grpc/grpc-swift-nio-transport.git", from: "2.4.4"),
65+
.package(url: "https://github.com/grpc/grpc-swift-nio-transport.git", from: "2.9.0"),
6666
.package(url: "https://github.com/grpc/grpc-swift-protobuf.git", from: "2.2.0"),
6767
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.20.1"),
6868
.package(url: "https://github.com/swiftlang/swift-docc-plugin.git", from: "1.1.0"),

Sources/ContainerBuild/Builder.swift

Lines changed: 17 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ import GRPCCore
2424
import GRPCNIOTransportHTTP2
2525
import Logging
2626
import NIO
27-
import NIOCore
28-
import NIOHPACK
29-
import NIOHTTP2
3027
import NIOPosix
3128

3229
public struct Builder: Sendable {
@@ -39,22 +36,27 @@ public struct Builder: Sendable {
3936
let clientTask: Task<Void, any Swift.Error>
4037
let logger: Logger
4138

42-
public init(socket: FileHandle, group: EventLoopGroup, logger: Logger) throws {
39+
public init(socket: FileHandle, group: EventLoopGroup, logger: Logger) async throws {
4340
try socket.setSendBufSize(4 << 20)
4441
try socket.setRecvBufSize(2 << 20)
4542

46-
let channel = try ClientBootstrap(group: group)
47-
.channelInitializer { channel in
48-
channel.eventLoop.makeCompletedFuture(withResultOf: {
49-
try channel.pipeline.syncOperations.addHandler(HTTP2ConnectBufferingHandler())
50-
})
43+
let transport = try await HTTP2ClientTransport.WrappedChannel.wrapping(
44+
config: .defaults,
45+
serviceConfig: .init()
46+
) { configure in
47+
try await withCheckedThrowingContinuation { continuation in
48+
ClientBootstrap(group: group)
49+
.channelInitializer { channel in
50+
configure(channel).map { configured in
51+
continuation.resume(returning: configured)
52+
}
53+
}
54+
.withConnectedSocket(socket.fileDescriptor)
55+
.whenFailure { error in
56+
continuation.resume(throwing: error)
57+
}
5158
}
52-
.withConnectedSocket(socket.fileDescriptor)
53-
.wait()
54-
55-
let transport = HTTP2ClientTransport.WrappedChannel.wrapping(
56-
channel: channel
57-
)
59+
}
5860

5961
let grpcClient = GRPCClient(transport: transport)
6062
self.grpcClient = grpcClient
@@ -429,49 +431,3 @@ extension FileHandle {
429431
}
430432
}
431433
}
432-
433-
/// Buffers incoming bytes until the full gRPC HTTP/2 pipeline is configured, then replays them.
434-
///
435-
/// See the equivalent in Containerization/Vminitd.swift for a full explanation.
436-
private final class HTTP2ConnectBufferingHandler: ChannelDuplexHandler, RemovableChannelHandler {
437-
typealias InboundIn = ByteBuffer
438-
typealias InboundOut = ByteBuffer
439-
typealias OutboundIn = ByteBuffer
440-
typealias OutboundOut = ByteBuffer
441-
442-
private var removalScheduled = false
443-
private var bufferedReads: [NIOAny] = []
444-
445-
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
446-
bufferedReads.append(data)
447-
}
448-
449-
func channelReadComplete(context: ChannelHandlerContext) {}
450-
451-
func flush(context: ChannelHandlerContext) {
452-
if !removalScheduled {
453-
removalScheduled = true
454-
context.eventLoop.assumeIsolatedUnsafeUnchecked().execute {
455-
context.pipeline.syncOperations.removeHandler(self, promise: nil)
456-
}
457-
}
458-
context.flush()
459-
}
460-
461-
func removeHandler(context: ChannelHandlerContext, removalToken: ChannelHandlerContext.RemovalToken) {
462-
var didRead = false
463-
while !bufferedReads.isEmpty {
464-
context.fireChannelRead(bufferedReads.removeFirst())
465-
didRead = true
466-
}
467-
if didRead {
468-
context.fireChannelReadComplete()
469-
}
470-
context.leavePipeline(removalToken: removalToken)
471-
}
472-
473-
func channelInactive(context: ChannelHandlerContext) {
474-
bufferedReads.removeAll()
475-
context.fireChannelInactive()
476-
}
477-
}

Sources/ContainerCommands/BuildCommand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ extension Application {
177177
let fh = try await client.dial(id: "buildkit", port: vsockPort)
178178

179179
let threadGroup: MultiThreadedEventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
180-
let b = try Builder(socket: fh, group: threadGroup, logger: log)
180+
let b = try await Builder(socket: fh, group: threadGroup, logger: log)
181181

182182
// If this call succeeds, then BuildKit is running.
183183
let _ = try await b.info()

0 commit comments

Comments
 (0)